Treating localization files like code: checks on every change
A localization file is a build input in every sense that matters — it is parsed, it feeds strings into running code, and a malformed one can break a build or crash a screen at runtime. Most teams do not treat it that way. Source code gets a test suite and a required check before merge; the CSV or JSON sitting next to it gets opened, eyeballed, and trusted.
The fix is not exotic: run the same kind of automated check on localization files that you already run on code, on every change, before the file reaches a build.
What is worth checking
The useful checks are the ones that are both deterministic and cheap — no judgment call, no AI review, just a pass or fail a machine can compute in milliseconds per file.
- Parseability — the file is well-formed CSV/JSON/whatever format it claims to be, with no stray delimiters or unclosed structures
- Encoding — the file is saved in the encoding the pipeline expects; a file silently saved in the wrong encoding produces garbled characters that many tools happily pass through unflagged
- Placeholder parity — every placeholder token present in the source string ({0}, {playerName}, %s) also appears in the translation, and no new ones were invented
- Tag balance — markup or rich-text tags (<b>, <color=...>) open and close in matching pairs, so a translator's copy-paste error does not leave a dangling style tag on screen
- Duplicate keys — no key appears twice with two different values, since which one wins is often undefined behavior in the loading code
A concrete failure mode
A translator working in a spreadsheet renames one instance of a repeated placeholder while leaving the others untouched, or drops it entirely while adapting the sentence to read more naturally. The row still looks fine to a human skimming for meaning. At runtime, the string either shows the raw token literally, or the format call throws because an expected argument never lands.
source: "You found {0} gold and {1} gems."
broken: "{0}ゴールドとジェムを見つけた。" // {1} silently dropped
check: placeholder set mismatch — {1} missing in targetDeterministic, fast, and loud on failure
These checks only earn their keep if they are fast enough to run on every change without becoming a bottleneck, and loud enough that a failure cannot be missed or misread. A failure message that just says validation failed sends the reporter back to square one; a useful one names the exact key, the line, and what specifically is wrong — missing placeholder {1}, unclosed <color> tag, duplicate key ui.button.confirm — so the fix is obvious without opening a debugger.
The underlying goal is simple to state: a malformed localization file should never be able to reach players. That means the check has to run before the file is merged or exported, not after — catching the same problem in a build that already shipped is strictly more expensive than catching it in a pull request.
What this changes for the team
The biggest shift is not technical, it is about feedback latency. Without automated checks, a translator finds out their file has a problem when someone plays a build and notices — which can be days or weeks after the file was submitted, by which point the translator has moved on to other work and has to re-load context they have already lost.
With checks running on every change, the same translator finds out within minutes, while the file and its context are still fresh in their head. This turns localization QA from an occasional, expensive interruption into routine, low-cost feedback — the same shift that automated testing already made for source code.