Skip to content

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.json is 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.*)

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
  • 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.json only (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 cache used as a feature label), treat it as translatable unless product explicitly says otherwise.
  • Prefer key-based exclusions over broad value-based exclusions when possible.
  1. Use generation/sync scripts to keep key parity and bootstrap text.
  2. Post-edit target locale for meaning and fluency (manual or AI-assisted).
  3. Re-check for structural safety:
    • JSON validity
    • key-set parity with en.json
    • placeholder integrity
  4. Spot-check in UI pages where the text appears.

Useful commands (from apps/web):

  • pnpm locales:generate
  • pnpm locales:fill -- --lang <code>
  • pnpm locales:sync
  • pnpm locales:validate
  • pnpm locales:validate:quality
  • pnpm locales:ci — single non-interactive gate (JSON, missing keys vs en.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:

text
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 &#123;&#123;id&#125;&#125;, &#123;&#123;count&#125;&#125;, 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 --all for 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.ts provides i18next declaration merging from locales/en.json.
  • src/types/i18n/index.ts provides TranslationKey and 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 TranslationKey for shared contracts that carry i18n keys.

Runtime loading model

  • src/i18n/index.ts uses lazy locale loading.
  • en is bundled in baseResources.
  • Other locales are loaded through localeLoaders and ensureLocaleLoaded() before language change.
  • Locale scripts (generate/delete/cleanup) patch localeLoaders entries in src/i18n/index.ts.

Where keys are typically used

  • Navigation labels: navigation.* and route definitions with labelKey
  • 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)