# React i18n — plugin registry, formatter primitives, and first-class react-ui i18n surface **Packages**: `@suaveplan/react-i18n`, `@suaveplan/react-ui` **Status**: PROPOSED **Type**: feature + extensibility refactor ## Why `@suaveplan/react-i18n` shipped 2026-05-05 as a complete React binding for `@suaveplan/i18n` — ``, 12 hooks, four components (``, ``, ``, ``), a 9-detector chain, and the branded `TranslationKey` contract that downstream `react-ui` / `react-forms` augment. It works, and downstream sweeps stay green. It is **monolithic in two specific ways** that block the next wave of consumer features: 1. **Detector chain is hard-wired.** `defaultDetectorChain()` builds the same fixed 9-element array every call (`detector/default-chain/default-chain.ts`). `` lets a *consumer* swap the entire array, but there is no surface for a third-party plugin to **insert** a single detector without re-authoring the chain. That means a future `react-i18n-detector-cookie-signed` or a consumer-owned A/B-bucket detector cannot ship as a drop-in package. 2. **Formatter access is ad-hoc.** Each hook (`useNumber`, `useDate`, `useCurrency`, `useList`, `useRelativeTime`, etc.) imports its own per-formatter package directly. There is no central formatter registry, no way to register a project-specific formatter (e.g. a domain-specific `useDuration`, `useTemperature`, or `useFiscalQuarter`) without forking the package, and no overlay mechanism for swapping the default `Intl.NumberFormat`-backed implementation with a custom one (BigDecimal currency, locale-tagged unit formatter, etc.). Separately, `@suaveplan/react-ui` already consumes react-i18n through `t()` calls in `Pagination`, `Calendar`, `DataTable`, `ErrorBoundary`, and others — and ships pre-built bundles for `en-GB`, `zh-CN`, `ar-AE`, `cy-GB`, `ru-RU` (`react-ui/src/locales/`). What it does **not** ship is **first-class formatted-value primitives**. Consumers who want a number, date, currency, plural, or relative-time inside a button label, a `` body, a `` cell, or a `` description currently call the bare hook (`useNumber`, `useDate`, …) and inline-format the value, with no shared component, no consistent fallback story, no built-in `` boundary, and no built-in dev-mode missing-key surface around formatted values. Components with locale-sensitive *behavior* (RTL slide direction on `` / ``, locale-bound numeric formatters on `` columns, locale-aware decimal/grouping on ``) are likewise un-wired today. This change closes both gaps in one atomic landing — the registry abstraction (react-i18n) AND the consumer-facing primitives (react-ui) ship together so downstream packages can adopt the new surface without two waves of churn. ## What changes ### `@suaveplan/react-i18n` #### Plugin registry — single substrate for detectors, formatters, and post-processors - ADDED: `src/registry/registry.ts` — `ReactI18nRegistry` interface with three surfaces: - `registerDetector(plugin: DetectorPlugin): Disposable` - `registerFormatter(plugin: FormatterPlugin): Disposable` - `registerPostProcessor(plugin: PostProcessorPlugin): Disposable` - Each registration is reversible (`Disposable`), idempotent on duplicate `plugin.id`, and validated against a Zod schema at registration time. Duplicate ids throw `REACT_I18N.PLUGIN_DUPLICATE_ID`. - ADDED: `src/registry/registry-context.tsx` — `` wraps `` (or composes with it) and injects a `ReactI18nRegistry` into context. - ADDED: `src/registry/use-registry.ts` — `useReactI18nRegistry()` returns the active registry from context, throws `REACT_I18N.REGISTRY_NOT_PROVIDED` outside a provider. - ADDED: subpath export `./registry` and per-plugin-type exports (`./registry/detectors`, `./registry/formatters`, `./registry/post-processors`). #### Detector plugins — chain becomes registry-driven - MODIFIED: `defaultDetectorChain()` consults the registry for any registered `DetectorPlugin` whose `priority` falls between two adjacent built-in detectors. Built-in detectors keep their current order; registered plugins splice in based on `priority` (numeric, lower = earlier) with deterministic tie-break by `id`. - ADDED: `DetectorPlugin` interface — `{ id: string; priority: number; environment: 'browser' | 'server' | 'both'; detect: LocaleDetector }` (re-uses `LocaleDetector` shape from `@suaveplan/i18n-detector`). - ADDED: error code `REACT_I18N.DETECTOR_PLUGIN_DUPLICATE_ID`, `REACT_I18N.DETECTOR_PLUGIN_INVALID`. #### Formatter plugins — pluggable `useFormatter(id, args)` surface - ADDED: `src/registry/formatters/formatter-plugin.types.ts` — `FormatterPlugin` interface: `{ id: string; format: (locale: string, args: TArgs) => TResult; schema?: ZodSchema; defaultOptions?: TArgs }`. - ADDED: `src/hooks/use-formatter/use-formatter.ts` — `useFormatter(id: string, args: TArgs): TResult`. Resolves the plugin from registry by id, calls `plugin.format(locale, args)`, memoizes by `(locale, id, args)`. Throws `REACT_I18N.FORMATTER_NOT_REGISTERED` for unknown ids. - MODIFIED: existing per-type formatter hooks (`useNumber`, `useDate`, `useCurrency`, `useList`, `useRelativeTime`, `usePlural`) keep their public signatures but internally delegate to the registry-backed `useFormatter` with built-in plugin ids (`'number'`, `'date'`, `'currency'`, `'list'`, `'relative-time'`, `'plural'`). Callers see no API change; the implementation is now overridable by registering a plugin with the same id (last-write-wins on `priority`). - ADDED: built-in formatter plugins for the six existing hooks live in `src/registry/formatters/built-in/` and are auto-registered by `` unless `disableBuiltInFormatters` is set. #### Post-processor plugins — message pipeline overlay - ADDED: `PostProcessorPlugin` interface — `{ id: string; priority: number; process(message: string, ctx: PostProcessorContext): string }`. Wraps the message AFTER ICU interpolation but BEFORE rendering. Use cases: pseudo-localization in dev, redaction overlays, custom debug markers. - The pipeline runs registered post-processors in `priority` order. Order is deterministic for identical priorities (lexicographic by `id`). #### Schema + observability + errors - ADDED: Zod schemas at every public registration boundary (`DetectorPluginSchema`, `FormatterPluginSchema`, `PostProcessorPluginSchema`). Invalid plugins throw `REACT_I18N.PLUGIN_INVALID` with the failed Zod issues attached. - ADDED: `react_i18n.registry.plugins_total{kind,outcome}` counter, `react_i18n.formatter.duration_ms{id}` histogram, `react_i18n.formatter.errors_total{id,code}` counter. - ADDED: error codes `REACT_I18N.PLUGIN_DUPLICATE_ID`, `REACT_I18N.PLUGIN_INVALID`, `REACT_I18N.REGISTRY_NOT_PROVIDED`, `REACT_I18N.FORMATTER_NOT_REGISTERED`, `REACT_I18N.DETECTOR_PLUGIN_DUPLICATE_ID`, `REACT_I18N.DETECTOR_PLUGIN_INVALID`. #### Locale-adapter plugin slot — for third-party date / number libraries - ADDED: `LocaleAdapterPlugin` — a fourth plugin kind. Registers a `(localeString: string) => TLocale` resolver under a stable `id` (e.g. `"date-fns"`, `"luxon"`, `"temporal"`). Used by primitives that need a non-`Intl` locale object (the canonical case is `react-day-picker`'s `locale` prop, which wants a date-fns `Locale`). Consumers swap the date library by registering a different adapter under the same id, no fork required. Built-in adapter is `date-fns` (loaded only when used — dynamic import inside the adapter to keep the universal-tier surface clean). - ADDED: `useLocaleAdapter(id: string): TLocale` hook. Throws `REACT_I18N.LOCALE_ADAPTER_NOT_REGISTERED` when no adapter matches. #### Backwards compatibility - All existing public exports keep their signatures. `useNumber(value, options)` / `useDate(...)` / `` / `` / detectors all continue to work without a registry — when no `` is mounted, hooks fall back to the current direct-import code path so no consumer change is required to upgrade. - `` is unchanged. `` is a NEW, additive layer that callers opt into when they want plugin support. Recommended composition: `...`. ### `@suaveplan/react-ui` — first-class i18n surface (new capability `react-ui-i18n`) #### Third-party library bridges — single source of truth for locale + direction The biggest flexibility risk in a shadcn-derived component library is that locale + direction state lives in *N* third-party providers (Radix, vaul, sonner, react-day-picker, tanstack/react-table) and can drift independently. This change centralizes propagation so every third-party context picks up the active i18n state automatically: - ADDED: `` — a single mounting point that lives inside (or composes with) ``. It reads `useLocale()` + `useDirection()` and: - Mounts `@radix-ui/react-direction`'s `` so EVERY Radix primitive (popover, dropdown, dialog, tooltip, select, menubar, context-menu, hover-card, scroll-area, tabs, toggle-group, …) gets RTL state without per-component wiring. - Sets `dir` on `` from `sonner` so toasts pick up direction. - Exposes the active locale to a `LocaleContext` consumed by ``'s default sort/filter functions (Intl.Collator-backed) and by the `` `locale` prop. - Updates the document `` and `` attributes when the active locale changes (opt-out via prop for SSR / nested-app scenarios). - ADDED: locale-aware default `sortingFn` and `filterFn` for `@tanstack/react-table` exposed as `localeAwareSortingFn(locale)` / `localeAwareFilterFn(locale)` from `react-ui/i18n/tanstack-bridge`. Consumers opt in per column or globally; `ColumnDef.formatter:` shortcut auto-installs them for the column it controls. This bridge is also what makes future third-party additions plug-and-play: a new TanStack lib (`@tanstack/react-virtual`, `@tanstack/router`) just consumes the same `LocaleContext` / `DirectionContext` exposed from the bridge, no second integration point. #### New formatted-value primitives — a `` component per formatter hook - ADDED: `src/i18n/formatted-number/formatted-number.tsx` — `` renders via `useNumber`. `as` defaults to ``, accepts any HTML element or React component. `options` matches the underlying `useNumber` options shape exactly. - ADDED: `src/i18n/formatted-currency/formatted-currency.tsx` — ``. - ADDED: `src/i18n/formatted-date/formatted-date.tsx` — ``. Accepts `Date | string | number`. - ADDED: `src/i18n/formatted-list/formatted-list.tsx` — ``. - ADDED: `src/i18n/formatted-relative-time/formatted-relative-time.tsx` — `` with an `auto` mode that re-renders on a configurable interval (`refreshIntervalMs`, default `60_000`). - ADDED: `src/i18n/plural/plural.tsx` — `` selects a child by CLDR plural rule for the active locale via `usePlural`. - ADDED: `src/i18n/formatted-message/formatted-message.tsx` — alias of `` re-exported from `react-ui` so consumers do not need a second import to render translated text. Brand-accepts `TranslationKey` so the type-safe key contract carries through. Each primitive ships with a sibling `.md`, a Vitest test (with WSL2-safe `pool: forks`), and a `.stories.tsx` (default + playground + variants) per the genesis story-required rule. #### Locale-aware behavior wiring on existing components - MODIFIED: `` (`src/components/drawer/drawer.tsx`) — when `direction` is `'inline-start'` / `'inline-end'`, the underlying `vaul` direction prop resolves to `'left'` / `'right'` based on `useDirection()`. Existing `direction='left' | 'right' | 'top' | 'bottom'` continues to work unchanged. - MODIFIED: `` (`src/components/sheet/sheet.tsx`) — adds `'inline-start'` / `'inline-end'` to the `side` union; same `useDirection`-based resolution. RTL locales render an `inline-start` Sheet from the right edge, LTR from the left. - MODIFIED: `` (`src/components/data-table/data-table.tsx`) — `ColumnDef` accepts an optional `formatter?: { kind: 'number' | 'currency' | 'date' | 'relative-time'; options?: ... }` shape. When set, the cell renderer wraps the raw value in the matching `` primitive. Cells with explicit `cell:` overrides win; `formatter:` is a shortcut for the common case. - MODIFIED: `` `pagination.tsx` — page-number rendering already uses `t()` via the `react-ui.pagination.*` keys; no surface change. The MOD here is upgrading the `PaginationStatus` "Page X of Y" line to render `` so locales using non-Latin numerals (Arabic-Indic, Devanagari) display native digits when the active locale's `numberingSystem` differs from `latn`. - MODIFIED: `` `calendar.tsx` — passes the `useLocale()` value into `react-day-picker`'s `locale` prop, resolved through `useLocaleAdapter("date-fns")`. The active date-library adapter (date-fns by default; luxon/Temporal/day.js if a consumer registered a different `LocaleAdapterPlugin` with id `"date-fns"`) provides the resolver. Today the chassis only translates the nav-button aria-labels; this MOD makes weekday and month names follow the active locale and is library-swappable without a fork. #### Numeric input — locale-aware decimal/grouping - ADDED: `src/components/input/input-number/input-number.tsx` — a thin `` wrapper that: - Parses incoming text using the active locale's decimal + grouping separators (via `Intl.NumberFormat(locale).formatToParts(12345.6)` introspection — no new external dep). - Emits a canonical `number` (or `null` when blank) up through `onValueChange`. - Renders the displayed value through `` when not focused, and switches to a raw editable buffer on focus to avoid cursor jumps. - The plain `` is **untouched**. Consumers opt into locale-aware numeric handling by switching to ``. This avoids surprising existing form code. #### Bundled translations + module augmentation extension - ADDED: bundles for the new primitive aria-labels and the new `` strings under each existing locale (`en-GB`, `zh-CN`, `ar-AE`, `cy-GB`, `ru-RU`). Schema (`locales/schema.ts`) extended with the new `react-ui.formatted.*` and `react-ui.inputNumber.*` namespaces. Module augmentation registers the new keys. - ADDED: extraction script `scripts/extract-translation-keys.ts` walks `src/components/**/*.tsx` AST for `t('react-ui.*')` calls and `` usages, diffs against `locales/en-GB.json`, and reports both missing keys (key in code, not in bundle) and orphan keys (key in bundle, not in code). Used by a new `bun run check:i18n-keys` script. Wired into the package's pre-publish gate. #### shadcn registry preservation — ejected components keep i18n contract `@suaveplan/react-ui` ships a shadcn-style component registry at `registry/r/*.json` that the shadcn CLI consumes to eject component source into a consumer's repo. The registry transform that runs during build rewrites `@suaveplan/react-ui` imports into local relative paths but explicitly preserves `@suaveplan/react-i18n` imports so the i18n contract survives ejection. This change extends the registry deliverable as follows: - ADDED: registry entries for every new primitive (`formatted-number.json`, `formatted-currency.json`, `formatted-date.json`, `formatted-list.json`, `formatted-relative-time.json`, `plural.json`, `formatted-message.json`, `input-number.json`, `react-ui-i18n-bridge.json`). Each entry declares its `@suaveplan/react-i18n` dependency in `registryDependencies` so the shadcn CLI installs the binding when the component is ejected. - MODIFIED: registry entries for Drawer, Sheet, Calendar, DataTable, Pagination regenerated to capture the new imports (`useDirection`, `useLocale`, `useLocaleAdapter`, ``). - ADDED: `scripts/check-registry-eject.ts` — a smoke test that resolves each registry entry as if ejected (clones the JSON's source files into a tmp dir, applies the transform, runs `tsc` against the result with the consumer's expected `@suaveplan/react-i18n` import shape). Wired into the package's pre-publish gate so registry / source drift fails the build. This is the gate that prevents a "works in-tree but broken when ejected" regression — the failure mode the spec already calls out as the load-bearing invariant. #### Theme integration react-ui ships three in-tree themes (`light`, `dark`, `high-contrast`) and is the foundation for downstream theme packages (`react-ui-theme-*`, in-flight per STATUS.md). - The new primitives are `as`-polymorphic and ship with no opaque DOM — they inherit theme tokens transparently through whatever wrapping element the consumer chooses (``, ``, raw ``). - Theme switcher already has translation keys (`react-ui.theme.*`); this change does not touch them. - **Future theme packages plug into the registry exactly like any other consumer**: a theme package that wants to contribute a per-theme post-processor (e.g. a "playful" theme that wraps every label, or a "compliance" theme that masks PII patterns) registers a `PostProcessorPlugin` through the same `` API. Theme packages contributing translations continue to use module augmentation against `ContributedTranslationKeys` exactly as the existing pattern documents. In short: themes are **not blocked** by this work, and the registry abstraction is the substrate themes will use to ship i18n-aware extensions in their own packages. #### Validation bridge for react-forms (additive) - ADDED: `src/i18n/validation-message-bridge/validation-message-bridge.ts` — exports `useValidationMessageBridge()` returning a `(error: SuaveplanError) => string` function that resolves the error to a localized message via `useErrorMessage` from react-i18n. This is the canonical adapter that `@suaveplan/react-forms` (today already i18n-aware) and downstream form-bound consumers use to render validation errors uniformly. The bridge already exists ad-hoc in react-forms; this change centralizes it inside react-ui so other UI surfaces (toasts, dialogs, alerts) can render validation errors with the same resolution rules. #### Out of scope for this change - **Authoring the full react-ui capability spec.** This change ships the narrow `react-ui-i18n` capability spec only. The broader `enterprise-react-ui-v2` spec is in-flight separately. - **Replacing the existing `pack/` translation merge path.** Packs continue to merge translations through `i18n.catalog.mergeMessages`. The new registry is for *plugins* (detectors, formatters, post-processors) not for translations. - **Non-React i18n primitives.** The plugin registry lives in `react-i18n` because formatter / post-processor / detector hooks are React-bound. Universal-tier plugin wiring belongs in `@suaveplan/i18n` itself and is out of scope. - **Replacing `` with a different ICU runtime.** `` continues to use the existing `@suaveplan/i18n-icu-parser` path. The post-processor pipeline runs *after* ICU interpolation, not in place of it. - **Built-in formatter plugins beyond the six existing hook-types.** Domain-specific formatters (`useDuration`, `useTemperature`, `useFiscalQuarter`, …) are exactly what the registry is for — consumers register them. We deliberately do not ship them in `react-i18n` itself. - **A redux-style global registry.** The registry is React-context-scoped. Two ``s in the same tree get two independent registries. This matches every other context-scoped capability in the React stack. - **`` integer/BigInt mode.** Today's scope is locale-aware decimal numbers. BigInt / integer-only / fixed-point variants are follow-up changes if demand arrives. - **CLI-side translation extraction.** The new `extract-translation-keys.ts` script lives inside `react-ui`. A repo-wide `@suaveplan/i18n-extract` CLI is a separate change; this script is a package-local tool only. ## Parity Checklist `@suaveplan/react-i18n` and `@suaveplan/react-ui` are genesis-original; no upstream `../suaveplan/` parent for the registry surface or the formatted-value primitives. The "upstream" column captures whether the current shipped `react-i18n` / `react-ui` exposes the capability today. | Capability | Current genesis | Post-change | Notes | |---|---|---|---| | Detector chain extension by 3rd-party plugin | 🚫 | ✅ | Today only `` lets a *consumer* swap the entire array. | | Formatter overlay (override the built-in `useNumber` impl) | 🚫 | ✅ | Today the formatter hooks import their per-package implementation directly. | | Project-specific formatter (`useDuration`, `useFiscalQuarter`) | 🚫 | ✅ via `useFormatter(id, args)` | Custom plugin pattern. | | Post-processor pipeline (pseudo-loc, debug overlay, redaction) | 🚫 | ✅ | The `@suaveplan/i18n` core has post-processors at the catalog layer; this exposes them at the React-render layer too. | | `` / `` / `` / `` / `` / `` | 🚫 | ✅ | New primitives live in `react-ui`. | | `` / `` `inline-start` / `inline-end` (RTL-aware slide) | 🚫 | ✅ | New direction values; existing values unchanged. | | `` weekday / month names follow active locale | 🚫 (only nav aria-labels translated) | ✅ | `react-day-picker` `locale` prop now wired. | | `` column-level locale formatter shortcut | 🚫 | ✅ | `formatter:` shortcut prop on `ColumnDef`; `cell:` override still wins. | | `` locale-aware decimal/grouping | 🚫 | ✅ | New component, plain `` untouched. | | `` non-Latin numeral rendering | 🚫 | ✅ | Already-translated keys, but page numbers are raw `String(n)` today. | | Translation-key extraction script | 🚫 | ✅ (package-local) | `bun run check:i18n-keys` in `react-ui`. | | Validation message bridge centralized in react-ui | 🚫 (lives ad-hoc in `react-forms`) | ✅ | Centralized so toasts / dialogs / alerts can reuse. | | OTel metrics for plugin registration + formatter latency / errors | 🚫 | ✅ | Genesis-wide rule. | | Structured error codes for plugin lifecycle | 🚫 | ✅ | Six new `REACT_I18N.*` codes. | | Backwards compatibility with no `` mounted | n/a | ✅ | All existing consumers continue working without opt-in. | | `LocaleAdapterPlugin` slot — swap date-fns for luxon / day.js / Temporal | 🚫 | ✅ | Calendar resolves through the adapter; default is date-fns. | | `` — single bridge to Radix ``, sonner ``, document ``/`` | 🚫 | ✅ | One mount point updates every third-party context. | | Locale-aware default `sortingFn` / `filterFn` for `@tanstack/react-table` | 🚫 | ✅ | `localeAwareSortingFn(locale)` / `localeAwareFilterFn(locale)` exposed; auto-installed by `formatter:` shortcut. | | shadcn registry: new entries for new primitives | n/a | ✅ | Registry shipped alongside source. | | shadcn registry: existing entries regenerated for modified components | n/a | ✅ | Drawer / Sheet / Calendar / DataTable / Pagination registry JSON refreshed. | | Registry ejection smoke test gate (`check-registry-eject.ts`) | 🚫 | ✅ | New pre-publish gate ensures ejected source still compiles against `@suaveplan/react-i18n`. | | Theme-package extension via registry post-processors / locale adapters | n/a (no theme packages exist on genesis yet) | ✅ contract | Documented; themes plug in through the same `` substrate, no new theme-side surface needed. | ## Verification approach - **Unit tests** (vitest, `pool: 'forks'`) per new module: - `registry/registry.test.ts` — registration, duplicate-id detection, disposable lifecycle, schema validation rejection. - `registry/use-registry.test.tsx` — context lookup, missing-provider error. - `hooks/use-formatter/use-formatter.test.tsx` — built-in id resolution, custom plugin override, missing-id error, memoization across renders. - Per-detector-plugin scenarios: priority splice, deterministic tie-break, environment gating, abort signal threading. - Per-formatted-primitive: renders correct text for each formatter id, `as` prop polymorphism, hydration mismatch suppression for `` `auto` mode. - **Spec-conformance tests** — one per requirement in the new spec deltas. - **Property tests** (fast-check) — registry idempotency, plugin priority ordering, post-processor pipeline associativity for same-priority plugins. - **Storybook stories** — every new primitive ships default + playground + variants; all 70 react-ui components keep their existing stories green. - **Translation-key gate** — the new `check:i18n-keys` script is run in CI, hard-fails on missing or orphan keys. - **Downstream sweep** — `bunx turbo test --filter=...@suaveplan/react-i18n` (covers `react-ui`, `react-forms`, every pack, every theme). ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/react-i18n` and `bunx turbo ls --filter=...@suaveplan/react-ui` to enumerate. - `@suaveplan/react-ui` — [x] tests run / [x] docs reviewed / [ ] no action needed (this change AUTHORS the new react-ui i18n surface) - `@suaveplan/react-forms` — [x] tests run / [x] docs reviewed / [ ] no action needed (validation-message bridge centralization; existing `useTranslation` / `useErrorMessage` consumption unchanged) - `@suaveplan/react-ui-themes/*` (per-theme packages) — [x] tests run / [ ] docs reviewed / [x] no action needed (theme labels go through `tDynamic`; surface unchanged) - `@suaveplan/react-ui-packs/*` (per-pack packages) — [x] tests run / [ ] docs reviewed / [x] no action needed (pack translation-merge surface unchanged; new registry is additive) - Every dependent of `react-ui` (large set) — [x] tests run via `bunx turbo test --filter=...@suaveplan/react-ui` / [ ] docs reviewed / [x] no action needed (no breaking surface change) `@suaveplan/react-i18n` is on the foundation path of the React stack, so the dependent sweep is non-optional. Step 10 of §13 is mandatory and runs: ```bash bunx turbo test --filter=...@suaveplan/react-i18n bunx turbo test --filter=...@suaveplan/react-ui ``` ## Migration impact - **No breaking changes.** All existing `react-i18n` exports keep their signatures. All existing `react-ui` component props keep their semantics; new directions on `` / `` and the new `formatter:` shortcut on `` columns are additive. - **Recommended adoption path** (for downstream packs / themes / consumer apps wanting plugin support): ```tsx // BEFORE {children} // AFTER (opt-in; old form continues to work) {children} ``` - **Locale-aware behavior wiring** (`inline-start` / `inline-end` Drawer/Sheet, locale-aware Calendar, ``, `` `formatter:` shortcut) is opt-in per call site. No global flag flip. ## Source mapping This change is genesis-original. The "source" is the current implementation of `react-i18n` and `react-ui` plus the `@suaveplan/i18n` core's plugin slot and post-processor pipeline (which already exists at the catalog layer and is now mirrored at the React-render layer). | Requirement | Source | Notes | |---|---|---| | `@suaveplan/i18n` plugin slot on `createI18n` | `packages/i18n/i18n/src/instance/instance.ts` | Existing pattern — registry mirrors this at the React layer. | | Catalog-layer post-processors | `packages/i18n/i18n/src/post-processors/` | Existing pattern — react-i18n adds a render-layer pipeline using the same `priority + id` scheme. | | Detector chain shape | `packages/i18n/i18n-detector/src/chain/chain.ts` | Existing — registry-driven splice extends this. | | `` context wiring | `packages/react/react-i18n/src/provider/provider.tsx` | Existing — `` is a sibling provider; both compose. | | `react-ui` locale bundles + module augmentation | `packages/react/react-ui/src/locales/` | Existing — extended with new namespaces for the formatted primitives. | | Pack-system translation merge | `packages/react/react-ui/src/pack/pack-registry-provider/pack-registry-provider.tsx:236` | Existing call site for `i18n.catalog.mergeMessages`. Untouched. |