diff --git a/fluent-dom/README.md b/fluent-dom/README.md
index 1e77142b..622de98b 100644
--- a/fluent-dom/README.md
+++ b/fluent-dom/README.md
@@ -41,6 +41,39 @@ const h1 = document.querySelector("h1");
l10n.setAttributes(h1, "welcome", { user: "Anna" });
```
+If your application already has a rendering lifecycle, for example when it's
+built with Web Components or a component framework, the `MutationObserver`
+used by `DOMLocalization` can become a performance problem as the number of
+connected roots grows. The `MiniDOMLocalization` class provides the same DOM
+translation API without the observer. Elements are only translated when you
+ask for it, typically from the component's render hook, and connected roots are
+retranslated when the language changes.
+
+```javascript
+import { MiniDOMLocalization } from "@fluent/dom";
+
+const l10n = new MiniDOMLocalization(
+ ["/browser/main.ftl", "/toolkit/menu.ftl"],
+ generateBundles
+);
+
+class MyElement extends HTMLElement {
+ connectedCallback() {
+ l10n.connectRoot(this.shadowRoot);
+ }
+
+ disconnectedCallback() {
+ l10n.disconnectRoot(this.shadowRoot);
+ }
+
+ render() {
+ this.shadowRoot.innerHTML = `
`;
+ // Nothing observes the DOM, so translate explicitly after rendering.
+ l10n.translateFragment(this.shadowRoot);
+ }
+}
+```
+
For imperative uses straight from the JS code, there's also a `Localization`
class that provides just the API needed to format messages in the running code.
diff --git a/fluent-dom/src/dom_localization.js b/fluent-dom/src/dom_localization.js
index dc7e410e..ec48662a 100644
--- a/fluent-dom/src/dom_localization.js
+++ b/fluent-dom/src/dom_localization.js
@@ -1,10 +1,7 @@
-import translateElement from "./overlay.js";
-import Localization from "./localization.js";
-
-const L10NID_ATTR_NAME = "data-l10n-id";
-const L10NARGS_ATTR_NAME = "data-l10n-args";
-
-const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
+import MiniDOMLocalization, {
+ L10NID_ATTR_NAME,
+ L10NARGS_ATTR_NAME,
+} from "./mini_dom_localization.js";
/**
* The `DOMLocalization` class is responsible for fetching resources and
@@ -13,8 +10,10 @@ const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
* It implements the fallback strategy in case of errors encountered during the
* formatting of translations and methods for observing DOM
* trees with a `MutationObserver`.
+ *
+ * See `MiniDOMLocalization` for a variant without the `MutationObserver`.
*/
-export default class DOMLocalization extends Localization {
+export default class DOMLocalization extends MiniDOMLocalization {
/**
* @param {Array} resourceIds - List of resource IDs
* @param {Function} generateBundles - Function that returns a
@@ -24,8 +23,6 @@ export default class DOMLocalization extends Localization {
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.
@@ -42,13 +39,6 @@ export default class DOMLocalization extends Localization {
};
}
- onChange(eager = false) {
- super.onChange(eager);
- if (this.roots) {
- this.translateRoots();
- }
- }
-
/**
* Set the `data-l10n-id` and `data-l10n-args` attributes on DOM elements.
* FluentDOM makes use of mutation observers to detect changes
@@ -94,26 +84,6 @@ export default class DOMLocalization extends Localization {
return element;
}
- /**
- * Get the `data-l10n-*` attributes from DOM elements.
- *
- * ```javascript
- * localization.getAttributes(
- * document.querySelector('#welcome')
- * );
- * // -> { id: 'hello', args: { who: 'world' } }
- * ```
- *
- * @param {Element} element - HTML element
- * @returns {{id: string, args: Object}}
- */
- getAttributes(element) {
- return {
- id: element.getAttribute(L10NID_ATTR_NAME),
- args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
- };
- }
-
/**
* Add `newRoot` to the list of roots managed by this `DOMLocalization`.
*
@@ -145,7 +115,7 @@ export default class DOMLocalization extends Localization {
);
}
- this.roots.add(newRoot);
+ super.connectRoot(newRoot);
this.mutationObserver.observe(newRoot, this.observerConfig);
}
@@ -162,11 +132,11 @@ export default class DOMLocalization extends Localization {
* @returns {boolean}
*/
disconnectRoot(root) {
- this.roots.delete(root);
+ const wasLast = super.disconnectRoot(root);
// Pause the mutation observer to stop observing `root`.
this.pauseObserving();
- if (this.roots.size === 0) {
+ if (wasLast) {
this.mutationObserver = null;
if (this.windowElement && this.pendingrAF) {
this.windowElement.cancelAnimationFrame(this.pendingrAF);
@@ -182,16 +152,6 @@ export default class DOMLocalization extends Localization {
return false;
}
- /**
- * Translate all roots associated with this `DOMLocalization`.
- *
- * @returns {Promise}
- */
- translateRoots() {
- const roots = Array.from(this.roots);
- return Promise.all(roots.map(root => this.translateFragment(root)));
- }
-
/**
* Pauses the `MutationObserver`.
*/
@@ -259,97 +219,29 @@ export default class DOMLocalization extends Localization {
}
}
- /**
- * Translate a DOM element or fragment asynchronously using this
- * `DOMLocalization` object.
- *
- * Manually trigger the translation (or re-translation) of a DOM fragment.
- * Use the `data-l10n-id` and `data-l10n-args` attributes to mark up the DOM
- * with information about which translations to use.
- *
- * Returns a `Promise` that gets resolved once the translation is complete.
- *
- * @param {Element | DocumentFragment} frag - Element or DocumentFragment to be translated
- * @returns {Promise}
- */
- translateFragment(frag) {
- return this.translateElements(this.getTranslatables(frag));
- }
-
- /**
- * Translate a list of DOM elements asynchronously using this
- * `DOMLocalization` object.
- *
- * Manually trigger the translation (or re-translation) of a list of elements.
- * Use the `data-l10n-id` and `data-l10n-args` attributes to mark up the DOM
- * with information about which translations to use.
- *
- * Returns a `Promise` that gets resolved once the translation is complete.
- *
- * @param {Array} elements - List of elements to be translated
- * @returns {Promise}
- */
- async translateElements(elements) {
- if (!elements.length) {
- return undefined;
- }
-
- const keys = elements.map(this.getKeysForElement);
- const translations = await this.formatMessages(keys);
- return this.applyTranslations(elements, translations);
- }
-
/**
* Applies translations onto elements.
*
* @param {Array} elements
* @param {Array