diff --git a/intl/l10n/Localization.jsm b/intl/l10n/Localization.jsm index f080b9ae461f..dd83cd6c2eca 100644 --- a/intl/l10n/Localization.jsm +++ b/intl/l10n/Localization.jsm @@ -114,6 +114,64 @@ class CachedAsyncIterable extends CachedIterable { } } +/* + * CachedSyncIterable caches the elements yielded by an iterable. + * + * It can be used to iterate over an iterable many times without depleting the + * iterable. + */ +class CachedSyncIterable extends CachedIterable { + /** + * Create an `CachedSyncIterable` instance. + * + * @param {Iterable} iterable + * @returns {CachedSyncIterable} + */ + constructor(iterable) { + super(); + + if (Symbol.iterator in Object(iterable)) { + this.iterator = iterable[Symbol.iterator](); + } else { + throw new TypeError("Argument must implement the iteration protocol."); + } + } + + [Symbol.iterator]() { + const cached = this; + let cur = 0; + + return { + next() { + if (cached.length <= cur) { + cached.push(cached.iterator.next()); + } + return cached[cur++]; + }, + }; + } + + /** + * This method allows user to consume the next element from the iterator + * into the cache. + * + * @param {number} count - number of elements to consume + */ + touchNext(count = 1) { + let idx = 0; + while (idx++ < count) { + const last = this[this.length - 1]; + if (last && last.done) { + break; + } + this.push(this.iterator.next()); + } + // Return the last cached {value, done} object to allow the calling + // code to decide if it needs to call touchNext again. + return this[this.length - 1]; + } +} + /** * The default localization strategy for Gecko. It comabines locales * available in L10nRegistry, with locales requested by the user to @@ -128,6 +186,11 @@ function defaultGenerateBundles(resourceIds) { return L10nRegistry.generateBundles(appLocales, resourceIds); } +function defaultGenerateBundlesSync(resourceIds) { + const appLocales = Services.locale.appLocalesAsBCP47; + return L10nRegistry.generateBundlesSync(appLocales, resourceIds); +} + /** * The `Localization` class is a central high-level API for vanilla * JavaScript use of Fluent. @@ -145,10 +208,14 @@ class Localization { constructor(resourceIds = [], generateBundles = defaultGenerateBundles) { this.resourceIds = resourceIds; this.generateBundles = generateBundles; - this.bundles = CachedAsyncIterable.from( + this.bundles = this.cached( this.generateBundles(this.resourceIds)); } + cached(iterable) { + return CachedAsyncIterable.from(iterable); + } + /** * @param {Array} resourceIds - List of resource IDs * @param {bool} eager - whether the I/O for new context should @@ -319,7 +386,7 @@ class Localization { * @param {bool} eager - whether the I/O for new context should begin eagerly */ onChange(eager = false) { - this.bundles = CachedAsyncIterable.from( + this.bundles = this.cached( this.generateBundles(this.resourceIds)); if (eager) { // If the first app locale is the same as last fallback @@ -339,6 +406,44 @@ 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 = []; + + for (const bundle of this.bundles) { + const missingIds = keysFromBundle(method, bundle, keys, translations); + + if (missingIds.size === 0) { + break; + } + + if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) { + const locale = bundle.locales[0]; + const ids = Array.from(missingIds).join(", "); + if (Cu.isInAutomation) { + throw new Error(`Missing translations in ${locale}: ${ids}`); + } + console.warn(`Missing translations in ${locale}: ${ids}`); + } + } + + return translations; + } + + formatValue(id, args) { + const [val] = this.formatValues([{id, args}]); + return val; + } +} + /** * Format the value of a message into a string. * @@ -459,4 +564,5 @@ function keysFromBundle(method, bundle, keys, translations) { } this.Localization = Localization; -var EXPORTED_SYMBOLS = ["Localization"]; +this.LocalizationSync = LocalizationSync; +var EXPORTED_SYMBOLS = ["Localization", "LocalizationSync"];