Search documentation

Search all pages, headings and text, in the current package or topic or in all documentation.

Changelog

What changed for users of active-i18n, per version.

@workspace/active-i18n 1.1.0Developmentunreleased, in development in Foundationbaseline ce081dc3

Release state

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.ts types its stub with typeof Intl.DisplayNames instead 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 types CurrencyOption and CurrencyOptionsConfig, 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 that apps/admin carried 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 with lookupFirstSupportedLocale; an exact comparison with primary tags no longer finds en in en-US.
  • The header reading allows whitespace around ; and =, ignores parameters other than q, and drops an invalid qvalue, q=0, * and a tag Intl refuses; a repeated tag keeps its highest quality; at most 32 entries.

Added (D33)#

  • lookupSupportedLocale and lookupFirstSupportedLocale on 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)#

  • LocaleSwitchOutcome gains 'declined', 'navigation-failed' and 'navigation-guarded'. An exhaustive switch over 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 own beforeunload instead of the return of location.replace(). A test with a location.replace double dispatches beforeunload from it.

Added#

  • confirmLocaleSwitch on <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 persistLocale is assimilated like Promise.resolve.
  • The error boundary (PKG-RECHECK-002): a throwing then getter is a persistence failure; a throwing logger never changes an outcome.
  • A beforeunload objection (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): the react peer is ^19.0.0 (was ^18.0.0 || ^19.0.0, which crashed on React 18 at React.use), and the @types/react peer ^19.0.0 (was ^19.3.0). The root needs no React.
  • setLocale(locale) returns Promise<LocaleSwitchOutcome> (D31), was boolean. The promise never rejects; 'switched' replaces true, and the refusals 'disabled', 'unchanged', 'unavailable' replace false. New: 'pending' (a switch is already under way) and 'persist-failed'.
  • persistLocale may return a promise (D31): the provider awaits it and navigates only on success; a throw or rejection does not navigate and is reported as Locale persistence failed.
  • createServerTranslations refuses a non-canonical, malformed or repeated supportedLocales entry (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-PT or zh-Hant loads 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)#

BeforeAfter
if (setLocale(code)) …if ((await setLocale(code)) === 'switched') …
onClick={() => setLocale(code)}unchanged, or void setLocale(code) to mark the ignored outcome
persistLocale={async (l) => { await save(l) }}unchanged; it is now awaited, and a rejection no longer navigates
supportedLocales: ['pt-br']supportedLocales: ['pt-BR'] (the error names the canonical form)
React 18React 19 (^19.0.0); with active-ui its own floor ^19.2.4

Changed — BREAKING (standalone product, D28, platform ADR-114)#

  • createServerTranslations(source, options): options is required and carries supportedLocales and fallbackLocale (previously read from @workspace/active-config); an empty set or a fallback outside it throws. New optional logger (a factory (source) => {warn, error}).
  • <I18nProvider>: cookieName and cookieDomain are removed; the new persistLocale?: (locale) => void receives a language switch, and the new logger? factory receives the provider's reports. Without them nothing is stored or reported.
  • getLanguageName / getStandaloneLanguageName: an optional third argument knownLabels replaces 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-logging or @workspace/active-types; date-fns and @date-fns/tz declared because the closure reaches them through active-format; next no longer a devDependency (an optional peer only), @types/react an optional peer; tsconfig self-contained.

Added#

  • The types KnownLanguageLabels, I18nLogger and I18nLoggerFactory.
  • Registration as a reusable product (., ./react; ./server and ./server/get-locale as Next framework integrations).

Removed — BREAKING (formatting moved to @workspace/active-format)#

  • The ./formatters entry. 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.
  • LocaleCode from the root. The open BCP 47 tag is owned by @workspace/active-format; this package imports it from there.
  • The compatibility selection formatters of the reusable package transfer ends with this change: no reusable product reaches this package any more, and its register row is removed. The recorded release 1.1.0-compat.formatters stays in the release register as history.

Changed#

  • The package depends on @workspace/active-format; date-fns and @date-fns/tz are 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#

BeforeAfter
import {…} from '@workspace/active-i18n/formatters'import {…} from '@workspace/active-format'
import type {LocaleCode} from '@workspace/active-i18n'import type {LocaleCode} from '@workspace/active-format'

[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.formatters and 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 formatters selection 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, the LICENSE, and a generated manifest. That manifest keeps this package's import name and license, names exactly the ./formatters export with the third-party dependencies its closure imports, and says it is a selection (an rpsSelection field 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.

This page as Markdown