i18n_extension
此包仅支持 Dart 2.6.0。因此,如果您的 Flutter 处于稳定频道,您可能需要通过在控制台中输入以下命令将其切换到开发频道:flutter channel dev 然后 flutter doctor。
Flutter 的非样板化翻译和国际化 (i18n)
从一个包含一些文本的小部件开始
Text("Hello, how are you?")
只需在字符串后添加 .i18n 即可翻译它
Text("Hello, how are you?".i18n)
如果当前区域设置为 'pt_BR',则屏幕上的文本将为
"Olá, como vai você?",即上述文本的葡萄牙语翻译。
依此类推,支持您想要的任何其他区域设置。
您还可以根据修饰符提供不同的翻译,例如 plural 数量
print("There is 1 item".plural(0)); // Prints 'There are no items'
print("There is 1 item".plural(1)); // Prints 'There is 1 item'
print("There is 1 item".plural(2)); // Prints 'There are 2 items'
您可以根据任何条件发明自己的修饰符。
例如,某些语言对不同的性别有不同的翻译。
因此,您可以为 Gender 修饰符创建 gender 版本
print("There is a person".gender(Gender.male)); // Prints 'There is a man'
print("There is a person".gender(Gender.female)); // Prints 'There is a woman'
print("There is a person".gender(Gender.they)); // Prints 'There is a person'
看看它的效果
尝试运行 示例。

适用于简单或复杂的应用程序
我一直有兴趣创建能够减少样板代码的软件包。
例如,async_redux 是关于无样板的 Redux,
而 align_positioned 是关于使用更少的小部件创建布局。
这个包也旨在减少翻译的样板代码,
所以它所做的任何事情,您都可以使用普通的 Localizations.of(context) 来完成。
也就是说,这个包既适用于单人应用开发者,也适用于大公司团队。
它涵盖了您翻译工作的各个阶段
-
当您创建小部件时,它会让您轻松地定义哪些字符串需要
翻译,只需在字符串后添加.i18n。这些字符串称为“可翻译字符串”。 -
当您想开始翻译工作时,它可以自动列出
所有需要翻译的字符串。如果您遗漏了任何字符串,或者稍后添加了更多字符串
或修改了其中一些字符串,它会告知您更改了什么以及如何修复。 -
然后,您可以以非常易于使用的格式手动提供翻译。
-
或者,您可以轻松地将其与专业的翻译服务集成,从
任何您想要的格式导入或导出。
设置
将您的 widget 树包装在 I18n widget 中,放在 MaterialApp 的下方
import 'package:i18n_extension/i18n_widget.dart';
...
@override
Widget build(BuildContext context) {
return MaterialApp(
home: I18n(child: ...)
);
}
上面的代码将把您的字符串翻译成当前系统区域设置。
或者,您可以像这样用自己的区域设置覆盖它
I18n(
initialLocale: Locale("pt_br"),
child: ...
注意:切勿在声明 I18n widget 的同一个 widget 中放置可翻译字符串,
因为它们可能不会响应将来的区域设置更改。例如,这是错误的
Widget build(BuildContext context) {
return I18n(
child: Scaffold(
appBar: AppBar(title: Text("Hello there".i18n)),
body: MyScreen(),
);
}
您可以将可翻译字符串放在树中的任何 widget 中。
翻译 widget
当您创建一个包含可翻译字符串的 widget 时,
将此默认导入添加到 widget 的文件中
import 'package:i18n_extension/default.i18n.dart';
这将允许您在字符串后添加 .i18n 和 .plural(),但不会翻译任何内容。
当您准备好进行翻译时,必须创建一个 dart 文件来保存它们。
此文件可以有任何名称,但我建议您将其命名为与您的 widget 相同
并将后缀更改为 .i18n.dart。
例如,如果您的 widget 在文件 my_widget.dart 中,
翻译可以放在文件 my_widget.i18n.dart 中
然后,您必须删除之前的默认导入,而是导入您自己的翻译文件
import 'my_widget.i18n.dart';
您的翻译文件本身将是这样的
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static var t = Translations("en_us") +
{
"en_us": "Hello, how are you?",
"pt_br": "Olá, como vai você?",
"es": "¿Hola! Cómo estás?",
"fr": "Salut, comment ca va?",
"de": "Hallo, wie geht es dir?",
};
String get i18n => localize(this, t);
}
上面的示例显示了一个可翻译字符串,翻译成了美式英语、
巴西葡萄牙语以及通用西班牙语、法语和德语。
但是,您可以翻译任意数量的字符串,只需添加更多
翻译映射即可:
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static var t = Translations("en_us") +
{
"en_us": "Hello, how are you?",
"pt_br": "Olá, como vai você?",
} +
{
"en_us": "Hi",
"pt_br": "Olá",
} +
{
"en_us": "Goodbye",
"pt_br": "Adeus",
};
String get i18n => localize(this, t);
}
字符串本身就是翻译键
在 Translations() 构造函数中传递的区域设置被称为默认区域设置。
例如,在 Translations("en_us") 中,默认区域设置为 en_us。
widget 文件中的所有可翻译字符串都应使用该区域设置的语言。
字符串本身用作键来查找其他区域设置的翻译。
例如,在下面的 Text 中,"Hello, how are you?" 既是英语翻译,
也是用于查找其其他翻译的键
Text("Hello, how are you?".i18n)
如果在翻译映射中缺少任何翻译键,将使用该键本身,
因此文本仍将显示在屏幕上,未翻译。
如果找到翻译键,它将根据以下规则选择语言
-
它将使用与当前区域设置完全匹配的翻译,例如
en_us。 -
如果此翻译不存在,它将使用当前区域设置的通用语言的翻译,
例如en。 -
如果此翻译不存在,它将使用具有相同语言的任何其他区域设置的翻译,
例如en_uk。 -
如果此翻译不存在,它将使用键本身作为翻译。
管理键
其他翻译包要求您为每个翻译定义键标识符,
并使用它们。例如,上述文本键可以是 helloHowAreYou 或简单地 greetings。
然后,您可以像这样访问它:MyLocalizations.of(context).greetings。
然而,定义标识符不仅是一项繁琐的任务,而且它还会迫使您
在需要记住 widget 的确切文本时导航到翻译。
使用 i18n_extension,您可以直接键入您想要的文本,即可完成。
如果某个字符串已被翻译,而您稍后在 widget 文件中更改了它,
这将破坏键与翻译映射之间的链接。
但是,i18n_extension 很智能,可以告知您何时发生这种情况,
因此易于修复。您甚至可以将此检查添加到测试中,以确保所有翻译都
已链接且完整。
当您运行应用程序或测试时,每个未找到的键都将被记录到静态集
Translations.missingKeys。如果找到键但当前区域设置没有翻译,
它将被记录到 Translations.missingTranslations。
您可以手动检查这些集合以查看它们是否为空,或者创建测试来自动执行此操作,
例如
expect(Translations.missingKeys, isEmpty);
expect(Translations.missingTranslations, isEmpty);
注意:您可以通过以下方式禁用对丢失键和翻译的记录
Translations.recordMissingKeys = false;
Translations.recordMissingTranslations = false;
如果您愿意,还可以做另一件事,即在任何翻译丢失时抛出错误。
为此,请将回调注入 Translations.missingKeyCallback 和
Translations.missingTranslationCallback。例如
Translations.missingKeyCallback = (key, locale)
=> throw TranslationsException("Translation key in '$locale' is missing: '$key'.");
Translations.missingTranslationCallback = (key, locale)
=> throw TranslationsException("There are no translations in '$locale' for '$key'.");
或者,您可以将问题记录下来,而不是抛出错误,
或者向负责翻译的人发送电子邮件。
按语言定义翻译而不是按键
如前所述,通过使用 Translations() 构造函数
您可以定义每个键,然后同时提供所有翻译。
当您手动进行翻译时,这是最简单的方法,
例如,当您会说英语和西班牙语,并自己创建应用程序的翻译时。
但是,在其他情况下,例如当您聘请专业翻译服务
或众包翻译时,如果您可以按区域设置/语言提供翻译,
而不是按键,可能会更容易。您可以通过使用 Translations.byLocale() 构造函数来实现。
static var t = Translations.byLocale("en_us") +
{
"en_us": {
"Hi.": "Hi.",
"Goodbye.": "Goodbye.",
},
"es_es": {
"Hi.": "Hola.",
"Goodbye.": "Adiós.",
}
};
您还可以使用 + 运算符添加映射
static var t = Translations.byLocale("en_us") +
{
"en_us": {
"Hi.": "Hi.",
"Goodbye.": "Goodbye.",
},
} +
{
"es_es": {
"Hi.": "Hola.",
"Goodbye.": "Adiós.",
}
};
上面的注意,由于 "en_us" 是默认区域设置,您无需为这些提供翻译。
组合翻译
要组合翻译,您可以使用 * 运算符。例如
var t1 = Translations("en_us") +
{
"en_us": "Hi.",
"pt_br": "Olá.",
};
var t2 = Translations("en_us") +
{
"en_us": "Goodbye.",
"pt_br": "Adeus.",
};
var translations = t1 * t2;
print(localize("Hi.", translations, locale: "pt_br");
print(localize("Goodbye.", translations, locale: "pt_br");
翻译修饰符
有时您会有不同的翻译,这些翻译取决于数字的数量。
而不是 .i18n,您可以使用 .plural() 并传递一个数字。例如
int numOfItems = 3;
return Text("You clicked the button %d times".plural(numOfItems));
这将进行翻译,如果翻译后的字符串包含 %d,它将被数字替换。
然后,您的翻译文件应包含类似以下内容
static var t = Translations("en_us") +
{
"en_us": "You clicked the button %d times"
.zero("You haven't clicked the button")
.one("You clicked it once")
.two("You clicked a couple times")
.many("You clicked %d times")
.times(12, "You clicked a dozen times"),
"pt_br": "Você clicou o botão %d vezes"
.zero("Você não clicou no botão")
.one("Você clicou uma única vez")
.two("Você clicou um par de vezes")
.many("Você clicou %d vezes")
.times(12, "Você clicou uma dúzia de vezes"),
};
自定义修饰符
您实际上可以创建任何想要的修饰符。
例如,某些语言对不同的性别有不同的翻译。
因此,您可以创建接受 Gender 修饰符的 .gender()
enum Gender {they, female, male}
int gnd = Gender.female;
return Text("There is a person".gender(gnd));
然后,您的翻译文件应使用 .modifier() 和 localizeVersion(),如下所示
static var t = Translations("en_us") +
{
"en_us": "There is a person"
.modifier(Gender.male, "There is a man")
.modifier(Gender.female, "There is a woman")
.modifier(Gender.they, "There is a person"),
"pt_br": "Há uma pessoa"
.modifier(Gender.male, "Há um homem")
.modifier(Gender.female, "Há uma mulher")
.modifier(Gender.they, "Há uma pessoa"),
};
String gender(Gender gnd) => localizeVersion(gnd, this, t);
直接使用翻译对象
如果您有翻译对象,可以直接使用 localize 函数执行翻译
var translations = Translations("en_us") +
{
"en_us": "Hi",
"pt_br": "Olá",
};
// Prints "Hi".
print(localize("Hi", translations, locale: "en_us");
// Prints "Olá".
print(localize("Hi", translations, locale: "pt_br");
// Prints "Hi".
print(localize("Hi", translations, locale: "not valid");
更改默认区域设置
要更改当前区域设置,请执行此操作
I18n.of(context).locale = Locale("pt_BR");
要将当前区域设置恢复为系统默认设置,请执行此操作
I18n.of(context).locale = null;
要读取当前区域设置,请执行此操作
Locale defaultLocale = I18n.of(context).locale;
注意:这只会更改 i18n_extension 的当前区域设置,而不会更改 Flutter 的整体。
导入和导出
此包经过优化,您可以自己轻松地手动创建和管理所有翻译。
但是,对于拥有大型团队的大型项目,您可能需要遵循一个更复杂的过程
将所有可翻译字符串导出到文件
以某种外部格式,您的专业翻译人员或众包工具会使用该格式(参见下面的格式)。
在等待翻译的同时继续开发您的应用程序。将翻译文件导入项目中,然后
在您添加的每种语言上测试应用程序。根据需要重复此过程,在每次
应用程序修订之间仅翻译更改。根据需要,自己执行其他本地化步骤。
导入和导出很容易做到,因为 Translation 构造函数使用映射作为输入。
因此,您可以轻松地从任何文件格式生成映射,
然后使用 Translation() 或 Translation.byLocale() 构造函数来创建翻译对象。
格式
翻译可以使用以下格式
-
ARB:基于 JSON,是 Flutter 本地化的默认格式。
https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification -
ICU:https://format-message.github.io/icu-message-format-for-translators/
-
XLIFF:基于 XML。https://en.wikipedia.org/wiki/XLIFF
-
CSV:您可以使用 Excel 打开它,将其保存为 .XLSX 并在其中进行编辑。
但是,请注意不要以错误的设置将其导出回 CSV
(使用 UTF-8 以外的编码)。
https://en.wikipedia.org/wiki/Comma-separated_values -
JSON:可以使用,但缺乏翻译的特定功能,如复数和性别。
-
YAML:可以使用,但缺乏翻译的特定功能,如复数和性别。
注意:我需要帮助来为上述所有格式创建导入方法。
如果您想提供帮助,请 PR:https://github.com/marcglasberg/i18n_extension。