跳到主内容

Flutter 应用国际化

如何国际化你的 Flutter 应用。

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

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

Flutter 中的本地化介绍

#

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

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

设置国际化应用:Flutter_localizations 包

#

默认情况下,Flutter 仅提供美国英语本地化。要添加对其他语言的支持,应用程序必须指定额外的 MaterialApp(或 CupertinoApp)属性,并包含一个名为 flutter_localizations 的包。

首先,使用 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 包现在应该能够正确地本地化为支持的区域设置之一。部件应该适应本地化的消息,以及正确的从左到右或从右到左的布局。

尝试将目标平台的区域设置切换到西班牙语 (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
    

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

    • App Resource Bundle (.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. 将导入语句添加到 app_localizations.dart 并将 AppLocalizations.delegate 添加到 MaterialApp 构造函数的调用中

    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),
    ),
    

此代码生成一个 Text 部件,如果目标设备的区域设置为英语,则显示“Hello World!”如果目标设备的区域设置为西班牙语,则显示“¡Hola Mundo!”在 arb 文件中,每个条目的键用作 AppLocalizations 代码中 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
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    // Returns 'Hello John'
    Text(AppLocalizations.of(context)!.hello('John')),
  ],
);

你还可以使用数值占位符来指定多个值。不同的语言对复数词有不同的处理方式。该语法还支持指定如何复数化一个词。一个复数化消息必须包含一个指示如何在不同情况下复数化该词的 num 参数。例如,英语将“person”复数化为“people”,但这还不够。message0 复数可能是“没有人”或“零人”。messageFew 复数可能是“几个人”、“一些人”或“少数人”。messageMany 复数可能是“大多数人”、“很多人”或“人群”。仅需要更通用的 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”。

转义语法

#

有时,你必须将 {} 等标记用作普通字符。要忽略这些标记的解析,请启用 use-escaping 标志,方法是在 l10n.yaml 中添加以下内容

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 构造函数

消息 "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.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);

指定应用支持的 Locales 参数

#

虽然 flutter_localizations 库支持许多语言和语言变体,但默认情况下仅提供英语翻译。开发者需要决定支持哪些语言。

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 中延迟加载每个区域设置。

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

请注意,此标志不影响其他平台,例如移动或桌面。
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 是否可以为 null。

默认情况下,此值为 true,以便 Localizations.of(context) 返回一个可为空的值,以实现向后兼容性。如果此值为 false,则会对 Localizations.of(context) 的返回值执行 null 检查,从而消除了用户代码中 null 检查的需要。
[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 包的类导入一个生成的消息目录,该目录提供 initializeMessages() 函数和 Intl.message() 的每个语言环境的后端存储。消息目录由一个 intl 工具 生成,该工具分析源代码以查找包含 Intl.message() 调用类。在这种情况下,这将只是 DemoLocalizations 类。

添加对新语言的支持

#

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

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

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

以下是完整的 add_language 示例的源代码,不包括实际的新诺斯克语翻译。

特定语言环境的 GlobalMaterialLocalizations 子类称为 NnMaterialLocalizations,而 LocalizationsDelegate 子类称为 _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 返回“原始”Dart 字符串,带有 r 前缀,例如 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 类将所有翻译直接包含在每个语言环境的 Maps 中

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 格式的映射,其中每个 Intl.message() 函数在 main.dart 中定义一个条目。此文件作为英语和西班牙语翻译 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 工具