Flutter 语言设置(国际化)一种实现方法(通过provider状态共享)

语言设置

效果如下

实现方法

核心:通过跨组件状态共享provider,更改MaterialAPP的locale属性。

接下来我们以MaterialApp类为入口的应用来说明如何支持国际化。

大多数应用程序都是通过MaterialApp为入口,但根据低级别的WidgetsApp类为入口编写的应用程序也可以使用相同的类和逻辑进行国际化。MaterialApp实际上也是WidgetsApp的一个包装。

Flutter 官方鼓励我们在写 Flutter 应用的时候直接从 MaterialApp 开始,原因是 MaterialApp
为我们集成好了很多 Material Design 所必须的控件,如AnimatedThemen、GridPager 等,另外还通过MaterialApp 配置了全局路由,方便进行页面的切换

准备工作:首先需要在pubspec.yaml中添加如下依赖

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  provider: ^4.3.1

 

默认情况下,Flutter SDK中的组件仅提供美国英语本地化资源(主要是文本)。要添加对其他语言的支持,应用程序须添加一个名为“flutter_localizations”的包依赖,然后还需要在MaterialApp中进行一些配置。

provider基本用法;1、新建状态管理类。 2、顶层注册状态管理类。3、状态组件绑定 。4、状态变更。

provider详细介绍

1、第一步

新建一个currentLocale.dart文件创建CurrentLocale model类(即我们要共享的状态)

import 'package:flutter/material.dart';


class CurrentLocale with ChangeNotifier 
{
    Locale _locale = const Locale('zh','CN');
 
    Locale get value => _locale;
    void setLocale(locale) 
    {
        _locale = locale;   
        notifyListeners();//通知依赖的Widget
    }
}

2、第二步

在main.dart文件main方法里注册顶层共享数据。注意导入provider包和新建的currentLocale.dart(时常注意别忘了导入用到的包或类不然会报错

void main() {
  //2、注册顶层共享数据
  return runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context)=>ThemeModel()),
        ChangeNotifierProvider(create: (context)=>CurrentLocale())//此是语言状态注册
      ],
      child: MyApp(),
    )
  );
}

MultiProvider用来注册你需要共享的状态,其中CurrentLocale是语言设置相关的状态是我们需要的,ThemeModel是主题设置的状态,上一篇主题设置blog有说,此处不管它,可以新建一个themeModel类,也可以删了它,如果你也想了解主题推荐新建一个然后可以看下上篇文章主题设置。

import 'package:flutter/material.dart';

/*1.创建theme数据Model
    这里的 Model 实际上就是我们的状态,它不仅储存了我们的数据模型,而且还包含了更改数据的方法,并暴露出它想要暴露出的数据
*/
class ThemeModel with ChangeNotifier {

      String _theme = 'blue';
      String get value => _theme;

      void setTheme(color) async
      {
          _theme = color;
          print(_theme);    
          notifyListeners();        
      }
}

3、第三步

实现Localizations,我们需要实现两个类:一个Localizations类一个Delegate类,其中Localizations类中主要实现提供了本地化值(即你需要国际化翻译的那些数据),

定义完 Localizations 以后,我们就需要想这么一个问题,这个类是谁负责初始化呢?答案自然不是我们自己主动去初始化,而是需要一个Delegate的类来完成,Delegate是一个抽象类,需要我们去实现它

新建localizations.dart文件,创建一个Localizations类-DemoLocalizations

import 'package:flutter/material.dart';

class DemoLocalizations {

  final Locale locale;

  DemoLocalizations(this.locale);

   //本demo只对四个文本进行了国际化,包括主界面标题...
  static Map<String, Map<String, String>> _localizedValues = { 
    'en': {
      'task title': 'Yshow',
      'set title': 'Setting',
      'theme set': 'Theme Settings',
      'language set':'Language Settings'
    },
    'zh': {
      'task title': '易米八一',
      'set title': '设置',
      'theme set': '主题设置',
      'language set':'语言设置'
    }
  };

  get taskTitle{
    return _localizedValues[locale.languageCode]['task title'];
  }

  get setTitle{
    return _localizedValues[locale.languageCode]['set title'];
  }

  get themeSet{
    return _localizedValues[locale.languageCode]['theme set'];
  }
  
  get langSet{
    return _localizedValues[locale.languageCode]['language set'];
  }
    // 这个特殊的Localizations.of()表达式会经常使用,这样可以直接调用便捷方法
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }
}

DemoLocalizations中会根据当前的语言来返回不同的文本,如title,我们可以将所有需要支持多语言的文本都在此类中定义。DemoLocalizations的实例将会在Delegate类的load方法中创建。

新建delegate.dart文件,创建一个delegate类-DemoLocalizationsDelegate

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'localizations.dart';

class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations>{

  const DemoLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    return ['en','zh'].contains(locale.languageCode);
  }

  @override
  Future<DemoLocalizations> load(Locale locale) {
    return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale));
  }

  @override
  bool shouldReload(LocalizationsDelegate<DemoLocalizations> old) {
    return false;
  }

  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}

shouldReload的返回值决定当Localizations组件重新build时,是否调用load方法重新加载Locale资源。一般情况下,Locale资源只应该在Locale切换时加载一次,不需要每次在Localizations重新build时都加载,所以返回false即可。可能有些人会担心返回false的话在APP启动后用户再改变系统语言时load方法将不会被调用,所以Locale资源将不会被加载。事实上,每当Locale改变时Flutter都会再调用load方法加载新的Locale,无论shouldReload返回true还是false

 

4、第四步

关键的一步

  首先是在main.dart文件中的MyApp类中将语言状态CurrentLocale与locale绑定,MaterialApp的locale的值默认是手机系统语言设置,我们将其与我们的currentLocale状态绑定来控制它。

然后指定MaterialApplocalizationsDelegatessupportedLocales

localizationsDelegates列表中的元素是生成本地化值集合的工厂。GlobalMaterialLocalizations.delegate 为Material 组件库提供的本地化的字符串和其他值,它可以使Material 组件支持多语言。 GlobalWidgetsLocalizations.delegate定义组件默认的文本方向,从左到右或从右到左,这是因为有些语言的阅读习惯并不是从左到右,比如如阿拉伯语就是从右向左的。

supportedLocales也接收一个Locale数组,表示我们的应用支持的语言列表,在本例中我们的应用只支持美国英语和中文简体两种语言。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //3、状态组件绑定,将语言状态与组件绑定
        return Consumer2<ThemeModel,CurrentLocale>(   //语言设置1:语言状态获取方式,暂时只看CurrentLocale
          builder: (context,themeModel,currentLocale,child)
          {
            return MaterialApp(
               localizationsDelegates: [
                // 本地化的代理类
                GlobalMaterialLocalizations.delegate,
                GlobalWidgetsLocalizations.delegate,
                DemoLocalizationsDelegate.delegate
              ],
              locale: currentLocale.value,//语言设置2
               supportedLocales: [
                   const Locale('zh', 'CN'), // 中文简体
                   const Locale('en', 'US'), // 美国英语              
                //其它Locales
              ],
              theme: AppTheme.getThemeData(themeModel.value), 
              onGenerateTitle: (context){                                              
                return DemoLocalizations.of(context).taskTitle;
              },
              initialRoute: welcomeRoute,//欢迎页
              routes: routes   //注册路由
            );
          },
        );

  }
}

关键是语言设置标注的两处,Consumer2是获取你注册的状态的一种方式,因为还有一个主题设置状态所以是2。如果你上面选择的是删除,没有新建一个themeModel那这里要改成Consumer,如下

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //3、状态组件绑定,将语言状态与组件绑定
        return Consumer<CurrentLocale>(   //语言设置1:语言状态获取方式
          builder: (context,currentLocale,child)
          {
            return MaterialApp(
               localizationsDelegates: [
                // 本地化的代理类
                GlobalMaterialLocalizations.delegate,
                GlobalWidgetsLocalizations.delegate,
                DemoLocalizationsDelegate.delegate
              ],
              locale: currentLocale.value,//语言设置2
               supportedLocales: [
                   const Locale('zh', 'CN'), // 中文简体
                   const Locale('en', 'US'), // 美国英语              
                //其它Locales
              ],
         
              onGenerateTitle: (context){                                              
                return DemoLocalizations.of(context).taskTitle;
              },
              initialRoute: welcomeRoute,//欢迎页
              routes: routes   //注册路由
            );
          },
        );

  }
}

然后通过locale我们可以手动指定APP的locale

我们现在需要先注册DemoLocalizationsDelegate类,然后在需要国际化文本的地方通过DemoLocalizations.of(context)来动态获取当前Locale文本。如上面的title,其它需要国际化的地方都通过此方法获取对应文本。

    这是设置页面
     appBar: AppBar(
        title:Text(DemoLocalizations.of(context).setTitle),
        centerTitle: true,
      ),

    此处DemoLocalizations.of(context).setTitle调用的就是DemoLocalizations里的文本

最后只需要在MaterialApp的localizationsDelegates列表中添加我们的Delegate实例即可完成注册

 

常见问题:为什么对title国际化要使用onGenerateTitle?

MaterialApp有一个title属性,用于指定APP的标题。在Android系统中,APP的标题会出现在任务管理器中。所以也需要对title进行国际化。但是问题是很多国际化的配置都是在MaterialApp上设置的,我们无法在构建MaterialApp时通过Localizations.of来获取本地化资源,所以我们需要设置一个onGenerateTitle回调。在我下面贴出的参考文章中有详细解释。

5、第五步

在你设置语言的地方调用另一种获取状态的方式Provider.of(context)调用内部设置方法改变语言状态。比如我是在设置页面

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/themeModel.dart';
import '../provider/locak_storage.dart';
import '../provider/currentLocale.dart';
import '../intl/localizations.dart';
class Set extends StatefulWidget {
  @override
  _SetState createState() => _SetState();
}
class _SetState extends State<Set> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:Text(DemoLocalizations.of(context).setTitle),
        centerTitle: true,
      ),
      body: ListView(
        children: [
          InkWell(
              child:Card(
                child: ListTile(
                  title: Text(DemoLocalizations.of(context).themeSet),
                  trailing: Icon(Icons.arrow_right),
                ),
              ),
              onTap: () async
              {
                  int i = await showDialog<int>(
                        context: context,
                        builder: (BuildContext context) {
                          return SimpleDialog(
                            title: const Text('请选择主题'),
                            children: <Widget>[
                              SimpleDialogOption(
                                onPressed: () {
                                  // 返回1
                                  Navigator.pop(context, 1);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('蓝色'),
                                ),
                              ),
                              SimpleDialogOption(
                                onPressed: () {
                                  // 返回2
                                  Navigator.pop(context, 2);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('紫色'),
                                ),
                              ),
                              SimpleDialogOption(
                                onPressed: () {
                                  // 返回2
                                  Navigator.pop(context, 3);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('粉色'),
                                ),
                              ),
                               SimpleDialogOption(
                                onPressed: () {
                                  // 返回2
                                  Navigator.pop(context, 4);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('深粉'),
                                ),
                              ),
                            ],
                          );
                        });

                    if (i != null) {
                        if(i==1)
                        {               
                          Provider.of<ThemeModel>(context,listen: //更改主题false).setTheme('blue');
                        }
                        else if(i==2)
                        {                
                          Provider.of<ThemeModel>(context,listen: false).setTheme('purple');
                        }
                        else if(i==3)
                        {                      
                          Provider.of<ThemeModel>(context,listen: false).setTheme('pink');
                        }
                        else if(i==4)
                        {                
                          Provider.of<ThemeModel>(context,listen: false).setTheme('deeppink');
                        }
                    }
              },
          ),
          InkWell(
                child:Card(
                child: ListTile(
                  title: Text(DemoLocalizations.of(context).langSet),
                  trailing: Icon(Icons.arrow_right),
                ),
              ),
              onTap: () async
              {
                  int i = await showDialog<int>(
                        context: context,
                        builder: (BuildContext context) {
                          return SimpleDialog(
                            title: const Text('请选择语言'),
                            children: <Widget>[
                              SimpleDialogOption(
                                onPressed: () {
                                  // 返回1
                                  Navigator.pop(context, 1);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('中文简体'),
                                ),
                              ),
                              SimpleDialogOption(
                                onPressed: () {
                                  // 返回2
                                  Navigator.pop(context, 2);
                                },
                                child: Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 6),
                                  child: const Text('美国英语'),
                                ),
                              ),
                            ],
                          );
                        });

                    if (i != null) {
                      if(i==1)
                      {
                               Provider.of<CurrentLocale>(context,listen: false).setLocale(const Locale('zh',"CH"));//更改语言
                      }
                      else 
                      {
                               Provider.of<CurrentLocale>(context,listen: false).setLocale(const Locale('en',"US"));
                      }

                    }
              },
          )

          
        ],
      ),
    );
  }
}


参考文章:(更多专业的理论解释可通过下面两个优秀文章理解,此篇博客着重实现效果)

https://book.flutterchina.club/chapter13/multi_languages_support.html

https://www.jianshu.com/p/8356a3bc8f6c

 

源码地址

https://github.com/Yimi81/Flutter-ThemeAndLang

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值