Day18 - Flutter - 国际化

概述

  • 国际化的认识
  • 国际化的适配
  • 国际化的工具
一、国际化的认识

开发一个App,如果我们的App需要面向不同的语种(比如中文、英文、繁体等),那么我们需要对齐进行国际化开发。
国际化的英文称呼:internationalization(简称为i18n,取前后两个字母,18表示中间省略字母的个数)
App国际化开发主要包括:文本国际化(包括文本的顺序),Widget显示的国际化:
比如我们下面开发的这个App
某些文本在英文环境下应该显示为英文;
某些Widget在中文环境下,应该显示中文(比如弹出的时间选择器);

某些Widget在中文环境下,应该显示中文(比如弹出的时间选择器)

二、国际化的适配
  • 2.1、Widget的国际化
    Flutter给我们提供的Widget默认情况下就是支持国际化,但是在没有进行特别的设置之前,它们无论在什么环境都是以英文的方式显示的。
    如果想要添加其他语言,你的应用必须指定额外的 MaterialApp 属性并且添加一个单独的 package,叫做 flutter_localizations
    截至到 2020 年 2 月份,这个 package 已经支持大约 77 种语言。

    • 2.1.1、pubspec.ymal 添加依赖,想要使用 flutter_localizations 的话,我们需要在 pubspec.yaml 文件中添加它作为依赖如下,记得点击 Pub get 更新一下

      dependencies:
          flutter:
             sdk: flutter
          flutter_localizations:
             sdk: flutter
      
    • 2.1.2. 设置 MaterialApp

      • 在localizationsDelegates中指定哪些Widget需要进行国际化

        • 用于生产本地化值集合的工厂
        • 我们这里指定了Material、Widgets、Cupertino都使用国际化
      • supportedLocales指定要支持哪些国际化:我们这里指定中文英文(也可以指定国家编码)

        MaterialApp(
            localizationsDelegates: [
               // 指定本地化的字符串和一些其他的值
               GlobalMaterialLocalizations.delegate, 
               // 对应的Cupertino风格
               GlobalCupertinoLocalizations.delegate, 
               // 指定默认的文本排列方向, 由左到右或由右到左
               GlobalWidgetsLocalizations.delegate 
            ],
            supportedLocales: [
                Locale('en'),
                Locale('zh')
            ],
        );
        

        提示:如果要指定语言代码、文字代码和国家代码,可以进行如下指定方式:

        // Full Chinese support for CN, TW, and HK
        supportedLocales: [
           const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
           const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
           const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
           const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
           const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
           const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
        ],
        
    • 2.1.3. 查看Widget结果设置完成后,我们在Android上将语言切换为中文,查看结果,我们可以看到英文的 时间Widget里面的文字是 中文了

      • 但是对于iOS,将语言切换为中文,依然显示是英文的Widget

        • 这是因为iOS定义了一些应用的元数据,其中包括支持的语言环境;
        • 我们必须将其对应的元数据中支持的语言添加进去;
        • 元数据的设置在iOS项目中对应的info.plist文件中;
      • 修改 iOS 的 info.plist 文件配置:

        1、Xcode打开工程

        2、添加语言的选项Localizations

        3、选择Localizations

        4、添加中文简体

        配置完成后,卸载之前的app,重新安装:我们可以看到和安卓模拟器一样的效果

  • 2.2、其它文本国际化
    App中除了有默认的Widget,我们也希望对自己的文本进行国际化

    • 2.2.1、创建本地化类,该类用于定义我们需要进行本地化的字符串等信息:

      • 1.我们需要一个构造器,并且传入一个Locale对象(后续会使用到)

      • 2.定义一个Map,其中存放我们不同语言对应的显示文本

      • 3.定义它们对应的getter方法,根据语言环境返回不同的结果

        import 'package:flutter/material.dart';
        
        class JKLocalizations {
            final Locale locale;
        
            JKLocalizations(this.locale);
        
            static JKLocalizations of(BuildContext context) {
                return Localizations.of(context, JKLocalizations);
            }
        
            static Map<String, Map<String, String>> _localizedValues = {
               "en": {
                   "title": "home",
                   "greet": "hello~",
                   "picktime": "Pick a Time"
               },
               "zh": {
                   "title": "首页",
                   "greet": "你好~",
                   "picktime": "选择一个时间"
               }
            };
            // 标题
            String get title {
                return _localizedValues[locale.languageCode]["title"];
            }
            // 问候
            String get greet {
                return _localizedValues[locale.languageCode]["greet"];
            }
            // 时间
            String get pickTime {
               return _localizedValues[locale.languageCode]["picktime"];
            }
        }
        
    • 2.2.2、自定义Delegate

      • 上面的类定义好后,我们在什么位置或者说如何对它进行初始化呢?
        • 答案是我们可以像Flutter Widget中的国际化方式一样对它们进行初始化;
        • 也就是我们也定义一个对象的Delegate类,并且将其传入localizationsDelegates中;
        • Delegate的作用就是当Locale发生改变时,调用对应的load方法,重新加载新的Locale资源;
      • JKLocalizationsDelegate需要继承自LocalizationsDelegate,并且有三个方法必须重写:
        • isSupported:用于当前环境的Locale,是否在我们支持的语言范围
        • shouldReload:当Localizations Widget重新build时,是否调用load方法重新加载Locale资源
          • 一般情况下,Locale资源只应该在Locale切换时加载一次,不需要每次Localizations重新build时都加载一遍;
          • 所以一般情况下返回false即可;
        • load方法:当Locale发生改变时(语言环境),加载对应的HYLocalizations资源
          • 这个方法返回的是一个Future,因为有可能是异步加载的;

          • 但是我们这里是直接定义的一个Map,因此可以直接返回一个同步的Future(SynchronousFuture)

            class JKLocalizationsDelegate extends LocalizationsDelegate<JKLocalizations> {
                 @override
                 // 支持的语言
                 bool isSupported(Locale locale) {
                     return ["en", "zh"].contains(locale.languageCode);
                 }
            
                 @override
                 bool shouldReload(LocalizationsDelegate<JKLocalizations> old) {
                    return false;
                 }
            
                 @override
                 Future<JKLocalizations> load(Locale locale) {
                    return SynchronousFuture(JKLocalizations(locale));
                 }
            
                 static JKLocalizationsDelegate delegate = JKLocalizationsDelegate();
            }
            
    • 2.2.3. 使用本地化类

      • 1>、 在MaterialApplocalizationsDelegates里里添加我们的 delegeate

      • 2>、接着我们可以在代码中使用JKLocalization类。
        我们可以通JKYLocalizations.of(context)获取到JKLocalizations对象,本质上是在调用 Localizations.of(context, JKLocalizations)

        Column(
             mainAxisAlignment: MainAxisAlignment.center,
             children: [
                 Text(JKLocalizations.of(context).greet),
                 RaisedButton(
                    child: Text(JKLocalizations.of(context).pickTime, style: TextStyle(fontSize: 20, color:  Colors.orange),),
                    onPressed: () {
                        showDatePicker(
                           context: context,
                           initialDate: DateTime.now(),
                           firstDate: DateTime(2000),
                           lastDate: DateTime(3000)
                        );
                    }
                 )
             ],
        ),
        
    • 2.2.4. 异步加载数据,也就是 JKLocalizations 里面的 _localizedValues 我们做一个抽取

      static Map<String, Map<String, String>> _localizedValues = {};
      
      // 获取配置的 Map
      Future<bool> loadJson() async {
           // 1.加载json文件
           String jsonString = await rootBundle.loadString("assets/json/i18n.json");
      
           // 2.转成map类型
           final Map<String, dynamic> map = json.decode(jsonString);
      
           // 3.注意:这里是将Map<String, dynamic>转成Map<String, Map<String, String>>类型
           _localizedValues = map.map((key, value) {
               return MapEntry(key, value.cast<String, String>());
           });
           return true;
      }
      

      提示:这里我们也可以改为网络请求,拿到数据后一样转换类型

      JKLocalizationsDelegate 中使用 异步 进行加载:

      @override
      Future<JKLocalizations> load(Locale locale) async {
         final localization = JKLocalizations(locale);
         await localization.loadJson();
         return localization;
      }
      

    提示:我们自己的配置 其它文本国际化,我们每次都要

    • 1、修改 json文件
    • 2、在 JKLocalizations 里面增加 对应的 get 方法,每次还要写 key ,很容易写错,要注意
    • 3、如果增加新的语言,我们还要 修改JKLocalizationsDelegate 增加支持的语言 和 在 iOS的 info.plist 文件里面增加新的语言类型
三、国际化的工具
  • 3.1、认识 arb文件
    目前我们已经可以通过加载对应的json文件来进行本地化了
    但是还有另外一个问题,我们在进行国际化的过程中,下面的代码依然需要根据 json文件 手动编写:

    String get title {
       return _localizedValues[locale.languageCode]["title"];
    }
    
    String get greet {
       return _localizedValues[locale.languageCode]["greet"];
    }
    
    String get pickTime {
       return _localizedValues[locale.languageCode]["picktime"];
    }
    
    • 有没有一种更好的方式,让我们可以快速在本地化文件-dart代码文件直接来转换呢?答案就是arb文件
  • 3.2、intl package
    官方文档推荐可以使用intl package来进行arb和dart文件之间的转换(通过终端指令)

    需要在在 pubspec.yaml 中添加其相关的依赖,具体步骤这里不再详细给出,可以参考官方文档

  • 3.3、使用IDE插件

    • 在之前有一个比较好用的Android Studio的插件:Flutter i18n
      • 但是这个插件已经很久不再维护了,所以不再推荐给大家使用
    • 目前我们可以使用另外一个插件:Flutter Intl
      • 该插件更新维护频率很高,并且广受好评;
      • 另外,在Android Studio和VSCode中都是支持的

    我们这里以Android Studio为例,讲解其使用过程:

    • 3.3.1、安装插件
      在Android Studio的Plugins中安装插件:
      Flutter Intl

      提示:如果遇到如下图的问题,可看下图修复

      找不到插件的解决办法

      选择自动
  • 3.3.2. 初始化 intl
    选择工具栏Tools - Flutter Intl - Initialize for the Project

    初始化 intl

    生成两个文件
    • 完成上面的操作之后会自动生成如下文件目录:
      • generated是自动生成的dart代码
      • I10n 是对应的 arb文件目录
  • 3.3.3、使用intl

    • 在localizationsDelegates中配置生成的class,名字是S

      • 1.添加对应的delegate

      • 2.supportedLocales使用S.delegate.supportedLocales

        localizationsDelegates: [
           GlobalMaterialLocalizations.delegate,
           GlobalWidgetsLocalizations.delegate,
           GlobalCupertinoLocalizations.delegate,
           JKLocalizationsDelegate.delegate,
           S.delegate
        ],
        supportedLocales: S.delegate.supportedLocales,
        

      因为我们目前还没有对应的本地化字符串,所以需要在intl_en.arb文件中编写:编写后ctrl(command) + s保存即可

      {
        "title": "home",
        "greet": "hello~",
        "picktime": "Pick a time"
      }
      

      在代码中使用即可,按照如下格式:S.of(context).title

  • 3.3.4、添加中文
    如果希望添加中文支持:add local,在弹出框中输入zh即可,我们会发现,会生成对应的intl_zh.arbmessages_zh.dart文件



    生成对应的`intl_zh.arb`和`messages_zh.dart`文件
    • 编写intl_zh.arb文件

      {
          "title": "首页",
          "greet": "您好~",
          "picktime": "选择一个时间"
      }
      

      查看界面,会根据当前语言显示对应的语言文本

  • 3.4. arb 其它语法

    • 如果我们希望在使用本地化的过程中传递一些参数:

      • 比如hello kobe或hello james
      • 比如你好啊,周杰伦 或 你好啊
    • 修改对应的arb文件:{name} 表示传递的参数

      {
        "title": "home",
        "greet": "hello~",
        "picktime": "Pick a time",
        "sayHello": "hello {name}"
      }
      

      在使用时,传入对应的参数即可:

      Text(S.of(context).sayHello("周杰伦")),
      
    • 提示: arb 的好处, 每次我们只需要修改 intl_en.arbintl_zh.arb等文件的 json数据,它会自动帮我们生成对用名字的 get 方法,到时候直接 S.of(context).名字 调用即可

最后对应的 i18n代码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345