← All articles

How to Compare Translation and Localization Files Across Versions

Your app ships in six languages. A new feature adds a dozen strings to the English locale file. Weeks later, someone notices the German version still shows raw string keys instead of translated text in that feature — the translation file simply never caught up, and nothing in the build process caught it.

The two different comparisons that both matter

Localization has two distinct diffing problems, and they need different approaches:

  1. Structural comparison — does the German locale file have the same set of keys as the English one? This has nothing to do with actual language and everything to do with whether translation kept pace with development.
  2. Content comparison — for keys that exist in both, did the translated text actually change between two releases (a wording update, a re-translation), or did it stay stale while the English source moved on?

Missing the first kind means users see literal checkout.confirm_button instead of translated text. Missing the second means a translation slowly drifts out of sync with what the English version actually says, even though nothing is technically “missing.”

Finding missing or extra keys (structural diff)

For structured formats (JSON, YAML), the fastest check is comparing the key structure, ignoring the values entirely:

# Extract just the keys, sorted, from a nested JSON locale file
jq -r 'paths(scalars) | join(".")' en.json | sort > en_keys.txt
jq -r 'paths(scalars) | join(".")' de.json | sort > de_keys.txt

Then diff the two key lists directly in DiffALL’s text comparison tool:

  • Keys in en_keys.txt but not de_keys.txt → missing translations. These are the raw-key bugs users will actually see.
  • Keys in de_keys.txt but not en_keys.txt → orphaned strings, likely left over from a removed feature. Safe to clean up, but worth confirming before deleting.

For .po files (gettext), the equivalent is comparing the list of msgid entries between two locale files the same way.

Finding stale translations (content diff)

Structural comparison tells you a key exists in both files — it doesn’t tell you whether the English source text changed since that translation was done. For this, compare the English source file across two versions (the release the translation was based on, and the current release):

  1. Extract the same key-sorted format from both English versions (old and new).
  2. Diff them the same way as above.
  3. Any key whose English value changed is a candidate for re-translation — even though the translated file itself never technically went “missing” a key, the meaning it’s translating may now be wrong.

This is the check most localization workflows skip, because it doesn’t produce an obvious error (nothing crashes, nothing shows a raw key) — it just produces a translation that’s quietly answering an outdated version of the question.

A practical example

en.json (v1): "cart.empty_message": "Your cart is empty"
en.json (v2): "cart.empty_message": "Your cart is empty. Browse our new arrivals!"
de.json:      "cart.empty_message": "Ihr Warenkorb ist leer"

The German key exists and technically “matches” structurally — but it’s now translating v1’s text while the English source has moved to v2. A structural diff alone says everything is fine. Only comparing the English source across versions reveals that this specific string needs a translator’s attention.

Handling pluralization and variable placeholders

Locale files often carry placeholders ({count}, {name}) or plural forms (one/other/few). When comparing, check that:

  • Placeholder names match between source and translation — a translated string missing {count} will either crash the render or silently show a broken message, depending on the framework.
  • All required plural forms are present — some languages need more plural categories than English (one/two/few/many/other); a translation file that only fills in one/other for a language that needs more will fall back incorrectly for those cases.

A text diff surfaces a missing or renamed placeholder immediately as a highlighted difference in that line — much faster than finding it via a runtime crash report.

Checklist

  • [ ] Compare key structure (not values) first, in both directions, to catch missing and orphaned keys.
  • [ ] Separately diff the English source across versions to find translations that are structurally complete but semantically stale.
  • [ ] Check that placeholders ({name}, {count}) match exactly between source and translated strings.
  • [ ] Confirm all required plural forms exist for languages that need more than English’s two.
  • [ ] Re-run the structural check as part of your release process, not just once during initial setup.

A locale file with every key present can still be wrong if the source text moved on without it. Diff the key lists and the source text separately — the two checks catch different bugs.

Stop hunting for differences by hand. DiffALL spots every change between any two files — automatically.

Compare your files — free