跳到主内容

国际化 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.yaml 文件。

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 应用的典型结构,本页提供了更多信息。

覆盖区域设置 (Locale)

#

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.dart 并添加 AppLocalizations.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),
    ),
    

此代码生成一个 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 参数的问候消息,请将以下内容添加到 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.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. Project Navigator 中,选择 Projects 下的 Runner 项目文件。

  3. 在项目编辑器中选择 Info 选项卡。

  4. Localizations 部分,点击 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_FR, fr_CA))也应该为了更细致的本地化而完全区分开。

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

#

Locale 类标识用户的语言。移动设备支持为所有应用设置区域设置,通常使用系统设置菜单。国际化应用通过显示特定于区域设置的值来响应。例如,如果用户将设备的区域设置从英语切换为法语,那么原本显示 "Hello World" 的 Text 组件将以 "Bonjour le monde" 重建。

Localizations 组件为其子组件定义区域设置以及子组件依赖的本地化资源。WidgetsApp 组件创建一个 Localizations 组件,并在系统区域设置更改时重建它。

你始终可以使用 Localizations.localeOf() 来查找应用的当前区域设置。

dart
Locale myLocale = Localizations.localeOf(context);

指定应用的 supportedLocales 参数

#

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

这可以通过减小 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 包的类导入一个生成的消息目录,该目录为 Intl.message() 提供 initializeMessages() 函数和每个区域设置的后备存储。消息目录由一个 intl 工具 产生,该工具分析源代码以查找包含 Intl.message() 调用的类。在这种情况下,那将只是 DemoLocalizations 类。

添加对新语言的支持

#

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

请参阅下文了解如何添加对新挪威语 (Nynorsk) 支持的示例。

一个新的 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 返回带有 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 Map<String, Object?> 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 格式的 map,为 main.dart 中定义的每个 Intl.message() 函数包含一个条目。此文件作为英语和西班牙语翻译 intl_en.arbintl_es.arb 的模板。这些翻译由你,即开发者,创建。

  2. 以应用的根目录为当前目录,为每个 intl_.arb 文件生成 intl_messages_.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 工具