Flutter 應用裡的國際化
如果你的 app 會部署給說其他語言的使用者使用,那麼你就需要對它進行國際化。這就意味著你在編寫 app 的時候,需要採用一種容易對它進行本地化的方式進行開發,這種方式讓你能夠為每一種語言或者 app 所支援的語言環境下的文字和佈局等進行本地化。 Flutter 提供了 widgets 和類來幫助開發者進行國際化,當然 Flutter 函式庫本身就是國際化的。
由於大多數應用程式都是以這種方式編寫的,因此該頁面主要介紹了使用 MaterialApp
和 CupertinoApp
對 Flutter 應用程式進行本地化所需的概念和工作流程。但是,使用較低級別的 WidgetsApp
類編寫的應用程式也可以使用相同的類別和邏輯進行國際化。
Flutter 應用本地化介紹
本節主要介紹如何對 Flutter 應用進行國際化,以及針對目標平台需要設定的其他內容。
你可以在 gen_l10n_example
倉庫找到原始碼。
設定一個國際化的 app:flutter_localizations package
預設情況下,Flutter 只提供美式英語的本地化。如果想要新增其他語言,你的應用必須指定額外的
MaterialApp
或者 CupertinoApp
屬性並且新增一個名為 flutter_localizations
的 package。截至到 2023 年 12 月份,這個 package 已經支援 115 種語言。
若要開始使用,在 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
套件現在應該被正確地本地化為 115 個受支援的語言環境之一。
widget 應當與本地化訊息保持同步,並具有正確的從左到右或從右到左的佈局。
你可以嘗試將目標平台的語言環境切換為西班牙語 (es
),然後應該可以發現訊息已經被本地化了。
基於 WidgetsApp
建立的 app 在新增語言環境時,除了 GlobalMaterialLocalizations.delegate
不需要之外,其他的操作是類似的。
雖然 語言環境 (Locale)
預設的建構式函式是完全沒有問題的,但是還是建議大家使用 Locale.fromSubtags
的建構式函式,因為它支援設定 文字程式碼。
localizationDelegates
陣列是用於生成本地化值集合的工廠。
GlobalMaterialLocalizations.delegate
為 Material 元件庫提供本地化的字串和一些其他的值。
GlobalWidgetsLocalizations.delegate
為 widgets 函式庫定義了預設的文字排列方向,由左到右或者由右到左。
想知道更多關於這些 app 屬性,它們依賴的型別以及那些國際化的 Flutter app 通常是如何組織的,可以繼續閱讀下面內容。
過載語言
Localizations.override
提供了一個工廠構造方法,使得你可以從在某一個位置設定與應用不同的語言(非一般情況)。
下面的範例展示了 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
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
該檔案用於設定本地化工具。它為你的專案設定瞭如下內容:
-
將 應用資源套件 (
.arb
) 的輸入路徑指定為${FLUTTER_PROJECT}/lib/l10n
。.arb
檔案提供了應用的本地化資源。 -
將英文的語言樣板設定為
app_en.arb
。 -
指定 Flutter 生成本地化內容到
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 run
指令,你將在${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n
中看到生成的檔案。同樣的,你可以在應用沒有執行的時候執行flutter 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, );
-
Material 應用啟動後,你就可以在應用的任意地方使用
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,
佔位符、複數和選項
你還可以使用特殊語法在訊息中包含應用程式的值,該語法使用 佔位符 生成方法(並非 getter)。佔位符必須是有效的 Dart 識別符號名稱,它將成為 AppLocalizations
程式碼中生成方法的位置引數。用大括號定義佔位符名稱,如下所示:
"{placeholderName}"
在應用程式 .arb
檔案內的 placeholders
物件中定義每個佔位符。例如,需要定義帶有 userName
引數的 hello 訊息,請在 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
欄位是必填的。
下面的範例定義了「袋熊」複數化的訊息:
"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
package 中的
NumberFormat
類,根據本地化和所需的格式來格式化數字。
int
、double
和 number
型別可以使用以下任何一個 NumberFormat
建構式函式:
|
|
---|---|
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
建構式函式提供了可選的命名引數。這些引數可以指定為 placeholders 中 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
package 中的
DateFormat
格式化。
格式變體共有 41 種,由 DateFormat
factory 建構式函式的名稱標識。在下面的範例種,出現在 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 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);
指定應用程式 supportedLocales 引數
儘管 flutter_localizations
函式庫目前支援 115 種語言和語言變體,但預設情況下僅提供英語譯文。具體支援哪些語言由開發人員決定。
MaterialApp
的 supportedLocales
引數限制了本地化設定的更改。當用戶更改裝置上的語言設定時,只有在 supportedLocales
引數清單中包含了使用者更改的本地化語言設定的情況下,應用程式的 Localizations
widget 才會生效。如果找不到與裝置本地化完全對應的語言,則會使用與 languageCode
對應的第一個受支援的語言。如果仍然找不到,則使用 supportedLocales
清單中的第一個元素。
如果應用程式希望使用不同的「本地化解析」方法,可以提供 localeResolutionCallback
。例如,應用程式可以無條件接受使用者選擇的任何語言:
MaterialApp(
localeResolutionCallback: (
locale,
supportedLocales,
) {
return locale;
},
);
設定 l10n.yaml 檔案
透過 l10n.yaml
檔案,你可以設定 gen-l10n
工具,指定以下內容:
-
所有輸入檔案的位置
-
所有輸出檔案的建立位置
-
為本地化委託賦予自定義的 Dart 類別名稱
獲取完整的選項清單,可在指令行中執行 flutter gen-l10n --help
或參考下表內容:
|
|
---|---|
arb-dir |
lib/l10n 。 |
output-dir |
synthetic-package 標誌設為 false。應用程式必須從該目錄匯入 output-localization-file 選項中指定的檔案。如果未指定,則預設與 arb-dir 中指定的輸入目錄相同。 |
template-arb-file |
app_en.arb 。 |
output-localization-file |
app_localizations.dart 。 |
untranslated-messages-file |
"locale": ["message_1", "message_2" ... "message_n"] 如果未指定此選項,則會在指令行中列印尚未翻譯的訊息摘要。 |
output-class |
AppLocalizations 。 |
preferred-supported-locales |
例如,裝置支援美式英語,則輸入 [ en_US ] 預設為美式英語。 |
header |
例如,輸入 "/// All localized files." ,就會在生成的 Dart 檔案中預置這個字串。 或者,還可以使用 header-file 選項來傳遞一個文字檔案,以獲得更長的標頭檔案。 |
header-file |
或者,還可以使用 header 選項來傳遞一個字串,以獲得更簡單的標頭檔案。 該檔案應放在 arb-dir 中指定的目錄下。 |
[no-]use-deferred-loading |
這可以減少 JavaScript 程式的大小,從而縮短 web 應用的初始啟動時間。當此標記設定為 true 時,Flutter 應用程式只會在需要時下載和載入特定語言的訊息。對於具有大量不同本地化字串的專案,延遲載入可以提高效能。對於本地化字串數量較少的專案,兩者之間的差異可以忽略不計,但是將本地化字串與應用程式的其他部分捆綁在一起相比,可能會降低啟動速度。 請注意,此標記不會影響移動或桌面等其他平台。 |
gen-inputs-and-outputs-list |
gen_l10n_inputs_and_outputs.json 。 這對於追蹤生成最新的本地化時使用了 Flutter 專案中的哪些檔案非常有用。例如,Flutter 工具的建立系統會使用此檔案來追蹤在熱過載期間何時呼叫 gen_l10n。 該選項的值是生成 JSON 檔案的目錄。如果為空,則不會生成 JSON 檔案。 |
synthetic-package |
true 。 synthetic-package 設定為 false 時,預設會在 arb-dir 指定的目錄下生成本地化檔案。如果指定了 output-dir 目錄,則會在該目錄下生成檔案。 |
project-dir |
如果為空,則使用當前工作目錄的相對路徑。 |
[no-]required-resource-attributes |
預設情況下,簡單訊息不需要元資料,但強烈建議使用元素據,因為它能為讀者提供訊息含義的上下文。 複數訊息仍然需要資源屬性。 |
[no-]nullable-getter |
預設情況下,該值為 true,這樣 Localizations.of(context) 就會回傳一個可歸零的值,以實現向下相容。如果該值為 false,則會對 Localizations.of(context) 回傳的值進行空值檢查,從而無需在使用者程式碼中進行空值檢查。 |
[no-]format |
dart format 指令。 |
use-escaping |
|
[no-]suppress-warnings |
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,
為 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/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
的子類別,你只需要給你的 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()
來查閱這些本地化的訊息。
更多訊息
如果你希望透過程式碼進行學習,你可以檢視以下的範例。
如果你還未使用過 intl
package,你可以閱讀 如何使用 Dart 的 intl 工具。