Bug 1558602 - Allow DocumentL10n to use LocalizationSync. r=smaug,Pike

Differential Revision: https://phabricator.services.mozilla.com/D34584

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Zibi Braniecki 2019-07-01 17:56:57 +00:00
parent 3cc10e36db
commit b0ba25f8b9
21 changed files with 510 additions and 84 deletions

View file

@ -9,8 +9,6 @@ var EXPORTED_SYMBOLS = ["AboutLoginsParent"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "E10SUtils",
"resource://gre/modules/E10SUtils.jsm");
ChromeUtils.defineModuleGetter(this, "Localization",
"resource://gre/modules/Localization.jsm");
ChromeUtils.defineModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
ChromeUtils.defineModuleGetter(this, "MigrationUtils",

View file

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Localization} = ChromeUtils.import("resource://gre/modules/Localization.jsm");
const {FxAccountsConfig} = ChromeUtils.import("resource://gre/modules/FxAccountsConfig.jsm");
const {AttributionCode} = ChromeUtils.import("resource:///modules/AttributionCode.jsm");
const {AddonRepository} = ChromeUtils.import("resource://gre/modules/addons/AddonRepository.jsm");

View file

@ -13,7 +13,6 @@ var {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
var {TransientPrefs} = ChromeUtils.import("resource:///modules/TransientPrefs.jsm");
var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var {L10nRegistry} = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm");
var {Localization} = ChromeUtils.import("resource://gre/modules/Localization.jsm");
var {HomePage} = ChromeUtils.import("resource:///modules/HomePage.jsm");
ChromeUtils.defineModuleGetter(this, "CloudStorage",
"resource://gre/modules/CloudStorage.jsm");

View file

@ -9,7 +9,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
AppConstants: "resource://gre/modules/AppConstants.jsm",
Localization: "resource://gre/modules/Localization.jsm",
});
/**

View file

@ -309,13 +309,54 @@ already_AddRefed<Promise> DOMLocalization::TranslateElements(
return nullptr;
}
if (mIsSync) {
nsTArray<JS::Value> jsKeys;
SequenceRooter<JS::Value> keysRooter(cx, &jsKeys);
for (auto& key : l10nKeys) {
JS::RootedValue jsKey(cx);
if (!ToJSValue(cx, key, &jsKey)) {
aRv.NoteJSContextException(cx);
return nullptr;
}
jsKeys.AppendElement(jsKey);
}
nsTArray<JS::Value> messages;
SequenceRooter<JS::Value> messagesRooter(cx, &messages);
mLocalization->FormatMessagesSync(jsKeys, messages);
nsTArray<L10nMessage> l10nData;
SequenceRooter<L10nMessage> l10nDataRooter(cx, &l10nData);
for (auto& msg : messages) {
JS::Rooted<JS::Value> rootedMsg(cx);
rootedMsg.set(msg);
L10nMessage* slotPtr = l10nData.AppendElement(mozilla::fallible);
if (!slotPtr) {
promise->MaybeRejectWithUndefined();
return MaybeWrapPromise(promise);
}
if (!slotPtr->Init(cx, rootedMsg)) {
promise->MaybeRejectWithUndefined();
return MaybeWrapPromise(promise);
}
}
ApplyTranslations(domElements, l10nData, aRv);
if (NS_WARN_IF(aRv.Failed())) {
promise->MaybeRejectWithUndefined();
return MaybeWrapPromise(promise);
}
promise->MaybeResolveWithUndefined();
} else {
RefPtr<Promise> callbackResult = FormatMessages(cx, l10nKeys, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nativeHandler->SetReturnValuePromise(promise);
callbackResult->AppendNativeHandler(nativeHandler);
}
return MaybeWrapPromise(promise);
}

View file

@ -34,6 +34,11 @@ DocumentL10n::DocumentL10n(Document* aDocument)
mDocument(aDocument),
mState(DocumentL10nState::Initialized) {
mContentSink = do_QueryInterface(aDocument->GetCurrentContentSink());
Element* elem = mDocument->GetDocumentElement();
if (elem) {
mIsSync = elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nsync);
}
}
void DocumentL10n::Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv) {
@ -133,6 +138,14 @@ void DocumentL10n::InitialDocumentTranslationCompleted() {
if (mContentSink) {
mContentSink->InitialDocumentTranslationCompleted();
}
// If sync was true, we want to change the state of
// mozILocalization to async now.
if (mIsSync) {
mIsSync = false;
mLocalization->SetIsSync(mIsSync);
}
}
Promise* DocumentL10n::Ready() { return mReady; }

View file

@ -36,5 +36,6 @@
[document_l10n/test_docl10n_initialize_after_parse.xul]
[document_l10n/test_docl10n.xhtml]
[document_l10n/test_docl10n.html]
[document_l10n/test_docl10n_sync.html]
[document_l10n/test_docl10n_ready_rejected.html]
[document_l10n/test_docl10n_removeResourceIds.html]

View file

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html data-l10n-sync>
<head>
<meta charset="utf-8">
<title>Test DocumentL10n in HTML environment</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="localization" href="crashreporter/aboutcrashes.ftl"/>
<script type="application/javascript">
"use strict";
SimpleTest.waitForExplicitFinish();
document.addEventListener("DOMContentLoaded", async function() {
await document.l10n.ready;
// Test for initial localization applied.
let desc = document.getElementById("main-desc");
is(desc.textContent.length > 0, true);
// Test for manual value formatting.
let msg = await document.l10n.formatValue("id-heading");
is(msg.length > 0, true);
// Test for mutations applied.
let verifyL10n = () => {
if (label.textContent.length > 0) {
window.removeEventListener("MozAfterPaint", verifyL10n);
SimpleTest.finish();
}
};
window.addEventListener("MozAfterPaint", verifyL10n);
let label = document.getElementById("label1");
document.l10n.setAttributes(
label,
"date-crashed-heading",
{
name: "John",
}
);
// Test for l10n.getAttributes
let l10nArgs = document.l10n.getAttributes(label);
is(l10nArgs.id, "date-crashed-heading");
is(l10nArgs.args.name, "John");
}, { once: true});
</script>
</head>
<body>
<h1 id="main-desc" data-l10n-id="crash-reports-title"></h1>
<p id="label1"></p>
</body>
</html>

View file

@ -81,7 +81,7 @@ const isParentProcess = appinfo.processType === appinfo.PROCESS_TYPE_DEFAULT;
*
* Notice: L10nRegistry is primarily an asynchronous API, but
* it does provide a synchronous version of it's main method
* for use by the `LocalizationSync` class.
* for use by the `Localization` class when in `sync` state.
* This API should be only used in very specialized cases and
* the uses should be reviewed by the toolkit owner/peer.
*/

View file

@ -34,14 +34,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Localization)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
Localization::Localization(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {}
Localization::Localization(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal), mIsSync(false) {}
void Localization::Init(nsTArray<nsString>& aResourceIds, ErrorResult& aRv) {
nsCOMPtr<mozILocalizationJSM> jsm =
do_ImportModule("resource://gre/modules/Localization.jsm");
MOZ_RELEASE_ASSERT(jsm);
Unused << jsm->GetLocalization(aResourceIds, getter_AddRefs(mLocalization));
Unused << jsm->GetLocalization(aResourceIds, mIsSync,
getter_AddRefs(mLocalization));
MOZ_RELEASE_ASSERT(mLocalization);
RegisterObservers();

View file

@ -74,6 +74,7 @@ class Localization : public nsIObserver,
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<mozILocalization> mLocalization;
bool mIsSync;
};
} // namespace intl

View file

@ -212,20 +212,28 @@ function maybeReportErrorToGecko(error) {
class Localization {
/**
* @param {Array<String>} resourceIds - List of resource IDs
* @param {Function} generateBundles - Function that returns a
* @param {Function} generateBundles - Function that returns an async
* generator over FluentBundles
* @param {Function} generateBundlesSync - Function that returns a sync
* generator over FluentBundles
*
* @returns {Localization}
*/
constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
constructor(resourceIds = [], sync = false, generateBundles = defaultGenerateBundles, generateBundlesSync = defaultGenerateBundlesSync) {
this.isSync = sync;
this.resourceIds = resourceIds;
this.generateBundles = generateBundles;
this.generateBundlesSync = generateBundlesSync;
this.onChange(true);
}
cached(iterable) {
if (this.isSync) {
return CachedSyncIterable.from(iterable);
} else {
return CachedAsyncIterable.from(iterable);
}
}
/**
* @param {Array<String>} resourceIds - List of resource IDs
@ -280,6 +288,46 @@ class Localization {
return translations;
}
/**
* Format translations and handle fallback if needed.
*
* Format translations for `keys` from `FluentBundle` instances on this
* Localization. In case of errors, fetch the next context in the
* fallback chain.
*
* @param {Array<Object>} keys - Translation keys to format.
* @param {Function} method - Formatting function.
* @returns {Array<string|Object>}
* @private
*/
formatWithFallbackSync(keys, method) {
if (!this.isSync) {
throw new Error("Can't use sync formatWithFallback when state is async.");
}
const translations = new Array(keys.length);
let hasAtLeastOneBundle = false;
for (const bundle of this.bundles) {
hasAtLeastOneBundle = true;
const missingIds = keysFromBundle(method, bundle, keys, translations);
if (missingIds.size === 0) {
break;
}
const locale = bundle.locales[0];
const ids = Array.from(missingIds).join(", ");
maybeReportErrorToGecko(`[fluent] Missing translations in ${locale}: ${ids}.`);
}
if (!hasAtLeastOneBundle) {
maybeReportErrorToGecko(`[fluent] Request for keys failed because no resource bundles got generated.\n keys: ${JSON.stringify(keys)}.\n resourceIds: ${JSON.stringify(this.resourceIds)}.`);
}
return translations;
}
/**
* Format translations into {value, attributes} objects.
*
@ -300,7 +348,7 @@ class Localization {
* // }
* // ]
*
* Returns a Promise resolving to an array of the translation strings.
* Returns a Promise resolving to an array of the translation messages.
*
* @param {Array<Object>} keys
* @returns {Promise<Array<{value: string, attributes: Object}>>}
@ -310,6 +358,19 @@ class Localization {
return this.formatWithFallback(keys, messageFromBundle);
}
/**
* Sync version of `formatMessages`.
*
* Returns an array of the translation messages.
*
* @param {Array<Object>} keys
* @returns {Array<{value: string, attributes: Object}>}
* @private
*/
formatMessagesSync(keys) {
return this.formatWithFallbackSync(keys, messageFromBundle);
}
/**
* Retrieve translations corresponding to the passed keys.
*
@ -333,6 +394,19 @@ class Localization {
return this.formatWithFallback(keys, valueFromBundle);
}
/**
* Sync version of `formatValues`.
*
* Returns an array of the translation strings.
*
* @param {Array<Object>} keys
* @returns {Array<string>}
* @private
*/
formatValuesSync(keys) {
return this.formatWithFallbackSync(keys, valueFromBundle);
}
/**
* Retrieve the translation corresponding to the `id` identifier.
*
@ -345,7 +419,7 @@ class Localization {
*
* // 'Hello, world!'
*
* Returns a Promise resolving to the translation string.
* Returns a Promise resolving to a translation string.
*
* Use this sparingly for one-off messages which don't need to be
* retranslated when the user changes their language preferences, e.g. in
@ -360,6 +434,20 @@ class Localization {
return val;
}
/**
* Sync version of `formatValue`.
*
* Returns a translation string.
*
* @param {Array<Object>} keys
* @returns {string>}
* @private
*/
formatValueSync(id, args) {
const [val] = this.formatValuesSync([{id, args}]);
return val;
}
/**
* Register weak observers on events that will trigger cache invalidation
*/
@ -398,8 +486,8 @@ class Localization {
* @param {bool} eager - whether the I/O for new context should begin eagerly
*/
onChange(eager = false) {
this.bundles = this.cached(
this.generateBundles(this.resourceIds));
let generateMessages = this.isSync ? this.generateBundlesSync : this.generateBundles;
this.bundles = this.cached(generateMessages(this.resourceIds));
if (eager) {
// If the first app locale is the same as last fallback
// it means that we have all resources in this locale, and
@ -412,51 +500,17 @@ class Localization {
this.bundles.touchNext(prefetchCount);
}
}
setIsSync(isSync) {
this.isSync = isSync;
this.onChange();
}
}
Localization.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsISupportsWeakReference,
]);
class LocalizationSync extends Localization {
constructor(resourceIds = [], generateBundles = defaultGenerateBundlesSync) {
super(resourceIds, generateBundles);
}
cached(iterable) {
return CachedSyncIterable.from(iterable);
}
formatWithFallback(keys, method) {
const translations = new Array(keys.length);
let hasAtLeastOneBundle = false;
for (const bundle of this.bundles) {
hasAtLeastOneBundle = true;
const missingIds = keysFromBundle(method, bundle, keys, translations);
if (missingIds.size === 0) {
break;
}
const locale = bundle.locales[0];
const ids = Array.from(missingIds).join(", ");
maybeReportErrorToGecko(`[fluent] Missing translations in ${locale}: ${ids}.`);
}
if (!hasAtLeastOneBundle) {
maybeReportErrorToGecko(`[fluent] Request for keys failed because no resource bundles got generated.\n keys: ${JSON.stringify(keys)}.\n resourceIds: ${JSON.stringify(this.resourceIds)}.`);
}
return translations;
}
formatValue(id, args) {
const [val] = this.formatValues([{id, args}]);
return val;
}
}
/**
* Format the value of a message into a string.
*
@ -577,7 +631,6 @@ function keysFromBundle(method, bundle, keys, translations) {
}
});
return missingIds;
}
@ -585,14 +638,13 @@ function keysFromBundle(method, bundle, keys, translations) {
* Helper function which allows us to construct a new
* Localization from Localization.
*/
var getLocalization = (resourceIds) => {
return new Localization(resourceIds);
var getLocalization = (resourceIds, sync = false) => {
return new Localization(resourceIds, sync);
};
var getLocalizationWithCustomGenerateMessages = (resourceIds, generateMessages) => {
return new Localization(resourceIds, generateMessages);
return new Localization(resourceIds, false, generateMessages);
};
this.Localization = Localization;
this.LocalizationSync = LocalizationSync;
var EXPORTED_SYMBOLS = ["Localization", "LocalizationSync", "getLocalization", "getLocalizationWithCustomGenerateMessages"];
var EXPORTED_SYMBOLS = ["Localization", "getLocalization", "getLocalizationWithCustomGenerateMessages"];

View file

@ -549,7 +549,7 @@ contexts manually using the `Localization` class:
let [isDefaultMsg, isNotDefaultMsg] =
myL10n.formatValues({id: "is-default"}, {id: "is-not-default"});
await myL10n.formatValues({id: "is-default"}, {id: "is-not-default"});
.. admonition:: Example
@ -563,6 +563,32 @@ contexts manually using the `Localization` class:
A developer may create manually a new context with the same resources as the main one,
but hardcode it to `en-US` and then build the search index using both contexts.
By default, all `Localization` contexts are asynchronous. It is possible to create a synchronous
one by passing an `sync = false` argument to the constructor, or calling the `SetIsSync(bool)` method
on the class.
.. code-block:: javascript
const { Localization } =
ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
const myL10n = new Localization([
"branding/brand.ftl",
"browser/preferences/preferences.ftl"
], false);
let [isDefaultMsg, isNotDefaultMsg] =
myL10n.formatValuesSync({id: "is-default"}, {id: "is-not-default"});
Synchronous contexts should be always avoided as they require synchronous I/O. If you think your use case
requires a synchronous localization context, please consult Gecko, Performance and L10n Drivers teams.
Designing Localizable APIs
==========================

View file

@ -3,6 +3,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is an internal XPIDL used to expose a JS based Localization class to be used
* by its C++ wrapper.
*
* Consumers should use the WebIDL Localization API instead of this one.
*/
#include "nsISupports.idl"
[scriptable, uuid(7d468600-551f-4fe0-98c9-92a53b63ec8d)]
@ -15,11 +21,14 @@ interface mozILocalization : nsISupports
Promise formatMessages(in Array<jsval> aKeys);
Promise formatValues(in Array<jsval> aKeys);
Promise formatValue(in AString aId, [optional] in jsval aArgs);
Array<jsval> formatMessagesSync(in Array<jsval> aKeys);
void setIsSync(in boolean isSync);
};
[scriptable, uuid(96632d26-1422-12e9-b1ce-9bb586acd241)]
interface mozILocalizationJSM : nsISupports
{
mozILocalization getLocalization(in Array<AString> resourceIds);
mozILocalization getLocalization(in Array<AString> resourceIds, in bool sync);
mozILocalization getLocalizationWithCustomGenerateMessages(in Array<AString> resourceIds, in jsval generateMessages);
};

View file

@ -35,7 +35,7 @@ add_task(async function test_methods_calling() {
const l10n = new Localization([
"/browser/menu.ftl",
], generateMessages);
], false, generateMessages);
let values = await l10n.formatValues([{id: "key"}, {id: "key2"}]);
@ -81,7 +81,7 @@ key = { PLATFORM() ->
const l10n = new Localization([
"/test.ftl",
], generateMessages);
], false, generateMessages);
let values = await l10n.formatValues([{id: "key"}]);
@ -114,7 +114,7 @@ add_task(async function test_add_remove_resourceIds() {
yield * await L10nRegistry.generateBundles(["en-US"], resIds);
}
const l10n = new Localization(["/browser/menu.ftl"], generateMessages);
const l10n = new Localization(["/browser/menu.ftl"], false, generateMessages);
let values = await l10n.formatValues([{id: "key1"}, {id: "key2"}]);
@ -139,3 +139,81 @@ add_task(async function test_add_remove_resourceIds() {
L10nRegistry.load = originalLoad;
Services.locale.requestedLocales = originalRequested;
});
add_task(async function test_switch_to_async() {
const { L10nRegistry, FileSource } =
ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm");
const fs = {
"/localization/en-US/browser/menu.ftl": "key1 = Value1",
"/localization/en-US/toolkit/menu.ftl": "key2 = Value2",
};
const originalLoad = L10nRegistry.load;
const originalLoadSync = L10nRegistry.loadSync;
const originalRequested = Services.locale.requestedLocales;
let syncLoads = 0;
let asyncLoads = 0;
L10nRegistry.load = async function(url) {
asyncLoads += 1;
return fs[url];
};
L10nRegistry.loadSync = function(url) {
syncLoads += 1;
return fs[url];
};
const source = new FileSource("test", ["en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
async function* generateMessages(resIds) {
yield * await L10nRegistry.generateBundles(["en-US"], resIds);
}
function* generateMessagesSync(resIds) {
yield * L10nRegistry.generateBundlesSync(["en-US"], resIds);
}
const l10n = new Localization(["/browser/menu.ftl"], false, generateMessages, generateMessagesSync);
let values = await l10n.formatValues([{id: "key1"}, {id: "key2"}]);
equal(values[0], "Value1");
equal(values[1], undefined);
equal(syncLoads, 0);
equal(asyncLoads, 1);
l10n.setIsSync(true);
l10n.addResourceIds(["/toolkit/menu.ftl"]);
// Nothing happens when we switch, because
// the next load is lazy.
equal(syncLoads, 0);
equal(asyncLoads, 1);
values = l10n.formatValuesSync([{id: "key1"}, {id: "key2"}]);
let values2 = await l10n.formatValues([{id: "key1"}, {id: "key2"}]);
deepEqual(values, values2);
equal(values[0], "Value1");
equal(values[1], "Value2");
equal(syncLoads, 1);
equal(asyncLoads, 1);
l10n.removeResourceIds(["/browser/menu.ftl"]);
values = await l10n.formatValues([{id: "key1"}, {id: "key2"}]);
equal(values[0], undefined);
equal(values[1], "Value2");
equal(syncLoads, 1);
equal(asyncLoads, 1);
L10nRegistry.sources.clear();
L10nRegistry.load = originalLoad;
L10nRegistry.loadSync = originalLoadSync;
Services.locale.requestedLocales = originalRequested;
});

View file

@ -0,0 +1,151 @@
/* 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");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
add_task(function test_methods_calling() {
const { L10nRegistry, FileSource } =
ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm");
const fs = {
"/localization/de/browser/menu.ftl": "key = [de] Value2",
"/localization/en-US/browser/menu.ftl": "key = [en] Value2\nkey2 = [en] Value3",
};
const originalLoadSync = L10nRegistry.loadSync;
const originalRequested = Services.locale.requestedLocales;
L10nRegistry.loadSync = function(url) {
return fs[url];
};
const source = new FileSource("test", ["de", "en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
function* generateMessagesSync(resIds) {
yield * L10nRegistry.generateBundlesSync(["de", "en-US"], resIds);
}
const l10n = new Localization([
"/browser/menu.ftl",
], true, null, generateMessagesSync);
let values = l10n.formatValuesSync([{id: "key"}, {id: "key2"}]);
equal(values[0], "[de] Value2");
equal(values[1], "[en] Value3");
L10nRegistry.sources.clear();
L10nRegistry.loadSync = originalLoadSync;
Services.locale.requestedLocales = originalRequested;
});
add_task(function test_builtins() {
const { L10nRegistry, FileSource } =
ChromeUtils.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 originalLoadSync = L10nRegistry.loadSync;
L10nRegistry.loadSync = function(url) {
return fs[url];
};
const source = new FileSource("test", ["en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
function* generateMessagesSync(resIds) {
yield * L10nRegistry.generateBundlesSync(["en-US"], resIds);
}
const l10n = new Localization([
"/test.ftl",
], true, null, generateMessagesSync);
let values = l10n.formatValuesSync([{id: "key"}]);
ok(values[0].includes(
`${ known_platforms[AppConstants.platform].toUpperCase() } Value`));
L10nRegistry.sources.clear();
L10nRegistry.loadSync = originalLoadSync;
});
add_task(function test_add_remove_resourceIds() {
const { L10nRegistry, FileSource } =
ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm");
const fs = {
"/localization/en-US/browser/menu.ftl": "key1 = Value1",
"/localization/en-US/toolkit/menu.ftl": "key2 = Value2",
};
const originalLoadSync = L10nRegistry.loadSYnc;
const originalRequested = Services.locale.requestedLocales;
L10nRegistry.loadSync = function(url) {
return fs[url];
};
const source = new FileSource("test", ["en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
function* generateMessagesSync(resIds) {
yield * L10nRegistry.generateBundlesSync(["en-US"], resIds);
}
const l10n = new Localization(["/browser/menu.ftl"], true, null, generateMessagesSync);
let values = l10n.formatValuesSync([{id: "key1"}, {id: "key2"}]);
equal(values[0], "Value1");
equal(values[1], undefined);
l10n.addResourceIds(["/toolkit/menu.ftl"]);
values = l10n.formatValuesSync([{id: "key1"}, {id: "key2"}]);
equal(values[0], "Value1");
equal(values[1], "Value2");
l10n.removeResourceIds(["/browser/menu.ftl"]);
values = l10n.formatValuesSync([{id: "key1"}, {id: "key2"}]);
equal(values[0], undefined);
equal(values[1], "Value2");
L10nRegistry.sources.clear();
L10nRegistry.loadSync = originalLoadSync;
Services.locale.requestedLocales = originalRequested;
});
add_task(function test_calling_sync_methods_in_async_mode_fails() {
const l10n = new Localization(["/browser/menu.ftl"], false);
Assert.throws(() => {
l10n.formatValuesSync([{ id: "key1" }, { id: "key2" }]);
}, /Can't use sync formatWithFallback when state is async./);
Assert.throws(() => {
l10n.formatValueSync("key1");
}, /Can't use sync formatWithFallback when state is async./);
Assert.throws(() => {
l10n.formatMessagesSync([{ id: "key1"}]);
}, /Can't use sync formatWithFallback when state is async./);
});

View file

@ -46,7 +46,7 @@ add_task(async function test_accented_works() {
const l10n = new Localization([
"/browser/menu.ftl",
], generateMessages);
], false, generateMessages);
l10n.registerObservers();
{
@ -108,7 +108,7 @@ add_task(async function test_unavailable_strategy_works() {
const l10n = new Localization([
"/browser/menu.ftl",
], generateMessages);
], false, generateMessages);
l10n.registerObservers();
{

View file

@ -4,5 +4,6 @@ head =
[test_l10nregistry.js]
[test_l10nregistry_sync.js]
[test_localization.js]
[test_localization_sync.js]
[test_messagecontext.js]
[test_pseudo.js]

View file

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { LocalizationSync } = ChromeUtils.import("resource://gre/modules/Localization.jsm", null);
const { Localization } = ChromeUtils.import("resource://gre/modules/Localization.jsm", null);
const mozIntlHelper =
Cc["@mozilla.org/mozintlhelper;1"].getService(Ci.mozIMozIntlHelper);
@ -269,7 +269,7 @@ class MozIntl {
}
if (!this._cache.hasOwnProperty("languageLocalization")) {
const loc = new LocalizationSync(["toolkit/intl/languageNames.ftl"]);
const loc = new Localization(["toolkit/intl/languageNames.ftl"], true);
this._cache.languageLocalization = loc;
}
@ -281,7 +281,7 @@ class MozIntl {
}
let lcLangCode = langCode.toLowerCase();
if (availableLocaleDisplayNames.language.has(lcLangCode)) {
const value = loc.formatValue(`language-name-${lcLangCode}`);
const value = loc.formatValueSync(`language-name-${lcLangCode}`);
if (value !== undefined) {
return value;
}
@ -296,7 +296,7 @@ class MozIntl {
}
if (!this._cache.hasOwnProperty("regionLocalization")) {
const loc = new LocalizationSync(["toolkit/intl/regionNames.ftl"]);
const loc = new Localization(["toolkit/intl/regionNames.ftl"], true);
this._cache.regionLocalization = loc;
}
@ -308,7 +308,7 @@ class MozIntl {
}
let lcRegionCode = regionCode.toLowerCase();
if (availableLocaleDisplayNames.region.has(lcRegionCode)) {
const value = loc.formatValue(`region-name-${lcRegionCode}`);
const value = loc.formatValueSync(`region-name-${lcRegionCode}`);
if (value !== undefined) {
return value;
}

View file

@ -312,6 +312,7 @@ module.exports = {
"KeyEvent": false,
"KeyboardEvent": false,
"KeyframeEffect": false,
"Localization": false,
"Location": false,
"MIDIAccess": false,
"MIDIConnectionEvent": false,

View file

@ -289,6 +289,7 @@ STATIC_ATOMS = [
Atom("datal10nargs", "data-l10n-args"),
Atom("datal10nattrs", "data-l10n-attrs"),
Atom("datal10nname", "data-l10n-name"),
Atom("datal10nsync", "data-l10n-sync"),
Atom("dataType", "data-type"),
Atom("dateTime", "date-time"),
Atom("date", "date"),