Flutter國(guó)際化

 * author:lebonbill
 * E-mail:lebonbillwu@gmail.com

前言

如果APP有需要支持多種語(yǔ)言,就需要支持國(guó)際化,無(wú)論是android和ios,現(xiàn)在針對(duì)flutter的國(guó)際化做了一些學(xué)習(xí)理解和總結(jié),這里主要是講解一下Flutter國(guó)際化的一些用法,包括Localization widget,intl包和flutter_i18n包的用法

支持國(guó)際化

在默認(rèn)情況下,Flutter只支持英文,要添加多國(guó)語(yǔ)言支持,需要在pubspec.yaml文件中添加“flutter_localizations”依賴,如下

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

然后指定MaterialApp的localizationsDelegates和supportedLocales

import 'package:flutter_localizations/flutter_localizations.dart';
new MaterialApp(
 localizationsDelegates: [
   // 本地化代理
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // 美國(guó)英語(yǔ)
    const Locale('zh', 'CN'), // 中文簡(jiǎn)體
    //其它Locales
  ],
  // ...
)

localizationsDelegates列表中的元素是生成本地化值集合的工廠。
GlobalMaterialLocalizations.delegate 為Material 組件庫(kù)提供的本地化的字符串和其他值,
它可以使Material Widget支持多語(yǔ)言。 GlobalWidgetsLocalizations.delegate定義widget默認(rèn)的文本方向,
從左到右或從右到左,這是因?yàn)橛行┱Z(yǔ)言的閱讀習(xí)慣并不是從左到右,比如如阿拉伯語(yǔ)就是從右向左的。

Locale區(qū)域

Locale類是用來(lái)標(biāo)識(shí)用戶的語(yǔ)言環(huán)境的,它包括語(yǔ)言和國(guó)家兩個(gè)標(biāo)志,languageCode和countryCode:

const Locale(languageCode,countryCode)
const Locale('zh', 'CN') // 中文簡(jiǎn)體

獲取當(dāng)前的語(yǔ)言環(huán)境:
Locale currentLocale = Localizations.localeOf(context);
Localizations Widget一般位于Widget樹(shù)中其它業(yè)務(wù)組件的頂部,它的作用是定義區(qū)域Locale以及設(shè)置子樹(shù)依賴的本地化資源。 如果系統(tǒng)的語(yǔ)言環(huán)境發(fā)生變化,WidgetsApp將創(chuàng)建一個(gè)新的Localizations Widget并重建它,這樣子樹(shù)中通過(guò)Localizations.localeOf(context) 獲取的Locale就會(huì)更新

監(jiān)聽(tīng)系統(tǒng)語(yǔ)言切換

當(dāng)我們更改系統(tǒng)語(yǔ)言設(shè)置時(shí),APP中的Localizations widget會(huì)重新構(gòu)建,最終界面會(huì)重新build達(dá)到切換語(yǔ)言的效果。
我們可以通過(guò)localeResolutionCallback或localeListResolutionCallback回調(diào)來(lái)監(jiān)聽(tīng)locale改變的事件

localeResolutionCallback:(Locale locale, Iterable<Locale> supportedLocales){};

locale返回值是如果用戶沒(méi)有指定locale的話,是默認(rèn)當(dāng)前系統(tǒng)語(yǔ)言,如果用戶切換系統(tǒng)語(yǔ)言,locale也會(huì)變更為相應(yīng)的語(yǔ)言,如果客戶自己指定語(yǔ)言的話,則改變系統(tǒng)語(yǔ)言不會(huì)變更locale的值
用戶可以自己指定locale的值如下:

MaterialApp{
    locale:const('zh','CN')//指定簡(jiǎn)體中文
}

supportedLocales返回的是系統(tǒng)支持的所有已添加的語(yǔ)言,如下圖


123.png

Localizations widget

Flutter的世界都是widget,當(dāng)中包括語(yǔ)言"組件",定義Localization widget,用來(lái)加載查找本地化的對(duì)象,通過(guò)Localization.of(context,type)使用這些語(yǔ)言資源,定義一個(gè)

實(shí)現(xiàn)Localizations類
class DemoLocalizations {
  DemoLocalizations(this._locale);
  final Locale _locale;
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of(context, DemoLocalizations);
  }

  static Map<String, Map<String, String>> _localValues = {
    'en': {"title": "title"},
    "zh": {"title": "標(biāo)題"}
  };

  String get title {
    return _localValues[_locale.languageCode]["title"];
  }
}

其中of是常用的方法,所以為了方便會(huì)定義靜態(tài)方法of并且返回自身對(duì)象

實(shí)現(xiàn)LocalizationDelegate代理
繼承LocalizationDelegate是為了加載新的locale資源,這里有一個(gè)重載的load方法需要實(shí)現(xiàn),

class DemoLocalizationDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  @override
  bool isSupported(Locale locale) {
    return ['en', 'zh'].contains(locale.languageCode);//是否支持的語(yǔ)言
  }

  @override
  Future<DemoLocalizations> load(Locale locale) {
    return SynchronousFuture(DemoLocalizations(locale));//異步記載資源
  }

  @override
  bool shouldReload(LocalizationsDelegate<DemoLocalizations> old) => false;
}

最后在MaterialApp和WidgetsApp的localizationsDelegates添加定義的代理

localizationsDelegates: [
   // 本地化代理
   DemoLocalizationDelegate();//自定義代理
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],

然后通過(guò)Localization widget的of方法使用資源:

DemoLocalization lo= DemoLocalization.of(context);
String title=lo.title;//默認(rèn)是英文的話會(huì)返回"title",切換語(yǔ)言的時(shí)候該值會(huì)變成"標(biāo)題"

使用intl包

為了方便翻譯人員和開(kāi)發(fā)人員分工協(xié)作,使用intl包可以很好的將翻譯資源文件和代碼分離,arb文件用做于翻譯資源,dart用于開(kāi)發(fā)人員編寫(xiě)相關(guān)的翻譯代碼,因此在pubspec.yaml中引用intl包依賴:

dependencies:
  intl: ^0.15.7 
dev_dependencies:
  intl_translation: ^0.17.2

intl包是用來(lái)翻譯前面所說(shuō)的Localization widget相關(guān)的dart的文件生成arb文件(arb文件下面會(huì)講解),如前面所說(shuō)的Localization widget類會(huì)改成這樣:

import 'dart:async';

import 'package:intl/intl.dart';
import 'package:flutter/widgets.dart';
class DemoLocalizations {
  DemoLocalizations(this._locale);
  final Locale _locale;
  
  static Future<DemoLocalizations> load(Locale locale) async {
    String name =
        locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    String localeName = Intl.canonicalizedLocale(name);
    return initializeMessages(locale.toString())
        .then((Object _) {
      return new DemoLocalizations(locale);
    });
  }
    
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of(context, DemoLocalizations);
  }

  String get title =>
      Intl.message("這個(gè)是一個(gè)標(biāo)題", name: "title", desc: "標(biāo)題用的翻譯文本", args: []);
  }    
}

這里的靜態(tài)函數(shù)load,是為了方便代理類調(diào)用新增,里面有一個(gè)initializeMessages會(huì)顯示報(bào)錯(cuò),先可以忽略,上面Localization類中使用了Intl.message,Intl.message的相關(guān)用法可以參考官方api(點(diǎn)擊查看),這里就是需要翻譯的文字,通過(guò)intl包會(huì)把Intl.message相關(guān)的資源"提取"生成arb文件,需要運(yùn)行命令行如下:

$ flutter pub pub run intl_translation:extract_to_arb --output-dir="保存arb文件的目錄" "需要提取翻譯的dart文件"

例子:
$ flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/demo_localizations.dart

需要在lib中新建l10n文件夾用于保存arb文件,后面帶的參數(shù)就是上述的DemoLocalizations類所在位置,運(yùn)行后會(huì)在l10n看到一個(gè)intl_messages.arb文件。
把上面的intl_messgaes文件當(dāng)成模板復(fù)制粘貼intl_zh.arb和intl_en.arb,然后翻譯相關(guān)的內(nèi)容,arb是一個(gè)json結(jié)構(gòu)的文件,intl_zh.arb文件結(jié)構(gòu)如下

{
  "@@last_modified": "2019-01-10T14:17:00.088434",
  "title": "這個(gè)是一個(gè)標(biāo)題",
  "@title": {
    "description": "標(biāo)題用的翻譯文本",
    "type": "text",
    "placeholders": {}
  }
}

翻譯完所有arb資源后,intl_translation包就是用來(lái)把a(bǔ)rb生成相關(guān)的dart代碼文件,運(yùn)行命令行:

$ flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n \
   --no-use-deferred-loading lib/demo_localizations.dart lib/l10n/intl_*.arb

運(yùn)行結(jié)果會(huì)出現(xiàn)

No @@locale or _locale field found in intl_en, assuming 'en' based on the file name.
No @@locale or _locale field found in intl_messages, assuming 'messages' based on the file name.
No @@locale or _locale field found in intl_zh, assuming 'zh' based on the file name.

各自生成如下的新文件
然后在剛才initializeMessages保存的DemoLocalizations中引入

import 'l10n/messages_all.dart';

警告就會(huì)消失
然后修改Localization代理中的load,引用上面的Localization widget load方法即可
調(diào)用資源的方法和上面的一樣

DemoLocalizations.of(context).title
DemoLocalizations.of(context).name

使用flutter_i18n

flutter_i18n是另外一種進(jìn)行國(guó)際化的方法,有別于intl,它主要是利用json文件來(lái)進(jìn)行翻譯,個(gè)人認(rèn)為最簡(jiǎn)單的一種,可以自己定app語(yǔ)言,方便刷新切換語(yǔ)言,不依賴系統(tǒng)語(yǔ)言

使用方法步驟如下:
在pubspec.yaml引用依賴并且添加資源目錄這里我定義存放資源文件目錄是flutter_i18n

dependencies:
  flutter:
    sdk: flutter
  flutter_i18n: ^0.5.2
flutter:
  assets:
  - flutter_i18n/  

新建json文件,命名規(guī)則可以這樣{languageCode}_{countryCode}.json或者{languageCode}.json,這里我命名為zh_CN.json,內(nèi)容如下:

{
  "test": {
    "title": "這個(gè)是一個(gè)標(biāo)題哦",
    "name": "我的名字叫{name}"
  }
}

添加Localization代理

localizationsDelegates: [
        FlutterI18nDelegate(useCountryCode, [fallbackFile, basePath]),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate
],
useCountryCode參數(shù)是用于json文件的命名規(guī)則:

如果你是用{languageCode}_{countryCode}來(lái)命名,useCountryCode參數(shù)必須為true
如果你是用{languageCode}來(lái)命名, useCountryCode就必須為false

fallbackFile參數(shù)引入版本0.1.0并提供默認(rèn)語(yǔ)言,在未提供當(dāng)前運(yùn)行系統(tǒng)的轉(zhuǎn)換時(shí)使用。這應(yīng)該包含assets文件夾中有效的json文件的名稱如"zh_CN"
basePath參數(shù)可選地用于設(shè)置翻譯的基本路徑。如果未設(shè)置此選項(xiàng),則默認(rèn)路徑為assets / flutter_i18n。此路徑必須與pubspec.yaml中定義的路徑相同。
配置例子如:
FlutterI18nDelegate(true, "en_US", "flutter_i18n")

flutter_i18n實(shí)現(xiàn)

配置完成后,可以通過(guò)以下方法調(diào)用:

FlutterI18n.translate(buildContext, "your.key")
FlutterI18n.translate(buildContext, "test.title");//這是一個(gè)標(biāo)題哦
FlutterI18n.translate(context, "test.name", {"name": "AAA"});我的名字叫做AAA

如果你想切換應(yīng)用語(yǔ)言可以這樣做:

await FlutterI18n.refresh(buildContext, languageCode, {countryCode});
await FlutterI18n.refresh(buildContext, "en", "US");//如切換英語(yǔ)

flutter_i18n 支持復(fù)數(shù),你可以調(diào)用以下方法:

FlutterI18n.plural(buildContext, "your.key", pluralValue);如
"clicked": {
    "times-0": "You clicked zero!",
    "times-1": "You clicked {time} time!",
    "times-2": "You clicked {times} times!"
  }
  
FlutterI18n.plural(buildContext, "clicked.times", 0)//You clicked zero!
FlutterI18n.plural(buildContext, "clicked.times", 1)//You clicked 1 time!
FlutterI18n.plural(buildContext, "clicked.times", 2)//You clicked 2 times!

總結(jié)

個(gè)人覺(jué)得還是flutter_i18n使用方便,可以用FlutterI18n.refresh來(lái)切換管理app語(yǔ)言,不用依賴手機(jī)系統(tǒng)語(yǔ)言
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容