Alle Beispiele unten wurden mit Flutter 3.47.5 und flutter gen-l10n getestet, mit einem Template app_en.arb und einer Übersetzung app_fr.arb. Für eine deutsche Datei app_de.arb gilt dasselbe, bis auf die Besonderheit der Kategorie one weiter unten.
Wenn es schnell gehen soll: Erzeugen Sie den Plural-Block jeder Sprache direkt im Browser, kostenlos, ohne Anmeldung und ohne Upload.
Was gen-l10n tatsächlich blockiert
Die ICU-Syntax wird in jeder Datei geprüft, nicht nur im Template. Eine Pluralform ohne Zweig other bricht die Generierung ab, mit Dateiname und Position des Fehlers:
[app_fr.arb:itemsOne] ICU Syntax Error: Plural expressions must have an "other" case.
{count, plural, one{{count} article}}
^
Dasselbe gilt für eine nicht geschlossene Klammer. Diese Fehler sehen Sie sofort, die folgenden nicht.
Ein verlorener Platzhalter geht kommentarlos durch
Das englische Template deklariert einen Parameter:
"welcomeUser": "Welcome back, {name}!",
"@welcomeUser": { "placeholders": { "name": { "type": "String" } } }
Die französische Übersetzung hat ihn verloren: "welcomeUser": "Bon retour parmi nous !". Die Generierung gelingt, der Analyzer schweigt, und die generierte Methode ignoriert ihr Argument einfach:
@override
String welcomeUser(String name) {
return 'Bon retour parmi nous !';
}
Nichts stürzt ab. Französischsprachige Nutzer sehen nur nie ihren Namen. Manchmal ist das gewollt (der Satz klingt ohne besser), meistens ist es ein Fehler, und kein Standardwerkzeug unterscheidet beides. Sie müssen die Platzhalter jeder Übersetzung mit denen des Templates vergleichen; ein Skript dafür finden Sie weiter unten.
Ein umbenannter Platzhalter ändert die API jeder Locale
Dieser Fall ist seltsamer. Die Übersetzung verwendet einen Namen, den das Template nicht hat, etwa {nombre} statt {count}:
"reminder": "Il reste {nombre} articles dans {listName}."
gen-l10n meldet weiterhin nichts. Es fügt nombre als zusätzlichen Parameter hinzu, in der abstrakten Klasse, also in jeder Locale:
String reminder(String listName, int count, Object nombre);
Der Fehler zeigt sich später, wenn die bestehenden Aufrufe kompilieren: Too few positional arguments: 3 required, 2 given. Er zeigt auf Ihren Dart-Code, nicht auf die ARB-Datei, die ihn verursacht hat. Wenn eine Lokalisierungsmethode plötzlich einen Object-Parameter bekommt, suchen Sie diesen Namen in Ihren Übersetzungen.
Im Französischen umfasst one auch die Null
Die Kategorien one, few, many… folgen den CLDR-Regeln jeder Sprache. Im Englischen und im Deutschen bedeutet one nur 1. Im Französischen bedeutet es 0 und 1. Mit dieser Nachricht:
"itemsOne": "{count, plural, one{{count} article} other{{count} articles}}"
| count | Englisch | Französisch |
|---|---|---|
| 0 | 0 items | 0 article |
| 1 | 1 item | 1 article |
| 2 | 2 items | 2 articles |
„0 article“ ist korrektes Französisch, hier also kein Problem. Die Falle liegt woanders: gen-l10n wandelt =0 und =1 in die Argumente zero und one von Intl.pluralLogic um. Ein Zweig =1 wird also zur Kategorie one, und im Französischen gilt diese Kategorie auch für null:
"exactOne": "{count, plural, =1{1 article restant} other{{count} articles restants}}"
Mit count = 0 zeigt Englisch „0 items left“ und Französisch „1 article restant“ (1 Artikel übrig). Der Text ist falsch, und nichts meldet es. Die Lösung: Fügen Sie einen Zweig =0 hinzu, oder schreiben Sie one{{count} article restant}, damit die angezeigte Zahl immer stimmt. Jede Sprache, in der one mehr als nur 1 abdeckt, hat dasselbe Problem; im Deutschen tritt es nicht auf.
Apostrophe und use-escaping
Standardmäßig ist ein gerader Apostroph einfach ein Zeichen: "Don't forget the milk" funktioniert. Wenn Sie use-escaping: true in l10n.yaml aktivieren (um literale geschweifte Klammern zu schreiben), wird er zum ICU-Escape-Zeichen, und ein einzelner Apostroph bricht die Generierung ab:
[app_en.arb:apostrophe] ICU Lexing Error: Unmatched single quotes.
Don't forget the milk
^
Dann müssen Sie ihn verdoppeln (Don''t) oder den typografischen Apostroph ’ verwenden, der nicht betroffen ist. In einem bestehenden Projekt heißt das Umschalten dieser Option, jede Sprache noch einmal durchzugehen; englische Kurzformen, französische Elisionen und deutsche Formen wie „geht's“ halten Sie auf Trab.
Eine Prüfung, die in die CI gehört
Am unauffälligsten ist der verlorene Platzhalter. Dieses Skript liest die im Template deklarierten Platzhalter und prüft, dass jede Übersetzung sie noch verwendet:
import 'dart:convert';
import 'dart:io';
void main() {
final template = _read('lib/l10n/app_en.arb');
var errors = 0;
for (final file in Directory('lib/l10n').listSync().whereType<File>()) {
if (!file.path.endsWith('.arb') || file.path.endsWith('app_en.arb')) continue;
final translation = _read(file.path);
for (final key in template.keys.where((k) => !k.startsWith('@'))) {
final text = translation[key];
if (text is! String) continue; // missing key: see untranslated-messages-file
final meta = template['@$key'] as Map<String, dynamic>?;
final declared = (meta?['placeholders'] as Map<String, dynamic>?)?.keys ?? const <String>[];
for (final name in declared) {
if (!RegExp(r'\{\s*' + name + r'\s*[,}]').hasMatch(text)) {
stderr.writeln('${file.path} · $key: {$name} missing');
errors++;
}
}
}
}
if (errors > 0) exit(1);
}
Map<String, dynamic> _read(String path) => jsonDecode(File(path).readAsStringSync()) as Map<String, dynamic>;
Für das Beispiel dieser Seite gibt es lib/l10n/app_fr.arb · welcomeUser: {name} missing aus und endet mit einem Fehler. Seine Grenzen: Es betrachtet nur die in @key.placeholders deklarierten Platzhalter, und bei einer Pluralform prüft es, dass die Variable irgendwo in der Nachricht vorkommt, nicht in jedem Zweig. Die beiden Plural-Fallen oben fangen Sie am besten mit einem Test ab, der Ihre Nachrichten mit 0, 1 und 2 aufruft.