Locale workflow for agents
This playbook helps agents edit locale files with low risk and high quality. The goal is simple: keep key structure stable, keep meaning aligned with en.json, and improve target-language phrasing without breaking UI behavior.
Source of truth
apps/web/locales/en.jsonis the canonical meaning for every key.- Non-English locale files must preserve the same key paths and data shapes.
- When unsure about meaning, infer from:
- sibling keys and section hierarchy in
en.json - where the key is used in UI (
t('...'),t(\${i18nKey...}`)`, labelKey usage) - domain context (
pages.domain.*,navigation.*,errors.codes.*)
- sibling keys and section hierarchy in
Core rules when editing translations
- Never rename, move, or delete keys unless explicitly asked.
- Never change placeholders or interpolation syntax:
- keep
{{id}},{{count}}, etc. exactly as-is - keep HTML/markup-like tokens if present
- keep
- Preserve punctuation/format intent where it matters (
:,%, units, abbreviations). - Keep translation tone consistent with product context:
- university maintenance and operations dashboard
- professional, clear, polite, technical UI copy
- Prefer natural target-language phrasing over literal word-by-word translation.
Agent rule: edit only en.json by default
When the task is “update i18n locales”, the safest default is:
- Modify
apps/web/locales/en.jsononly (add/remove/rename keys and update English meaning). - Do NOT directly edit non-English JSON files in
apps/web/locales/. - Tell the user to run:
pnpm locales:sync(align non-EN key shapes)pnpm locales:fill -- --all(optional: fill EN-identical gaps)pnpm locales:validate(optional: JSON parsing sanity check)
If the user explicitly asks to update non-English locales too, then follow the workflow in docs/frontend/i18n/*.md and docs/frontend/i18n/examples.md, including pnpm locales:validate:quality and then generator/fill steps for the affected languages.
Technical term preserve policy
The project uses explicit preserve policies in:
apps/web/scripts/locales/shared/preserve.ts
Current behavior is intentionally split:
COPY_EN_VERBATIM_KEYS: keys copied from English during generation (no machine translation)EXCLUDE_EN_COMPARE_*: keys/values ignored during EN-parity checks (sync/fill)
When adding preserve entries:
- Add only terms/keys that must remain in English for product reasons (brand, canonical abbreviations, fixed acronyms).
- Do not add generally translatable UI words to preserve lists.
- If a value is a normal label in context (for example, lowercase
cacheused as a feature label), treat it as translatable unless product explicitly says otherwise. - Prefer key-based exclusions over broad value-based exclusions when possible.
Translation quality workflow (recommended)
- Use generation/sync scripts to keep key parity and bootstrap text.
- Post-edit target locale for meaning and fluency (manual or AI-assisted).
- Re-check for structural safety:
- JSON validity
- key-set parity with
en.json - placeholder integrity
- Spot-check in UI pages where the text appears.
Useful commands (from apps/web):
pnpm locales:generatepnpm locales:fill -- --lang <code>pnpm locales:syncpnpm locales:validatepnpm locales:validate:qualitypnpm locales:ci— single non-interactive gate (JSON, missing keys vsen.json, quality/parity); see ci.md
AI post-editing guidance
When asked to improve a non-English locale value:
- Use the corresponding English key/value as semantic anchor.
- Keep the same functional meaning, not just similar words.
- Optimize for clarity in UI contexts (buttons, toasts, tables, empty states, errors).
- Keep terms consistent across related keys in the same section.
Recommended system prompt for translation polishing:
You are a professional UI translator for a university maintenance management system.
Your job is to improve locale text so it sounds natural, polite, and consistent
for technicians, department heads, administrators, and developers.
Rules:
- Preserve JSON keys exactly.
- Preserve placeholders like {{id}}, {{count}}, and formatting tokens.
- Keep meaning aligned with en.json for each corresponding key.
- Keep terminology consistent across related keys.
- Do not translate product/brand terms that are explicitly preserved by policy.
- Prefer concise UI wording over literal sentence-by-sentence translation.
- Keep action labels clear and imperative where appropriate (e.g., buttons, toasts).
- Keep register professional and user-friendly (no slang, no over-formal legal tone unless key context requires it).Batch generation policy
- Generation intentionally runs one language at a time in normal mode.
- Reasoning:
- each language requires metadata confirmation and human review prompts,
- key count is already high (~650+) and still growing, so per-language review keeps quality controllable,
- failure isolation is cleaner (one locale can be retried/fixed without affecting others).
- Fill mode supports
--allfor bulk catch-up of EN-identical strings, but full generation remains single-locale by design.
Typed i18n status
Type-safe keys are now active:
src/types/i18n/locales.d.tsprovidesi18nextdeclaration merging fromlocales/en.json.src/types/i18n/index.tsprovidesTranslationKeyand runtime guards for dynamic input.- Sidebar/navigation/recents label keys are typed and validated.
Still in progress:
- Continue reducing generic
t(string)usage in other feature areas. - Prefer
TranslationKeyfor shared contracts that carry i18n keys.
Runtime loading model
src/i18n/index.tsuses lazy locale loading.enis bundled inbaseResources.- Other locales are loaded through
localeLoadersandensureLocaleLoaded()before language change. - Locale scripts (
generate/delete/cleanup) patchlocaleLoadersentries insrc/i18n/index.ts.
Where keys are typically used
- Navigation labels:
navigation.*and route definitions withlabelKey - Shared/common buttons and short labels:
common.* - Page copy by domain:
pages.domain.<area>.<page>.* - Backend error-code mapping:
errors.codes.*
If context is unclear, search for the key before editing:
rg "<dot.path.key>" apps/web/src apps/web/locales
Agent checklist before finishing
- Keys unchanged and complete relative to
en.json - No broken placeholders/interpolation tokens
- Preserve policy respected (only true English-locked terms excluded)
- Translation reads naturally in target language
- Relevant locale validation/parity checks run (or explicitly noted if skipped)