Finding missing keys in a Flutter app's ARB files
Updated · GLabs325
A key added in English and forgotten in German, a placeholder renamed in only one language: these mistakes compile fine and end up in front of your users. Here is how to catch them before release.
Quick recap: how ARB files are organized
With flutter gen-l10n, each language has its own file (app_en.arb, app_fr.arb…). The template file, set by template-arb-file in l10n.yaml, is the reference: it holds every key and its @key metadata (description, placeholders).
{
"cartCount": "{count, plural, =0{Empty cart} =1{1 item} other{{count} items}}",
"@cartCount": {
"placeholders": { "count": { "type": "int" } }
}
}
1. Let Flutter list untranslated messages
Add the untranslated-messages-file option to l10n.yaml:
arb-dir: lib/l10n
template-arb-file: app_en.arb
untranslated-messages-file: untranslated.json
On every generation, Flutter writes the template keys missing from each language into that file. It is the simplest check to set up; make sure the file is empty (or only contains {}) before you ship.
2. Compare keys with a small Dart script
For a CI check that fails clearly, a script is enough:
import 'dart:convert';
import 'dart:io';
void main() {
final dir = Directory('lib/l10n');
final template = _keys(File('lib/l10n/app_en.arb'));
var failed = false;
for (final file in dir.listSync().whereType<File>()) {
if (!file.path.endsWith('.arb')) continue;
final missing = template.difference(_keys(file));
if (missing.isNotEmpty) {
failed = true;
print('${file.path}: ${missing.join(', ')}');
}
}
if (failed) exit(1);
}
Set<String> _keys(File f) => (jsonDecode(f.readAsStringSync()) as Map<String, dynamic>)
.keys
.where((k) => !k.startsWith('@'))
.toSet();
Keys starting with @ are metadata, so they are excluded from the comparison.
3. Check placeholders
A key present everywhere can still break: if the French translation uses {nombre} while the template declares {count}, gen-l10n adds nombre as an extra parameter in every locale, and existing call sites stop compiling. Conversely, a translation that simply drops a placeholder goes through without any warning (details in the ICU plurals and placeholders guide). For each key, compare the names in braces with those declared in @key.placeholders. Watch out for ICU messages (plural, select): keywords such as other are not placeholders.
4. Watch for empty values too
Some translation pipelines create the key with an empty value until it is translated. It does not show up as "missing", so add a check that flags empty strings in every language except the template.
Pre-release checklist
- The untranslated messages file is empty.
- Every language has the same keys as the template.
- Placeholders match, key by key.
- No empty values outside the template.
- Long messages have been checked on screen in the wordiest languages (German, for example).
Frequently asked questions
Does Flutter report missing translations?
Yes, if you add untranslated-messages-file to l10n.yaml: flutter gen-l10n lists the template keys missing from each language there. Without it, you only get a summary warning.
Do I need to copy @key metadata into every language?
No. Metadata (description, placeholders) is read from the template file; the other files only need the translations.
How do I avoid misnamed placeholders?
For each key, compare the names in braces in the translation with those declared in the template, and fail CI when they differ.