Aşağıdaki tüm örnekler Flutter 3.47.5 ve flutter gen-l10n ile, bir app_en.arb şablonu ve bir app_fr.arb çevirisi kullanılarak test edildi.
Aceleniz varsa her dilin çoğul bloğunu doğrudan tarayıcınızda oluşturun. Araç ücretsizdir, kayıt istemez ve hiçbir şey yüklemez.
gen-l10n'in gerçekten engelledikleri
ICU sözdizimi yalnızca şablonda değil, her dosyada kontrol edilir. other dalı olmayan bir çoğul, dosya adı ve hatanın konumuyla birlikte üretimi durdurur:
[app_fr.arb:itemsOne] ICU Syntax Error: Plural expressions must have an "other" case.
{count, plural, one{{count} article}}
^
Kapatılmamış bir süslü parantezde de durum aynı. Bu hataları hemen görürsünüz; aşağıdakiler ise sessizce geçer.
Atlanan bir placeholder uyarı vermeden geçer
İngilizce şablon bir parametre tanımlıyor:
"welcomeUser": "Welcome back, {name}!",
"@welcomeUser": { "placeholders": { "name": { "type": "String" } } }
Fransızca çeviri ise onu kaybetmiş: "welcomeUser": "Bon retour parmi nous !". Üretim başarılı olur, analyzer sessiz kalır ve üretilen metot argümanını basitçe yok sayar:
@override
String welcomeUser(String name) {
return 'Bon retour parmi nous !';
}
Hiçbir şey çökmez. Uygulamayı Fransızca kullananlar adlarını hiç görmez. Bazen bu bilinçli bir tercihtir (cümle onsuz daha iyi okunur), çoğu zaman ise hatadır ve hiçbir standart araç ikisini ayırt etmez. Her çevirinin placeholder'larını şablonunkilerle karşılaştırmanız gerekir; bunun için aşağıda bir script var.
Yeniden adlandırılan bir placeholder tüm locale'lerin API'sini değiştirir
Bu daha tuhaf bir durum. Çeviri, şablonda olmayan bir ad kullanıyor; diyelim ki {count} yerine {nombre}:
"reminder": "Il reste {nombre} articles dans {listName}."
gen-l10n yine hiçbir şey bildirmez. nombre'yi soyut sınıfa, dolayısıyla tüm locale'lere fazladan bir parametre olarak ekler:
String reminder(String listName, int count, Object nombre);
Hata daha sonra, mevcut her çağrı derlenirken ortaya çıkar: Too few positional arguments: 3 required, 2 given. Hata, sorunun kaynağı olan ARB dosyasını değil, Dart kodunuzu gösterir. Bir yerelleştirme metoduna aniden bir Object parametresi eklenmişse, çevirilerinizde o adı arayın.
Fransızcada one sıfırı da kapsar
one, few, many… kategorileri her dilin CLDR kurallarını izler. İngilizcede ve Türkçede one yalnızca 1 demektir. Fransızcada 0 ve 1 demektir. Şu mesajla:
"itemsOne": "{count, plural, one{{count} article} other{{count} articles}}"
| count | İngilizce | Fransızca |
|---|---|---|
| 0 | 0 items | 0 article |
| 1 | 1 item | 1 article |
| 2 | 2 items | 2 articles |
"0 article" doğru bir Fransızcadır, yani burada sorun yok. Tuzak başka yerde: gen-l10n =0 ve =1'i Intl.pluralLogic'in zero ve one argümanlarına dönüştürür. Dolayısıyla bir =1 dalı one kategorisi olur ve Fransızcada bu kategori sıfır için de geçerlidir:
"exactOne": "{count, plural, =1{1 article restant} other{{count} articles restants}}"
count = 0 olduğunda İngilizce "0 items left" gösterirken Fransızca "1 article restant" (bir öğe kaldı) gösterir. Metin yanlış, ama hiçbir araç uyarı vermez. Çözüm: bir =0 dalı ekleyin ya da gösterilen sayının her zaman doğru olması için one{{count} article restant} yazın. one'ın 1'den fazlasını kapsadığı her dilde aynı sorun vardır.
Kesme işaretleri ve use-escaping
Varsayılan olarak düz kesme işareti sıradan bir karakterdir: "Don't forget the milk" çalışır. l10n.yaml içinde use-escaping: true'yu açarsanız (süslü parantezleri olduğu gibi yazabilmek için), kesme işareti ICU kaçış karakteri olur ve tek başına bir kesme işareti üretimi durdurur:
[app_en.arb:apostrophe] ICU Lexing Error: Unmatched single quotes.
Don't forget the milk
^
Bu durumda onu ikilemeniz (Don''t) ya da bundan etkilenmeyen tipografik kesme işaretini ’ kullanmanız gerekir. Mevcut bir projede bu seçeneği açmak her dili yeniden gözden geçirmek demektir; Türkçede ekleri ayıran kesme işaretleri (iOS'ta, Flutter'ın) de bu kurala takılır.
CI'a eklemeye değer bir kontrol
Hiç uyarı vermeyen durum atlanan placeholder'dır. Bu script şablonda tanımlanan placeholder'ları okur ve her çevirinin bunları hâlâ kullandığını kontrol eder:
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>;
Bu sayfadaki örnekte lib/l10n/app_fr.arb · welcomeUser: {name} missing yazdırır ve hatayla çıkar. Sınırları: yalnızca @key.placeholders içinde tanımlanan placeholder'lara bakar ve bir çoğulda değişkenin her dalda değil, mesajın herhangi bir yerinde geçtiğini kontrol eder. Yukarıdaki iki çoğul tuzağını yakalamanın en iyi yolu, mesajlarınızı 0, 1 ve 2 ile çağıran bir testtir.