Flutter 應用裡的國際化
如果你的 app 會部署給說其他語言的使用者使用,那麼你就需要對它進行國際化。這就意味著你在編寫 app 的時候,需要採用一種容易對它進行本地化的方式進行開發,這種方式讓你能夠為每一種語言或者 app 所支援的語言環境下的文字和佈局等進行本地化。 Flutter 提供了 widgets 和類來幫助開發者進行國際化,當然 Flutter 庫本身就是國際化的。
由於大多數應用程式都是以這種方式編寫的,因此該頁面主要介紹了使用 MaterialApp
和 CupertinoApp
對 Flutter 應用程式進行本地化所需的概念和工作流程。但是,使用較低級別的 WidgetsApp
類編寫的應用程式也可以使用相同的類和邏輯進行國際化。
Flutter 應用本地化介紹
本節主要介紹如何對 Flutter 應用進行國際化,以及針對目標平台需要設定的其他內容。
配置一個國際化的 app:flutter_localizations package
預設情況下,Flutter 只提供美式英語的本地化。如果想要新增其他語言,你的應用必須指定額外的
MaterialApp
或者 CupertinoApp
屬性並且新增一個名為 flutter_localizations
的 package。截至到 2023 年 1 月份,這個 package 已經支援大約 79 種語言。
若要開始使用,在 Flutter 工程資料夾下執行 flutter create
命令:
$ 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
檔案中形如:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: any
下一步,先執行 pub get packages
,然後引入 flutter_localizations 庫,然後為 MaterialApp 指定 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
package 並添加了上面的程式碼之後,
Material
和 Cupertino
包現在應該被正確地本地化為 78 個受支援的語言環境之一。
widget 應當與本地化資訊保持同步,並具有正確的從左到右或從右到左的佈局。
你可以嘗試將目標平台的語言環境切換為西班牙語(es
),然後應該可以發現資訊已經被本地化了。
基於 WidgetsApp
建構的 app 在新增語言環境時,除了 GlobalMaterialLocalizations.delegate
不需要之外,其他的操作是類似的。
雖然 語言環境 (Locale)
預設的建構函式是完全沒有問題的,但是還是建議大家使用 Locale.fromSubtags
的建構函式,因為它支援設定 文字程式碼。
localizationDelegates
陣列是用於產生本地化值集合的工廠。
GlobalMaterialLocalizations.delegate
為 Material 元件庫提供本地化的字串和一些其他的值。
GlobalWidgetsLocalizations.delegate
為 widgets 庫定義了預設的文字排列方向,由左到右或者由右到左。
想知道更多關於這些 app 屬性,它們依賴的型別以及那些國際化的 Flutter app 通常是如何組織的,可以繼續閱讀下面內容。
Overriding the Locale
Localizations.override
is a factory constructor for the Localizations
widget
that allows for (the typically rare) situation where a section of your application
needs to be localized to a different locale than the locale configured for your device.
To observe this behavior, add a call to Localizations.override
and a simple 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
widget 顯示為西班牙語了。
新增您自己的本地化資訊
引入 flutter_localizations
package 後,請按照以下說明將本地化的文字新增到您的應用。
-
將
intl
package 新增為依賴,使用any
作為flutter_localizations
的版本值:$ flutter pub add intl:any
-
另外,在
pubspec.yaml
檔案中,啟用generate
標誌。該設定項新增在 pubspec 中 Flutter 部分,通常處在 pubspec 檔案中後面的部分。# The following section is specific to Flutter. flutter: generate: true # Add this line
-
在 Flutter 專案的根目錄中新增一個新的 yaml 檔案,命名為
l10n.yaml
,其內容如下:arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart
該檔案用於配置本地化工具;在上面的範例中,指定輸入檔案在
${FLUTTER_PROJECT}/lib/l10n
中,app_en.arb
檔案提供範本,產生的本地化檔案在app_localizations.dart
檔案中。 -
在
${FLUTTER_PROJECT}/lib/l10n
中,新增app_en.arb
範本檔案。如下:{ "helloWorld": "Hello World!", "@helloWorld": { "description": "The conventional newborn programmer greeting" } }
-
接下來,在同一目錄中新增一個
app_es.arb
檔案,對同一條資訊做西班牙語的翻譯:{ "helloWorld": "¡Hola Mundo!" }
-
現在,執行
flutter gen-l10n
命令,您將在${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n
中看到產生的檔案。同樣的,你可以在應用沒有執行的時候執行那個命令來產生本地化檔案。
-
在呼叫
MaterialApp
的建構函式時候,新增import
陳述式,匯入app_localizations.dart
和AppLocalizations.delegate
。import 'package:flutter_gen/gen_l10n/app_localizations.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
類也可以自動自動產生localizationsDelegates
和supportedLocales
列表,而無需手動提供它們。const MaterialApp( title: 'Localizations Sample App', localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, );
-
現在,你可以在應用的任意地方使用
AppLocalizations
了: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 widget 會展示「Hello World!」。如果目標裝置的語言環境設定為西班牙語,則展示「Hola Mundo!」,在
arb
檔案中,每個條目的鍵值都被用作 getter 的方法名稱,而該條目的值則表示本地化的資訊。
要檢視使用該工具的範例 Flutter 應用,請參閱 gen_l10n_example
。
如需本地化裝置應用描述,你可以將本地化後的字串傳遞給
MaterialApp.onGenerateTitle
:
return MaterialApp(
onGenerateTitle: (context) => DemoLocalizations.of(context).title,
Placeholders, plurals, and selects
You can also include application values in a message with
special syntax that uses a placeholder to generate a method
instead of a getter.
A placeholder, which must be a valid Dart identifier name,
becomes a positional parameter in the generated method in the
AppLocalizations
code. Define a placeholder name by wrapping
it in curly braces as follows:
"{placeholderName}"
Define each placeholder in the placeholders
object in the app’s .arb
file.
For example, to define a hello message with a userName
parameter,
add the following to lib/l10n/app_en.arb
:
"hello": "Hello {userName}",
"@hello": {
"description": "A message with a single parameter",
"placeholders": {
"userName": {
"type": "String",
"example": "Bob"
}
}
}
Regenerate the AppLocalizations
file. This adds a hello
method call to
the AppLocalizations.of(context)
object, and the method accepts
a parameter of type String
; the hello
method returns a string.
Implement this by replacing the code passed into Builder
with the following:
// Examples of internationalized strings.
return Column(
children: <Widget>[
// Returns 'Hello John'
Text(AppLocalizations.of(context)!.hello('John')),
],
);
You can also use numerical placeholders to specify multiple values.
Different languages have different ways to pluralize words.
The syntax also supports specifying how a word should be pluralized.
A pluralized message must include a num
parameter indicating
how to pluralize the word in different situations. English, for example,
pluralizes “person” to “people”, but that doesn’t go far enough. The
message0
plural might be “no people” or “zero people”.
The messageFew
plural might be
“several people”, “some people”, or “a few people”.
The messageMany
plural might
be “most people” or “many people”, or “a crowd”.
Only the more general messageOther
field is required.
The following example shows what options are available:
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"
The expression above will be replaced by the message variation
(message0
, message1
, …) corresponding to the value
of the countPlaceholder
. Only the messageOther
field is required.
The following example defines a message that pluralizes the word, “wombat”:
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
"description": "A plural message",
"placeholders": {
"count": {
"type": "num",
"format": "compact"
}
}
}
Using a plural method is easy enough, just pass it the item count parameter:
// 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)),
],
);
Similar to plurals, you can also choose a value based on a String
placeholder.
This is most often used to support gendered languages. The syntax is:
"{selectPlaceholder, select, case{message} ... other{messageOther}}"
The following example defines a message that selects a pronoun based on gender:
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
"description": "A gendered message",
"placeholders": {
"gender": {
"type": "String"
}
}
}
To use this feature, pass the gender string as a parameter:
// 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')),
],
);
Keep in mind that when using select
statements, comparison between the
parameter and the actual value is case-sensitive. That is,
AppLocalizations.of(context)!.pronoun("Male")
will default to the
“other” case, and return “they”.
Messages with numbers and currencies
Numbers, including those that represent currency values,
are displayed very differently in different locales.
The localizations generation tool in flutter_localizations
uses the
intl
package’s NumberFormat
class
to properly format numbers based on the locale and the
desired format.
The int
, double
, and number
types can use any of the
following NumberFormat
constructors:
Message “format” value | Output for 1200000 |
---|---|
compact |
“1.2M” |
compactCurrency * |
“$1.2M” |
compactSimpleCurrency * |
“$1.2M” |
compactLong |
“1.2 million” |
currency * |
“USD1,200,000.00” |
decimalPattern |
“1,200,000” |
decimalPercentPattern * |
“120,000,000%” |
percentPattern |
“120,000,000%” |
scientificPattern |
“1E6” |
simpleCurrency * |
“$1,200,000” |
The starred NumberFormat
constructors in the table offer optional, named parameters.
Those parameters can be specified as the value of the placeholder’s optionalParameters
object.
For example, to specify the optional decimalDigits
parameter for compactCurrency
,
make the following changes to the lib/l10n/app_en.arg
file:
"numberOfDataPoints": "Number of data points: {value}",
"@numberOfDataPoints": {
"description": "A message with a formatted int parameter",
"placeholders": {
"value": {
"type": "int",
"format": "compactCurrency",
"optionalParameters": {
"decimalDigits": 2
}
}
}
}
Messages with dates
Dates strings are formatted in many different ways depending both the locale and the app’s needs.
Placeholder values with type DateTime
are formatted with
intl
’s DateFormat
class.
There are 41 format variations, identified by the names of their DateFormat
factory constructors.
In the following example, the DateTime
value that appears in the helloWorldOn
message is formatted with DateFormat.yMd
:
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
"description": "A message with a date parameter",
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMd"
}
}
}
在語言環境為英語(美國)的應用中,以下表達式將會是 7/10/1996,在俄羅斯語言環境中,它將是 10.07.1996。
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1996, 7, 10))
有關本地化工具的更多資訊,例如處理 DateTime 和複數,請參見 國際化使用者指南。
iOS 本地化:更新 iOS app bundle
iOS 應用在內置於應用程式套件中的 Info.plist
檔案中定義了關鍵的應用程式元資料,其中包括了受支援的語言環境,要配置您的應用支援的語言環境,請按照以下步驟進行操作:
-
開啟專案的
ios/Runner.xcworkspace
Xcode 檔案。 -
在 Project Navigator 中,開啟
Runner
專案的Runner
資料夾下的Info.plist
檔案。 -
選擇 Information Property List 項。然後從 Editor 選單中選擇 Add Item,接著從彈出選單中選擇 Localizations。
-
選擇並展開新建立的
Localizations
項。對於您的應用程式支援的每種語言環境,請新增一個新項,然後從 Value 欄位中的彈出選單中選擇要新增的語言環境。該列表應需要與 supportedLocales 引數中列出的語言一致。 -
新增所有受支援的語言環境後,儲存檔案。
客製的進階操作
本節介紹自訂本地 Flutter 應用程式的其他方法。
高階語言環境定義
一些具有著多個變體的語言僅用 languageCode
來區分是不夠充分的。
例如,在多語言應用開發這個話題裡,如果要更好的區分具有多種語言變體的中文,則需要指定其 languageCode
、scriptCode
和 countryCode
。因為目前有兩種主要的,且存在地區使用差異的中文書寫系統:簡體和繁體。
為了讓 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 只會解析定義在 supportedLocales
裡面的語言環境。對於那些常用語言,Flutter 為本地化內容提供了文字程式碼級別的區分。檢視 Localizations
瞭解 Flutter 是如何解析支援的語言環境和首選的語言環境的。
雖然中文是最主要的一個範例,但是其他語言如法語(fr_FR
,fr_CA
等等)也應該為了更細緻的本地化而做完全的區分。
獲取語言環境:Locale 類和 Localizations Widget
Locale
類用來識別使用者的語言。移動裝置支援為所有的應用設定語言環境,經常是透過系統設定選單來進行操作。設定完之後,國際化的 app 就會展示成對應特定語言環境的值。例如,如果使用者把裝置的語言環境從英語切換到法語,顯示 “Hello World” 的文字 widget 會使用 “Bonjour le monde” 進行重建。
Localizations
widget 定義了它的子節點的語言環境和依賴的本地化的資源。
WidgetsApp
建立了一個本地化的 widget,如果系統的語言環境變化了,它會重建這個 widget。
你可以透過呼叫 Localizations.localeOf()
方法來檢視 app 當前的語言環境:
Locale myLocale = Localizations.localeOf(context);
Specifying the app’s supportedLocales parameter
Although the flutter_localizations
library currently supports 78
languages and language variants, only English language translations
are available by default. It’s up to the developer to decide exactly
which languages to support.
The MaterialApp
supportedLocales
parameter limits locale changes. When the user changes the locale
setting on their device, the app’s Localizations
widget only
follows suit if the new locale is a member of this list.
If an exact match for the device locale isn’t found,
then the first supported locale with a matching languageCode
is used. If that fails, then the first element of the
supportedLocales
list is used.
An app that wants to use a different “locale resolution”
method can provide a localeResolutionCallback
.
For example, to have your app unconditionally accept
whatever locale the user selects:
MaterialApp(
localeResolutionCallback: (
locale,
supportedLocales,
) {
return locale;
},
);
Configuring the l10n.yaml file
The l10n.yaml
file allows you to configure the gen-l10n
tool
to specify:
- where all the input files are located
- where all the output files should be created
- what Dart class name to give your localizations delegate
For a full list of options, check out the following table:
Option | Description |
---|---|
arb-dir |
The directory where the template and translated arb files are located. The default is lib/l10n . |
output-dir |
The directory where the generated localization classes will be written. This option is only relevant if you want to generate the localizations code somewhere else in the Flutter project. You will also need to set the synthetic-package flag to false. The app must import the file specified in the output-localization-file option from this directory. If unspecified, this defaults to the same directory as the input directory specified in arb-dir . |
template-arb-file |
The template arb file that will be used as the basis for generating the Dart localization and messages files. The default is app_en.arb . |
output-localization-file |
The filename for the output localization and localizations delegate classes. The default is app_localizations.dart . |
untranslated-messages-file |
The location of a file that describes the localization messages have not been translated yet. Using this option will create a JSON file at the target location, in the following format: "locale": ["message_1", "message_2" ... "message_n"] If this option is not specified, a summary of the messages that have not been translated will be printed on the command line. |
output-class |
The Dart class name to use for the output localization and localizations delegate classes. The default is AppLocalizations . |
preferred-supported-locales |
The list of preferred supported locales for the application. By default, the tool will generate the supported locales list in alphabetical order. Use this flag if you would like to default to a different locale. For example, pass in [ en_US ] if you would like your app to default to American English if a device supports it. |
synthetic-package |
Determines whether or not the generated output files will be generated as a synthetic package or at a specified directory in the Flutter project. This flag is set to true by default. When synthetic-package is set to false , it will generate the localizations files in the directory specified by arb-dir by default. If output-dir is specified, files will be generated there. |
header |
The header to prepend to the generated Dart localizations files. This option takes in a string. For example, pass in "/// All localized files." if you would like this string prepended to the generated Dart file. Alternatively, see the header-file option to pass in a text file for longer headers. |
header-file |
The header to prepend to the generated Dart localizations files. The value of this option is the name of the file that contains the header text which will be inserted at the top of each generated Dart file. Alternatively, see the header option to pass in a string for a simpler header. This file should be placed in the directory specified in arb-dir . |
[no-]use-deferred-loading |
Whether to generate the Dart localization file with locales imported as deferred, allowing for lazy loading of each locale in Flutter web. This can reduce a web app’s initial startup time by decreasing the size of the JavaScript bundle. When this flag is set to true, the messages for a particular locale are only downloaded and loaded by the Flutter app as they are needed. For projects with a lot of different locales and many localization strings, it can be a performance improvement to have deferred loading. For projects with a small number of locales, the difference is negligible, and might slow down the start up compared to bundling the localizations with the rest of the application. Note that this flag does not affect other platforms such as mobile or desktop. |
Flutter 裡的國際化是如何工作的
本節涵蓋了 Flutter 中本地化工作的技術細節,如果你計劃使用自定的一套本地化訊息,下面的內容會很有幫助。反之則可以跳過本節。
載入和獲取本地化值
我們使用 Localizations
widget 來載入和查詢那些包含本地化值集合的物件。
app 透過呼叫 Localizations.of(context,type)
來參考這些物件。如果裝置的語言環境變化了,Localizations widget 會自動地載入新的語言環境的值,然後重建那些使用了語言環境的 widget。這是因為 Localizations
像 繼承 widget 一樣執行。當一個建構過程涉及到繼承 widget,對繼承 widget 的隱含依賴就建立了。當一個繼承 widget 變化了(即 Localizations widget 的語言環境變化),它的依賴上下文就會被重建。
本地化的值是透過使用 Localizations
widget 的 LocalizationsDelegate
載入的。每一個 delegate 必須定義一個非同步的 load()
方法。這個方法生成了一個封裝本地化值的物件,通常這些物件為每個本地化的值定義了一個方法。
在一個大型的 app 中,不同的模組或者 package 需要和它們對應的本地化資源打包在一起。這就是為什麼 Localizations
widget 管理著物件的一個對應表,每個 LocalizationsDelegate
對應一個物件。為了獲得由 LocalizationsDelegate
的 load
方法產生的物件,你需要指定一個建構上下文 (BuildContext
) 和物件的型別。
例如,Material 元件 widget 的本地化字串是由 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,
Defining a class for the app’s localized resources
為 app 的本地化資源定義一個類別
綜合所有這些在一起,一個需要國際化的 app 經常以一個封裝 app 本地化值的類開始的。下面是使用這種類別的典型範例。
此範例 app 的 完整的原始碼。
這個範例是基於 intl
package 提供的 API 和工具開發的,
app 本地化資源的替代方法
裡面講解了一個不依賴於 intl package 的 範例。
DemoLocalizations
類包含了 app 語言環境內支援的已經翻譯成了本地化語言的字串(本例子只有一個)。它透過呼叫由 Dart 的 intl
package 產生的
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
package 的類引入了一個產生好的資訊目錄,它提供了 initializeMessage()
方法和 Intl.message()
方法的每個語言環境的備份儲存。
intl
工具 透過分析包含 Intl.message()
呼叫類別的原始碼產生這個資訊目錄。在當前情況下,就是 DemoLocalizations 的類(包含了 Intl.message()
呼叫)。
新增支援新的語言
如果你要開發一個 app 需要支援的語言不在 GlobalMaterialLocalizations
當中,那就需要做一些額外的工作:它必須提供大概 70 個字和詞還有日期以及符號的翻譯(本地化)。
舉個例子,我們將給大家展示如何支援挪威尼諾斯克語。
我們需要定義一個新的 GlobalMaterialLocalizations
子類別,它定義了 Material 庫依賴的本地化資源。同時,我們也必須定義一個新的 LocalizationsDelegate
子類別,它是給 GlobalMaterialLocalizations
子類別作為一個工廠使用的。
這是支援新增一種新語言的一個完整例子的原始碼,相對實際上要翻譯的尼諾斯克語數量,我們只翻譯了一小部分。
這個特定語言環境的 GlobalMaterialLocalizations
子類別被稱為 NnMaterialLocalizations
,
LocalizationsDelegate
子類別被稱為 _NnMaterialLocalizationsDelegate
。
BeMaterialLocalizations.delegate
是 delegate 的一個例項,這就是 app 使用這些本地化所需要的全部。
delegate 類包括基本的日期和數字格式的本地化。其他所有的本地化是由 BeMaterialLocalizations
裡面的 String
字串值屬性的 getters 所定義的,像下面這樣:
@override
String get moreButtonTooltip => r'More';
@override
String get aboutListTileTitleRaw => r'About $applicationName';
@override
String get alertDialogLabel => r'Alert';
當然,這些都是英語翻譯。為了完成本地化操作,你需要把每一個 getter 的返回值翻譯成合適的新挪威語 (Nynorsk) 字串。
像 r'About $applicationName'
一樣,這些帶 r 字首的 getters 返回的是原始的字串,因為有一些時候這些字串會包含一些帶有 $
字首的變數。透過呼叫帶引數的本地化方法,這些變數會被替換:
@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/intl/blob/master/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
的子類別,你只需要給你的 app 新增此語言以及一個 delegate 的例項。這裡有一些程式碼展示瞭如何設定 app 的語言為尼諾斯克語以及如何給 app 的
localizationsDelegates
列表新增 NnMaterialLocalizations
delegate 例項。
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
package 定義,為了簡單起見,或者可能想要與不同的 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']!;
}
}
在 minimal 應用中,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
package 進行建構 API 之前,你應該想要了解一下 intl
package 的文件。
這個 demo app 依賴於一個產生的原始檔,叫做 l10n/messages_all.dart
,這個檔案定義了 app 使用的所有本地化的字串。
重建 l10n/messages_all.dart
需要 2 步。
-
在 app 的根目錄,使用
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
分別作為英語和西班牙語翻譯的範本。這些翻譯是由你(開發者)來建立的。 -
在 app 的根目錄,產生每個
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 系統不支援檔名萬用字元。列出的
.arb
檔案是由intl_translation:extract_to_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()
來查閱這些本地化的資訊。