概述
- 國際化的認(rèn)識(shí)
- 國際化的適配
- 國際化的工具
一、國際化的認(rèn)識(shí)
開發(fā)一個(gè)App,如果我們的App需要面向不同的語種(比如中文、英文、繁體等),那么我們需要對(duì)齊進(jìn)行國際化開發(fā)。
國際化的英文稱呼:internationalization
(簡(jiǎn)稱為i18n
,取前后兩個(gè)字母,18表示中間省略字母的個(gè)數(shù))
App國際化開發(fā)主要包括:文本國際化(包括文本的順序),Widget顯示的國際化:
比如我們下面開發(fā)的這個(gè)App
某些文本在英文環(huán)境下應(yīng)該顯示為英文;
某些Widget在中文環(huán)境下,應(yīng)該顯示中文(比如彈出的時(shí)間選擇器);
二、國際化的適配
-
2.1、Widget的國際化
Flutter給我們提供的Widget默認(rèn)情況下就是支持國際化,但是在沒有進(jìn)行特別的設(shè)置之前,它們無論在什么環(huán)境都是以英文的方式顯示的。
如果想要添加其他語言,你的應(yīng)用必須指定額外的MaterialApp
屬性并且添加一個(gè)單獨(dú)的package
,叫做flutter_localizations
。
截至到 2020 年 2 月份,這個(gè) package 已經(jīng)支持大約 77 種語言。-
2.1.1、
pubspec.ymal
添加依賴,想要使用 flutter_localizations 的話,我們需要在 pubspec.yaml 文件中添加它作為依賴如下,記得點(diǎn)擊Pub get
更新一下
dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter
-
2.1.2. 設(shè)置
MaterialApp
-
在localizationsDelegates中指定哪些Widget需要進(jìn)行國際化
- 用于生產(chǎn)
本地化值
集合的工廠 - 我們這里指定了Material、Widgets、Cupertino都使用國際化
- 用于生產(chǎn)
-
supportedLocales指定要支持哪些國際化:我們這里指定
中文
和英文
(也可以指定國家編碼)MaterialApp( localizationsDelegates: [ // 指定本地化的字符串和一些其他的值 GlobalMaterialLocalizations.delegate, // 對(duì)應(yīng)的Cupertino風(fēng)格 GlobalCupertinoLocalizations.delegate, // 指定默認(rèn)的文本排列方向, 由左到右或由右到左 GlobalWidgetsLocalizations.delegate ], supportedLocales: [ Locale('en'), Locale('zh') ], );
提示:如果要指定語言代碼、文字代碼和國家代碼,可以進(jìn)行如下指定方式:
// 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è)置完成后,我們?cè)贏ndroid上將語言切換為中文,查看結(jié)果,我們可以看到英文的 時(shí)間Widget里面的文字是 中文了
-
但是對(duì)于iOS,將語言切換為中文,依然顯示是英文的Widget
- 這是因?yàn)閕OS定義了一些應(yīng)用的元數(shù)據(jù),其中包括支持的語言環(huán)境;
- 我們必須將其對(duì)應(yīng)的元數(shù)據(jù)中支持的語言添加進(jìn)去;
- 元數(shù)據(jù)的設(shè)置在iOS項(xiàng)目中對(duì)應(yīng)的info.plist文件中;
-
修改 iOS 的
info.plist
文件配置:1、Xcode打開工程
2、添加語言的選項(xiàng)Localizations
3、選擇Localizations
4、添加中文簡(jiǎn)體
配置完成后,卸載之前的app,重新安裝
:我們可以看到和安卓模擬器一樣的效果
-
-
-
2.2、其它文本國際化
App中除了有默認(rèn)的Widget,我們也希望對(duì)自己的文本進(jìn)行國際化-
2.2.1、創(chuàng)建本地化類,該類用于定義我們需要進(jìn)行本地化的字符串等信息:
1.我們需要一個(gè)構(gòu)造器,并且傳入一個(gè)Locale對(duì)象(后續(xù)會(huì)使用到)
2.定義一個(gè)Map,其中存放我們不同語言對(duì)應(yīng)的顯示文本
-
3.定義它們對(duì)應(yīng)的getter方法,根據(jù)語言環(huán)境返回不同的結(jié)果
import 'package:flutter/material.dart'; class JKLocalizations { final Locale locale; JKLocalizations(this.locale); static JKLocalizations of(BuildContext context) { return Localizations.of(context, JKLocalizations); } static Map<String, Map<String, String>> _localizedValues = { "en": { "title": "home", "greet": "hello~", "picktime": "Pick a Time" }, "zh": { "title": "首頁", "greet": "你好~", "picktime": "選擇一個(gè)時(shí)間" } }; // 標(biāo)題 String get title { return _localizedValues[locale.languageCode]["title"]; } // 問候 String get greet { return _localizedValues[locale.languageCode]["greet"]; } // 時(shí)間 String get pickTime { return _localizedValues[locale.languageCode]["picktime"]; } }
-
2.2.2、自定義Delegate
- 上面的類定義好后,我們?cè)谑裁次恢没蛘哒f如何對(duì)它進(jìn)行初始化呢?
- 答案是我們可以像Flutter Widget中的國際化方式一樣對(duì)它們進(jìn)行初始化;
- 也就是我們也定義一個(gè)對(duì)象的Delegate類,并且將其傳入localizationsDelegates中;
- Delegate的作用就是當(dāng)Locale發(fā)生改變時(shí),調(diào)用對(duì)應(yīng)的load方法,重新加載新的Locale資源;
-
JKLocalizationsDelegate
需要繼承自LocalizationsDelegate
,并且有三個(gè)方法必須重寫:- isSupported:用于當(dāng)前環(huán)境的Locale,是否在我們支持的語言范圍
- shouldReload:當(dāng)Localizations Widget重新build時(shí),是否調(diào)用load方法重新加載Locale資源
- 一般情況下,Locale資源只應(yīng)該在Locale切換時(shí)加載一次,不需要每次Localizations重新build時(shí)都加載一遍;
- 所以一般情況下返回false即可;
- load方法:當(dāng)Locale發(fā)生改變時(shí)(語言環(huán)境),加載對(duì)應(yīng)的HYLocalizations資源
這個(gè)方法返回的是一個(gè)Future,因?yàn)橛锌赡苁钱惒郊虞d的;
-
但是我們這里是直接定義的一個(gè)Map,因此可以直接返回一個(gè)同步的Future(SynchronousFuture)
class JKLocalizationsDelegate extends LocalizationsDelegate<JKLocalizations> { @override // 支持的語言 bool isSupported(Locale locale) { return ["en", "zh"].contains(locale.languageCode); } @override bool shouldReload(LocalizationsDelegate<JKLocalizations> old) { return false; } @override Future<JKLocalizations> load(Locale locale) { return SynchronousFuture(JKLocalizations(locale)); } static JKLocalizationsDelegate delegate = JKLocalizationsDelegate(); }
- 上面的類定義好后,我們?cè)谑裁次恢没蛘哒f如何對(duì)它進(jìn)行初始化呢?
-
2.2.3. 使用本地化類
-
1>、 在
MaterialApp
的localizationsDelegates
里里添加我們的 delegeate -
2>、接著我們可以在代碼中使用JKLocalization類。
我們可以通JKYLocalizations.of(context)
獲取到JKLocalizations
對(duì)象,本質(zhì)上是在調(diào)用Localizations.of(context, JKLocalizations)
Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(JKLocalizations.of(context).greet), RaisedButton( child: Text(JKLocalizations.of(context).pickTime, style: TextStyle(fontSize: 20, color: Colors.orange),), onPressed: () { showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(3000) ); } ) ], ),
-
-
2.2.4. 異步加載數(shù)據(jù),也就是 JKLocalizations 里面的 _localizedValues 我們做一個(gè)抽取
static Map<String, Map<String, String>> _localizedValues = {}; // 獲取配置的 Map 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; }
提示:這里我們也可以改為網(wǎng)絡(luò)請(qǐng)求,拿到數(shù)據(jù)后一樣轉(zhuǎn)換類型
在
JKLocalizationsDelegate
中使用異步
進(jìn)行加載:@override Future<JKLocalizations> load(Locale locale) async { final localization = JKLocalizations(locale); await localization.loadJson(); return localization; }
提示:我們自己的配置 其它文本國際化,我們每次都要
- 1、修改 json文件
- 2、在 JKLocalizations 里面增加 對(duì)應(yīng)的 get 方法,每次還要寫 key ,很容易寫錯(cuò),要注意
- 3、如果增加新的語言,我們還要 修改JKLocalizationsDelegate 增加支持的語言 和 在 iOS的 info.plist 文件里面增加新的語言類型
-
三、國際化的工具
-
3.1、認(rèn)識(shí)
arb文件
目前我們已經(jīng)可以通過加載對(duì)應(yīng)的json文件來進(jìn)行本地化了
但是還有另外一個(gè)問題,我們?cè)谶M(jìn)行國際化的過程中,下面的代碼依然需要根據(jù) json文件 手動(dòng)編寫: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ì)就是一個(gè) json 文件,但是可以根據(jù)該文件轉(zhuǎn)成對(duì)應(yīng)的語言環(huán)境;
- arb 的說明文檔:https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification
-
- 有沒有一種更好的方式,讓我們可以快速在本地化文件-dart代碼文件直接來轉(zhuǎn)換呢?答案就是arb文件
-
3.2、intl package
官方文檔推薦可以使用intl package來進(jìn)行arb和dart文件之間的轉(zhuǎn)換(通過終端指令)需要在在
pubspec.yaml
中添加其相關(guān)的依賴,具體步驟這里不再詳細(xì)給出,可以參考官方文檔 -
3.3、使用IDE插件
- 在之前有一個(gè)比較好用的Android Studio的插件:
Flutter i18n
- 但是這個(gè)插件已經(jīng)很久不再維護(hù)了,所以不再推薦給大家使用
- 目前我們可以使用另外一個(gè)插件:Flutter Intl
- 該插件更新維護(hù)頻率很高,并且廣受好評(píng);
- 另外,在Android Studio和VSCode中都是支持的
我們這里以Android Studio為例,講解其使用過程:
- 3.3.1、安裝插件
在Android Studio的Plugins中安裝插件:Flutter Intl
提示:如果遇到如下圖的問題,可看下圖修復(fù)
找不到插件的解決辦法
選擇自動(dòng)
- 在之前有一個(gè)比較好用的Android Studio的插件:
-
3.3.2. 初始化 intl
選擇工具欄Tools - Flutter Intl - Initialize for the Project初始化 intl
生成兩個(gè)文件- 完成上面的操作之后會(huì)自動(dòng)生成如下文件目錄:
-
generated
是自動(dòng)生成的dart代碼 -
I10n
是對(duì)應(yīng)的arb文件
目錄
-
- 完成上面的操作之后會(huì)自動(dòng)生成如下文件目錄:
-
3.3.3、使用intl
-
在localizationsDelegates中配置生成的class,名字是S
1.添加對(duì)應(yīng)的delegate
-
2.supportedLocales使用S.delegate.supportedLocales
localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, JKLocalizationsDelegate.delegate, S.delegate ], supportedLocales: S.delegate.supportedLocales,
因?yàn)槲覀兡壳斑€沒有對(duì)應(yīng)的本地化字符串,所以需要在intl_en.arb文件中編寫:編寫后
ctrl(command) + s
保存即可{ "title": "home", "greet": "hello~", "picktime": "Pick a time" }
在代碼中使用即可,按照如下格式:
S.of(context).title
-
-
3.3.4、添加中文
如果希望添加中文支持:add local,在彈出框中輸入zh即可,我們會(huì)發(fā)現(xiàn),會(huì)生成對(duì)應(yīng)的intl_zh.arb
和messages_zh.dart
文件
生成對(duì)應(yīng)的`intl_zh.arb`和`messages_zh.dart`文件-
編寫intl_zh.arb文件
{ "title": "首頁", "greet": "您好~", "picktime": "選擇一個(gè)時(shí)間" }
查看界面,會(huì)根據(jù)當(dāng)前語言顯示對(duì)應(yīng)的語言文本
-
-
3.4. arb 其它語法
-
如果我們希望在使用本地化的過程中傳遞一些參數(shù):
- 比如hello kobe或hello james
- 比如你好啊,周杰倫 或 你好啊
-
修改對(duì)應(yīng)的arb文件:
{name}
表示傳遞的參數(shù){ "title": "home", "greet": "hello~", "picktime": "Pick a time", "sayHello": "hello {name}" }
在使用時(shí),傳入對(duì)應(yīng)的參數(shù)即可:
Text(S.of(context).sayHello("周杰倫")),
-
提示
: arb 的好處, 每次我們只需要修改intl_en.arb
、intl_zh.arb
等文件的 json數(shù)據(jù),它會(huì)自動(dòng)幫我們生成對(duì)用名字的 get 方法,到時(shí)候直接S.of(context).名字
調(diào)用即可
-
最后對(duì)應(yīng)的 i18n代碼