Changelog
What changed for users of active-i18n, per version.
@workspace/active-i18n 1.1.0Developmentunreleased, in development in Foundationbaseline ce081dc3
The version line at the top of this page is read from the package and the release register of Foundation. A version heading in the changelog below is not a release by itself: only a recorded release is. The section Unreleased lists what changed since the last version, including the changes made since Foundation maintains the package.
Notable changes to @workspace/active-i18n. Format based on
Keep a Changelog; versioning per
Semantic Versioning.
This changelog starts on 2026-10-18. The manifest already carried version
1.1.0, but no release of this package was ever recorded: there is no earlier
changelog and no release heading, so it is not known which content 1.1.0 once
named. The history before this file is not reconstructed here; the completed
milestones are in DONE.md and the durable decisions in
DECISIONS.md, dated, but not tied to a version. The first
release heading below, [1.1.0], is the first release of this package.
[Unreleased]#
Maintained in Foundation (since 2026-10-21)#
- A lint warning in a test:
language-names.test.tstypes its stub withtypeof Intl.DisplayNamesinstead of an unused variable. No code changed. - Documentation: references to documents of the source repository are
text, marked
(source repository: …), instead of links that resolve nowhere here.
Added (2026-10-19, active-ui ADR-039)#
getCurrencyOptions(codes, displayLocale, {priorityCodes?, current?})with the typesCurrencyOptionandCurrencyOptionsConfig, from the root: the options of a currency picker (priority first, then by name; read tolerance and a server-resolved name for the stored code). Replaces the option assembly thatapps/admincarried itself.
Changed (Accept-Language candidates, 2026-10-19: D33)#
- Breaking:
getAcceptLanguageCandidates()returns full canonical tags (en-US,zh-Hant-TW) instead of lower-case primary subtags. Match them withlookupFirstSupportedLocale; an exact comparison with primary tags no longer findseninen-US. - The header reading allows whitespace around
;and=, ignores parameters other thanq, and drops an invalid qvalue,q=0,*and a tagIntlrefuses; a repeated tag keeps its highest quality; at most 32 entries.
Added (D33)#
lookupSupportedLocaleandlookupFirstSupportedLocaleon the root: the RFC 4647 lookup of the runtime, public and framework-free.
Removed (D33)#
normalizeLocale(internal helper; it had no user left).
Changed (re-check remediation, 2026-10-19: D32)#
LocaleSwitchOutcomegains'declined','navigation-failed'and'navigation-guarded'. An exhaustiveswitchover the outcome adds them; a caller that only tests for'switched'needs nothing.setLocale()never throws and its promise never rejects, also when the host's guard, writer or logger throws, or when starting the navigation throws. A failure after a successful write is'navigation-failed'.'switched'now means the browser is leaving the page: the outcome follows the page's ownbeforeunloadinstead of the return oflocation.replace(). A test with alocation.replacedouble dispatchesbeforeunloadfrom it.
Added#
confirmLocaleSwitchon<I18nProvider>: the host's guard, asked before anything is stored;false, a throw or a rejection resolves'declined'.
Fixed#
- A callable thenable is awaited (PKG-RECHECK-001): any object or
function returned by
persistLocaleis assimilated likePromise.resolve. - The error boundary (PKG-RECHECK-002): a throwing
thengetter is a persistence failure; a throwing logger never changes an outcome. - A
beforeunloadobjection (PKG-RECHECK-003) no longer leaves<html lang>on the new language and the selector locked: the page stays consistent and accepts a new choice ('navigation-guarded').
Changed — BREAKING (independent review remediation, 2026-10-19: D29, D30, D31)#
- React 19 is required by
/react(D30): thereactpeer is^19.0.0(was^18.0.0 || ^19.0.0, which crashed on React 18 atReact.use), and the@types/reactpeer^19.0.0(was^19.3.0). The root needs no React. setLocale(locale)returnsPromise<LocaleSwitchOutcome>(D31), wasboolean. The promise never rejects;'switched'replacestrue, and the refusals'disabled','unchanged','unavailable'replacefalse. New:'pending'(a switch is already under way) and'persist-failed'.persistLocalemay return a promise (D31): the provider awaits it and navigates only on success; a throw or rejection does not navigate and is reported asLocale persistence failed.createServerTranslationsrefuses a non-canonical, malformed or repeatedsupportedLocalesentry (D29), and a fallback not spelled as an entry.
Fixed#
- Regional and script locales on the server (D29, PKG-ARCH-001): a
supported
en-GB,pt-BR,pt-PTorzh-Hantloads itself instead of the fallback. The request is canonicalised, the exact tag wins, and only then is it made less specific; it never reaches a sibling region or script. - Navigation before asynchronous persistence (D31, PKG-ARCH-003): the
order is now
persist-start → persist-complete → navigate.
Added#
- The type
LocaleSwitchOutcome(@workspace/active-i18n/react).
Migration (D29 to D31)#
| Before | After |
|---|---|
if (set | if ((await set |
on | unchanged, or void set to mark the ignored outcome |
persist | unchanged; it is now awaited, and a rejection no longer navigates |
supported | supported (the error names the canonical form) |
| React 18 | React 19 (^19.0.0); with active-ui its own floor ^19.2.4 |
Changed — BREAKING (standalone product, D28, platform ADR-114)#
createServerTranslations(source, options):optionsis required and carriessupportedLocalesandfallbackLocale(previously read from@workspace/active-config); an empty set or a fallback outside it throws. New optionallogger(a factory(source) => {warn, error}).<I18nProvider>:cookieNameandcookieDomainare removed; the newpersistLocale?: (locale) => voidreceives a language switch, and the newlogger?factory receives the provider's reports. Without them nothing is stored or reported.getLanguageName/getStandaloneLanguageName: an optional third argumentknownLabelsreplaces the built-in fallback onto the platform language set; without it an unnamed language shows its code in upper case.- Manifest: no
@workspace/active-config,@workspace/active-loggingor@workspace/active-types;date-fnsand@date-fns/tzdeclared because the closure reaches them through active-format;nextno longer a devDependency (an optional peer only),@types/reactan optional peer; tsconfig self-contained.
Added#
- The types
KnownLanguageLabels,I18nLoggerandI18nLoggerFactory. - Registration as a reusable product (
.,./react;./serverand./server/get-localeas Next framework integrations).
Removed — BREAKING (formatting moved to @workspace/active-format)#
- The
./formattersentry. Every export of it now lives, under the same name and with the same behaviour, in@workspace/active-format(its one root entry). There is no second path and no re-export. LocaleCodefrom the root. The open BCP 47 tag is owned by@workspace/active-format; this package imports it from there.- The compatibility selection
formattersof the reusable package transfer ends with this change: no reusable product reaches this package any more, and its register row is removed. The recorded release1.1.0-compat.formattersstays in the release register as history.
Changed#
- The package depends on
@workspace/active-format;date-fnsand@date-fns/tzare no longer its own dependencies. /react(useFormatters,usePresentation,<RecencyTime>,<ViewerDateTime>, the presentation-profile and time-zone providers) is unchanged in behaviour and imports the formatters from@workspace/active-format.
Migration#
| Before | After |
|---|---|
import {…} from '@workspace/ | import {…} from '@workspace/ |
import type {Locale | import type {Locale |
[1.1.0] — 2026-10-18#
The first recorded release of this package. 1.1.0 is the source state recorded on this date; the content before it is not reconstructed. What this release does and does not guarantee:
- An older copy that says 1.1.0 is not automatically byte-identical to this release. The version string existed before any release was recorded, and earlier development transfers carried it with other content.
- The content guarantee covers the recorded selection
1.1.0-compat.formattersand every later recorded release: the release register binds that name, selection and version to exactly one content hash. - The record does not prove that the full package is portable on its own.
Only the
formattersselection is transferred; the root, server and React entries are not part of it.
Added — compatibility selection formatters (2026-10-18, platform ADR-112, RPS-D10)#
- The reusable package transfer carries this package as the compatibility
selection
formatters, not as the full package: the full package also has a root, a server and a React entry that depend on packages outside the transfer. The selection is a fixed definition in the reusable package register: the closure of the portable entry./formatters, theLICENSE, and a generated manifest. That manifest keeps this package's import name andlicense, names exactly the./formattersexport with the third-party dependencies its closure imports, and says it is a selection (anrpsSelectionfield with its owner, owner version and end condition). - Its own identity. The selection's version is
<this package's version>-compat.formatters, with its own content hash and, once released, its own row in the release register. An equal import name or owner version does not make it the full package. - End condition (from the register): the selection ends when this package can be transferred as its own canonical folder, which is the separate decoupling workstream.
- No source change: the formatter code and its public API are unchanged by this entry.