import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; /// The messages of the `.arb` file [name] in `lib/l10n/`, without metadata. Map _messages(String name) => { for (final MapEntry(:key, :value) in (jsonDecode( File('lib/l10n/$name').readAsStringSync(), ) as Map).entries) if (!key.startsWith('@')) key: value! as String, }; /// The names of the placeholders in [message], with plural and select /// arguments; the words of their branches don't count. (gen-l10n reads /// quotes as text here: `use-escaping` is off.) Set _placeholders(String message) { final names = {}; // Even depths hold text and placeholders, odd ones a plural's branches. var depth = 0; for (var i = 0; i < message.length; i++) { switch (message[i]) { case '{': final name = RegExp(r'\{\s*(\w+)\s*[,}]').matchAsPrefix(message, i); if (name != null && depth.isEven) { names.add(name.group(1)!); } depth++; case '}': depth--; } } return names; } void main() { final template = _messages('app_en.arb'); test('German has every message, with the same placeholders', () { final german = _messages('app_de.arb'); expect( template.keys.where((key) => !german.containsKey(key)), isEmpty, reason: 'untranslated', ); expect( german.keys.where((key) => !template.containsKey(key)), isEmpty, reason: 'not in the template', ); for (final MapEntry(:key, :value) in german.entries) { expect(_placeholders(value), _placeholders(template[key]!), reason: key); } }); test("the English variants override the template's messages only, with " 'the same placeholders', () { for (final variant in ['app_en_GB.arb', 'app_en_US.arb']) { for (final MapEntry(:key, :value) in _messages(variant).entries) { expect(template, contains(key), reason: '$variant: $key'); expect( _placeholders(value), _placeholders(template[key]!), reason: '$variant: $key', ); } } }); test('a placeholder is found also inside a plural', () { expect( _placeholders('{count, plural, =1{1 card} other{{count} cards in {t}}}'), {'count', 't'}, ); expect(_placeholders("Couldn't open {name}"), {'name'}); }); }