Flutter官方提供的實現國際化有些繁瑣,需要自己實現WidgetsLocalizations,並且國際化的strings都是在代碼中寫的,而i18n插件可以自動生成這些代碼,並且國際化的strings也是使用文件以json形式配置。加以使用redux實現語言切換及持久化。
Android Studio安裝i18n插件
安裝後在菜單欄中會有如下圖標,點擊用於生成i18n代碼
項目中設置國際化
要使用flutter_localizations,將軟件包作爲依賴項添加到pubspec.yaml
文件中:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
點擊i18n插件按鈕,在項目中會生成lib/generated/i18n.dart和lib/res/values/strings_en.arb。在strings_en.arb中配置英語顯示的字符,如果要支持中文簡體和中文繁體(香港),需要在lib/res/values文件夾中添加strings_zh_CN.arb、strings_zh_HK.arb,i18n插件也提供了圖形化添加不同國家語言arb文件,並在arb文件中添加文本,如下所示:
點擊i18n按鈕,就會重新生成lib/generated/i18n.dart文件,並且在values中添加的國際化文本,都會自動生成中在i18n.dart中。
接下來,使用flutter_localizations庫,並在MaterialApp中
指定 localizationsDelegates
和supportedLocales
爲:
MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: S.delegate.supportedLocales,
localeResolutionCallback: I18nUtil.localeResolutionCallback(),
locale: store.state.locale,//如果locale設置爲null或者不指定,那麼會跟隨系統的Locale從supportedLocales中找是否支持,不支持可以使用localeResolutionCallback來指定支持的Locale
title: 'Flutter Basic', //在安卓任務管理列表中顯示的名稱
theme: defaultTargetPlatform == TargetPlatform.iOS
? kIOSTheme
: kDefaultTheme,
routes: <String,WidgetBuilder>{
'/':(BuildContext context) => new StoreConnector<AppState,dynamic>(
builder: (BuildContext context,dynamic isAuthenticated) =>
isAuthenticated ? MainPage() : LoginPage(),
converter: (Store<AppState> store) => store.state.authState.isAuthenticated??false,
),
'/main':(BuildContext context) => MainPage(),
'/login':(BuildContext context) => LoginPage(),
},
)
localeResolutionCallback: I18nUtil.localeResolutionCallback()可以不指定,主要作用是在設置locale或者系統切換的語言該應用不支持的時候,通過該方法過濾,並且重新指定supportedLocales支持語言中的一個。不指定也不會報錯,可能會根據不同手機隨機指定supportedLocales中的一種語言。在i18n.dart中GeneratedLocalizationsDelegate也有相應的實現resolution,但是可能並不是你想要的實現,也可以自己實現。
static LocaleResolutionCallback localeResolutionCallback(){
return (Locale locale, Iterable<Locale> supported) {
if (locale == null || !S.delegate.isSupported(locale)) {
return Locale('en','');
}
for(var element in supported){
if(element.languageCode == locale.languageCode &&
element.countryCode == locale.countryCode){
return element;
}
}
return Locale('en','');
};
}
Redux配合使用,實現切換語言和持久化
包依賴,在pubspec.yaml
文件中添加
//redux
redux: ^3.0.0
flutter_redux: ^0.5.2
//redux log
redux_logging: ^0.3.0
//redux中間件
redux_thunk: ^0.2.0
//redux持久化
redux_persist_flutter: ^0.6.0-rc.1
redux_persist: ^0.7.0-rc.2
state代碼
@immutable
class AppState {
/*
* 國際化
*
* 設置爲null,就相當於auto跟隨系統,
* 將locale持久化,讓切換語言在關閉應用時也能生效
*
* 如果MaterialApp.locale設置爲null或者不指定,
* 那麼會跟隨系統的Locale從supportedLocales中找是否支持,
* 不支持可以使用localeResolutionCallback來指定支持的Locale
*/
final Locale locale;
AppState({Locale locale}):
this.locale = locale;
static AppState fromJson(dynamic json) => new AppState(
locale: LocaleExtension.fromJson(json['locale'])
);
//toJson名字固定
Map<String,dynamic> toJson() => {
'locale':locale == null ? null : locale.toJson()
};
}
//以下是Locale的擴展函數
import 'package:flutter/material.dart';
extension LocaleExtension on Locale {
Map<String,dynamic> toJson() {
var map = Map<String,dynamic>();
if(this.languageCode != null && this.languageCode != "")
map["languageCode"] = this.languageCode;
if(this.scriptCode != null && this.scriptCode != "")
map["scriptCode"] = this.scriptCode;
if(this.countryCode != null && this.countryCode != "")
map["countryCode"] = this.countryCode;
return map;
}
static Locale fromJson(Map<String,dynamic> json) =>
json != null ? Locale.fromSubtags(languageCode:json['languageCode'],
scriptCode:json['scriptCode'],
countryCode:json['countryCode']) : null;
int compareTo(Locale other){
if(other == null) return -1;
return this.toString().compareTo(other.toString());
}
}
action代碼
import 'package:flutter_basic/models/app_state.dart';
import 'package:redux/redux.dart';
class LocaleAction{
Locale locale;
LocaleAction({this.locale});
}
Function changeLocale = (Locale locale){
return (Store<AppState> store){
store.dispatch(LocaleAction(locale: locale));
};
};
reducer代碼
import 'package:flutter_basic/actions/locale_action.dart';
import 'package:redux/redux.dart';
Reducer<Locale> localeReducer = combineReducers([
new TypedReducer<Locale,LocaleAction>(changeLocaleReducer),
]);
Locale changeLocaleReducer(Locale locale,LocaleAction action){
locale = action.locale;
return locale;
}
store和middleware代碼
//store
Store<AppState> createStore(){
Store<AppState> store = new Store(
appReducer,
initialState: new AppState(),
middleware: createMiddleware()
);
persistor.start(store);
return store;
}
//middleware
final persistor = new Persistor<AppState>(storage: new FlutterStorage('basic-app'),decoder: AppState.fromJson);
List<Middleware<AppState>> createMiddleware() => <Middleware<AppState>>[
thunkMiddleware,
persistor.createMiddleware(),
LoggingMiddleware.printer(),
];
程序入口代碼改造
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final store = createStore();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return PersistorGate(
persistor: persistor,
loading: LoadingPage(),
builder: (context) => new StoreProvider(store: store, child: StoreBuilder<AppState>(//國際化必須要用StoreBuilder包裹一下
builder: (context,store){
return MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: S.delegate.supportedLocales,
localeResolutionCallback: I18nUtil.localeResolutionCallback(),
locale: store.state.locale,//如果locale設置爲null或者不指定,那麼會跟隨系統的Locale從supportedLocales中找是否支持,不支持可以使用localeResolutionCallback來指定支持的Locale
title: 'Flutter Basic', //在安卓任務管理列表中顯示的名稱
theme: defaultTargetPlatform == TargetPlatform.iOS
? kIOSTheme
: kDefaultTheme,
routes: <String,WidgetBuilder>{
'/':(BuildContext context) => new StoreConnector<AppState,dynamic>(
builder: (BuildContext context,dynamic isAuthenticated) =>
isAuthenticated ? MainPage() : LoginPage(),
converter: (Store<AppState> store) => store.state.authState.isAuthenticated??false,
),
'/main':(BuildContext context) => MainPage(),
'/login':(BuildContext context) => LoginPage(),
},
);
},
)),
);
}
}
切換語言代碼,主要代碼store.dispatch(changeLocale(locale))
class LanguagePageState extends State<LanguagePage>{
List<LanguageModel> supports = [];
@override
Widget build(BuildContext context) {
supports.clear();
supports.addAll(I18nUtil.getSupportLanguagesSort(context));
return Scaffold(
appBar: AppBar(
title: Text(I18nUtil.getS(context).multi_language),
),
body: StoreConnector<AppState,dynamic>(
converter: (store){
return (Locale locale)=>store.dispatch(changeLocale(locale));
},
builder: (context,changeLocale){
return ListView.builder(
itemCount: supports.length,
itemBuilder: (BuildContext context,int index){
LanguageModel model = supports[index];
return ListTile(
title: Text(model?.title),
onTap: ()=>switchLanguage(model.locale,changeLocale),
trailing: new Radio(
value: true,
groupValue: model.isSelected == true,
activeColor: colorStyles['primary'],
onChanged: (value) {
switchLanguage(model.locale,changeLocale);
}),
);
},
);
},
),
);
}
switchLanguage(Locale locale,changeLocale){
setState(() {
changeLocale(locale);
});
}
}
iOS工程還需要單獨進行本地化配置,添加所支持的語言,如下圖:
項目地址(國際化代碼在tag:v_language):
https://github.com/xiaojinwei/FlutterBasic