Files
suaveplan/openspec/archive/2026-05-05-enterprise-react-i18n/BLOCKER.md
2026-05-27 23:29:53 +01:00

17 KiB
Raw Blame History

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 + defaultDetectorChain factory
  • generate-translation-types.ts script + co-located test
  • check-no-backwards-deps.ts CI gate + co-located test
  • Storybook stories per component + a Playwright E2E sweep
  • README + sibling .md per 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:

  1. packages/i18n/i18n/package.json — add the ./detector subpath 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"
      }
    }
    
  2. packages/i18n/i18n/src/index.ts — add the root re-exports so callers can import { 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:

  1. 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
    
  2. Scaffold packages/react/react-i18n/ per tasks.md §2:

    • package.json (peer react ^18 || ^19, dev deps for react-dom, @vitejs/plugin-react, @testing-library/react, happy-dom, @types/react, @vitest/coverage-istanbul, fast-check, @suaveplan/testing-dom)
    • tsconfig.lib.json (extends tsconfig.base.json, jsx: react-jsx)
    • vite.config.ts (preserveModules: true, externals for every workspace dep + react, react-dom, react/jsx-runtime)
    • vitest.config.ts (env happy-dom, pool: forks, fileParallelism: false, provider: istanbul, 100% thresholds)
    • tests/setup/polyfills.ts (webcrypto)
    • Subpath exports per tasks.md §2.7
  3. Land @suaveplan/react-i18n modules in this dependency order (each module green at 100% cov before the next starts):

    1. errors/error-codes.ts + errors/react-i18n-error.ts — five codes via defineCodes("REACT_I18N", [...]) per spec
    2. schemas/i18n-config.schemas.ts — Zod for I18nConfig
    3. i18n/config.ts — createI18nConfig + toI18nInstanceOptions
    4. utils/env.ts — isProductionEnv + warnOncePerProcess
    5. utils/identity-instance.ts — production no-provider fallback
    6. observability/react-i18n-metrics.ts — counters + histogram
    7. provider/provider-context.ts + provider/provider.tsx (TranslationProvider with useSyncExternalStore wiring)
    8. hooks/use-i18n/, hooks/use-translation/ (Suspense-aware), hooks/use-translate/, hooks/use-locale/, hooks/use-direction/
    9. hooks/formatting/use-date/, use-number/, use-currency/, use-relative-time/, use-list/
    10. hooks/use-error-message/, hooks/use-plural/
    11. components/trans/trans.tsx (calls MessageParser.formatChunks<ReactNode>)
    12. components/language-switcher/ (headless render-prop + useTransition)
    13. components/missing-key-overlay/ (50-cap, dev-only, aria-live)
    14. components/i18n-suspense/ (default aria-live fallback + counter increment)
    15. hoc/with-translation/
    16. translation-key/translation-key.types.ts (AppTranslationKeys, ContributedTranslationKeys, branded TranslationKey)
    17. translation-key/t-dynamic.ts (runtime catalog assertion + REACT_I18N.UNKNOWN_KEY + warn log)
  4. Implement the 9 detectors under packages/react/react-i18n/src/detector/<name>/ per tasks.md §22 (each detector is a <name>.ts + <name>.md + <name>.test.ts + index.ts). Each detector imports LocaleDetector from @suaveplan/i18n/detector (the subpath landed in this session). The defaultDetectorChain(opts) factory lives at src/detector/default-chain/. The useLocaleDetection hook lives at src/hooks/use-locale-detection/ and reads the chain via useSyncExternalStore.

  5. CI gate check-no-backwards-deps — author scripts/lint/check-no-backwards-deps.ts (Bun + TypeScript compiler API) that asserts @suaveplan/react-i18n/package.json declares no @suaveplan/react-ui* / @suaveplan/react-forms* / @suaveplan/react-ui-theme-* / @suaveplan/react-ui-pack-* deps. Co-locate scripts/lint/check-no-backwards-deps.test.ts with one passing case + three failing cases (one per banned dep type). Wire into the package's lint turbo task via package.json#scripts.lint.

  6. Auto-generation script — author packages/react/react-i18n/scripts/generate-translation-types.ts that reads a package's <package>/src/locales/en-GB.json and emits <package>/src/locales/translation-keys.ts (string literal union) plus <package>/src/locales/augmentation.d.ts (TypeScript module augmentation into ContributedTranslationKeys). Test the script against a fixture bundle.

  7. Storybook stories + E2E — tasks.md §15 + §16. Stories per component (default, playground, variants, states, WithI18n × 5 locales including ar-AE RTL, WithTheme, A11y, Composition). E2E e2e/react-i18n.spec.ts covering 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 per CLAUDE.md §"Web App & Page Verification".

  8. 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).
    
  9. 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.