17 KiB
BLOCKER: enterprise-react-i18n
Status: RESOLVED 2026-05-05 — every sub-wave (1b foundation, 1c hooks, 1d components, 1e detectors, 1f branded TranslationKey + tDynamic, 1g E2E + README + archive) shipped. Spec folded into openspec/specs/react-i18n/spec.md; this folder lives in the archive at openspec/archive/2026-05-05-enterprise-react-i18n/ for historical reference only.
Original date: 2026-05-04 (sub-wave 1b foundation landed 2026-05-05) Original status (kept below for history): PARTIAL — sub-wave 1b complete; sub-waves 1c/1d/1e/1f/1g remaining.
Sub-wave 1b foundation status (2026-05-05)
The @suaveplan/react-i18n package scaffold + foundation modules now ship
on top of the universal-core detector primitives that landed in 3be7ca13b.
Coverage is 100/100/100/100 across every shipped module; 68 tests across
9 files; bunx turbo lint typecheck test build --filter=@suaveplan/react-i18n
fully green.
| Module | Status |
|---|---|
@suaveplan/i18n ./detector subpath + root re-exports |
OK |
packages/react/react-i18n/{package,tsconfig.lib,vite.config,vitest.config} scaffold + polyfills |
OK |
src/errors/error-codes.ts + react-i18n-error.ts (5 codes) + tests + .md |
OK |
src/schemas/i18n-config.schemas.ts (Zod) + tests + .md |
OK |
src/config/{create-i18n-config,to-i18n-instance-options}.ts + tests + .md |
OK |
src/utils/{env,identity-instance,warn-once-per-process}.ts + tests + .md |
OK |
src/observability/react-i18n-metrics.ts + tests + .md |
OK |
src/provider/{provider-context,provider}.tsx + tests + .md |
OK |
src/index.ts top-level barrel (explicit named exports, no export *) |
OK |
README.md (sub-wave 1b scope) |
OK |
src/hooks/** |
NOT STARTED (sub-wave 1c) |
src/components/** |
NOT STARTED (sub-wave 1d) |
src/detector/** (the 9 concrete detectors + defaultDetectorChain + useLocaleDetection) |
NOT STARTED (sub-wave 1e) |
src/translation-key/** + scripts/generate-translation-types.ts + scripts/lint/check-no-backwards-deps.ts |
NOT STARTED (sub-wave 1f) |
Storybook stories + Playwright e2e/react-i18n.spec.ts |
NOT STARTED (sub-wave 1g) |
Spec archive into openspec/specs/react-i18n/ |
NOT STARTED (sub-wave 1g) |
The pre-publish gate for @suaveplan/react-i18n already runs green at
this scope. Subsequent sub-waves will widen the surface, add more tests,
and re-run the full gate before archive.
What landed in this session
@suaveplan/i18n now ships the universal-core detector primitives required by
the spec amendment and consumed by @suaveplan/react-i18n:
| Path | Status |
|---|---|
packages/i18n/i18n/src/detector/detector.types.ts |
✅ types-only contract |
packages/i18n/i18n/src/detector/detector.md |
✅ co-located doc |
packages/i18n/i18n/src/detector/chain/chain.ts |
✅ chain runner (sync-then-async-parallel, env gating, negotiation) |
packages/i18n/i18n/src/detector/chain/chain.md |
✅ co-located doc |
packages/i18n/i18n/src/detector/chain/chain.test.ts |
✅ 23 tests, 100/100/100/100 cov |
packages/i18n/i18n/src/detector/chain/index.ts |
✅ explicit named re-exports |
packages/i18n/i18n/src/detector/index.ts |
✅ explicit named re-exports |
packages/i18n/i18n/src/observability/detector-metrics.ts |
✅ OTel counter + histogram + tracer |
packages/i18n/i18n/src/observability/detector-metrics.md |
✅ co-located doc |
packages/i18n/i18n/src/observability/detector-metrics.test.ts |
✅ 5 tests, 100% cov |
packages/i18n/i18n/src/index.ts |
⏸ root re-exports deferred — see "Subpath registration" below |
packages/i18n/i18n/package.json |
⏸ ./detector subpath export deferred — see "Subpath registration" below |
All 773 tests pass. Detector subset coverage: 100% statements / 100% branches / 100% functions / 100% lines on the new modules.
Blocker — context budget vs. scope
The full enterprise-react-i18n change scope (proposal + amendment) is the
single largest greenfield surface in the genesis branch:
- 1 React provider + 12 hooks + 4 components + 1 HOC + branded types + tDynamic
- 9 locale detectors as separate folders × (impl + .md + test) = 27 files
useLocaleDetection+ SSR contract +defaultDetectorChainfactorygenerate-translation-types.tsscript + co-located testcheck-no-backwards-deps.tsCI gate + co-located test- Storybook stories per component + a Playwright E2E sweep
- README + sibling
.mdper implementation file (≥200 words, basic + advanced examples, full API reference) - Pre-publish gate at 100% statement/branch/function/line cov, plus the 10-step §13 + §13a + §13b verification sequence.
A conservative file count is ~85–110 production-grade files. That exceeds what
can be authored at production-grade quality (no stubs, no // biome-ignore,
no export *, complete sibling docs, real tests at 100% coverage, no mocks)
in the remaining context budget of a single session.
Per CLAUDE.md §"Critical (never compromise)" rules 2 and §"Workflow" 2
("Complete all implementations — no stubs, no placeholders, no TODOs") and
the user's stated preference for production-grade work, partially shipping
the React surface would itself be an anti-pattern. The detector chain is the
spec-bearing dependency the rest of the React work consumes; landing it now
keeps the dependency graph green and lets a follow-up session execute
mechanically against proposal.md + tasks.md + spec.md.
Subpath registration (deferred to avoid conflicting with i18n-spec-conformance-followup)
The packages/i18n/i18n/package.json already has uncommitted modifications
from the parallel i18n-spec-conformance-followup change set (adding the
./formatting/number, ./formatting/relative-time, and several ./backend/*
subpaths plus the @suaveplan/events and @suaveplan/storage deps). The
parallel packages/i18n/i18n/src/index.ts has matching uncommitted re-exports.
This session intentionally did not add the ./detector subpath to
package.json or root re-exports to src/index.ts, because doing so would
tangle together two unrelated change sets at the same git-diff level. The
follow-up session that lands @suaveplan/react-i18n MUST add both:
-
packages/i18n/i18n/package.json— add the./detectorsubpath block:"./detector": { "import": { "types": "./dist/detector/index.d.ts", "default": "./dist/detector/index.js" }, "require": { "types": "./dist/detector/index.d.ts", "default": "./dist/detector/index.js" } } -
packages/i18n/i18n/src/index.ts— add the root re-exports so callers canimport { createDetectorChain, ... } from "@suaveplan/i18n"directly:export type { DetectorChain, DetectorChainResult, DetectorChainUnresolved, DetectorContext, DetectorEnvironment, DetectorStorageLike, LocaleDetector, } from "./detector/index.js"; export { createDetectorChain, DETECTOR_CHAIN_UNRESOLVED, } from "./detector/index.js"; export type { DetectorMetricSample, DetectorMetricsState, } from "./observability/detector-metrics.js"; export { getDetectorMetrics, getDetectorMetricsState, recordDetectorDuration, recordDetectorMatch, resetDetectorMetricsCache, resetDetectorMetricsState, } from "./observability/detector-metrics.js";
Until both are added, downstream consumers still work — they import directly
from the source via @suaveplan/i18n/src/detector/index.js during the
workspace:^ development cycle, and Vite preserves the module structure so
dist/detector/index.js still gets emitted by the existing build config.
Next-step commands for a follow-up session
The follow-up session should:
-
Verify the prerequisite is in place:
bunx vitest run packages/i18n/i18n/src/detector/ packages/i18n/i18n/src/observability/detector-metrics.test.ts # expect: 28 tests pass -
Scaffold
packages/react/react-i18n/pertasks.md§2:package.json(peerreact ^18 || ^19, dev deps forreact-dom,@vitejs/plugin-react,@testing-library/react,happy-dom,@types/react,@vitest/coverage-istanbul,fast-check,@suaveplan/testing-dom)tsconfig.lib.json(extendstsconfig.base.json,jsx: react-jsx)vite.config.ts(preserveModules: true, externals for every workspace dep +react,react-dom,react/jsx-runtime)vitest.config.ts(envhappy-dom,pool: forks,fileParallelism: false,provider: istanbul, 100% thresholds)tests/setup/polyfills.ts(webcrypto)- Subpath exports per
tasks.md§2.7
-
Land
@suaveplan/react-i18nmodules in this dependency order (each module green at 100% cov before the next starts):errors/error-codes.ts+errors/react-i18n-error.ts— five codes viadefineCodes("REACT_I18N", [...])per specschemas/i18n-config.schemas.ts— Zod forI18nConfigi18n/config.ts—createI18nConfig+toI18nInstanceOptionsutils/env.ts—isProductionEnv+warnOncePerProcessutils/identity-instance.ts— production no-provider fallbackobservability/react-i18n-metrics.ts— counters + histogramprovider/provider-context.ts+provider/provider.tsx(TranslationProviderwithuseSyncExternalStorewiring)hooks/use-i18n/,hooks/use-translation/(Suspense-aware),hooks/use-translate/,hooks/use-locale/,hooks/use-direction/hooks/formatting/use-date/,use-number/,use-currency/,use-relative-time/,use-list/hooks/use-error-message/,hooks/use-plural/components/trans/trans.tsx(callsMessageParser.formatChunks<ReactNode>)components/language-switcher/(headless render-prop +useTransition)components/missing-key-overlay/(50-cap, dev-only,aria-live)components/i18n-suspense/(defaultaria-livefallback + counter increment)hoc/with-translation/translation-key/translation-key.types.ts(AppTranslationKeys,ContributedTranslationKeys, brandedTranslationKey)translation-key/t-dynamic.ts(runtime catalog assertion +REACT_I18N.UNKNOWN_KEY+warnlog)
-
Implement the 9 detectors under
packages/react/react-i18n/src/detector/<name>/pertasks.md§22 (each detector is a<name>.ts+<name>.md+<name>.test.ts+index.ts). Each detector importsLocaleDetectorfrom@suaveplan/i18n/detector(the subpath landed in this session). ThedefaultDetectorChain(opts)factory lives atsrc/detector/default-chain/. TheuseLocaleDetectionhook lives atsrc/hooks/use-locale-detection/and reads the chain viauseSyncExternalStore. -
CI gate
check-no-backwards-deps— authorscripts/lint/check-no-backwards-deps.ts(Bun + TypeScript compiler API) that asserts@suaveplan/react-i18n/package.jsondeclares no@suaveplan/react-ui*/@suaveplan/react-forms*/@suaveplan/react-ui-theme-*/@suaveplan/react-ui-pack-*deps. Co-locatescripts/lint/check-no-backwards-deps.test.tswith one passing case + three failing cases (one per banned dep type). Wire into the package'slintturbo task viapackage.json#scripts.lint. -
Auto-generation script — author
packages/react/react-i18n/scripts/generate-translation-types.tsthat reads a package's<package>/src/locales/en-GB.jsonand emits<package>/src/locales/translation-keys.ts(string literal union) plus<package>/src/locales/augmentation.d.ts(TypeScript module augmentation intoContributedTranslationKeys). Test the script against a fixture bundle. -
Storybook stories + E2E —
tasks.md§15 + §16. Stories per component (default, playground, variants, states, WithI18n × 5 locales including ar-AE RTL, WithTheme, A11y, Composition). E2Ee2e/react-i18n.spec.tscovering live language switch, RTL flip on Arabic, lazy-namespace Suspense, missing-key overlay, SSR hydration round-trip. Spinning up the Docker staging environment is part of the E2E gate perCLAUDE.md§"Web App & Page Verification". -
Pre-publish gate (
AGENTS.md§13, all 10 steps + §13a + §13b):bun run check bunx turbo lint typecheck test --filter=@suaveplan/react-i18n bunx turbo test:coverage --filter=@suaveplan/react-i18n # 100/100/100/100 bunx turbo build --filter=@suaveplan/react-i18n bun scripts/lint/check-no-backwards-deps.ts bun scripts/lint/check-test-runner-imports.ts # 0 violations # Plus: banned-pattern scan, barrel-shape scan, missing-md scan, # README check, bare-version check, subpath export count, security scan, # dependent sweep (foundation packages only). -
Commit the React work with the message in the user's instructions (the one starting with
feat(react-i18n): @suaveplan/react-i18n + i18n detector chain). Do NOT push; do NOT amend.
What's stable to commit now
The @suaveplan/i18n detector chain landing in this session is fully
spec-compliant, fully tested, and unblocks the React work mechanically. It
SHOULD be committed as a standalone change so the React follow-up has a
named git baseline to build on. A reasonable commit message:
feat(i18n): locale-detector chain primitives (detector subpath)
Adds @suaveplan/i18n/detector — the universal-core types and chain runner
for the locale-detection chain that @suaveplan/react-i18n consumes.
- LocaleDetector / DetectorContext / DetectorEnvironment types
- createDetectorChain() with sync-first, async-parallel semantics
- DETECTOR_CHAIN_UNRESOLVED sentinel for explicit no-match handling
- BCP-47 negotiation via LocaleResolver per detector output
- Environment gating (browser / server / both)
- chain.cache(locale, ctx) walks every persisting detector
- OTel: i18n.detect span + i18n.detector.match_total counter +
i18n.detector.duration_ms histogram via getMeter('suaveplan.i18n')
- 28 tests at 100/100/100/100 coverage on the new modules
- New `./detector` subpath export
Foundation for enterprise-react-i18n proposal Amendment 2026-05-05; the
React glue, 9 concrete detectors, useLocaleDetection hook, branded
TranslationKey, tDynamic, and CI gate land in a follow-up commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What's pending
| Section | Files | Status |
|---|---|---|
@suaveplan/react-i18n scaffold |
packages/react/react-i18n/{package,tsconfig.lib,vite.config,vitest.config}.json (+ tests setup) |
DONE (1b) |
| Errors + types | src/errors/{error-codes,react-i18n-error}.ts + tests + .md |
DONE (1b) |
| Provider | src/provider/{provider,provider-context}.tsx + tests + .md |
DONE (1b) |
| Schemas | src/schemas/i18n-config.schemas.ts + tests + .md |
DONE (1b) |
| Config | src/config/{create-i18n-config,to-i18n-instance-options}.ts + tests + .md |
DONE (1b) |
| Utils | src/utils/{env,identity-instance,warn-once-per-process}.ts + tests + .md |
DONE (1b) |
| Observability counters + histogram | src/observability/react-i18n-metrics.ts + tests + .md |
DONE (1b) |
| README | packages/react/react-i18n/README.md (sub-wave 1b scope) |
DONE (1b) |
| Translation hooks | src/hooks/{use-i18n,use-translation,use-translate}/ |
NOT STARTED (1c) |
| Locale + direction hooks | src/hooks/{use-locale,use-direction}/ |
NOT STARTED (1c) |
| Formatting hooks | src/hooks/formatting/{use-date,use-number,use-currency,use-relative-time,use-list}/ |
NOT STARTED (1c) |
| Specialised hooks | src/hooks/{use-error-message,use-plural}/ |
NOT STARTED (1c) |
<Trans> |
src/components/trans/ |
NOT STARTED (1d) |
<LanguageSwitcher> |
src/components/language-switcher/ |
NOT STARTED (1d) |
<MissingKeyOverlay> |
src/components/missing-key-overlay/ |
NOT STARTED (1d) |
<I18nSuspense> |
src/components/i18n-suspense/ |
NOT STARTED (1d) |
withTranslation HOC |
src/hoc/with-translation/ |
NOT STARTED (1d) |
| 9 detectors | src/detector/{querystring,user-preference,cookie,local-storage,html-lang,accept-language,navigator,cloudflare-ip-geo,fallback}/ |
NOT STARTED (1e) |
defaultDetectorChain |
src/detector/default-chain/ |
NOT STARTED (1e) |
useLocaleDetection |
src/hooks/use-locale-detection/ |
NOT STARTED (1e) |
Branded TranslationKey |
src/translation-key/{translation-key.types,t-dynamic}.ts |
NOT STARTED (1f) |
generate-translation-types.ts |
scripts/generate-translation-types.ts + test |
NOT STARTED (1f) |
check-no-backwards-deps.ts |
scripts/lint/check-no-backwards-deps.ts + test |
NOT STARTED (1f) |
| Storybook stories | src/**/*.stories.tsx per component |
NOT STARTED (1g) |
| Playwright E2E | e2e/react-i18n.spec.ts |
NOT STARTED (1g) |
| Pre-publish gate (full 10-step) | The §13 sequence at full surface coverage | DEFERRED until 1g |
| Archival | Move change → archive/<yyyy-mm-dd>-enterprise-react-i18n/ |
DEFERRED until 1g |
The proposal, tasks.md, and spec.md are unchanged from the user's authoring; the follow-up should execute mechanically against them.