如果您的应用可能会部署给讲其他语言的用户,那么您需要对其进行国际化。这意味着您需要以一种方式编写应用,使其能够针对应用支持的每种语言或区域设置对文本和布局等值进行本地化。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,请将该包作为依赖项添加到您的 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 库,并为您的 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. 将一个新的 yaml 文件添加到 Flutter 项目的根目录。将此文件命名为 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"
    }
  }
}

此代码片段将 hello 方法调用添加到 AppLocalizations.of(context) 对象,并且该方法接受 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}}"

前面的表达式被消息变体(message0message1、...)替换,该变体对应于 countPlaceholder 的值。只需要 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 的输出
紧凑“1.2M”
紧凑货币*“$1.2M”
紧凑简单货币*“$1.2M”
紧凑长“120 万”
货币*“USD1,200,000.00”
小数模式"1,200,000"
小数模式数字*"1,200,000"
小数百分比模式*"120,000,000%"
百分比模式"120,000,000%"
科学模式“1E6”
简单货币*"$1,200,000"

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

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. 在**本地化**部分,单击 添加 按钮 (+) 将支持的语言和区域添加到您的项目。当要求选择文件和参考语言时,只需选择 完成

  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 小部件会自动为新区域设置加载值,然后重建使用它的小部件。发生这种情况是因为 Localizations 的工作方式类似于 InheritedWidget。当构建函数引用继承的小部件时,会创建对继承小部件的隐式依赖。当继承的小部件更改(当 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_all.dart 生成 intl_messages_<locale>.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 工具