diff --git a/.gitignore b/.gitignore
index 20c62c8ea..c28424f5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@ node_modules
html
.vscode
+/fluent-*/dist/
+
# Test coverage
.nyc_output
coverage
diff --git a/.prettierignore b/.prettierignore
index 195cca863..6b53b9882 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,8 +4,6 @@ html
coverage/
dist/
-esm/
-/fluent-*/index.js
/tools/**/*.ftl
/tools/**/*.json
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 409194ea8..252cdd97d 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,7 +10,6 @@ export default [
"**/node_modules/",
"*/coverage/",
"*/dist/",
- "*/esm/",
"*/vendor/",
"fluent-*/index.js",
"html/",
diff --git a/fluent-bundle/.gitignore b/fluent-bundle/.gitignore
deleted file mode 100644
index 0bd1f3fc5..000000000
--- a/fluent-bundle/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-esm/
-/index.js
diff --git a/fluent-bundle/.npmignore b/fluent-bundle/.npmignore
deleted file mode 100644
index abad613ff..000000000
--- a/fluent-bundle/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-.nyc_output
-coverage
-esm/.compiled
-src
-test
-makefile
-tsconfig.json
diff --git a/fluent-bundle/esm/package.json b/fluent-bundle/esm/package.json
deleted file mode 100644
index 3dbc1ca59..000000000
--- a/fluent-bundle/esm/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "type": "module"
-}
diff --git a/fluent-bundle/package.json b/fluent-bundle/package.json
index 730a4f357..362e570c4 100644
--- a/fluent-bundle/package.json
+++ b/fluent-bundle/package.json
@@ -2,6 +2,16 @@
"name": "@fluent/bundle",
"description": "Localization library for expressive translations.",
"version": "0.19.1",
+ "type": "module",
+ "exports": {
+ ".": "./dist/index.js",
+ "./ast": "./dist/ast.js",
+ "./builtins": "./dist/builtins.js",
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "./dist/"
+ ],
"homepage": "https://projectfluent.org",
"author": "Mozilla ",
"license": "Apache-2.0",
@@ -15,9 +25,6 @@
"email": "stas@mozilla.com"
}
],
- "main": "./index.js",
- "module": "./esm/index.js",
- "types": "./esm/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/projectfluent/fluent.js.git"
@@ -39,14 +46,12 @@
"parser"
],
"scripts": {
- "build": "tsc",
- "postbuild": "rollup -c ../rollup.config.mjs"
+ "build": "tsc"
},
"engines": {
"node": "^20.19 || ^22.12 || >=24"
},
"devDependencies": {
- "@fluent/dedent": "^0.5.0",
"temporal-polyfill": "^0.2.5"
}
}
diff --git a/fluent-bundle/src/index.ts b/fluent-bundle/src/index.ts
index 0470f9e56..726ccef86 100644
--- a/fluent-bundle/src/index.ts
+++ b/fluent-bundle/src/index.ts
@@ -6,14 +6,14 @@
*/
export type { Message } from "./ast.js";
-export { FluentBundle, TextTransform } from "./bundle.js";
+export { FluentBundle, type TextTransform } from "./bundle.js";
export { FluentResource } from "./resource.js";
export type { Scope } from "./scope.js";
export {
- FluentValue,
- FluentVariable,
- FluentType,
- FluentFunction,
+ type FluentValue,
+ type FluentVariable,
+ type FluentType,
+ type FluentFunction,
FluentNone,
FluentNumber,
FluentDateTime,
diff --git a/fluent-bundle/tsconfig.json b/fluent-bundle/tsconfig.json
index 1a76d9a43..ae18d140e 100644
--- a/fluent-bundle/tsconfig.json
+++ b/fluent-bundle/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
- "outDir": "./esm",
+ "outDir": "./dist",
"rootDir": "./src"
},
"include": ["./src/**/*.ts"]
diff --git a/fluent-dedent/.gitignore b/fluent-dedent/.gitignore
deleted file mode 100644
index 0bd1f3fc5..000000000
--- a/fluent-dedent/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-esm/
-/index.js
diff --git a/fluent-dedent/.npmignore b/fluent-dedent/.npmignore
deleted file mode 100644
index abad613ff..000000000
--- a/fluent-dedent/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-.nyc_output
-coverage
-esm/.compiled
-src
-test
-makefile
-tsconfig.json
diff --git a/fluent-dedent/esm/package.json b/fluent-dedent/esm/package.json
deleted file mode 100644
index 3dbc1ca59..000000000
--- a/fluent-dedent/esm/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "type": "module"
-}
diff --git a/fluent-dedent/package.json b/fluent-dedent/package.json
index 9f60cbd64..973e11994 100644
--- a/fluent-dedent/package.json
+++ b/fluent-dedent/package.json
@@ -2,6 +2,14 @@
"name": "@fluent/dedent",
"description": "A template literal tag for dedenting Fluent code",
"version": "0.5.0",
+ "type": "module",
+ "exports": {
+ ".": "./dist/index.js",
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "./dist/"
+ ],
"homepage": "https://projectfluent.org",
"author": "Mozilla ",
"license": "Apache-2.0",
@@ -11,9 +19,6 @@
"email": "stas@mozilla.com"
}
],
- "main": "./index.js",
- "module": "./esm/index.js",
- "types": "./esm/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/projectfluent/fluent.js.git"
@@ -24,8 +29,7 @@
"ftl"
],
"scripts": {
- "build": "tsc",
- "postbuild": "rollup -c ../rollup.config.mjs"
+ "build": "tsc"
},
"engines": {
"node": "^20.19 || ^22.12 || >=24"
diff --git a/fluent-dedent/tsconfig.json b/fluent-dedent/tsconfig.json
index 1c5501631..d3d83334d 100644
--- a/fluent-dedent/tsconfig.json
+++ b/fluent-dedent/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
- "outDir": "./esm"
+ "outDir": "./dist"
},
"include": ["./src/**/*.ts"]
}
diff --git a/fluent-dom/.gitignore b/fluent-dom/.gitignore
deleted file mode 100644
index 0bd1f3fc5..000000000
--- a/fluent-dom/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-esm/
-/index.js
diff --git a/fluent-dom/.npmignore b/fluent-dom/.npmignore
deleted file mode 100644
index abad613ff..000000000
--- a/fluent-dom/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-.nyc_output
-coverage
-esm/.compiled
-src
-test
-makefile
-tsconfig.json
diff --git a/fluent-dom/esm/package.json b/fluent-dom/esm/package.json
deleted file mode 100644
index 3dbc1ca59..000000000
--- a/fluent-dom/esm/package.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "type": "module"
-}
diff --git a/fluent-dom/package.json b/fluent-dom/package.json
index 315a853a1..b29bcebce 100644
--- a/fluent-dom/package.json
+++ b/fluent-dom/package.json
@@ -2,6 +2,15 @@
"name": "@fluent/dom",
"version": "0.10.2",
"description": "Fluent bindings for DOM",
+ "type": "module",
+ "exports": {
+ ".": "./dist/index.js",
+ "./overlay": "./dist/overlay.js",
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "./dist/"
+ ],
"repository": {
"type": "git",
"url": "https://github.com/projectfluent/fluent.js.git"
@@ -18,9 +27,6 @@
"email": "stas@mozilla.com"
}
],
- "main": "./index.js",
- "module": "./esm/index.js",
- "types": "./esm/index.d.ts",
"keywords": [
"localization",
"l10n",
@@ -34,15 +40,11 @@
"format"
],
"scripts": {
- "build": "tsc",
- "postbuild": "rollup -c ../rollup.config.mjs --globals cached-iterable:CachedIterable"
+ "build": "tsc"
},
"engines": {
"node": "^20.19 || ^22.12 || >=24"
},
- "devDependencies": {
- "@fluent/bundle": "^0.19.0"
- },
"dependencies": {
"cached-iterable": "^0.3"
}
diff --git a/fluent-dom/src/dom_localization.js b/fluent-dom/src/dom_localization.ts
similarity index 68%
rename from fluent-dom/src/dom_localization.js
rename to fluent-dom/src/dom_localization.ts
index dc7e410e7..717962458 100644
--- a/fluent-dom/src/dom_localization.js
+++ b/fluent-dom/src/dom_localization.ts
@@ -1,5 +1,10 @@
+import type { FluentBundle } from "@fluent/bundle";
+import {
+ Localization,
+ type FormattedMessage,
+ type MessageKey,
+} from "./localization.js";
import translateElement from "./overlay.js";
-import Localization from "./localization.js";
const L10NID_ATTR_NAME = "data-l10n-id";
const L10NARGS_ATTR_NAME = "data-l10n-args";
@@ -14,37 +19,36 @@ const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
* formatting of translations and methods for observing DOM
* trees with a `MutationObserver`.
*/
-export default class DOMLocalization extends Localization {
- /**
- * @param {Array} resourceIds - List of resource IDs
- * @param {Function} generateBundles - Function that returns a
- * generator over FluentBundles
- * @returns {DOMLocalization}
- */
- constructor(resourceIds, generateBundles) {
- super(resourceIds, generateBundles);
+export class DOMLocalization extends Localization {
+ // A Set of DOM trees observed by the `MutationObserver`.
+ roots = new Set();
- // 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;
- this.mutationObserver = null;
-
- this.observerConfig = {
- attributes: true,
- characterData: false,
- childList: true,
- subtree: true,
- attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
- };
+ // requestAnimationFrame handler.
+ pendingrAF: number | null = null;
+
+ pendingElements = new Set();
+ windowElement: (Window & typeof globalThis) | null = null;
+ mutationObserver: MutationObserver | null = null;
+
+ observerConfig = {
+ attributes: true,
+ characterData: false,
+ childList: true,
+ subtree: true,
+ attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
+ };
+
+ constructor(
+ resourceIds: string[],
+ generateBundles: (resourceIds: string[]) => Iterable
+ ) {
+ super(resourceIds, generateBundles);
}
- onChange(eager = false) {
+ onChange(eager = false): void {
super.onChange(eager);
if (this.roots) {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.translateRoots();
}
}
@@ -79,13 +83,23 @@ export default class DOMLocalization extends Localization {
*
* ```
*
- * @param {Element} element - Element to set attributes on
- * @param {string} id - l10n-id string
- * @param {Object} args - KVP list of l10n arguments
- * @returns {Element}
+ * @param element - Element to set attributes on
+ * @param id - l10n-id string
+ * @param args - KVP list of l10n arguments
*/
- setAttributes(element, id, args) {
+ setAttributes(
+ element: Element,
+ id: string,
+ args?: Record
+ ): Element {
element.setAttribute(L10NID_ATTR_NAME, id);
+ return this.setArgs(element, args);
+ }
+
+ /**
+ * Set only the `data-l10n-args` attribute on a DOM element.
+ */
+ setArgs(element: Element, args?: Record): Element {
if (args) {
element.setAttribute(L10NARGS_ATTR_NAME, JSON.stringify(args));
} else {
@@ -103,14 +117,15 @@ export default class DOMLocalization extends Localization {
* );
* // -> { id: 'hello', args: { who: 'world' } }
* ```
- *
- * @param {Element} element - HTML element
- * @returns {{id: string, args: Object}}
*/
- getAttributes(element) {
+ getAttributes(element: Element): {
+ id: string | null;
+ args: Record | null;
+ } {
+ const args = element.getAttribute(L10NARGS_ATTR_NAME);
return {
id: element.getAttribute(L10NID_ATTR_NAME),
- args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
+ args: args ? (JSON.parse(args) as Record) : null,
};
}
@@ -119,10 +134,8 @@ export default class DOMLocalization extends Localization {
*
* Additionally, if this `DOMLocalization` has an observer, start observing
* `newRoot` in order to translate mutations in it.
- *
- * @param {Element | DocumentFragment} newRoot - Root to observe.
*/
- connectRoot(newRoot) {
+ connectRoot(newRoot: Element | DocumentFragment): void {
for (const root of this.roots) {
if (
root === newRoot ||
@@ -140,13 +153,13 @@ export default class DOMLocalization extends Localization {
}
} else {
this.windowElement = newRoot.ownerDocument.defaultView;
- this.mutationObserver = new this.windowElement.MutationObserver(
+ this.mutationObserver = new this.windowElement!.MutationObserver(
mutations => this.translateMutations(mutations)
);
}
this.roots.add(newRoot);
- this.mutationObserver.observe(newRoot, this.observerConfig);
+ this.mutationObserver!.observe(newRoot, this.observerConfig);
}
/**
@@ -157,11 +170,8 @@ export default class DOMLocalization extends Localization {
*
* Returns `true` if the root was the last one managed by this
* `DOMLocalization`.
- *
- * @param {Element | DocumentFragment} root - Root to disconnect.
- * @returns {boolean}
*/
- disconnectRoot(root) {
+ disconnectRoot(root: Element | DocumentFragment): boolean {
this.roots.delete(root);
// Pause the mutation observer to stop observing `root`.
this.pauseObserving();
@@ -187,7 +197,7 @@ export default class DOMLocalization extends Localization {
*
* @returns {Promise}
*/
- translateRoots() {
+ translateRoots(): Promise {
const roots = Array.from(this.roots);
return Promise.all(roots.map(root => this.translateFragment(root)));
}
@@ -195,7 +205,7 @@ export default class DOMLocalization extends Localization {
/**
* Pauses the `MutationObserver`.
*/
- pauseObserving() {
+ pauseObserving(): void {
if (!this.mutationObserver) {
return;
}
@@ -207,7 +217,7 @@ export default class DOMLocalization extends Localization {
/**
* Resumes the `MutationObserver`.
*/
- resumeObserving() {
+ resumeObserving(): void {
if (!this.mutationObserver) {
return;
}
@@ -219,19 +229,19 @@ export default class DOMLocalization extends Localization {
/**
* Translate mutations detected by the `MutationObserver`.
- *
- * @private
*/
- translateMutations(mutations) {
+ private translateMutations(mutations: MutationRecord[]): void {
for (const mutation of mutations) {
switch (mutation.type) {
- case "attributes":
- if (mutation.target.hasAttribute("data-l10n-id")) {
- this.pendingElements.add(mutation.target);
+ case "attributes": {
+ const target = mutation.target as HTMLElement;
+ if (target.hasAttribute("data-l10n-id")) {
+ this.pendingElements.add(target);
}
break;
+ }
case "childList":
- for (const addedNode of mutation.addedNodes) {
+ for (const addedNode of mutation.addedNodes as NodeListOf) {
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
if (addedNode.childElementCount) {
for (const element of this.getTranslatables(addedNode)) {
@@ -250,7 +260,8 @@ export default class DOMLocalization extends Localization {
// into a single requestAnimationFrame.
if (this.pendingElements.size > 0) {
if (this.pendingrAF === null) {
- this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
+ this.pendingrAF = this.windowElement!.requestAnimationFrame(() => {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.translateElements(Array.from(this.pendingElements));
this.pendingElements.clear();
this.pendingrAF = null;
@@ -269,10 +280,10 @@ export default class DOMLocalization extends Localization {
*
* Returns a `Promise` that gets resolved once the translation is complete.
*
- * @param {Element | DocumentFragment} frag - Element or DocumentFragment to be translated
+ * @param {} frag - Element or DocumentFragment to be translated
* @returns {Promise}
*/
- translateFragment(frag) {
+ translateFragment(frag: Element | DocumentFragment): Promise {
return this.translateElements(this.getTranslatables(frag));
}
@@ -285,28 +296,25 @@ export default class DOMLocalization extends Localization {
* 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) {
+ async translateElements(elements: Element[]): Promise {
if (!elements.length) {
return undefined;
}
- const keys = elements.map(this.getKeysForElement);
- const translations = await this.formatMessages(keys);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const keys = elements.map(this.getAttributes);
+ const translations = await this.formatMessages(keys as MessageKey[]);
return this.applyTranslations(elements, translations);
}
/**
* Applies translations onto elements.
- *
- * @param {Array} elements
- * @param {Array