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
--- 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;
+});