Flutter国际化

 * author:lebonbill
 * E-mail:lebonbillwu@gmail.com

前言

如果APP有需要支持多种语言,就需要支持国际化,无论是android和ios,现在针对flutter的国际化做了一些学习理解和总结,这里主要是讲解一下Flutter国际化的一些用法,包括Localization widget,intl包和flutter_i18n包的用法

支持国际化

在默认情况下,Flutter只支持英文,要添加多国语言支持,需要在pubspec.yaml文件中添加“flutter_localizations”依赖,如下

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

然后指定MaterialApp的localizationsDelegates和supportedLocales

import 'package:flutter_localizations/flutter_localizations.dart';
new MaterialApp(
 localizationsDelegates: [
   // 本地化代理
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // 美国英语
    const Locale('zh', 'CN'), // 中文简体
    //其它Locales
  ],
  // ...
)

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

Locale区域

Locale类是用来标识用户的语言环境的,它包括语言和国家两个标志,languageCode和countryCode:

const Locale(languageCode,countryCode)
const Locale('zh', 'CN') // 中文简体

获取当前的语言环境:
Locale currentLocale = Localizations.localeOf(context);
Localizations Widget一般位于Widget树中其它业务组件的顶部,它的作用是定义区域Locale以及设置子树依赖的本地化资源。 如果系统的语言环境发生变化,WidgetsApp将创建一个新的Localizations Widget并重建它,这样子树中通过Localizations.localeOf(context) 获取的Locale就会更新

监听系统语言切换

当我们更改系统语言设置时,APP中的Localizations widget会重新构建,最终界面会重新build达到切换语言的效果。
我们可以通过localeResolutionCallback或localeListResolutionCallback回调来监听locale改变的事件

localeResolutionCallback:(Locale locale, Iterable<Locale> supportedLocales){};

locale返回值是如果用户没有指定locale的话,是默认当前系统语言,如果用户切换系统语言,locale也会变更为相应的语言,如果客户自己指定语言的话,则改变系统语言不会变更locale的值
用户可以自己指定locale的值如下:

MaterialApp{
    locale:const('zh','CN')//指定简体中文
}

supportedLocales返回的是系统支持的所有已添加的语言,如下图


123.png

Localizations widget

Flutter的世界都是widget,当中包括语言"组件",定义Localization widget,用来加载查找本地化的对象,通过Localization.of(context,type)使用这些语言资源,定义一个

实现Localizations类
class DemoLocalizations {
  DemoLocalizations(this._locale);
  final Locale _locale;
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of(context, DemoLocalizations);
  }

  static Map<String, Map<String, String>> _localValues = {
    'en': {"title": "title"},
    "zh": {"title": "标题"}
  };

  String get title {
    return _localValues[_locale.languageCode]["title"];
  }
}

其中of是常用的方法,所以为了方便会定义静态方法of并且返回自身对象

实现LocalizationDelegate代理
继承LocalizationDelegate是为了加载新的locale资源,这里有一个重载的load方法需要实现,

class DemoLocalizationDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  @override
  bool isSupported(Locale locale) {
    return ['en', 'zh'].contains(locale.languageCode);//是否支持的语言
  }

  @override
  Future<DemoLocalizations> load(Locale locale) {
    return SynchronousFuture(DemoLocalizations(locale));//异步记载资源
  }

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

最后在MaterialApp和WidgetsApp的localizationsDelegates添加定义的代理

localizationsDelegates: [
   // 本地化代理
   DemoLocalizationDelegate();//自定义代理
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],

然后通过Localization widget的of方法使用资源:

DemoLocalization lo= DemoLocalization.of(context);
String title=lo.title;//默认是英文的话会返回"title",切换语言的时候该值会变成"标题"

使用intl包

为了方便翻译人员和开发人员分工协作,使用intl包可以很好的将翻译资源文件和代码分离,arb文件用做于翻译资源,dart用于开发人员编写相关的翻译代码,因此在pubspec.yaml中引用intl包依赖:

dependencies:
  intl: ^0.15.7 
dev_dependencies:
  intl_translation: ^0.17.2

intl包是用来翻译前面所说的Localization widget相关的dart的文件生成arb文件(arb文件下面会讲解),如前面所说的Localization widget类会改成这样:

import 'dart:async';

import 'package:intl/intl.dart';
import 'package:flutter/widgets.dart';
class DemoLocalizations {
  DemoLocalizations(this._locale);
  final Locale _locale;
  
  static Future<DemoLocalizations> load(Locale locale) async {
    String name =
        locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    String localeName = Intl.canonicalizedLocale(name);
    return initializeMessages(locale.toString())
        .then((Object _) {
      return new DemoLocalizations(locale);
    });
  }
    
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of(context, DemoLocalizations);
  }

  String get title =>
      Intl.message("这个是一个标题", name: "title", desc: "标题用的翻译文本", args: []);
  }    
}

这里的静态函数load,是为了方便代理类调用新增,里面有一个initializeMessages会显示报错,先可以忽略,上面Localization类中使用了Intl.message,Intl.message的相关用法可以参考官方api(点击查看),这里就是需要翻译的文字,通过intl包会把Intl.message相关的资源"提取"生成arb文件,需要运行命令行如下:

$ flutter pub pub run intl_translation:extract_to_arb --output-dir="保存arb文件的目录" "需要提取翻译的dart文件"

例子:
$ flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/demo_localizations.dart

需要在lib中新建l10n文件夹用于保存arb文件,后面带的参数就是上述的DemoLocalizations类所在位置,运行后会在l10n看到一个intl_messages.arb文件。
把上面的intl_messgaes文件当成模板复制粘贴intl_zh.arb和intl_en.arb,然后翻译相关的内容,arb是一个json结构的文件,intl_zh.arb文件结构如下

{
  "@@last_modified": "2019-01-10T14:17:00.088434",
  "title": "这个是一个标题",
  "@title": {
    "description": "标题用的翻译文本",
    "type": "text",
    "placeholders": {}
  }
}

翻译完所有arb资源后,intl_translation包就是用来把arb生成相关的dart代码文件,运行命令行:

$ flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n \
   --no-use-deferred-loading lib/demo_localizations.dart lib/l10n/intl_*.arb

运行结果会出现

No @@locale or _locale field found in intl_en, assuming 'en' based on the file name.
No @@locale or _locale field found in intl_messages, assuming 'messages' based on the file name.
No @@locale or _locale field found in intl_zh, assuming 'zh' based on the file name.

各自生成如下的新文件
然后在刚才initializeMessages保存的DemoLocalizations中引入

import 'l10n/messages_all.dart';

警告就会消失
然后修改Localization代理中的load,引用上面的Localization widget load方法即可
调用资源的方法和上面的一样

DemoLocalizations.of(context).title
DemoLocalizations.of(context).name

使用flutter_i18n

flutter_i18n是另外一种进行国际化的方法,有别于intl,它主要是利用json文件来进行翻译,个人认为最简单的一种,可以自己定app语言,方便刷新切换语言,不依赖系统语言

使用方法步骤如下:
在pubspec.yaml引用依赖并且添加资源目录这里我定义存放资源文件目录是flutter_i18n

dependencies:
  flutter:
    sdk: flutter
  flutter_i18n: ^0.5.2
flutter:
  assets:
  - flutter_i18n/  

新建json文件,命名规则可以这样{languageCode}_{countryCode}.json或者{languageCode}.json,这里我命名为zh_CN.json,内容如下:

{
  "test": {
    "title": "这个是一个标题哦",
    "name": "我的名字叫{name}"
  }
}

添加Localization代理

localizationsDelegates: [
        FlutterI18nDelegate(useCountryCode, [fallbackFile, basePath]),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate
],
useCountryCode参数是用于json文件的命名规则:

如果你是用{languageCode}_{countryCode}来命名,useCountryCode参数必须为true
如果你是用{languageCode}来命名, useCountryCode就必须为false

fallbackFile参数引入版本0.1.0并提供默认语言,在未提供当前运行系统的转换时使用。这应该包含assets文件夹中有效的json文件的名称如"zh_CN"
basePath参数可选地用于设置翻译的基本路径。如果未设置此选项,则默认路径为assets / flutter_i18n。此路径必须与pubspec.yaml中定义的路径相同。
配置例子如:
FlutterI18nDelegate(true, "en_US", "flutter_i18n")

flutter_i18n实现

配置完成后,可以通过以下方法调用:

FlutterI18n.translate(buildContext, "your.key")
FlutterI18n.translate(buildContext, "test.title");//这是一个标题哦
FlutterI18n.translate(context, "test.name", {"name": "AAA"});我的名字叫做AAA

如果你想切换应用语言可以这样做:

await FlutterI18n.refresh(buildContext, languageCode, {countryCode});
await FlutterI18n.refresh(buildContext, "en", "US");//如切换英语

flutter_i18n 支持复数,你可以调用以下方法:

FlutterI18n.plural(buildContext, "your.key", pluralValue);如
"clicked": {
    "times-0": "You clicked zero!",
    "times-1": "You clicked {time} time!",
    "times-2": "You clicked {times} times!"
  }
  
FlutterI18n.plural(buildContext, "clicked.times", 0)//You clicked zero!
FlutterI18n.plural(buildContext, "clicked.times", 1)//You clicked 1 time!
FlutterI18n.plural(buildContext, "clicked.times", 2)//You clicked 2 times!

总结

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

推荐阅读更多精彩内容