# Tasks — react-i18n plugin registry & react-ui first-class i18n surface Phased so each phase ends with a green pre-publish gate. Each task is a single concrete unit of work; check off when the implementation, sibling `.md`, test file, and (where applicable) story file are all complete and the corresponding gate is green. ## Phase 1 — `@suaveplan/react-i18n` plugin registry foundation - [ ] 1.1 Add new error codes (`PLUGIN_DUPLICATE_ID`, `PLUGIN_INVALID`, `REGISTRY_NOT_PROVIDED`, `FORMATTER_NOT_REGISTERED`, `DETECTOR_PLUGIN_DUPLICATE_ID`, `DETECTOR_PLUGIN_INVALID`) to `src/errors/error-codes.ts`. Update the registry test. - [ ] 1.2 Author `src/registry/registry.types.ts` — `DetectorPlugin`, `FormatterPlugin`, `PostProcessorPlugin`, `PostProcessorContext`, `ReactI18nRegistry` interfaces. No nested object literals; every shape named. - [ ] 1.3 Author `src/registry/schemas/registry.schemas.ts` — `DetectorPluginSchema`, `FormatterPluginSchema`, `PostProcessorPluginSchema`. Strict-object Zod schemas. Export both schemas and `RegistryConfigSchema` (the union container shape `` accepts). - [ ] 1.4 Implement `src/registry/registry.ts` — `createReactI18nRegistry()` returning a fresh `ReactI18nRegistry`. Idempotent re-registration, duplicate-id throw, OTel observability (`react_i18n.registry.plugins_total{kind,outcome}`), disposable handles. - [ ] 1.5 Author `src/registry/registry.test.ts` — registration / duplicate / dispose / schema-rejection / OTel-emit scenarios. Property test for register-then-dispose-N-times → empty registry. - [ ] 1.6 Sibling `.md` for `registry.ts` (≥200 words, all six required sections). ## Phase 2 — `` and `useReactI18nRegistry` - [ ] 2.1 Implement `src/registry/registry-context.ts` — context with `null` default and a brand symbol for safe cross-bundle identity. - [ ] 2.2 Implement `src/registry/registry-context.tsx` — ``. Mounts a fresh registry on first render, registers built-in formatters unless disabled, registers user-provided plugins, disposes the registry on unmount. - [ ] 2.3 Implement `src/registry/use-registry.ts` — `useReactI18nRegistry()`. Throws `REACT_I18N.REGISTRY_NOT_PROVIDED` outside a provider. - [ ] 2.4 Tests for the provider — `registry-context.test.tsx`. Verify context isolation, dispose-on-unmount, built-in registration, user-plugin registration, disabled built-ins. - [ ] 2.5 Sibling `.md` files for both `registry-context.tsx` and `use-registry.ts`. ## Phase 3 — Detector plugin spliced into chain - [ ] 3.1 Modify `src/detector/default-chain/default-chain.ts` — accept an optional `registry?: ReactI18nRegistry` argument; when provided, splice plugins by `priority` between built-ins. - [ ] 3.2 Modify `src/hooks/use-locale-detection/use-locale-detection.ts` — read registry from context (when available) and feed it into `defaultDetectorChain`. - [ ] 3.3 Update `default-chain.test.ts` — coverage for plugin splice, deterministic tie-break, environment gating, no-registry fallback to current behavior. - [ ] 3.4 Update sibling `default-chain.md` to document the new optional parameter. ## Phase 4 — Formatter plugins + `useFormatter` hook - [ ] 4.1 Author `src/registry/formatters/built-in/` — six files (`number.ts`, `date.ts`, `currency.ts`, `list.ts`, `relative-time.ts`, `plural.ts`), each exporting a `FormatterPlugin` whose `format` calls the existing per-package implementation. - [ ] 4.2 Implement `src/hooks/use-formatter/use-formatter.ts` — looks up plugin by id from registry, returns `format` result, memoizes on `(locale, id, JSON.stringify(args))`. - [ ] 4.3 Modify each existing formatter hook (`use-number`, `use-date`, `use-currency`, `use-list`, `use-relative-time`, `use-plural`) to delegate through `useFormatter` when a registry is available; fall back to direct call when not. - [ ] 4.4 Tests — `use-formatter.test.tsx` (built-in resolution, custom override, missing-id throw, memoization), updated tests for each modified hook (no behavior change without a registry, override works with a registry). - [ ] 4.5 Sibling `.md` for `use-formatter.ts` and updates to each modified hook's `.md` documenting the registry-override path. ## Phase 4b — Locale-adapter plugin slot - [ ] 4b.1 Author `src/registry/locale-adapters/locale-adapters.types.ts` — `LocaleAdapterPlugin` interface. - [ ] 4b.2 Author `src/registry/locale-adapters/built-in/date-fns.ts` — built-in adapter, dynamic-import the date-fns locales map so non-Calendar consumers do not pay the bundle cost. - [ ] 4b.3 Implement `src/hooks/use-locale-adapter/use-locale-adapter.ts` + sibling `.md`. - [ ] 4b.4 Wire `` to auto-register the `date-fns` built-in unless `disableBuiltInLocaleAdapters` is set. - [ ] 4b.5 Tests — built-in resolution, swap-with-luxon-shaped adapter, missing-id throw. - [ ] 4b.6 Add `LOCALE_ADAPTER_NOT_REGISTERED` to error registry. ## Phase 5 — Post-processor pipeline - [ ] 5.1 Author `src/registry/post-processors/post-processors.ts` — `runPostProcessors(message: string, ctx: PostProcessorContext, plugins: PostProcessorPlugin[]): string`. Sorts by priority, ties broken lexicographically by id. - [ ] 5.2 Modify `src/components/trans/trans.tsx` to run the registry's post-processor pipeline after ICU interpolation, before rendering. - [ ] 5.3 Tests — pipeline ordering, identity (no plugins → message unchanged), associativity property test, integration with `` rendering. - [ ] 5.4 Sibling `.md` for `post-processors.ts` and update `trans.md`. ## Phase 6 — Subpath exports + barrel - [ ] 6.1 Add subpath exports to `react-i18n/package.json::exports`: `./registry`, `./registry/detectors`, `./registry/formatters`, `./registry/post-processors`. - [ ] 6.2 Add explicit named re-exports to `src/registry/index.ts` (no `export *`). - [ ] 6.3 Add the new public surface to `src/index.ts` root barrel (also explicit named). - [ ] 6.4 Run §13 step 4a barrel-shape scan — zero hits expected. ## Phase 7 — Pre-publish gate for react-i18n - [ ] 7.1 `bunx turbo lint typecheck test --filter=@suaveplan/react-i18n` green. - [ ] 7.2 `bunx turbo test:coverage --filter=@suaveplan/react-i18n` ≥ 100 / 100 / 100 / 100. - [ ] 7.3 `bunx turbo build --filter=@suaveplan/react-i18n` green. - [ ] 7.4 §13 banned-pattern scan, missing-`.md` scan, README check, bare-version check, subpath-export count, security scan — all clean. - [ ] 7.5 `bunx turbo test --filter=...@suaveplan/react-i18n` (downstream sweep) — green. Includes `react-ui`, `react-forms`, every pack/theme. ## Phase 8 — `@suaveplan/react-ui` formatted primitives - [ ] 8.1 `src/i18n/formatted-number/formatted-number.tsx` + `.md` + `.test.tsx` + `.stories.tsx`. - [ ] 8.2 `src/i18n/formatted-currency/formatted-currency.tsx` + 3 siblings. - [ ] 8.3 `src/i18n/formatted-date/formatted-date.tsx` + 3 siblings. - [ ] 8.4 `src/i18n/formatted-list/formatted-list.tsx` + 3 siblings. - [ ] 8.5 `src/i18n/formatted-relative-time/formatted-relative-time.tsx` + 3 siblings (auto-mode interval test uses fakeClock from `@suaveplan/testing/runner`). - [ ] 8.6 `src/i18n/plural/plural.tsx` + 3 siblings. - [ ] 8.7 `src/i18n/formatted-message/formatted-message.tsx` (re-export `` with `react-ui` branding) + 3 siblings. - [ ] 8.8 `src/i18n/index.ts` explicit-named barrel. - [ ] 8.9 New subpath exports in `react-ui/package.json::exports`: `./i18n`, `./i18n/formatted-number`, `./i18n/formatted-currency`, etc. ## Phase 8b — ReactUiI18nBridge + TanStack collator bridges - [ ] 8b.1 `src/i18n/react-ui-i18n-bridge/react-ui-i18n-bridge.tsx` + `.md` + `.test.tsx` + `.stories.tsx`. Mounts `` (Radix), exposes `LocaleContext`, syncs ``/`` (opt-out), forwards `dir` to descendant sonner ``. - [ ] 8b.2 `src/i18n/tanstack-bridge/tanstack-bridge.ts` — `localeAwareSortingFn(locale)` and `localeAwareFilterFn(locale)` backed by `Intl.Collator`. Plus sibling `.md` and `.test.ts`. - [ ] 8b.3 Compose `` into the default `` so the canonical app-level provider gets it for free; standalone mounting remains supported. - [ ] 8b.4 Subpath exports: `./i18n/react-ui-i18n-bridge`, `./i18n/tanstack-bridge`. ## Phase 9 — Locale-aware behavior wiring on existing components - [ ] 9.1 `` adds `inline-start` / `inline-end` direction values; resolves via `useDirection`. Update `drawer.tsx`, `drawer.md`, `drawer.test.tsx`, `drawer.stories.tsx`. - [ ] 9.2 `` adds `inline-start` / `inline-end` `side` values; same `useDirection` resolution. Update siblings. - [ ] 9.3 `` `ColumnDef.formatter` shortcut. Update `data-table.tsx`, `data-table.md`, `data-table.test.tsx`, `data-table.stories.tsx`. - [ ] 9.4 `` page-number rendering switches to ``. Verify keys remain stable. - [ ] 9.5 `` wires `useLocale()` into `react-day-picker`'s `locale` prop. Add `localeStringToDateFnsLocale` helper with a curated map for the five bundled locales + a documented fallback path. Update siblings. ## Phase 10 — `` - [ ] 10.1 `src/components/input/input-number/input-number.tsx` + `.md` + `.test.tsx` + `.stories.tsx`. Locale-aware decimal/grouping parse, focus-state buffer, `onValueChange` numeric emission. - [ ] 10.2 Subpath export `./components/input/input-number`. - [ ] 10.3 Bundle keys for placeholder / aria-label across `en-GB`, `zh-CN`, `ar-AE`, `cy-GB`, `ru-RU`. ## Phase 11 — Translation bundles + extraction script - [ ] 11.1 Extend `src/locales/schema.ts` with the new namespaces. - [ ] 11.2 Extend each `src/locales/.json` with translations for the new keys (real translations, not English placeholders for non-English bundles — request review from native readers if uncertain, document in proposal). - [ ] 11.3 Update `src/locales/augmentation.ts` to register the new keys into `ContributedTranslationKeys`. - [ ] 11.4 Author `scripts/extract-translation-keys.ts`. AST walk via `@babel/parser` (or the same parser the rest of the repo uses), diff against the canonical `en-GB.json`. Document missing-vs-orphan output format. - [ ] 11.5 Add `bun run check:i18n-keys` to `react-ui/package.json::scripts`. Wire into `bunx turbo lint` via the package's `turbo.json` lint pipeline. ## Phase 12 — Validation message bridge - [ ] 12.1 `src/i18n/validation-message-bridge/validation-message-bridge.ts` + sibling siblings. - [ ] 12.2 Update ``, ``, and `` (the obvious validation-error consumers) to optionally accept a `SuaveplanError` and render it through the bridge. Existing string props continue to work. - [ ] 12.3 Optional touch on `react-forms` to import the bridge from `@suaveplan/react-ui/i18n/validation-message-bridge` instead of its own ad-hoc copy. (Only if the diff stays trivial; otherwise mark as a follow-up.) ## Phase 12b — shadcn registry regeneration + ejection gate - [ ] 12b.1 Author / regenerate `registry/r/.json` for every new primitive (formatted-number, formatted-currency, formatted-date, formatted-list, formatted-relative-time, plural, formatted-message, input-number, react-ui-i18n-bridge, tanstack-bridge, validation-message-bridge). Each lists its `@suaveplan/react-i18n` dep in `registryDependencies`. - [ ] 12b.2 Regenerate registry entries for modified components: drawer, sheet, calendar, data-table, pagination — capturing new imports (`useDirection`, `useLocale`, `useLocaleAdapter`, ``). - [ ] 12b.3 Author `scripts/check-registry-eject.ts` — applies the registry transform to each entry's source files, type-checks the transformed result against a synthesized consumer `tsconfig.json`, asserts `@suaveplan/react-i18n` imports survive. - [ ] 12b.4 Wire `bun run check:registry-eject` into `package.json::scripts` and into `bunx turbo lint` via `turbo.json`. - [ ] 12b.5 Test: `scripts/check-registry-eject.test.ts` — covers happy-path eject, missing-registry-entry diagnostic, transformed-source-fails-tsc diagnostic. ## Phase 13 — Pre-publish gate for react-ui - [ ] 13.1 `bunx turbo lint typecheck test --filter=@suaveplan/react-ui` green. - [ ] 13.2 `bunx turbo test:coverage --filter=@suaveplan/react-ui` ≥ 100 / 100 / 100 / 100. - [ ] 13.3 `bunx turbo build --filter=@suaveplan/react-ui` green. - [ ] 13.4 `bun run check:i18n-keys` zero-defect. - [ ] 13.4b `bun run check:registry-eject` zero-defect — every registry entry compiles when ejected. - [ ] 13.5 §13 banned-pattern / missing-`.md` / barrel-shape / runner-import scans — all clean. - [ ] 13.6 `bunx turbo test --filter=...@suaveplan/react-ui` downstream sweep — green. - [ ] 13.7 Storybook smoke run — every new story renders without console errors / warnings. ## Phase 14 — Spec fold + archive - [ ] 14.1 Fold `specs/react-i18n/spec.md` deltas into `openspec/specs/react-i18n/spec.md`. - [ ] 14.2 Create `openspec/specs/react-ui-i18n/spec.md` from the deltas. - [ ] 14.3 Update root docs: `MANIFEST.md` (auto-generate), `MISSING.yml` (auto-generate), `STATUS.md`, `SUMMARY.md`. - [ ] 14.4 `bunx markdownlint-cli2 --fix` across touched files; second pass without `--fix` exits 0. - [ ] 14.5 Move `openspec/changes/react-i18n-plugin-registry-and-ui-primitives/` → `openspec/archive/2026-MM-DD-react-i18n-plugin-registry-and-ui-primitives/`.