从0开始设计Flutter独立APP | 第二篇: 完整的国际化语言支持

鉴于Flutter高性能渲染和跨平台的优势,闪点清单在移动端APP上,使用了完整的Flutter框架来开发。既然是完整APP,架构搭建完全不受历史Native APP的影响,没有历史包袱的沉淀,设计也能更灵活和健壮。

国际化语言的支持,是很多APP都有的一个强需求,APP无论大小,只要还不想放弃国外的客户,一般就需要支持国际化。


flutter国际化

官方支持

Flutter官方方案提供了国际化的基础支持,如Flutter内置组件的国际化、语言代理、Widget使用语言包、语言设置回调等,并支持自定义第三方类来扩展,可以参考Flutter国际化文档。
官方支持代码示例:

class DemoLocalizations {
  DemoLocalizations(this.locale);

  final Locale locale;

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }

  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'Hello World',
    },
    'es': {
      'title': 'Hola Mundo',
    },
  };

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

官方方案的缺陷

官方的支持有几个缺陷:

  1. 依赖于BuildContext对象,在非Widget中调用时,需要层层传递BuildContext对象,或存储全局BuildContext对象。
  2. 在MaterialApp初始化前无法使用国际化(原因也是依赖于BuildContext对象)。
  3. 语言包定义推荐使用Map方式,无法利用静态语言的优势(语法提示、错误检查等);而为语言包每个属性自定义类和类字段,成本较高、使用和更新灵活性差。

i18n介绍

鉴于Flutter官方支持的缺陷,我们调研了很多第三方库,最终发现了i18n,并在此基础上、结合Flutter官方支持和自身封装,实现了更灵活易用的方案。

flutter国际化

基础使用

i18n使用yaml格式来定义语言包,同时提供构建脚本一键生成Dart语言包Class。如下:

lib/messages.i18n.yaml

button:
  save: Save
  load: Load
users:
  welcome(String name): "Hello $name!"
  logout: Logout

该配置会生成几个Class:Messages、ButtonMessages、UserMessages,生成后的Dart文件使用方式如下:

Messages m = Messages();
debugPrint(m.users.logout);
debugPrint(m.users.welcome('World'));

生成的Dart文件预览(开发时无需关心):

class Messages {
    const Messages();
    ButtonMessages get button => ButtonExampleMessages(this);
    UsersMessages get users => UsersExampleMessages(this);
}
class ButtonMessages {
    final Messages _parent;
    const ButtonMessages(this._parent);
    String get save => "Save";
    String get load => "Load";
}
class UsersMessages {
    final Messages _parent;
    const UsersMessages(this._parent);
    String get logout => "Logout";
    String welcome(String name) => "Hello $name!";
}

进阶功能

下面讲解一些进阶用法。

函数定义

i18n支持函数定义,并支持传参,如上述的welcome函数:

debugPrint(m.users.welcome('World'));

参数定义基本没有限制,可以随意定义参数个数和类型。

内置函数

i18n支持了一些内置函数,用于做不同语言解析的体验优化,如:plural、cardinal、ordinal。具体规则和使用,可以参考这里:http://cldr.unicode.org/index/cldr-spec/plural-rules

使用Dart字符串模板

Dart字符串模板是非常强大的,而在i18n中,你可以使用字符串模板(这点非常赞),如:

count(int cnt): "You have created $cnt ${_plural(cnt, one:'invoice', many:'invoices')}."

前置编译

i18n依然依赖了Dart官方提供的builder_runner工具,来从yaml文件生成Dart文件,使用方式: flutter pub run build_runner build

flutter国际化

语言包使用

前置编译后,每个语言包会生成N个Class(语言包的每一个分类或组合会生成一个Class文件),然后会生成一个根Class,我们可以直接使用根Class(当然也可以使用任何一个分类层级的Class)。

比如两个语言包文件: AppMessages.i18n.yamlAppMessages_en.i18n.yaml(未加语言后缀的,会认为是默认语言包,因此AppMessages.i18n.yaml是默认语言包),会生成2个根Dart Class: class AppMessagesclass AppMessages_en extends AppMessages

AppMessages_en自动继承自AppMessages,因此我们可以直接使用AppMessages类型来存储语言包,并在语言切换时重新为其实例化对应的子类:

AppMessages appMessages = new AppMessages();

resetLocalLang(String localeName) {
  switch (localeName) {
    case 'en':
      appMessages = AppMessages_en();
      break;
    case 'zh':
    default:
      appMessages = AppMessages();
      break;
  }
}

然后你可以在任意地方使用语言包:

debugPrint('Load Button: ${appMessages.button.load}');

FlatButton(
  child: Text(appMessages.button.save),
  onPressed: () {
    /// 干点什么
  },
)

Flutter集成

集成到Flutter,依然要依赖于官方的支持,在MaterialApp中设置和监听本地语言包:

@override
Widget build(BuildContext context) {
  return MaterialApp(
    localeResolutionCallback:
        (Locale locale, Iterable<Locale> supportedLocales) {
      /// Local changed
    },
    localizationsDelegates: [
      GlobalMaterialLocalizations.delegate,
      GlobalWidgetsLocalizations.delegate,
    ],
    supportedLocales: ['zh', 'en']
        .map((loc) => new Locale(loc))
        .toList(growable: false),
    /// ...
  );
}

结尾

国际化支持,是一个移动端APP框架层的基础能力,设计原则应该是使用无感知、灵活易扩展;但维护成本是难免有增加的,比如每次改文案要所有语言包同时更改。

讲到这里,还并没有完成基础框架的搭建,后面我们会讲解更多的Flutter架构设计内容,比如:通知、分享、UI设计等等。


持续分享闪点清单在Flutter上的开发经验。闪点清单,一款悬浮清单软件:

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