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'

看看它的效果

尝试运行 示例

i18n_extension

适用于简单或复杂的应用程序

我一直有兴趣创建能够减少样板代码的软件包。
例如,async_redux 是关于无样板的 Redux,
align_positioned 是关于使用更少的小部件创建布局。
这个包也旨在减少翻译的样板代码,
所以它所做的任何事情,您都可以使用普通的 Localizations.of(context) 来完成。

也就是说,这个包既适用于单人应用开发者,也适用于大公司团队。
它涵盖了您翻译工作的各个阶段

  1. 当您创建小部件时,它会让您轻松地定义哪些字符串需要
    翻译,只需在字符串后添加 .i18n。这些字符串称为“可翻译字符串”

  2. 当您想开始翻译工作时,它可以自动列出
    所有需要翻译的字符串。如果您遗漏了任何字符串,或者稍后添加了更多字符串
    或修改了其中一些字符串,它会告知您更改了什么以及如何修复。

  3. 然后,您可以以非常易于使用的格式手动提供翻译。

  4. 或者,您可以轻松地将其与专业的翻译服务集成,从
    任何您想要的格式导入或导出。

设置

将您的 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)

如果在翻译映射中缺少任何翻译键,将使用该键本身,
因此文本仍将显示在屏幕上,未翻译。

如果找到翻译键,它将根据以下规则选择语言

  1. 它将使用与当前区域设置完全匹配的翻译,例如 en_us

  2. 如果此翻译不存在,它将使用当前区域设置的通用语言的翻译,
    例如 en

  3. 如果此翻译不存在,它将使用具有相同语言的任何其他区域设置的翻译,
    例如 en_uk

  4. 如果此翻译不存在,它将使用键本身作为翻译。

管理键

其他翻译包要求您为每个翻译定义键标识符,
并使用它们。例如,上述文本键可以是 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() 构造函数来创建翻译对象。

格式

翻译可以使用以下格式

注意:我需要帮助来为上述所有格式创建导入方法。
如果您想提供帮助,请 PR:https://github.com/marcglasberg/i18n_extension

GitHub

https://github.com/marcglasberg/i18n_extension