gecko-dev/intl/l10n/fluent.js.patch

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

476 строки
15 KiB
Diff
Исходник Обычный вид История

diff --git a/intl/l10n/Fluent.jsm b/intl/l10n/Fluent.jsm
--- a/intl/l10n/Fluent.jsm
+++ b/intl/l10n/Fluent.jsm
@@ -16,7 +16,7 @@
*/
-/* fluent@0.10.0 */
+/* fluent-dom@0.4.0 */
/* global Intl */
@@ -139,53 +139,7 @@ function values(opts) {
return unwrapped;
}
-/**
- * @overview
- *
- * The role of the Fluent resolver is to format a translation object to an
- * instance of `FluentType` or an array of instances.
- *
- * Translations can contain references to other messages or variables,
- * conditional logic in form of select expressions, traits which describe their
- * grammatical features, and can use Fluent builtins which make use of the
- * `Intl` formatters to format numbers, dates, lists and more into the
- * bundle's language. See the documentation of the Fluent syntax for more
- * information.
- *
- * In case of errors the resolver will try to salvage as much of the
- * translation as possible. In rare situations where the resolver didn't know
- * how to recover from an error it will return an instance of `FluentNone`.
- *
- * `MessageReference`, `VariantExpression`, `AttributeExpression` and
- * `SelectExpression` resolve to raw Runtime Entries objects and the result of
- * the resolution needs to be passed into `Type` to get their real value.
- * This is useful for composing expressions. Consider:
- *
- * brand-name[nominative]
- *
- * which is a `VariantExpression` with properties `id: MessageReference` and
- * `key: Keyword`. If `MessageReference` was resolved eagerly, it would
- * instantly resolve to the value of the `brand-name` message. Instead, we
- * want to get the message object and look for its `nominative` variant.
- *
- * All other expressions (except for `FunctionReference` which is only used in
- * `CallExpression`) resolve to an instance of `FluentType`. The caller should
- * use the `toString` method to convert the instance to a native value.
- *
- *
- * All functions in this file pass around a special object called `env`.
- * This object stores a set of elements used by all resolve functions:
- *
- * * {FluentBundle} bundle
- * bundle for which the given resolution is happening
- * * {Object} args
- * list of developer provided arguments that can be used
- * * {Array} errors
- * list of errors collected while resolving
- * * {WeakSet} dirty
- * Set of patterns already encountered during this resolution.
- * This is used to prevent cyclic resolutions.
- */
+/* global Intl */
// Prevent expansion of too long placeables.
const MAX_PLACEABLE_LENGTH = 2500;
@@ -514,7 +468,7 @@ function Pattern(env, ptn) {
*/
function resolve(bundle, args, message, errors = []) {
const env = {
- bundle, args, errors, dirty: new WeakSet(),
+ bundle, args, errors, dirty: new WeakSet()
};
return Type(env, message).toString(bundle);
}
@@ -1064,7 +1018,7 @@ class FluentBundle {
constructor(locales, {
functions = {},
useIsolating = true,
- transform = v => v,
+ transform = v => v
} = {}) {
this.locales = Array.isArray(locales) ? locales : [locales];
@@ -1235,6 +1189,14 @@ class FluentBundle {
}
}
+/*
+ * @module fluent
+ * @overview
+ *
+ * `fluent` is a JavaScript implementation of Project Fluent, a localization
+ * framework designed to unleash the expressive power of the natural language.
+ *
+ */
+
this.FluentBundle = FluentBundle;
-this.FluentResource = FluentResource;
-var EXPORTED_SYMBOLS = ["FluentBundle", "FluentResource"];
+this.EXPORTED_SYMBOLS = ["FluentBundle"];
diff --git a/intl/l10n/Localization.jsm b/intl/l10n/Localization.jsm
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -16,34 +16,27 @@
*/
-/* fluent-dom@fa25466f (October 12, 2018) */
-
-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
-/* global console */
-
-const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+/* fluent-dom@0.4.0 */
/*
* Base CachedIterable class.
*/
class CachedIterable extends Array {
- /**
- * Create a `CachedIterable` instance from an iterable or, if another
- * instance of `CachedIterable` is passed, return it without any
- * modifications.
- *
- * @param {Iterable} iterable
- * @returns {CachedIterable}
- */
- static from(iterable) {
- if (iterable instanceof this) {
- return iterable;
+ /**
+ * Create a `CachedIterable` instance from an iterable or, if another
+ * instance of `CachedIterable` is passed, return it without any
+ * modifications.
+ *
+ * @param {Iterable} iterable
+ * @returns {CachedIterable}
+ */
+ static from(iterable) {
+ if (iterable instanceof this) {
+ return iterable;
+ }
+
+ return new this(iterable);
}
-
- return new this(iterable);
- }
}
/*
@@ -53,80 +46,88 @@ class CachedIterable extends Array {
* iterable.
*/
class CachedAsyncIterable extends CachedIterable {
- /**
- * Create an `CachedAsyncIterable` instance.
- *
- * @param {Iterable} iterable
- * @returns {CachedAsyncIterable}
- */
- constructor(iterable) {
- super();
+ /**
+ * Create an `CachedAsyncIterable` instance.
+ *
+ * @param {Iterable} iterable
+ * @returns {CachedAsyncIterable}
+ */
+ constructor(iterable) {
+ super();
+
+ if (Symbol.asyncIterator in Object(iterable)) {
+ this.iterator = iterable[Symbol.asyncIterator]();
+ } else if (Symbol.iterator in Object(iterable)) {
+ this.iterator = iterable[Symbol.iterator]();
+ } else {
+ throw new TypeError("Argument must implement the iteration protocol.");
+ }
+ }
- if (Symbol.asyncIterator in Object(iterable)) {
- this.iterator = iterable[Symbol.asyncIterator]();
- } else if (Symbol.iterator in Object(iterable)) {
- this.iterator = iterable[Symbol.iterator]();
- } else {
- throw new TypeError("Argument must implement the iteration protocol.");
- }
- }
+ /**
+ * Synchronous iterator over the cached elements.
+ *
+ * Return a generator object implementing the iterator protocol over the
+ * cached elements of the original (async or sync) iterable.
+ */
+ [Symbol.iterator]() {
+ const cached = this;
+ let cur = 0;
- /**
- * Asynchronous iterator caching the yielded elements.
- *
- * Elements yielded by the original iterable will be cached and available
- * synchronously. Returns an async generator object implementing the
- * iterator protocol over the elements of the original (async or sync)
- * iterable.
- */
- [Symbol.asyncIterator]() {
- const cached = this;
- let cur = 0;
+ return {
+ next() {
+ if (cached.length === cur) {
+ return {value: undefined, done: true};
+ }
+ return cached[cur++];
+ }
+ };
+ }
- return {
- async next() {
- if (cached.length <= cur) {
- cached.push(cached.iterator.next());
- }
- return cached[cur++];
- },
- };
- }
+ /**
+ * Asynchronous iterator caching the yielded elements.
+ *
+ * Elements yielded by the original iterable will be cached and available
+ * synchronously. Returns an async generator object implementing the
+ * iterator protocol over the elements of the original (async or sync)
+ * iterable.
+ */
+ [Symbol.asyncIterator]() {
+ const cached = this;
+ let cur = 0;
- /**
- * This method allows user to consume the next element from the iterator
- * into the cache.
- *
- * @param {number} count - number of elements to consume
- */
- async touchNext(count = 1) {
- let idx = 0;
- while (idx++ < count) {
- const last = this[this.length - 1];
- if (last && (await last).done) {
- break;
- }
- this.push(this.iterator.next());
+ return {
+ async next() {
+ if (cached.length <= cur) {
+ cached.push(await cached.iterator.next());
+ }
+ return cached[cur++];
+ }
+ };
}
- // 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];
- }
+
+ /**
+ * This method allows user to consume the next element from the iterator
+ * into the cache.
+ *
+ * @param {number} count - number of elements to consume
+ */
+ async touchNext(count = 1) {
+ let idx = 0;
+ while (idx++ < count) {
+ const last = this[this.length - 1];
+ if (last && last.done) {
+ break;
+ }
+ this.push(await 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
- * generate the iterator over FluentBundles.
- *
- * In the future, we may want to allow certain modules to override this
- * with a different negotitation strategy to allow for the module to
- * be localized into a different language - for example DevTools.
- */
-function defaultGenerateBundles(resourceIds) {
- const appLocales = Services.locale.appLocalesAsBCP47;
- return L10nRegistry.generateBundles(appLocales, resourceIds);
-}
+/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
/**
* The `Localization` class is a central high-level API for vanilla
@@ -142,21 +143,16 @@ class Localization {
*
* @returns {Localization}
*/
- constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
+ constructor(resourceIds = [], generateBundles) {
this.resourceIds = resourceIds;
this.generateBundles = generateBundles;
this.bundles = CachedAsyncIterable.from(
this.generateBundles(this.resourceIds));
}
- /**
- * @param {Array<String>} resourceIds - List of resource IDs
- * @param {bool} eager - whether the I/O for new context should
- * begin eagerly
- */
- addResourceIds(resourceIds, eager = false) {
+ addResourceIds(resourceIds) {
this.resourceIds.push(...resourceIds);
- this.onChange(eager);
+ this.onChange();
return this.resourceIds.length;
}
@@ -188,12 +184,9 @@ class Localization {
break;
}
- if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
+ if (typeof console !== "undefined") {
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}`);
}
}
@@ -281,64 +274,21 @@ class Localization {
return val;
}
- /**
- * Register weak observers on events that will trigger cache invalidation
- */
- registerObservers() {
- Services.obs.addObserver(this, "intl:app-locales-changed", true);
- Services.prefs.addObserver("intl.l10n.pseudo", this, true);
- }
-
- /**
- * Default observer handler method.
- *
- * @param {String} subject
- * @param {String} topic
- * @param {Object} data
- */
- observe(subject, topic, data) {
- switch (topic) {
- case "intl:app-locales-changed":
- this.onChange();
- break;
- case "nsPref:changed":
- switch (data) {
- case "intl.l10n.pseudo":
- this.onChange();
- }
- break;
- default:
- break;
- }
+ handleEvent() {
+ this.onChange();
}
/**
* This method should be called when there's a reason to believe
* that language negotiation or available resources changed.
- *
- * @param {bool} eager - whether the I/O for new context should begin eagerly
*/
- onChange(eager = false) {
+ onChange() {
this.bundles = CachedAsyncIterable.from(
this.generateBundles(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
- // we want to eagerly fetch just that one.
- // Otherwise, we're in a scenario where the first locale may
- // be partial and we want to eagerly fetch a fallback as well.
- const appLocale = Services.locale.appLocaleAsBCP47;
- const lastFallback = Services.locale.lastFallbackLocale;
- const prefetchCount = appLocale === lastFallback ? 1 : 2;
- this.bundles.touchNext(prefetchCount);
- }
+ this.bundles.touchNext(2);
}
}
-Localization.prototype.QueryInterface = ChromeUtils.generateQI([
- Ci.nsISupportsWeakReference,
-]);
-
/**
* Format the value of a message into a string.
*
@@ -430,7 +380,7 @@ function messageFromBundle(bundle, error
* See `Localization.formatWithFallback` for more info on how this is used.
*
* @param {Function} method
- * @param {FluentBundle} bundle
+ * @param {FluentBundle} bundle
* @param {Array<string>} keys
* @param {{Array<{value: string, attributes: Object}>}} translations
*
@@ -458,5 +408,44 @@ function keysFromBundle(method, bundle,
return missingIds;
}
-this.Localization = Localization;
-var EXPORTED_SYMBOLS = ["Localization"];
+/* global Components */
+/* eslint no-unused-vars: 0 */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const { L10nRegistry } =
+ Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
+const ObserverService =
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+const { Services } =
+ Cu.import("resource://gre/modules/Services.jsm", {});
+
+/**
+ * The default localization strategy for Gecko. It comabines locales
+ * available in L10nRegistry, with locales requested by the user to
+ * generate the iterator over FluentBundles.
+ *
+ * In the future, we may want to allow certain modules to override this
+ * with a different negotitation strategy to allow for the module to
+ * be localized into a different language - for example DevTools.
+ */
+function defaultGenerateBundles(resourceIds) {
+ const requestedLocales = Services.locale.getRequestedLocales();
+ const availableLocales = L10nRegistry.getAvailableLocales();
+ const defaultLocale = Services.locale.defaultLocale;
+ const locales = Services.locale.negotiateLanguages(
+ requestedLocales, availableLocales, defaultLocale,
+ );
+ return L10nRegistry.generateContexts(locales, resourceIds);
+}
+
+class GeckoLocalization extends Localization {
+ constructor(resourceIds, generateBundles = defaultGenerateBundles) {
+ super(resourceIds, generateBundles);
+ }
+}
+
+this.Localization = GeckoLocalization;
+this.EXPORTED_SYMBOLS = ["Localization"];