Listen for language changes. Returns a remover.
The active tag (as requested — may be more specific than the tag that resolves).
Every tag any contributor supplies (always includes 'en').
The loaded HOST pack for a tag (default: the active resolution), if any.
Optionaltag: stringEvery loaded pack for a tag — host + package contributions — in ASCENDING precedence order
(packages in registration order so the last registration wins under a "later wins" reducer,
host after all of them so it overrides everything, matching the message chain). Empty when none
are loaded. The domain that owns extra plane fields (the SDK) merges these; utils treats each
pack as opaque. Base-tag packs come BEFORE the exact-tag ones, so the more specific locale
(de-AT) overrides its base (de) under the same reducer.
Optionaltag: stringA monotonic counter bumped on every change notification — a language switch AND every late
pack/message load that joins the active chain. Unlike getActive (whose value is unchanged
when a contribution lands for the language already in use), this always advances, so a
useSyncExternalStore snapshot built on it re-renders on those async loads instead of swallowing
them (the initial-load race where a string resolves to its key and then never refreshes).
true if the active language (or its base) has a message for key.
Resolve key in the active language, or undefined on a miss (the caller then
falls back to its inline English literal).
Optionalparams: Record<string, unknown>Listen for language changes. Returns a remover.
Eagerly load packs without activating (zero pop-in on later switch).
Optionaltags: string | string[]Register the host app's whole pack (or a lazy loader) for a tag. Re-registering replaces.
The host is simply the highest-precedence contributor — the same mechanism as registerMessages, reserved to one id — so an app can override any package's string without editing the package. It differs only in carrying the whole LanguagePack, which getPack hands back.
Generic over the pack type so a package that EXTENDS LanguagePack with its own plane (the SDK's function names / error values) can pass an object literal without excess-property errors — and without this package ever knowing those fields exist.
Register a package's bundled default-language messages — the eager, lowest-precedence baseline
that makes a plain npm install render correctly with NOTHING served (I18N zero-config goal).
Unlike registerMessages, this contributes under a DISTINCT low-precedence id, so it
coexists with — and always loses per-key to — a same-package registerMessages contribution for
the same tag (a host/user override fetched from a resource root, or a plugin's slice). The bundled
catalog fills only the keys the override omits: the additive-override contract, now with an
always-present floor instead of raw keys.
Only the default language (en, the source language) is ever bundled; other languages still load
through the fetch path. Because the lookup chain only ever includes the active tag and its base
tag, a bundled en never leaks in as a fallback under a de session — it simply is not in that
chain — so this preserves "English is a language, not a fallback" for translated sessions.
Register one package's messages for a tag (I18N-DESIGN.md decision 6). Packages own their own
call-site (t()) strings — shipped as locales/<tag>/messages.json and fetched through the
shared LocaleResource boundary — so several packages, and independently-published plugins, can
contribute to the SAME language without clobbering each other. Lookup walks contributors in
precedence order: the host first, then packages with the LAST registration winning a key
collision — a downstream package (registered later, since it imports its dependencies) can
re-word a dependency's key, and a plugin can override anything but the host. Re-registering a
(pkg, tag) replaces just that contribution.
Register one package's whole pack (or a lazy loader) for a tag — the merge-friendly sibling of
register. Unlike the host (one contributor, highest precedence), any number of packages may
contribute a pack for the same tag; getPacks returns them all in precedence order for a
consumer to merge. This is how the spreadsheet-language plane (function names / help / errors /
booleans) is sourced from the packages that OWN the functions (@sheetxl/formulas,
@sheetxl/formulas-extended) without those packages depending on the SDK — utils stores the pack
opaquely; the SDK reads and merges its own plane fields.
A pack's messages still join the message lookup chain (so a pack slice may also carry t()
strings); re-registering a (pkg, tag) replaces just that contribution.
Activate a language: loads every contributor's messages for it (if lazy) then fires change listeners. Lenient like Excel — an unregistered tag still activates, resolving through its base tag if that is registered, and otherwise rendering keys.
Resolve key in the active language. On a miss returns the key itself —
use lookup when the caller has a better (English) fallback.
Optionalparams: Record<string, unknown>
The translation boundary. Everything user-facing resolves through this interface; nothing in the SDK/UI depends on an i18n framework — only on this surface.
Fallback contract: a missing key falls back to the authored English literal at the call site (via ITranslator.lookup returning
undefined), never to a bare key.