本地化 (i18n)
Finch 内置了支持多语言的 i18n 系统。语言字符串可以存储在 JSON 文件中,也可以直接在 Dart 中定义。
配置
在 FinchConfigs 中设置语言选项:
FinchConfigs configs = FinchConfigs(
// 存放语言 JSON 文件的目录路径
languagePath: pathTo(env.get('LANGUAGE_PATH', './lib/languages')),
// 使用 JSON 文件(LanguageSource.json)还是 Dart map(LanguageSource.dart)
languageSource: LanguageSource.json,
// 如果使用 LanguageSource.dart,请在此处提供 map:
// dartLanguages: languageDart,
);
选项 1:JSON 文件
在你的 languagePath 目录中为每种语言创建一个 JSON 文件,文件名即为语言代码:
lib/languages/en.json
{
"dir": "ltr",
"logo.title": "My App",
"greeting": "Hello, World!",
"example.params": "My name is {name}, my age is {age}",
"example.params.arr": "My name is {0}, my age is {1}"
}
lib/languages/fa.json
{
"dir": "rtl",
"logo.title": "برنامه من",
"greeting": "سلام دنیا!",
"example.params": "نام من {name} است، سن من {age} سال است"
}
这里的
dir键(ltr/rtl)和其他键一样,只是一个普通的翻译键——应该在模板中使用{{ $t('dir') }}来读取它(正如 Templates 示例中所做的那样),而不是{{ $e.dir }}。$e.dir查找的是另一个命名方式不同的键——相关区别请参阅 Template Events。
选项 2:Dart Map
将语言定义为 Dart 的 Map<String, Map<String, String>>,并让 FinchConfigs 指向它:
// lib/languages/language_dart.g.dart(或任意文件名)
const languageDart = <String, Map<String, String>>{
'en': {
'dir': 'ltr',
'logo.title': 'My App',
'greeting': 'Hello, World!',
'example.params': 'My name is {name}, my age is {age}',
},
'fa': {
'dir': 'rtl',
'logo.title': 'برنامه من',
'greeting': 'سلام دنیا!',
'example.params': 'نام من {name} است، سن من {age} سال است',
},
};
FinchConfigs configs = FinchConfigs(
languageSource: LanguageSource.dart,
dartLanguages: languageDart,
);
finch serve监听器可以自动将 JSON 文件转换为 Dart map。生成的文件会写入language_dart.g.dart。
Translating in Controllers
对任意键字符串使用 .tr 扩展。.write() 会渲染出最终的翻译字符串:
// 简单翻译
String text = 'logo.title'.tr.write();
// 使用命名参数
String text = 'example.params'.tr.write({'name': 'Alice', 'age': 30});
// 使用位置参数(数组)—— 并不存在单纯的 `.writeArr()`;
// 需要将当前语言显式传给 `writeByLangArr`
String text = 'example.params.arr'.tr.writeByLangArr(rq.getLanguage(), ['Alice', 30]);
将翻译后的字符串传递给模板:
rq.addParams({
'greeting': 'greeting'.tr.write(),
'userLine': 'example.params'.tr.write({'name': 'Alice', 'age': 30}),
});
return rq.renderView(path: 'pages/home');
在模板中翻译
使用 {{ $t('key') }} 进行内联翻译:
<h1>{{ $t('logo.title') }}</h1>
<p>{{ $t('example.params', {'name': user.name, 'age': user.age}) }}</p>
<p>{{ $t('example.params.arr', ['Alice', 30]) }}</p>
语言切换
语言按以下顺序确定:
- 第一个路径段,如果它匹配某个已知的语言代码(例如
/fa/home) - API 接口上的
lang数据字段 languagecookielanguage会话(session)键- 配置中的默认值
无论最终解析出的值是什么,都会始终根据 FinchConfigs.languages 进行校验;如果它不属于已配置的语言之一(例如无效或过期的 cookie),Finch 会回退使用 'en'。
在控制器中修改语言:
rq.changeLanguege('fa');
在模板中生成一个用于切换语言的 URL:
<a href="{{ $e.urlToLanguage('fa') }}">فارسی</a>
<a href="{{ $e.urlToLanguage('en') }}">English</a>
模板中的可用语言
{% for lang in $e.langs %}
<a href="{{ $e.urlToLanguage(lang.code) }}">{{ lang.label }}</a>
{% endfor %}
每个条目的结构为:{ code: 'en', label: 'English', contry: 'United States' }——注意,除非你已经添加了 Template Events 中所述的 language.<code>_label/language.<code>_contry 键,否则 label/contry 字段会是空的或无法解析。
TString —— 翻译对象
TString 会包装一个键,让你可以延迟进行翻译:
var ts = TString('example.params');
rq.addParam('exampleTString', ts.write());
// 或者使用 .tr 简写形式:
rq.addParam('examplePathString', 'example.path'.tr.write());
位置(数组)参数
翻译字符串除了可以使用命名的 {name} 占位符外,还可以使用位置占位符 {0}、{1}……:
{
"example.params.arr": "My name is {0}, my age is {1}"
}
在模板中,通过给 $t 传入一个列表参数来填充它们:
<p>{{ $t('example.params.arr', ['Alexandre', 30]) }}</p>
或者在 Dart 中使用 writeByLangArr(并不存在单纯的 .writeArr() 简写形式——参见上文的 Translating in Controllers):
rq.renderString(text: 'example.params.arr'.tr.writeByLangArr(rq.getLanguage(), ['Alexandre', 30]));