【Flutter 實(shí)戰(zhàn)】國際化及App 內(nèi)切換語言功能

老孟導(dǎo)讀:本文介紹如何實(shí)現(xiàn)國際化以及實(shí)現(xiàn) App 內(nèi)切換語言功能。
使App支持國際化
當(dāng)應(yīng)用程序支持不同語言的時候,就需要對應(yīng)用程序進(jìn)行國際化,當(dāng)然國際化不僅僅指文字,也可以是布局、圖片等。Flutter 已經(jīng)提供了組件來實(shí)現(xiàn)國際化,下面是實(shí)現(xiàn)國際化的步驟:
在 MaterialApp.supportedLocales 中添加支持的語言:
MaterialApp(
??title:?'Flutter?IntlApp',
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??...
)
比如上面的代碼中,只支持英文和中文。
根據(jù)不同的語言獲取不同的資源:
class?AppLocalizations?{
??final?Locale?locale;
??AppLocalizations(this.locale);
??static?AppLocalizations?of(BuildContext?context)?{
????return?Localizations.of(context,?AppLocalizations);
??}
??static?Map<String,?Map<String,?String>>?_localizedValues?=?{
????'en':?{
??????'title':?'Hello?World',
????},
????'zh':?{
??????'title':?'你好',
????},
??};
??String?get?title?{
????return?_localizedValues[locale.languageCode]['title'];
??}
}
這里只設(shè)置了一個文案,實(shí)際項(xiàng)目中建議不同語言存放在不同的文件中。
設(shè)置用于加載語言的 Delegate:
class?AppLocalizationsDelegate?extends?LocalizationsDelegate<AppLocalizations>?{
??const?AppLocalizationsDelegate();
??@override
??bool?isSupported(Locale?locale)?=>?['en',?'zh'].contains(locale.languageCode);
??@override
??Future?load(Locale?locale)?{
????return?SynchronousFuture(AppLocalizations(locale));
??}
??@override
??bool?shouldReload(AppLocalizationsDelegate?old)?=>?false;
}
將此 Delegate 添加到 MaterialApp:
MaterialApp(
??title:?'Flutter?IntlApp',
??localizationsDelegates:?[
????AppLocalizationsDelegate(),
??],
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??home:?_HomePage(),
)
使用:
Scaffold(
??????body:?Column(
????????mainAxisAlignment:?MainAxisAlignment.center,
????????children:?[
??????????Text('國際化:${AppLocalizations.of(context).title}'),
?????????],
??????????),
????????],
??????),
????)
注意:Scaffold 不要添加 AppBar 數(shù)據(jù),否則報錯,具體原因下面會給出。
重點(diǎn)是這句:
AppLocalizations.of(context).title

此時,App會根據(jù)系統(tǒng)語言作為當(dāng)前的語言。
系統(tǒng)是如何實(shí)現(xiàn)國際化的?
Flutter 的國際化是通過 Localizations 組件實(shí)現(xiàn),上面沒有用到 Localizations 組件啊,是的,App 中并沒有直接使用,因?yàn)?MaterialApp 內(nèi)部封裝了此組件,通過 DevTools 可以查看:

Localizations 組件用于加載本地化資源、獲取系統(tǒng)語言,Localizations 組件內(nèi)部使用了 InheritedWidget 組件,當(dāng)其屬性即 Locale 發(fā)生變化時,其子組件將重建。
上面定義的 AppLocalizations 類內(nèi)部的 of 方法:
static?AppLocalizations?of(BuildContext?context)?{
??return?Localizations.of(context,?AppLocalizations);
}
Localizations.of 源代碼:

這段代碼是獲取 Type 類型(App 傳入的類型為 AppLocalizations)的資源,看一下 resourcesFor 的源代碼:

關(guān)鍵在 _typeToResources :

_typeToResources 是一個 Map 類型, _typeToResources 初始化數(shù)據(jù)的:

widget.delegates 的類型是:
///?This?list?collectively?defines?the?localized?resources?objects?that?can
///?be?retrieved?with?[Localizations.of].
final?Listdynamic>>?delegates;
是否還記得 MaterialApp localizationsDelegates 屬性,此 delegates 就是在 MaterialApp 中設(shè)置的值,到此我們理解了
Localizations.of(context,?AppLocalizations)
這句是如何獲取 AppLocalizations 實(shí)例的,當(dāng)然中間還有一些其他的判斷,具體可自行查看源代碼。
添加系統(tǒng)國際化支持
前面說到 Scaffold 不要添加 AppBar 數(shù)據(jù),否則報錯,填上看其異常信息:
Scaffold(
??????appBar:?AppBar(),
??????body:?Column(
????????mainAxisAlignment:?MainAxisAlignment.center,
????????children:?[
??????????Text('國際化:${AppLocalizations.of(context).title}'),
????????],
??????),
????)

上面的異常效果不明顯,看控制臺的異常信息:

提示 MaterialLocalizations 找不到,MaterialLocalizations 是什么呢?其實(shí)它是系統(tǒng)組件的國際化資源,所以修復(fù)以上異常的方法是引入 MaterialLocalizations,在pubspec.yaml文件中添加包依賴:
dependencies:
??flutter:
????sdk:?flutter
??flutter_localizations:
????sdk:?flutter
MaterialApp 修改如下:
MaterialApp(
??title:?'Flutter?IntlApp',
??localizationsDelegates:?[
????AppLocalizationsDelegate(),
????GlobalMaterialLocalizations.delegate,
????GlobalWidgetsLocalizations.delegate,
????GlobalCupertinoLocalizations.delegate,
??],
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??home:?_HomePage(),
)

flutter_localizations 99%的概率會引入,但我們要知道這個并不是必須的。
添加應(yīng)用程序 title 國際化
按照上面的方式國際化:
MaterialApp(
??title:?'${AppLocalizations.of(context).title}',
??localizationsDelegates:?[
????AppLocalizationsDelegate(),
????GlobalMaterialLocalizations.delegate,
????GlobalWidgetsLocalizations.delegate,
????GlobalCupertinoLocalizations.delegate,
??],
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??home:?_HomePage(),
)

直接異常了,因?yàn)榇藭r使用的 context 是從 build 方法中傳入的,而 Localizations 從 context 開始向上查找,國際化資源是在 MaterialApp 組件中的,所以無法找到 AppLocalizations。
修改方式是使用 onGenerateTitle:
MaterialApp(
??onGenerateTitle:?(context)?{
????return?AppLocalizations.of(context).title;
??},
??localizationsDelegates:?[
????AppLocalizationsDelegate(),
????GlobalMaterialLocalizations.delegate,
????GlobalWidgetsLocalizations.delegate,
????GlobalCupertinoLocalizations.delegate,
??],
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??home:?_HomePage(),
)
系統(tǒng)語言為英文:

系統(tǒng)語言為中文:

此方法只在 Android 上有效,iOS 上沒有效果。
設(shè)置默認(rèn)語言
如果 App 僅支持英文和中文,其他系統(tǒng)的語言也默認(rèn)使用中文:
MaterialApp(
??onGenerateTitle:?(context)?{
????return?AppLocalizations.of(context).title;
??},
??localeResolutionCallback:
??????(Locale?locale,?Iterable?supportedLocales)?{
????var?result?=?supportedLocales
????????.where((element)?=>?element.languageCode?==?locale.languageCode);
????if?(result.isNotEmpty)?{
??????return?locale;
????}
????return?Locale('zh');
??},
??localizationsDelegates:?[
????AppLocalizationsDelegate(),
????GlobalMaterialLocalizations.delegate,
????GlobalWidgetsLocalizations.delegate,
????GlobalCupertinoLocalizations.delegate,
??],
??supportedLocales:?[
????const?Locale('zh'),
????const?Locale('en'),
??],
??home:?_HomePage(),
)
localeResolutionCallback 回調(diào)中 locale 參數(shù)表示當(dāng)前系統(tǒng)語言,supportedLocales 表示支持的語言,即 MaterialApp.supportedLocales 設(shè)置的值。
通過這兩個參數(shù)判斷當(dāng)然系統(tǒng)語言是否在支持的范圍內(nèi),如果支持則返回系統(tǒng)語言,不支持則返回默認(rèn)語言。
使用此方法也可以實(shí)現(xiàn)所有英語區(qū)域的國家使用英語,而國內(nèi)、香港、澳門等使用中文。
監(jiān)聽系統(tǒng)語言切換
當(dāng)更改系統(tǒng)語言設(shè)置時,Localizations 組件將會重新 build,而用戶就看到了語言的切換,這個過程是系統(tǒng)完成的,代碼并不需要主動去監(jiān)聽語言切換,但如果想監(jiān)聽語言切換可以通過 localeResolutionCallback 或 localeListResolutionCallback 回調(diào)來監(jiān)聽。通常情況下,使用localeListResolutionCallback,localeListResolutionCallback有兩個參數(shù):List

supportedLocales為當(dāng)前應(yīng)用支持的locale列表,是在MaterialApp中設(shè)置supportedLocales的值。localeListResolutionCallback返回一個Locale,此Locale表示最終使用的Locale,一般情況下在App不支持當(dāng)前語言時返回一個默認(rèn)值。localeListResolutionCallback的用法如下:
MaterialApp(
??????supportedLocales:?[
????????Locale('zh'),
????????Locale('en'),
??????],
??????localeListResolutionCallback:?(List?locales,?Iterable?supportLocales){
????????print('locales:$locales');
????????print('supportLocales:$supportLocales');
??????},?????
)
輸出如下:
locales:[zh_Hans_CN,?ja_JP,?en_GB]
supportLocales:[zh,?en]
也可以通過如下代碼獲取當(dāng)前系統(tǒng)語言:
Locale?myLocale?=?Localizations.localeOf(context);
應(yīng)用程序內(nèi)切換語言
應(yīng)用程序?qū)崿F(xiàn)切換語言功能只需將 MaterialApp 中 locale 屬性作為一個變量,切換不同的 Locale 即可達(dá)到切換語言的目的。代碼如下:
class?IntlApp?extends?StatelessWidget?{
??@override
??Widget?build(BuildContext?context)?{
????return?MyApp();
??}
}
class?MyApp?extends?StatefulWidget?{
??@override
??MyAppState?createState()?=>?MyAppState();
}
class?MyAppState?extends?State<MyApp>?{
??static?_AppSetting?setting?=?_AppSetting();
??@override
??void?initState()?{
????super.initState();
????setting.changeLocale?=?(Locale?locale)?{
??????setState(()?{
????????setting._locale?=?locale;
??????});
????};
??}
??@override
??Widget?build(BuildContext?context)?{
????return?MaterialApp(
??????onGenerateTitle:?(context)?{
????????return?AppLocalizations.of(context).title;
??????},
??????localeResolutionCallback:
??????????(Locale?locale,?Iterable?supportedLocales)?{
????????var?result?=?supportedLocales
????????????.where((element)?=>?element.languageCode?==?locale.languageCode);
????????if?(result.isNotEmpty)?{
??????????return?locale;
????????}
????????return?Locale('zh');
??????},
??????locale:?setting._locale,
??????localizationsDelegates:?[
????????AppLocalizationsDelegate(),
????????GlobalMaterialLocalizations.delegate,
????????GlobalWidgetsLocalizations.delegate,
????????GlobalCupertinoLocalizations.delegate,
??????],
??????supportedLocales:?[
????????const?Locale('zh'),
????????const?Locale('en'),
??????],
??????home:?_HomePage(),
????);
??}
}
_HomePage 代碼:
class?_HomePage?extends?StatefulWidget?{
??@override
??__HomePageState?createState()?=>?__HomePageState();
}
class?__HomePageState?extends?State<_HomePage>?{
??@override
??Widget?build(BuildContext?context)?{
????return?Scaffold(
??????appBar:?AppBar(),
??????body:?Column(
????????mainAxisAlignment:?MainAxisAlignment.center,
????????children:?[
??????????Text('${AppLocalizations.of(context).title}'),
??????????Row(
????????????mainAxisAlignment:?MainAxisAlignment.center,
????????????children:?[
??????????????RaisedButton(
????????????????child:?Text('中文'),
????????????????onPressed:?()?{
??????????????????MyAppState.setting.changeLocale(Locale('zh'));
????????????????},
??????????????),
??????????????RaisedButton(
????????????????child:?Text('英文'),
????????????????onPressed:?()?{
??????????????????MyAppState.setting.changeLocale(Locale('en'));
????????????????},
??????????????),
????????????],
??????????),
????????],
??????),
????);
??}
}
_AppSetting 代碼:
class?_AppSetting?{
??_AppSetting();
??Null?Function(Locale?locale)?changeLocale;
??Locale?_locale;
}


