Bug 1498960 - Update Fluent in Gecko to 0.9. r=stas
authorZibi Braniecki <zbraniecki@mozilla.com>
Sat, 20 Oct 2018 16:35:50 +0000
changeset 490589 b2a4b8894c2a0c396ebe118a52490f514f3a5bb2
parent 490588 507a9e5aaeea48bcf83b11022568a07718ca321a
child 490590 9eefd490403607a910d9805665b8f2a6d3a32c49
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersstas
bugs1498960
milestone64.0a1
Bug 1498960 - Update Fluent in Gecko to 0.9. r=stas Differential Revision: https://phabricator.services.mozilla.com/D8689
browser/components/preferences/in-content/main.js
devtools/client/aboutdebugging-new/aboutdebugging.js
devtools/client/aboutdebugging-new/src/components/App.js
devtools/client/application/initializer.js
devtools/client/application/src/components/App.js
intl/l10n/DOMLocalization.jsm
intl/l10n/Fluent.jsm
intl/l10n/L10nRegistry.jsm
intl/l10n/Localization.jsm
intl/l10n/MessageContext.jsm
intl/l10n/README
intl/l10n/docs/fluent_tutorial.rst
intl/l10n/fluent.js.patch
intl/l10n/moz.build
intl/l10n/test/dom/test_domloc.xul
intl/l10n/test/dom/test_domloc_attr_sanitized.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_all.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_overlay_sanitized.html
intl/l10n/test/dom/test_domloc_repeated_l10nid.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
intl/l10n/test/test_messagecontext.js
intl/l10n/test/test_pseudo.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -192,23 +192,23 @@ var promiseLoadHandlersList;
 
 // Load the preferences string bundle for other locales with fallbacks.
 function getBundleForLocales(newLocales) {
   let locales = Array.from(new Set([
     ...newLocales,
     ...Services.locale.requestedLocales,
     Services.locale.lastFallbackLocale,
   ]));
-  function generateContexts(resourceIds) {
-    return L10nRegistry.generateContexts(locales, resourceIds);
+  function generateBundles(resourceIds) {
+    return L10nRegistry.generateBundles(locales, resourceIds);
   }
   return new Localization([
     "browser/preferences/preferences.ftl",
     "branding/brand.ftl",
-  ], generateContexts);
+  ], generateBundles);
 }
 
 var gNodeToObjectMap = new WeakMap();
 
 var gMainPane = {
   // The set of types the app knows how to handle.  A hash of HandlerInfoWrapper
   // objects, indexed by type.
   _handledTypes: {},
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -50,57 +50,57 @@ const AboutDebugging = {
 
     this.onAdbAddonUpdated = this.onAdbAddonUpdated.bind(this);
     this.onNetworkLocationsUpdated = this.onNetworkLocationsUpdated.bind(this);
     this.onUSBRuntimesUpdated = this.onUSBRuntimesUpdated.bind(this);
 
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
-    const messageContexts = await this.createMessageContexts();
+    const fluentBundles = await this.createFluentBundles();
 
     render(
-      Provider({ store: this.store }, App({ messageContexts })),
+      Provider({ store: this.store }, App({ fluentBundles })),
       this.mount
     );
 
     this.actions.selectPage(PAGES.THIS_FIREFOX, RUNTIMES.THIS_FIREFOX);
     this.actions.updateNetworkLocations(getNetworkLocations());
 
     addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
     addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
     await enableUSBRuntimes();
 
     adbAddon.on("update", this.onAdbAddonUpdated);
     this.onAdbAddonUpdated();
   },
 
-  async createMessageContexts() {
+  async createFluentBundles() {
     // XXX Until the strings for the updated about:debugging stabilize, we
     // locate them outside the regular directory for locale resources so that
     // they don't get picked up by localization tools.
     if (!L10nRegistry.sources.has("aboutdebugging")) {
       const temporarySource = new FileSource(
         "aboutdebugging",
         ["en-US"],
         "chrome://devtools/content/aboutdebugging-new/tmp-locale/{locale}/"
       );
       L10nRegistry.registerSource(temporarySource);
     }
 
     const locales = Services.locale.appLocalesAsBCP47;
     const generator =
-      L10nRegistry.generateContexts(locales, ["aboutdebugging.ftl"]);
+      L10nRegistry.generateBundles(locales, ["aboutdebugging.ftl"]);
 
-    const contexts = [];
-    for await (const context of generator) {
-      contexts.push(context);
+    const bundles = [];
+    for await (const bundle of generator) {
+      bundles.push(bundle);
     }
 
-    return contexts;
+    return bundles;
   },
 
   onAdbAddonUpdated() {
     this.actions.updateAdbAddonStatus(adbAddon.status);
   },
 
   onNetworkLocationsUpdated() {
     this.actions.updateNetworkLocations(getNetworkLocations());
--- a/devtools/client/aboutdebugging-new/src/components/App.js
+++ b/devtools/client/aboutdebugging-new/src/components/App.js
@@ -22,17 +22,17 @@ const Sidebar = createFactory(require(".
 class App extends PureComponent {
   static get propTypes() {
     return {
       adbAddonStatus: PropTypes.string,
       // The "dispatch" helper is forwarded to the App component via connect.
       // From that point, components are responsible for forwarding the dispatch
       // property to all components who need to dispatch actions.
       dispatch: PropTypes.func.isRequired,
-      messageContexts: PropTypes.arrayOf(PropTypes.object).isRequired,
+      fluentBundles: PropTypes.arrayOf(PropTypes.object).isRequired,
       networkLocations: PropTypes.arrayOf(PropTypes.string).isRequired,
       networkRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
       selectedPage: PropTypes.string,
       usbRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
     };
   }
 
   getSelectedPageComponent() {
@@ -60,24 +60,24 @@ class App extends PureComponent {
         return RuntimePage({ dispatch });
     }
   }
 
   render() {
     const {
       adbAddonStatus,
       dispatch,
-      messageContexts,
+      fluentBundles,
       networkRuntimes,
       selectedPage,
       usbRuntimes,
     } = this.props;
 
     return LocalizationProvider(
-      { messages: messageContexts },
+      { messages: fluentBundles },
       dom.div(
         { className: "app" },
         Sidebar(
           {
             adbAddonStatus,
             className: "app__sidebar",
             dispatch,
             networkRuntimes,
--- a/devtools/client/application/initializer.js
+++ b/devtools/client/application/initializer.js
@@ -47,33 +47,33 @@ window.Application = {
     this.client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     this.client.addListener("registration-changed", this.updateWorkers);
     this.client.addListener("processListChanged", this.updateWorkers);
     this.toolbox.target.on("navigate", this.updateDomain);
 
     this.updateDomain();
     await this.updateWorkers();
 
-    const messageContexts = await this.createMessageContexts();
+    const fluentBundles = await this.createFluentBundles();
 
     // Render the root Application component.
-    const app = App({ client: this.client, messageContexts, serviceContainer });
+    const app = App({ client: this.client, fluentBundles, serviceContainer });
     render(Provider({ store: this.store }, app), this.mount);
   },
 
   /**
    * Retrieve message contexts for the current locales, and return them as an array of
-   * MessageContext elements.
+   * FluentBundles elements.
    */
-  async createMessageContexts() {
+  async createFluentBundles() {
     const locales = Services.locale.appLocalesAsBCP47;
     const generator =
-      L10nRegistry.generateContexts(locales, ["devtools/application.ftl"]);
+      L10nRegistry.generateBundles(locales, ["devtools/application.ftl"]);
 
-    // Return value of generateContexts is a generator and should be converted to
+    // Return value of generateBundles is a generator and should be converted to
     // a sync iterable before using it with React.
     const contexts = [];
     for await (const message of generator) {
       contexts.push(message);
     }
 
     return contexts;
   },
--- a/devtools/client/application/src/components/App.js
+++ b/devtools/client/application/src/components/App.js
@@ -20,30 +20,30 @@ const WorkerListEmpty = createFactory(re
  */
 class App extends Component {
   static get propTypes() {
     return {
       client: PropTypes.object.isRequired,
       workers: PropTypes.object.isRequired,
       serviceContainer: PropTypes.object.isRequired,
       domain: PropTypes.string.isRequired,
-      messageContexts: PropTypes.array.isRequired,
+      fluentBundles: PropTypes.array.isRequired,
     };
   }
 
   render() {
-    let { workers, domain, client, serviceContainer, messageContexts } = this.props;
+    let { workers, domain, client, serviceContainer, fluentBundles } = this.props;
 
     // Filter out workers from other domains
     workers = workers.filter((x) => new URL(x.url).hostname === domain);
     const isEmpty = workers.length === 0;
 
     return (
       LocalizationProvider(
-        { messages: messageContexts },
+        { messages: fluentBundles },
         main(
           { className: `application ${isEmpty ? "application--empty" : ""}` },
           isEmpty ? WorkerListEmpty({ serviceContainer })
                   : WorkerList({ workers, client })
         )
       )
     );
   }
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -10,18 +10,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-dom@cab517f (July 31, 2018) */
+/* fluent-dom@fa25466f (October 12, 2018) */
 
 const { Localization } =
   ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 const { Services } =
   ChromeUtils.import("resource://gre/modules/Services.jsm", {});
 
 // Match the opening angle bracket (<) in HTML tags, and HTML entities like
 // &amp;, &#0038;, &#x0026;.
@@ -171,17 +170,17 @@ function overlayAttributes(fromElement, 
   // Remove existing localizable attributes.
   for (const attr of Array.from(toElement.attributes)) {
     if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
       toElement.removeAttribute(attr.name);
     }
   }
 
   // fromElement might be a {value, attributes} object as returned by
-  // Localization.messageFromContext. In which case attributes may be null to
+  // Localization.messageFromBundle. In which case attributes may be null to
   // save GC cycles.
   if (!fromElement.attributes) {
     return;
   }
 
   // Set localizable attributes.
   for (const attr of Array.from(fromElement.attributes)) {
     if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
@@ -401,23 +400,23 @@ const L10N_ELEMENT_QUERY = `[${L10NID_AT
  * formatting translations.
  *
  * It implements the fallback strategy in case of errors encountered during the
  * formatting of translations and methods for observing DOM
  * trees with a `MutationObserver`.
  */
 class DOMLocalization extends Localization {
   /**
-   * @param {Array<String>}    resourceIds      - List of resource IDs
-   * @param {Function}         generateMessages - Function that returns a
-   *                                              generator over MessageContexts
+   * @param {Array<String>}    resourceIds     - List of resource IDs
+   * @param {Function}         generateBundles - Function that returns a
+   *                                             generator over FluentBundles
    * @returns {DOMLocalization}
    */
-  constructor(resourceIds, generateMessages) {
-    super(resourceIds, generateMessages);
+  constructor(resourceIds, generateBundles) {
+    super(resourceIds, generateBundles);
 
     // A Set of DOM trees observed by the `MutationObserver`.
     this.roots = new Set();
     // requestAnimationFrame handler.
     this.pendingrAF = null;
     // list of elements pending for translation.
     this.pendingElements = new Set();
     this.windowElement = null;
rename from intl/l10n/MessageContext.jsm
rename to intl/l10n/Fluent.jsm
--- a/intl/l10n/MessageContext.jsm
+++ b/intl/l10n/Fluent.jsm
@@ -11,1023 +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.7.0 */
-
-/*  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_?-]*$/;
-const unicodeEscapeRe = /^[a-fA-F0-9]{4}$/;
-const trailingWSRe = /[ \t\n\r]+$/;
-
-
-/**
- * The `Parser` class is responsible for parsing FTL resources.
- *
- * It's only public method is `getResource(source)` which takes an FTL string
- * and returns a two element Array with an Object of entries generated from the
- * source as the first element and an array of SyntaxError objects as the
- * second.
- *
- * This parser is optimized for runtime performance.
- *
- * There is an equivalent of this parser in syntax/parser which is
- * generating full AST which is useful for FTL tools.
- */
-class RuntimeParser {
-  /**
-   * Parse FTL code into entries formattable by the MessageContext.
-   *
-   * Given a string of FTL syntax, return a map of entries that can be passed
-   * to MessageContext.format and a list of errors encountered during parsing.
-   *
-   * @param {String} string
-   * @returns {Array<Object, Array>}
-   */
-  getResource(string) {
-    this._source = string;
-    this._index = 0;
-    this._length = string.length;
-    this.entries = {};
-
-    const errors = [];
-
-    this.skipWS();
-    while (this._index < this._length) {
-      try {
-        this.getEntry();
-      } catch (e) {
-        if (e instanceof SyntaxError) {
-          errors.push(e);
-
-          this.skipToNextEntryStart();
-        } else {
-          throw e;
-        }
-      }
-      this.skipWS();
-    }
-
-    return [this.entries, errors];
-  }
-
-  /**
-   * Parse the source string from the current index as an FTL entry
-   * 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") {
-      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 === "#" &&
-        [" ", "#", "\n"].includes(this._source[this._index + 1])) {
-      this.skipComment();
-      return;
-    }
-
-    this.getMessage();
-  }
-
-  /**
-   * Parse the source string from the current index as an FTL message
-   * and add it to the entries property on the Parser.
-   *
-   * @private
-   */
-  getMessage() {
-    const id = this.getEntryIdentifier();
-
-    this.skipInlineWS();
-
-    if (this._source[this._index] === "=") {
-      this._index++;
-    } else {
-      throw this.error("Expected \"=\" after the identifier");
-    }
-
-    this.skipInlineWS();
-
-    const val = this.getPattern();
-
-    if (id.startsWith("-") && val === null) {
-      throw this.error("Expected term to have a value");
-    }
-
-    let attrs = null;
-
-    if (this._source[this._index] === " ") {
-      const lineStart = this._index;
-      this.skipInlineWS();
-
-      if (this._source[this._index] === ".") {
-        this._index = lineStart;
-        attrs = this.getAttributes();
-      }
-    }
-
-    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");
-      }
-
-      this.entries[id] = {};
-
-      if (val !== null) {
-        this.entries[id].val = val;
-      }
-
-      if (attrs !== null) {
-        this.entries[id].attrs = attrs;
-      }
-    }
-  }
-
-  /**
-   * Skip whitespace.
-   *
-   * @private
-   */
-  skipWS() {
-    let ch = this._source[this._index];
-    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") {
-      ch = this._source[++this._index];
-    }
-  }
-
-  /**
-   * Skip blank lines.
-   *
-   * @private
-   */
-  skipBlankLines() {
-    while (true) {
-      const ptr = this._index;
-
-      this.skipInlineWS();
-
-      if (this._source[this._index] === "\n") {
-        this._index += 1;
-      } else {
-        this._index = ptr;
-        break;
-      }
-    }
-  }
-
-  /**
-   * Get identifier using the provided regex.
-   *
-   * By default this will get identifiers of public messages, attributes and
-   * variables (without the $).
-   *
-   * @returns {String}
-   * @private
-   */
-  getIdentifier(re = identifierRe) {
-    re.lastIndex = this._index;
-    const result = re.exec(this._source);
-
-    if (result === null) {
-      this._index += 1;
-      throw this.error(`Expected an identifier [${re.toString()}]`);
-    }
-
-    this._index = re.lastIndex;
-    return result[0];
-  }
-
-  /**
-   * Get identifier of a Message or a Term (staring with a dash).
-   *
-   * @returns {String}
-   * @private
-   */
-  getEntryIdentifier() {
-    return this.getIdentifier(entryIdentifierRe);
-  }
-
-  /**
-   * Get Variant name.
-   *
-   * @returns {Object}
-   * @private
-   */
-  getVariantName() {
-    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_])");
-    }
-
-    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);
-    }
-
-    // If we encountered the end of name, we want to test if the last
-    // collected character is a space.
-    // 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 };
-  }
-
-  /**
-   * Get simple string argument enclosed in `"`.
-   *
-   * @returns {String}
-   * @private
-   */
-  getString() {
-    let value = "";
-    this._index++;
-
-    while (this._index < this._length) {
-      const ch = this._source[this._index];
-
-      if (ch === '"') {
-        this._index++;
-        break;
-      }
-
-      if (ch === "\n") {
-        throw this.error("Unterminated string expression");
-      }
-
-      if (ch === "\\") {
-        value += this.getEscapedCharacter(["{", "\\", "\""]);
-      } else {
-        this._index++;
-        value += ch;
-      }
-    }
-
-    return value;
-  }
-
-  /**
-   * Parses a Message pattern.
-   * Message Pattern may be a simple string or an array of strings
-   * and placeable expressions.
-   *
-   * @returns {String|Array}
-   * @private
-   */
-  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);
-
-    if (eol === -1) {
-      eol = this._length;
-    }
-
-    // If there's any text between the = and the EOL, store it for now. The next
-    // non-empty line will decide what to do with it.
-    const firstLineContent = start !== eol
-      // Trim the trailing whitespace in case this is a single-line pattern.
-      // Multiline patterns are parsed anew by getComplexPattern.
-      ? this._source.slice(start, eol).replace(trailingWSRe, "")
-      : null;
-
-    if (firstLineContent
-      && (firstLineContent.includes("{")
-        || firstLineContent.includes("\\"))) {
-      return this.getComplexPattern();
-    }
-
-    this._index = eol + 1;
-
-    this.skipBlankLines();
-
-    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] === ".") {
-      // 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
-      // identifier. Reparse the whole pattern to make sure we get all of it.
-      this._index = start;
-    }
-
-    return this.getComplexPattern();
-  }
-
-  /**
-   * Parses a complex Message pattern.
-   * This function is called by getPattern when the message is multiline,
-   * or contains escape chars or placeables.
-   * It does full parsing of complex patterns.
-   *
-   * @returns {Array}
-   * @private
-   */
-  /* eslint-disable complexity */
-  getComplexPattern() {
-    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") {
-        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] !== " ") {
-          break;
-        }
-        this.skipInlineWS();
-
-        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";
-        }
-        ch = this._source[this._index];
-        continue;
-      }
-
-      if (ch === undefined) {
-        break;
-      }
-
-      if (ch === "\\") {
-        buffer += this.getEscapedCharacter();
-        ch = this._source[this._index];
-        continue;
-      }
-
-      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 = "";
-        content.push(this.getPlaceable());
-
-        ch = this._source[++this._index];
-        placeables++;
-        continue;
-      }
-
-      buffer += ch;
-      ch = this._source[++this._index];
-    }
-
-    if (content.length === 0) {
-      return buffer.length ? buffer : null;
-    }
-
-    if (buffer.length) {
-      // Trim trailing whitespace, too.
-      content.push(buffer.replace(trailingWSRe, ""));
-    }
-
-    return content;
-  }
-  /* eslint-enable complexity */
-
-  /**
-   * Parse an escape sequence and return the unescaped character.
-   *
-   * @returns {string}
-   * @private
-   */
-  getEscapedCharacter(specials = ["{", "\\"]) {
-    this._index++;
-    const next = this._source[this._index];
-
-    if (specials.includes(next)) {
-      this._index++;
-      return next;
-    }
-
-    if (next === "u") {
-      const sequence = this._source.slice(this._index + 1, this._index + 5);
-      if (unicodeEscapeRe.test(sequence)) {
-        this._index += 5;
-        return String.fromCodePoint(parseInt(sequence, 16));
-      }
-
-      throw this.error(`Invalid Unicode escape sequence: \\u${sequence}`);
-    }
-
-    throw this.error(`Unknown escape sequence: \\${next}`);
-  }
-
-  /**
-   * Parses a single placeable in a Message pattern and returns its
-   * expression.
-   *
-   * @returns {Object}
-   * @private
-   */
-  getPlaceable() {
-    const start = ++this._index;
-
-    this.skipWS();
-
-    if (this._source[this._index] === "*" ||
-       (this._source[this._index] === "[" &&
-        this._source[this._index + 1] !== "]")) {
-      const variants = this.getVariants();
-
-      return {
-        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 === "getattr" && selector.id.name.startsWith("-")) {
-        throw this.error(
-          "Attributes of private messages cannot be interpolated."
-        );
-      }
-
-      return selector;
-    }
-
-    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 === "getvar") {
-      throw this.error("Variants cannot be used as selectors.");
-    }
-
-    if (selector.type === "getattr" && !selector.id.name.startsWith("-")) {
-      throw this.error(
-        "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");
-    }
-
-    this.skipWS();
-
-    const variants = this.getVariants();
-
-    if (variants[0].length === 0) {
-      throw this.error("Expected members for the select expression");
-    }
-
-    return {
-      type: "sel",
-      exp: selector,
-      vars: variants[0],
-      def: variants[1],
-    };
-  }
-
-  /**
-   * Parses a selector expression.
-   *
-   * @returns {Object}
-   * @private
-   */
-  getSelectorExpression() {
-    if (this._source[this._index] === "{") {
-      return this.getPlaceable();
-    }
-
-    const literal = this.getLiteral();
-
-    if (literal.type !== "ref") {
-      return literal;
-    }
-
-    if (this._source[this._index] === ".") {
-      this._index++;
-
-      const name = this.getIdentifier();
-      this._index++;
-      return {
-        type: "getattr",
-        id: literal,
-        name,
-      };
-    }
-
-    if (this._source[this._index] === "[") {
-      this._index++;
-
-      const key = this.getVariantKey();
-      this._index++;
-      return {
-        type: "getvar",
-        id: literal,
-        key,
-      };
-    }
-
-    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");
-      }
-
-      this._index++;
-
-      literal.type = "fun";
-
-      return {
-        type: "call",
-        fun: literal,
-        args,
-      };
-    }
-
-    return literal;
-  }
-
-  /**
-   * Parses call arguments for a CallExpression.
-   *
-   * @returns {Array}
-   * @private
-   */
-  getCallArgs() {
-    const args = [];
-
-    while (this._index < this._length) {
-      this.skipWS();
-
-      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") {
-        args.push(exp);
-      } else {
-        this.skipInlineWS();
-
-        if (this._source[this._index] === ":") {
-          this._index++;
-          this.skipWS();
-
-          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" ||
-              Array.isArray(val) ||
-              val.type === "num") {
-            args.push({
-              type: "narg",
-              name: exp.name,
-              val,
-            });
-          } else {
-            this._index = this._source.lastIndexOf(":", this._index) + 1;
-            throw this.error(
-              "Expected string in quotes, number.");
-          }
-
-        } else {
-          args.push(exp);
-        }
-      }
-
-      this.skipWS();
-
-      if (this._source[this._index] === ")") {
-        break;
-      } 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 cc = this._source.charCodeAt(this._index);
-
-    // The number literal may start with negative sign `-`.
-    if (cc === 45) {
-      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}"`);
-    }
-
-    // followed by potentially more digits
-    while (cc >= 48 && cc <= 57) {
-      num += this._source[this._index++];
-      cc = this._source.charCodeAt(this._index);
-    }
-
-    // followed by an optional decimal separator `.`
-    if (cc === 46) {
-      num += this._source[this._index++];
-      cc = this._source.charCodeAt(this._index);
-
-      // followed by at least one digit
-      if (cc < 48 || cc > 57) {
-        throw this.error(`Unknown literal "${num}"`);
-      }
-
-      // and optionally more digits
-      while (cc >= 48 && cc <= 57) {
-        num += this._source[this._index++];
-        cc = this._source.charCodeAt(this._index);
-      }
-    }
-
-    return {
-      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] !== " ") {
-        break;
-      }
-      this.skipInlineWS();
-
-      if (this._source[this._index] !== ".") {
-        break;
-      }
-      this._index++;
-
-      const key = this.getIdentifier();
-
-      this.skipInlineWS();
-
-      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");
-      }
-
-      if (typeof val === "string") {
-        attrs[key] = val;
-      } else {
-        attrs[key] = {
-          val,
-        };
-      }
-
-      this.skipBlankLines();
-    }
-
-    return attrs;
-  }
-
-  /**
-   * Parses a list of Selector variants.
-   *
-   * @returns {Array}
-   * @private
-   */
-  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 !== "*") {
-        break;
-      }
-      if (ch === "*") {
-        this._index++;
-        defaultIndex = 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");
-      }
-
-      variants[index++] = {key, val};
-
-      this.skipWS();
-    }
-
-    return [variants, defaultIndex];
-  }
-
-  /**
-   * Parses a Variant key.
-   *
-   * @returns {String}
-   * @private
-   */
-  getVariantKey() {
-    // VariantKey may be a Keyword or Number
-
-    const cc = this._source.charCodeAt(this._index);
-    let literal;
-
-    if ((cc >= 48 && cc <= 57) || cc === 45) {
-      literal = this.getNumber();
-    } else {
-      literal = this.getVariantName();
-    }
-
-    if (this._source[this._index] !== "]") {
-      throw this.error('Expected "]"');
-    }
-
-    this._index++;
-    return literal;
-  }
-
-  /**
-   * Parses an FTL literal.
-   *
-   * @returns {Object}
-   * @private
-   */
-  getLiteral() {
-    const cc0 = this._source.charCodeAt(this._index);
-
-    if (cc0 === 36) { // $
-      this._index++;
-      return {
-        type: "var",
-        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",
-        name: this.getEntryIdentifier(),
-      };
-    }
-
-    if ((cc1 >= 48 && cc1 <= 57)) { // 0-9
-      return this.getNumber();
-    }
-
-    if (cc0 === 34) { // "
-      return this.getString();
-    }
-
-    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);
-
-    while (eol !== -1
-      && this._source[eol + 1] === "#"
-      && [" ", "#"].includes(this._source[eol + 2])) {
-
-      this._index = eol + 3;
-      eol = this._source.indexOf("\n", this._index);
-
-      if (eol === -1) {
-        break;
-      }
-    }
-
-    if (eol === -1) {
-      this._index = this._length;
-    } else {
-      this._index = eol + 1;
-    }
-  }
-
-  /**
-   * Creates a new SyntaxError object with a given message.
-   *
-   * @param {String} message
-   * @returns {Object}
-   * @private
-   */
-  error(message) {
-    return new SyntaxError(message);
-  }
-
-  /**
-   * Skips to the beginning of a next entry after the current position.
-   * This is used to mark the boundary of junk entry in case of error,
-   * and recover from the returned position.
-   *
-   * @private
-   */
-  skipToNextEntryStart() {
-    let start = this._index;
-
-    while (true) {
-      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 === 45) { // -
-          this._index = start;
-          return;
-        }
-      }
-
-      start = this._source.indexOf("\n", start);
-
-      if (start === -1) {
-        this._index = this._length;
-        return;
-      }
-      start++;
-    }
-  }
-}
-
-/**
- * Parses an FTL string using RuntimeParser and returns the generated
- * object with entries and a list of errors.
- *
- * @param {String} string
- * @returns {Array<Object, Array>}
- */
-function parse(string) {
-  const parser = new RuntimeParser();
-  return parser.getResource(string);
-}
+/* fluent@fa25466f (October 12, 2018) */
 
 /* global Intl */
 
 /**
  * The `FluentType` class is the base of Fluent's type system.
  *
  * Fluent types wrap JavaScript values and store additional configuration for
  * them, which can then be used in the `toString` method together with a proper
@@ -1054,21 +48,21 @@ class FluentType {
    */
   valueOf() {
     return this.value;
   }
 
   /**
    * Format this instance of `FluentType` to a string.
    *
-   * Formatted values are suitable for use outside of the `MessageContext`.
-   * This method can use `Intl` formatters memoized by the `MessageContext`
+   * Formatted values are suitable for use outside of the `FluentBundle`.
+   * This method can use `Intl` formatters memoized by the `FluentBundle`
    * instance passed as an argument.
    *
-   * @param   {MessageContext} [ctx]
+   * @param   {FluentBundle} [bundle]
    * @returns {string}
    */
   toString() {
     throw new Error("Subclasses of FluentType must implement toString.");
   }
 }
 
 class FluentNone extends FluentType {
@@ -1077,88 +71,47 @@ class FluentNone extends FluentType {
   }
 }
 
 class FluentNumber extends FluentType {
   constructor(value, opts) {
     super(parseFloat(value), opts);
   }
 
-  toString(ctx) {
+  toString(bundle) {
     try {
-      const nf = ctx._memoizeIntlObject(
+      const nf = bundle._memoizeIntlObject(
         Intl.NumberFormat, this.opts
       );
       return nf.format(this.value);
     } catch (e) {
       // XXX Report the error.
       return this.value;
     }
   }
-
-  /**
-   * Compare the object with another instance of a FluentType.
-   *
-   * @param   {MessageContext} ctx
-   * @param   {FluentType}     other
-   * @returns {bool}
-   */
-  match(ctx, other) {
-    if (other instanceof FluentNumber) {
-      return this.value === other.value;
-    }
-    return false;
-  }
 }
 
 class FluentDateTime extends FluentType {
   constructor(value, opts) {
     super(new Date(value), opts);
   }
 
-  toString(ctx) {
+  toString(bundle) {
     try {
-      const dtf = ctx._memoizeIntlObject(
+      const dtf = bundle._memoizeIntlObject(
         Intl.DateTimeFormat, this.opts
       );
       return dtf.format(this.value);
     } catch (e) {
       // XXX Report the error.
       return this.value;
     }
   }
 }
 
-class FluentSymbol extends FluentType {
-  toString() {
-    return this.value;
-  }
-
-  /**
-   * Compare the object with another instance of a 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") {
-      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;
-  }
-}
-
 /**
  * @overview
  *
  * The FTL resolver ships with a number of functions built-in.
  *
  * Each function take two arguments:
  *   - args - an array of positional args
  *   - opts - an object of key-value args
@@ -1191,17 +144,17 @@ function values(opts) {
  *
  * The role of the Fluent resolver is to format a translation object to an
  * instance of `FluentType` or an array of instances.
  *
  * Translations can contain references to other messages or variables,
  * conditional logic in form of select expressions, traits which describe their
  * grammatical features, and can use Fluent builtins which make use of the
  * `Intl` formatters to format numbers, dates, lists and more into the
- * context's language. See the documentation of the Fluent syntax for more
+ * bundle's language. See the documentation of the Fluent syntax for more
  * information.
  *
  * In case of errors the resolver will try to salvage as much of the
  * translation as possible.  In rare situations where the resolver didn't know
  * how to recover from an error it will return an instance of `FluentNone`.
  *
  * `MessageReference`, `VariantExpression`, `AttributeExpression` and
  * `SelectExpression` resolve to raw Runtime Entries objects and the result of
@@ -1218,18 +171,18 @@ function values(opts) {
  * All other expressions (except for `FunctionReference` which is only used in
  * `CallExpression`) resolve to an instance of `FluentType`.  The caller should
  * use the `toString` method to convert the instance to a native value.
  *
  *
  * All functions in this file pass around a special object called `env`.
  * This object stores a set of elements used by all resolve functions:
  *
- *  * {MessageContext} ctx
- *      context for which the given resolution is happening
+ *  * {FluentBundle} bundle
+ *      bundle for which the given resolution is happening
  *  * {Object} args
  *      list of developer provided arguments that can be used
  *  * {Array} errors
  *      list of errors collected while resolving
  *  * {WeakSet} dirty
  *      Set of patterns already encountered during this resolution.
  *      This is used to prevent cyclic resolutions.
  */
@@ -1238,32 +191,70 @@ function values(opts) {
 const MAX_PLACEABLE_LENGTH = 2500;
 
 // Unicode bidi isolation characters.
 const FSI = "\u2068";
 const PDI = "\u2069";
 
 
 /**
+ * Helper for matching a variant key to the given selector.
+ *
+ * Used in SelectExpressions and VariantExpressions.
+ *
+ * @param   {FluentBundle} bundle
+ *    Resolver environment object.
+ * @param   {FluentType} key
+ *    The key of the currently considered variant.
+ * @param   {FluentType} selector
+ *    The selector based om which the correct variant should be chosen.
+ * @returns {FluentType}
+ * @private
+ */
+function match(bundle, selector, key) {
+  if (key === selector) {
+    // Both are strings.
+    return true;
+  }
+
+  if (key instanceof FluentNumber
+    && selector instanceof FluentNumber
+    && key.value === selector.value) {
+    return true;
+  }
+
+  if (selector instanceof FluentNumber && typeof key === "string") {
+    let category = bundle
+      ._memoizeIntlObject(Intl.PluralRules, selector.opts)
+      .select(selector.value);
+    if (key === category) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
  * Helper for choosing the default value from a set of members.
  *
  * Used in SelectExpressions and Type.
  *
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Object} members
  *    Hash map of variants from which the default value is to be selected.
- * @param   {Number} def
+ * @param   {Number} star
  *    The index of the default variant.
  * @returns {FluentType}
  * @private
  */
-function DefaultMember(env, members, def) {
-  if (members[def]) {
-    return members[def];
+function DefaultMember(env, members, star) {
+  if (members[star]) {
+    return members[star];
   }
 
   const { errors } = env;
   errors.push(new RangeError("No default"));
   return new FluentNone();
 }
 
 
@@ -1275,20 +266,20 @@ function DefaultMember(env, members, def
  * @param   {Object} id
  *    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 { bundle, errors } = env;
   const message = name.startsWith("-")
-    ? ctx._terms.get(name)
-    : ctx._messages.get(name);
+    ? bundle._terms.get(name)
+    : bundle._messages.get(name);
 
   if (!message) {
     const err = name.startsWith("-")
       ? new ReferenceError(`Unknown term: ${name}`)
       : new ReferenceError(`Unknown message: ${name}`);
     errors.push(err);
     return new FluentNone(name);
   }
@@ -1298,71 +289,73 @@ function MessageReference(env, {name}) {
 
 /**
  * Resolve a variant expression to the variant object.
  *
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Object} expr
  *    An expression to be resolved.
- * @param   {Object} expr.id
+ * @param   {Object} expr.ref
  *    An Identifier of a message for which the variant is resolved.
  * @param   {Object} expr.id.name
  *    Name a message for which the variant is resolved.
  * @param   {Object} expr.key
  *    Variant key to be resolved.
  * @returns {FluentType}
  * @private
  */
-function VariantExpression(env, {id, key}) {
-  const message = MessageReference(env, id);
+function VariantExpression(env, {ref, selector}) {
+  const message = MessageReference(env, ref);
   if (message instanceof FluentNone) {
     return message;
   }
 
-  const { ctx, errors } = env;
-  const keyword = Type(env, key);
+  const { bundle, errors } = env;
+  const sel = Type(env, selector);
+  const value = message.value || message;
 
   function isVariantList(node) {
     return Array.isArray(node) &&
-      node[0].type === "sel" &&
-      node[0].exp === null;
+      node[0].type === "select" &&
+      node[0].selector === null;
   }
 
-  if (isVariantList(message.val)) {
+  if (isVariantList(value)) {
     // 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)) {
+    for (const variant of value[0].variants) {
+      const key = Type(env, variant.key);
+      if (match(env.bundle, sel, key)) {
         return variant;
       }
     }
   }
 
-  errors.push(new ReferenceError(`Unknown variant: ${keyword.toString(ctx)}`));
+  errors.push(
+    new ReferenceError(`Unknown variant: ${sel.toString(bundle)}`));
   return Type(env, message);
 }
 
 
 /**
  * Resolve an attribute expression to the attribute object.
  *
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Object} expr
  *    An expression to be resolved.
- * @param   {String} expr.id
+ * @param   {String} expr.ref
  *    An ID of a message for which the attribute is resolved.
  * @param   {String} expr.name
  *    Name of the attribute to be resolved.
  * @returns {FluentType}
  * @private
  */
-function AttributeExpression(env, {id, name}) {
-  const message = MessageReference(env, id);
+function AttributeExpression(env, {ref, name}) {
+  const message = MessageReference(env, ref);
   if (message instanceof FluentNone) {
     return message;
   }
 
   if (message.attrs) {
     // Match the specified name against keys of each attribute.
     for (const attrName in message.attrs) {
       if (name === attrName) {
@@ -1378,53 +371,44 @@ function AttributeExpression(env, {id, n
 
 /**
  * Resolve a select expression to the member object.
  *
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Object} expr
  *    An expression to be resolved.
- * @param   {String} expr.exp
+ * @param   {String} expr.selector
  *    Selector expression
- * @param   {Array} expr.vars
+ * @param   {Array} expr.variants
  *    List of variants for the select expression.
- * @param   {Number} expr.def
+ * @param   {Number} expr.star
  *    Index of the default variant.
  * @returns {FluentType}
  * @private
  */
-function SelectExpression(env, {exp, vars, def}) {
-  if (exp === null) {
-    return DefaultMember(env, vars, def);
+function SelectExpression(env, {selector, variants, star}) {
+  if (selector === null) {
+    return DefaultMember(env, variants, star);
   }
 
-  const selector = Type(env, exp);
-  if (selector instanceof FluentNone) {
-    return DefaultMember(env, vars, def);
+  let sel = Type(env, selector);
+  if (sel instanceof FluentNone) {
+    return DefaultMember(env, variants, star);
   }
 
   // Match the selector against keys of each variant, in order.
-  for (const variant of vars) {
+  for (const variant of variants) {
     const key = Type(env, variant.key);
-    const keyCanMatch =
-      key instanceof FluentNumber || key instanceof FluentSymbol;
-
-    if (!keyCanMatch) {
-      continue;
-    }
-
-    const { ctx } = env;
-
-    if (key.match(ctx, selector)) {
+    if (match(env.bundle, sel, key)) {
       return variant;
     }
   }
 
-  return DefaultMember(env, vars, def);
+  return DefaultMember(env, variants, star);
 }
 
 
 /**
  * Resolve expression to a Fluent type.
  *
  * JavaScript strings are a special case.  Since they natively have the
  * `toString` method they can be used as if they were a Fluent type without
@@ -1436,60 +420,58 @@ function SelectExpression(env, {exp, var
  *    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") {
-    return env.ctx._transform(expr);
+    return env.bundle._transform(expr);
   }
   if (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":
-      return new FluentSymbol(expr.name);
     case "num":
-      return new FluentNumber(expr.val);
+      return new FluentNumber(expr.value);
     case "var":
       return VariableReference(env, expr);
-    case "fun":
+    case "func":
       return FunctionReference(env, expr);
     case "call":
       return CallExpression(env, expr);
     case "ref": {
       const message = MessageReference(env, expr);
       return Type(env, message);
     }
     case "getattr": {
       const attr = AttributeExpression(env, expr);
       return Type(env, attr);
     }
     case "getvar": {
       const variant = VariantExpression(env, expr);
       return Type(env, variant);
     }
-    case "sel": {
+    case "select": {
       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);
+      if (expr.value !== null && expr.value !== undefined) {
+        return Type(env, expr.value);
       }
 
       const { errors } = env;
       errors.push(new RangeError("No value"));
       return new FluentNone();
     }
     default:
       return new FluentNone();
@@ -1550,18 +532,18 @@ function VariableReference(env, {name}) 
  *    An expression to be resolved.
  * @param   {String} expr.name
  *    Name of the function to be returned.
  * @returns {Function}
  * @private
  */
 function FunctionReference(env, {name}) {
   // Some functions are built-in.  Others may be provided by the runtime via
-  // the `MessageContext` constructor.
-  const { ctx: { _functions }, errors } = env;
+  // the `FluentBundle` constructor.
+  const { bundle: { _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") {
@@ -1574,43 +556,43 @@ function FunctionReference(env, {name}) 
 
 /**
  * Resolve a call to a Function with positional and key-value arguments.
  *
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Object} expr
  *    An expression to be resolved.
- * @param   {Object} expr.fun
+ * @param   {Object} expr.callee
  *    FTL Function object.
  * @param   {Array} expr.args
  *    FTL Function argument list.
  * @returns {FluentType}
  * @private
  */
-function CallExpression(env, {fun, args}) {
-  const callee = FunctionReference(env, fun);
+function CallExpression(env, {callee, args}) {
+  const func = FunctionReference(env, callee);
 
-  if (callee instanceof FluentNone) {
-    return callee;
+  if (func instanceof FluentNone) {
+    return func;
   }
 
   const posargs = [];
   const keyargs = {};
 
   for (const arg of args) {
     if (arg.type === "narg") {
-      keyargs[arg.name] = Type(env, arg.val);
+      keyargs[arg.name] = Type(env, arg.value);
     } else {
       posargs.push(Type(env, arg));
     }
   }
 
   try {
-    return callee(posargs, keyargs);
+    return func(posargs, keyargs);
   } catch (e) {
     // XXX Report errors.
     return new FluentNone();
   }
 }
 
 /**
  * Resolve a pattern (a complex string with placeables).
@@ -1618,38 +600,38 @@ function CallExpression(env, {fun, args}
  * @param   {Object} env
  *    Resolver environment object.
  * @param   {Array} ptn
  *    Array of pattern elements.
  * @returns {Array}
  * @private
  */
 function Pattern(env, ptn) {
-  const { ctx, dirty, errors } = env;
+  const { bundle, dirty, errors } = env;
 
   if (dirty.has(ptn)) {
     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 = [];
 
   // Wrap interpolations with Directional Isolate Formatting characters
   // only when the pattern has more than one element.
-  const useIsolating = ctx._useIsolating && ptn.length > 1;
+  const useIsolating = bundle._useIsolating && ptn.length > 1;
 
   for (const elem of ptn) {
     if (typeof elem === "string") {
-      result.push(ctx._transform(elem));
+      result.push(bundle._transform(elem));
       continue;
     }
 
-    const part = Type(env, elem).toString(ctx);
+    const part = Type(env, elem).toString(bundle);
 
     if (useIsolating) {
       result.push(FSI);
     }
 
     if (part.length > MAX_PLACEABLE_LENGTH) {
       errors.push(
         new RangeError(
@@ -1669,98 +651,550 @@ function Pattern(env, ptn) {
 
   dirty.delete(ptn);
   return result.join("");
 }
 
 /**
  * Format a translation into a string.
  *
- * @param   {MessageContext} ctx
- *    A MessageContext instance which will be used to resolve the
+ * @param   {FluentBundle} bundle
+ *    A FluentBundle instance which will be used to resolve the
  *    contextual information of the message.
  * @param   {Object}         args
  *    List of arguments provided by the developer which can be accessed
  *    from the message.
  * @param   {Object}         message
  *    An object with the Message to be resolved.
  * @param   {Array}          errors
  *    An error array that any encountered errors will be appended to.
  * @returns {FluentType}
  */
-function resolve(ctx, args, message, errors = []) {
+function resolve(bundle, args, message, errors = []) {
   const env = {
-    ctx, args, errors, dirty: new WeakSet(),
+    bundle, args, errors, dirty: new WeakSet(),
   };
-  return Type(env, message).toString(ctx);
+  return Type(env, message).toString(bundle);
 }
 
+class FluentError extends Error {}
+
+// This regex is used to iterate through the beginnings of messages and terms.
+// With the /m flag, the ^ matches at the beginning of every line.
+const RE_MESSAGE_START = /^(-?[a-zA-Z][a-zA-Z0-9_-]*) *= */mg;
+
+// Both Attributes and Variants are parsed in while loops. These regexes are
+// used to break out of them.
+const RE_ATTRIBUTE_START = /\.([a-zA-Z][a-zA-Z0-9_-]*) *= */y;
+// [^] matches all characters, including newlines.
+// XXX Use /s (dotall) when it's widely supported.
+const RE_VARIANT_START = /\*?\[[^]*?] */y;
+
+const RE_IDENTIFIER = /(-?[a-zA-Z][a-zA-Z0-9_-]*)/y;
+const RE_NUMBER_LITERAL = /(-?[0-9]+(\.[0-9]+)?)/y;
+
+// A "run" is a sequence of text or string literal characters which don't
+// require any special handling. For TextElements such special characters are:
+// { (starts a placeable), \ (starts an escape sequence), and line breaks which
+// require additional logic to check if the next line is indented. For
+// StringLiterals they are: \ (starts an escape sequence), " (ends the
+// literal), and line breaks which are not allowed in StringLiterals. Also note
+// that string runs may be empty, but text runs may not.
+const RE_TEXT_RUN = /([^\\{\n\r]+)/y;
+const RE_STRING_RUN = /([^\\"\n\r]*)/y;
+
+// Escape sequences.
+const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})/y;
+const RE_STRING_ESCAPE = /\\([\\"])/y;
+const RE_TEXT_ESCAPE = /\\([\\{])/y;
+
+// Used for trimming TextElements and indents. With the /m flag, the $ matches
+// the end of every line.
+const RE_TRAILING_SPACES = / +$/mg;
+// CRLFs are normalized to LF.
+const RE_CRLF = /\r\n/g;
+
+// Common tokens.
+const TOKEN_BRACE_OPEN = /{\s*/y;
+const TOKEN_BRACE_CLOSE = /\s*}/y;
+const TOKEN_BRACKET_OPEN = /\[\s*/y;
+const TOKEN_BRACKET_CLOSE = /\s*]/y;
+const TOKEN_PAREN_OPEN = /\(\s*/y;
+const TOKEN_ARROW = /\s*->\s*/y;
+const TOKEN_COLON = /\s*:\s*/y;
+// Note the optional comma. As a deviation from the Fluent EBNF, the parser
+// doesn't enforce commas between call arguments.
+const TOKEN_COMMA = /\s*,?\s*/y;
+const TOKEN_BLANK = /\s+/y;
+
+// Maximum number of placeables in a single Pattern to protect against Quadratic
+// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
+const MAX_PLACEABLES = 100;
+
 /**
- * Fluent Resource is a structure storing a map
- * of localization entries.
+ * Fluent Resource is a structure storing a map of parsed localization entries.
  */
 class FluentResource extends Map {
-  constructor(entries, errors = []) {
-    super(entries);
-    this.errors = errors;
-  }
+  /**
+   * Create a new FluentResource from Fluent code.
+   */
+  static fromString(source) {
+    RE_MESSAGE_START.lastIndex = 0;
+
+    let resource = new this();
+    let cursor = 0;
+
+    // Iterate over the beginnings of messages and terms to efficiently skip
+    // comments and recover from errors.
+    while (true) {
+      let next = RE_MESSAGE_START.exec(source);
+      if (next === null) {
+        break;
+      }
+
+      cursor = RE_MESSAGE_START.lastIndex;
+      try {
+        resource.set(next[1], parseMessage());
+      } catch (err) {
+        if (err instanceof FluentError) {
+          // Don't report any Fluent syntax errors. Skip directly to the
+          // beginning of the next message or term.
+          continue;
+        }
+        throw err;
+      }
+    }
+
+    return resource;
+
+    // The parser implementation is inlined below for performance reasons.
+
+    // The parser focuses on minimizing the number of false negatives at the
+    // expense of increasing the risk of false positives. In other words, it
+    // aims at parsing valid Fluent messages with a success rate of 100%, but it
+    // may also parse a few invalid messages which the reference parser would
+    // reject. The parser doesn't perform any validation and may produce entries
+    // which wouldn't make sense in the real world. For best results users are
+    // advised to validate translations with the fluent-syntax parser
+    // pre-runtime.
+
+    // The parser makes an extensive use of sticky regexes which can be anchored
+    // to any offset of the source string without slicing it. Errors are thrown
+    // to bail out of parsing of ill-formed messages.
+
+    function test(re) {
+      re.lastIndex = cursor;
+      return re.test(source);
+    }
+
+    // Advance the cursor by the char if it matches. May be used as a predicate
+    // (was the match found?) or, if errorClass is passed, as an assertion.
+    function consumeChar(char, errorClass) {
+      if (source[cursor] === char) {
+        cursor++;
+        return true;
+      }
+      if (errorClass) {
+        throw new errorClass(`Expected ${char}`);
+      }
+      return false;
+    }
+
+    // Advance the cursor by the token if it matches. May be used as a predicate
+    // (was the match found?) or, if errorClass is passed, as an assertion.
+    function consumeToken(re, errorClass) {
+      if (test(re)) {
+        cursor = re.lastIndex;
+        return true;
+      }
+      if (errorClass) {
+        throw new errorClass(`Expected ${re.toString()}`);
+      }
+      return false;
+    }
+
+    // Execute a regex, advance the cursor, and return the capture group.
+    function match(re) {
+      re.lastIndex = cursor;
+      let result = re.exec(source);
+      if (result === null) {
+        throw new FluentError(`Expected ${re.toString()}`);
+      }
+      cursor = re.lastIndex;
+      return result[1];
+    }
+
+    function parseMessage() {
+      let value = parsePattern();
+      let attrs = parseAttributes();
+
+      if (attrs === null) {
+        return value;
+      }
+
+      return {value, attrs};
+    }
+
+    function parseAttributes() {
+      let attrs = {};
+      let hasAttributes = false;
+
+      while (test(RE_ATTRIBUTE_START)) {
+        if (!hasAttributes) {
+          hasAttributes = true;
+        }
+
+        let name = match(RE_ATTRIBUTE_START);
+        attrs[name] = parsePattern();
+      }
+
+      return hasAttributes ? attrs : null;
+    }
+
+    function parsePattern() {
+      // First try to parse any simple text on the same line as the id.
+      if (test(RE_TEXT_RUN)) {
+        var first = match(RE_TEXT_RUN);
+      }
+
+      // If there's a backslash escape or a placeable on the first line, fall
+      // back to parsing a complex pattern.
+      switch (source[cursor]) {
+        case "{":
+        case "\\":
+          return first
+            // Re-use the text parsed above, if possible.
+            ? parsePatternElements(first)
+            : parsePatternElements();
+      }
+
+      // RE_TEXT_VALUE stops at newlines. Only continue parsing the pattern if
+      // what comes after the newline is indented.
+      let indent = parseIndent();
+      if (indent) {
+        return first
+          // If there's text on the first line, the blank block is part of the
+          // translation content.
+          ? parsePatternElements(first, trim(indent))
+          // Otherwise, we're dealing with a block pattern. The blank block is
+          // the leading whitespace; discard it.
+          : parsePatternElements();
+      }
+
+      if (first) {
+        // It was just a simple inline text after all.
+        return trim(first);
+      }
+
+      return null;
+    }
+
+    // Parse a complex pattern as an array of elements.
+    function parsePatternElements(...elements) {
+      let placeableCount = 0;
+      let needsTrimming = false;
+
+      while (true) {
+        if (test(RE_TEXT_RUN)) {
+          elements.push(match(RE_TEXT_RUN));
+          needsTrimming = true;
+          continue;
+        }
+
+        if (source[cursor] === "{") {
+          if (++placeableCount > MAX_PLACEABLES) {
+            throw new FluentError("Too many placeables");
+          }
+          elements.push(parsePlaceable());
+          needsTrimming = false;
+          continue;
+        }
+
+        let indent = parseIndent();
+        if (indent) {
+          elements.push(trim(indent));
+          needsTrimming = false;
+          continue;
+        }
+
+        if (source[cursor] === "\\") {
+          elements.push(parseEscapeSequence(RE_TEXT_ESCAPE));
+          needsTrimming = false;
+          continue;
+        }
+
+        break;
+      }
+
+      if (needsTrimming) {
+        // Trim the trailing whitespace of the last element if it's a
+        // TextElement. Use a flag rather than a typeof check to tell
+        // TextElements and StringLiterals apart (both are strings).
+        let lastIndex = elements.length - 1;
+        elements[lastIndex] = trim(elements[lastIndex]);
+      }
+
+      return elements;
+    }
 
-  static fromString(source) {
-    const [entries, errors] = parse(source);
-    return new FluentResource(Object.entries(entries), errors);
+    function parsePlaceable() {
+      consumeToken(TOKEN_BRACE_OPEN, FluentError);
+
+      // VariantLists are parsed as selector-less SelectExpressions.
+      let onlyVariants = parseVariants();
+      if (onlyVariants) {
+        consumeToken(TOKEN_BRACE_CLOSE, FluentError);
+        return {type: "select", selector: null, ...onlyVariants};
+      }
+
+      let selector = parseInlineExpression();
+      if (consumeToken(TOKEN_BRACE_CLOSE)) {
+        return selector;
+      }
+
+      if (consumeToken(TOKEN_ARROW)) {
+        let variants = parseVariants();
+        consumeToken(TOKEN_BRACE_CLOSE, FluentError);
+        return {type: "select", selector, ...variants};
+      }
+
+      throw new FluentError("Unclosed placeable");
+    }
+
+    function parseInlineExpression() {
+      if (source[cursor] === "{") {
+        // It's a nested placeable.
+        return parsePlaceable();
+      }
+
+      if (consumeChar("$")) {
+        return {type: "var", name: match(RE_IDENTIFIER)};
+      }
+
+      if (test(RE_IDENTIFIER)) {
+        let ref = {type: "ref", name: match(RE_IDENTIFIER)};
+
+        if (consumeChar(".")) {
+          let name = match(RE_IDENTIFIER);
+          return {type: "getattr", ref, name};
+        }
+
+        if (source[cursor] === "[") {
+          return {type: "getvar", ref, selector: parseVariantKey()};
+        }
+
+        if (consumeToken(TOKEN_PAREN_OPEN)) {
+          let callee = {...ref, type: "func"};
+          return {type: "call", callee, args: parseArguments()};
+        }
+
+        return ref;
+      }
+
+      return parseLiteral();
+    }
+
+    function parseArguments() {
+      let args = [];
+      while (true) {
+        switch (source[cursor]) {
+          case ")": // End of the argument list.
+            cursor++;
+            return args;
+          case undefined: // EOF
+            throw new FluentError("Unclosed argument list");
+        }
+
+        args.push(parseArgument());
+        // Commas between arguments are treated as whitespace.
+        consumeToken(TOKEN_COMMA);
+      }
+    }
+
+    function parseArgument() {
+      let ref = parseInlineExpression();
+      if (ref.type !== "ref") {
+        return ref;
+      }
+
+      if (consumeToken(TOKEN_COLON)) {
+        // The reference is the beginning of a named argument.
+        return {type: "narg", name: ref.name, value: parseLiteral()};
+      }
+
+      // It's a regular message reference.
+      return ref;
+    }
+
+    function parseVariants() {
+      let variants = [];
+      let count = 0;
+      let star;
+
+      while (test(RE_VARIANT_START)) {
+        if (consumeChar("*")) {
+          star = count;
+        }
+
+        let key = parseVariantKey();
+        cursor = RE_VARIANT_START.lastIndex;
+        variants[count++] = {key, value: parsePattern()};
+      }
+
+      return count > 0 ? {variants, star} : null;
+    }
+
+    function parseVariantKey() {
+      consumeToken(TOKEN_BRACKET_OPEN, FluentError);
+      let key = test(RE_NUMBER_LITERAL)
+        ? parseNumberLiteral()
+        : match(RE_IDENTIFIER);
+      consumeToken(TOKEN_BRACKET_CLOSE, FluentError);
+      return key;
+    }
+
+    function parseLiteral() {
+      if (test(RE_NUMBER_LITERAL)) {
+        return parseNumberLiteral();
+      }
+
+      if (source[cursor] === "\"") {
+        return parseStringLiteral();
+      }
+
+      throw new FluentError("Invalid expression");
+    }
+
+    function parseNumberLiteral() {
+      return {type: "num", value: match(RE_NUMBER_LITERAL)};
+    }
+
+    function parseStringLiteral() {
+      consumeChar("\"", FluentError);
+      let value = "";
+      while (true) {
+        value += match(RE_STRING_RUN);
+
+        if (source[cursor] === "\\") {
+          value += parseEscapeSequence(RE_STRING_ESCAPE);
+          continue;
+        }
+
+        if (consumeChar("\"")) {
+          return value;
+        }
+
+        // We've reached an EOL of EOF.
+        throw new FluentError("Unclosed string literal");
+      }
+    }
+
+    // Unescape known escape sequences.
+    function parseEscapeSequence(reSpecialized) {
+      if (test(RE_UNICODE_ESCAPE)) {
+        let sequence = match(RE_UNICODE_ESCAPE);
+        return String.fromCodePoint(parseInt(sequence, 16));
+      }
+
+      if (test(reSpecialized)) {
+        return match(reSpecialized);
+      }
+
+      throw new FluentError("Unknown escape sequence");
+    }
+
+    // Parse blank space. Return it if it looks like indent before a pattern
+    // line. Skip it othwerwise.
+    function parseIndent() {
+      let start = cursor;
+      consumeToken(TOKEN_BLANK);
+
+      // Check the first non-blank character after the indent.
+      switch (source[cursor]) {
+        case ".":
+        case "[":
+        case "*":
+        case "}":
+        case undefined: // EOF
+          // A special character. End the Pattern.
+          return false;
+        case "{":
+          // Placeables don't require indentation (in EBNF: block-placeable).
+          // Continue the Pattern.
+          return source.slice(start, cursor).replace(RE_CRLF, "\n");
+      }
+
+      // If the first character on the line is not one of the special characters
+      // listed above, it's a regular text character. Check if there's at least
+      // one space of indent before it.
+      if (source[cursor - 1] === " ") {
+        // It's an indented text character (in EBNF: indented-char). Continue
+        // the Pattern.
+        return source.slice(start, cursor).replace(RE_CRLF, "\n");
+      }
+
+      // A not-indented text character is likely the identifier of the next
+      // message. End the Pattern.
+      return false;
+    }
+
+    // Trim spaces trailing on every line of text.
+    function trim(text) {
+      return text.replace(RE_TRAILING_SPACES, "");
+    }
   }
 }
 
 /**
- * Message contexts are single-language stores of translations.  They are
+ * Message bundles are single-language stores of translations.  They are
  * responsible for parsing translation resources in the Fluent syntax and can
  * format translation units (entities) to strings.
  *
- * Always use `MessageContext.format` to retrieve translation units from a
- * context. Translations can contain references to other entities or variables,
+ * Always use `FluentBundle.format` to retrieve translation units from a
+ * bundle. Translations can contain references to other entities or variables,
  * conditional logic in form of select expressions, traits which describe their
  * grammatical features, and can use Fluent builtins which make use of the
  * `Intl` formatters to format numbers, dates, lists and more into the
- * context's language. See the documentation of the Fluent syntax for more
+ * bundle's language. See the documentation of the Fluent syntax for more
  * information.
  */
-class MessageContext {
+class FluentBundle {
 
   /**
-   * Create an instance of `MessageContext`.
+   * Create an instance of `FluentBundle`.
    *
    * The `locales` argument is used to instantiate `Intl` formatters used by
-   * translations.  The `options` object can be used to configure the context.
+   * translations.  The `options` object can be used to configure the bundle.
    *
    * Examples:
    *
-   *     const ctx = new MessageContext(locales);
+   *     const bundle = new FluentBundle(locales);
    *
-   *     const ctx = new MessageContext(locales, { useIsolating: false });
+   *     const bundle = new FluentBundle(locales, { useIsolating: false });
    *
-   *     const ctx = new MessageContext(locales, {
+   *     const bundle = new FluentBundle(locales, {
    *       useIsolating: true,
    *       functions: {
    *         NODE_ENV: () => process.env.NODE_ENV
    *       }
    *     });
    *
    * Available options:
    *
    *   - `functions` - an object of additional functions available to
    *                   translations as builtins.
    *
    *   - `useIsolating` - boolean specifying whether to use Unicode isolation
    *                    marks (FSI, PDI) for bidi interpolations.
    *
    *   - `transform` - a function used to transform string parts of patterns.
    *
-   * @param   {string|Array<string>} locales - Locale or locales of the context
+   * @param   {string|Array<string>} locales - Locale or locales of the bundle
    * @param   {Object} [options]
-   * @returns {MessageContext}
+   * @returns {FluentBundle}
    */
   constructor(locales, {
     functions = {},
     useIsolating = true,
     transform = v => v,
   } = {}) {
     this.locales = Array.isArray(locales) ? locales : [locales];
 
@@ -1777,85 +1211,86 @@ class MessageContext {
    *
    * @returns {Iterator}
    */
   get messages() {
     return this._messages[Symbol.iterator]();
   }
 
   /*
-   * Check if a message is present in the context.
+   * Check if a message is present in the bundle.
    *
    * @param {string} id - The identifier of the message to check.
    * @returns {bool}
    */
   hasMessage(id) {
     return this._messages.has(id);
   }
 
   /*
    * Return the internal representation of a message.
    *
    * The internal representation should only be used as an argument to
-   * `MessageContext.format`.
+   * `FluentBundle.format`.
    *
    * @param {string} id - The identifier of the message to check.
    * @returns {Any}
    */
   getMessage(id) {
     return this._messages.get(id);
   }
 
   /**
-   * Add a translation resource to the context.
+   * Add a translation resource to the bundle.
    *
    * The translation resource must use the Fluent syntax.  It will be parsed by
-   * the context and each translation unit (message) will be available in the
-   * context by its identifier.
+   * the bundle and each translation unit (message) will be available in the
+   * bundle by its identifier.
    *
-   *     ctx.addMessages('foo = Foo');
-   *     ctx.getMessage('foo');
+   *     bundle.addMessages('foo = Foo');
+   *     bundle.getMessage('foo');
    *
    *     // Returns a raw representation of the 'foo' message.
    *
    * Parsed entities should be formatted with the `format` method in case they
    * contain logic (references, select expressions etc.).
    *
    * @param   {string} source - Text resource with translations.
    * @returns {Array<Error>}
    */
   addMessages(source) {
     const res = FluentResource.fromString(source);
     return this.addResource(res);
   }
 
   /**
-   * Add a translation resource to the context.
+   * Add a translation resource to the bundle.
    *
-   * The translation resource must be a proper FluentResource
-   * parsed by `MessageContext.parseResource`.
+   * The translation resource must be an instance of FluentResource,
+   * e.g. parsed by `FluentResource.fromString`.
    *
-   *     let res = MessageContext.parseResource("foo = Foo");
-   *     ctx.addResource(res);
-   *     ctx.getMessage('foo');
+   *     let res = FluentResource.fromString("foo = Foo");
+   *     bundle.addResource(res);
+   *     bundle.getMessage('foo');
    *
    *     // Returns a raw representation of the 'foo' message.
    *
    * Parsed entities should be formatted with the `format` method in case they
    * contain logic (references, select expressions etc.).
    *
    * @param   {FluentResource} res - FluentResource object.
    * @returns {Array<Error>}
    */
   addResource(res) {
-    const errors = res.errors.slice();
+    const errors = [];
+
     for (const [id, value] of res) {
       if (id.startsWith("-")) {
         // Identifiers starting with a dash (-) define terms. Terms are private
-        // and cannot be retrieved from MessageContext.
+        // and cannot be retrieved from FluentBundle.
         if (this._terms.has(id)) {
           errors.push(`Attempt to override an existing term: "${id}"`);
           continue;
         }
         this._terms.set(id, value);
       } else {
         if (this._messages.has(id)) {
           errors.push(`Attempt to override an existing message: "${id}"`);
@@ -1866,57 +1301,57 @@ class MessageContext {
     }
 
     return errors;
   }
 
   /**
    * Format a message to a string or null.
    *
-   * Format a raw `message` from the context into a string (or a null if it has
+   * Format a raw `message` from the bundle into a string (or a null if it has
    * a null value).  `args` will be used to resolve references to variables
    * passed as arguments to the translation.
    *
    * In case of errors `format` will try to salvage as much of the translation
    * as possible and will still return a string.  For performance reasons, the
    * encountered errors are not returned but instead are appended to the
    * `errors` array passed as the third argument.
    *
    *     const errors = [];
-   *     ctx.addMessages('hello = Hello, { $name }!');
-   *     const hello = ctx.getMessage('hello');
-   *     ctx.format(hello, { name: 'Jane' }, errors);
+   *     bundle.addMessages('hello = Hello, { $name }!');
+   *     const hello = bundle.getMessage('hello');
+   *     bundle.format(hello, { name: 'Jane' }, errors);
    *
    *     // Returns 'Hello, Jane!' and `errors` is empty.
    *
-   *     ctx.format(hello, undefined, errors);
+   *     bundle.format(hello, undefined, errors);
    *
    *     // Returns 'Hello, name!' and `errors` is now:
    *
    *     [<ReferenceError: Unknown variable: name>]
    *
    * @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") {
       return this._transform(message);
     }
 
-    // optimize simple-string entities with attributes
-    if (typeof message.val === "string") {
-      return this._transform(message.val);
+    // optimize entities with null values
+    if (message === null || message.value === null) {
+      return null;
     }
 
-    // optimize entities with null values
-    if (message.val === undefined) {
-      return null;
+    // optimize simple-string entities with attributes
+    if (typeof message.value === "string") {
+      return this._transform(message.value);
     }
 
     return resolve(this, args, message, errors);
   }
 
   _memoizeIntlObject(ctor, opts) {
     const cache = this._intls.get(ctor) || {};
     const id = JSON.stringify(opts);
@@ -1925,11 +1360,11 @@ class MessageContext {
       cache[id] = new ctor(this.locales, opts);
       this._intls.set(ctor, cache);
     }
 
     return cache[id];
   }
 }
 
-this.MessageContext = MessageContext;
+this.FluentBundle = FluentBundle;
 this.FluentResource = FluentResource;
-var EXPORTED_SYMBOLS = ["MessageContext", "FluentResource"];
+var EXPORTED_SYMBOLS = ["FluentBundle", "FluentResource"];
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -1,21 +1,21 @@
 const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
-const { MessageContext, FluentResource } = ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+const { FluentBundle, FluentResource } = ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGlobalGetters(this, ["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
+ * It's primary purpose is to allow for building an iterator over FluentBundle objects
  * that will be utilized by a localization API.
  *
  * The generator creates all possible permutations of locales and sources to allow for
  * complete fallbacking.
  *
  * Example:
  *
  *   FileSource1:
@@ -28,17 +28,17 @@ XPCOMUtils.defineLazyGlobalGetters(this,
  *   FileSource2:
  *     name: 'platform'
  *     locales: ['en-US', 'de']
  *     resources: [
  *       '/platform/toolkit.ftl',
  *     ]
  *
  * If the user will request:
- *   L10nRegistry.generateContexts(['de', 'en-US'], [
+ *   L10nRegistry.generateBundles(['de', 'en-US'], [
  *     '/browser/menu.ftl',
  *     '/platform/toolkit.ftl'
  *   ]);
  *
  * the generator will return an async iterator over the following contexts:
  *
  *   {
  *     locale: 'de',
@@ -64,54 +64,54 @@ XPCOMUtils.defineLazyGlobalGetters(this,
  *   {
  *     locale: 'en-US',
  *     resources: [
  *       ['app', '/browser/menu.ftl'],
  *       ['platform', '/platform/toolkit.ftl'],
  *     ]
  *   }
  *
- * This allows the localization API to consume the MessageContext and lazily fallback
+ * This allows the localization API to consume the FluentBundle and lazily fallback
  * on the next in case of a missing string or error.
  *
  * If during the life-cycle of the app a new source is added, the generator can be called again
  * and will produce a new set of permutations placing the language pack provided resources
  * at the top.
  */
 const L10nRegistry = {
   sources: new Map(),
   bootstrap: null,
 
   /**
    * 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>}
+   * @returns {AsyncIterator<FluentBundle>}
    */
-  async* generateContexts(requestedLangs, resourceIds) {
+  async* generateBundles(requestedLangs, resourceIds) {
     if (this.bootstrap !== null) {
       await this.bootstrap;
     }
     const sourcesOrder = Array.from(this.sources.keys()).reverse();
     const pseudoNameFromPref = Services.prefs.getStringPref("intl.l10n.pseudo", "");
     for (const locale of requestedLangs) {
       for await (const dataSets of generateResourceSetsForLocale(locale, sourcesOrder, resourceIds)) {
-        const ctx = new MessageContext(locale, {
+        const bundle = new FluentBundle(locale, {
           ...MSG_CONTEXT_OPTIONS,
           transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
         });
         for (const data of dataSets) {
           if (data === null) {
             return;
           }
-          ctx.addResource(data);
+          bundle.addResource(data);
         }
-        yield ctx;
+        yield bundle;
       }
     }
   },
 
   /**
    * Adds a new resource source to the L10nRegistry.
    *
    * @param {FileSource} source
@@ -164,28 +164,28 @@ const L10nRegistry = {
         locales.add(locale);
       }
     }
     return Array.from(locales);
   },
 };
 
 /**
- * This function generates an iterator over MessageContexts for a single locale
+ * This function generates an iterator over FluentBundles 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
  * and uses the last, optional parameter, to pass the already resolved
  * sources order.
  *
  * @param {String} locale
  * @param {Array} sourcesOrder
  * @param {Array} resourceIds
  * @param {Array} [resolvedOrder]
- * @returns {AsyncIterator<MessageContext>}
+ * @returns {AsyncIterator<FluentBundle>}
  */
 async function* generateResourceSetsForLocale(locale, sourcesOrder, resourceIds, resolvedOrder = []) {
   const resolvedLength = resolvedOrder.length;
   const resourcesLength = resourceIds.length;
 
   // Inside that loop we have a list of resources and the sources for them, like this:
   //   ['test.ftl', 'menu.ftl', 'foo.ftl']
   //   ['app', 'platform', 'app']
@@ -338,28 +338,28 @@ function transformString(map, elongate =
 }
 
 const PSEUDO_STRATEGIES = {
   "accented": transformString.bind(null, ACCENTED_MAP, true, "", ""),
   "bidi": transformString.bind(null, FLIPPED_MAP, false, "\u202e", "\u202c"),
 };
 
 /**
- * Generates a single MessageContext by loading all resources
+ * Generates a single FluentBundle 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
  * try/catch clauses.
  *
  * @param {String} locale
  * @param {Array} sourcesOrder
  * @param {Array} resourceIds
- * @returns {Promise<MessageContext>}
+ * @returns {Promise<FluentBundle>}
  */
 async function generateResourceSet(locale, sourcesOrder, resourceIds) {
   return Promise.all(resourceIds.map((resourceId, i) => {
     return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
   }));
 }
 
 /**
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -11,25 +11,28 @@
  * 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-dom@cab517f (July 31, 2018) */
+/* fluent-dom@fa25466f (October 12, 2018) */
 
 /* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 /* global console */
 
 const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
 const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
 
+/*
+ * Base CachedIterable class.
+ */
 class CachedIterable extends Array {
   /**
    * Create a `CachedIterable` instance from an iterable or, if another
    * instance of `CachedIterable` is passed, return it without any
    * modifications.
    *
    * @param {Iterable} iterable
    * @returns {CachedIterable}
@@ -38,16 +41,22 @@ class CachedIterable extends Array {
     if (iterable instanceof this) {
       return iterable;
     }
 
     return new this(iterable);
   }
 }
 
+/*
+ * CachedAsyncIterable caches the elements yielded by an async iterable.
+ *
+ * It can be used to iterate over an iterable many times without depleting the
+ * iterable.
+ */
 class CachedAsyncIterable extends CachedIterable {
   /**
    * Create an `CachedAsyncIterable` instance.
    *
    * @param {Iterable} iterable
    * @returns {CachedAsyncIterable}
    */
   constructor(iterable) {
@@ -92,77 +101,77 @@ class CachedAsyncIterable extends Cached
    */
   [Symbol.asyncIterator]() {
     const cached = this;
     let cur = 0;
 
     return {
       async next() {
         if (cached.length <= cur) {
-          cached.push(cached.iterator.next());
+          cached.push(await cached.iterator.next());
         }
         return cached[cur++];
       },
     };
   }
 
   /**
    * This method allows user to consume the next element from the iterator
    * into the cache.
    *
    * @param {number} count - number of elements to consume
    */
   async touchNext(count = 1) {
     let idx = 0;
     while (idx++ < count) {
       const last = this[this.length - 1];
-      if (last && await (last).done) {
+      if (last && last.done) {
         break;
       }
-      this.push(this.iterator.next());
+      this.push(await this.iterator.next());
     }
     // Return the last cached {value, done} object to allow the calling
     // code to decide if it needs to call touchNext again.
     return this[this.length - 1];
   }
 }
 
 /**
  * The default localization strategy for Gecko. It comabines locales
  * available in L10nRegistry, with locales requested by the user to
- * generate the iterator over MessageContexts.
+ * generate the iterator over FluentBundles.
  *
  * In the future, we may want to allow certain modules to override this
  * with a different negotitation strategy to allow for the module to
  * be localized into a different language - for example DevTools.
  */
-function defaultGenerateMessages(resourceIds) {
+function defaultGenerateBundles(resourceIds) {
   const appLocales = Services.locale.appLocalesAsBCP47;
-  return L10nRegistry.generateContexts(appLocales, resourceIds);
+  return L10nRegistry.generateBundles(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
+ * It combines language negotiation, FluentBundle and I/O to
  * provide a scriptable API to format translations.
  */
 class Localization {
   /**
-   * @param {Array<String>} resourceIds      - List of resource IDs
-   * @param {Function}      generateMessages - Function that returns a
-   *                                           generator over MessageContexts
+   * @param {Array<String>} resourceIds     - List of resource IDs
+   * @param {Function}      generateBundles - Function that returns a
+   *                                          generator over FluentBundles
    *
    * @returns {Localization}
    */
-  constructor(resourceIds = [], generateMessages = defaultGenerateMessages) {
+  constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
     this.resourceIds = resourceIds;
-    this.generateMessages = generateMessages;
-    this.ctxs = CachedAsyncIterable.from(
-      this.generateMessages(this.resourceIds));
+    this.generateBundles = generateBundles;
+    this.bundles = CachedAsyncIterable.from(
+      this.generateBundles(this.resourceIds));
   }
 
   /**
    * @param {Array<String>} resourceIds - List of resource IDs
    * @param {bool}                eager - whether the I/O for new context should
    *                                      begin eagerly
    */
   addResourceIds(resourceIds, eager = false) {
@@ -175,37 +184,37 @@ class Localization {
     this.resourceIds = this.resourceIds.filter(r => !resourceIds.includes(r));
     this.onChange();
     return this.resourceIds.length;
   }
 
   /**
    * Format translations and handle fallback if needed.
    *
-   * Format translations for `keys` from `MessageContext` instances on this
+   * Format translations for `keys` from `FluentBundle` instances on this
    * DOMLocalization. In case of errors, fetch the next context in the
    * fallback chain.
    *
    * @param   {Array<Object>}         keys    - Translation keys to format.
    * @param   {Function}              method  - Formatting function.
    * @returns {Promise<Array<string|Object>>}
    * @private
    */
   async formatWithFallback(keys, method) {
     const translations = [];
 
-    for await (const ctx of this.ctxs) {
-      const missingIds = keysFromContext(method, ctx, keys, translations);
+    for await (const bundle of this.bundles) {
+      const missingIds = keysFromBundle(method, bundle, keys, translations);
 
       if (missingIds.size === 0) {
         break;
       }
 
       if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
-        const locale = ctx.locales[0];
+        const locale = bundle.locales[0];
         const ids = Array.from(missingIds).join(", ");
         if (Cu.isInAutomation) {
           throw new Error(`Missing translations in ${locale}: ${ids}`);
         }
         console.warn(`Missing translations in ${locale}: ${ids}`);
       }
     }
 
@@ -221,27 +230,30 @@ class Localization {
    *
    *     docL10n.formatMessages([
    *       {id: 'hello', args: { who: 'Mary' }},
    *       {id: 'welcome'}
    *     ]).then(console.log);
    *
    *     // [
    *     //   { value: 'Hello, Mary!', attributes: null },
-   *     //   { value: 'Welcome!', attributes: { title: 'Hello' } }
+   *     //   {
+   *     //     value: 'Welcome!',
+   *     //     attributes: [ { name: "title", value: 'Hello' } ]
+   *     //   }
    *     // ]
    *
    * Returns a Promise resolving to an array of the translation strings.
    *
    * @param   {Array<Object>} keys
    * @returns {Promise<Array<{value: string, attributes: Object}>>}
    * @private
    */
   formatMessages(keys) {
-    return this.formatWithFallback(keys, messageFromContext);
+    return this.formatWithFallback(keys, messageFromBundle);
   }
 
   /**
    * Retrieve translations corresponding to the passed keys.
    *
    * A generalized version of `DOMLocalization.formatValue`. Keys can
    * either be simple string identifiers or `[id, args]` arrays.
    *
@@ -254,17 +266,17 @@ class Localization {
    *     // ['Hello, Mary!', 'Hello, John!', 'Welcome!']
    *
    * Returns a Promise resolving to an array of the translation strings.
    *
    * @param   {Array<Object>} keys
    * @returns {Promise<Array<string>>}
    */
   formatValues(keys) {
-    return this.formatWithFallback(keys, valueFromContext);
+    return this.formatWithFallback(keys, valueFromBundle);
   }
 
   /**
    * Retrieve the translation corresponding to the `id` identifier.
    *
    * If passed, `args` is a simple hash object with a list of variables that
    * will be interpolated in the value of the translation.
    *
@@ -322,146 +334,146 @@ class Localization {
 
   /**
    * This method should be called when there's a reason to believe
    * that language negotiation or available resources changed.
    *
    * @param {bool} eager - whether the I/O for new context should begin eagerly
    */
   onChange(eager = false) {
-    this.ctxs = CachedAsyncIterable.from(
-      this.generateMessages(this.resourceIds));
+    this.bundles = CachedAsyncIterable.from(
+      this.generateBundles(this.resourceIds));
     if (eager) {
       // If the first app locale is the same as last fallback
       // it means that we have all resources in this locale, and
       // we want to eagerly fetch just that one.
       // Otherwise, we're in a scenario where the first locale may
       // be partial and we want to eagerly fetch a fallback as well.
       const appLocale = Services.locale.appLocaleAsBCP47;
       const lastFallback = Services.locale.lastFallbackLocale;
       const prefetchCount = appLocale === lastFallback ? 1 : 2;
-      this.ctxs.touchNext(prefetchCount);
+      this.bundles.touchNext(prefetchCount);
     }
   }
 }
 
 Localization.prototype.QueryInterface = ChromeUtils.generateQI([
   Ci.nsISupportsWeakReference,
 ]);
 
 /**
  * Format the value of a message into a string.
  *
- * This function is passed as a method to `keysFromContext` and resolve
- * a value of a single L10n Entity using provided `MessageContext`.
+ * This function is passed as a method to `keysFromBundle` and resolve
+ * a value of a single L10n Entity using provided `FluentBundle`.
  *
  * If the function fails to retrieve the entity, it will return an ID of it.
  * If formatting fails, it will return a partially resolved entity.
  *
  * In both cases, an error is being added to the errors array.
  *
- * @param   {MessageContext} ctx
+ * @param   {FluentBundle} bundle
  * @param   {Array<Error>}   errors
  * @param   {string}         id
  * @param   {Object}         args
  * @returns {string}
  * @private
  */
-function valueFromContext(ctx, errors, id, args) {
-  const msg = ctx.getMessage(id);
-  return ctx.format(msg, args, errors);
+function valueFromBundle(bundle, errors, id, args) {
+  const msg = bundle.getMessage(id);
+  return bundle.format(msg, args, errors);
 }
 
 /**
  * Format all public values of a message into a {value, attributes} object.
  *
- * This function is passed as a method to `keysFromContext` and resolve
- * a single L10n Entity using provided `MessageContext`.
+ * This function is passed as a method to `keysFromBundle` and resolve
+ * a single L10n Entity using provided `FluentBundle`.
  *
  * The function will return an object with a value and attributes of the
  * entity.
  *
  * If the function fails to retrieve the entity, the value is set to the ID of
  * an entity, and attributes to `null`. If formatting fails, it will return
  * a partially resolved value and attributes.
  *
  * In both cases, an error is being added to the errors array.
  *
- * @param   {MessageContext} ctx
+ * @param   {FluentBundle} bundle
  * @param   {Array<Error>}   errors
  * @param   {String}         id
  * @param   {Object}         args
  * @returns {Object}
  * @private
  */
-function messageFromContext(ctx, errors, id, args) {
-  const msg = ctx.getMessage(id);
+function messageFromBundle(bundle, errors, id, args) {
+  const msg = bundle.getMessage(id);
 
   const formatted = {
-    value: ctx.format(msg, args, errors),
+    value: bundle.format(msg, args, errors),
     attributes: null,
   };
 
   if (msg.attrs) {
     formatted.attributes = [];
     for (const [name, attr] of Object.entries(msg.attrs)) {
-      const value = ctx.format(attr, args, errors);
+      const value = bundle.format(attr, args, errors);
       if (value !== null) {
         formatted.attributes.push({name, value});
       }
     }
   }
 
   return formatted;
 }
 
 /**
  * This function is an inner function for `Localization.formatWithFallback`.
  *
- * It takes a `MessageContext`, list of l10n-ids and a method to be used for
- * key resolution (either `valueFromContext` or `messageFromContext`) and
- * optionally a value returned from `keysFromContext` executed against
- * another `MessageContext`.
+ * It takes a `FluentBundle`, list of l10n-ids and a method to be used for
+ * key resolution (either `valueFromBundle` or `messageFromBundle`) and
+ * optionally a value returned from `keysFromBundle` executed against
+ * another `FluentBundle`.
  *
- * The idea here is that if the previous `MessageContext` did not resolve
+ * The idea here is that if the previous `FluentBundle` did not resolve
  * all keys, we're calling this function with the next context to resolve
  * the remaining ones.
  *
  * In the function, we loop over `keys` and check if we have the `prev`
  * passed and if it has an error entry for the position we're in.
  *
  * If it doesn't, it means that we have a good translation for this key and
  * we return it. If it does, we'll try to resolve the key using the passed
- * `MessageContext`.
+ * `FluentBundle`.
  *
  * In the end, we fill the translations array, and return the Set with
  * missing ids.
  *
  * See `Localization.formatWithFallback` for more info on how this is used.
  *
  * @param {Function}       method
- * @param {MessageContext} ctx
+ * @param {FluentBundle}   bundle
  * @param {Array<string>}  keys
  * @param {{Array<{value: string, attributes: Object}>}} translations
  *
  * @returns {Set<string>}
  * @private
  */
-function keysFromContext(method, ctx, keys, translations) {
+function keysFromBundle(method, bundle, keys, translations) {
   const messageErrors = [];
   const missingIds = new Set();
 
   keys.forEach(({id, args}, i) => {
     if (translations[i] !== undefined) {
       return;
     }
 
-    if (ctx.hasMessage(id)) {
+    if (bundle.hasMessage(id)) {
       messageErrors.length = 0;
-      translations[i] = method(ctx, messageErrors, id, args);
+      translations[i] = method(bundle, messageErrors, id, args);
       // XXX: Report resolver errors
     } else {
       missingIds.add(id);
     }
   });
 
   return missingIds;
 }
--- a/intl/l10n/README
+++ b/intl/l10n/README
@@ -1,15 +1,14 @@
 The content of this directory is partially sourced from the fluent.js project.
 
 The following files are affected:
- - MessageContext.jsm
+ - Fluent.jsm
  - Localization.jsm
  - DOMLocalization.jsm
- - l10n.js
 
 At the moment, the tool used to produce those files in fluent.js repository, doesn't
 fully align with how the code is structured here, so we perform a manual adjustments
 mostly around header and footer.
 
 The result difference is stored in `./fluent.js.patch` file which can be used to
 approximate the changes needed to be applied on the output of the 
 fluent.js/fluent-gecko's make.
--- a/intl/l10n/docs/fluent_tutorial.rst
+++ b/intl/l10n/docs/fluent_tutorial.rst
@@ -487,17 +487,17 @@ in Polish as `28 lut 2018`.
 
 At the moment Fluent supports two formatters that match JS Intl API counterparts:
 
  * **NUMBER**: `Intl.NumberFormat`__
  * **DATETIME**: `Intl.DateTimeFormat`__
 
 With time more formatters will be added.
 
-__ http://projectfluent.org/fluent/guide/functions.html#partial-arguments
+__ https://projectfluent.org/fluent/guide/functions.html#partial-arguments
 __ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
 __ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
 
 Registering New L10n Files
 ==========================
 
 In the previous system, a new localization file had to be registered in order to
 add it in the `jar.mn` file for packaging.
@@ -663,33 +663,33 @@ select the strategy to be used:
 Inner Structure of Fluent
 =========================
 
 The inner structure of Fluent in Gecko is out of scope of this tutorial, but
 since the class and file names may show up during debugging or profiling,
 below is a list of major components, each with a corresponding file in `/intl/l10n`
 modules in Gecko.
 
-MessageContext
+FluentBundle
 --------------
 
-MessageContext is the lowest level API. It's fully synchronous, contains a parser for the
+FluentBundle is the lowest level API. It's fully synchronous, contains a parser for the
 FTL file format and a resolver for the logic. It is not meant to be used by
 consumers directly.
 
 In the future we intend to offer this layer for standardization and it may become
 part of the :js:`mozIntl.*` or even :js:`Intl.*` API sets.
 
 That part of the codebase is also the first that we'll be looking to port to Rust.
 
 
 Localization
 ------------
 
-Localization is a higher level API which uses :js:`MessageContext` internally but
+Localization is a higher level API which uses :js:`FluentBundle` internally but
 provides a full layer of compound message formatting and robust error fall-backing.
 
 It is intended for use in runtime code and contains all fundamental localization
 methods.
 
 
 DOMLocalization
 ---------------
@@ -709,24 +709,24 @@ DocumentL10n
 DocumentL10n implements the DocumentL10n WebIDL API and allows nsIDocument
 to communicate with mozDOMLocalization.
 
 L10nRegistry
 ------------
 
 L10nRegistry is our resource management service. It replaces :js:`ChromeRegistry` and
 maintains the state of resources packaged into the build and language packs,
-providing an asynchronous iterator of :js:`MessageContext` objects for a given locale set
+providing an asynchronous iterator of :js:`FluentBundle` objects for a given locale set
 and resources that the :js:`Localization` class uses.
 
 
-.. _Fluent: http://projectfluent.org/
+.. _Fluent: https://projectfluent.org/
 .. _DTD: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Localization
 .. _StringBundle: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Property_Files
 .. _Firefox Preferences: https://bugzilla.mozilla.org/show_bug.cgi?id=1415730
 .. _Unprivileged Contexts: https://bugzilla.mozilla.org/show_bug.cgi?id=1407418
 .. _System Add-ons: https://bugzilla.mozilla.org/show_bug.cgi?id=1425104
 .. _CLDR: http://cldr.unicode.org/
 .. _ICU: http://site.icu-project.org/
 .. _Unicode: https://www.unicode.org/
-.. _Fluent Syntax Guide: http://projectfluent.org/fluent/guide/
+.. _Fluent Syntax Guide: https://projectfluent.org/fluent/guide/
 .. _Pontoon: https://pontoon.mozilla.org/
 .. _Plural Rules: http://cldr.unicode.org/index/cldr-spec/plural-rules
--- a/intl/l10n/fluent.js.patch
+++ b/intl/l10n/fluent.js.patch
@@ -1,566 +1,736 @@
-diff -uN -x README -x moz.build -x L10nRegistry.jsm -x jar.mn -x fluent.js.patch ./intl/l10n/DOMLocalization.jsm /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/DOMLocalization.jsm
---- ./intl/l10n/DOMLocalization.jsm	2018-08-03 13:25:20.275840905 -0700
-+++ /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/DOMLocalization.jsm	2018-08-01 09:15:58.916763182 -0700
-@@ -16,10 +16,12 @@
+--- ./dist/Fluent.jsm	2018-10-19 08:40:36.557032837 -0600
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Fluent.jsm	2018-10-19 21:22:35.174315857 -0600
+@@ -16,7 +16,7 @@
+  */
+ 
+ 
+-/* fluent-dom@0.4.0 */
++/* fluent@fa25466f (October 12, 2018) */
+ 
+ /* global Intl */
+ 
+@@ -139,7 +139,53 @@
+   return unwrapped;
+ }
+ 
+-/* global Intl */
++/**
++ * @overview
++ *
++ * The role of the Fluent resolver is to format a translation object to an
++ * instance of `FluentType` or an array of instances.
++ *
++ * Translations can contain references to other messages or variables,
++ * conditional logic in form of select expressions, traits which describe their
++ * grammatical features, and can use Fluent builtins which make use of the
++ * `Intl` formatters to format numbers, dates, lists and more into the
++ * bundle's language. See the documentation of the Fluent syntax for more
++ * information.
++ *
++ * In case of errors the resolver will try to salvage as much of the
++ * translation as possible.  In rare situations where the resolver didn't know
++ * how to recover from an error it will return an instance of `FluentNone`.
++ *
++ * `MessageReference`, `VariantExpression`, `AttributeExpression` and
++ * `SelectExpression` resolve to raw Runtime Entries objects and the result of
++ * the resolution needs to be passed into `Type` to get their real value.
++ * This is useful for composing expressions.  Consider:
++ *
++ *     brand-name[nominative]
++ *
++ * which is a `VariantExpression` with properties `id: MessageReference` and
++ * `key: Keyword`.  If `MessageReference` was resolved eagerly, it would
++ * instantly resolve to the value of the `brand-name` message.  Instead, we
++ * want to get the message object and look for its `nominative` variant.
++ *
++ * All other expressions (except for `FunctionReference` which is only used in
++ * `CallExpression`) resolve to an instance of `FluentType`.  The caller should
++ * use the `toString` method to convert the instance to a native value.
++ *
++ *
++ * All functions in this file pass around a special object called `env`.
++ * This object stores a set of elements used by all resolve functions:
++ *
++ *  * {FluentBundle} bundle
++ *      bundle for which the given resolution is happening
++ *  * {Object} args
++ *      list of developer provided arguments that can be used
++ *  * {Array} errors
++ *      list of errors collected while resolving
++ *  * {WeakSet} dirty
++ *      Set of patterns already encountered during this resolution.
++ *      This is used to prevent cyclic resolutions.
++ */
+ 
+ // Prevent expansion of too long placeables.
+ const MAX_PLACEABLE_LENGTH = 2500;
+@@ -1319,14 +1365,6 @@
+   }
+ }
+ 
+-/*
+- * @module fluent
+- * @overview
+- *
+- * `fluent` is a JavaScript implementation of Project Fluent, a localization
+- * framework designed to unleash the expressive power of the natural language.
+- *
+- */
+-
+ this.FluentBundle = FluentBundle;
+-this.EXPORTED_SYMBOLS = ["FluentBundle"];
++this.FluentResource = FluentResource;
++var EXPORTED_SYMBOLS = ["FluentBundle", "FluentResource"];
+--- ./dist/Localization.jsm	2018-10-19 08:40:36.773712561 -0600
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm	2018-10-19 21:20:57.295233460 -0600
+@@ -16,27 +16,34 @@
   */
  
  
--/* fluent-dom@cab517f (July 31, 2018) */
-+/* fluent-dom@0.3.0 */
- 
--const { Localization } =
--  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
-+import Localization from '../../fluent-dom/src/localization.js';
+-/* fluent-dom@0.4.0 */
++/* fluent-dom@fa25466f (October 12, 2018) */
 +
-+/* eslint no-console: ["error", {allow: ["warn"]}] */
++/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 +/* global console */
++
++const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
++const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
++const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
  
- // Match the opening angle bracket (<) in HTML tags, and HTML entities like
- // &amp;, &#0038;, &#x0026;.
-@@ -61,9 +63,7 @@
-     global: [
-       "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label"
-     ],
--    description: ["value"],
-     key: ["key", "keycode"],
--    label: ["value"],
-     textbox: ["placeholder"],
-     toolbarbutton: ["tooltiptext"],
-   }
-@@ -96,7 +96,6 @@
-       const templateElement = element.ownerDocument.createElementNS(
-         "http://www.w3.org/1999/xhtml", "template"
-       );
--      // eslint-disable-next-line no-unsanitized/property
-       templateElement.innerHTML = value;
-       overlayChildNodes(templateElement.content, element);
+ /*
+  * Base CachedIterable class.
+  */
+ class CachedIterable extends Array {
+-    /**
+-     * Create a `CachedIterable` instance from an iterable or, if another
+-     * instance of `CachedIterable` is passed, return it without any
+-     * modifications.
+-     *
+-     * @param {Iterable} iterable
+-     * @returns {CachedIterable}
+-     */
+-    static from(iterable) {
+-        if (iterable instanceof this) {
+-            return iterable;
+-        }
+-
+-        return new this(iterable);
++  /**
++   * Create a `CachedIterable` instance from an iterable or, if another
++   * instance of `CachedIterable` is passed, return it without any
++   * modifications.
++   *
++   * @param {Iterable} iterable
++   * @returns {CachedIterable}
++   */
++  static from(iterable) {
++    if (iterable instanceof this) {
++      return iterable;
      }
-@@ -350,46 +349,6 @@
-   return toElement;
++
++    return new this(iterable);
++  }
  }
  
--/**
-- * Sanitizes a translation before passing them to Node.localize API.
-- *
-- * It returns `false` if the translation contains DOM Overlays and should
-- * not go into Node.localize.
-- *
-- * Note: There's a third item of work that JS DOM Overlays do - removal
-- * of attributes from the previous translation.
-- * This is not trivial to implement for Node.localize scenario, so
-- * at the moment it is not supported.
-- *
-- * @param {{
-- *          localName: string,
-- *          namespaceURI: string,
-- *          type: string || null
-- *          l10nId: string,
-- *          l10nArgs: Array<Object> || null,
-- *          l10nAttrs: string ||null,
-- *        }}                                     l10nItems
-- * @param {{value: string, attrs: Object}} translations
-- * @returns boolean
-- * @private
-- */
--function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
--  if (reOverlay.test(translation.value)) {
--    return false;
--  }
+ /*
+@@ -46,88 +53,100 @@
+  * iterable.
+  */
+ class CachedAsyncIterable extends CachedIterable {
+-    /**
+-     * Create an `CachedAsyncIterable` instance.
+-     *
+-     * @param {Iterable} iterable
+-     * @returns {CachedAsyncIterable}
+-     */
+-    constructor(iterable) {
+-        super();
+-
+-        if (Symbol.asyncIterator in Object(iterable)) {
+-            this.iterator = iterable[Symbol.asyncIterator]();
+-        } else if (Symbol.iterator in Object(iterable)) {
+-            this.iterator = iterable[Symbol.iterator]();
+-        } else {
+-            throw new TypeError("Argument must implement the iteration protocol.");
+-        }
+-    }
++  /**
++   * Create an `CachedAsyncIterable` instance.
++   *
++   * @param {Iterable} iterable
++   * @returns {CachedAsyncIterable}
++   */
++  constructor(iterable) {
++    super();
+ 
+-    /**
+-     * Synchronous iterator over the cached elements.
+-     *
+-     * Return a generator object implementing the iterator protocol over the
+-     * cached elements of the original (async or sync) iterable.
+-     */
+-    [Symbol.iterator]() {
+-        const cached = this;
+-        let cur = 0;
 -
--  if (translation.attributes) {
--    const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
--      l10nItem.l10nAttrs.split(",").map(i => i.trim());
--    for (const [j, {name}] of translation.attributes.entries()) {
--      if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
--        translation.attributes.splice(j, 1);
--      }
+-        return {
+-            next() {
+-                if (cached.length === cur) {
+-                    return {value: undefined, done: true};
+-                }
+-                return cached[cur++];
+-            }
+-        };
++    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.");
+     }
++  }
+ 
+-    /**
+-     * Asynchronous iterator caching the yielded elements.
+-     *
+-     * Elements yielded by the original iterable will be cached and available
+-     * synchronously. Returns an async generator object implementing the
+-     * iterator protocol over the elements of the original (async or sync)
+-     * iterable.
+-     */
+-    [Symbol.asyncIterator]() {
+-        const cached = this;
+-        let cur = 0;
+-
+-        return {
+-            async next() {
+-                if (cached.length <= cur) {
+-                    cached.push(await cached.iterator.next());
+-                }
+-                return cached[cur++];
+-            }
+-        };
 -    }
--  }
--  return true;
--}
--
- const L10NID_ATTR_NAME = "data-l10n-id";
- const L10NARGS_ATTR_NAME = "data-l10n-args";
- 
-@@ -530,6 +489,7 @@
-       );
-     }
- 
++  /**
++   * Synchronous iterator over the cached elements.
++   *
++   * Return a generator object implementing the iterator protocol over the
++   * cached elements of the original (async or sync) iterable.
++   */
++  [Symbol.iterator]() {
++    const cached = this;
++    let cur = 0;
 +
-     this.roots.add(newRoot);
-     this.mutationObserver.observe(newRoot, this.observerConfig);
-   }
-@@ -639,10 +599,7 @@
-     if (this.pendingElements.size > 0) {
-       if (this.pendingrAF === null) {
-         this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
--          // We need to filter for elements that lost their l10n-id while
--          // waiting for the animation frame.
--          this.translateElements(Array.from(this.pendingElements)
--            .filter(elem => elem.hasAttribute("data-l10n-id")));
-+          this.translateElements(Array.from(this.pendingElements));
-           this.pendingElements.clear();
-           this.pendingrAF = null;
-         });
-@@ -664,63 +621,6 @@
-    * @returns {Promise}
-    */
-   translateFragment(frag) {
--    if (frag.localize) {
--      // This is a temporary fast-path offered by Gecko to workaround performance
--      // issues coming from Fluent and XBL+Stylo performing unnecesary
--      // operations during startup.
--      // For details see bug 1441037, bug 1442262, and bug 1363862.
--
--      // A sparse array which will store translations separated out from
--      // all translations that is needed for DOM Overlay.
--      const overlayTranslations = [];
--
--      const getTranslationsForItems = async l10nItems => {
--        const keys = l10nItems.map(
--          l10nItem => ({id: l10nItem.l10nId, args: l10nItem.l10nArgs}));
--        const translations = await this.formatMessages(keys);
--
--        // Here we want to separate out elements that require DOM Overlays.
--        // Those elements will have to be translated using our JS
--        // implementation, while everything else is going to use the fast-path.
--        for (const [i, translation] of translations.entries()) {
--          if (translation === undefined) {
--            continue;
--          }
--
--          const hasOnlyText =
--            sanitizeTranslationForNodeLocalize(l10nItems[i], translation);
--          if (!hasOnlyText) {
--            // Removing from translations to make Node.localize skip it.
--            // We will translate it below using JS DOM Overlays.
--            overlayTranslations[i] = translations[i];
--            translations[i] = undefined;
--          }
--        }
--
--        // We pause translation observing here because Node.localize
--        // will translate the whole DOM next, using the `translations`.
--        //
--        // The observer will be resumed after DOM Overlays are localized
--        // in the next microtask.
--        this.pauseObserving();
--        return translations;
--      };
--
--      return frag.localize(getTranslationsForItems.bind(this))
--        .then(untranslatedElements => {
--          for (let i = 0; i < overlayTranslations.length; i++) {
--            if (overlayTranslations[i] !== undefined &&
--                untranslatedElements[i] !== undefined) {
--              translateElement(untranslatedElements[i], overlayTranslations[i]);
++    return {
++      next() {
++        if (cached.length === cur) {
++          return {value: undefined, done: true};
++        }
++        return cached[cur++];
++      }
++    };
++  }
+ 
+-    /**
+-     * This method allows user to consume the next element from the iterator
+-     * into the cache.
+-     *
+-     * @param {number} count - number of elements to consume
+-     */
+-    async touchNext(count = 1) {
+-        let idx = 0;
+-        while (idx++ < count) {
+-            const last = this[this.length - 1];
+-            if (last && last.done) {
+-                break;
 -            }
--          }
--          this.resumeObserving();
--        })
--        .catch(e => {
--          this.resumeObserving();
--          throw e;
--        });
--    }
-     return this.translateElements(this.getTranslatables(frag));
-   }
- 
-@@ -800,5 +700,37 @@
-   }
+-            this.push(await this.iterator.next());
++  /**
++   * Asynchronous iterator caching the yielded elements.
++   *
++   * Elements yielded by the original iterable will be cached and available
++   * synchronously. Returns an async generator object implementing the
++   * iterator protocol over the elements of the original (async or sync)
++   * iterable.
++   */
++  [Symbol.asyncIterator]() {
++    const cached = this;
++    let cur = 0;
++
++    return {
++      async next() {
++        if (cached.length <= cur) {
++          cached.push(await cached.iterator.next());
+         }
+-        // Return the last cached {value, done} object to allow the calling
+-        // code to decide if it needs to call touchNext again.
+-        return this[this.length - 1];
++        return cached[cur++];
++      }
++    };
++  }
++
++  /**
++   * This method allows user to consume the next element from the iterator
++   * into the cache.
++   *
++   * @param {number} count - number of elements to consume
++   */
++  async touchNext(count = 1) {
++    let idx = 0;
++    while (idx++ < count) {
++      const last = this[this.length - 1];
++      if (last && last.done) {
++        break;
++      }
++      this.push(await this.iterator.next());
+     }
++    // Return the last cached {value, done} object to allow the calling
++    // code to decide if it needs to call touchNext again.
++    return this[this.length - 1];
++  }
  }
  
--this.DOMLocalization = DOMLocalization;
--var EXPORTED_SYMBOLS = ["DOMLocalization"];
-+/* global L10nRegistry, Services */
-+
+-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 +/**
 + * The default localization strategy for Gecko. It comabines locales
 + * available in L10nRegistry, with locales requested by the user to
-+ * generate the iterator over MessageContexts.
++ * generate the iterator over FluentBundles.
 + *
 + * In the future, we may want to allow certain modules to override this
 + * with a different negotitation strategy to allow for the module to
 + * be localized into a different language - for example DevTools.
 + */
-+function defaultGenerateMessages(resourceIds) {
-+  const requestedLocales = Services.locale.getRequestedLocales();
-+  const availableLocales = L10nRegistry.getAvailableLocales();
-+  const defaultLocale = Services.locale.defaultLocale;
-+  const locales = Services.locale.negotiateLanguages(
-+    requestedLocales, availableLocales, defaultLocale,
-+  );
-+  return L10nRegistry.generateContexts(locales, resourceIds);
++function defaultGenerateBundles(resourceIds) {
++  const appLocales = Services.locale.appLocalesAsBCP47;
++  return L10nRegistry.generateContexts(appLocales, resourceIds);
 +}
-+
+ 
+ /**
+  * The `Localization` class is a central high-level API for vanilla
+@@ -143,16 +162,21 @@
+    *
+    * @returns {Localization}
+    */
+-  constructor(resourceIds = [], generateBundles) {
++  constructor(resourceIds = [], generateBundles = defaultGenerateBundles) {
+     this.resourceIds = resourceIds;
+     this.generateBundles = generateBundles;
+     this.bundles = CachedAsyncIterable.from(
+       this.generateBundles(this.resourceIds));
+   }
+ 
+-  addResourceIds(resourceIds) {
++  /**
++   * @param {Array<String>} resourceIds - List of resource IDs
++   * @param {bool}                eager - whether the I/O for new context should
++   *                                      begin eagerly
++   */
++  addResourceIds(resourceIds, eager = false) {
+     this.resourceIds.push(...resourceIds);
+-    this.onChange();
++    this.onChange(eager);
+     return this.resourceIds.length;
+   }
+ 
+@@ -184,9 +208,12 @@
+         break;
+       }
+ 
+-      if (typeof console !== "undefined") {
++      if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
+         const locale = bundle.locales[0];
+         const ids = Array.from(missingIds).join(", ");
++        if (Cu.isInAutomation) {
++          throw new Error(`Missing translations in ${locale}: ${ids}`);
++        }
+         console.warn(`Missing translations in ${locale}: ${ids}`);
+       }
+     }
+@@ -274,21 +301,64 @@
+     return val;
+   }
+ 
+-  handleEvent() {
+-    this.onChange();
++  /**
++   * Register weak observers on events that will trigger cache invalidation
++   */
++  registerObservers() {
++    Services.obs.addObserver(this, "intl:app-locales-changed", true);
++    Services.prefs.addObserver("intl.l10n.pseudo", this, true);
++  }
 +
-+class GeckoDOMLocalization extends DOMLocalization {
-+  constructor(
-+    windowElement,
-+    resourceIds,
-+    generateMessages = defaultGenerateMessages
-+  ) {
-+    super(windowElement, resourceIds, generateMessages);
-+  }
-+}
-+
-+this.DOMLocalization = GeckoDOMLocalization;
-+this.EXPORTED_SYMBOLS = ["DOMLocalization"];
-diff -uN -x README -x moz.build -x L10nRegistry.jsm -x jar.mn -x fluent.js.patch ./intl/l10n/l10n.js /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/l10n.js
---- ./intl/l10n/l10n.js	2018-08-03 13:26:42.691527746 -0700
-+++ /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/l10n.js	2018-08-01 09:15:59.253432348 -0700
-@@ -1,6 +1,7 @@
-+/* global Components, document, window */
- {
-   const { DOMLocalization } =
--    ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
-+    Components.utils.import("resource://gre/modules/DOMLocalization.jsm");
++  /**
++   * Default observer handler method.
++   *
++   * @param {String} subject
++   * @param {String} topic
++   * @param {Object} data
++   */
++  observe(subject, topic, data) {
++    switch (topic) {
++      case "intl:app-locales-changed":
++        this.onChange();
++        break;
++      case "nsPref:changed":
++        switch (data) {
++          case "intl.l10n.pseudo":
++            this.onChange();
++        }
++        break;
++      default:
++        break;
++    }
+   }
  
    /**
-    * Polyfill for document.ready polyfill.
-@@ -44,13 +45,16 @@
- 
-   const resourceIds = getResourceLinks(document.head || document);
- 
--  document.l10n = new DOMLocalization(resourceIds);
-+  document.l10n = new DOMLocalization(window, resourceIds);
- 
--  // Trigger the first two contexts to be loaded eagerly.
--  document.l10n.ctxs.touchNext(2);
-+  // trigger first context to be fetched eagerly
-+  document.l10n.ctxs.touchNext();
- 
-   document.l10n.ready = documentReady().then(() => {
-     document.l10n.registerObservers();
-+    window.addEventListener("unload", () => {
-+      document.l10n.unregisterObservers();
-+    });
-     document.l10n.connectRoot(document.documentElement);
-     return document.l10n.translateRoots();
-   });
-diff -uN -x README -x moz.build -x L10nRegistry.jsm -x jar.mn -x fluent.js.patch ./intl/l10n/Localization.jsm /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/Localization.jsm
---- ./intl/l10n/Localization.jsm	2018-08-03 13:20:57.417703171 -0700
-+++ /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/Localization.jsm	2018-08-01 09:15:58.546760435 -0700
-@@ -16,128 +16,11 @@
-  */
- 
- 
--/* fluent-dom@cab517f (July 31, 2018) */
-+/* fluent-dom@0.3.0 */
+    * This method should be called when there's a reason to believe
+    * that language negotiation or available resources changed.
++   *
++   * @param {bool} eager - whether the I/O for new context should begin eagerly
+    */
+-  onChange() {
++  onChange(eager = false) {
+     this.bundles = CachedAsyncIterable.from(
+       this.generateBundles(this.resourceIds));
+-    this.bundles.touchNext(2);
++    if (eager) {
++      // If the first app locale is the same as last fallback
++      // it means that we have all resources in this locale, and
++      // we want to eagerly fetch just that one.
++      // Otherwise, we're in a scenario where the first locale may
++      // be partial and we want to eagerly fetch a fallback as well.
++      const appLocale = Services.locale.appLocaleAsBCP47;
++      const lastFallback = Services.locale.lastFallbackLocale;
++      const prefetchCount = appLocale === lastFallback ? 1 : 2;
++      this.bundles.touchNext(prefetchCount);
++    }
+   }
+ }
  
--/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
--/* global console */
--
--const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
--const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
--const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
-+import { CachedAsyncIterable } from 'cached-iterable';
++Localization.prototype.QueryInterface = ChromeUtils.generateQI([
++  Ci.nsISupportsWeakReference
++]);
++
+ /**
+  * Format the value of a message into a string.
+  *
+@@ -380,7 +450,7 @@
+  * See `Localization.formatWithFallback` for more info on how this is used.
+  *
+  * @param {Function}       method
+- * @param {FluentBundle} bundle
++ * @param {FluentBundle}   bundle
+  * @param {Array<string>}  keys
+  * @param {{Array<{value: string, attributes: Object}>}} translations
+  *
+@@ -408,44 +478,5 @@
+   return missingIds;
+ }
  
--class CachedIterable extends Array {
--  /**
--   * Create a `CachedIterable` instance from an iterable or, if another
--   * instance of `CachedIterable` is passed, return it without any
--   * modifications.
--   *
--   * @param {Iterable} iterable
--   * @returns {CachedIterable}
--   */
--  static from(iterable) {
--    if (iterable instanceof this) {
--      return iterable;
--    }
--
--    return new this(iterable);
--  }
--}
--
--class CachedAsyncIterable extends CachedIterable {
--  /**
--   * Create an `CachedAsyncIterable` instance.
--   *
--   * @param {Iterable} iterable
--   * @returns {CachedAsyncIterable}
--   */
--  constructor(iterable) {
--    super();
+-/* global Components */
+-/* eslint no-unused-vars: 0 */
 -
--    if (Symbol.asyncIterator in Object(iterable)) {
--      this.iterator = iterable[Symbol.asyncIterator]();
--    } else if (Symbol.iterator in Object(iterable)) {
--      this.iterator = iterable[Symbol.iterator]();
--    } else {
--      throw new TypeError("Argument must implement the iteration protocol.");
--    }
--  }
--
--  /**
--   * Synchronous iterator over the cached elements.
--   *
--   * Return a generator object implementing the iterator protocol over the
--   * cached elements of the original (async or sync) iterable.
--   */
--  [Symbol.iterator]() {
--    const cached = this;
--    let cur = 0;
--
--    return {
--      next() {
--        if (cached.length === cur) {
--          return {value: undefined, done: true};
--        }
--        return cached[cur++];
--      }
--    };
--  }
+-const Cu = Components.utils;
+-const Cc = Components.classes;
+-const Ci = Components.interfaces;
 -
--  /**
--   * Asynchronous iterator caching the yielded elements.
--   *
--   * Elements yielded by the original iterable will be cached and available
--   * synchronously. Returns an async generator object implementing the
--   * iterator protocol over the elements of the original (async or sync)
--   * iterable.
--   */
--  [Symbol.asyncIterator]() {
--    const cached = this;
--    let cur = 0;
--
--    return {
--      async next() {
--        if (cached.length <= cur) {
--          cached.push(await cached.iterator.next());
--        }
--        return cached[cur++];
--      }
--    };
--  }
--
--  /**
--   * This method allows user to consume the next element from the iterator
--   * into the cache.
--   *
--   * @param {number} count - number of elements to consume
--   */
--  async touchNext(count = 1) {
--    let idx = 0;
--    while (idx++ < count) {
--      const last = this[this.length - 1];
--      if (last && last.done) {
--        break;
--      }
--      this.push(await this.iterator.next());
--    }
--    // Return the last cached {value, done} object to allow the calling
--    // code to decide if it needs to call touchNext again.
--    return this[this.length - 1];
--  }
--}
+-const { L10nRegistry } =
+-  Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
+-const ObserverService =
+-  Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+-const { Services } =
+-  Cu.import("resource://gre/modules/Services.jsm", {});
 -
 -/**
 - * The default localization strategy for Gecko. It comabines locales
 - * available in L10nRegistry, with locales requested by the user to
-- * generate the iterator over MessageContexts.
+- * generate the iterator over FluentBundles.
 - *
 - * In the future, we may want to allow certain modules to override this
 - * with a different negotitation strategy to allow for the module to
 - * be localized into a different language - for example DevTools.
 - */
--function defaultGenerateMessages(resourceIds) {
--  const appLocales = Services.locale.getAppLocalesAsBCP47();
--  return L10nRegistry.generateContexts(appLocales, resourceIds);
+-function defaultGenerateBundles(resourceIds) {
+-  const requestedLocales = Services.locale.getRequestedLocales();
+-  const availableLocales = L10nRegistry.getAvailableLocales();
+-  const defaultLocale = Services.locale.defaultLocale;
+-  const locales = Services.locale.negotiateLanguages(
+-    requestedLocales, availableLocales, defaultLocale,
+-  );
+-  return L10nRegistry.generateContexts(locales, resourceIds);
+-}
+-
+-class GeckoLocalization extends Localization {
+-  constructor(resourceIds, generateBundles = defaultGenerateBundles) {
+-    super(resourceIds, generateBundles);
+-  }
 -}
-+/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
+-
+-this.Localization = GeckoLocalization;
+-this.EXPORTED_SYMBOLS = ["Localization"];
++this.Localization = Localization;
++var EXPORTED_SYMBOLS = ["Localization"];
+--- ./dist/DOMLocalization.jsm	2018-10-19 08:40:37.000392886 -0600
++++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm	2018-10-19 21:38:25.963726161 -0600
+@@ -15,13 +15,12 @@
+  * limitations under the License.
+  */
+ 
++/* fluent-dom@fa25466f (October 12, 2018) */
+ 
+-/* fluent-dom@0.4.0 */
+-
+-import Localization from '../../fluent-dom/src/localization.js';
+-
+-/* eslint no-console: ["error", {allow: ["warn"]}] */
+-/* global console */
++const { Localization } =
++  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
++const { Services } =
++  ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+ 
+ // Match the opening angle bracket (<) in HTML tags, and HTML entities like
+ // &amp;, &#0038;, &#x0026;.
+@@ -61,11 +60,12 @@
+   },
+   "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",
++      "title", "tooltiptext"],
++    description: ["value"],
+     key: ["key", "keycode"],
++    label: ["value"],
+     textbox: ["placeholder"],
+-    toolbarbutton: ["tooltiptext"],
+   }
+ };
  
- /**
-  * The `Localization` class is a central high-level API for vanilla
-@@ -153,7 +36,7 @@
-    *
-    * @returns {Localization}
-    */
--  constructor(resourceIds = [], generateMessages = defaultGenerateMessages) {
-+  constructor(resourceIds = [], generateMessages) {
-     this.resourceIds = resourceIds;
-     this.generateMessages = generateMessages;
-     this.ctxs = CachedAsyncIterable.from(
-@@ -194,12 +77,9 @@
-         break;
-       }
+@@ -96,6 +96,7 @@
+       const templateElement = element.ownerDocument.createElementNS(
+         "http://www.w3.org/1999/xhtml", "template"
+       );
++      // eslint-disable-next-line no-unsanitized/property
+       templateElement.innerHTML = value;
+       overlayChildNodes(templateElement.content, element);
+     }
+@@ -349,6 +350,46 @@
+   return toElement;
+ }
  
--      if (AppConstants.NIGHTLY_BUILD || Cu.isInAutomation) {
-+      if (typeof console !== "undefined") {
-         const locale = ctx.locales[0];
-         const ids = Array.from(missingIds).join(", ");
--        if (Cu.isInAutomation) {
--          throw new Error(`Missing translations in ${locale}: ${ids}`);
--        }
-         console.warn(`Missing translations in ${locale}: ${ids}`);
-       }
-     }
-@@ -284,35 +164,8 @@
-     return val;
++/**
++ * Sanitizes a translation before passing them to Node.localize API.
++ *
++ * It returns `false` if the translation contains DOM Overlays and should
++ * not go into Node.localize.
++ *
++ * Note: There's a third item of work that JS DOM Overlays do - removal
++ * of attributes from the previous translation.
++ * This is not trivial to implement for Node.localize scenario, so
++ * at the moment it is not supported.
++ *
++ * @param {{
++ *          localName: string,
++ *          namespaceURI: string,
++ *          type: string || null
++ *          l10nId: string,
++ *          l10nArgs: Array<Object> || null,
++ *          l10nAttrs: string ||null,
++ *        }}                                     l10nItems
++ * @param {{value: string, attrs: Object}} translations
++ * @returns boolean
++ * @private
++ */
++function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
++  if (reOverlay.test(translation.value)) {
++    return false;
++  }
++
++  if (translation.attributes) {
++    const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
++      l10nItem.l10nAttrs.split(",").map(i => i.trim());
++    for (const [j, {name}] of translation.attributes.entries()) {
++      if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
++        translation.attributes.splice(j, 1);
++      }
++    }
++  }
++  return true;
++}
++
+ const L10NID_ATTR_NAME = "data-l10n-id";
+ const L10NARGS_ATTR_NAME = "data-l10n-args";
+ 
+@@ -390,8 +431,8 @@
+     };
    }
  
--  /**
--   * Register weak observers on events that will trigger cache invalidation
--   */
--  registerObservers() {
--    Services.obs.addObserver(this, "intl:app-locales-changed", true);
--    Services.prefs.addObserver("intl.l10n.pseudo", this, true);
--  }
+-  onChange() {
+-    super.onChange();
++  onChange(eager = false) {
++    super.onChange(eager);
+     this.translateRoots();
+   }
+ 
+@@ -478,18 +519,17 @@
+     }
+ 
+     if (this.windowElement) {
+-      if (this.windowElement !== newRoot.ownerDocument.defaultView) {
++      if (this.windowElement !== newRoot.ownerGlobal) {
+         throw new Error(`Cannot connect a root:
+           DOMLocalization already has a root from a different window.`);
+       }
+     } else {
+-      this.windowElement = newRoot.ownerDocument.defaultView;
++      this.windowElement = newRoot.ownerGlobal;
+       this.mutationObserver = new this.windowElement.MutationObserver(
+         mutations => this.translateMutations(mutations)
+       );
+     }
+ 
 -
--  /**
--   * Default observer handler method.
--   *
--   * @param {String} subject
--   * @param {String} topic
--   * @param {Object} data
--   */
--  observe(subject, topic, data) {
--    switch (topic) {
--      case "intl:app-locales-changed":
--        this.onChange();
--        break;
--      case "nsPref:changed":
--        switch (data) {
--          case "intl.l10n.pseudo":
--            this.onChange();
--        }
--        break;
--      default:
--        break;
--    }
-+  handleEvent() {
-+    this.onChange();
+     this.roots.add(newRoot);
+     this.mutationObserver.observe(newRoot, this.observerConfig);
+   }
+@@ -532,7 +572,20 @@
+   translateRoots() {
+     const roots = Array.from(this.roots);
+     return Promise.all(
+-      roots.map(root => this.translateFragment(root))
++      roots.map(async root => {
++        // We want to first retranslate the UI, and
++        // then (potentially) flip the directionality.
++        //
++        // This means that the DOM alternations and directionality
++        // are set in the same microtask.
++        await this.translateFragment(root);
++        let primaryLocale = Services.locale.appLocaleAsBCP47;
++        let direction = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
++        root.setAttribute("lang", primaryLocale);
++        root.setAttribute(root.namespaceURI ===
++          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
++          ? "localedir" : "dir", direction);
++      })
+     );
    }
  
-   /**
-@@ -326,10 +179,6 @@
+@@ -599,7 +652,10 @@
+     if (this.pendingElements.size > 0) {
+       if (this.pendingrAF === null) {
+         this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
+-          this.translateElements(Array.from(this.pendingElements));
++          // We need to filter for elements that lost their l10n-id while
++          // waiting for the animation frame.
++          this.translateElements(Array.from(this.pendingElements)
++            .filter(elem => elem.hasAttribute("data-l10n-id")));
+           this.pendingElements.clear();
+           this.pendingrAF = null;
+         });
+@@ -621,6 +677,63 @@
+    * @returns {Promise}
+    */
+   translateFragment(frag) {
++    if (frag.localize) {
++      // This is a temporary fast-path offered by Gecko to workaround performance
++      // issues coming from Fluent and XBL+Stylo performing unnecesary
++      // operations during startup.
++      // For details see bug 1441037, bug 1442262, and bug 1363862.
++
++      // A sparse array which will store translations separated out from
++      // all translations that is needed for DOM Overlay.
++      const overlayTranslations = [];
++
++      const getTranslationsForItems = async l10nItems => {
++        const keys = l10nItems.map(
++          l10nItem => ({id: l10nItem.l10nId, args: l10nItem.l10nArgs}));
++        const translations = await this.formatMessages(keys);
++
++        // Here we want to separate out elements that require DOM Overlays.
++        // Those elements will have to be translated using our JS
++        // implementation, while everything else is going to use the fast-path.
++        for (const [i, translation] of translations.entries()) {
++          if (translation === undefined) {
++            continue;
++          }
++
++          const hasOnlyText =
++            sanitizeTranslationForNodeLocalize(l10nItems[i], translation);
++          if (!hasOnlyText) {
++            // Removing from translations to make Node.localize skip it.
++            // We will translate it below using JS DOM Overlays.
++            overlayTranslations[i] = translations[i];
++            translations[i] = undefined;
++          }
++        }
++
++        // We pause translation observing here because Node.localize
++        // will translate the whole DOM next, using the `translations`.
++        //
++        // The observer will be resumed after DOM Overlays are localized
++        // in the next microtask.
++        this.pauseObserving();
++        return translations;
++      };
++
++      return frag.localize(getTranslationsForItems.bind(this))
++        .then(untranslatedElements => {
++          for (let i = 0; i < overlayTranslations.length; i++) {
++            if (overlayTranslations[i] !== undefined &&
++                untranslatedElements[i] !== undefined) {
++              translateElement(untranslatedElements[i], overlayTranslations[i]);
++            }
++          }
++          this.resumeObserving();
++        })
++        .catch(e => {
++          this.resumeObserving();
++          throw e;
++        });
++    }
+     return this.translateElements(this.getTranslatables(frag));
+   }
+ 
+@@ -700,37 +813,5 @@
    }
  }
  
--Localization.prototype.QueryInterface = ChromeUtils.generateQI([
--  Ci.nsISupportsWeakReference
--]);
+-/* global L10nRegistry, Services */
 -
- /**
-  * Format the value of a message into a string.
-  *
-@@ -449,5 +298,44 @@
-   return missingIds;
- }
- 
--this.Localization = Localization;
--var EXPORTED_SYMBOLS = ["Localization"];
-+/* global Components */
-+/* eslint no-unused-vars: 0 */
-+
-+const Cu = Components.utils;
-+const Cc = Components.classes;
-+const Ci = Components.interfaces;
-+
-+const { L10nRegistry } =
-+  Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
-+const ObserverService =
-+  Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-+const { Services } =
-+  Cu.import("resource://gre/modules/Services.jsm", {});
-+
-+/**
-+ * The default localization strategy for Gecko. It comabines locales
-+ * available in L10nRegistry, with locales requested by the user to
-+ * generate the iterator over 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 requestedLocales = Services.locale.getRequestedLocales();
-+  const availableLocales = L10nRegistry.getAvailableLocales();
-+  const defaultLocale = Services.locale.defaultLocale;
-+  const locales = Services.locale.negotiateLanguages(
-+    requestedLocales, availableLocales, defaultLocale,
-+  );
-+  return L10nRegistry.generateContexts(locales, resourceIds);
-+}
-+
-+class GeckoLocalization extends Localization {
-+  constructor(resourceIds, generateMessages = defaultGenerateMessages) {
-+    super(resourceIds, generateMessages);
-+  }
-+}
-+
-+this.Localization = GeckoLocalization;
-+this.EXPORTED_SYMBOLS = ["Localization"];
-diff -uN -x README -x moz.build -x L10nRegistry.jsm -x jar.mn -x fluent.js.patch ./intl/l10n/MessageContext.jsm /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/MessageContext.jsm
---- ./intl/l10n/MessageContext.jsm	2018-08-03 13:11:36.949029757 -0700
-+++ /home/zbraniecki/projects/fluent/fluent.js/fluent-gecko/dist/MessageContext.jsm	2018-08-01 09:15:58.176757688 -0700
-@@ -16,7 +16,7 @@
-  */
- 
- 
--/* fluent@0.6.0 */
-+/* fluent-dom@0.3.0 */
- 
- /*  eslint no-magic-numbers: [0]  */
- 
-@@ -1930,6 +1930,57 @@
-   }
- }
- 
-+/*
-+ * @overview
-+ *
-+ * Functions for managing ordered sequences of MessageContexts.
-+ *
-+ * An ordered iterable of MessageContext instances can represent the current
-+ * negotiated fallback chain of languages.  This iterable can be used to find
-+ * the best existing translation for a given identifier.
-+ *
-+ * The mapContext* methods can be used to find the first MessageContext in the
-+ * given iterable which contains the translation with the given identifier.  If
-+ * the iterable is ordered according to the result of a language negotiation
-+ * the returned MessageContext contains the best available translation.
-+ *
-+ * A simple function which formats translations based on the identifier might
-+ * be implemented as follows:
-+ *
-+ *     formatString(id, args) {
-+ *         const ctx = mapContextSync(contexts, id);
-+ *
-+ *         if (ctx === null) {
-+ *             return id;
-+ *         }
-+ *
-+ *         const msg = ctx.getMessage(id);
-+ *         return ctx.format(msg, args);
-+ *     }
-+ *
-+ * In order to pass an iterator to mapContext*, wrap it in
-+ * Cached{Sync|Async}Iterable.
-+ * This allows multiple calls to mapContext* without advancing and eventually
-+ * depleting the iterator.
-+ *
-+ *     function *generateMessages() {
-+ *         // Some lazy logic for yielding MessageContexts.
-+ *         yield *[ctx1, ctx2];
-+ *     }
-+ *
-+ *     const contexts = new CachedSyncIterable(generateMessages());
-+ *     const ctx = mapContextSync(contexts, id);
-+ *
-+ */
-+
-+/*
-+ * @module fluent
-+ * @overview
-+ *
-+ * `fluent` is a JavaScript implementation of Project Fluent, a localization
-+ * framework designed to unleash the expressive power of the natural language.
-+ *
-+ */
-+
- this.MessageContext = MessageContext;
--this.FluentResource = FluentResource;
--var EXPORTED_SYMBOLS = ["MessageContext", "FluentResource"];
-+this.EXPORTED_SYMBOLS = ["MessageContext"];
+-/**
+- * The default localization strategy for Gecko. It comabines locales
+- * available in L10nRegistry, with locales requested by the user to
+- * generate the iterator over FluentBundles.
+- *
+- * In the future, we may want to allow certain modules to override this
+- * with a different negotitation strategy to allow for the module to
+- * be localized into a different language - for example DevTools.
+- */
+-function defaultGenerateBundles(resourceIds) {
+-  const requestedLocales = Services.locale.getRequestedLocales();
+-  const availableLocales = L10nRegistry.getAvailableLocales();
+-  const defaultLocale = Services.locale.defaultLocale;
+-  const locales = Services.locale.negotiateLanguages(
+-    requestedLocales, availableLocales, defaultLocale,
+-  );
+-  return L10nRegistry.generateContexts(locales, resourceIds);
+-}
+-
+-
+-class GeckoDOMLocalization extends DOMLocalization {
+-  constructor(
+-    windowElement,
+-    resourceIds,
+-    generateBundles = defaultGenerateBundles
+-  ) {
+-    super(windowElement, resourceIds, generateBundles);
+-  }
+-}
+-
+-this.DOMLocalization = GeckoDOMLocalization;
+-this.EXPORTED_SYMBOLS = ["DOMLocalization"];
++this.DOMLocalization = DOMLocalization;
++var EXPORTED_SYMBOLS = ["DOMLocalization"];
--- a/intl/l10n/moz.build
+++ b/intl/l10n/moz.build
@@ -1,19 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES += [
     'DOMLocalization.jsm',
+    'Fluent.jsm',
     'L10nRegistry.jsm',
     'Localization.jsm',
-    'MessageContext.jsm',
 ]
 
 XPIDL_SOURCES += [
     'mozIDOMLocalization.idl',
 ]
 
 XPIDL_MODULE = 'locale'
 
--- a/intl/l10n/test/dom/test_domloc.xul
+++ b/intl/l10n/test/dom/test_domloc.xul
@@ -8,30 +8,30 @@
         title="Testing DOMLocalization in XUL environment">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript">
   <![CDATA[
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function * generateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`
 file-menu =
     .label = File
     .accesskey = F
 new-tab =
     .label = New Tab
     .accesskey = N
 `);
-    yield mc;
+    yield bundle;
   }
 
   SimpleTest.waitForExplicitFinish();
 
 
   const domLoc = new DOMLocalization(
     [],
     generateMessages
--- a/intl/l10n/test/dom/test_domloc_attr_sanitized.html
+++ b/intl/l10n/test/dom/test_domloc_attr_sanitized.html
@@ -4,27 +4,27 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's attr sanitization functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`
 key1 = Value for Key 1
 
 key2 = Value for <a>Key 2<a/>.
     `);
-    yield mc;
+    yield bundle;
   }
 
   SimpleTest.waitForExplicitFinish();
   addLoadEvent(async () => {
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
     );
--- a/intl/l10n/test/dom/test_domloc_mutations.html
+++ b/intl/l10n/test/dom/test_domloc_mutations.html
@@ -4,24 +4,24 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's MutationObserver</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages("title = Hello World");
-    mc.addMessages("title2 = Hello Another World");
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages("title = Hello World");
+    bundle.addMessages("title2 = Hello Another World");
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_overlay.html
+++ b/intl/l10n/test/dom/test_domloc_overlay.html
@@ -4,24 +4,24 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's DOMOverlay functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages("title = <strong>Hello</strong> World");
-    mc.addMessages(`title2 = This is <a data-l10n-name="link">a link</a>!`);
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages("title = <strong>Hello</strong> World");
+    bundle.addMessages(`title2 = This is <a data-l10n-name="link">a link</a>!`);
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_overlay_missing_all.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_all.html
@@ -4,23 +4,23 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's DOMOverlay functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
+    const bundle = new FluentBundle(locales);
     // No translations!
-    yield mc;
+    yield bundle;
   }
 
   SimpleTest.waitForExplicitFinish();
   addLoadEvent(async () => {
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
@@ -4,23 +4,23 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's DOMOverlay functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_overlay_repeated.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_repeated.html
@@ -4,23 +4,23 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's DOMOverlay functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_overlay_sanitized.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_sanitized.html
@@ -4,29 +4,29 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's DOMOverlay functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`
 key1 =
     .href = https://www.hacked.com
 
 key2 =
     .href = https://pl.wikipedia.org
 `);
-    yield mc;
+    yield bundle;
   }
 
   async function test() {
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
     );
 
--- a/intl/l10n/test/dom/test_domloc_repeated_l10nid.html
+++ b/intl/l10n/test/dom/test_domloc_repeated_l10nid.html
@@ -4,27 +4,27 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization's matching l10nIds functionality</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages(`
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages(`
 key1 = Translation For Key 1
 
 key2 = Visit <a data-l10n-name="link">this link<a/>.
     `);
-    yield mc;
+    yield bundle;
   }
 
   SimpleTest.waitForExplicitFinish();
   addLoadEvent(async () => {
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
     );
--- a/intl/l10n/test/dom/test_domloc_translateElements.html
+++ b/intl/l10n/test/dom/test_domloc_translateElements.html
@@ -4,24 +4,24 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization.prototype.translateElements</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages("title = Hello World");
-    mc.addMessages("link =\n    .title = Click me");
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages("title = Hello World");
+    bundle.addMessages("link =\n    .title = Click me");
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_translateFragment.html
+++ b/intl/l10n/test/dom/test_domloc_translateFragment.html
@@ -4,41 +4,41 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization.prototype.translateFragment</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages("title = Hello World");
-    mc.addMessages("subtitle = Welcome to Fluent");
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages("title = Hello World");
+    bundle.addMessages("subtitle = Welcome to FluentBundle");
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
     );
 
     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");
+    is(p1.textContent, "Welcome to FluentBundle");
 
     SimpleTest.finish();
   };
   </script>
 </head>
 <body>
   <div>
     <h1 data-l10n-id="title" />
--- a/intl/l10n/test/dom/test_domloc_translateRoots.html
+++ b/intl/l10n/test/dom/test_domloc_translateRoots.html
@@ -4,24 +4,24 @@
   <meta charset="utf-8">
   <title>Test DOMLocalization.prototype.translateRoots</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", {});
-  const { MessageContext } =
-    ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } =
+    ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
   async function* mockGenerateMessages(locales, resourceIds) {
-    const mc = new MessageContext(locales);
-    mc.addMessages("title = Hello World");
-    mc.addMessages("title2 = Hello Another World");
-    yield mc;
+    const bundle = new FluentBundle(locales);
+    bundle.addMessages("title = Hello World");
+    bundle.addMessages("title2 = Hello Another World");
+    yield bundle;
   }
 
   window.onload = async function() {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       [],
       mockGenerateMessages
--- a/intl/l10n/test/test_l10nregistry.js
+++ b/intl/l10n/test/test_l10nregistry.js
@@ -12,50 +12,50 @@ let fs;
 L10nRegistry.load = async function(url) {
   if (!fs.hasOwnProperty(url)) {
     return Promise.reject("Resource unavailable");
   }
   return fs[url];
 };
 
 add_task(function test_methods_presence() {
-  equal(typeof L10nRegistry.generateContexts, "function");
+  equal(typeof L10nRegistry.generateBundles, "function");
   equal(typeof L10nRegistry.getAvailableLocales, "function");
   equal(typeof L10nRegistry.registerSource, "function");
   equal(typeof L10nRegistry.updateSource, "function");
 });
 
 /**
  * Test that passing empty resourceIds list works.
  */
 add_task(async function test_empty_resourceids() {
   fs = {};
 
   const source = new FileSource("test", ["en-US"], "/localization/{locale}");
   L10nRegistry.registerSource(source);
 
-  const ctxs = L10nRegistry.generateContexts(["en-US"], []);
+  const bundles = L10nRegistry.generateBundles(["en-US"], []);
 
-  const done = (await ctxs.next()).done;
+  const done = (await bundles.next()).done;
 
   equal(done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * Test that passing empty sources list works.
  */
 add_task(async function test_empty_sources() {
   fs = {};
 
-  const ctxs = L10nRegistry.generateContexts(["en-US"], []);
+  const bundles = L10nRegistry.generateBundles(["en-US"], []);
 
-  const done = (await ctxs.next()).done;
+  const done = (await bundles.next()).done;
 
   equal(done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
@@ -65,21 +65,21 @@ add_task(async function test_empty_sourc
 add_task(async function test_methods_calling() {
   fs = {
     "/localization/en-US/browser/menu.ftl": "key = Value",
   };
 
   const source = new FileSource("test", ["en-US"], "/localization/{locale}");
   L10nRegistry.registerSource(source);
 
-  const ctxs = L10nRegistry.generateContexts(["en-US"], ["/browser/menu.ftl"]);
+  const bundles = L10nRegistry.generateBundles(["en-US"], ["/browser/menu.ftl"]);
 
-  const ctx = (await ctxs.next()).value;
+  const bundle = (await bundles.next()).value;
 
-  equal(ctx.hasMessage("key"), true);
+  equal(bundle.hasMessage("key"), true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that the public methods return expected values
  * for the single source scenario
@@ -95,28 +95,28 @@ add_task(async function test_has_one_sou
   // has one source
 
   equal(L10nRegistry.sources.size, 1);
   equal(L10nRegistry.sources.has("app"), true);
 
 
   // returns a single context
 
-  let ctxs = L10nRegistry.generateContexts(["en-US"], ["test.ftl"]);
-  let ctx0 = (await ctxs.next()).value;
-  equal(ctx0.hasMessage("key"), true);
+  let bundles = L10nRegistry.generateBundles(["en-US"], ["test.ftl"]);
+  let bundle0 = (await bundles.next()).value;
+  equal(bundle0.hasMessage("key"), true);
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
 
   // returns no contexts for missing locale
 
-  ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
+  bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that public methods return expected values
  * for the dual source scenario.
@@ -137,42 +137,42 @@ add_task(async function test_has_two_sou
 
   equal(L10nRegistry.sources.size, 2);
   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 ctx0 = (await ctxs.next()).value;
+  let bundles = L10nRegistry.generateBundles(["en-US"], ["test.ftl"]);
+  let bundle0 = (await bundles.next()).value;
 
-  equal(ctx0.hasMessage("key"), true);
-  let msg = ctx0.getMessage("key");
-  equal(ctx0.format(msg), "platform value");
+  equal(bundle0.hasMessage("key"), true);
+  let msg = bundle0.getMessage("key");
+  equal(bundle0.format(msg), "platform value");
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
 
   // returns correct contexts for [pl, en-US]
 
-  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");
+  bundles = L10nRegistry.generateBundles(["pl", "en-US"], ["test.ftl"]);
+  bundle0 = (await bundles.next()).value;
+  equal(bundle0.locales[0], "pl");
+  equal(bundle0.hasMessage("key"), true);
+  let msg0 = bundle0.getMessage("key");
+  equal(bundle0.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");
+  let bundle1 = (await bundles.next()).value;
+  equal(bundle1.locales[0], "en-US");
+  equal(bundle1.hasMessage("key"), true);
+  let msg1 = bundle1.getMessage("key");
+  equal(bundle1.format(msg1), "platform value");
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that behavior specific to the IndexedFileSource
  * works correctly.
@@ -216,30 +216,30 @@ add_task(async function test_override() 
   fs = {
     "/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);
 
-  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");
+  let bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  let bundle0 = (await bundles.next()).value;
+  equal(bundle0.locales[0], "pl");
+  equal(bundle0.hasMessage("key"), true);
+  let msg0 = bundle0.getMessage("key");
+  equal(bundle0.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");
+  let bundle1 = (await bundles.next()).value;
+  equal(bundle1.locales[0], "pl");
+  equal(bundle1.hasMessage("key"), true);
+  let msg1 = bundle1.getMessage("key");
+  equal(bundle1.format(msg1), "value");
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that new contexts are returned
  * after source update.
@@ -248,35 +248,35 @@ add_task(async function test_updating() 
   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",
   };
 
-  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");
+  let bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  let bundle0 = (await bundles.next()).value;
+  equal(bundle0.locales[0], "pl");
+  equal(bundle0.hasMessage("key"), true);
+  let msg0 = bundle0.getMessage("key");
+  equal(bundle0.format(msg0), "value");
 
 
   const newSource = new IndexedFileSource("langpack-pl", ["pl"], "/data/locales/{locale}/", [
     "/data/locales/pl/test.ftl",
   ]);
   fs["/data/locales/pl/test.ftl"] = "key = new value";
   L10nRegistry.updateSource(newSource);
 
   equal(L10nRegistry.sources.size, 1);
-  ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
-  ctx0 = (await ctxs.next()).value;
-  msg0 = ctx0.getMessage("key");
-  equal(ctx0.format(msg0), "new value");
+  bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  bundle0 = (await bundles.next()).value;
+  msg0 = bundle0.getMessage("key");
+  equal(bundle0.format(msg0), "new value");
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that generated contexts return correct values
  * after sources are being removed.
@@ -293,55 +293,55 @@ add_task(async function test_removing() 
   fs = {
     "/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);
 
-  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");
+  let bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  let bundle0 = (await bundles.next()).value;
+  equal(bundle0.locales[0], "pl");
+  equal(bundle0.hasMessage("key"), true);
+  let msg0 = bundle0.getMessage("key");
+  equal(bundle0.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");
+  let bundle1 = (await bundles.next()).value;
+  equal(bundle1.locales[0], "pl");
+  equal(bundle1.hasMessage("key"), true);
+  let msg1 = bundle1.getMessage("key");
+  equal(bundle1.format(msg1), "value");
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // Remove langpack
 
   L10nRegistry.removeSource("langpack-pl");
 
   equal(L10nRegistry.sources.size, 1);
   equal(L10nRegistry.sources.has("langpack-pl"), false);
 
-  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");
+  bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  bundle0 = (await bundles.next()).value;
+  equal(bundle0.locales[0], "pl");
+  equal(bundle0.hasMessage("key"), true);
+  msg0 = bundle0.getMessage("key");
+  equal(bundle0.format(msg0), "value");
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // Remove app source
 
   L10nRegistry.removeSource("app");
 
   equal(L10nRegistry.sources.size, 0);
 
-  ctxs = L10nRegistry.generateContexts(["pl"], ["test.ftl"]);
-  equal((await ctxs.next()).done, true);
+  bundles = L10nRegistry.generateBundles(["pl"], ["test.ftl"]);
+  equal((await bundles.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that the logic works correctly when there's a missing
  * file in the FileSource scenario.
@@ -363,33 +363,33 @@ add_task(async function test_missing_fil
 
   equal(L10nRegistry.sources.size, 2);
   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 bundles = L10nRegistry.generateBundles(["en-US"], ["test.ftl", "test2.ftl"]);
 
   // First permutation:
   //   [platform, platform] - both present
-  let ctx1 = (await ctxs.next());
-  equal(ctx1.value.hasMessage("key"), true);
+  let bundle1 = (await bundles.next());
+  equal(bundle1.value.hasMessage("key"), true);
 
   // Second permutation skipped:
   //   [platform, app] - second missing
   // Third permutation:
   //   [app, platform] - both present
-  let ctx2 = (await ctxs.next());
-  equal(ctx2.value.hasMessage("key"), true);
+  let bundle2 = (await bundles.next());
+  equal(bundle2.value.hasMessage("key"), true);
 
   // Fourth permutation skipped:
   //   [app, app] - second missing
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
 });
 
 /**
  * This test verifies that each file is that all files requested
  * by a single context are fetched at the same time, even
@@ -427,26 +427,26 @@ add_task(async function test_parallel_io
   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",
   };
 
   // returns a single context
 
-  let ctxs = L10nRegistry.generateContexts(["en-US"], ["slow-file.ftl", "test.ftl", "test2.ftl"]);
+  let bundles = L10nRegistry.generateBundles(["en-US"], ["slow-file.ftl", "test.ftl", "test2.ftl"]);
 
   equal(fetchIndex.size, 0);
 
-  let ctx0 = await ctxs.next();
+  let bundle0 = await bundles.next();
 
-  equal(ctx0.done, false);
+  equal(bundle0.done, false);
 
-  equal((await ctxs.next()).done, true);
+  equal((await bundles.next()).done, true);
 
   // When requested again, the cache should make the load operation not
   // increase the fetchedIndex count
-  L10nRegistry.generateContexts(["en-US"], ["test.ftl", "test2.ftl", "slow-file.ftl"]);
+  L10nRegistry.generateBundles(["en-US"], ["test.ftl", "test2.ftl", "slow-file.ftl"]);
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.load = originalLoad;
 });
--- a/intl/l10n/test/test_localization.js
+++ b/intl/l10n/test/test_localization.js
@@ -25,17 +25,17 @@ add_task(async function test_methods_cal
   L10nRegistry.load = async function(url) {
     return fs[url];
   };
 
   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.generateBundles(["de", "en-US"], resIds);
   }
 
   const l10n = new Localization([
     "/browser/menu.ftl",
   ], generateMessages);
 
   let values = await l10n.formatValues([{id: "key"}, {id: "key2"}]);
 
@@ -71,17 +71,17 @@ key = { PLATFORM() ->
   L10nRegistry.load = async function(url) {
     return fs[url];
   };
 
   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.generateBundles(["en-US"], resIds);
   }
 
   const l10n = new Localization([
     "/test.ftl",
   ], generateMessages);
 
   let values = await l10n.formatValues([{id: "key"}]);
 
@@ -106,17 +106,17 @@ add_task(async function test_add_remove_
   L10nRegistry.load = async function(url) {
     return fs[url];
   };
 
   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.generateBundles(["en-US"], resIds);
   }
 
   const l10n = new Localization(["/browser/menu.ftl"], generateMessages);
 
   let values = await l10n.formatValues([{id: "key1"}, {id: "key2"}]);
 
   equal(values[0], "Value1");
   equal(values[1], undefined);
--- a/intl/l10n/test/test_messagecontext.js
+++ b/intl/l10n/test/test_messagecontext.js
@@ -1,33 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
-  const { MessageContext } = ChromeUtils.import("resource://gre/modules/MessageContext.jsm", {});
+  const { FluentBundle } = ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 
-  test_methods_presence(MessageContext);
-  test_methods_calling(MessageContext);
+  test_methods_presence(FluentBundle);
+  test_methods_calling(FluentBundle);
 
   ok(true);
 }
 
-function test_methods_presence(MessageContext) {
-  const ctx = new MessageContext(["en-US", "pl"]);
-  equal(typeof ctx.addMessages, "function");
-  equal(typeof ctx.format, "function");
+function test_methods_presence(FluentBundle) {
+  const bundle = new FluentBundle(["en-US", "pl"]);
+  equal(typeof bundle.addMessages, "function");
+  equal(typeof bundle.format, "function");
 }
 
-function test_methods_calling(MessageContext) {
-  const ctx = new MessageContext(["en-US", "pl"], {
+function test_methods_calling(FluentBundle) {
+  const bundle = new FluentBundle(["en-US", "pl"], {
     useIsolating: false,
   });
-  ctx.addMessages("key = Value");
+  bundle.addMessages("key = Value");
 
-  const msg = ctx.getMessage("key");
-  equal(ctx.format(msg), "Value");
+  const msg = bundle.getMessage("key");
+  equal(bundle.format(msg), "Value");
 
-  ctx.addMessages("key2 = Hello { $name }");
+  bundle.addMessages("key2 = Hello { $name }");
 
-  const msg2 = ctx.getMessage("key2");
-  equal(ctx.format(msg2, { name: "Amy" }), "Hello Amy");
+  const msg2 = bundle.getMessage("key2");
+  equal(bundle.format(msg2, { name: "Amy" }), "Hello Amy");
   ok(true);
 }
--- a/intl/l10n/test/test_pseudo.js
+++ b/intl/l10n/test/test_pseudo.js
@@ -22,17 +22,17 @@ key = This is a single message
   L10nRegistry.load = async function(url) {
     return fs[url];
   };
 
   const source = new FileSource("test", ["de"], "/localization/{locale}");
   L10nRegistry.registerSource(source);
 
   return async function* generateMessages(resIds) {
-    yield * await L10nRegistry.generateContexts(["de"], resIds);
+    yield * await L10nRegistry.generateBundles(["de"], resIds);
   };
 }
 
 /**
  * This test verifies that as we switching between
  * different pseudo strategies the Localization object
  * follows and formats using the given strategy.
  *
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
@@ -115,26 +115,26 @@ add_task(async function() {
 add_task(async function() {
   let [, {addon}] = await Promise.all([
     promiseLangpackStartup(),
     AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
   ]);
 
   {
     // Toolkit string
-    let ctxs = L10nRegistry.generateContexts(["und"], ["toolkit_test.ftl"]);
-    let ctx0 = (await ctxs.next()).value;
-    equal(ctx0.hasMessage("message-id1"), true);
+    let bundles = L10nRegistry.generateBundles(["und"], ["toolkit_test.ftl"]);
+    let bundle0 = (await bundles.next()).value;
+    equal(bundle0.hasMessage("message-id1"), true);
   }
 
   {
     // Browser string
-    let ctxs = L10nRegistry.generateContexts(["und"], ["browser.ftl"]);
-    let ctx0 = (await ctxs.next()).value;
-    equal(ctx0.hasMessage("message-browser"), true);
+    let bundles = L10nRegistry.generateBundles(["und"], ["browser.ftl"]);
+    let bundle0 = (await bundles.next()).value;
+    equal(bundle0.hasMessage("message-browser"), true);
   }
 
   {
     // Test chrome package
     let reqLocs = Services.locale.requestedLocales;
     Services.locale.requestedLocales = ["und"];
 
     let bundle = Services.strings.createBundle(