Bug 1428769 - Add intl/l10n to be covered by eslint. r=Pike
authorZibi Braniecki <zbraniecki@mozilla.com>
Tue, 20 Feb 2018 14:02:54 -1000
changeset 404747 2a34dd4ed3e384d031da4a0aa70290b0707bec35
parent 404746 3175dabf16c53c48249a4c6cff45a14fc3aa4c74
child 404748 7ba3850e05a443f3c9eeb5e40367a7d6545d4af5
push id33489
push userdluca@mozilla.com
push dateThu, 22 Feb 2018 09:59:00 +0000
treeherdermozilla-central@c1c444858b32 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersPike
bugs1428769
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1428769 - Add intl/l10n to be covered by eslint. r=Pike MozReview-Commit-ID: 6Mu1A8xkxn4
.eslintignore
intl/l10n/DOMLocalization.jsm
intl/l10n/L10nRegistry.jsm
intl/l10n/Localization.jsm
intl/l10n/MessageContext.jsm
intl/l10n/l10n.js
intl/l10n/test/.eslintrc.js
intl/l10n/test/dom/test_domloc_connectRoot.html
intl/l10n/test/dom/test_domloc_disconnectRoot.html
intl/l10n/test/dom/test_domloc_getAttributes.html
intl/l10n/test/dom/test_domloc_mutations.html
intl/l10n/test/dom/test_domloc_overlay.html
intl/l10n/test/dom/test_domloc_overlay_missing_children.html
intl/l10n/test/dom/test_domloc_overlay_repeated.html
intl/l10n/test/dom/test_domloc_setAttributes.html
intl/l10n/test/dom/test_domloc_translateElements.html
intl/l10n/test/dom/test_domloc_translateFragment.html
intl/l10n/test/dom/test_domloc_translateRoots.html
intl/l10n/test/test_l10nregistry.js
intl/l10n/test/test_localization.js
--- 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
 // &amp;, &#0038;, &#x0026;.
 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;
 });