How to Compare Two ARB Files and Find Missing Translations in Flutter
Quick answer: To compare two .arb files, diff their keys to find entries that exist in your template (e.g. app_en.arb) but are missing in a translation (e.g. app_es.arb), then check that every {placeholder} and plural/select branch matches across both. The fastest way is the free ARB Diff tool — paste both files and it instantly lists missing keys, extra keys, and placeholder mismatches. This guide also shows how to do it manually and in CI.
⚡ Compare ARB files in seconds (no signup)
| ✅ Find keys missing in the target file | ✅ Spot extra/orphaned keys |
✅ Detect {placeholder} mismatches |
✅ Catch plural/select differences |
| ✅ Compare metadata & descriptions | ✅ Runs 100% in your browser |
Why comparing ARB files matters
When you maintain a Flutter app in multiple languages, every locale has its own .arb file. Your English file is the template (the source of truth). Each translation file must contain the same keys with the same placeholders — otherwise:
- A missing key throws
Null check operator used on a null valueor shows a blank string at runtime. - A mismatched placeholder (e.g.
{userName}translated as{user}) breaks ICU parsing andflutter gen-l10nfails. - An extra key in a translation is dead weight that silently rots.
Catching these before you ship is the entire job of an ARB diff.
What "different" means between two ARB files
A proper comparison checks four things, not just text:
| Difference type | Example | Why it breaks |
|---|---|---|
| Missing in target | checkout exists in en but not es |
Untranslated string at runtime |
| Missing in source | oldKey in es but not in en |
Orphaned/dead key |
| Placeholder mismatch | en: "Hi {name}" vs es: "Hola {nombre}" |
ICU parse error / runtime crash |
| Plural branch mismatch | en has =0/=1/other, es only other |
Wrong text for some counts |
Option 1 — Compare visually (fastest)
- Open the ARB Diff tool.
- Paste your template file (e.g.
app_en.arb) on the left and a translation (e.g.app_es.arb) on the right — or drag-and-drop both. - Read the categorized results: missing in target, missing in source, placeholder mismatch, value different.
- Filter to "missing in target" to get your exact translation to-do list.
Because it runs entirely in the browser, your strings never leave your machine.
Option 2 — Compare on the command line
If you just need the key difference, jq gets you a long way:
# Keys in the English template that are missing in the Spanish file
comm -23 \
<(jq -r 'keys[] | select(startswith("@") | not)' app_en.arb | sort) \
<(jq -r 'keys[] | select(startswith("@") | not)' app_es.arb | sort)
This lists every translatable key (ignoring @-metadata) present in app_en.arb but absent from app_es.arb. It won't catch placeholder mismatches, though — for that you need to parse the ICU messages.
Option 3 — Catch it automatically with a Dart script
You can fail your CI build when a locale drifts from the template:
import 'dart:convert';
import 'dart:io';
/// Returns keys present in [template] but missing in [target].
Set<String> missingKeys(File template, File target) {
final t = json.decode(template.readAsStringSync()) as Map<String, dynamic>;
final g = json.decode(target.readAsStringSync()) as Map<String, dynamic>;
bool isKey(String k) => !k.startsWith('@');
final templateKeys = t.keys.where(isKey).toSet();
final targetKeys = g.keys.where(isKey).toSet();
return templateKeys.difference(targetKeys);
}
void main() {
final missing = missingKeys(File('lib/l10n/app_en.arb'),
File('lib/l10n/app_es.arb'));
if (missing.isNotEmpty) {
stderr.writeln('❌ Missing ${missing.length} translations: $missing');
exit(1);
}
print('✅ All keys translated');
}
Run it in your pipeline so a pull request can't merge with untranslated keys.
Placeholder mismatches: the silent killer
The most common bug isn't a missing key — it's a placeholder that was "translated":
// app_en.arb ✅
"greeting": "Welcome, {userName}!"
// app_es.arb ❌ placeholder renamed — gen-l10n will fail
"greeting": "¡Bienvenido, {nombreUsuario}!"
Placeholder names must be identical across all locales — only the surrounding text gets translated. The ARB Diff tool flags these automatically, which is hard to do with plain jq.
Frequently asked questions
How do I compare two ARB files online?
Open the ARB Diff tool, paste or drag your two .arb files into the left and right panels, and it instantly shows missing keys, extra keys, and placeholder mismatches. No signup and nothing is uploaded — it runs in your browser.
How do I find missing translations in Flutter?
Diff each translation file against your template .arb (usually app_en.arb). Any key in the template that's absent from the translation is a missing string. The ARB Diff tool lists these under "missing in target," or you can script it with jq / a Dart CI check.
Why does flutter gen-l10n fail after translating?
The most frequent cause is a renamed placeholder. Placeholder names like {userName} must be identical in every locale; only the surrounding text is translated. Comparing placeholders across files surfaces the mismatch.
Can I automate ARB comparison in CI?
Yes. Add a Dart or jq script that exits non-zero when a locale is missing keys, and run it in your CI pipeline so untranslated strings block the merge.
Keep your translations in sync automatically
Diffing files by hand works for two locales. Once you have a team and ten languages, FlutterLocalisation keeps every locale in sync, auto-fills missing keys with AI, validates placeholders on save, and pushes updates over-the-air. Start free →
Related tools & guides:
- ARB Diff tool — compare two files now
- ARB Editor & Validator — fix a single file
- l10n.yaml Generator — configure gen-l10n
- Flutter ARB Placeholders: Complete Guide