一. 國際化的認(rèn)識
開發(fā)一個App德挣,如果我們的App需要面向不同的語種(比如中文内狗、英文、繁體等)考传,那么我們需要對齊進行國際化開發(fā)。
國際化的英文稱呼:internationalization(簡稱為i18n证鸥,取前后兩個字母僚楞,18表示中間省略字母的個數(shù))。
App國際化開發(fā)主要包括:文本國際化(包括文本的順序)枉层,Widget顯示的國際化泉褐,比如我們下面開發(fā)的這個App:
- 某些文本在英文環(huán)境下應(yīng)該顯示為英文;
- 某些Widget在中文環(huán)境下鸟蜡,應(yīng)該顯示中文(比如彈出的時間選擇器)膜赃;
二. 國際化的適配
2.1. Widget的國際化
Flutter給我們提供的Widget默認(rèn)情況下就是支持國際化,但是在沒有進行特別的設(shè)置之前揉忘,它們無論在什么環(huán)境都是以英文的方式顯示的跳座。
如果想要添加其他語言,你的應(yīng)用必須指定額外的 MaterialApp 屬性并且添加一個單獨的 package泣矛,叫做 flutter_localizations
疲眷。
截至到 2020 年 2 月份,這個 package 已經(jīng)支持大約 77 種語言您朽。
2.1.1. pubspec添加依賴
想要使用 flutter_localizations
的話狂丝,我們需要在 pubspec.yaml
文件中添加它作為依賴:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
2.1.2. 設(shè)置MaterialApp
- 設(shè)置localizationsDelegates中指定哪些Widget需要進行國際化
- 用于生產(chǎn)本地化值集合的工廠
- 我們這里指定了Material、Widgets、Cupertino都使用國際化
- supportedLocales指定要支持哪些國際化
- 我們這里指定中文和英文(也可以指定國家編碼)
MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate, // 指定本地化的字符串和一些其他的值
GlobalCupertinoLocalizations.delegate, // 對應(yīng)的Cupertino風(fēng)格
GlobalWidgetsLocalizations.delegate // 指定默認(rèn)的文本排列方向, 由左到右或由右到左
],
supportedLocales: [
Locale("en"),
Locale("zh")
],
)
注意:如果要指定語言代碼几颜、文字代碼和國家代碼倍试,可以進行如下指定方式:
// Full Chinese support for CN, TW, and HK
supportedLocales: [
const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
],
2.1.3. 查看Widget結(jié)果
設(shè)置完成后,我們在Android上將語言切換為中文蛋哭,查看結(jié)果:
但是對于iOS县习,將語言切換為中文,依然顯示是英文的Widget
- 這是因為iOS定義了一些應(yīng)用的元數(shù)據(jù)谆趾,其中包括支持的語言環(huán)境准颓;
- 我們必須將其對應(yīng)的元數(shù)據(jù)中支持的語言添加進去;
- 元數(shù)據(jù)的設(shè)置在iOS項目中對應(yīng)的info.plist文件中棺妓;
修改iOS的info.plist文件配置:
- 選擇 Information Property List 項;
- 從 Editor 菜單中選擇 Add Item炮赦,然后從彈出菜單中選擇 Localizations怜跑;
- 為array添加一項選擇 Add Item,選擇Chinese吠勘;
配置完成后性芬,卸載之前的app,重新安裝:
2.2. 其它文本國際化
App中除了有默認(rèn)的Widget剧防,我們也希望對自己的文本進行國際化植锉,如何做到呢?
2.2.1. 創(chuàng)建本地化類
該類用于定義我們需要進行本地化的字符串等信息:
- 我們需要一個構(gòu)造器峭拘,并且傳入一個Locale對象(后續(xù)會使用到)
- 定義一個Map俊庇,其中存放我們不同語言對應(yīng)的顯示文本
- 定義它們對應(yīng)的getter方法,根據(jù)語言環(huán)境返回不同的結(jié)果
新建一個i18n文件夾鸡挠,新建localizations.dart文件如下:
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"];
}
}
2.2.2. 自定義Delegate
上面的類定義好后辉饱,我們在什么位置或者說如何對它進行初始化呢?
- 答案是我們可以像Flutter Widget中的國際化方式一樣對它們進行初始化拣展;
- 也就是我們也定義一個對象的Delegate類彭沼,并且將其傳入localizationsDelegates中;
- Delegate的作用就是當(dāng)
Locale發(fā)生改變
時备埃,調(diào)用對應(yīng)的load方法
姓惑,重新加載新的Locale資源
;
HYLocalizationsDelegate需要繼承自LocalizationsDelegate按脚,并且有三個方法必須重寫:
- isSupported:用于當(dāng)前環(huán)境的Locale于毙,是否在我們支持的語言范圍
- shouldReload:當(dāng)Localizations Widget重新build時,是否調(diào)用load方法重新加載Locale資源
- 一般情況下辅搬,Locale資源只應(yīng)該在Locale切換時加載一次望众,不需要每次Localizations重新build時都加載一遍;
- 所以一般情況下返回false即可;
- load方法:當(dāng)Locale發(fā)生改變時(語言環(huán)境)烂翰,加載對應(yīng)的HYLocalizations資源
- 這個方法返回的是一個Future夯缺,因為有可能是異步加載的;
- 但是我們這里是直接定義的一個Map甘耿,因此可以直接返回一個同步的Future(SynchronousFuture)
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:i18n_demo/i18n/localizations.dart';
class HYLocalizationsDelegate extends LocalizationsDelegate<HYLocalizations> {
@override
bool isSupported(Locale locale) {
return ["en", "zh"].contains(locale.languageCode);
}
@override
bool shouldReload(LocalizationsDelegate<HYLocalizations> old) {
return false;
}
@override
Future<HYLocalizations> load(Locale locale) {
// 返回一個同步的HYLocalizations對象
return SynchronousFuture(HYLocalizations(locale));
}
static HYLocalizationsDelegate delegate = HYLocalizationsDelegate();
}
然后在localizationsDelegates里面添加HYLocalizationsDelegate.delegate即可:
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
HYLocalizationsDelegate.delegate,
],
2.2.3. 使用本地化類
接著我們可以在代碼中使用HYLocalization類踊兜。
- 我們可以通過Localizations.of(context, HYLocalizations)獲取到HYLocalizations對象
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Localizations.of(context, HYLocalizations).title),
),
body: Center(
child: Column(
children: <Widget>[
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) {
});
},
)
],
),
),
);
}
當(dāng)然,我們可以對Localizations.of(context, HYLocalizations)進行一個優(yōu)化佳恬。
- 給HYLocalizations定義一個of的靜態(tài)方法
class HYLocalizations {
static HYLocalizations of(BuildContext context) {
return Localizations.of(context, HYLocalizations);
}
}
接下來我們就可以通過下面的方式來使用了(其它地方也是一樣):
appBar: AppBar(
title: Text(HYLocalizations.of(context).title),
)
2.2.4. 異步加載數(shù)據(jù)
假如我們的數(shù)據(jù)是異步加載的捏境,比如來自Json文件或者服務(wù)器,應(yīng)該如何處理呢毁葱?
當(dāng)然垫言,在加載之前我們需要在pubspec.yaml文件里面添加如下配置:
assets:
- assets/json/
這里我們可以修改HYLocalizations的數(shù)據(jù)加載:
// 初始數(shù)據(jù)為空
static Map<String, Map<String, String>> _localizedValues = {};
// 加載數(shù)據(jù)的方法
Future<bool> loadJson() async {
// 1.加載json文件
String jsonString = await rootBundle.loadString("assets/json/i18n.json");
// 2.轉(zhuǎn)成map類型
final Map<String, dynamic> map = json.decode(jsonString);
// 3.注意:這里是將Map<String, dynamic>轉(zhuǎn)成Map<String, Map<String, String>>類型
_localizedValues = map.map((key, value) {
return MapEntry(key, value.cast<String, String>());
});
return true;
}
在HYLocalizationsDelegate中的load方法使用異步進行加載:
@override
Future<HYLocalizations> load(Locale locale) async {
final localization = HYLocalizations(locale);
await localization.loadJson();
return localization;
}
三. 國際化的工具
3.1. 認(rèn)識arb文件
目前我們已經(jīng)可以通過加載對應(yīng)的json文件來進行本地化了。
但是還有另外一個問題倾剿,我們在進行國際化的過程中筷频,下面的代碼依然需要根據(jù)json文件手動編寫
:
String get title {
return _localizedValues[locale.languageCode]["title"];
}
String get greet {
return _localizedValues[locale.languageCode]["greet"];
}
String get pickTime {
return _localizedValues[locale.languageCode]["picktime"];
}
有沒有一種更好的方式,讓我們可以快速在本地化文件-dart代碼文件直接來轉(zhuǎn)換呢前痘?答案就是arb文件
- arb文件全稱Application Resource Bundle凛捏,表示應(yīng)用資源包,目前已經(jīng)得到Google的支持芹缔;
- 其本質(zhì)就是一個json文件坯癣,但是可以根據(jù)該文件轉(zhuǎn)成對應(yīng)的語言環(huán)境;
- arb的說明文檔:https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification
3.2. intl package
官方文檔推薦可以使用intl package來進行arb和dart文件之間的轉(zhuǎn)換(通過終端指令)
需要在在pubspec.yaml中添加其相關(guān)的依賴最欠,具體步驟這里不再詳細(xì)給出示罗,可以參考官方文檔。
3.3. 使用IDE插件
在之前有一個比較好用的Android Studio的插件:Flutter i18n
- 但是這個插件已經(jīng)很久不再維護了芝硬,所以不再推薦給大家使用鹉勒;
目前我們可以使用另外一個插件: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是對應(yīng)的arb文件目錄
3.3.3. 使用intl
在localizationsDelegates中配置生成的class脯倒,名字是S
- 添加對應(yīng)的S.delegate
- supportedLocales使用S.delegate.supportedLocales
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
HYLocalizationsDelegate.delegate,
S.delegate
],
supportedLocales: S.delegate.supportedLocales,
因為我們目前還沒有對應(yīng)的本地化字符串,所以需要在intl_en.arb文件中編寫:
- 編寫后ctrl(command) + s保存即可
{
"title": "home",
"greet": "hello~",
"picktime": "Pick a time"
}
在代碼中使用即可捺氢,按照如下格式:S.of(context).title
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).title),
),
body: Center(
child: Column(
children: <Widget>[
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) {
});
},
)
],
),
),
);
}
3.3.4. 添加中文
如果希望添加中文支持:add local
- 在彈出框中輸入zh即可
我們會發(fā)現(xiàn)藻丢,會生成對應(yīng)的intl_zh.arb和messages_zh.dart文件
編寫intl_zh.arb文件:
{
"title": "首頁",
"greet": "您好~",
"picktime": "選擇一個時間"
}
查看界面,會根據(jù)當(dāng)前語言顯示對應(yīng)的語言文本
3.4. arb其它語法
arb文件的本質(zhì)就是json文件加一些語法摄乒,如果我們希望在使用本地化的過程中傳遞一些參數(shù):
- 比如hello kobe或hello james
- 比如你好啊悠反,李銀河或你好啊残黑,王小波
修改對應(yīng)的arb文件:
- {name}:表示傳遞的參數(shù)
{
"title": "home",
"greet": "hello~",
"picktime": "Pick a time",
"sayHello": "hello {name}"
}
在使用時,傳入對應(yīng)的參數(shù)即可:
Text(S.of(context).sayHello("李銀河")),
arb還有更多的語法斋否,大家可以在之后慢慢學(xué)習(xí)和發(fā)掘梨水。