如果你的应用可能会部署给讲其他语言的用户,那么你需要对其进行国际化。这意味着你需要以一种能够针对应用支持的每种语言或语言环境,本地化文本和布局等值的方式编写应用。Flutter 提供了有助于国际化的小部件和类,并且 Flutter 库本身也已国际化。

本页涵盖了使用 MaterialAppCupertinoApp 类本地化 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,请将该包以及 intl 包作为依赖项添加到你的 pubspec.yaml 文件中。

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 库,并为你的 MaterialAppCupertinoApp 指定 localizationsDelegatessupportedLocales

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'), // English
    Locale('es'), // Spanish
  ],
  home: MyHomePage(),
);

在引入 flutter_localizations 包并添加前面的代码后,MaterialCupertino 包现在应该已在 115 个支持的语言环境之一中正确本地化。小部件应该适应本地化的消息,并具有正确的从左到右或从右到左的布局。

尝试将目标平台的语言环境切换到西班牙语(es),消息应该会被本地化。

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

推荐使用完整的 Locale.fromSubtags 构造函数,因为它支持 scriptCode,尽管 Locale 默认构造函数仍然完全有效。

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

本页介绍了更多关于这些应用属性、它们所依赖的类型以及国际化 Flutter 应用通常如何组织的信息。

覆盖语言环境

#

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

要观察此行为,请添加对 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>[
          // Add the following code
          Localizations.override(
            context: context,
            locale: const Locale('es'),
            // Using a Builder to get the correct BuildContext.
            // Alternatively, you can create a new widget and Localizations.override
            // will pass the updated BuildContext to the new widget.
            child: Builder(
              builder: (context) {
                // A toy example for an internationalized Material widget.
                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
    # The following section is specific to Flutter.
    flutter:
      generate: true # Add this line
  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 getflutter run,代码生成将自动进行。你可以在通过 arb-diroutput-dir 选项指定的路径目录中找到生成的文件。或者,你也可以运行 flutter gen-l10n 来生成相同的文件而无需运行应用程序。

  7. 在调用 MaterialApp 构造函数时,添加 app_localizations.dartAppLocalizations.delegate 的导入语句。

    dart
    import 'l10n/app_localizations.dart';
    dart
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        AppLocalizations.delegate, // Add this line
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('es'), // Spanish
      ],
      home: MyHomePage(),
    );

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

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

    dart
    appBar: AppBar(
      // The [AppBar] title text should update its message
      // according to the system locale of the target platform.
      // Switching between English and Spanish locales should
      // cause this text to update.
      title: Text(AppLocalizations.of(context)!.helloWorld),
    ),

如果目标设备的语言环境设置为英语,此代码将生成一个显示“Hello World!”的 Text 小部件;如果目标设备的语言环境设置为西班牙语,则显示“¡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 参数的问候消息,请将以下内容添加到 lib/l10n/app_en.arb 中:

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

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

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

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    // Returns '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 值对应的消息变体(message0message1 等)替换。只有 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
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'no wombats'
    Text(AppLocalizations.of(context)!.nWombats(0)),
    // Returns '1 wombat'
    Text(AppLocalizations.of(context)!.nWombats(1)),
    // Returns '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
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'he'
    Text(AppLocalizations.of(context)!.pronoun('male')),
    // Returns 'she'
    Text(AppLocalizations.of(context)!.pronoun('female')),
    // Returns '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 类,根据语言环境和所需格式来格式化数字。

intdoublenum 类型可以使用以下任何 NumberFormat 构造函数:

消息“格式”值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. 在项目编辑器中选择 Info 选项卡。

  4. 本地化部分,单击 Add 按钮 (+) 将支持的语言和地区添加到你的项目。当要求选择文件和参考语言时,只需选择 Finish

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

进一步定制的高级主题

#

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

高级语言环境定义

#

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

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

为了完整表达国家代码 CNTWHK 的每种中文变体,支持的语言环境列表应包含:

dart
supportedLocales: [
  Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
  ), // generic simplified Chinese 'zh_Hans'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
  ), // generic traditional Chinese '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_FRfr_CA)也应完全区分以实现更细致的本地化。

跟踪语言环境:Locale 类和 Localizations 组件

#

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

Localizations 小部件定义了其子项的语言环境以及子项所依赖的本地化资源。WidgetsApp 小部件会创建一个 Localizations 小部件,并在系统语言环境更改时重建它。

你始终可以使用 Localizations.localeOf() 查找应用程序的当前语言环境。

dart
Locale myLocale = Localizations.localeOf(context);

指定应用的 supportedLocales 参数

#

尽管 flutter_localizations 库目前支持 115 种语言和语言变体,但默认情况下仅提供英语翻译。由开发人员决定具体支持哪些语言。

MaterialAppsupportedLocales 参数限制了语言环境的更改。当用户更改设备上的语言环境设置时,应用程序的 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 小部件会自动加载新语言环境的值,然后重建使用它的小部件。之所以会发生这种情况,是因为 LocalizationsInheritedWidget 一样工作。当构建函数引用继承的小部件时,会创建对继承的小部件的隐式依赖。当继承的小部件发生变化(当 Localizations 小部件的语言环境发生变化时),其依赖上下文会被重建。

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

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

例如,Material Components 小部件的本地化字符串由 MaterialLocalizations 类定义。此类实例由 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);
}

/// References to the localized values defined by MaterialLocalizations
/// are typically written like this:

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 包的类导入一个生成的 message catalog,该 catalog 提供了 initializeMessages() 函数以及 Intl.message() 的每个语言环境支持存储。message catalog 由一个 intl 工具生成,该工具分析包含 Intl.message() 调用的类的源代码。在这种情况下,它将只是 DemoLocalizations 类。

添加对新语言的支持

#

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

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

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

这是完整的 add_language 示例的源代码,不包括实际的新挪威语翻译。

特定于语言环境的 GlobalMaterialLocalizations 子类称为 NnMaterialLocalizationsLocalizationsDelegate 子类是 _NnMaterialLocalizationsDelegateNnMaterialLocalizations.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 的返回值更改为适当的新挪威语字符串。

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());

    // The locale (in this case `nn`) needs to be initialized into the custom
    // date symbols and patterns setup that Flutter uses.
    date_symbol_data_custom.initializeDateFormattingCustom(
      locale: localeName,
      patterns: nnLocaleDatePatterns,
      symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols),
    );

    return SynchronousFuture<MaterialLocalizations>(
      NnMaterialLocalizations(
        localeName: localeName,
        // The `intl` library's NumberFormat class is generated from CLDR data
        // (see https://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number_symbols_data.dart).
        // Unfortunately, there is no way to use a locale that isn't defined in
        // this map and the only way to work around this is to use a listed
        // locale's NumberFormat symbols. So, here we use the number formats
        // for 'en_US' instead.
        decimalFormat: intl.NumberFormat('#,##0.###', 'en_US'),
        twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en_US'),
        // DateFormat here will use the symbols and patterns provided in the
        // `date_symbol_data_custom.initializeDateFormattingCustom` call above.
        // However, an alternative is to simply use a supported locale's
        // DateFormat symbols, similar to NumberFormat above.
        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 README

一旦你实现了针对特定语言的 GlobalMaterialLocalizationsLocalizationsDelegate 子类,你需要将该语言和一个代理实例添加到你的应用程序中。以下代码将应用程序语言设置为新挪威语,并将 NnMaterialLocalizations 代理实例添加到应用程序的 localizationsDelegates 列表:

dart
const MaterialApp(
  localizationsDelegates: [
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    NnMaterialLocalizations.delegate, // Add the newly created 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']!;
  }
}

在极简应用程序中,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) {
    // Returning a SynchronousFuture here because an async "load" operation
    // isn't needed to produce an instance of 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.arbintl_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 工具