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

4.4 KiB

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

bun run check:i18n-keys
# check-i18n-keys: clean — 23 keys in code, 23 keys in bundle.

When a key is missing:

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:

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):

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.