# Capability Spec: react-i18n **Package**: `@suaveplan/react-i18n` **Status**: SHIPPED 2026-05-05 (`enterprise-react-i18n` archived) > Every requirement carries a `**Verified by:**` line. (See `openspec/AGENTS.md` Section 13.) ## Downstream consumers This spec is the foundation of the React stack. Every dependent capability spec MUST reference this one rather than duplicating its requirements: | Dependent capability | Owning change | Consumed surface | |---|---|---| | `react-ui` | `enterprise-react-ui-v2` (in-flight) | `TranslationProvider` (composed by `ReactUiProvider`); `TranslationKey` brand on every component prop carrying user-facing text; `ContributedTranslationKeys` augmentation for the `react-ui` namespace; `useTranslation`, `useDirection`, `useLocale`, `useErrorMessage` hooks; `tDynamic` for theme-label fallback resolution; `useDirection` re-exported as the chassis hook; `i18n.catalog.mergeMessages` is the runtime call site for bundled-translation auto-registration | | `react-forms` | `enterprise-react-forms-v2` (in-flight) | `TranslationKey` brand on every `FieldConfig.label` / `placeholder` / `helperText` / `errorMessage`; `useTranslation` for label resolution at render time; `useErrorMessage` for `SuaveplanError`-coded validation failures; `ContributedTranslationKeys` augmentation for default recipe button labels | | `react-ui-themes` (per-theme) | `enterprise-react-ui-theme-dracula` (in-flight, more themes follow) | `TranslationKey` brands the theme's `labelKey`; per-theme translations contributed via module augmentation | | `react-ui-packs` (per-pack) | `enterprise-react-ui-pack-auth` (in-flight, more packs follow) | `TranslationKey` brands every pack-rendered string; per-pack translations contributed via module augmentation; `tDynamic` for runtime-computed error-code-to-message lookups | | `registry-deployment` | `react-ui-registry-deployment` (in-flight) | The registry transform preserves `@suaveplan/react-i18n` imports in source-ejected components — the i18n contract survives ejection | The **architectural invariant** documented in the "Branded TranslationKey typing contract" requirement below is the load-bearing piece: every dependent package augments `ContributedTranslationKeys` (NEVER imports a downstream package back into react-i18n), and the `scripts/lint/check-no-backwards-deps.ts` CI gate enforces this with per-tier banned-pattern subsets. Downstream specs MUST cite this requirement rather than re-stating it. ## Requirements ### Requirement: TranslationProvider mounts an I18nInstance The package SHALL expose `TranslationProvider` that mounts and exposes an `I18nInstance` from `@suaveplan/i18n` to descendants via React context. The Provider SHALL accept either `i18n: I18nInstance` (caller-managed; Provider does NOT dispose) or `config: I18nConfig` (Provider-managed; calls `createI18n(config)` on mount and `i18n.dispose()` on unmount). It SHALL connect the instance event bus to React state via `useSyncExternalStore` so any subscribed hook re-renders on `languageChanged` / `loaded`. #### Scenario: caller-managed instance is not disposed on unmount - **GIVEN** an externally created `I18nInstance` passed via `i18n` prop - **WHEN** the Provider unmounts - **THEN** the instance's `dispose()` was NOT called #### Scenario: provider-managed instance is disposed on unmount - **GIVEN** a Provider mounted with `config={...}` - **WHEN** the Provider unmounts - **THEN** the internally-created instance's `dispose()` was called exactly once #### Scenario: SSR initial messages hydrate without round-trip - **GIVEN** server-rendered HTML with embedded `