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

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