Localization (l10n)

l10n.mjs · locales.json · Components

Small library for reading translated strings and subscribing components to language changes. On module load it fetches /assets/locales.json and negotiates the initial locale: the persisted choice (localStorage['lang']) → the first supported navigator.languages entry → 'en'. If the catalog can't be loaded it falls back to an empty one and t() returns raw keys.

The catalog fetch is not awaited at module scope, because a top-level await would delay every importing module and its customElements.define() call. Import t normally; it returns the key until the catalog is available. Components use l10n_register_listener to render again when the catalog loads or the locale changes. A page that needs translations before showing its content can await L10N.readyP.

ExportSignaturePurpose
L10N {ready, readyP, t, l10n_plural, …} Access the module through one object: await L10N.readyP reads like the ready / readyP pair every component exposes. ready is the boolean, readyP the promise — never await the boolean, because await false does not wait for readiness. The utility functions below are on it as well as exported flat: rendering code can use import {t}, startup code can use import {L10N}.
set_locale (tag, {persist?}) => void Validates via Intl.Locale, falls back to 'en', sets <html lang>, notifies all listeners. Pass {persist: true} (a user choice) to also write localStorage['lang'].
t (key, params?) => string Looks up the current language, falls back to English, then to the key itself. {name} placeholders are replaced from params.
l10n_plural (key, count, params?) => string Count-aware lookup. The catalog value is a {one, other} object; the form is chosen with Intl.PluralRules and {count} is available for interpolation alongside params.
l10n_ready Promise<void> The older flat spelling of L10N.readyP, and the same promise object. Settles when the catalog has been installed, or has failed and left the empty one in place. Never rejects. Await it from a page's boot script; never at the top level of a module others import — that reintroduces the suspension the note above describes. Every awaiting site still reads this name; write new code against L10N.readyP.
apply_static_i18n (root?) => Promise<void> Translates declarative static markup: data-i18n="key" sets textContent; data-i18n-attr="attr:key;…" sets attributes. Idempotent — safe to re-run on locale change. Awaits L10N.readyP first, so tagged markup keeps its authored text until there is a translation to replace it with.
get_locale () => Intl.Locale The currently selected locale object.
supported_locales () => string[] Base language tags the catalog carries (e.g. ['en', 'sv']).
l10n_register_listener (el) => void Calls el._on_lang?.() once now, then on every locale change and when the catalog arrives — so a component connected before catalog readiness replaces raw keys with translations. Use in connectedCallback.
l10n_unregister_listener (el) => void Stops reactivity. Pair with the above in disconnectedCallback.

1. Language picker

A <select> populated from the top-level keys of /assets/locales.json. Changing it calls set_locale(value) — every registered listener on the page fires synchronously, so the demos below react immediately.

Show code

2. Reactive custom element — _on_lang()

A component registers itself with l10n_register_listener in connectedCallback, implements _on_lang(), and unregisters in disconnectedCallback. Below are two instances with different name attributes — switching the picker above updates each instance independently. This also shows {name} parameter substitution via t().

Show code

3. Built-in components react automatically

Components that call l10n_register_listener internally need no extra wiring. Hover the copy button on the ui-code below to see its title toggle between Copy and Kopiera. Open the dialog to see the Cancel / OK footer labels follow the picker — the dialog is constructed via response_dialog, which attaches its own _on_lang hook for the default buttons.

Show code // Hover the copy button to see the localized tooltip. const answer = 42

Show code