agentsclimarketplace

I18n changes

Skill andymai/gridfinity-layout-tool/.claude/skills/i18n-changes

Adding/renaming/removing translation keys in src/i18n/locales/en.ts and the 14 non-English locale JSONs, fixing the four i18n check failures ("i18n key mismatch detected", "Untranslated values detected", "interpolation mismatch detected", "Unused i18n keys detected"), the missing en.json TS2307 error, the values allowlist, plurals without ICU, and adding a new locale.From its SKILL.md

Install
npx -y skills add andymai/gridfinity-layout-tool --skill i18n-changes

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

10.0 KB, as published. Nobody here has run it

i18n Changes

When to use

  • Adding, renaming, or removing any user-facing string (nearly every feature PR).
  • A pre-commit or CI quality-job step starting with check:i18n failed.
  • pnpm run typecheck fails with TS2307: Cannot find module './locales/en.json'.
  • UI shows a raw key (feature.thing), a literal {count}, or an ICU block.

Mental model

  • src/i18n/locales/en.ts is the hand-edited source of truth (~2500 flat dotted keys). src/i18n/locales/en.json is a gitignored build artifact regenerated by pnpm run build:en-locale (also runs automatically inside dev and build). Never hand-edit en.json; on a fresh clone, generate it before typecheck.
  • The translated locale JSONs (cs, de, es, fr, it, ja, ko, nb, nl, pl, pt-BR, sv, uk, zh-CN — 14 today) must each have exactly the same key set as en.ts. Never trust a memorized count: both check scripts readdirSync the locales dir and skip en.json, so a new locale is picked up the moment its JSON lands. SUPPORTED_LOCALES in src/i18n/types.ts (15 incl. en) is the matching runtime list.
  • All four check scripts parse en.ts with regex, never import it. Keep en.ts regex-parseable: single-quoted keys, plain quoted string values, no template literals, no concatenation, no escaped quotes (use double-quoted values for apostrophes). A value the regex can't match silently disappears from every check.
  • Runtime interpolation (interpolate() in src/i18n/context.tsx) is literal {name} replaceAll. ICU is never evaluated even though the interpolation check tolerates its syntax. Plurals = two keys + a ternary in code.
  • t() fallback is translations[key] || _loadedEn?.[key] || key — empty string counts as missing; you cannot intentionally blank a string per-locale.
  • Gridfinity domain terms stay in English in all locales: bin, drawer, layer, staging/stash, grid unit, height unit, print bed, Gridfinity (see the en.ts header comment).

Recipe: add a string

  1. Add 'feature.context.element': 'Value', to src/i18n/locales/en.ts under the matching // section comment.
  2. Add the same key, translated, to every non-English locale JSON. If a locale's value is legitimately identical to English (brand name, format string, domain term), add the key to the "keys" array in scripts/i18n-values-allowlist.json. Identical values ≤3 chars without spaces (OK, 3MF) or matching a valuePatterns regex (placeholder-only strings) auto-pass.
  3. In code: const t = useTranslation(); then t('feature.context.element') or t('key', { count }) — the param name must equal the {count} token exactly. Outside React (workers, ErrorBoundary) use getStaticTranslation from @/i18n.
  4. Plurals: two keys, e.g. 'baseplate.bedLoads.one': 'Prints in 1 build-plate load' and 'baseplate.bedLoads.other': 'Prints in {count} build-plate loads', selected by a ternary at the call site (see src/features/baseplate/components/BaseplatePage/BaseplatePage.tsx ~line 326). Never {count, plural, ...}.
  5. Verify (see below).

Recipe: rename or remove a key

  1. rg "old\.key" src/ — catch data-structure refs (labelKey: 'x.y') and ternary keys, not just t() calls.
  2. Change en.ts, every non-English JSON, and every code ref in the same commit. Touching only en.ts produces "Missing" (add) or "Extra ... (not in en.ts)" (remove) per locale.
  3. Merging duplicates: pnpm exec tsx scripts/consolidate-i18n-key.ts --from old.key --to keep.key --dry-run — refuses unless both keys are identical in every locale; drop --dry-run to apply, then update the code refs it lists.
  4. Because you touched src/i18n/, pre-commit WILL run the unused check and may surface unrelated pre-existing debt — fix it, don't bypass.

Recipe: add a locale

  1. Create src/i18n/locales/<code>.json with every en.ts key translated (use en.json as template).
  2. src/i18n/types.ts: extend the Locale union and append to SUPPORTED_LOCALES (renders in the language selector).
  3. src/i18n/context.tsx: add entries to localeLoaders and OG_LOCALE_MAP (underscore OG format, e.g. sv_SE) — typecheck errors on both Record<Locale, ...> until you do.
  4. src/i18n/detection.ts: add LANGUAGE_MAP entries for the base code and regional variants.
  5. pnpm run typecheck && pnpm run check:i18n && pnpm run check:i18n:values.

Verification

pnpm run build:en-locale
pnpm run check:i18n && pnpm run check:i18n:values && \
pnpm run check:i18n:interpolation && pnpm run check:i18n:unused

Each failure prints a distinct ❌ line naming exact keys/files/lines. Pre-commit runs checks 1–3 always and the unused check only when src/i18n/ files are staged (.husky/pre-commit); the CI quality job runs each with continue-on-error and aggregates, so read the whole log — there may be several failures at once. After changing context.tsx/detection.ts: pnpm run test:run src/i18n.

For the other pre-commit gates (boundaries, design-system, exhaustiveness, missing-tests), see the quality-gates skill. For where strings live inside a feature slice, see the feature-slices skill.

Traps

SymptomCauseFix
TS2307: Cannot find module './locales/en.json' at context.tsxen.json is gitignored; fresh clone has nonepnpm run build:en-locale
❌ i18n key mismatch detectedKey set differs between en.ts and a locale JSON (typos show as Missing + Extra)Apply the exact per-file Missing/Extra list
❌ Untranslated values detectedLocale value copied from English, misses allowlist heuristicsTranslate it, or allowlist the key if identity is intentional
❌ i18n interpolation mismatch detected{name} token in en.ts ≠ param name at the t() call (output gives file:line)Make token and param identical — fix whichever side is wrong
❌ Unused i18n keys detected blocks a commit that only added one keyAn earlier PR deleted the last ref; the gated check fires on the next i18n toucherDelete the listed definitely-unused keys from en.ts + all JSONs; ⚠️ possibly-unused (dynamic-prefix) lines are warn-only
UI renders literal {count} or a raw ICU blockRuntime is plain replaceAll; the interpolation check only sees literal t('key', ...) calls — ternary/dynamic keys are invisible to itPass the vars object; replace ICU with .one/.other keys + ternary. 'collab.participantCount' in en.ts is a live latent bug — do not copy it
UI shows raw dotted key, sometimes only briefly or only non-EnglishKey missing in active locale before the en chunk lazy-loads; pre-load resolves only the hardcoded fallback map in context.tsx (errorBoundary., seo., common.loading)Run check:i18n for parity; keys needed outside React/before load must also go in that fallback map
Unrelated commit blocked by an i18n checkChecks scan whole files, not staged hunksFix or stash existing i18n debt first
Component test asserts translated text without a provider and passessrc/test/setup.ts globally mocks @/i18n with real en.ts stringsExpected; locale-switching/fallback behavior is only tested in src/i18n/context.test.tsx
Missing loading spinner on first page load after a locale changeInitial mount deliberately renders children with the fallback map to avoid CLS; spinner appears only on explicit setLocale()Not a bug — don't "fix" it

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.