Initial Commit
This commit is contained in:
277
openspec/archive/2026-05-05-enterprise-react-i18n/tasks.md
Normal file
277
openspec/archive/2026-05-05-enterprise-react-i18n/tasks.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Tasks: enterprise-react-i18n
|
||||
|
||||
**Change ID**: enterprise-react-i18n
|
||||
**Package**: `@suaveplan/react-i18n`
|
||||
**Target path**: `packages/react/react-i18n`
|
||||
**Prerequisite changes**: `i18n-storage-backends`, `i18n-completion` (must be archived before this change ships)
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/i18n` (with the two changes above folded in), `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/locale-data`, `@suaveplan/types`
|
||||
|
||||
## 0. Supersession
|
||||
|
||||
- [ ] 0.1 Move `openspec/changes/enterprise-react-translate/` → `openspec/archive/<yyyy-mm-dd>-enterprise-react-translate-superseded/` with a top-level note explaining the supersession (legacy was i18next-based; this change supersedes by building on the universal core)
|
||||
- [ ] 0.2 Update `STATUS.md` and `SUMMARY.md` so `@suaveplan/react-translate` is replaced by `@suaveplan/react-i18n` in the pending list
|
||||
- [ ] 0.3 Document the consumer-side rename `@suaveplan/react-translate` → `@suaveplan/react-i18n` in the React migration section of `docs/`
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [ ] 1.1 Read this folder's `proposal.md` and `specs/react-i18n/spec.md` end-to-end
|
||||
- [ ] 1.2 Read `openspec/specs/i18n/spec.md` (folded with the two prerequisite changes) — every API used here should be listed there
|
||||
- [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
|
||||
- [ ] 1.4 `openspec change validate enterprise-react-i18n` exits with no errors
|
||||
|
||||
## 2. Scaffold
|
||||
|
||||
- [ ] 2.1 Create `packages/react/react-i18n/` with `package.json` at `0.1.0`, `tsconfig.lib.json`, `vitest.config.ts`, `vite.config.ts`, scripts. React-library tier (`extends ../../tsconfig/react-library.json`).
|
||||
- [ ] 2.2 Dependencies: `@suaveplan/i18n workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/events workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/locale-data workspace:^`, `@suaveplan/types workspace:^`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`, `zod catalog:forms`
|
||||
- [ ] 2.3 Peer dependencies: `react ^18.0.0 || ^19.0.0`
|
||||
- [ ] 2.4 Dev dependencies: `@suaveplan/testing workspace:^`, `@suaveplan/testing-dom workspace:^`, `@testing-library/react`, `@types/react`, `@types/react-dom`, `react`, `react-dom`, `vitest`, `vite`, `vite-plugin-dts`, `fast-check`, `@vitest/coverage-istanbul`
|
||||
- [ ] 2.5 NO `i18next`, `react-i18next`, `i18next-browser-languagedetector`, `i18next-http-backend` — verified by grep before commit
|
||||
- [ ] 2.6 `"sideEffects": false`
|
||||
- [ ] 2.7 Subpath exports: `.`, `./provider`, `./hooks`, `./components`, `./components/trans`, `./components/language-switcher`, `./components/missing-key-overlay`, `./components/i18n-suspense`, `./hoc`, `./errors`, `./schemas`
|
||||
|
||||
## 3. Errors and types
|
||||
|
||||
- [ ] 3.1 Define `ReactI18nError` and four codes via `defineCodes("REACT_I18N", [...])`: `NO_PROVIDER`, `NAMESPACE_LOAD_FAILED`, `HYDRATION_MISMATCH`, `TRANS_INVALID_CHILD`
|
||||
- [ ] 3.2 Define types: `I18nConfig`, `LocaleDescriptor`, `LanguageSwitcherState`, `TranslationProviderProps`, `UseTranslationReturn`, `UseTranslateReturn`, `UseLocaleReturn`, `UseDirectionReturn`, `UseErrorMessageReturn`, `UseI18nReturn`, formatting-hook return types
|
||||
- [ ] 3.3 `react-i18n-error.test.ts` — code registry, error chain, JSON wire format
|
||||
- [ ] 3.4 Sibling `.md` for the error class
|
||||
|
||||
## 4. I18nConfig + provider
|
||||
|
||||
### 4.1 Config
|
||||
|
||||
- [ ] 4.1.1 `createI18nConfig(options)` builds an `I18nConfig` validated through `I18nConfigSchema` (Zod)
|
||||
- [ ] 4.1.2 Schema fields: `defaultLocale`, `fallbackLocale`, `supportedLocales`, `backend`, `backends`, `initialMessages`, `escapeHtml`, `nestingDepth`, `plugins`, `saveMissing`
|
||||
- [ ] 4.1.3 Throws `ReactI18nError(HYDRATION_MISMATCH)` on invalid `initialMessages`; `I18nError(INVALID_OPTIONS)` on other shape failures
|
||||
- [ ] 4.1.4 `config.test.ts` — happy path, every Zod rejection path
|
||||
- [ ] 4.1.5 Sibling `.md`
|
||||
|
||||
### 4.2 Provider
|
||||
|
||||
- [ ] 4.2.1 `TranslationProvider` accepts `i18n` (caller-managed) OR `config` (Provider-managed); rejects both / neither at runtime via Zod refinement
|
||||
- [ ] 4.2.2 Internal subscription via `useSyncExternalStore` to `i18n.events.on('languageChanged' | 'loaded')`
|
||||
- [ ] 4.2.3 SSR-safe: `useSyncExternalStore` server snapshot returns the synchronous catalog state
|
||||
- [ ] 4.2.4 Disposes the instance on unmount only when Provider-managed
|
||||
- [ ] 4.2.5 OTel span on mount with attributes `{ locale, supportedLocales }`
|
||||
- [ ] 4.2.6 `provider.test.tsx` — caller-managed-not-disposed, provider-managed-disposed, SSR initial-messages hydration, both-i18n-and-config-rejected
|
||||
- [ ] 4.2.7 Sibling `.md`
|
||||
|
||||
## 5. Hooks — translation
|
||||
|
||||
### 5.1 useI18n / useTranslation / useTranslate
|
||||
|
||||
- [ ] 5.1.1 `useI18n()` returns the active `I18nInstance`; throws `ReactI18nError(NO_PROVIDER)` in dev when no Provider
|
||||
- [ ] 5.1.2 `useTranslation(namespace?)` triggers `i18n.preload(namespace)` on first mount when a backend is registered; throws the in-flight Promise (Suspense)
|
||||
- [ ] 5.1.3 Returns `{ t, i18n, ready }` where `t` is the ergonomic-options-bag form
|
||||
- [ ] 5.1.4 `useTranslate(namespace?)` is `useTranslation(namespace).t` (terse alias)
|
||||
- [ ] 5.1.5 Production no-provider path: log once + identity translator
|
||||
- [ ] 5.1.6 `use-translation.test.tsx` — preload Suspense + already-loaded sync path + namespace-load failure
|
||||
- [ ] 5.1.7 `use-translation.spec-conformance.test.tsx` — dev throw, prod soft-fail (asserts `process.env.NODE_ENV` switch with vitest stubGlobal)
|
||||
- [ ] 5.1.8 Sibling `.md` documenting Suspense semantics + the dev/prod no-provider distinction
|
||||
|
||||
## 6. Hooks — locale + direction
|
||||
|
||||
- [ ] 6.1 `useLocale()` returns `{ locale, setLocale }`; `setLocale` calls `i18n.changeLanguage`
|
||||
- [ ] 6.2 `useDirection()` returns `{ direction, locale }` derived from `getTextDirection`
|
||||
- [ ] 6.3 Both via `useSyncExternalStore` subscribing to `languageChanged`
|
||||
- [ ] 6.4 `use-locale.test.tsx`, `use-direction.test.tsx` — re-render on locale change, ar → rtl
|
||||
- [ ] 6.5 Sibling `.md` for each
|
||||
|
||||
## 7. Hooks — formatting
|
||||
|
||||
- [ ] 7.1 `useDate(date, options?)` — memoised `formatDate(date, locale, options)`
|
||||
- [ ] 7.2 `useNumber(value, options?)` — memoised `formatNumber(value, locale, options)`
|
||||
- [ ] 7.3 `useCurrency(amount, currency, options?)` — memoised `formatCurrency(amount, currency, locale, options)`
|
||||
- [ ] 7.4 `useRelativeTime(deltaMs, options?)` — memoised `formatRelativeTime(deltaMs, locale, options)`
|
||||
- [ ] 7.5 `useList(values, options?)` — memoised `formatList(values, locale, options)`
|
||||
- [ ] 7.6 Memoisation key: `(locale, ...inputs, JSON.stringify(options))` — re-render on locale change verified
|
||||
- [ ] 7.7 `use-date.test.tsx`, `use-number.test.tsx`, `use-currency.test.tsx`, `use-relative-time.test.tsx`, `use-list.test.tsx`
|
||||
- [ ] 7.8 Sibling `.md` for each
|
||||
|
||||
## 8. Hooks — specialised
|
||||
|
||||
- [ ] 8.1 `useErrorMessage(error)` — `errors:<error.code>` lookup with `error.message` fallback
|
||||
- [ ] 8.2 `usePlural(count, locale?)` — returns `{ category, rule }` from `PluralizationEngine`
|
||||
- [ ] 8.3 `use-error-message.test.tsx`, `use-plural.test.tsx`
|
||||
- [ ] 8.4 Sibling `.md` for each
|
||||
|
||||
## 9. Components — Trans
|
||||
|
||||
- [ ] 9.1 `<Trans i18nKey values components count children?>` parses message via `MessageParser` and calls `formatChunks<ReactNode>(message, values, components)`
|
||||
- [ ] 9.2 Fallback when key missing: parse the component's `children` as the default ICU message
|
||||
- [ ] 9.3 `count` prop drives plural branch via `PluralizationEngine.resolve`
|
||||
- [ ] 9.4 Throws `ReactI18nError(TRANS_INVALID_CHILD)` when a child cannot be matched to a tag pair
|
||||
- [ ] 9.5 Emits `react_i18n.render.duration_ms` histogram
|
||||
- [ ] 9.6 `trans.test.tsx` — tag substitution, nested tags, count, default message, missing-key fallback
|
||||
- [ ] 9.7 `trans.spec-conformance.test.tsx` — missing-key counter, render duration histogram
|
||||
- [ ] 9.8 Sibling `.md` with five rich-text examples (bold, link, nested, plural with tag, fallback)
|
||||
- [ ] 9.9 `trans.stories.tsx` — Storybook with all five examples
|
||||
|
||||
## 10. Components — LanguageSwitcher
|
||||
|
||||
- [ ] 10.1 Headless render-prop API: `children: (state) => ReactNode`
|
||||
- [ ] 10.2 `state.change(locale)` wraps in `useTransition`
|
||||
- [ ] 10.3 Locales sourced from `i18n.supportedLocales` (or override prop) with metadata from `@suaveplan/locale-data`
|
||||
- [ ] 10.4 `language-switcher.test.tsx` — render prop receives full state, change wraps in transition, isPending toggles
|
||||
- [ ] 10.5 Sibling `.md`
|
||||
- [ ] 10.6 `language-switcher.stories.tsx` — Storybook with native `<select>`, custom button list, command-palette-style picker
|
||||
|
||||
## 11. Components — MissingKeyOverlay
|
||||
|
||||
- [ ] 11.1 Subscribes to `missingKey` event; renders fixed-position list with `aria-live="polite"`
|
||||
- [ ] 11.2 Tree-shaken to `null` in production via `process.env.NODE_ENV` check
|
||||
- [ ] 11.3 50-entry cap with FIFO eviction
|
||||
- [ ] 11.4 `missing-key-overlay.test.tsx` — event renders entry, production null, 50-cap
|
||||
- [ ] 11.5 Sibling `.md`
|
||||
- [ ] 11.6 `missing-key-overlay.stories.tsx`
|
||||
|
||||
## 12. Components — I18nSuspense
|
||||
|
||||
- [ ] 12.1 Wraps `React.Suspense` with default `aria-live` fallback region
|
||||
- [ ] 12.2 Increments `react_i18n.suspense.fallback.total` on every fallback render
|
||||
- [ ] 12.3 `i18n-suspense.test.tsx` — fallback rendered, counter incremented
|
||||
- [ ] 12.4 Sibling `.md`
|
||||
|
||||
## 13. HOC — withTranslation
|
||||
|
||||
- [ ] 13.1 `withTranslation<P>(Component)` injects `t` and `i18n` props
|
||||
- [ ] 13.2 Hoists static methods via `hoist-non-react-statics`-equivalent (write a 30-line internal helper to avoid the dep)
|
||||
- [ ] 13.3 `with-translation.test.tsx` — class-component happy path, displayName correct
|
||||
- [ ] 13.4 Sibling `.md`
|
||||
|
||||
## 14. Wiring and exports
|
||||
|
||||
- [ ] 14.1 `src/index.ts` — explicit named exports for every public symbol; NO `export *`
|
||||
- [ ] 14.2 Subpath exports declared in `package.json` for every public module
|
||||
- [ ] 14.3 `README.md` at the package root with: install, quickstart (5 lines), every hook with one example, every component with one example, link to each co-located `.md`
|
||||
- [ ] 14.4 `tests/dep-graph.test.ts` — verifies no `i18next*`, no `react-i18next` deps
|
||||
- [ ] 14.5 Add to `MANIFEST.md`
|
||||
|
||||
## 15. Storybook
|
||||
|
||||
- [ ] 15.1 `provider.stories.tsx` — language-switcher playground swapping en/de/ar/fr
|
||||
- [ ] 15.2 `rtl.stories.tsx` — Arabic with mirrored layout via `useDirection`
|
||||
- [ ] 15.3 `plurals.stories.tsx` — `<Trans count={n}>` demo
|
||||
- [ ] 15.4 `formatting.stories.tsx` — every formatting hook in one panel
|
||||
- [ ] 15.5 `suspense.stories.tsx` — namespace lazy-load with Suspense fallback
|
||||
- [ ] 15.6 `missing-key.stories.tsx` — overlay demo
|
||||
|
||||
## 16. E2E (Playwright)
|
||||
|
||||
- [ ] 16.1 `e2e/react-i18n.spec.ts`:
|
||||
- [ ] Switch language en → de → ar; assert UI strings update
|
||||
- [ ] Switch to ar; assert `<html dir="rtl">` (or wrapper) applied
|
||||
- [ ] Trigger lazy-namespace load; assert Suspense fallback renders then resolves
|
||||
- [ ] Visit page with intentional missing key; assert `<MissingKeyOverlay>` lists it (dev build only)
|
||||
- [ ] SSR mode: render server-side, hydrate; assert no Suspense fallback flashed during hydration
|
||||
|
||||
## 17. OTel instrumentation
|
||||
|
||||
- [ ] 17.1 `react_i18n.missing_key.total{locale,namespace,componentName}` — emit from `<Trans>`
|
||||
- [ ] 17.2 `react_i18n.suspense.fallback.total{namespace,locale}` — emit from `<I18nSuspense>`
|
||||
- [ ] 17.3 `react_i18n.render.duration_ms` — emit from `<Trans>` formatChunks render
|
||||
- [ ] 17.4 Tested with in-memory `MeterProvider` in `*.spec-conformance.test.tsx`
|
||||
|
||||
## 18. Pre-archive verification (the 5-gate triple)
|
||||
|
||||
- [ ] 18.1 `bun run check` — Biome 0/0/0 across the monorepo
|
||||
- [ ] 18.2 `bunx turbo lint typecheck test --filter=@suaveplan/react-i18n` — green
|
||||
- [ ] 18.3 `bunx turbo test:coverage --filter=@suaveplan/react-i18n` — 100/100/100/100 (use `provider: 'istanbul'`)
|
||||
- [ ] 18.4 `bun scripts/openspec-conformance.ts @suaveplan/react-i18n` — exit 0
|
||||
- [ ] 18.5 `bunx turbo build --filter=@suaveplan/react-i18n` — green
|
||||
- [ ] 18.6 Verify only one React version installed: `find . -path "*/node_modules/react/package.json" | head -5`
|
||||
|
||||
## 19. Pre-archive 7-row checklist (AGENTS.md Section 13)
|
||||
|
||||
- [ ] 19.1 Generics threaded; `expectTypeOf` assertions on `useTranslation` return type, `<Trans>` `values` shape
|
||||
- [ ] 19.2 Zod schemas at every public boundary (`I18nConfigSchema`, `TranslationProviderPropsSchema`)
|
||||
- [ ] 19.3 All four error codes thrown from at least one production path
|
||||
- [ ] 19.4 Strict-mode tested: dev no-provider throw, prod soft-fail, hydration mismatch, Trans invalid child
|
||||
- [ ] 19.5 OTel histograms / counters emitted from production paths and asserted with a real in-memory `MeterProvider`
|
||||
- [ ] 19.6 Lifecycle dedup: caller-managed instance not disposed, Provider-managed disposed exactly once
|
||||
- [ ] 19.7 No retroactive tick — every box reflects work done in this change
|
||||
|
||||
## 20. Publish + archive
|
||||
|
||||
- [ ] 20.1 `bun scripts/publish.ts --package @suaveplan/react-i18n`
|
||||
- [ ] 20.2 Fold spec into `openspec/specs/react-i18n/spec.md` (new capability folder)
|
||||
- [ ] 20.3 Move `openspec/changes/enterprise-react-i18n/` → `openspec/archive/<yyyy-mm-dd>-enterprise-react-i18n/`
|
||||
- [ ] 20.4 Refresh `MANIFEST.md`
|
||||
- [ ] 20.5 Refresh `SUMMARY.md` and `STATUS.md` to mark `@suaveplan/react-i18n` shipped
|
||||
|
||||
---
|
||||
|
||||
## Tasks added by Amendment 2026-05-05
|
||||
|
||||
These tasks are introduced by the cross-cutting design at `openspec/cross-cutting-designs/2026-05-05-react-ui-react-i18n-react-forms.md` (see proposal `## Amendment 2026-05-05` for context). They land in the same change set; sequenced after §3-§10 module work but before the §15 conformance suite so the conformance tests exercise them.
|
||||
|
||||
## 21. Locale-detection chain — universal core re-export
|
||||
|
||||
- [ ] 21.1 Verify `@suaveplan/i18n/detector` exports `LocaleDetector`, `DetectorContext`, `createDetectorChain`. If absent (deferred from a since-archived `i18n-completion`), add them to `@suaveplan/i18n` first via a separate PR landing before this one, with their own test coverage.
|
||||
- [ ] 21.2 Re-export `LocaleDetector`, `DetectorContext`, `createDetectorChain` from `@suaveplan/react-i18n/detector` (no behaviour added; React glue lives separately)
|
||||
|
||||
## 22. Built-in detectors
|
||||
|
||||
Each detector lives in its own folder under `src/detector/` per CLAUDE.md 5-files-per-feature layout.
|
||||
|
||||
- [ ] 22.1 `querystringDetector` — env `'both'`, sync; reads `?lang=...` from `URL.searchParams`. Configurable `paramName`. Tests cover server (URL injected via `ctx.request.url`) and browser (`window.location.search`).
|
||||
- [ ] 22.2 `userPreferenceDetector` — env `'both'`, async, persisting; calls `fetch(opts.endpoint)` with `extractAuth(ctx)` headers. POSTs back on `cache(locale, ctx)`. Tests cover SSR auth threading, browser path, network failure (returns `null`), persisting flow.
|
||||
- [ ] 22.3 `cookieDetector` — env `'both'`, sync, persisting; reads `Cookie` header (server) or `document.cookie` (browser). Writes `Set-Cookie` (server via `ctx.responseHeaders`) or `document.cookie` (browser) on cache. Configurable name, maxAge, sameSite.
|
||||
- [ ] 22.4 `localStorageDetector` — env `'browser'`, sync, persisting; reads/writes `localStorage[opts.key]`. Returns `null` if `localStorage` unavailable.
|
||||
- [ ] 22.5 `htmlLangDetector` — env `'browser'`, sync; reads `document.documentElement.lang`.
|
||||
- [ ] 22.6 `acceptLanguageDetector` — env `'server'`, sync; parses `Accept-Language` per RFC 9110 q-values; returns highest-priority supported entry.
|
||||
- [ ] 22.7 `navigatorDetector` — env `'browser'`, sync; iterates `navigator.languages || [navigator.language]`; returns first supported.
|
||||
- [ ] 22.8 `cloudflareIpGeoDetector` — env `'server'`, sync; reads `cf-ipcountry` header; maps ISO 3166 country code to locale via `@suaveplan/locale-data`. Configurable header name and country→locale override map.
|
||||
- [ ] 22.9 `fallbackDetector(locale)` — env `'both'`, sync; always returns the configured fallback. Always last in chain.
|
||||
- [ ] 22.10 `defaultDetectorChain(opts)` factory — returns the canonical 8- or 9-detector array depending on whether `opts.userPreferenceEndpoint` is set
|
||||
- [ ] 22.11 Per-detector co-located `.md` files satisfying §4 of `openspec/AGENTS.md`
|
||||
- [ ] 22.12 Detector tests at 100% statement / branch / function / line coverage
|
||||
- [ ] 22.13 Property test (fast-check): for any sequence of detector outputs, the chain selects the first non-null supported value
|
||||
|
||||
## 23. React glue — useLocaleDetection
|
||||
|
||||
- [ ] 23.1 `useLocaleDetection()` hook in `src/hooks/use-locale-detection/`
|
||||
- [ ] 23.2 `<TranslationProvider>` runs the chain on mount and exposes the result via `useSyncExternalStore`
|
||||
- [ ] 23.3 `setLocale(newLocale)` walks every persisting detector and calls `cache(newLocale, ctx)`
|
||||
- [ ] 23.4 `redetect()` re-runs the chain
|
||||
- [ ] 23.5 SSR contract: `<TranslationProvider initialLocale=...>` skips browser detectors during hydration; tests assert no hydration mismatch warning under React strict mode
|
||||
|
||||
## 24. Detector telemetry
|
||||
|
||||
- [ ] 24.1 Counter `i18n.detector.match_total{detector}` emitted via `getMeter('suaveplan.react-i18n')`
|
||||
- [ ] 24.2 Histogram `i18n.detector.duration_ms{detector}` for `detectAsync` calls
|
||||
- [ ] 24.3 Span `i18n.detect` wrapping the chain run, attributes `{ resolved_locale, winning_detector }`
|
||||
- [ ] 24.4 Tests use a real in-memory `MeterProvider` and `TracerProvider`
|
||||
|
||||
## 25. Branded TranslationKey typing contract
|
||||
|
||||
- [ ] 25.1 `src/translation-key/translation-key.types.ts` exports `AppTranslationKeys`, `ContributedTranslationKeys`, `TranslationKey`
|
||||
- [ ] 25.2 `TranslationKey` is `string & { readonly __brand: 'TranslationKey' } & (keyof AppTranslationKeys | keyof ContributedTranslationKeys)`
|
||||
- [ ] 25.3 `expectTypeOf` assertions: empty interfaces → `TranslationKey extends never`; after augmentation → branded literal union typechecks
|
||||
- [ ] 25.4 Co-located `.md` documenting the augmentation pattern with two worked examples (consumer-app augmentation; downstream-package augmentation)
|
||||
- [ ] 25.5 `scripts/generate-translation-types.ts` reads a package's `locales/en-GB.json` and emits `<package>/src/locales/translation-keys.ts` (the union) plus `<package>/src/locales/augmentation.d.ts` (augments `ContributedTranslationKeys`). Same script ships for use by every downstream package.
|
||||
- [ ] 25.6 `react-i18n` itself uses the script to publish its own internal-error keys (`REACT_I18N.NO_PROVIDER` etc. resolved via `useErrorMessage`)
|
||||
|
||||
## 26. CI gate — no-backwards-deps
|
||||
|
||||
- [ ] 26.1 `scripts/lint/check-no-backwards-deps.ts` — Bun script using TypeScript compiler API
|
||||
- [ ] 26.2 Asserts `@suaveplan/react-i18n/package.json` declares no `@suaveplan/react-ui*` / `@suaveplan/react-forms*` deps
|
||||
- [ ] 26.3 Wired into `bunx turbo lint` for the `@suaveplan/react-i18n` package
|
||||
- [ ] 26.4 Sibling co-located test (`scripts/lint/check-no-backwards-deps.test.ts`) covers passing case and three failing cases (one per banned dep type)
|
||||
|
||||
## 27. tDynamic runtime escape hatch
|
||||
|
||||
- [ ] 27.1 `src/translation-key/t-dynamic.ts` — `tDynamic(key, values?)` looks up active catalog
|
||||
- [ ] 27.2 Missing key throws `ReactI18nError(REACT_I18N.UNKNOWN_KEY)` with `context: { key, locale }`
|
||||
- [ ] 27.3 Each call emits one `warn` log via `@suaveplan/logger` (or `console.warn` fallback)
|
||||
- [ ] 27.4 Co-located `.md` documenting the rare-use intent and the warn-log observability surface
|
||||
- [ ] 27.5 Add `REACT_I18N.UNKNOWN_KEY` to the `defineCodes("REACT_I18N", [...])` registry; existing four codes unchanged
|
||||
|
||||
## 28. Conformance — amendment additions
|
||||
|
||||
The existing §15 conformance suite gains four new spec-conformance tests:
|
||||
|
||||
- [ ] 28.1 `detector/chain.spec-conformance.test.ts` — exercises every default-chain order rule
|
||||
- [ ] 28.2 `detector/ssr-contract.spec-conformance.test.tsx` — server resolves; browser detectors skipped during hydration; `redetect()` runs full chain
|
||||
- [ ] 28.3 `translation-key/branded-type.spec-conformance.test.ts` — `expectTypeOf` assertions for empty and augmented states
|
||||
- [ ] 28.4 `translation-key/t-dynamic.spec-conformance.test.ts` — known key returns; unknown key throws `UNKNOWN_KEY`; warn log fires once per call
|
||||
Reference in New Issue
Block a user