Skip to main content

国际化 Flutter 应用

如果你的应用可能部署给说其他语言的用户,那么你需要对其进行国际化。这意味着你需要以一种方式编写应用程序,使其能够为应用程序支持的每种语言或区域设置本地化文本和布局等值。Flutter 提供了有助于国际化的部件和类,Flutter 库本身也是国际化的。

此页面涵盖了使用 MaterialApp 和 CupertinoApp 类本地化 Flutter 应用程序所需的 概念和工作流程,因为大多数应用程序都是这样编写的。但是,使用较低级别 WidgetsApp 类编写的应用程序也可以使用相同的类和逻辑进行国际化。

Flutter 中本地化的介绍

#

本节提供关于如何创建和国际化新的 Flutter 应用程序的教程,以及目标平台可能需要的任何其他设置。

你可以在 gen_l10n_example 中找到此示例的源代码。

设置国际化应用程序:Flutter_localizations 包

#

默认情况下,Flutter 仅提供美式英语本地化。要添加对其他语言的支持,应用程序必须指定其他 MaterialApp(或 CupertinoApp)属性,并包含名为 flutter_localizations 的包。截至 2023 年 12 月,此包支持 115 种语言 和语言变体。

首先,使用 flutter create 命令在你选择的目录中创建一个新的 Flutter 应用程序。

flutter create <name_of_flutter_app>

要使用 flutter_localizations,请将该包作为依赖项添加到你的 pubspec.yaml 文件中,以及 intl 包:

flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl:any

这将创建一个包含以下条目的 pubspec.yml 文件:

yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

然后导入 flutter_localizations 库并为你的 MaterialApp 或 CupertinoApp 指定 localizationsDelegates 和 supportedLocales:

dart
import 'package:flutter_localizations/flutter_localizations.dart';
dart
return const MaterialApp(
  title: 'Localizations Sample App',
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    Locale('en'), // 英语
    Locale('es'), // 西班牙语
  ],
  home: MyHomePage(),
);

引入 flutter_localizations 包并添加之前的代码后,Material 和 Cupertino 包现在应该在 115 个支持的区域设置之一中正确本地化。小部件应该适应本地化的消息,以及正确的从左到右或从右到左的布局。

尝试将目标平台的区域设置切换为西班牙语 (es),消息应该会本地化。

基于 WidgetsApp 的应用程序类似,只是不需要 GlobalMaterialLocalizations.delegate。

完整的 Locale.fromSubtags 构造函数是首选,因为它支持 scriptCode,尽管 Locale 默认构造函数仍然完全有效。

localizationsDelegates 列表的元素是生成本地化值集合的工厂。GlobalMaterialLocalizations.delegate 为 Material Components 库提供本地化字符串和其他值。GlobalWidgetsLocalizations.delegate 定义小部件库的默认文本方向,即从左到右或从右到左。

此页面涵盖了有关这些应用程序属性、它们依赖的类型以及通常如何构建国际化 Flutter 应用程序的更多信息。

覆盖区域设置

#

Localizations.override 是 Localizations 小部件的工厂构造函数,它允许(通常很少见)应用程序的某个部分需要本地化为与设备配置的区域设置不同的区域设置的情况。

要观察此行为,请添加对 Localizations.override 和简单的 CalendarDatePicker 的调用:

dart
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // 添加以下代码
          Localizations.override(
            context: context,
            locale: const Locale('es'),
            // 使用 Builder 获取正确的 BuildContext。
            // 或者,你可以创建一个新的部件,Localizations.override
            // 将把更新的 BuildContext 传递给新的部件。
            child: Builder(
              builder: (context) {
                // 国际化 Material 小部件的示例。
                return CalendarDatePicker(
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                  onDateChanged: (value) {},
                );
              },
            ),
          ),
        ],
      ),
    ),
  );
}

热重载应用程序,CalendarDatePicker 小部件应该以西班牙语重新渲染。

添加你自己的本地化消息

#

添加 flutter_localizations 包后,你可以配置本地化。要向应用程序添加本地化文本,请完成以下说明:

  1. 将 intl 包添加为依赖项,引入 flutter_localizations 锁定的版本:

    flutter pub add intl:any
  2. 打开 pubspec.yaml 文件并启用 generate 标志。此标志位于 pubspec 文件中的 flutter 部分。

    yaml
    # 以下部分特定于 Flutter。
    flutter:
      generate: true # 添加此行
  3. 向 Flutter 项目的根目录添加一个新的 yaml 文件。将此文件命名为 l10n.yaml 并包含以下内容:

    yaml
    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart

    此文件配置本地化工具。在此示例中,你已执行以下操作:

    • 将 应用程序资源包 (.arb) 输入文件放在 ${FLUTTER_PROJECT}/lib/l10n 中。.arb 提供应用程序的本地化资源。
    • 将英语模板设置为 app_en.arb。
    • 告诉 Flutter 在 app_localizations.dart 文件中生成本地化。
  4. 在 ${FLUTTER_PROJECT}/lib/l10n 中,添加 app_en.arb 模板文件。例如:

    json
    {
      "helloWorld": "Hello World!",
      "@helloWorld": {
        "description": "The conventional newborn programmer greeting"
      }
    }
  5. 在同一目录中添加另一个名为 app_es.arb 的包文件。在此文件中,添加相同消息的西班牙语翻译。

    json
    {
        "helloWorld": "¡Hola Mundo!"
    }
  6. 现在,运行 flutter pub get 或 flutter run,代码生成将自动进行。你应该在 ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n 中找到生成的文 件。或者,你也可以运行 flutter gen-l10n 来生成相同的文件,而无需运行应用程序。

  7. 在对 MaterialApp 的构造函数调用中,添加对 app_localizations.dart 和 AppLocalizations.delegate 的导入语句:

    dart
    import 'package:flutter_gen/gen_l10n/app_localizations.dart';
    dart
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        AppLocalizations.delegate, // 添加此行
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // 英语
        Locale('es'), // 西班牙语
      ],
      home: MyHomePage(),
    );

    AppLocalizations 类还提供自动生成的 localizationsDelegates 和 supportedLocales 列表。你可以使用这些列表,而不是手动提供它们。

    dart
    const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    );
  8. Material 应用程序启动后,你可以在应用程序的任何位置使用 AppLocalizations:

    dart
    appBar: AppBar(
      // [AppBar] 标题文本应根据目标平台的系统区域设置更新其消息。
      // 在英语和西班牙语区域设置之间切换应导致此文本更新。
      title: Text(AppLocalizations.of(context)!.helloWorld),
    ),

此代码生成一个 Text 小部件,如果目标设备的区域设置设置为英语,则显示“Hello World!”,如果目标设备的区域设置设置为西班牙语,则显示“¡Hola Mundo!”。在 arb 文件中,每个条目的键用作 getter 的方法名,而该条目的值包含本地化消息。

gen_l10n_example 使用此工具。

要本地化你的设备应用程序描述,请将本地化字符串传递给 MaterialApp.onGenerateTitle:

dart
return MaterialApp(
  onGenerateTitle: (context) => DemoLocalizations.of(context).title,

占位符、复数和选择

#

你还可以使用特殊语法在消息中包含应用程序值,该语法使用 占位符 来生成方法而不是 getter。占位符(必须是有效的 Dart 标识符名称)成为 AppLocalizations 代码中生成方法的位置参数。通过以下方式将占位符名称括在花括号中来定义:

json
"{placeholderName}"

在应用程序的 .arb 文件中的 placeholders 对象中定义每个占位符。例如,要定义带有 userName 参数的 hello 消息,请将以下内容添加到 lib/l10n/app_en.arb:

json
"hello": "Hello {userName}",
"@hello": {
  "description": "A message with a single parameter",
  "placeholders": {
    "userName": {
      "type": "String",
      "example": "Bob"
    }
  }
}

此代码片段将 hello 方法调用添加到 AppLocalizations.of(context) 对象,并且该方法接受类型为 String 的参数;hello 方法返回一个字符串。重新生成 AppLocalizations 文件。

将传递给 Builder 的代码替换为以下内容:

dart
// 国际化字符串的示例。
return Column(
  children: <Widget>[
    // 返回 'Hello John'
    Text(AppLocalizations.of(context)!.hello('John')),
  ],
);

你还可以使用数字占位符来指定多个值。不同的语言有不同的单词复数化方式。该语法还支持指定单词的复数化方式。复数 消息必须包含一个 num 参数,指示如何在不同情况下将单词复数化。例如,英语将“person”复数化为“people”,但这还不够。message0 复数可能是“no people”或“zero people”。messageFew 复数可能是“several people”、“some people”或“a few people”。messageMany 复数可能是“most people”或“many people”,或“a crowd”。只有更通用的 messageOther 字段是必需的。以下示例显示了可用的选项:

json
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"

前面的表达式将被与 countPlaceholder 值对应的消息变体(message0、message1……)替换。只有 messageOther 字段是必需的。

以下示例定义了一个将单词“wombat”复数化的消息:

json
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
  "description": "A plural message",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}

通过传入 count 参数来使用复数方法:

dart
// 国际化字符串的示例。
return Column(
  children: <Widget>[
    ...
    // 返回 'no wombats'
    Text(AppLocalizations.of(context)!.nWombats(0)),
    // 返回 '1 wombat'
    Text(AppLocalizations.of(context)!.nWombats(1)),
    // 返回 '5 wombats'
    Text(AppLocalizations.of(context)!.nWombats(5)),
  ],
);

类似于复数,你还可以根据 String 占位符选择一个值。这最常用于支持性别语言。语法如下:

json
"{selectPlaceholder, select, case{message} ... other{messageOther}}"

下一个示例定义了一个根据性别选择代词的消息:

json
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}

通过将性别字符串作为参数传入来使用此功能:

dart
// 国际化字符串的示例。
return Column(
  children: <Widget>[
    ...
    // 返回 'he'
    Text(AppLocalizations.of(context)!.pronoun('male')),
    // 返回 'she'
    Text(AppLocalizations.of(context)!.pronoun('female')),
    // 返回 'they'
    Text(AppLocalizations.of(context)!.pronoun('other')),
  ],
);

请记住,使用 select 语句时,参数与实际值之间的比较区分大小写。也就是说,AppLocalizations.of(context)!.pronoun("Male") 默认使用“other”情况,并返回“they”。

转义语法

#

有时,你必须将诸如 { 和 } 之类的标记用作普通字符。要忽略这些标记的解析,请通过向 l10n.yaml 添加以下内容来启用 use-escaping 标志:

yaml
use-escaping: true

解析器会忽略用单引号括起来的任何字符串。要使用普通的单引号字符,请使用一对连续的单引号。例如,以下文本将转换为 Dart String:

json
{
  "helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}

生成的字符串如下所示:

dart
"Hello! {Isn't} this a wonderful day?"

包含数字和货币的消息

#

数字(包括表示货币值的数字)在不同的区域设置中显示方式大相径庭。flutter_localizations 中的本地化生成工具使用 intl 包中的 NumberFormat 类来根据区域设置和所需的格式来格式化数字。

int、double 和 number 类型可以使用以下任何 NumberFormat 构造函数:

消息“format”值1200000 的输出
compact"1.2M"
compactCurrency*"$1.2M"
compactSimpleCurrency*"$1.2M"
compactLong"1.2 million"
currency*"USD1,200,000.00"
decimalPattern"1,200,000"
decimalPatternDigits*"1,200,000"
decimalPercentPattern*"120,000,000%"
percentPattern"120,000,000%"
scientificPattern"1E6"
simpleCurrency*"$1,200,000"

表中带星号的 NumberFormat 构造函数提供可选的命名参数。这些参数可以指定为占位符的 optionalParameters 对象的值。例如,要为 compactCurrency 指定可选的 decimalDigits 参数,请对 lib/l10n/app_en.arg 文件进行以下更改:

json
"numberOfDataPoints": "Number of data points: {value}",
"@numberOfDataPoints": {
  "description": "A message with a formatted int parameter",
  "placeholders": {
    "value": {
      "type": "int",
      "format": "compactCurrency",
      "optionalParameters": {
        "decimalDigits": 2
      }
    }
  }
}

包含日期的消息

#

日期字符串的格式多种多样,这取决于区域设置和应用程序的需求。

类型为 DateTime 的占位符值使用 intl 包中的 DateFormat 进行格式化。

有 41 种格式变体,由其 DateFormat 工厂构造函数的名称标识。在以下示例中,出现在 helloWorldOn 消息中的 DateTime 值使用 DateFormat.yMd 进行格式化:

json
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
  "description": "A message with a date parameter",
  "placeholders": {
    "date": {
      "type": "DateTime",
      "format": "yMd"
    }
  }
}

在区域设置为美式英语的应用程序中,以下表达式将生成“7/9/1959”。在俄语区域设置中,它将生成“9.07.1959”。

dart
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))

本地化 iOS:更新 iOS 应用程序包

#

虽然本地化由 Flutter 处理,但你需要在 Xcode 项目中添加支持的语言。这确保你的 App Store 条目正确显示支持的语言。

要配置你的应用程序支持的区域设置,请使用以下说明:

  1. 打开项目的 ios/Runner.xcodeproj Xcode 文件。

  2. 在 项目导航器 中,选择 项目 下的 Runner 项目文件。

  3. 选择项目编辑器中的 信息 选项卡。

  4. 在 本地化 部分,单击 添加 按钮(+)以将支持的语言和区域添加到你的项目。当系统要求你选择文件和参考语言时,只需选择“完成”。

  5. Xcode 会自动创建空的 .strings 文件并更新 ios/Runner.xcodeproj/project.pbxproj 文件。App Store 使用这些文件来确定你的应用程序支持哪些语言和区域。

高级主题以进行进一步自定义

#

本节涵盖了自定义本地化 Flutter 应用程序的其他方法。

高级区域设置定义

#

某些具有多种变体的语言需要不止一个语言代码才能正确区分。

例如,完全区分所有中文变体需要指定语言代码、脚本代码和国家/地区代码。这是由于存在简体和繁体字,以及在同一脚本类型中书写字符方式的区域差异。

为了完全表达 CN、TW 和 HK 国家/地区代码的所有中文变体,支持的区域设置列表应包括:

dart
supportedLocales: [
  Locale.fromSubtags(languageCode: 'zh'), // 通用中文 'zh'
  Locale.fromSubtags(
      languageCode: 'zh',
      scriptCode: 'Hans'), // 通用简体中文 'zh_Hans'
  Locale.fromSubtags(
      languageCode: 'zh',
      scriptCode: 'Hant'), // 通用繁体中文 'zh_Hant'
  Locale.fromSubtags(
      languageCode: 'zh',
      scriptCode: 'Hans',
      countryCode: 'CN'), // 'zh_Hans_CN'
  Locale.fromSubtags(
      languageCode: 'zh',
      scriptCode: 'Hant',
      countryCode: 'TW'), // 'zh_Hant_TW'
  Locale.fromSubtags(
      languageCode: 'zh',
      scriptCode: 'Hant',
      countryCode: 'HK'), // 'zh_Hant_HK'
],

此显式完整定义确保你的应用程序可以区分并向所有这些国家/地区代码的组合提供完全细致的本地化内容。如果未指定用户的首选区域设置,Flutter 将选择最接近的匹配项,该匹配项可能与用户预期存在差异。Flutter 仅解析 supportedLocales 中定义的区域设置,并为常用语言提供区分脚本代码的本地化内容。 查看 Localizations 以了解如何解析支持的区域设置和首选区域设置。

虽然中文是一个主要的示例,但其他语言,如法语(fr_FR、fr_CA),也应该进行充分区分,以便进行更细致的本地化。

跟踪区域设置:Locale 类和 Localizations 小部件

#

Locale 类标识用户的语言。移动设备支持为所有应用程序设置区域设置,通常使用系统设置菜单。国际化应用程序通过显示特定于区域设置的值来响应。例如,如果用户将设备的区域设置从英语切换到法语,则最初显示“Hello World”的 Text 小部件将使用“Bonjour le monde”重新构建。

Localizations 小部件为其子项和子项依赖的本地化资源定义区域设置。WidgetsApp 小部件创建一个 Localizations 小部件,如果系统的区域设置发生更改,则会重新构建它。

你可以随时使用 Localizations.localeOf() 查询应用程序的当前区域设置:

dart
Locale myLocale = Localizations.localeOf(context);

指定应用程序支持的 Locales 参数

#

虽然 flutter_localizations 库目前支持 115 种语言和语言变体,但默认情况下只有英语翻译可用。开发者需要决定究竟支持哪些语言。

MaterialApp 的 supportedLocales 参数限制了区域设置更改。当用户更改设备上的区域设置时,应用程序的 Localizations 小部件只有在新区域设置是此列表的成员时才会随之更改。如果找不到设备区域设置的确切匹配项,则使用第一个具有匹配 languageCode 的支持的区域设置。如果这失败了,则使用 supportedLocales 列表的第一个元素。

想要使用不同“区域设置解析”方法的应用程序可以提供一个 localeResolutionCallback。例如,要让你的应用程序无条件地接受用户选择的任何区域设置:

dart
MaterialApp(
  localeResolutionCallback: (
    locale,
    supportedLocales,
  ) {
    return locale;
  },
);

配置 l10n.yaml 文件

#

l10n.yaml 文件允许你配置 gen-l10n 工具来指定以下内容:

  • 所有输入文件的位置
  • 所有输出文件应创建的位置
  • 为你的本地化委托提供哪个 Dart 类名

有关选项的完整列表,请在命令行中运行 flutter gen-l10n --help 或参考下表:

选项描述
arb-dir模板和翻译后的 arb 文件所在的目录。默认值为 lib/l10n。
output-dir生成的本地化类写入的目录。仅当你想在 Flutter 项目的其他位置生成本地化代码时,此选项才相关。你还需要将 synthetic-package 标志设置为 false。

应用程序必须从此目录导入 output-localization-file 选项中指定的文件。如果未指定,则默认为与 arb-dir 中指定的输入目录相同的目录。
template-arb-file用作生成 Dart 本地化和消息文件的依据的模板 arb 文件。默认值为 app_en.arb。
output-localization-file输出本地化和本地化委托类的文件名。默认值为 app_localizations.dart。
untranslated-messages-file描述尚未翻译的本地化消息的文件位置。使用此选项将在目标位置创建 JSON 文件,格式如下:

"locale": ["message_1", "message_2" ... "message_n"]

如果未指定此选项,则会在命令行上打印尚未翻译的消息摘要。
output-class用于输出本地化和本地化委托类的 Dart 类名。默认值为 AppLocalizations。
preferred-supported-locales应用程序的首选支持的区域设置列表。默认情况下,该工具按字母顺序生成支持的区域设置列表。使用此标志默认为不同的区域设置。

例如,传入 [ en_US ] 表示如果设备支持,则默认为美式英语。
header预先添加到生成的 Dart 本地化文件的标题。此选项接受字符串。

例如,传入 "/// All localized files." 将此字符串预先添加到生成的 Dart 文件。

或者,查看 header-file 选项以传入文本文件以获得更长的标题。
header-file预先添加到生成的 Dart 本地化文件的标题。此选项的值是包含在每个生成的 Dart 文件顶部插入的标题文本的文件名。

或者,查看 header 选项以传入字符串以获得更简单的标题。

此文件应放在 arb-dir 中指定的目录中。
[no-]use-deferred-loading指定是否使用延迟导入的区域设置生成 Dart 本地化文件,允许在 Flutter web 中延迟加载每个区域设置。

这可以通过减小 JavaScript 包的大小来减少 Web 应用程序的初始启动时间。当此标志设置为 true 时,Flutter 应用程序仅在需要时才下载和加载特定区域设置的消息。对于具有许多不同区域设置和许多本地化字符串的项目,这可以提高性能。对于区域设置数量较少的项目,差异可以忽略不计,并且与将本地化与应用程序的其余部分捆绑在一起相比,可能会减慢启动速度。

请注意,此标志不影响移动设备或桌面等其他平台。
gen-inputs-and-outputs-list指定时,该工具将生成一个 JSON 文件,其中包含该工具的输入和输出,名为 gen_l10n_inputs_and_outputs.json。

这对于跟踪在生成最新本地化集时使用了哪些 Flutter 项目文件非常有用。例如,Flutter 工具的构建系统使用此文件来跟踪何时在热重载期间调用 gen_l10n。

此选项的值是生成 JSON 文件的目录。为 null 时,不会生成 JSON 文件。
synthetic-package确定生成的输出文件是作为合成包生成还是在 Flutter 项目的指定目录中生成。此标志默认为 true。当 synthetic-package 设置为 false 时,它默认在 arb-dir 指定的目录中生成本地化文件。如果指定了 output-dir,则在该目录中生成文件。
project-dir指定时,该工具将使用此选项中传递的路径作为根 Flutter 项目的目录。

为 null 时,将使用当前工作目录的相对路径。
[no-]required-resource-attributes要求所有资源 ID 都包含相应的资源属性。

默认情况下,简单的消息不需要元数据,但强烈建议这样做,因为这为读者提供了消息含义的上下文。

复数消息仍然需要资源属性。
[no-]nullable-getter指定本地化类 getter 是否可为空。

默认情况下,此值为 true,以便 Localizations.of(context) 返回可为空的值以实现向后兼容性。如果此值为 false,则对 Localizations.of(context) 的返回值执行空检查,无需在用户代码中进行空检查。
[no-]format指定时,在生成本地化文件后运行 dart format 命令。
use-escaping指定是否启用使用单引号作为转义语法的功能。
[no-]suppress-warnings指定时,将抑制所有警告。
[no-]relax-syntax指定时,语法将被放宽,以便如果特殊字符“{”后面没有有效的占位符,则将其视为字符串;如果“}”不关闭任何先前被视为特殊字符的“{”,则将其视为字符串。
[no-]use-named-parameters是否为生成的本地化方法使用命名参数。

Flutter 中国际化的工作原理

#

本节介绍本地化在 Flutter 中的工作原理的技术细节。如果你计划支持你自己的本地化消息集,以下内容将有所帮助。否则,你可以跳过本节。

加载和检索本地化值

#

Localizations 小部件用于加载和查找包含本地化值集合的对象。应用程序使用 Localizations.of(context,type) 来引用这些对象。如果设备的区域设置发生更改,Localizations 小部件会自动加载新区域设置的值,然后重新构建使用它的部件。发生这种情况是因为 Localizations 的工作方式类似于 InheritedWidget。当构建函数引用继承的小部件时,会创建对继承的小部件的隐式依赖关系。当继承的小部件更改(当 Localizations 小部件的区域设置更改时),其依赖的上下文将被重新构建。

本地化值由 Localizations 小部件的 LocalizationsDelegate 列表加载。每个委托都必须定义一个异步 load() 方法,该方法生成一个封装本地化值集合的对象。这些对象通常为每个本地化值定义一个方法。

在一个大型应用程序中,不同的模块或包可能与其自己的本地化捆绑在一起。这就是 Localizations 小部件管理对象表的原因,每个 LocalizationsDelegate 一个。要检索由其中一个 LocalizationsDelegate 的 load 方法生成的对象,请指定一个 BuildContext 和对象的类型。

例如,Material Components 小部件的本地化字符串

由 MaterialLocalizations 类定义 Material Components 小部件的本地化字符串。此类的实例由 MaterialApp 类提供的 LocalizationDelegate 创建。可以使用 Localizations.of() 获取它们:

dart
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);

此特定的 Localizations.of() 表达式经常使用,因此 MaterialLocalizations 类提供了一个方便的简写:

dart
static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}

/// 对 MaterialLocalizations 定义的本地化值的引用
/// 通常这样编写:

tooltip: MaterialLocalizations.of(context).backButtonTooltip,

为应用程序的本地化资源定义一个类

#

构建国际化 Flutter 应用程序通常从封装应用程序本地化值的类开始。以下示例是此类类的典型示例。

此应用程序的 intl_example 的完整源代码。

此示例基于 intl 包提供的 API 和工具。应用程序本地化资源的替代类 部分描述了一个示例,它不依赖于 intl 包。

DemoLocalizations 类(在以下代码片段中定义)包含应用程序的字符串(示例中只有一个),这些字符串已翻译成应用程序支持的区域设置。它使用 Dart 的 intl 包生成的 initializeMessages() 函数,Intl.message() 来查找它们。

dart
class DemoLocalizations {
  DemoLocalizations(this.localeName);

  static Future<DemoLocalizations> load(Locale locale) {
    final String name =
        locale.countryCode == null || locale.countryCode!.isEmpty
            ? locale.languageCode
            : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);

    return initializeMessages(localeName).then((_) {
      return DemoLocalizations(localeName);
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  final String localeName;

  String get title {
    return Intl.message(
      'Hello World',
      name: 'title',
      desc: 'Title for the Demo application',
      locale: localeName,
    );
  }
}

基于 intl 包的类导入生成的的消息目录,该目录提供 initializeMessages() 函数和 Intl.message() 的每个区域设置的后台存储。消息目录由 intl 工具 生成,该工具分析包含 Intl.message() 调用的类的源代码。在这种情况下,它只是 DemoLocalizations 类。

添加对新语言的支持

#

需要支持 GlobalMaterialLocalizations 中未包含的语言的应用程序必须执行一些额外的工作:它必须为单词或短语以及该区域设置的日期模式和符号提供大约 70 个翻译(“本地化”)。

请参阅以下内容,了解如何添加对挪威 Nynorsk 语言的支持的示例。

一个新的 GlobalMaterialLocalizations 子类定义了 Material 库依赖的本地化。还必须定义一个新的 LocalizationsDelegate 子类,它作为 GlobalMaterialLocalizations 子类的工厂。

以下是完整 add_language 示例的源代码,减去实际的 Nynorsk 翻译。

特定于区域设置的 GlobalMaterialLocalizations 子类称为 NnMaterialLocalizations,LocalizationsDelegate 子类称为 _NnMaterialLocalizationsDelegate。NnMaterialLocalizations.delegate 的值是委托的实例,对于使用这些本地化的应用程序来说,这已经足够了。

委托类包括基本的日期和数字格式本地化。所有其他本地化都由 NnMaterialLocalizations 中的 String 值属性 getter 定义,如下所示:

dart
@override
String get moreButtonTooltip => r'More';

@override
String get aboutListTileTitleRaw => r'About $applicationName';

@override
String get alertDialogLabel => r'Alert';

当然,这些是英语翻译。要完成这项工作,你需要将每个 getter 的返回值更改为适当的 Nynorsk 字符串。

getter 返回带有 r 前缀的“原始”Dart 字符串,例如 r'About $applicationName',因为有时字符串包含带有 $ 前缀的变量。变量由参数化本地化方法扩展:

dart
@override
String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount';

@override
String get pageRowsInfoTitleApproximateRaw =>
    r'$firstRow–$lastRow of about $rowCount';

还需要指定该区域设置的日期模式和符号,这些在源代码中定义如下:

dart
const nnLocaleDatePatterns = {
  'd': 'd.',
  'E': 'ccc',
  'EEEE': 'cccc',
  'LLL': 'LLL',
  // ...
}
dart
const nnDateSymbols = {
  'NAME': 'nn',
  'ERAS': <dynamic>[
    'f.Kr.',
    'e.Kr.',
  ],
  // ...
}

需要修改这些值才能使区域设置使用正确的日期格式。不幸的是,由于 intl 库对于数字格式化没有相同的灵活性,因此必须使用现有区域设置的格式作为 _NnMaterialLocalizationsDelegate 中的替代品:

dart
class _NnMaterialLocalizationsDelegate
    extends LocalizationsDelegate<MaterialLocalizations> {
  const _NnMaterialLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => locale.languageCode == 'nn';

  @override
  Future<MaterialLocalizations> load(Locale locale) async {
    final String localeName = intl.Intl.canonicalizedLocale(locale.toString());

    // 该区域设置(在本例中为 `nn`)需要初始化到 Flutter 使用的自定义日期符号和模式设置中。
    date_symbol_data_custom.initializeDateFormattingCustom(
      locale: localeName,
      patterns: nnLocaleDatePatterns,
      symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols),
    );

    return SynchronousFuture<MaterialLocalizations>(
      NnMaterialLocalizations(
        localeName: localeName,
        // `intl` 库的 NumberFormat 类是从 CLDR 数据生成的
        // (请参阅 https://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number_symbols_data.dart)。
        // 不幸的是,无法使用此映射中未定义的区域设置,解决此问题的唯一方法是使用列出的
        // 区域设置的 NumberFormat 符号。因此,这里我们使用
        // 'en_US' 的数字格式。
        decimalFormat: intl.NumberFormat('#,##0.###', 'en_US'),
        twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en_US'),
        // 此处的 DateFormat 将使用上面 `date_symbol_data_custom.initializeDateFormattingCustom` 调用中提供的符号和模式。
        // 但是,另一种方法是简单地使用支持的区域设置的
        // DateFormat 符号,类似于上面的 NumberFormat。
        fullYearFormat: intl.DateFormat('y', localeName),
        compactDateFormat: intl.DateFormat('yMd', localeName),
        shortDateFormat: intl.DateFormat('yMMMd', localeName),
        mediumDateFormat: intl.DateFormat('EEE, MMM d', localeName),
        longDateFormat: intl.DateFormat('EEEE, MMMM d, y', localeName),
        yearMonthFormat: intl.DateFormat('MMMM y', localeName),
        shortMonthDayFormat: intl.DateFormat('MMM d'),
      ),
    );
  }

  @override
  bool shouldReload(_NnMaterialLocalizationsDelegate old) => false;
}

有关本地化字符串的更多信息,请查看flutter_localizations 自述文件。

实现 GlobalMaterialLocalizations 和 LocalizationsDelegate 的特定于语言的子类后,你需要将语言和委托实例添加到你的应用程序。以下代码将应用程序的语言设置为 Nynorsk,并将 NnMaterialLocalizations 委托实例添加到应用程序的 localizationsDelegates 列表:

dart
const MaterialApp(
  localizationsDelegates: [
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    NnMaterialLocalizations.delegate, // 添加新创建的委托
  ],
  supportedLocales: [
    Locale('en', 'US'),
    Locale('nn'),
  ],
  home: Home(),
),

其他国际化工作流程

#

本节介绍国际化 Flutter 应用程序的不同方法。

应用程序本地化资源的替代类

#

前面的示例是根据 Dart intl 包定义的。为了简单起见,或者为了与不同的 i18n 框架集成,你可以选择你自己的方法来管理本地化值。

minimal 应用程序的完整源代码。

在以下示例中,DemoLocalizations 类直接在其每个语言的 Map 中包含所有翻译:

dart
class DemoLocalizations {
  DemoLocalizations(this.locale);

  final Locale locale;

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  static const _localizedValues = <String, Map<String, String>>{
    'en': {
      'title': 'Hello World',
    },
    'es': {
      'title': 'Hola Mundo',
    },
  };

  static List<String> languages() => _localizedValues.keys.toList();

  String get title {
    return _localizedValues[locale.languageCode]!['title']!;
  }
}

在 minimal 应用程序中,DemoLocalizationsDelegate 略有不同。它的 load 方法返回一个 SynchronousFuture,因为不需要进行异步加载。

dart
class DemoLocalizationsDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) =>
      DemoLocalizations.languages().contains(locale.languageCode);

  @override
  Future<DemoLocalizations> load(Locale locale) {
    // 在此处返回 SynchronousFuture,因为不需要异步“加载”操作
    // 来生成 DemoLocalizations 的实例。
    return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale));
  }

  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}

使用 Dart intl 工具

#

在使用 Dart intl 包构建 API 之前,请查看 intl 包的文档。以下列表总结了本地化依赖于 intl 包的应用程序的过程:

演示应用程序依赖于一个名为 l10n/messages_all.dart 的生成的源文件,该文件定义了应用程序使用的所有可本地化字符串。

重新构建 l10n/messages_all.dart 需要两个步骤。

  1. 以应用程序的根目录作为当前目录,从 lib/main.dart 生成 l10n/intl_messages.arb:

    dart run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart

    intl_messages.arb 文件是一个 JSON 格式的映射,其中 main.dart 中定义的每个 Intl.message() 函数都有一个条目。此文件用作英语和西班牙语翻译 intl_en.arb 和 intl_es.arb 的模板。这些翻译由开发者创建。

  2. 以应用程序的根目录作为当前目录,为每个 intl_<locale>.arb 文件生成 intl_messages_<locale>.dart 和 intl_messages_all.dart,后者导入所有消息文件:

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart lib/l10n/intl_*.arb

    Windows 不支持文件名通配符。 请改为列出由 intl_translation:extract_to_arb 命令生成的 .arb 文件。

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart \
        lib/l10n/intl_en.arb lib/l10n/intl_fr.arb lib/l10n/intl_messages.arb

    DemoLocalizations 类使用生成的 initializeMessages() 函数(在 intl_messages_all.dart 中定义)来加载本地化消息,并使用 Intl.message() 来查找它们。

更多信息

#

如果你更喜欢阅读代码来学习,请查看以下示例。

如果你不熟悉 Dart 的 intl 包,请查看使用 Dart intl 工具。