flutter i18n+redux實現國際化

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中指定 localizationsDelegatessupportedLocales爲:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章