--- a/.eslintignore
+++ b/.eslintignore
@@ -19,17 +19,16 @@ extensions/cookie/**
extensions/spellcheck/**
extensions/universalchardet/**
gfx/layers/**
gfx/tests/browser/**
gfx/tests/chrome/**
gfx/tests/mochitest/**
gfx/tests/unit/**
image/**
-intl/**
layout/**
memory/replace/dmd/test/**
modules/**
netwerk/base/NetUtil.jsm
netwerk/cookie/test/browser/**
netwerk/cookie/test/unit/**
netwerk/protocol/**
netwerk/dns/**
@@ -298,16 +297,22 @@ dom/xul/**
# Third-party
dom/media/webvtt/**
# Third-party
gfx/ots/**
gfx/skia/**
+# intl/ exclusions
+intl/icu/**
+intl/locale/**
+intl/strres/**
+intl/uconv/**
+
# Exclude everything but self-hosted JS
js/ductwork/**
js/examples/**
js/ipc/**
js/public/**
js/xpconnect/**
js/src/devtools/**
js/src/octane/**
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -11,95 +11,96 @@
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* fluent@0.6.0 */
+/* fluent@0.6.3 */
const { Localization } =
ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
// Match the opening angle bracket (<) in HTML tags, and HTML entities like
// &, &, &.
const reOverlay = /<|&#?\w+;/;
/**
* The list of elements that are allowed to be inserted into a localization.
*
* Source: https://www.w3.org/TR/html5/text-level-semantics.html
*/
const LOCALIZABLE_ELEMENTS = {
- 'http://www.w3.org/1999/xhtml': [
- 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
- 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
- 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
+ "http://www.w3.org/1999/xhtml": [
+ "a", "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
+ "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
+ "mark", "ruby", "rt", "rp", "bdi", "bdo", "span", "br", "wbr"
],
};
const LOCALIZABLE_ATTRIBUTES = {
- 'http://www.w3.org/1999/xhtml': {
- global: ['title', 'aria-label', 'aria-valuetext', 'aria-moz-hint'],
- a: ['download'],
- area: ['download', 'alt'],
+ "http://www.w3.org/1999/xhtml": {
+ global: ["title", "aria-label", "aria-valuetext", "aria-moz-hint"],
+ a: ["download"],
+ area: ["download", "alt"],
// value is special-cased in isAttrNameLocalizable
- input: ['alt', 'placeholder'],
- menuitem: ['label'],
- menu: ['label'],
- optgroup: ['label'],
- option: ['label'],
- track: ['label'],
- img: ['alt'],
- textarea: ['placeholder'],
- th: ['abbr']
+ input: ["alt", "placeholder"],
+ menuitem: ["label"],
+ menu: ["label"],
+ optgroup: ["label"],
+ option: ["label"],
+ track: ["label"],
+ img: ["alt"],
+ textarea: ["placeholder"],
+ th: ["abbr"]
},
- 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul': {
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": {
global: [
- 'accesskey', 'aria-label', 'aria-valuetext', 'aria-moz-hint', 'label'
+ "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label"
],
- key: ['key', 'keycode'],
- textbox: ['placeholder'],
- toolbarbutton: ['tooltiptext'],
+ key: ["key", "keycode"],
+ textbox: ["placeholder"],
+ toolbarbutton: ["tooltiptext"],
}
};
/**
* Overlay translation onto a DOM element.
*
* @param {Element} targetElement
* @param {string|Object} translation
* @private
*/
function overlayElement(targetElement, translation) {
const value = translation.value;
- if (typeof value === 'string') {
+ if (typeof value === "string") {
if (!reOverlay.test(value)) {
// If the translation doesn't contain any markup skip the overlay logic.
targetElement.textContent = value;
} else {
// Else parse the translation's HTML using an inert template element,
// sanitize it and replace the targetElement's content.
const templateElement = targetElement.ownerDocument.createElementNS(
- 'http://www.w3.org/1999/xhtml', 'template');
+ "http://www.w3.org/1999/xhtml", "template");
+ // eslint-disable-next-line no-unsanitized/property
templateElement.innerHTML = value;
targetElement.appendChild(
// The targetElement will be cleared at the end of sanitization.
sanitizeUsing(templateElement.content, targetElement)
);
}
}
- const explicitlyAllowed = targetElement.hasAttribute('data-l10n-attrs')
- ? targetElement.getAttribute('data-l10n-attrs')
- .split(',').map(i => i.trim())
+ const explicitlyAllowed = targetElement.hasAttribute("data-l10n-attrs")
+ ? targetElement.getAttribute("data-l10n-attrs")
+ .split(",").map(i => i.trim())
: null;
// Remove localizable attributes which may have been set by a previous
// translation.
for (const attr of Array.from(targetElement.attributes)) {
if (isAttrNameLocalizable(attr.name, targetElement, explicitlyAllowed)) {
targetElement.removeAttribute(attr.name);
}
@@ -177,17 +178,17 @@ function sanitizeUsing(translationFragme
mergedChild.setAttribute(attr.name, attr.value);
}
translationFragment.replaceChild(mergedChild, childNode);
}
// SourceElement might have been already modified by shiftNamedElement.
// Let's clear it to make sure other code doesn't rely on random leftovers.
- sourceElement.textContent = '';
+ sourceElement.textContent = "";
return translationFragment;
}
/**
* Sanitize and merge attributes.
*
* Only localizable attributes from the translated child element and only
@@ -269,20 +270,20 @@ function isAttrNameLocalizable(name, ele
}
// Is it allowed on this element?
if (allowed[elemName].includes(attrName)) {
return true;
}
// Special case for value on HTML inputs with type button, reset, submit
- if (element.namespaceURI === 'http://www.w3.org/1999/xhtml' &&
- elemName === 'input' && attrName === 'value') {
+ if (element.namespaceURI === "http://www.w3.org/1999/xhtml" &&
+ elemName === "input" && attrName === "value") {
const type = element.type.toLowerCase();
- if (type === 'submit' || type === 'button' || type === 'reset') {
+ if (type === "submit" || type === "button" || type === "reset") {
return true;
}
}
return false;
}
/**
@@ -298,18 +299,18 @@ function shiftNamedElement(element, loca
if (child.localName === localName) {
element.removeChild(child);
return child;
}
}
return null;
}
-const L10NID_ATTR_NAME = 'data-l10n-id';
-const L10NARGS_ATTR_NAME = 'data-l10n-args';
+const L10NID_ATTR_NAME = "data-l10n-id";
+const L10NARGS_ATTR_NAME = "data-l10n-args";
const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
/**
* The `DOMLocalization` class is responsible for fetching resources and
* formatting translations.
*
* It implements the fallback strategy in case of errors encountered during the
@@ -425,17 +426,17 @@ class DOMLocalization extends Localizati
*
* @param {Element} newRoot - Root to observe.
*/
connectRoot(newRoot) {
for (const root of this.roots) {
if (root === newRoot ||
root.contains(newRoot) ||
newRoot.contains(root)) {
- throw new Error('Cannot add a root that overlaps with existing root.');
+ throw new Error("Cannot add a root that overlaps with existing root.");
}
}
this.roots.add(newRoot);
this.mutationObserver.observe(newRoot, this.observerConfig);
}
/**
@@ -495,20 +496,20 @@ class DOMLocalization extends Localizati
/**
* Translate mutations detected by the `MutationObserver`.
*
* @private
*/
translateMutations(mutations) {
for (const mutation of mutations) {
switch (mutation.type) {
- case 'attributes':
+ case "attributes":
this.pendingElements.add(mutation.target);
break;
- case 'childList':
+ case "childList":
for (const addedNode of mutation.addedNodes) {
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
if (addedNode.childElementCount) {
for (const element of this.getTranslatables(addedNode)) {
this.pendingElements.add(element);
}
} else if (addedNode.hasAttribute(L10NID_ATTR_NAME)) {
this.pendingElements.add(addedNode);
@@ -595,17 +596,17 @@ class DOMLocalization extends Localizati
*
* @param {Element} element
* @returns {Array<Element>}
* @private
*/
getTranslatables(element) {
const nodes = Array.from(element.querySelectorAll(L10N_ELEMENT_QUERY));
- if (typeof element.hasAttribute === 'function' &&
+ if (typeof element.hasAttribute === "function" &&
element.hasAttribute(L10NID_ATTR_NAME)) {
nodes.push(element);
}
return nodes;
}
/**
@@ -620,9 +621,9 @@ class DOMLocalization extends Localizati
return [
element.getAttribute(L10NID_ATTR_NAME),
JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null)
];
}
}
this.DOMLocalization = DOMLocalization;
-this.EXPORTED_SYMBOLS = ['DOMLocalization'];
+this.EXPORTED_SYMBOLS = ["DOMLocalization"];
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -1,12 +1,12 @@
const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
-const { Services } = ChromeUtils.import('resource://gre/modules/Services.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 */
+Cu.importGlobalProperties(["fetch"]);
/**
* L10nRegistry is a localization resource management system for Gecko.
*
* It manages the list of resource sources provided with the app and allows
* for additional sources to be added and updated.
*
* It's primary purpose is to allow for building an iterator over MessageContext objects
@@ -84,17 +84,17 @@ const L10nRegistry = {
/**
* Based on the list of requested languages and resource Ids,
* this function returns an lazy iterator over message context permutations.
*
* @param {Array} requestedLangs
* @param {Array} resourceIds
* @returns {AsyncIterator<MessageContext>}
*/
- async * generateContexts(requestedLangs, resourceIds) {
+ async* generateContexts(requestedLangs, resourceIds) {
if (this.bootstrap !== null) {
await this.bootstrap;
}
const sourcesOrder = Array.from(this.sources.keys()).reverse();
for (const locale of requestedLangs) {
yield * generateContextsForLocale(locale, sourcesOrder, resourceIds);
}
},
@@ -162,18 +162,18 @@ const L10nRegistry = {
* MessageContexts.
*
* @param {String} locale
* @param {Array} sourcesOrder
* @param {Array} resourceIds
* @returns {String}
*/
function generateContextID(locale, sourcesOrder, resourceIds) {
- const sources = sourcesOrder.join(',');
- const ids = resourceIds.join(',');
+ const sources = sourcesOrder.join(",");
+ const ids = resourceIds.join(",");
return `${locale}|${sources}|${ids}`;
}
/**
* This function generates an iterator over MessageContexts for a single locale
* for a given list of resourceIds for all possible combinations of sources.
*
* This function is called recursively to generate all possible permutations
@@ -214,17 +214,17 @@ 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 = {
+const MSG_CONTEXT_OPTIONS = {
// Temporarily disable bidi isolation due to Microsoft not supporting FSI/PDI.
// See bug 1439018 for details.
useIsolating: Services.prefs.getBoolPref("intl.l10n.enable-bidi-marks", false),
functions: {
/**
* PLATFORM is a built-in allowing localizers to differentiate message
* variants depending on the target platform.
*/
@@ -237,17 +237,17 @@ const MSG_CONTEXT_OPTIONS = {
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
@@ -355,21 +355,19 @@ class FileSource {
if (this.cache.hasOwnProperty(fullPath)) {
if (this.cache[fullPath] === false) {
return Promise.reject(`The source has no resources for path "${fullPath}"`);
}
if (this.cache[fullPath].then) {
return this.cache[fullPath];
}
- } else {
- if (this.indexed) {
+ } else if (this.indexed) {
return Promise.reject(`The source has no resources for path "${fullPath}"`);
}
- }
return this.cache[fullPath] = L10nRegistry.load(fullPath).then(
data => {
return this.cache[fullPath] = data;
},
err => {
this.cache[fullPath] = false;
return Promise.reject(err);
}
@@ -412,17 +410,17 @@ class IndexedFileSource extends FileSour
*
* @returns {Promise<string>}
*/
L10nRegistry.load = function(url) {
return fetch(url).then(response => {
if (!response.ok) {
return Promise.reject(response.statusText);
}
- return response.text()
+ return response.text();
});
};
this.L10nRegistry = L10nRegistry;
this.FileSource = FileSource;
this.IndexedFileSource = IndexedFileSource;
-this.EXPORTED_SYMBOLS = ['L10nRegistry', 'FileSource', 'IndexedFileSource'];
+this.EXPORTED_SYMBOLS = ["L10nRegistry", "FileSource", "IndexedFileSource"];
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -11,25 +11,24 @@
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* fluent@0.6.0 */
+/* fluent@0.6.3 */
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
/* global console */
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
-const LocaleService = Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
-const ObserverService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
/*
* CachedIterable caches the elements yielded by an iterable.
*
* It can be used to iterate over an iterable many times without depleting the
* iterable.
*/
class CachedIterable {
@@ -40,17 +39,17 @@ class CachedIterable {
* @returns {CachedIterable}
*/
constructor(iterable) {
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.');
+ throw new TypeError("Argument must implement the iteration protocol.");
}
this.seen = [];
}
[Symbol.iterator]() {
const { seen, iterator } = this;
let cur = 0;
@@ -99,34 +98,34 @@ class CachedIterable {
* mechanism to be triggered vs errors that should be reported, but
* do not prevent the message from being used.
*
* An example of an L10nError is a missing entry.
*/
class L10nError extends Error {
constructor(message) {
super();
- this.name = 'L10nError';
+ this.name = "L10nError";
this.message = message;
}
}
/**
* The default localization strategy for Gecko. It comabines locales
* available in L10nRegistry, with locales requested by the user to
* generate the iterator over MessageContexts.
*
* 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 defaultGenerateMessages(resourceIds) {
const availableLocales = L10nRegistry.getAvailableLocales();
- const appLocales = LocaleService.getAppLocalesAsLangTags();
+ const appLocales = Services.locale.getAppLocalesAsLangTags();
return L10nRegistry.generateContexts(appLocales, resourceIds);
}
/**
* The `Localization` class is a central high-level API for vanilla
* JavaScript use of Fluent.
* It combines language negotiation, MessageContext and I/O to
* provide a scriptable API to format translations.
@@ -157,17 +156,17 @@ class Localization {
* @returns {Promise<Array<string|Object>>}
* @private
*/
async formatWithFallback(keys, method) {
const translations = [];
for await (let ctx of this.ctxs) {
// This can operate on synchronous and asynchronous
// contexts coming from the iterator.
- if (typeof ctx.then === 'function') {
+ if (typeof ctx.then === "function") {
ctx = await ctx;
}
const errors = keysFromContext(method, ctx, keys, translations);
if (!errors) {
break;
}
}
return translations;
@@ -249,36 +248,36 @@ class Localization {
const [val] = await this.formatValues([[id, args]]);
return val;
}
/**
* Register weak observers on events that will trigger cache invalidation
*/
registerObservers() {
- ObserverService.addObserver(this, 'intl:app-locales-changed', true);
+ Services.obs.addObserver(this, 'intl:app-locales-changed', true);
}
/**
* Unregister observers on events that will trigger cache invalidation
*/
unregisterObservers() {
- ObserverService.removeObserver(this, 'intl:app-locales-changed');
+ Services.obs.removeObserver(this, 'intl:app-locales-changed');
}
/**
* Default observer handler method.
*
* @param {String} subject
* @param {String} topic
* @param {Object} data
*/
observe(subject, topic, data) {
switch (topic) {
- case 'intl:app-locales-changed':
+ case "intl:app-locales-changed":
this.onLanguageChange();
break;
default:
break;
}
}
/**
@@ -430,9 +429,9 @@ function keysFromContext(method, ctx, ke
messageErrors.forEach(error => console.warn(error));
}
});
return hasErrors;
}
this.Localization = Localization;
-this.EXPORTED_SYMBOLS = ['Localization'];
+this.EXPORTED_SYMBOLS = ["Localization"];
--- a/intl/l10n/MessageContext.jsm
+++ b/intl/l10n/MessageContext.jsm
@@ -11,17 +11,17 @@
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/* fluent@0.6.0 */
+/* fluent@0.6.3 */
/* eslint no-magic-numbers: [0] */
const MAX_PLACEABLES = 100;
const entryIdentifierRe = /-?[a-zA-Z][a-zA-Z0-9_-]*/y;
const identifierRe = /[a-zA-Z][a-zA-Z0-9_-]*/y;
const functionIdentifierRe = /^[A-Z][A-Z_?-]*$/;
@@ -81,58 +81,58 @@ class RuntimeParser {
* and add it to object's entries property.
*
* @private
*/
getEntry() {
// The index here should either be at the beginning of the file
// or right after new line.
if (this._index !== 0 &&
- this._source[this._index - 1] !== '\n') {
+ this._source[this._index - 1] !== "\n") {
throw this.error(`Expected an entry to start
at the beginning of the file or on a new line.`);
}
const ch = this._source[this._index];
// We don't care about comments or sections at runtime
- if (ch === '/' ||
- (ch === '#' &&
- [' ', '#', '\n'].includes(this._source[this._index + 1]))) {
+ if (ch === "/" ||
+ (ch === "#" &&
+ [" ", "#", "\n"].includes(this._source[this._index + 1]))) {
this.skipComment();
return;
}
- if (ch === '[') {
+ if (ch === "[") {
this.skipSection();
return;
}
this.getMessage();
}
/**
* Skip the section entry from the current index.
*
* @private
*/
skipSection() {
this._index += 1;
- if (this._source[this._index] !== '[') {
+ if (this._source[this._index] !== "[") {
throw this.error('Expected "[[" to open a section');
}
this._index += 1;
this.skipInlineWS();
this.getVariantName();
this.skipInlineWS();
- if (this._source[this._index] !== ']' ||
- this._source[this._index + 1] !== ']') {
+ if (this._source[this._index] !== "]" ||
+ this._source[this._index + 1] !== "]") {
throw this.error('Expected "]]" to close a section');
}
this._index += 2;
}
/**
* Parse the source string from the current index as an FTL message
@@ -140,45 +140,45 @@ class RuntimeParser {
*
* @private
*/
getMessage() {
const id = this.getEntryIdentifier();
this.skipInlineWS();
- if (this._source[this._index] === '=') {
+ if (this._source[this._index] === "=") {
this._index++;
}
this.skipInlineWS();
const val = this.getPattern();
- if (id.startsWith('-') && val === null) {
- throw this.error('Expected term to have a value');
+ if (id.startsWith("-") && val === null) {
+ throw this.error("Expected term to have a value");
}
let attrs = null;
- if (this._source[this._index] === ' ') {
+ if (this._source[this._index] === " ") {
const lineStart = this._index;
this.skipInlineWS();
- if (this._source[this._index] === '.') {
+ if (this._source[this._index] === ".") {
this._index = lineStart;
attrs = this.getAttributes();
}
}
- if (attrs === null && typeof val === 'string') {
+ if (attrs === null && typeof val === "string") {
this.entries[id] = val;
} else {
if (val === null && attrs === null) {
- throw this.error('Expected message to have a value or attributes');
+ throw this.error("Expected message to have a value or attributes");
}
this.entries[id] = {};
if (val !== null) {
this.entries[id].val = val;
}
@@ -190,45 +190,45 @@ class RuntimeParser {
/**
* Skip whitespace.
*
* @private
*/
skipWS() {
let ch = this._source[this._index];
- while (ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r') {
+ while (ch === " " || ch === "\n" || ch === "\t" || ch === "\r") {
ch = this._source[++this._index];
}
}
/**
* Skip inline whitespace (space and \t).
*
* @private
*/
skipInlineWS() {
let ch = this._source[this._index];
- while (ch === ' ' || ch === '\t') {
+ while (ch === " " || ch === "\t") {
ch = this._source[++this._index];
}
}
/**
* Skip blank lines.
*
* @private
*/
skipBlankLines() {
while (true) {
const ptr = this._index;
this.skipInlineWS();
- if (this._source[this._index] === '\n') {
+ if (this._source[this._index] === "\n") {
this._index += 1;
} else {
this._index = ptr;
break;
}
}
}
@@ -266,27 +266,27 @@ class RuntimeParser {
/**
* Get Variant name.
*
* @returns {Object}
* @private
*/
getVariantName() {
- let name = '';
+ let name = "";
const start = this._index;
let cc = this._source.charCodeAt(this._index);
if ((cc >= 97 && cc <= 122) || // a-z
(cc >= 65 && cc <= 90) || // A-Z
cc === 95 || cc === 32) { // _ <space>
cc = this._source.charCodeAt(++this._index);
} else {
- throw this.error('Expected a keyword (starting with [a-zA-Z_])');
+ throw this.error("Expected a keyword (starting with [a-zA-Z_])");
}
while ((cc >= 97 && cc <= 122) || // a-z
(cc >= 65 && cc <= 90) || // A-Z
(cc >= 48 && cc <= 57) || // 0-9
cc === 95 || cc === 45 || cc === 32) { // _- <space>
cc = this._source.charCodeAt(++this._index);
}
@@ -296,17 +296,17 @@ class RuntimeParser {
// If it is, we will backtrack to the last non-space character because
// the keyword cannot end with a space character.
while (this._source.charCodeAt(this._index - 1) === 32) {
this._index--;
}
name += this._source.slice(start, this._index);
- return { type: 'varname', name };
+ return { type: "varname", name };
}
/**
* Get simple string argument enclosed in `"`.
*
* @returns {String}
* @private
*/
@@ -315,18 +315,18 @@ class RuntimeParser {
while (++this._index < this._length) {
const ch = this._source[this._index];
if (ch === '"') {
break;
}
- if (ch === '\n') {
- throw this.error('Unterminated string expression');
+ if (ch === "\n") {
+ throw this.error("Unterminated string expression");
}
}
return this._source.substring(start, this._index++);
}
/**
* Parses a Message pattern.
@@ -338,45 +338,45 @@ class RuntimeParser {
*/
getPattern() {
// We're going to first try to see if the pattern is simple.
// If it is we can just look for the end of the line and read the string.
//
// Then, if either the line contains a placeable opening `{` or the
// next line starts an indentation, we switch to complex pattern.
const start = this._index;
- let eol = this._source.indexOf('\n', this._index);
+ let eol = this._source.indexOf("\n", this._index);
if (eol === -1) {
eol = this._length;
}
const firstLineContent = start !== eol ?
this._source.slice(start, eol) : null;
- if (firstLineContent && firstLineContent.includes('{')) {
+ if (firstLineContent && firstLineContent.includes("{")) {
return this.getComplexPattern();
}
this._index = eol + 1;
this.skipBlankLines();
- if (this._source[this._index] !== ' ') {
+ if (this._source[this._index] !== " ") {
// No indentation means we're done with this message. Callers should check
// if the return value here is null. It may be OK for messages, but not OK
// for terms, attributes and variants.
return firstLineContent;
}
const lineStart = this._index;
this.skipInlineWS();
- if (this._source[this._index] === '.') {
+ if (this._source[this._index] === ".") {
// The pattern is followed by an attribute. Rewind _index to the first
// column of the current line as expected by getAttributes.
this._index = lineStart;
return firstLineContent;
}
if (firstLineContent) {
// It's a multiline pattern which started on the same line as the
@@ -393,73 +393,73 @@ class RuntimeParser {
* or contains escape chars or placeables.
* It does full parsing of complex patterns.
*
* @returns {Array}
* @private
*/
/* eslint-disable complexity */
getComplexPattern() {
- let buffer = '';
+ let buffer = "";
const content = [];
let placeables = 0;
let ch = this._source[this._index];
while (this._index < this._length) {
// This block handles multi-line strings combining strings separated
// by new line.
- if (ch === '\n') {
+ if (ch === "\n") {
this._index++;
// We want to capture the start and end pointers
// around blank lines and add them to the buffer
// but only if the blank lines are in the middle
// of the string.
const blankLinesStart = this._index;
this.skipBlankLines();
const blankLinesEnd = this._index;
- if (this._source[this._index] !== ' ') {
+ if (this._source[this._index] !== " ") {
break;
}
this.skipInlineWS();
- if (this._source[this._index] === '}' ||
- this._source[this._index] === '[' ||
- this._source[this._index] === '*' ||
- this._source[this._index] === '.') {
+ if (this._source[this._index] === "}" ||
+ this._source[this._index] === "[" ||
+ this._source[this._index] === "*" ||
+ this._source[this._index] === ".") {
this._index = blankLinesEnd;
break;
}
buffer += this._source.substring(blankLinesStart, blankLinesEnd);
if (buffer.length || content.length) {
- buffer += '\n';
+ buffer += "\n";
}
ch = this._source[this._index];
continue;
- } else if (ch === '\\') {
+ } else if (ch === "\\") {
const ch2 = this._source[this._index + 1];
- if (ch2 === '"' || ch2 === '{' || ch2 === '\\') {
+ if (ch2 === '"' || ch2 === "{" || ch2 === "\\") {
ch = ch2;
this._index++;
}
- } else if (ch === '{') {
+ } else if (ch === "{") {
// Push the buffer to content array right before placeable
if (buffer.length) {
content.push(buffer);
}
if (placeables > MAX_PLACEABLES - 1) {
throw this.error(
`Too many placeables, maximum allowed is ${MAX_PLACEABLES}`);
}
- buffer = '';
+ buffer = "";
content.push(this.getPlaceable());
this._index++;
ch = this._source[this._index];
placeables++;
continue;
}
@@ -490,143 +490,143 @@ class RuntimeParser {
* @returns {Object}
* @private
*/
getPlaceable() {
const start = ++this._index;
this.skipWS();
- if (this._source[this._index] === '*' ||
- (this._source[this._index] === '[' &&
- this._source[this._index + 1] !== ']')) {
+ if (this._source[this._index] === "*" ||
+ (this._source[this._index] === "[" &&
+ this._source[this._index + 1] !== "]")) {
const variants = this.getVariants();
return {
- type: 'sel',
+ type: "sel",
exp: null,
vars: variants[0],
def: variants[1]
};
}
// Rewind the index and only support in-line white-space now.
this._index = start;
this.skipInlineWS();
const selector = this.getSelectorExpression();
this.skipWS();
const ch = this._source[this._index];
- if (ch === '}') {
- if (selector.type === 'attr' && selector.id.name.startsWith('-')) {
+ if (ch === "}") {
+ if (selector.type === "attr" && selector.id.name.startsWith("-")) {
throw this.error(
- 'Attributes of private messages cannot be interpolated.'
+ "Attributes of private messages cannot be interpolated."
);
}
return selector;
}
- if (ch !== '-' || this._source[this._index + 1] !== '>') {
+ if (ch !== "-" || this._source[this._index + 1] !== ">") {
throw this.error('Expected "}" or "->"');
}
- if (selector.type === 'ref') {
- throw this.error('Message references cannot be used as selectors.');
+ if (selector.type === "ref") {
+ throw this.error("Message references cannot be used as selectors.");
}
- if (selector.type === 'var') {
- throw this.error('Variants cannot be used as selectors.');
+ if (selector.type === "var") {
+ throw this.error("Variants cannot be used as selectors.");
}
- if (selector.type === 'attr' && !selector.id.name.startsWith('-')) {
+ if (selector.type === "attr" && !selector.id.name.startsWith("-")) {
throw this.error(
- 'Attributes of public messages cannot be used as selectors.'
+ "Attributes of public messages cannot be used as selectors."
);
}
this._index += 2; // ->
this.skipInlineWS();
- if (this._source[this._index] !== '\n') {
- throw this.error('Variants should be listed in a new line');
+ if (this._source[this._index] !== "\n") {
+ throw this.error("Variants should be listed in a new line");
}
this.skipWS();
const variants = this.getVariants();
if (variants[0].length === 0) {
- throw this.error('Expected members for the select expression');
+ throw this.error("Expected members for the select expression");
}
return {
- type: 'sel',
+ type: "sel",
exp: selector,
vars: variants[0],
def: variants[1]
};
}
/**
* Parses a selector expression.
*
* @returns {Object}
* @private
*/
getSelectorExpression() {
const literal = this.getLiteral();
- if (literal.type !== 'ref') {
+ if (literal.type !== "ref") {
return literal;
}
- if (this._source[this._index] === '.') {
+ if (this._source[this._index] === ".") {
this._index++;
const name = this.getIdentifier();
this._index++;
return {
- type: 'attr',
+ type: "attr",
id: literal,
name
};
}
- if (this._source[this._index] === '[') {
+ if (this._source[this._index] === "[") {
this._index++;
const key = this.getVariantKey();
this._index++;
return {
- type: 'var',
+ type: "var",
id: literal,
key
};
}
- if (this._source[this._index] === '(') {
+ if (this._source[this._index] === "(") {
this._index++;
const args = this.getCallArgs();
if (!functionIdentifierRe.test(literal.name)) {
- throw this.error('Function names must be all upper-case');
+ throw this.error("Function names must be all upper-case");
}
this._index++;
- literal.type = 'fun';
+ literal.type = "fun";
return {
- type: 'call',
+ type: "call",
fun: literal,
args
};
}
return literal;
}
@@ -637,86 +637,86 @@ class RuntimeParser {
* @private
*/
getCallArgs() {
const args = [];
while (this._index < this._length) {
this.skipInlineWS();
- if (this._source[this._index] === ')') {
+ if (this._source[this._index] === ")") {
return args;
}
const exp = this.getSelectorExpression();
// MessageReference in this place may be an entity reference, like:
// `call(foo)`, or, if it's followed by `:` it will be a key-value pair.
- if (exp.type !== 'ref') {
+ if (exp.type !== "ref") {
args.push(exp);
} else {
this.skipInlineWS();
- if (this._source[this._index] === ':') {
+ if (this._source[this._index] === ":") {
this._index++;
this.skipInlineWS();
const val = this.getSelectorExpression();
// If the expression returned as a value of the argument
// is not a quote delimited string or number, throw.
//
// We don't have to check here if the pattern is quote delimited
// because that's the only type of string allowed in expressions.
- if (typeof val === 'string' ||
+ if (typeof val === "string" ||
Array.isArray(val) ||
- val.type === 'num') {
+ val.type === "num") {
args.push({
- type: 'narg',
+ type: "narg",
name: exp.name,
val
});
} else {
- this._index = this._source.lastIndexOf(':', this._index) + 1;
+ this._index = this._source.lastIndexOf(":", this._index) + 1;
throw this.error(
- 'Expected string in quotes, number.');
+ "Expected string in quotes, number.");
}
} else {
args.push(exp);
}
}
this.skipInlineWS();
- if (this._source[this._index] === ')') {
+ if (this._source[this._index] === ")") {
break;
- } else if (this._source[this._index] === ',') {
+ } else if (this._source[this._index] === ",") {
this._index++;
} else {
throw this.error('Expected "," or ")"');
}
}
return args;
}
/**
* Parses an FTL Number.
*
* @returns {Object}
* @private
*/
getNumber() {
- let num = '';
+ let num = "";
let cc = this._source.charCodeAt(this._index);
// The number literal may start with negative sign `-`.
if (cc === 45) {
- num += '-';
+ num += "-";
cc = this._source.charCodeAt(++this._index);
}
// next, we expect at least one digit
if (cc < 48 || cc > 57) {
throw this.error(`Unknown literal "${num}"`);
}
@@ -739,59 +739,59 @@ class RuntimeParser {
// and optionally more digits
while (cc >= 48 && cc <= 57) {
num += this._source[this._index++];
cc = this._source.charCodeAt(this._index);
}
}
return {
- type: 'num',
+ type: "num",
val: num
};
}
/**
* Parses a list of Message attributes.
*
* @returns {Object}
* @private
*/
getAttributes() {
const attrs = {};
while (this._index < this._length) {
- if (this._source[this._index] !== ' ') {
+ if (this._source[this._index] !== " ") {
break;
}
this.skipInlineWS();
- if (this._source[this._index] !== '.') {
+ if (this._source[this._index] !== ".") {
break;
}
this._index++;
const key = this.getIdentifier();
this.skipInlineWS();
- if (this._source[this._index] !== '=') {
+ if (this._source[this._index] !== "=") {
throw this.error('Expected "="');
}
this._index++;
this.skipInlineWS();
const val = this.getPattern();
if (val === null) {
- throw this.error('Expected attribute to have a value');
+ throw this.error("Expected attribute to have a value");
}
- if (typeof val === 'string') {
+ if (typeof val === "string") {
attrs[key] = val;
} else {
attrs[key] = {
val
};
}
this.skipBlankLines();
@@ -809,39 +809,39 @@ class RuntimeParser {
getVariants() {
const variants = [];
let index = 0;
let defaultIndex;
while (this._index < this._length) {
const ch = this._source[this._index];
- if ((ch !== '[' || this._source[this._index + 1] === '[') &&
- ch !== '*') {
+ if ((ch !== "[" || this._source[this._index + 1] === "[") &&
+ ch !== "*") {
break;
}
- if (ch === '*') {
+ if (ch === "*") {
this._index++;
defaultIndex = index;
}
- if (this._source[this._index] !== '[') {
+ if (this._source[this._index] !== "[") {
throw this.error('Expected "["');
}
this._index++;
const key = this.getVariantKey();
this.skipInlineWS();
const val = this.getPattern();
if (val === null) {
- throw this.error('Expected variant to have a value');
+ throw this.error("Expected variant to have a value");
}
variants[index++] = {key, val};
this.skipWS();
}
return [variants, defaultIndex];
@@ -860,17 +860,17 @@ class RuntimeParser {
let literal;
if ((cc >= 48 && cc <= 57) || cc === 45) {
literal = this.getNumber();
} else {
literal = this.getVariantName();
}
- if (this._source[this._index] !== ']') {
+ if (this._source[this._index] !== "]") {
throw this.error('Expected "]"');
}
this._index++;
return literal;
}
/**
@@ -880,63 +880,63 @@ class RuntimeParser {
* @private
*/
getLiteral() {
const cc0 = this._source.charCodeAt(this._index);
if (cc0 === 36) { // $
this._index++;
return {
- type: 'ext',
+ type: "ext",
name: this.getIdentifier()
};
}
const cc1 = cc0 === 45 // -
// Peek at the next character after the dash.
? this._source.charCodeAt(this._index + 1)
// Or keep using the character at the current index.
: cc0;
if ((cc1 >= 97 && cc1 <= 122) || // a-z
(cc1 >= 65 && cc1 <= 90)) { // A-Z
return {
- type: 'ref',
+ type: "ref",
name: this.getEntryIdentifier()
};
}
if ((cc1 >= 48 && cc1 <= 57)) { // 0-9
return this.getNumber();
}
if (cc0 === 34) { // "
return this.getString();
}
- throw this.error('Expected literal');
+ throw this.error("Expected literal");
}
/**
* Skips an FTL comment.
*
* @private
*/
skipComment() {
// At runtime, we don't care about comments so we just have
// to parse them properly and skip their content.
- let eol = this._source.indexOf('\n', this._index);
+ let eol = this._source.indexOf("\n", this._index);
while (eol !== -1 &&
- ((this._source[eol + 1] === '/' && this._source[eol + 2] === '/') ||
- (this._source[eol + 1] === '#' &&
- [' ', '#'].includes(this._source[eol + 2])))) {
+ ((this._source[eol + 1] === "/" && this._source[eol + 2] === "/") ||
+ (this._source[eol + 1] === "#" &&
+ [" ", "#"].includes(this._source[eol + 2])))) {
this._index = eol + 3;
- eol = this._source.indexOf('\n', this._index);
+ eol = this._source.indexOf("\n", this._index);
if (eol === -1) {
break;
}
}
if (eol === -1) {
this._index = this._length;
@@ -962,28 +962,28 @@ class RuntimeParser {
* and recover from the returned position.
*
* @private
*/
skipToNextEntryStart() {
let start = this._index;
while (true) {
- if (start === 0 || this._source[start - 1] === '\n') {
+ if (start === 0 || this._source[start - 1] === "\n") {
const cc = this._source.charCodeAt(start);
if ((cc >= 97 && cc <= 122) || // a-z
(cc >= 65 && cc <= 90) || // A-Z
cc === 47 || cc === 91) { // /[
this._index = start;
return;
}
}
- start = this._source.indexOf('\n', start);
+ start = this._source.indexOf("\n", start);
if (start === -1) {
this._index = this._length;
return;
}
start++;
}
}
@@ -1039,23 +1039,23 @@ class FluentType {
* Formatted values are suitable for use outside of the `MessageContext`.
* This method can use `Intl` formatters memoized by the `MessageContext`
* instance passed as an argument.
*
* @param {MessageContext} [ctx]
* @returns {string}
*/
toString() {
- throw new Error('Subclasses of FluentType must implement toString.');
+ throw new Error("Subclasses of FluentType must implement toString.");
}
}
class FluentNone extends FluentType {
toString() {
- return this.value || '???';
+ return this.value || "???";
}
}
class FluentNumber extends FluentType {
constructor(value, opts) {
super(parseFloat(value), opts);
}
@@ -1114,17 +1114,17 @@ class FluentSymbol extends FluentType {
*
* @param {MessageContext} ctx
* @param {FluentType} other
* @returns {bool}
*/
match(ctx, other) {
if (other instanceof FluentSymbol) {
return this.value === other.value;
- } else if (typeof other === 'string') {
+ } else if (typeof other === "string") {
return this.value === other;
} else if (other instanceof FluentNumber) {
const pr = ctx._memoizeIntlObject(
Intl.PluralRules, other.opts
);
return this.value === pr.select(other.value);
}
return false;
@@ -1140,19 +1140,19 @@ class FluentSymbol extends FluentType {
* - args - an array of positional args
* - opts - an object of key-value args
*
* Arguments to functions are guaranteed to already be instances of
* `FluentType`. Functions must return `FluentType` objects as well.
*/
const builtins = {
- 'NUMBER': ([arg], opts) =>
+ "NUMBER": ([arg], opts) =>
new FluentNumber(arg.valueOf(), merge(arg.opts, opts)),
- 'DATETIME': ([arg], opts) =>
+ "DATETIME": ([arg], opts) =>
new FluentDateTime(arg.valueOf(), merge(arg.opts, opts)),
};
function merge(argopts, opts) {
return Object.assign({}, argopts, values(opts));
}
function values(opts) {
@@ -1206,23 +1206,22 @@ function values(opts) {
* 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.
*/
-
// Prevent expansion of too long placeables.
const MAX_PLACEABLE_LENGTH = 2500;
// Unicode bidi isolation characters.
-const FSI = '\u2068';
-const PDI = '\u2069';
+const FSI = "\u2068";
+const PDI = "\u2069";
/**
* Helper for choosing the default value from a set of members.
*
* Used in SelectExpressions and Type.
*
* @param {Object} env
@@ -1235,17 +1234,17 @@ const PDI = '\u2069';
* @private
*/
function DefaultMember(env, members, def) {
if (members[def]) {
return members[def];
}
const { errors } = env;
- errors.push(new RangeError('No default'));
+ errors.push(new RangeError("No default"));
return new FluentNone();
}
/**
* Resolve a reference to another message.
*
* @param {Object} env
@@ -1254,22 +1253,22 @@ function DefaultMember(env, members, def
* The identifier of the message to be resolved.
* @param {String} id.name
* The name of the identifier.
* @returns {FluentType}
* @private
*/
function MessageReference(env, {name}) {
const { ctx, errors } = env;
- const message = name.startsWith('-')
+ const message = name.startsWith("-")
? ctx._terms.get(name)
: ctx._messages.get(name);
if (!message) {
- const err = name.startsWith('-')
+ const err = name.startsWith("-")
? new ReferenceError(`Unknown term: ${name}`)
: new ReferenceError(`Unknown message: ${name}`);
errors.push(err);
return new FluentNone(name);
}
return message;
}
@@ -1296,17 +1295,17 @@ function VariantExpression(env, {id, key
return message;
}
const { ctx, errors } = env;
const keyword = Type(env, key);
function isVariantList(node) {
return Array.isArray(node) &&
- node[0].type === 'sel' &&
+ node[0].type === "sel" &&
node[0].exp === null;
}
if (isVariantList(message.val)) {
// Match the specified key against keys of each variant, in order.
for (const variant of message.val[0].vars) {
const variantKey = Type(env, variant.key);
if (keyword.match(ctx, variantKey)) {
@@ -1413,62 +1412,62 @@ function SelectExpression(env, {exp, var
* @param {Object} expr
* An expression object to be resolved into a Fluent type.
* @returns {FluentType}
* @private
*/
function Type(env, expr) {
// A fast-path for strings which are the most common case, and for
// `FluentNone` which doesn't require any additional logic.
- if (typeof expr === 'string' || expr instanceof FluentNone) {
+ if (typeof expr === "string" || expr instanceof FluentNone) {
return expr;
}
// The Runtime AST (Entries) encodes patterns (complex strings with
// placeables) as Arrays.
if (Array.isArray(expr)) {
return Pattern(env, expr);
}
switch (expr.type) {
- case 'varname':
+ case "varname":
return new FluentSymbol(expr.name);
- case 'num':
+ case "num":
return new FluentNumber(expr.val);
- case 'ext':
+ case "ext":
return ExternalArgument(env, expr);
- case 'fun':
+ case "fun":
return FunctionReference(env, expr);
- case 'call':
+ case "call":
return CallExpression(env, expr);
- case 'ref': {
+ case "ref": {
const message = MessageReference(env, expr);
return Type(env, message);
}
- case 'attr': {
+ case "attr": {
const attr = AttributeExpression(env, expr);
return Type(env, attr);
}
- case 'var': {
+ case "var": {
const variant = VariantExpression(env, expr);
return Type(env, variant);
}
- case 'sel': {
+ case "sel": {
const member = SelectExpression(env, expr);
return Type(env, member);
}
case undefined: {
// If it's a node with a value, resolve the value.
if (expr.val !== null && expr.val !== undefined) {
return Type(env, expr.val);
}
const { errors } = env;
- errors.push(new RangeError('No value'));
+ errors.push(new RangeError("No value"));
return new FluentNone();
}
default:
return new FluentNone();
}
}
/**
@@ -1495,21 +1494,21 @@ function ExternalArgument(env, {name}) {
// Return early if the argument already is an instance of FluentType.
if (arg instanceof FluentType) {
return arg;
}
// Convert the argument to a Fluent type.
switch (typeof arg) {
- case 'string':
+ case "string":
return arg;
- case 'number':
+ case "number":
return new FluentNumber(arg);
- case 'object':
+ case "object":
if (arg instanceof Date) {
return new FluentDateTime(arg);
}
default:
errors.push(
new TypeError(`Unsupported external type: ${name}, ${typeof arg}`)
);
return new FluentNone(name);
@@ -1534,17 +1533,17 @@ function FunctionReference(env, {name})
const { ctx: { _functions }, errors } = env;
const func = _functions[name] || builtins[name];
if (!func) {
errors.push(new ReferenceError(`Unknown function: ${name}()`));
return new FluentNone(`${name}()`);
}
- if (typeof func !== 'function') {
+ if (typeof func !== "function") {
errors.push(new TypeError(`Function ${name}() is not callable`));
return new FluentNone(`${name}()`);
}
return func;
}
/**
@@ -1567,17 +1566,17 @@ function CallExpression(env, {fun, args}
if (callee instanceof FluentNone) {
return callee;
}
const posargs = [];
const keyargs = {};
for (const arg of args) {
- if (arg.type === 'narg') {
+ if (arg.type === "narg") {
keyargs[arg.name] = Type(env, arg.val);
} else {
posargs.push(Type(env, arg));
}
}
try {
return callee(posargs, keyargs);
@@ -1596,55 +1595,55 @@ function CallExpression(env, {fun, args}
* Array of pattern elements.
* @returns {Array}
* @private
*/
function Pattern(env, ptn) {
const { ctx, dirty, errors } = env;
if (dirty.has(ptn)) {
- errors.push(new RangeError('Cyclic reference'));
+ errors.push(new RangeError("Cyclic reference"));
return new FluentNone();
}
// Tag the pattern as dirty for the purpose of the current resolution.
dirty.add(ptn);
const result = [];
for (const elem of ptn) {
- if (typeof elem === 'string') {
+ if (typeof elem === "string") {
result.push(elem);
continue;
}
const part = Type(env, elem).toString(ctx);
if (ctx._useIsolating) {
result.push(FSI);
}
if (part.length > MAX_PLACEABLE_LENGTH) {
errors.push(
new RangeError(
- 'Too many characters in placeable ' +
+ "Too many characters in placeable " +
`(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})`
)
);
result.push(part.slice(MAX_PLACEABLE_LENGTH));
} else {
result.push(part);
}
if (ctx._useIsolating) {
result.push(PDI);
}
}
dirty.delete(ptn);
- return result.join('');
+ return result.join("");
}
/**
* Format a translation into a string.
*
* @param {MessageContext} ctx
* A MessageContext instance which will be used to resolve the
* contextual information of the message.
@@ -1768,17 +1767,17 @@ class MessageContext {
* contain logic (references, select expressions etc.).
*
* @param {string} source - Text resource with translations.
* @returns {Array<Error>}
*/
addMessages(source) {
const [entries, errors] = parse(source);
for (const id in entries) {
- if (id.startsWith('-')) {
+ if (id.startsWith("-")) {
// Identifiers starting with a dash (-) define terms. Terms are private
// and cannot be retrieved from MessageContext.
this._terms.set(id, entries[id]);
} else {
this._messages.set(id, entries[id]);
}
}
@@ -1812,22 +1811,22 @@ class MessageContext {
*
* @param {Object | string} message
* @param {Object | undefined} args
* @param {Array} errors
* @returns {?string}
*/
format(message, args, errors) {
// optimize entities which are simple strings with no attributes
- if (typeof message === 'string') {
+ if (typeof message === "string") {
return message;
}
// optimize simple-string entities with attributes
- if (typeof message.val === 'string') {
+ if (typeof message.val === "string") {
return message.val;
}
// optimize entities with null values
if (message.val === undefined) {
return null;
}
@@ -1843,9 +1842,9 @@ class MessageContext {
this._intls.set(ctor, cache);
}
return cache[id];
}
}
this.MessageContext = MessageContext;
-this.EXPORTED_SYMBOLS = ['MessageContext'];
+this.EXPORTED_SYMBOLS = ["MessageContext"];
--- a/intl/l10n/l10n.js
+++ b/intl/l10n/l10n.js
@@ -1,69 +1,69 @@
{
const { DOMLocalization } =
- ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
+ ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
/**
* Polyfill for document.ready polyfill.
* See: https://github.com/whatwg/html/issues/127 for details.
*
* XXX: The callback is a temporary workaround for bug 1193394. Once Promises in Gecko
* start beeing a microtask and stop pushing translation post-layout, we can
* remove it and start using the returned Promise again.
*
* @param {Function} callback - function to be called when the document is ready.
* @returns {Promise}
*/
function documentReady(callback) {
- if (document.contentType === 'application/vnd.mozilla.xul+xml') {
+ if (document.contentType === "application/vnd.mozilla.xul+xml") {
// XUL
return new Promise(
resolve => document.addEventListener(
- 'MozBeforeInitialXULLayout', () => {
+ "MozBeforeInitialXULLayout", () => {
resolve(callback());
}, { once: true }
)
);
}
// HTML
const rs = document.readyState;
- if (rs === 'interactive' || rs === 'completed') {
- return Promise.resolve(callback());
+ if (rs === "interactive" || rs === "completed") {
+ return Promise.resolve(callback);
}
return new Promise(
resolve => document.addEventListener(
- 'readystatechange', () => {
+ "readystatechange", () => {
resolve(callback());
}, { once: true }
)
);
}
/**
* Scans the `elem` for links with localization resources.
*
* @param {Element} elem
* @returns {Array<string>}
*/
function getResourceLinks(elem) {
return Array.from(elem.querySelectorAll('link[rel="localization"]')).map(
- el => el.getAttribute('href')
+ el => el.getAttribute("href")
);
}
const resourceIds = getResourceLinks(document.head || document);
document.l10n = new DOMLocalization(window, resourceIds);
// trigger first context to be fetched eagerly
document.l10n.ctxs.touchNext();
document.l10n.ready = documentReady(() => {
document.l10n.registerObservers();
- window.addEventListener('unload', () => {
+ window.addEventListener("unload", () => {
document.l10n.unregisterObservers();
});
document.l10n.connectRoot(document.documentElement);
return document.l10n.translateRoots();
});
}
new file mode 100644
--- /dev/null
+++ b/intl/l10n/test/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "plugin:mozilla/xpcshell-test",
+ "plugin:mozilla/browser-test"
+ ]
+};
--- a/intl/l10n/test/dom/test_domloc_connectRoot.html
+++ b/intl/l10n/test/dom/test_domloc_connectRoot.html
@@ -5,30 +5,30 @@
<title>Test DOMLocalization.prototype.connectRoot</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const frag = document.querySelectorAll('div')[0];
+ const frag = document.querySelectorAll("div")[0];
domLoc.connectRoot(frag);
is(domLoc.roots.has(frag), true);
SimpleTest.finish();
};
</script>
</head>
--- a/intl/l10n/test/dom/test_domloc_disconnectRoot.html
+++ b/intl/l10n/test/dom/test_domloc_disconnectRoot.html
@@ -5,29 +5,29 @@
<title>Test DOMLocalization.prototype.disconnectRoot</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const frag = document.querySelectorAll('div')[0];
+ const frag = document.querySelectorAll("div")[0];
domLoc.connectRoot(frag);
is(domLoc.roots.has(frag), true);
domLoc.disconnectRoot(frag);
is(domLoc.roots.has(frag), false);
SimpleTest.finish();
--- a/intl/l10n/test/dom/test_domloc_getAttributes.html
+++ b/intl/l10n/test/dom/test_domloc_getAttributes.html
@@ -5,30 +5,30 @@
<title>Test DOMLocalization.prototype.getAttributes</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {}
+ async function* mockGenerateMessages(locales, resourceIds) {}
- window.onload = function () {
+ window.onload = function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const p1 = document.querySelectorAll('p')[0];
- const p2 = document.querySelectorAll('p')[1];
- const p3 = document.querySelectorAll('p')[2];
+ const p1 = document.querySelectorAll("p")[0];
+ const p2 = document.querySelectorAll("p")[1];
+ const p3 = document.querySelectorAll("p")[2];
const attrs1 = domLoc.getAttributes(p1);
const attrs2 = domLoc.getAttributes(p2);
const attrs3 = domLoc.getAttributes(p3);
isDeeply(attrs1, {
id: null,
args: null
});
isDeeply(attrs2, {
--- a/intl/l10n/test/dom/test_domloc_mutations.html
+++ b/intl/l10n/test/dom/test_domloc_mutations.html
@@ -7,50 +7,50 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Hello World');
- mc.addMessages('title2 = Hello Another World');
+ mc.addMessages("title = Hello World");
+ mc.addMessages("title2 = Hello Another World");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const h1 = document.querySelectorAll('h1')[0];
+ const h1 = document.querySelectorAll("h1")[0];
domLoc.connectRoot(document.body);
await domLoc.translateRoots();
is(h1.textContent, "Hello World");
const mo = new MutationObserver(function onMutations(mutations) {
is(h1.textContent, "Hello Another World");
mo.disconnect();
SimpleTest.finish();
});
mo.observe(h1, { childList: true, characterData: true });
- domLoc.setAttributes(h1, 'title2');
+ domLoc.setAttributes(h1, "title2");
};
</script>
</head>
<body>
<div>
<h1 data-l10n-id="title"></h1>
</div>
</body>
--- a/intl/l10n/test/dom/test_domloc_overlay.html
+++ b/intl/l10n/test/dom/test_domloc_overlay.html
@@ -7,46 +7,46 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = <strong>Hello</strong> World');
- mc.addMessages('title2 = This is <a>a link</a>!');
+ mc.addMessages("title = <strong>Hello</strong> World");
+ mc.addMessages("title2 = This is <a>a link</a>!");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const p1 = document.querySelectorAll('p')[0];
- const p2 = document.querySelectorAll('p')[1];
- const a = p2.querySelector('a');
- a.addEventListener('click', function() {
+ const p1 = document.querySelectorAll("p")[0];
+ const p2 = document.querySelectorAll("p")[1];
+ const a = p2.querySelector("a");
+ a.addEventListener("click", function() {
SimpleTest.finish();
});
await domLoc.translateFragment(document.body);
- is(p1.querySelector('strong').textContent, "Hello");
+ is(p1.querySelector("strong").textContent, "Hello");
- is(p2.querySelector('a').getAttribute('href'), "http://www.mozilla.org");
- is(p2.querySelector('a').textContent, "a link");
+ is(p2.querySelector("a").getAttribute("href"), "http://www.mozilla.org");
+ is(p2.querySelector("a").textContent, "a link");
a.click();
};
</script>
</head>
<body>
<p data-l10n-id="title" />
<p data-l10n-id="title2">
--- a/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
@@ -7,41 +7,41 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Visit <a>Mozilla</a> or <a>Firefox</a> website!');
+ mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
await domLoc.translateFragment(document.body);
- const p1 = document.querySelectorAll('p')[0];
- const linkList = p1.querySelectorAll('a');
+ const p1 = document.querySelectorAll("p")[0];
+ const linkList = p1.querySelectorAll("a");
- is(linkList[0].getAttribute('href'), 'http://www.mozilla.org');
- is(linkList[0].textContent, 'Mozilla');
- is(linkList[1].getAttribute('href'), 'http://www.firefox.com');
- is(linkList[1].textContent, 'Firefox');
+ is(linkList[0].getAttribute("href"), "http://www.mozilla.org");
+ is(linkList[0].textContent, "Mozilla");
+ is(linkList[1].getAttribute("href"), "http://www.firefox.com");
+ is(linkList[1].textContent, "Firefox");
is(linkList.length, 2, "There should be exactly two links in the result.");
SimpleTest.finish();
};
</script>
</head>
<body>
--- a/intl/l10n/test/dom/test_domloc_overlay_repeated.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_repeated.html
@@ -7,41 +7,41 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Visit <a>Mozilla</a> or <a>Firefox</a> website!');
+ mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
await domLoc.translateFragment(document.body);
- const p1 = document.querySelectorAll('p')[0];
- const linkList = p1.querySelectorAll('a');
+ const p1 = document.querySelectorAll("p")[0];
+ const linkList = p1.querySelectorAll("a");
- is(linkList[0].getAttribute('href'), 'http://www.mozilla.org');
- is(linkList[0].textContent, 'Mozilla');
- is(linkList[1].getAttribute('href'), 'http://www.firefox.com');
- is(linkList[1].textContent, 'Firefox');
+ is(linkList[0].getAttribute("href"), "http://www.mozilla.org");
+ is(linkList[0].textContent, "Mozilla");
+ is(linkList[1].getAttribute("href"), "http://www.firefox.com");
+ is(linkList[1].textContent, "Firefox");
SimpleTest.finish();
};
</script>
</head>
<body>
<p data-l10n-id="title">
<a href="http://www.mozilla.org"></a>
--- a/intl/l10n/test/dom/test_domloc_setAttributes.html
+++ b/intl/l10n/test/dom/test_domloc_setAttributes.html
@@ -5,35 +5,35 @@
<title>Test DOMLocalization.prototype.setAttributes</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {}
+ async function* mockGenerateMessages(locales, resourceIds) {}
- window.onload = function () {
+ window.onload = function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const p1 = document.querySelectorAll('p')[0];
+ const p1 = document.querySelectorAll("p")[0];
- domLoc.setAttributes(p1, 'title');
- is(p1.getAttribute('data-l10n-id'), 'title');
+ domLoc.setAttributes(p1, "title");
+ is(p1.getAttribute("data-l10n-id"), "title");
- domLoc.setAttributes(p1, 'title2', {userName: "John"});
- is(p1.getAttribute('data-l10n-id'), 'title2');
- is(p1.getAttribute('data-l10n-args'), JSON.stringify({userName: "John"}));
+ domLoc.setAttributes(p1, "title2", {userName: "John"});
+ is(p1.getAttribute("data-l10n-id"), "title2");
+ is(p1.getAttribute("data-l10n-args"), JSON.stringify({userName: "John"}));
SimpleTest.finish();
};
</script>
</head>
<body>
<p />
</body>
--- a/intl/l10n/test/dom/test_domloc_translateElements.html
+++ b/intl/l10n/test/dom/test_domloc_translateElements.html
@@ -7,39 +7,39 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Hello World');
- mc.addMessages('link\n .title = Click me');
+ mc.addMessages("title = Hello World");
+ mc.addMessages("link\n .title = Click me");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const p1 = document.querySelectorAll('p')[0];
- const link1 = document.querySelectorAll('a')[0];
+ const p1 = document.querySelectorAll("p")[0];
+ const link1 = document.querySelectorAll("a")[0];
await domLoc.translateElements([p1, link1]);
is(p1.textContent, "Hello World");
- is(link1.getAttribute('title'), "Click me");
+ is(link1.getAttribute("title"), "Click me");
SimpleTest.finish();
};
</script>
</head>
<body>
<p data-l10n-id="title" />
<a data-l10n-id="link" />
--- a/intl/l10n/test/dom/test_domloc_translateFragment.html
+++ b/intl/l10n/test/dom/test_domloc_translateFragment.html
@@ -7,35 +7,35 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Hello World');
- mc.addMessages('subtitle = Welcome to Fluent');
+ mc.addMessages("title = Hello World");
+ mc.addMessages("subtitle = Welcome to Fluent");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const frag = document.querySelectorAll('div')[0];
- const h1 = document.querySelectorAll('h1')[0];
- const p1 = document.querySelectorAll('p')[0];
+ const frag = document.querySelectorAll("div")[0];
+ const h1 = document.querySelectorAll("h1")[0];
+ const p1 = document.querySelectorAll("p")[0];
await domLoc.translateFragment(frag);
is(h1.textContent, "Hello World");
is(p1.textContent, "Welcome to Fluent");
SimpleTest.finish();
};
</script>
--- a/intl/l10n/test/dom/test_domloc_translateRoots.html
+++ b/intl/l10n/test/dom/test_domloc_translateRoots.html
@@ -7,36 +7,36 @@
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript">
"use strict";
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { MessageContext } =
ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
- async function * mockGenerateMessages(locales, resourceIds) {
+ async function* mockGenerateMessages(locales, resourceIds) {
const mc = new MessageContext(locales);
- mc.addMessages('title = Hello World');
- mc.addMessages('title2 = Hello Another World');
+ mc.addMessages("title = Hello World");
+ mc.addMessages("title2 = Hello Another World");
yield mc;
}
- window.onload = async function () {
+ window.onload = async function() {
SimpleTest.waitForExplicitFinish();
const domLoc = new DOMLocalization(
window,
[],
mockGenerateMessages
);
- const frag1 = document.querySelectorAll('div')[0];
- const frag2 = document.querySelectorAll('div')[1];
- const h1 = document.querySelectorAll('h1')[0];
- const h2 = document.querySelectorAll('h2')[0];
+ const frag1 = document.querySelectorAll("div")[0];
+ const frag2 = document.querySelectorAll("div")[1];
+ const h1 = document.querySelectorAll("h1")[0];
+ const h2 = document.querySelectorAll("h2")[0];
domLoc.connectRoot(frag1);
domLoc.connectRoot(frag2);
await domLoc.translateRoots();
is(h1.textContent, "Hello World");
is(h2.textContent, "Hello Another World");
--- a/intl/l10n/test/test_l10nregistry.js
+++ b/intl/l10n/test/test_l10nregistry.js
@@ -6,348 +6,348 @@ const {
FileSource,
IndexedFileSource
} = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
ChromeUtils.import("resource://gre/modules/Timer.jsm");
let fs;
L10nRegistry.load = async function(url) {
if (!fs.hasOwnProperty(url)) {
- return Promise.reject('Resource unavailable');
+ return Promise.reject("Resource unavailable");
}
return fs[url];
-}
+};
add_task(function test_methods_presence() {
equal(typeof L10nRegistry.generateContexts, "function");
equal(typeof L10nRegistry.getAvailableLocales, "function");
equal(typeof L10nRegistry.registerSource, "function");
equal(typeof L10nRegistry.updateSource, "function");
});
/**
* This test tests generation of a proper context for a single
* source scenario
*/
add_task(async function test_methods_calling() {
fs = {
- '/localization/en-US/browser/menu.ftl': 'key = Value',
+ "/localization/en-US/browser/menu.ftl": "key = Value",
};
- const source = new FileSource('test', ['en-US'], '/localization/{locale}');
+ const source = new FileSource("test", ["en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
- const ctxs = L10nRegistry.generateContexts(['en-US'], ['/browser/menu.ftl']);
+ const ctxs = L10nRegistry.generateContexts(["en-US"], ["/browser/menu.ftl"]);
const ctx = (await ctxs.next()).value;
- equal(ctx.hasMessage('key'), true);
+ equal(ctx.hasMessage("key"), true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that the public methods return expected values
* for the single source scenario
*/
add_task(async function test_has_one_source() {
- let oneSource = new FileSource('app', ['en-US'], './app/data/locales/{locale}/');
+ let oneSource = new FileSource("app", ["en-US"], "./app/data/locales/{locale}/");
fs = {
- './app/data/locales/en-US/test.ftl': 'key = value en-US'
+ "./app/data/locales/en-US/test.ftl": "key = value en-US"
};
L10nRegistry.registerSource(oneSource);
// has one source
equal(L10nRegistry.sources.size, 1);
- equal(L10nRegistry.sources.has('app'), true);
+ equal(L10nRegistry.sources.has("app"), true);
// returns a single context
- let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["en-US"], ["test.ftl"]);
let ctx0 = (await ctxs.next()).value;
- equal(ctx0.hasMessage('key'), true);
+ equal(ctx0.hasMessage("key"), true);
equal((await ctxs.next()).done, true);
// returns no contexts for missing locale
- ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
equal((await ctxs.next()).done, true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that public methods return expected values
* for the dual source scenario.
*/
add_task(async function test_has_two_sources() {
- let oneSource = new FileSource('platform', ['en-US'], './platform/data/locales/{locale}/');
+ let oneSource = new FileSource("platform", ["en-US"], "./platform/data/locales/{locale}/");
L10nRegistry.registerSource(oneSource);
- let secondSource = new FileSource('app', ['pl'], './app/data/locales/{locale}/');
+ let secondSource = new FileSource("app", ["pl"], "./app/data/locales/{locale}/");
L10nRegistry.registerSource(secondSource);
fs = {
- './platform/data/locales/en-US/test.ftl': 'key = platform value',
- './app/data/locales/pl/test.ftl': 'key = app value'
+ "./platform/data/locales/en-US/test.ftl": "key = platform value",
+ "./app/data/locales/pl/test.ftl": "key = app value"
};
// has two sources
equal(L10nRegistry.sources.size, 2);
- equal(L10nRegistry.sources.has('app'), true);
- equal(L10nRegistry.sources.has('platform'), true);
+ equal(L10nRegistry.sources.has("app"), true);
+ equal(L10nRegistry.sources.has("platform"), true);
// returns correct contexts for en-US
- let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["en-US"], ["test.ftl"]);
let ctx0 = (await ctxs.next()).value;
- equal(ctx0.hasMessage('key'), true);
- let msg = ctx0.getMessage('key');
- equal(ctx0.format(msg), 'platform value');
+ equal(ctx0.hasMessage("key"), true);
+ let msg = ctx0.getMessage("key");
+ equal(ctx0.format(msg), "platform value");
equal((await ctxs.next()).done, true);
// returns correct contexts for [pl, en-US]
- ctxs = L10nRegistry.generateContexts(['pl', 'en-US'], ['test.ftl']);
+ ctxs = L10nRegistry.generateContexts(["pl", "en-US"], ["test.ftl"]);
ctx0 = (await ctxs.next()).value;
- equal(ctx0.locales[0], 'pl');
- equal(ctx0.hasMessage('key'), true);
- let msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'app value');
+ equal(ctx0.locales[0], "pl");
+ equal(ctx0.hasMessage("key"), true);
+ let msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "app value");
let ctx1 = (await ctxs.next()).value;
- equal(ctx1.locales[0], 'en-US');
- equal(ctx1.hasMessage('key'), true);
- let msg1 = ctx1.getMessage('key');
- equal(ctx1.format(msg1), 'platform value');
+ equal(ctx1.locales[0], "en-US");
+ equal(ctx1.hasMessage("key"), true);
+ let msg1 = ctx1.getMessage("key");
+ equal(ctx1.format(msg1), "platform value");
equal((await ctxs.next()).done, true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that behavior specific to the IndexedFileSource
* works correctly.
*
* In particular it tests that IndexedFileSource correctly returns
* missing files as `false` instead of `undefined`.
*/
add_task(async function test_indexed() {
- let oneSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
- '/data/locales/pl/test.ftl',
+ let oneSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
+ "/data/locales/pl/test.ftl",
]);
L10nRegistry.registerSource(oneSource);
fs = {
- '/data/locales/pl/test.ftl': 'key = value'
+ "/data/locales/pl/test.ftl": "key = value"
};
equal(L10nRegistry.sources.size, 1);
- equal(L10nRegistry.sources.has('langpack-pl'), true);
+ equal(L10nRegistry.sources.has("langpack-pl"), true);
- equal(oneSource.getPath('pl', 'test.ftl'), '/data/locales/pl/test.ftl');
- equal(oneSource.hasFile('pl', 'test.ftl'), true);
- equal(oneSource.hasFile('pl', 'missing.ftl'), false);
+ equal(oneSource.getPath("pl", "test.ftl"), "/data/locales/pl/test.ftl");
+ equal(oneSource.hasFile("pl", "test.ftl"), true);
+ equal(oneSource.hasFile("pl", "missing.ftl"), false);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test checks if the correct order of contexts is used for
* scenarios where a new file source is added on top of the default one.
*/
add_task(async function test_override() {
- let fileSource = new FileSource('app', ['pl'], '/app/data/locales/{locale}/');
+ let fileSource = new FileSource("app", ["pl"], "/app/data/locales/{locale}/");
L10nRegistry.registerSource(fileSource);
- let oneSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
- '/data/locales/pl/test.ftl'
+ let oneSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
+ "/data/locales/pl/test.ftl"
]);
L10nRegistry.registerSource(oneSource);
fs = {
- '/app/data/locales/pl/test.ftl': 'key = value',
- '/data/locales/pl/test.ftl': 'key = addon value'
+ "/app/data/locales/pl/test.ftl": "key = value",
+ "/data/locales/pl/test.ftl": "key = addon value"
};
equal(L10nRegistry.sources.size, 2);
- equal(L10nRegistry.sources.has('langpack-pl'), true);
+ equal(L10nRegistry.sources.has("langpack-pl"), true);
- let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
let ctx0 = (await ctxs.next()).value;
- equal(ctx0.locales[0], 'pl');
- equal(ctx0.hasMessage('key'), true);
- let msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'addon value');
+ equal(ctx0.locales[0], "pl");
+ equal(ctx0.hasMessage("key"), true);
+ let msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "addon value");
let ctx1 = (await ctxs.next()).value;
- equal(ctx1.locales[0], 'pl');
- equal(ctx1.hasMessage('key'), true);
- let msg1 = ctx1.getMessage('key');
- equal(ctx1.format(msg1), 'value');
+ equal(ctx1.locales[0], "pl");
+ equal(ctx1.hasMessage("key"), true);
+ let msg1 = ctx1.getMessage("key");
+ equal(ctx1.format(msg1), "value");
equal((await ctxs.next()).done, true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that new contexts are returned
* after source update.
*/
add_task(async function test_updating() {
- let oneSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
- '/data/locales/pl/test.ftl',
+ let oneSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
+ "/data/locales/pl/test.ftl",
]);
L10nRegistry.registerSource(oneSource);
fs = {
- '/data/locales/pl/test.ftl': 'key = value'
+ "/data/locales/pl/test.ftl": "key = value"
};
- let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
let ctx0 = (await ctxs.next()).value;
- equal(ctx0.locales[0], 'pl');
- equal(ctx0.hasMessage('key'), true);
- let msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'value');
+ equal(ctx0.locales[0], "pl");
+ equal(ctx0.hasMessage("key"), true);
+ let msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "value");
- const newSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
- '/data/locales/pl/test.ftl'
+ const newSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
+ "/data/locales/pl/test.ftl"
]);
- fs['/data/locales/pl/test.ftl'] = 'key = new value';
+ fs["/data/locales/pl/test.ftl"] = "key = new value";
L10nRegistry.updateSource(newSource);
equal(L10nRegistry.sources.size, 1);
- ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
ctx0 = (await ctxs.next()).value;
- msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'new value');
+ msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "new value");
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that generated contexts return correct values
* after sources are being removed.
*/
add_task(async function test_removing() {
- let fileSource = new FileSource('app', ['pl'], '/app/data/locales/{locale}/');
+ let fileSource = new FileSource("app", ["pl"], "/app/data/locales/{locale}/");
L10nRegistry.registerSource(fileSource);
- let oneSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
- '/data/locales/pl/test.ftl'
+ let oneSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
+ "/data/locales/pl/test.ftl"
]);
L10nRegistry.registerSource(oneSource);
fs = {
- '/app/data/locales/pl/test.ftl': 'key = value',
- '/data/locales/pl/test.ftl': 'key = addon value'
+ "/app/data/locales/pl/test.ftl": "key = value",
+ "/data/locales/pl/test.ftl": "key = addon value"
};
equal(L10nRegistry.sources.size, 2);
- equal(L10nRegistry.sources.has('langpack-pl'), true);
+ equal(L10nRegistry.sources.has("langpack-pl"), true);
- let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
let ctx0 = (await ctxs.next()).value;
- equal(ctx0.locales[0], 'pl');
- equal(ctx0.hasMessage('key'), true);
- let msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'addon value');
+ equal(ctx0.locales[0], "pl");
+ equal(ctx0.hasMessage("key"), true);
+ let msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "addon value");
let ctx1 = (await ctxs.next()).value;
- equal(ctx1.locales[0], 'pl');
- equal(ctx1.hasMessage('key'), true);
- let msg1 = ctx1.getMessage('key');
- equal(ctx1.format(msg1), 'value');
+ equal(ctx1.locales[0], "pl");
+ equal(ctx1.hasMessage("key"), true);
+ let msg1 = ctx1.getMessage("key");
+ equal(ctx1.format(msg1), "value");
equal((await ctxs.next()).done, true);
// Remove langpack
- L10nRegistry.removeSource('langpack-pl');
+ L10nRegistry.removeSource("langpack-pl");
equal(L10nRegistry.sources.size, 1);
- equal(L10nRegistry.sources.has('langpack-pl'), false);
+ equal(L10nRegistry.sources.has("langpack-pl"), false);
- ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
ctx0 = (await ctxs.next()).value;
- equal(ctx0.locales[0], 'pl');
- equal(ctx0.hasMessage('key'), true);
- msg0 = ctx0.getMessage('key');
- equal(ctx0.format(msg0), 'value');
+ equal(ctx0.locales[0], "pl");
+ equal(ctx0.hasMessage("key"), true);
+ msg0 = ctx0.getMessage("key");
+ equal(ctx0.format(msg0), "value");
equal((await ctxs.next()).done, true);
// Remove app source
- L10nRegistry.removeSource('app');
+ L10nRegistry.removeSource("app");
equal(L10nRegistry.sources.size, 0);
- ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
+ ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
equal((await ctxs.next()).done, true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
/**
* This test verifies that the logic works correctly when there's a missing
* file in the FileSource scenario.
*/
add_task(async function test_missing_file() {
- let oneSource = new FileSource('app', ['en-US'], './app/data/locales/{locale}/');
+ let oneSource = new FileSource("app", ["en-US"], "./app/data/locales/{locale}/");
L10nRegistry.registerSource(oneSource);
- let twoSource = new FileSource('platform', ['en-US'], './platform/data/locales/{locale}/');
+ let twoSource = new FileSource("platform", ["en-US"], "./platform/data/locales/{locale}/");
L10nRegistry.registerSource(twoSource);
fs = {
- './app/data/locales/en-US/test.ftl': 'key = value en-US',
- './platform/data/locales/en-US/test.ftl': 'key = value en-US',
- './platform/data/locales/en-US/test2.ftl': 'key2 = value2 en-US'
+ "./app/data/locales/en-US/test.ftl": "key = value en-US",
+ "./platform/data/locales/en-US/test.ftl": "key = value en-US",
+ "./platform/data/locales/en-US/test2.ftl": "key2 = value2 en-US"
};
// has two sources
equal(L10nRegistry.sources.size, 2);
- equal(L10nRegistry.sources.has('app'), true);
- equal(L10nRegistry.sources.has('platform'), true);
+ equal(L10nRegistry.sources.has("app"), true);
+ equal(L10nRegistry.sources.has("platform"), true);
// returns a single context
- let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl', 'test2.ftl']);
- let ctx0 = (await ctxs.next()).value;
- let ctx1 = (await ctxs.next()).value;
+ let ctxs = L10nRegistry.generateContexts(["en-US"], ["test.ftl", "test2.ftl"]);
+ (await ctxs.next());
+ (await ctxs.next());
equal((await ctxs.next()).done, true);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
});
@@ -363,53 +363,53 @@ add_task(async function test_parallel_io
let fetchIndex = new Map();
L10nRegistry.load = function(url) {
if (!fetchIndex.has(url)) {
fetchIndex.set(url, 0);
}
fetchIndex.set(url, fetchIndex.get(url) + 1);
- if (url === '/en-US/slow-file.ftl') {
+ if (url === "/en-US/slow-file.ftl") {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Despite slow-file being the first on the list,
// by the time the it finishes loading, the other
// two files are already fetched.
- equal(fetchIndex.get('/en-US/test.ftl'), 1);
- equal(fetchIndex.get('/en-US/test2.ftl'), 1);
+ equal(fetchIndex.get("/en-US/test.ftl"), 1);
+ equal(fetchIndex.get("/en-US/test2.ftl"), 1);
- resolve('');
+ resolve("");
}, 10);
});
- };
- return Promise.resolve('');
- }
- let oneSource = new FileSource('app', ['en-US'], '/{locale}/');
+ }
+ return Promise.resolve("");
+ };
+ let oneSource = new FileSource("app", ["en-US"], "/{locale}/");
L10nRegistry.registerSource(oneSource);
fs = {
- '/en-US/test.ftl': 'key = value en-US',
- '/en-US/test2.ftl': 'key2 = value2 en-US',
- '/en-US/slow-file.ftl': 'key-slow = value slow en-US',
+ "/en-US/test.ftl": "key = value en-US",
+ "/en-US/test2.ftl": "key2 = value2 en-US",
+ "/en-US/slow-file.ftl": "key-slow = value slow en-US",
};
// returns a single context
- let ctxs = L10nRegistry.generateContexts(['en-US'], ['slow-file.ftl', 'test.ftl', 'test2.ftl']);
+ let ctxs = L10nRegistry.generateContexts(["en-US"], ["slow-file.ftl", "test.ftl", "test2.ftl"]);
equal(fetchIndex.size, 0);
let ctx0 = await ctxs.next();
equal(ctx0.done, false);
equal((await ctxs.next()).done, true);
// When requested again, the cache should make the load operation not
// increase the fetchedIndex count
- let ctxs2= L10nRegistry.generateContexts(['en-US'], ['test.ftl', 'test2.ftl', 'slow-file.ftl']);
+ L10nRegistry.generateContexts(["en-US"], ["test.ftl", "test2.ftl", "slow-file.ftl"]);
// cleanup
L10nRegistry.sources.clear();
L10nRegistry.ctxCache.clear();
L10nRegistry.load = originalLoad;
});
--- a/intl/l10n/test/test_localization.js
+++ b/intl/l10n/test/test_localization.js
@@ -1,95 +1,93 @@
/* 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_presence() {
equal(typeof Localization.prototype.formatValues, "function");
equal(typeof Localization.prototype.formatMessages, "function");
equal(typeof Localization.prototype.formatValue, "function");
});
add_task(async function test_methods_calling() {
const { L10nRegistry, FileSource } =
ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
- const LocaleService =
- Components.classes["@mozilla.org/intl/localeservice;1"].getService(
- Components.interfaces.mozILocaleService);
const fs = {
- '/localization/de/browser/menu.ftl': 'key = [de] Value2',
- '/localization/en-US/browser/menu.ftl': 'key = [en] Value2\nkey2 = [en] Value3',
+ "/localization/de/browser/menu.ftl": "key = [de] Value2",
+ "/localization/en-US/browser/menu.ftl": "key = [en] Value2\nkey2 = [en] Value3",
};
const originalLoad = L10nRegistry.load;
- const originalRequested = LocaleService.getRequestedLocales();
+ const originalRequested = Services.locale.getRequestedLocales();
L10nRegistry.load = async function(url) {
return fs[url];
- }
+ };
- const source = new FileSource('test', ['de', 'en-US'], '/localization/{locale}');
+ const source = new FileSource("test", ["de", "en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
async function* generateMessages(resIds) {
- yield * await L10nRegistry.generateContexts(['de', 'en-US'], resIds);
+ yield * await L10nRegistry.generateContexts(["de", "en-US"], resIds);
}
const l10n = new Localization([
- '/browser/menu.ftl'
+ "/browser/menu.ftl"
], generateMessages);
- let values = await l10n.formatValues([['key'], ['key2']]);
+ let values = await l10n.formatValues([["key"], ["key2"]]);
- equal(values[0], '[de] Value2');
- equal(values[1], '[en] Value3');
+ equal(values[0], "[de] Value2");
+ equal(values[1], "[en] Value3");
L10nRegistry.sources.clear();
L10nRegistry.load = originalLoad;
- LocaleService.setRequestedLocales(originalRequested);
+ Services.locale.setRequestedLocales(originalRequested);
});
add_task(async function test_builtins() {
const { L10nRegistry, FileSource } =
- Components.utils.import("resource://gre/modules/L10nRegistry.jsm", {});
+ ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
const known_platforms = {
- 'linux': 'linux',
- 'win': 'windows',
- 'macosx': 'macos',
- 'android': 'android',
+ "linux": "linux",
+ "win": "windows",
+ "macosx": "macos",
+ "android": "android",
};
const fs = {
- '/localization/en-US/test.ftl': `
+ "/localization/en-US/test.ftl": `
key = { PLATFORM() ->
${ Object.values(known_platforms).map(
- name => ` [${ name }] ${ name.toUpperCase() } Value\n`).join('') }
+ 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}');
+ const source = new FileSource("test", ["en-US"], "/localization/{locale}");
L10nRegistry.registerSource(source);
async function* generateMessages(resIds) {
- yield * await L10nRegistry.generateContexts(['en-US'], resIds);
+ yield * await L10nRegistry.generateContexts(["en-US"], resIds);
}
const l10n = new Localization([
- '/test.ftl'
+ "/test.ftl"
], generateMessages);
- let values = await l10n.formatValues([['key']]);
+ let values = await l10n.formatValues([["key"]]);
ok(values[0].includes(
`${ known_platforms[AppConstants.platform].toUpperCase() } Value`));
L10nRegistry.sources.clear();
L10nRegistry.load = originalLoad;
});