Settings offers English (UK), English (US) and Deutsch after the device's language, each named in its own language. app_en.arb, the template, is now British English (colour, centre) and the fallback for any language the app doesn't speak; app_en_US.arb overrides the five messages American English spells differently, and app_en_GB.arb is empty, there so that Material's texts and dates are British too. The German translation says "du" and keeps one set of terms throughout (Lernset, Karteikarte, Lernpfad; Anki's own Stapel and Unterstapel in the Anki import). arb_files_test checks that German has every message of the template and that every translation keeps its placeholders. resolveAppLocale picks the first language the device prefers that the app speaks, in its country's variant where there is one; other English is British, and a country alone never picks a language (Spanish in the United States isn't American English), which Flutter's own resolution would do. Both MaterialApps take material_ui's GlobalMaterialLocalizations delegates: gen-l10n's list brings flutter_localizations', which serve only package:flutter/material.dart, so material_ui's widgets found no Material texts in German and threw. The editor's own text blocks show our placeholders while empty (list item, to-do, quote, heading, and the desktop's / hint): the editor's own follow a locale of their own, process-wide, in few languages. The last texts written into widgets (the person's data failing to open, the cards failing to read, the About dialog) moved into the arb files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
78 lines
2.3 KiB
Dart
78 lines
2.3 KiB
Dart
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<String, String> _messages(String name) => {
|
|
for (final MapEntry(:key, :value) in (jsonDecode(
|
|
File('lib/l10n/$name').readAsStringSync(),
|
|
) as Map<String, Object?>).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<String> _placeholders(String message) {
|
|
final names = <String>{};
|
|
// 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'});
|
|
});
|
|
}
|