赶时间?直接在浏览器中检查你的 ARB 文件:免费,无需注册,不上传任何内容。
回顾:ARB 文件的组织方式
使用 flutter gen-l10n 时,每种语言都有自己的文件(app_en.arb、app_fr.arb 等)。由 l10n.yaml 中 template-arb-file 指定的模板文件是参照:它包含每个键及其 @key 元数据(描述、占位符)。
{
"cartCount": "{count, plural, =0{Empty cart} =1{1 item} other{{count} items}}",
"@cartCount": {
"placeholders": { "count": { "type": "int" } }
}
}
1. 让 Flutter 列出未翻译的消息
在 l10n.yaml 中添加 untranslated-messages-file 选项:
arb-dir: lib/l10n
template-arb-file: app_en.arb
untranslated-messages-file: untranslated.json
每次生成时,Flutter 都会把每种语言缺少的模板键写入该文件。这是最容易设置的检查;发布前请确认该文件为空(或只包含 {})。
2. 用一个小 Dart 脚本比较键
如果想在 CI 中做一个失败时信息清晰的检查,一个脚本就够了:
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();
以 @ 开头的键是元数据,因此不参与比较。
3. 检查占位符
键在所有语言中都存在,也可能出问题:如果法语译文使用 {nombre},而模板声明的是 {count},gen-l10n 会在每个语言中把 nombre 添加为额外参数,现有的调用处就无法再编译。反过来,译文直接丢掉占位符时,则不会有任何警告(详见 ICU 复数与占位符指南)。对每个键,把花括号中的名称与 @key.placeholders 中声明的名称比较。注意 ICU 消息(plural、select):other 之类的关键字不是占位符。
4. 同时留意空值
有些翻译流程会先用空值创建键,等翻译后再填写。它不会显示为“缺失”,所以要再加一项检查,在除模板以外的每种语言中标记空字符串。
发布前检查清单
- 未翻译消息文件为空。
- 每种语言的键都与模板相同。
- 占位符逐键一致。
- 模板以外没有空值。
- 已在文字最长的语言(例如德语)中实际查看过长消息的显示效果。