@bandf/i18n
v1.1.2
Published
Automatic, on-device internationalization for plain JavaScript and native Web Components, powered by the Bergamot translation engine compiled to WebAssembly.
Maintainers
Readme
@bandf/i18n
On-device internationalization for plain JavaScript and Web Components. Translation runs in the browser, in a worker, with the Bergamot engine (the engine behind Firefox Translations) compiled to WebAssembly and Mozilla's models: 54 languages from English, most of them both ways. Nothing is sent to a server; there is no key, no catalog and no build step to start with. Pages are not annotated: every text node, inline sentence and translatable attribute is translated in place, in the same nodes, and restored on the way back.
| you want to | read |
|---|---|
| see it run | The demo |
| add it to a Vite project | In a Vite project, docs/VITE.md |
| add it to a site without Vite | On any static host, docs/DEPLOYMENT.md |
| translate from Node or a script | Command line, docs/API.md |
| know what the page must and must not contain | Markup, docs/API.md |
| the full API, options and events | docs/API.md |
| how the DOM layer and the text pipeline work | docs/ARCHITECTURE.md, docs/TEXT-PIPELINE.md |
| check every part by hand | docs/QA.md |
| work on the library itself | Working in the repository |
| know how this differs from the browser's own translation | Why not the browser's translation |
The demo
npx @bandf/i18n # serves the demo from the package on a free port and opens it; --port=, --no-openThe demo is an unannotated page with a <language-select>, and links at the bottom to every test
page: the library against a fake engine, real Lit components, the engine's budgets, a page that
rewrites itself, hostile inputs, and snapshots of Wikipedia, MDN, Bootstrap and other public pages.
The first language chosen downloads its model (25–40 MB) into the package's own models/
directory; a corpus page is snapshotted on first visit; Lit is installed for its page on first
visit.
In a Vite project
npm install @bandf/i18n # or yarn add @bandf/i18n// vite.config.js
import { bandfI18n } from '@bandf/i18n/vite';
export default { plugins: [bandfI18n({ directions: [`en-fr`, `en-de`] })] };<language-select></language-select>import { createI18n, defineLanguageSelect } from '@bandf/i18n';
const i18n = createI18n({ sourceLocale: `en` });
defineLanguageSelect(i18n);
i18n.observe(document);The plugin serves the models in development, downloading a direction the first time a page asks
for it (verified against the hashes pinned in the package) into
node_modules/.cache/bandf-i18n/models, and vite build copies the declared directions into
dist/models/. prerender: { languages: ['fr'] } also writes dist/fr/index.html in French, so
that visit downloads no model and search engines index French text. Build-time catalogs, source
recording and every option: docs/VITE.md.
On any static host
npx @bandf/i18n models en-fr en-de # downloads the pinned models into ./modelsServe that directory as models/ next to your pages, or name it with
<meta name="bandf-i18n-models" content="/path/to/models/"> or
createI18n({ models: { modelsUrl } }). Mozilla's registry does not allow cross-origin requests;
every site hosts the models it offers. Headers, caching, storage and memory: docs/DEPLOYMENT.md.
Markup
The library reads only standard HTML:
translate="no"(orclass="notranslate") — leave this subtree alone: brand names, formatted values, user content.lang="fr"— this subtree is already in another language.title,alt,placeholder,aria-labeland the other translatable attributes are translated;code,kbd,samp,var,pre, text the user typed and embedded media are not.
Open shadow roots are followed; a closed one is passed explicitly (i18n.observe(this.shadowRoot)).
Content the application writes later is translated as it changes, cached translations are applied
before the next paint, and a locale change back to the source restores the original nodes and text.
Reviewed translations are overrides keyed by source text; short labels come from a ten-language
glossary or a role-aware carrier sentence. Placeholders such as {name} are protected, and an
output that loses one is rejected rather than shown.
Programmatic use: i18n.translate(text, { role }), i18n.translateBatch(items),
i18n.setLocale(), i18n.plural(), number(), date(), list(); events progress,
statechange, localechange, error, translationfallback. See docs/API.md.
Command line
npx @bandf/i18n [demo] [--port=0] [--no-open]
npx @bandf/i18n models [--out=models] [en-fr en-de ...]
npx @bandf/i18n catalogs --languages=fr,de [--sources=bandf-i18n.sources.json] [--out=catalogs] [--models=models] [--source=en]
npx @bandf/i18n prerender --languages=fr,de [--root=dist] [--out=dist] [--base=/] [--models=models] [--source=en] [--catalogs=catalogs] [pages...]
npx @bandf/i18n listdemo(the default) serves the demo page and opens it in the default browser, or in the command named byBROWSER;--no-openorBROWSER=noneskips the opening.modelsdownloads the pinned models (every direction without arguments) and verifies them; a second run downloads nothing.catalogstranslates a recorded sources file (docs/VITE.md) into<out>/<language>.jsonplusreview.jsonfor rejected strings.prerenderrenders the HTML pages under--rootin each language into<out>/<language>/(needsjsdom).listprints the pinned directions with architecture, tier and size.
Under Node, createNodeEngine() from @bandf/i18n/node runs the engine on worker threads with
models read from disk, and createI18n({ engine }) on top of it is the complete pipeline
(docs/API.md, "Recording and Node").
Layout
src/— the library:engine/,dom/,text/,labels/,elements/,cache.js; the module map is indocs/ARCHITECTURE.mdvite/— the Vite pluginbin/— thebandf-i18ncommandtools/—models.js(pinned downloads),build-catalogs.js,prerender.js,serve.js(the package server),corpus-snapshot.js; in the repository alsolocal-models.js(yarn models),mirror-models.js(yarn models:refresh) andbuild-engine.js(yarn engine:build)engine/— the built engine,VERSION.jsonand license (engine/README.md)models/—manifest.json(every direction Mozilla's registry has released: registry path, size and SHA-256 of each file, quality metrics, tier),directions.json(whatyarn modelsmirrors and the architecture preferenceyarn models:refreshpins with), and the mirrored binaries, git-ignored; a pivot through English needs both halves (de-enanden-frfor German to French)examples/— the demo (index.html, served as/) andvite-app/tests/,scripts/— the suites, their pages and their runners; the pages ship in the package so the demo can link to them, the runners do not
Working in the repository
git clone https://gitlab.com/bandf-os/i18n.git && cd i18n
yarn install # Yarn 4, pinned in .yarn/releases; node_modules linker
yarn models # the directions the demo and suites use (~400 MB); models:refresh re-pins every released direction
yarn dev # serves on a free port, prints the URL and opens it; --no-open skips, PORT chooses the portThe development server serves the whole repository; the demo links to every page:
| page | what it shows |
|---|---|
| / | the demo (examples/index.html): an unannotated page, the language control, live rewrites, per-direction download progress |
| /tests/browser.html | the DOM, protocol and cache suites against a fake engine; results render on the page |
| /tests/lit.html | real Lit components translated, re-rendered by Lit, reordered, swapped and restored |
| /tests/engine/index.html | the real engine, run on open: load times cold and warm, throughput, token survival, markup retention per language, pivoting, memory; the corpus is tests/engine/corpus.js |
| /tests/engine/auto.html | the same run, self-reporting; used for Firefox and Safari |
| /tests/page/live.html | a page that rewrites itself continuously while being translated |
| /tests/page/hostile.html | code, placeholders, URLs, RTL, nested inline markup, translate="no" |
| /tests/corpus/cache/*.html | snapshots of public pages, taken on first request (or all at once by yarn test:corpus); ?lang=fr runs the check on one, restore included; ?lang=fr&hold leaves it translated |
| examples/vite-app/ | a Vite project with recorded sources and build-time catalogs (npx vite inside it) |
The suites drive those pages in headless Chrome:
yarn lint # eslint.config.js; lint:fix applies what it can
yarn test # Node: text pipeline, labels, core contracts, worker protocol, the DOM layer under jsdom
yarn test:browser # /tests/browser.html, twice in one profile (fresh and returning visitor)
yarn test:lit # /tests/lit.html
yarn test:engine # /tests/engine/index.html
yarn test:page # /tests/page/: live churn, hostile inputs, RTL, restore, residency
yarn test:corpus # the public-page snapshots: fidelity, coverage, throughput
yarn test:vite # examples/vite-app: dev server recording, build, catalogs, bundled engine, pre-rendering
yarn test:package # packs the tarball, installs it into a throwaway project, runs Vite dev and build against it
yarn test:browsers # macOS: auto.html in Firefox and Safari
yarn test:all # everything aboveMeasurements (docs/QA.md, "Measurements"): yarn bench:labels, bench:page, bench:parallel,
measure:languages.
Why not the browser's translation
Two different things hide behind "the browser can translate pages".
The browser's translate button (Chrome's bar, Firefox Translations, Safari). The visitor does it to your site; you do not do it for them. It cannot be tied to a language control in your UI, cannot ship reviewed strings or terminology, does not tell the page what it did, and behaves differently in each browser — Chrome's sends text to Google's servers. Most visitors never find it. With this library the site translates itself, the same way in every browser, as a feature you own.
Chrome's Translator API (the one a page can call). It translates strings, only in Chrome — no Firefox, no Safari — and only on devices Chrome deems eligible. The engine is also the smallest part of the job. Deciding what on a page is translatable, keeping markup and node identity intact, watching for changes without loops, protecting placeholders and code, handling short labels with roles and a glossary, restoring the source, persisting results, and building reviewable catalogs at build time with the same model the visitor runs — that is the layer above any engine, and it is what this library is. It carries its own engine so that layer behaves identically everywhere, with model versions you pin, so a catalog built on your machine matches what the visitor sees.
Licenses
The library is MIT. The engine and the models are Mozilla Public License 2.0; see
THIRD_PARTY_NOTICES.md and engine/LICENSE.
