Bug 1426063 - Add PLATFORM built-in to Fluent in Gecko. r?pike draft
authorZibi Braniecki <zbraniecki@mozilla.com>
Wed, 20 Dec 2017 14:25:10 -0800
changeset 753625 ed8bb7b93d9a9adc362c2751333ee2270dd3cbff
parent 753624 e085978037f2e483e076161fd422c6bf97a34409
push id98613
push userbmo:gandalf@aviary.pl
push dateSun, 11 Feb 2018 04:53:18 +0000
reviewerspike
bugs1426063
milestone60.0a1
Bug 1426063 - Add PLATFORM built-in to Fluent in Gecko. r?pike In order to allow localizations to produce different string depending on the platform, we need to add a Gecko-specific built-in function to the MessageContext. I'm explicitly listing the variables which we pass, rather than just passing the value in order to ensure we don't start returning values we didn't plan for in case the AppConstants.platform gets updated. MozReview-Commit-ID: 1KZ6bf6zIY2
intl/l10n/L10nRegistry.jsm
intl/l10n/test/test_localization.js
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -1,8 +1,9 @@
+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
 const { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm', {});
 const { MessageContext } = ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
 Components.utils.importGlobalProperties(["fetch"]); /* globals fetch */
 
 /**
  * L10nRegistry is a localization resource management system for Gecko.
  *
  * It manages the list of resource sources provided with the app and allows
@@ -213,16 +214,38 @@ async function* generateContextsForLocal
     } else {
       // otherwise recursively load another generator that walks over the
       // partially resolved list of sources.
       yield * generateContextsForLocale(locale, sourcesOrder, resourceIds, order);
     }
   }
 }
 
+const  MSG_CONTEXT_OPTIONS = {
+  functions: {
+    /**
+     * PLATFORM is a built-in allowing localizers to differentiate message
+     * variants depending on the target platform.
+     */
+    PLATFORM: () => {
+      switch (AppConstants.platform) {
+        case "linux":
+        case "android":
+          return AppConstants.platform;
+        case "win":
+          return "windows";
+        case "macosx":
+          return "macos";
+        default:
+          return "other";
+      }
+    }
+  }
+}
+
 /**
  * Generates a single MessageContext by loading all resources
  * from the listed sources for a given locale.
  *
  * The function casts all error cases into a Promise that resolves with
  * value `null`.
  * This allows the caller to be an async generator without using
  * try/catch clauses.
@@ -239,17 +262,17 @@ function generateContext(locale, sources
   }
 
   const fetchPromises = resourceIds.map((resourceId, i) => {
     return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
   });
 
   const ctxPromise = Promise.all(fetchPromises).then(
     dataSets => {
-      const ctx = new MessageContext(locale);
+      const ctx = new MessageContext(locale, MSG_CONTEXT_OPTIONS);
       for (const data of dataSets) {
         if (data === null) {
           return null;
         }
         ctx.addMessages(data);
       }
       return ctx;
     },
--- a/intl/l10n/test/test_localization.js
+++ b/intl/l10n/test/test_localization.js
@@ -1,11 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
 const { Localization } = ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 
 add_task(function test_methods_presence() {
   equal(typeof Localization.prototype.formatValues, "function");
   equal(typeof Localization.prototype.formatMessages, "function");
   equal(typeof Localization.prototype.formatValue, "function");
 });
 
@@ -25,17 +26,17 @@ add_task(async function test_methods_cal
 
   L10nRegistry.load = async function(url) {
     return fs[url];
   }
 
   const source = new FileSource('test', ['de', 'en-US'], '/localization/{locale}');
   L10nRegistry.registerSource(source);
 
-  async function * generateMessages(resIds) {
+  async function* generateMessages(resIds) {
     yield * await L10nRegistry.generateContexts(['de', 'en-US'], resIds);
   }
 
   const l10n = new Localization([
     '/browser/menu.ftl'
   ], generateMessages);
 
   let values = await l10n.formatValues([['key'], ['key2']]);
@@ -43,8 +44,52 @@ add_task(async function test_methods_cal
   equal(values[0], '[de] Value2');
   equal(values[1], '[en] Value3');
 
   L10nRegistry.sources.clear();
   L10nRegistry.load = originalLoad;
   LocaleService.setRequestedLocales(originalRequested);
 });
 
+add_task(async function test_builtins() {
+  const { L10nRegistry, FileSource } =
+    Components.utils.import("resource://gre/modules/L10nRegistry.jsm", {});
+
+  const known_platforms = {
+    'linux': 'linux',
+    'win': 'windows',
+    'macosx': 'macos',
+    'android': 'android',
+  };
+
+  const fs = {
+    '/localization/en-US/test.ftl': `
+key = { PLATFORM() ->
+        ${ Object.values(known_platforms).map(
+              name => `      [${ name }] ${ name.toUpperCase() } Value\n`).join('') }
+       *[other] OTHER Value
+    }`,
+  };
+  const originalLoad = L10nRegistry.load;
+
+  L10nRegistry.load = async function(url) {
+    return fs[url];
+  }
+
+  const source = new FileSource('test', ['en-US'], '/localization/{locale}');
+  L10nRegistry.registerSource(source);
+
+  async function* generateMessages(resIds) {
+    yield * await L10nRegistry.generateContexts(['en-US'], resIds);
+  }
+
+  const l10n = new Localization([
+    '/test.ftl'
+  ], generateMessages);
+
+  let values = await l10n.formatValues([['key']]);
+
+  ok(values[0].includes(
+    `${ known_platforms[AppConstants.platform].toUpperCase() } Value`));
+
+  L10nRegistry.sources.clear();
+  L10nRegistry.load = originalLoad;
+});