Files
skills/docs/ui/react/react-ui/scripts/check-i18n-keys.md
2026-05-29 08:20:56 +01:00

78 lines
4.4 KiB
Markdown

# `check-i18n-keys`
## Purpose
Translation-key drift gate for `@suaveplan/react-ui`. Walks every `*.{ts,tsx}` under the package's source roots, extracts every `react-ui.*` translation-key reference from the documented call shapes, and diffs the discovered set against the canonical `src/locales/en-GB.json`. Reports `MISSING <key>` for keys referenced in code but absent from the bundle (consumers will hit a missing-key warning) and `ORPHAN <key>` for bundle keys nothing in source references (dead translation keys). Catches drift the moment a developer renames a key in code but forgets the bundle, or vice versa.
## Features
- Pattern-based extraction over six chassis-recognised call shapes: `t("react-ui.…")`, `tDynamic(...)`, `i18nKey="…"` JSX attribute, `i18nKey: "…"` object literal, `<name>Key={"…"}` prop default, `?? "react-ui.…"` fallback shorthand, `const DEFAULT_*_KEY = "…"` module-scope literal, `labelKey: "…"` plugin shape.
- Source location (`<file>:<line>`) attached to every MISSING diagnostic for quick navigation.
- `--strict` flag promotes ORPHAN diagnostics to errors. Default exits 0 on ORPHAN-only (warn) so the gate ships green even when the bundle has pre-existing dead keys; CI can opt into strict mode after a cleanup pass.
- Wired into `bunx turbo lint` — every commit in CI runs the default mode.
## Usage: Basic Example
```bash
bun run check:i18n-keys
# check-i18n-keys: clean — 23 keys in code, 23 keys in bundle.
```
When a key is missing:
```bash
bun run check:i18n-keys
# MISSING react-ui.foo.absent at src/components/foo/foo.tsx:42
# check-i18n-keys: 1 missing, 0 orphan (orphan = warn)
# (exit 1)
```
## Usage: Advanced Example
Strict mode for a periodic bundle-cleanup pass:
```bash
bun run check:i18n-keys:strict
# ORPHAN react-ui.popover.close
# ORPHAN react-ui.alertDialog.cancel
# check-i18n-keys: 0 missing, 2 orphan (strict)
# (exit 1)
```
The script's exported helpers can be reused by other tooling (e.g. a CI report aggregator):
```ts
import {
diff,
extractKeysFromSource,
loadBundleKeys,
} from "./check-i18n-keys.js";
const sourceKeys = (await extractKeysFromSource(files, root)).keys;
const bundleKeys = await loadBundleKeys(bundlePath);
const diagnostics = diff(sourceKeys, bundleKeys);
```
## API Reference
### CLI
`bun scripts/check-i18n-keys.ts [--strict]`
- Default mode: exits non-zero only when at least one MISSING diagnostic appears. ORPHANs are printed as warnings.
- `--strict`: exits non-zero on any diagnostic (MISSING or ORPHAN).
### Exported helpers
- `extractKeysFromSource(files, rootForRelative): Promise<{ keys: Map<key, location> }>` — walks the supplied source files, returns a map keyed by translation key, value `<relative-path>:<line>`.
- `loadBundleKeys(bundlePath): Promise<Set<string>>` — reads the bundle JSON and returns the subset of keys in the `react-ui.*` namespace.
- `diff(sourceKeys, bundleKeys): Diagnostic[]` — pure function returning sorted diagnostics (MISSING before ORPHAN, alphabetic within each kind).
## Implementation Notes
- Pattern-based instead of AST-based — no `@babel/parser` dep. The supported call shapes are a closed set documented at the top of the script. New idioms (e.g. a future `const KEYS = { close: "react-ui.foo" }` const-record style) need a new pattern; the script falls into the catch-all-of-last-resort `?? "react-ui.…"` only when the value lives in a discriminating context.
- The extraction explicitly EXCLUDES `*.test.tsx`, `*.stories.tsx`, `*.stories.test.tsx`, and `*.bench.ts` — test fixtures and stories often reference keys that exist in the bundle for component contracts, not for the test's own runtime use; treating them as references would mask real orphans.
- Scan roots: `src/components`, `src/i18n`, `src/theme`, `src/pack`, `src/provider`, `src/hooks`, `src/errors`. Any future surface that ships `react-ui.*` keys must be added here.
- The default-vs-strict split is deliberate. Pre-existing orphans in the bundle (`react-ui.empty.defaultDescription`, `react-ui.popover.close`, …) are public-API keys consumers may pass via `*Key` prop overrides; removing them would be a breaking change. Default mode warns; strict mode lets the team enforce zero-orphan after a bundle cleanup change.
- The script is a `Bun.import.meta.main` entry point; `process.exit(await main())` is the only top-level side effect. Imports are pure helpers reusable from tests.