一. 國際化的認識
開發一個App,如果我們的App需要面向不同的語種(比如中文、英文、繁體等),那麼我們需要對其進行國際化開發。
國際化的英文稱呼:internationalization(簡稱為i18n,取前後兩個字母,18表示中間省略字母的個數)
App國際化開發主要包括:文本國際化(包括文本的順序),Widget顯示的國際化:
- 比如我們下面開發的這個App
- 某些文本在英文環境下應該顯示為英文;
- 某些Widget在中文環境下,應該顯示中文(比如彈出的時間選擇器);
二. 國際化的適配
2.1. Widget的國際化
Flutter給我們提供的Widget默認情況下就是支持國際化,但是在沒有進行特別的設置之前,它們無論在什麼環境都是以英文的方式顯示的。
如果想要添加其他語言,你的應用必須指定額外的 MaterialApp 屬性並且添加一個單獨的 package,叫做 flutter_localizations。
- 截至到 2020 年 2 月份,這個 package 已經支持大約 77 種語言。
2.1.1. pubspec添加依賴
想要使用 flutter_localizations 的話,我們需要在 pubspec.yaml 文件中添加它作為依賴:
<code>dependencies
:flutter
:sdk
: flutterflutter_localizations
:sdk
: flutter/<code>
2.1.2. 設置MaterialApp
- 在localizationsDelegates中指定哪些Widget需要進行國際化
- 用於生產本地化值集合的工廠
- 我們這裡指定了Material、Widgets、Cupertino都使用國際化
- supportedLocales指定要支持哪些國際化
- 我們這裡指定中文和英文(也可以指定國家編碼)
<code>MaterialApp( localizationsDelegates: [ GlobalMaterialLocalizations.delegate
, GlobalCupertinoLocalizations.delegate
, GlobalWidgetsLocalizations.delegate
], supportedLocales: [ Locale("en"
), Locale("zh"
) ], )/<code>
注意:如果要指定語言代碼、文字代碼和國家代碼,可以進行如下指定方式:
<code>supportedLocales
: [ const Locale.fromSubtags(languageCode
:'zh'
), const Locale.fromSubtags(languageCode
:'zh'
,scriptCode
:'Hans'
), const Locale.fromSubtags(languageCode
:'zh'
,scriptCode
:'Hant'
), const Locale.fromSubtags(languageCode
:'zh'
,scriptCode
:'Hans'
,countryCode
:'CN'
), const Locale.fromSubtags(languageCode
:'zh'
,scriptCode
:'Hant'
,countryCode
:'TW'
), const Locale.fromSubtags(languageCode
:'zh'
,scriptCode
:'Hant'
,countryCode
:'HK'
), ],/<code>
2.1.3. 查看Widget結果
設置完成後,我們在Android上將語言切換為中文,查看結果:
但是對於iOS,將語言切換為中文,依然顯示是英文的Widget
- 這是因為iOS定義了一些應用的元數據,其中包括支持的語言環境;
- 我們必須將其對應的元數據中支持的語言添加進去;
- 元數據的設置在iOS項目中對應的info.plist文件中;
修改iOS的info.plist文件配置:
- 選擇 Information Property List 項;
- 從 Editor 菜單中選擇 Add Item,然後從彈出菜單中選擇 Localizations;
- 為array添加一項選擇 Add Item,選擇Chinese;
配置完成後,卸載之前的app,重新安裝:
2.2. 其它文本國際化
App中除了有默認的Widget,我們也希望對自己的文本進行國際化,如何做到呢?
2.2.1. 創建本地化類
該類用於定義我們需要進行本地化的字符串等信息:
- 1.我們需要一個構造器,並且傳入一個Locale對象(後續會使用到)
- 2.定義一個Map,其中存放我們不同語言對應的顯示文本
- 3.定義它們對應的getter方法,根據語言環境返回不同的結果
<code>import
'package:flutter/material.dart'
;class
HYLocalizations
{ final Locale locale; HYLocalizations(this
.locale);static
Map
<String
,Map
<String
,String
>> _localizedValues = {"en"
: {"title"
:"home"
,"greet"
:"hello~"
,"picktime"
:"Pick a Time"
},"zh"
: {"title"
:"首頁"
,"greet"
:"你好~"
,"picktime"
:"選擇一個時間"
} };String
get
title {return
_localizedValues[locale.languageCode]["title"
]; }String
get
greet {return
_localizedValues[locale.languageCode]["greet"
]; }String
get
pickTime {return
_localizedValues[locale.languageCode]["picktime"
]; } }/<code>
2.2.2. 自定義Delegate
上面的類定義好後,我們在什麼位置或者說如何對它進行初始化呢?
- 答案是我們可以像Flutter Widget中的國際化方式一樣對它們進行初始化;
- 也就是我們也定義一個對象的Delegate類,並且將其傳入localizationsDelegates中;
- Delegate的作用就是當Locale發生改變時,調用對應的load方法,重新加載新的Locale資源;
HYLocalizationsDelegate需要繼承自LocalizationsDelegate,並且有三個方法必須重寫:
- isSupported:用於當前環境的Locale,是否在我們支持的語言範圍
- shouldReload:當Localizations Widget重新build時,是否調用load方法重新加載Locale資源
- 一般情況下,Locale資源只應該在Locale切換時加載一次,不需要每次Localizations重新build時都加載一遍;
- 所以一般情況下返回false即可;
- load方法:當Locale發生改變時(語言環境),加載對應的HYLocalizations資源
- 這個方法返回的是一個Future,因為有可能是異步加載的;
- 但是我們這裡是直接定義的一個Map,因此可以直接返回一個同步的Future(SynchronousFuture)
<code>import
'package:flutter/cupertino.dart'
;import
'package:flutter/foundation.dart'
;import
'package:i18n_demo/i18n/localizations.dart'
;class
HYLocalizationsDelegate
extends
LocalizationsDelegate
<HYLocalizations
> {bool
isSupported
(Locale locale)
{return
["en"
,"zh"
].contains(locale.languageCode); }bool
shouldReload
(LocalizationsDelegate old)
{return
false
; }Future
load
(Locale locale)
{return
SynchronousFuture(HYLocalizations(locale)); }static
HYLocalizationsDelegate delegate = HYLocalizationsDelegate(); }/<code>
2.2.3. 使用本地化類
接著我們可以在代碼中使用HYLocalization類。
- 我們可以通過Localizations.of(context, HYLocalizations)獲取到HYLocalizations對象
<code>Widget
build
(BuildContext context) {return
Scaffold
(appBar
: AppBar(title
: Text(Localizations.of(context, HYLocalizations).title), ),body
: Center(child
: Column(children
: [ Text(Localizations.of(context, HYLocalizations).greet), RaisedButton(child
: Text(Localizations.of(context, HYLocalizations).pickTime),onPressed
: () { showDatePicker(context
: context,initialDate
: DateTime.now(),firstDate
: DateTime(2019
),lastDate
: DateTime(2022
) ).then((pickTime) { }); }, ) ], ), ), ); }/<code>
當然,我們可以對Localizations.of(context, HYLocalizations)進行一個優化
- 給HYLocalizations定義一個of的靜態方法
<code>class
HYLocalizations
{static
HYLocalizationsof
(BuildContext context)
{return
Localizations.of(context, HYLocalizations); } }/<code>
接下來我們就可以通過下面的方式來使用了(其它地方也是一樣):
<code>appBar
:AppBar
(title
:Text
(HYLocalizations
.of
(context
).title
), )/<code>
2.2.4. 異步加載數據
假如我們的數據是異步加載的,比如來自Json文件或者服務器,應該如何處理呢?
這裡我們可以修改HYLocalizations的數據加載:
<code>static
Map
<String
,Map
<String
,String
>> _localizedValues = {}; Future loadJson()async
{String
jsonString =await
rootBundle.loadString("assets/json/i18n.json"
); finalMap
<String
, dynamic> map = json.decode(jsonString); _localizedValues = map.map((key, value) {return
MapEntry(key, value.cast<String
,String
>()); });return
true
; }/<code>
在HYLocalizationsDelegate中使用異步進行加載:
<code> @override
Futureload
(Locale locale
)async
{ final localization = HYLocalizations(locale);await
localization.loadJson();return
localization; }/<code>
三. 國際化的工具
3.1. 認識arb文件
目前我們已經可以通過加載對應的json文件來進行本地化了。
但是還有另外一個問題,我們在進行國際化的過程中,下面的代碼依然需要根據json文件手動編寫:
<code> String get title { return _localizedValues[locale.languageCode
]["title"
]; } String get greet { return _localizedValues[locale.languageCode
]["greet"
]; } String get pickTime { return _localizedValues[locale.languageCode
]["picktime"
]; }/<code>
有沒有一種更好的方式,讓我們可以快速在本地化文件-dart代碼文件直接來轉換呢?答案就是arb文件
- arb文件全稱Application Resource Bundle,表示應用資源包,目前已經得到Google的支持;
- 其本質就是一個json文件,但是可以根據該文件轉成對應的語言環境;
- arb的說明文檔:https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification
3.2. intl package
官方文檔推薦可以使用intl package來進行arb和dart文件之間的轉換(通過終端指令)
- https://flutter.dev/docs/development/accessibility-and-localization/internationalization#appendix-using-the-dart-intl-tools
需要在在pubspec.yaml中添加其相關的依賴,具體步驟這裡不再詳細給出,可以參考官方文檔
3.3. 使用IDE插件
在之前有一個比較好用的Android Studio的插件:Flutter i18n
- 但是這個插件已經很久不再維護了,所以不再推薦給大家使用
目前我們可以使用另外一個插件:Flutter Intl
- 該插件更新維護頻率很高,並且廣受好評;
- 另外,在Android Studio和VSCode中都是支持的
我們這裡以Android Studio為例,講解其使用過程:
3.3.1. 安裝插件
在Android Studio的Plugins中安裝插件:
3.3.2. 初始化intl
選擇工具欄Tools - Flutter Intl - Initialize for the Project
完成上面的操作之後會自動生成如下文件目錄:
- generated是自動生成的dart代碼
- I10n是對應的arb文件目錄
3.3.3. 使用intl
在localizationsDelegates中配置生成的class,名字是S
- 1.添加對應的delegate
- 2.supportedLocales使用S.delegate.supportedLocales
<code>localizationsDelegates: [ GlobalMaterialLocalizations.delegate
, GlobalWidgetsLocalizations.delegate
, GlobalCupertinoLocalizations.delegate
, HYLocalizationsDelegate.delegate
, S.delegate
], supportedLocales: S.delegate
.supportedLocales,/<code>
因為我們目前還沒有對應的本地化字符串,所以需要在intl_en.arb文件中編寫:
- 編寫後ctrl(command) + s保存即可
<code>{"title"
:"home"
,"greet"
:"hello~"
,"picktime"
:"Pick a time"
}/<code>
在代碼中使用即可
- 按照如下格式:S.of(context).title
<code>@override
Widget build(BuildContext context) {return
Scaffold
(appBar
: AppBar(title
: Text(S.of(context).title), ),body
: Center(child
: Column(children
: [ Text(S.of(context).greet), RaisedButton(child
: Text(S.of(context).picktime),onPressed
: () { showDatePicker(context
: context,initialDate
: DateTime.now(),firstDate
: DateTime(2019
),lastDate
: DateTime(2022
) ).then((pickTime) { }); }, ) ], ), ), ); }/<code>
3.3.4. 添加中文
如果希望添加中文支持:add local
- 在彈出框中輸入zh即可
我們會發現,會生成對應的intl_zh.arb和messages_zh.dart文件
編寫intl_zh.arb文件:
<code>{"title"
:"首頁"
,"greet"
:"您好~"
,"picktime"
:"選擇一個時間"
}/<code>
查看界面,會根據當前語言顯示對應的語言文本
3.4. arb其它語法
如果我們希望在使用本地化的過程中傳遞一些參數:
- 比如hello kobe或hello james
- 比如你好啊,李銀河 或 你好啊,王小波
修改對應的arb文件:
- {name}:表示傳遞的參數
<code>{"title"
:"home"
,"greet"
:"hello~"
,"picktime"
:"Pick a time"
,"sayHello"
:"hello {name}"
}/<code>
在使用時,傳入對應的參數即可:
<code>Text
(S
.of
(context
).sayHello
("李銀河")),/<code>
arb還有更多的語法,大家可以在之後慢慢學習和發掘~
關鍵字: arb languageCode return