Flutter 应用的国际化
如果你的应用可能会部署给讲其他语言的用户,那么你需要对其进行国际化。这意味着你需要以一种能够针对应用支持的每种语言或语言环境,本地化文本和布局等值的方式编写应用。Flutter 提供了有助于国际化的小部件和类,并且 Flutter 库本身也已国际化。
本页涵盖了使用 MaterialApp
和 CupertinoApp
类本地化 Flutter 应用程序所需的概念和工作流程,因为大多数应用程序都是以这种方式编写的。然而,使用较低级别 WidgetsApp
类编写的应用程序也可以使用相同的类和逻辑进行国际化。
Flutter 本地化简介
#本节提供了关于如何创建和国际化新的 Flutter 应用程序的教程,以及目标平台可能需要的任何额外设置。
你可以在 gen_l10n_example
中找到此示例的源代码。
设置国际化应用:Flutter_localizations 包
#默认情况下,Flutter 仅提供美式英语本地化。要添加对其他语言的支持,应用程序必须指定额外的 MaterialApp
(或 CupertinoApp
)属性,并包含一个名为 flutter_localizations
的包。截至 2023 年 12 月,此包支持 115 种语言和语言变体。
首先,使用 flutter create
命令在你选择的目录中创建一个新的 Flutter 应用程序。
flutter create <name_of_flutter_app>
要使用 flutter_localizations
,请将该包以及 intl
包作为依赖项添加到你的 pubspec.yaml
文件中。
flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl:any
这将创建一个包含以下条目的 pubspec.yml
文件:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: any
然后导入 flutter_localizations
库,并为你的 MaterialApp
或 CupertinoApp
指定 localizationsDelegates
和 supportedLocales
。
import 'package:flutter_localizations/flutter_localizations.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
包并添加前面的代码后,Material
和 Cupertino
包现在应该已在 115 个支持的语言环境之一中正确本地化。小部件应该适应本地化的消息,并具有正确的从左到右或从右到左的布局。
尝试将目标平台的语言环境切换到西班牙语(es
),消息应该会被本地化。
基于 WidgetsApp
的应用程序类似,但不需要 GlobalMaterialLocalizations.delegate
。
推荐使用完整的 Locale.fromSubtags
构造函数,因为它支持 scriptCode
,尽管 Locale
默认构造函数仍然完全有效。
localizationsDelegates
列表的元素是生成本地化值集合的工厂。GlobalMaterialLocalizations.delegate
为 Material Components 库提供本地化字符串和其他值。GlobalWidgetsLocalizations.delegate
为小部件库定义默认文本方向,可以是左到右或右到左。
本页介绍了更多关于这些应用属性、它们所依赖的类型以及国际化 Flutter 应用通常如何组织的信息。
覆盖语言环境
#Localizations.override
是 Localizations
小部件的工厂构造函数,它允许(通常很少见)应用程序的某个部分需要本地化到与设备配置的语言环境不同的语言环境的情况。
要观察此行为,请添加对 Localizations.override
和一个简单的 CalendarDatePicker
的调用。
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
包后,你可以配置本地化。要为应用程序添加本地化文本,请完成以下说明:
将
intl
包添加为依赖项,并拉取由flutter_localizations
锁定的版本。flutter pub add intl:any
打开
pubspec.yaml
文件并启用generate
标志。此标志位于 pubspec 文件的flutter
部分。yaml# The following section is specific to Flutter. flutter: generate: true # Add this line
在 Flutter 项目的根目录中添加一个新的 yaml 文件。将此文件命名为
l10n.yaml
并包含以下内容:yamlarb-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
文件中生成本地化。
- 将 应用程序资源包 (
在
${FLUTTER_PROJECT}/lib/l10n
中,添加app_en.arb
模板文件。例如:json{ "helloWorld": "Hello World!", "@helloWorld": { "description": "The conventional newborn programmer greeting" } }
在同一目录中添加另一个名为
app_es.arb
的捆绑文件。在此文件中,添加相同消息的西班牙语翻译。json{ "helloWorld": "¡Hola Mundo!" }
现在,运行
flutter pub get
或flutter run
,代码生成将自动进行。你可以在通过arb-dir
或output-dir
选项指定的路径目录中找到生成的文件。或者,你也可以运行flutter gen-l10n
来生成相同的文件而无需运行应用程序。在调用
MaterialApp
构造函数时,添加app_localizations.dart
和AppLocalizations.delegate
的导入语句。dartimport 'l10n/app_localizations.dart';
dartreturn 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
类还提供自动生成的localizationsDelegates
和supportedLocales
列表。你可以使用这些列表,而不是手动提供。dartconst MaterialApp( title: 'Localizations Sample App', localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, );
Material 应用启动后,你可以在应用的任何地方使用
AppLocalizations
。dartappBar: 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
。
return MaterialApp(
onGenerateTitle: (context) => DemoLocalizations.of(context).title,
占位符、复数和选择
#你还可以使用特殊语法在消息中包含应用程序值,该语法使用一个占位符来生成方法而不是 getter。占位符必须是有效的 Dart 标识符名称,它将成为 AppLocalizations
代码中生成方法的位置参数。通过将占位符名称用花括号括起来进行定义,如下所示:
"{placeholderName}"
在应用程序的 .arb
文件中的 placeholders
对象中定义每个占位符。例如,要定义带有 userName
参数的问候消息,请将以下内容添加到 lib/l10n/app_en.arb
中:
"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
的代码替换为以下内容:
// 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
字段是必需的。以下示例显示了可用的选项:
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"
前面的表达式被与 countPlaceholder
值对应的消息变体(message0
、message1
等)替换。只有 messageOther
字段是必需的。
以下示例定义了一个将单词“wombat”复数化的消息:
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
"description": "A plural message",
"placeholders": {
"count": {
"type": "num",
"format": "compact"
}
}
}
通过传入 count
参数来使用复数方法。
// 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
占位符选择一个值。这最常用于支持有性别的语言。语法如下:
"{selectPlaceholder, select, case{message} ... other{messageOther}}"
下一个示例定义了一个根据性别选择代词的消息:
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
"description": "A gendered message",
"placeholders": {
"gender": {
"type": "String"
}
}
}
通过将性别字符串作为参数传入来使用此功能。
// 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
标志:
use-escaping: true
解析器会忽略用一对单引号括起来的任何字符串。要使用普通的单引号字符,请使用一对连续的单引号。例如,以下文本被转换为 Dart String
:
{
"helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}
结果字符串如下:
"Hello! {Isn't} this a wonderful day?"
包含数字和货币的消息
#数字,包括代表货币值的数字,在不同的语言环境中显示方式差异很大。flutter_localizations
中的本地化生成工具使用 intl
包中的 NumberFormat
类,根据语言环境和所需格式来格式化数字。
int
、double
和 num
类型可以使用以下任何 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
文件进行以下更改:
"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
进行格式化:
"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”。
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))
iOS 本地化:更新 iOS 应用包
#尽管本地化由 Flutter 处理,你仍需要在 Xcode 项目中添加支持的语言。这可确保你的 App Store 条目正确显示支持的语言。
要配置应用支持的语言环境,请使用以下说明:
打开你的项目的
ios/Runner.xcodeproj
Xcode 文件。在项目导航器中,选择项目下的
Runner
项目文件。在项目编辑器中选择
Info
选项卡。在本地化部分,单击
Add
按钮 (+
) 将支持的语言和地区添加到你的项目。当要求选择文件和参考语言时,只需选择Finish
。Xcode 会自动创建空的
.strings
文件并更新ios/Runner.xcodeproj/project.pbxproj
文件。这些文件由 App Store 用于确定你的应用程序支持哪些语言和地区。
进一步定制的高级主题
#本节介绍了自定义本地化 Flutter 应用程序的其他方法。
高级语言环境定义
#一些具有多个变体的语言需要不仅仅是语言代码才能正确区分。
例如,要完全区分所有中文变体,需要指定语言代码、脚本代码和国家代码。这是因为存在简体和繁体脚本,以及同一种脚本类型中字符书写方式的地区差异。
为了完整表达国家代码 CN
、TW
和 HK
的每种中文变体,支持的语言环境列表应包含:
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()
查找应用程序的当前语言环境。
Locale myLocale = Localizations.localeOf(context);
指定应用的 supportedLocales 参数
#尽管 flutter_localizations
库目前支持 115 种语言和语言变体,但默认情况下仅提供英语翻译。由开发人员决定具体支持哪些语言。
MaterialApp
的 supportedLocales
参数限制了语言环境的更改。当用户更改设备上的语言环境设置时,应用程序的 Localizations
小部件仅在新语言环境是此列表的成员时才随之更改。如果找不到与设备语言环境完全匹配的项,则使用具有匹配 languageCode
的第一个受支持语言环境。如果失败,则使用 supportedLocales
列表的第一个元素。
想要使用不同“语言环境解析”方法的应用程序可以提供 localeResolutionCallback
。例如,要让你的应用程序无条件接受用户选择的任何语言环境:
MaterialApp(
localeResolutionCallback: (locale, supportedLocales) {
return locale;
},
);
配置 l10n.yaml 文件
#l10n.yaml
文件允许你配置 gen-l10n
工具以指定以下内容:
- 所有输入文件的位置
- 所有输出文件应创建的位置
- 为你的本地化代理指定什么 Dart 类名
有关选项的完整列表,请在命令行运行 flutter gen-l10n --help
或参阅下表:
选项 | 描述 |
---|---|
arb-dir | 模板和翻译后的 arb 文件所在的目录。默认值为 lib/l10n 。 |
output-dir | 生成本地化类写入的目录。此选项仅在你想在 Flutter 项目中的其他位置生成本地化代码时才相关。你还需要将 synthetic-package 标志设置为 false。应用程序必须从此目录导入在 output-localization-file 选项中指定的文件。如果未指定,则默认与 arb-dir 中指定的输入目录相同。 |
template-arb-file | 用作生成 Dart 本地化和消息文件的基础的模板 arb 文件。默认值为 app_en.arb 。 |
output-localization-file | 输出本地化和本地化代理类的文件名。默认值为 app_localizations.dart 。 |
untranslated-messages-file | 描述尚未翻译的本地化消息的文件位置。使用此选项会在目标位置创建一个 JSON 文件,格式如下:"locale": ["message_1", "message_2" ... "message_n"] 如果未指定此选项,则未翻译消息的摘要将在命令行中打印。 |
output-class | 用于输出本地化和本地化代理类的 Dart 类名。默认值为 AppLocalizations 。 |
preferred-supported-locales | 应用程序首选支持的语言环境列表。默认情况下,工具按字母顺序生成支持的语言环境列表。使用此标志可默认使用不同的语言环境。 例如,如果设备支持,则传入 [ en_US ] 以默认使用美式英语。 |
header | 要添加到生成的 Dart 本地化文件开头的头部。此选项接受一个字符串。 例如,传入 "/// All localized files." 以将此字符串添加到生成的 Dart 文件开头。或者,查看 header-file 选项以传入一个文本文件以获取更长的头部。 |
header-file | 要添加到生成的 Dart 本地化文件开头的头部。此选项的值是包含插入到每个生成 Dart 文件顶部的头部文本的文件名。 或者,查看 header 选项以传入一个字符串以获取更简单的头部。此文件应放置在 arb-dir 中指定的目录中。 |
[no-]use-deferred-loading | 指定是否生成 Dart 本地化文件,其中语言环境以延迟方式导入,从而允许在 Flutter Web 中延迟加载每个语言环境。 这可以通过减小 JavaScript 包的大小来减少 Web 应用的初始启动时间。当此标志设置为 true 时,特定语言环境的消息仅在需要时由 Flutter 应用下载和加载。对于具有许多不同语言环境和许多本地化字符串的项目,延迟加载可以提高性能。对于语言环境数量较少的项目,差异可以忽略不计,并且与将本地化与应用程序的其余部分捆绑相比,可能会减慢启动速度。 请注意,此标志不影响其他平台,例如移动或桌面。 |
gen-inputs-and-outputs-list | 指定后,该工具会生成一个 JSON 文件,其中包含工具的输入和输出,文件名为 gen_l10n_inputs_and_outputs.json 。这对于跟踪生成最新本地化集时使用了 Flutter 项目的哪些文件很有用。例如,Flutter 工具的构建系统使用此文件来跟踪热重载期间何时调用 gen_l10n。 此选项的值是生成 JSON 文件的目录。当为 null 时,将不会生成 JSON 文件。 |
synthetic-package | 确定生成的输出文件是作为合成包生成,还是在 Flutter 项目中的指定目录中生成。此标志默认设置为 true 。当 synthetic-package 设置为 false 时,默认情况下会在 arb-dir 指定的目录中生成本地化文件。如果指定了 output-dir ,则文件将在那里生成。 |
project-dir | 指定后,该工具会将传递到此选项的路径用作 Flutter 根项目的目录。 当为 null 时,使用当前工作目录的相对路径。 |
[no-]required-resource-attributes | 要求所有资源 ID 包含相应的资源属性。 默认情况下,简单消息不需要元数据,但强烈建议使用,因为这为读者提供了消息含义的上下文。 复数消息仍然需要资源属性。 |
[no-]nullable-getter | 指定本地化类 getter 是否可为空。 默认情况下,此值为 true,以便 Localizations.of(context) 返回一个可为空的值以实现向后兼容性。如果此值为 false,则会对 Localizations.of(context) 的返回值执行空检查,从而无需在用户代码中进行空检查。 |
[no-]format | 指定后,在生成本地化文件后运行 dart format 命令。 |
use-escaping | 指定是否启用单引号作为转义语法。 |
[no-]suppress-warnings | 指定后,所有警告将被抑制。 |
[no-]relax-syntax | 指定后,语法将放宽,以便特殊字符“{”在后面没有有效占位符时被视为字符串,并且“}”在不关闭任何以前被视为特殊字符的“{”时被视为字符串。 |
[no-]use-named-parameters | 是否为生成的本地化方法使用命名参数。 |
Flutter 国际化工作原理
#本节涵盖了 Flutter 中本地化工作的技术细节。如果你计划支持自己的本地化消息集,以下内容会很有帮助。否则,你可以跳过本节。
加载和检索本地化值
#Localizations
小部件用于加载和查找包含本地化值集合的对象。应用程序使用 Localizations.of(context,type)
引用这些对象。如果设备的语言环境发生变化,Localizations
小部件会自动加载新语言环境的值,然后重建使用它的小部件。之所以会发生这种情况,是因为 Localizations
像 InheritedWidget
一样工作。当构建函数引用继承的小部件时,会创建对继承的小部件的隐式依赖。当继承的小部件发生变化(当 Localizations
小部件的语言环境发生变化时),其依赖上下文会被重建。
本地化值由 Localizations
小部件的 LocalizationsDelegate
列表加载。每个代理必须定义一个异步 load()
方法,该方法生成一个封装本地化值集合的对象。通常,这些对象为每个本地化值定义一个方法。
在大型应用程序中,不同的模块或包可能捆绑有自己的本地化。这就是 Localizations
小部件管理一个对象表的原因,每个 LocalizationsDelegate
一个对象。要检索由某个 LocalizationsDelegate
的 load
方法生成的对象,请指定 BuildContext
和对象的类型。
例如,Material Components 小部件的本地化字符串由 MaterialLocalizations
类定义。此类实例由 MaterialApp
类提供的 LocalizationDelegate
创建。它们可以使用 Localizations.of()
进行检索。
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
这个特殊的 Localizations.of()
表达式经常使用,因此 MaterialLocalizations
类提供了一个方便的简写:
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()
来查找它们。
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
子类称为 NnMaterialLocalizations
,LocalizationsDelegate
子类是 _NnMaterialLocalizationsDelegate
。NnMaterialLocalizations.delegate
的值是代理的一个实例,并且是使用这些本地化的应用程序所需的一切。
代理类包括基本的日期和数字格式本地化。所有其他本地化都由 NnMaterialLocalizations
中具有 String
值的属性 getter 定义,如下所示:
@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'
,因为有时字符串包含带有 $
前缀的变量。这些变量由参数化的本地化方法扩展。
@override
String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount';
@override
String get pageRowsInfoTitleApproximateRaw =>
r'$firstRow–$lastRow of about $rowCount';
还需要指定语言环境的日期模式和符号,它们在源代码中定义如下:
const nnLocaleDatePatterns = {
'd': 'd.',
'E': 'ccc',
'EEEE': 'cccc',
'LLL': 'LLL',
// ...
}
const nnDateSymbols = {
'NAME': 'nn',
'ERAS': <dynamic>['f.Kr.', 'e.Kr.'],
需要修改这些值,以便语言环境使用正确的日期格式。不幸的是,由于 intl
库对于数字格式化不具备相同的灵活性,因此必须在 _NnMaterialLocalizationsDelegate
中使用现有语言环境的格式化作为替代。
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。
一旦你实现了针对特定语言的 GlobalMaterialLocalizations
和 LocalizationsDelegate
子类,你需要将该语言和一个代理实例添加到你的应用程序中。以下代码将应用程序语言设置为新挪威语,并将 NnMaterialLocalizations
代理实例添加到应用程序的 localizationsDelegates
列表:
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 中:
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
,因为不需要进行异步加载。
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
需要两个步骤。
将应用程序的根目录作为当前目录,从
lib/main.dart
生成l10n/intl_messages.arb
:dart run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart
intl_messages.arb
文件是一个 JSON 格式的映射,其中包含main.dart
中定义的每个Intl.message()
函数的一个条目。此文件用作英语和西班牙语翻译(intl_en.arb
和intl_es.arb
)的模板。这些翻译由你(开发人员)创建。将应用程序的根目录作为当前目录,为每个
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()
查找它们。
更多信息
#如果你通过阅读代码学习效果最佳,请查看以下示例。
minimal
minimal
示例旨在尽可能简单。intl_example
使用intl
包提供的 API 和工具。
如果你对 Dart 的 intl
包不熟悉,请查看 使用 Dart intl 工具。