Catching Missing Translation Keys and Interpolation Mismatches Before Users Do
The developer's locale never has missing keys, because the developer wrote the feature against it. Missing keys only appear in the locales added later, by translators, and they show up as raw key paths rendered to real users. Comparing locale files structurally, instead of eyeballing them, is how you find these before shipping.
Internationalization bugs are unique because they are invisible to the person who introduces them. You build a feature against your own locale, which has every key, and the feature works. The bug appears only in a locale that is missing the key, which is usually a locale you do not speak and do not test in. The result is a raw key path like settings.profile.title shown to a real user, or an interpolation that renders empty because a parameter was renamed.
Why missing keys are hard to catch in review
Code review catches missing keys only if the reviewer diffs the locale files, which most reviewers do not. The feature works in the development locale, the tests pass in the development locale, and the missing key is in a file the reviewer never opens. The bug ships, and the first sign of it is a user screenshot showing a raw key path where a translated string should be.
The structural problem is that locale files grow independently. The English file gets a new key with each feature. The translator adds the corresponding key to the other locales later, or misses it. There is no compile error when a key is missing, because locale lookups fall back to the key itself. The fallback is the bug.
Comparing locales structurally
The fix is to compare locale files as nested key structures, not as text. A text diff shows which lines changed, but a missing key in a nested object is not a line change, it is an absent line. A structural comparison flattens both files to key paths and reports which paths exist in one but not the other.
The comparison has to go both directions. English may have a key that Japanese lacks, which is the obvious missing-key bug. Japanese may have a key that English lacks, which is usually a stale key left over from a removed feature. The stale key is not user-facing, but it is dead weight, and it can hide a real bug if the key name collides with a new feature later.
Interpolation parameter mismatches
A translation string with interpolation, like Hello, {name}, expects a parameter called name. If the translator rewrites the string to Bonjour {prenom}, the parameter is now prenom, but the code still passes name. Depending on the i18n library, the result is an empty string, a literal {name}, or a runtime error.
This class of bug is harder to catch than missing keys because the key exists in both locales. A structural key comparison passes. The mismatch is only in the parameters inside the string value, which requires parsing the interpolation syntax of each string and comparing the parameter sets.
ICU MessageFormat makes this stricter, because it adds {count, plural, one {...} other {...}} constructs where the parameter name appears multiple times and the library validates the count. A translated plural that drops the count parameter, or renames it, breaks at runtime. Detecting this means parsing the ICU syntax, not just grepping for braces.
Nested objects versus flat keys
Locale files come in two shapes: nested objects, where settings.profile.title is settings: { profile: { title: ... } }, and flat keys, where the same string is a single key settings.profile.title. The comparison tool has to handle both, because projects mix them. A flat-key file compared against a nested file is a common source of false positives if the tool only handles one shape.
The deeper issue is key collision across the two shapes. A flat file with a key settings.profile and a nested file with settings: { profile: ... } look different in text but may resolve to the same lookup path depending on the library. Normalizing both to a canonical path list before comparing avoids this.
When to run the check
Run the comparison whenever a locale file changes, which in practice means on every pull request that touches translations. The check is fast, it is deterministic, and it catches a class of bug that is otherwise invisible until a user reports it. Running it in CI, not just locally, means a translator's missing key is caught before merge, not after release.
The comparison is also useful when adding a new locale. Pasting the new locale file and the reference locale file into the tool gives an immediate list of what the new locale is missing, which is the starting checklist for the translator.
What the comparison will not catch
A missing key is objective. A wrong translation is not. The comparison cannot tell you whether a translation is accurate, only whether the keys and parameters match. A locale with every key present and every parameter matched can still have a completely wrong translation, and that is a problem for a bilingual reviewer, not a diffing tool.
The comparison also cannot catch cultural issues, like a date format that is technically correct but reads oddly, or a string that is too long for the layout it appears in. These need human review against the actual UI. The tool catches the mechanical bugs, the missing keys and the parameter mismatches, so the human review can focus on the ones that require judgment.
The i18n tool on this site runs the structural comparison and the parameter check in the browser, so you can paste two locale files and get the diff without sending them anywhere. It is the check that should run before every translation merge.
Primary references
Standards and official documentation used to check the technical details in this guide.
Find missing keys and param mismatches locally
The i18n tool on this site compares locale JSON files, lists missing keys in each direction, and flags interpolation parameter mismatches. Runs in the browser, so your locale files stay on your machine.
Open the i18n toolRelated guides
Continue with practical guides from the same topic area.
What Actually Belongs in a Production Dockerfile for Node (and What Doesn't)
Most Node Dockerfiles in the wild copy node_modules into the image, run as root, and ship a 900 MB layer. A short guide to the few decisions that matter: base image, multi-stage builds, layer caching for dependencies, the NODE_ENV trap, and why your docker-compose should not mirror production.
Mock Data That Actually Exercises Your UI (Not Just Fills It)
Most mock data is ten copies of the same row with a different id. It fills the page and tests nothing. A guide to generating mock data that exercises layout edge cases, long names, missing fields, empty states, and the date and number formats that break formatting code, with field inference so a sample JSON becomes a realistic dataset in one step.
Converting cURL to Fetch, Axios, or Python Without Losing the Headers That Matter
cURL is the lingua franca of HTTP debugging, but pasting a cURL command into application code is a category error. A guide to what survives the conversion to fetch, Axios, and Python requests, what gets lost, and the headers and body encodings that quietly change behavior when you translate.