Flutter多语言 2022-11-15 周二

方案选择

  • Flutter对多语言是支持的,不够功能有限,也不是很好用。

  • GetX中对多语言的支持做得很好,果断选择采用。

GetX中关于多语言介绍

  • 继承类Translations,以key,value的形式实现多语言。第一级的key为语言选择,第二级的key为字段标签。最后的value就是最终显示的文本。
import 'package:get/get.dart';

class Messages extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en_US': {
          'hello': 'Hello World',
        },
        'de_DE': {
          'hello': 'Hallo Welt',
        }
      };
}
  • 使用的话,只要加.tr后缀就可以,非常方便
Text('hello'.tr);
  • 通过@标记,还可以带参数,这个和OC的习惯一致。
import 'package:get/get.dart';


Map<String, Map<String, String>> get keys => {
    'en_US': {
        'logged_in': 'logged in as @name with email @email',
    },
    'es_ES': {
       'logged_in': 'iniciado sesión como @name con e-mail @email',
    }
};

Text('logged_in'.trParams({
  'name': 'Jhon',
  'email': 'jhon@example.com'
  }));
  • 初始化,使用Translations的地方在程序初始化的地方。这也导致Flutter的热加载不适用于多语言,每次改动都要重新加载,不是很方便。
return GetMaterialApp(
    translations: Messages(), // your translations
    locale: Locale('en', 'US'), // translations will be displayed in that locale
    fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected.
);
  • 切换语言,其实就是更新Locale,更换Translations的第一级key
var locale = Locale('en', 'US');
Get.updateLocale(locale);
  • 获取系统语言设置。也就是获取系统的语言key,自动设置。说实话,这作用不大,还不如固定一个默认语言(用在初始化中),然后提供入口进行切换(比如app的设置页面)。
    如果非要根据手机的语言设置,自动设置程序的默认语言,那么就可以按照下面的方式来写
return GetMaterialApp(
    locale: Get.deviceLocale,
);

修改方案

所有的语言定义都在Translations一个文件中,很容易出现文件过长的问题。所以会把两级的Map进行拆分,把第二级的Map独立为各个语言文件。

  • 将Translations的第二级Map独立成文件
class TranslationService extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en': en_language,
        'es': es_language,
        'zh': zh_language,
      };
}
  • 第二级的语言文件就是一个Map
const Map<String, String> en_language = {
  /// 公共部分-基础
  WidgetIds.commonBaseTitle: 'Title',
  WidgetIds.commonBaseContent: 'Content',
  WidgetIds.commonBaseDescription: 'Description',
  WidgetIds.commonBaseOk: 'OK',
  WidgetIds.commonBaseCancel: 'Cancel',
  WidgetIds.commonBaseSubmit: 'Submit',
}
  • 各个语言文件的key是公用的,所以集中在一个地方进行定义。
class WidgetIds {
  /// 简要说明:
  /// 这里定义组件的id
  /// 变量定义采用小驼峰的命名习惯
  /// 变量的值采用小写加点隔离的方式,类似包名的习惯
  /// 可以用来区分组件: dart中组件统一用Widget表示,所以这里命名为WidgetIds
  /// 使用场景有多语言,统计等等需要组件定位的场景。
  /// 多语言:使用的时候,需要加上.tr后缀,比如:  WidgetIds.commonBaseTitle.tr
  /// 多语言:定义文件中,作为字典的key来用,比如: WidgetIds.commonBaseTitle: 'Title',

  /// 公共部分-基础
  static const String commonBaseTitle = 'common.base.title';
  static const String commonBaseContent = 'common.base.content';
  static const String commonBaseDescription = 'common.base.description';
  static const String commonBaseOk = 'common.base.ok';
}
  • 相关的文件可以放在一个文件夹中
企业微信截图_8eeea650-0d45-4533-8170-207242935534.png

简略方案

  • 将语言定义独立成单独的key ,value文件,这个保持不变。

  • 组件id定义,也就是语言文件的key定义,与实际的字符显示相差较远。另外定义想名字也是键头疼的事。

  • 使用起来也比较麻烦,像WidgetIds.commonBaseTitle.tr之类的,看上去也不直观。在这种时候,更容易忘记.tr后缀,导致显示'common.base.title'之类的内容。

  • 如果key对应的value没有定义,那么会直接用key的内容来代替,比如Text('hello'.tr); 如果定义了语言文件,会显示定义过的 'Hello World'或者'Hallo Welt'。但是如果没有定义对应的key,value,那么就会直接显示'hello'

  • 我们的应用,默认语言是英语,所以为图方便,我们就直接拿英语做key了,这样还可以少两个文件。widget_ids.dart这key的定义文件就不需要了。

  • 当然,这样做对于那种给@参数的用法就不适用了。凡事有利有弊。当然,要用也是可以的,把两种思路结合起来:大部分用英文作为key;带参数的,就自定义key。

  • 其他的语言文件,直接以英语当做key

const Map<String, String> zh_language = {
  /// 公共部分-基础
  'Title': '标题',
  'Content': '内容',
  'Description': '描述',
  'OK': '确定',
  'Cancel': '取消',
}
  • 适用的地方直接在英语后面加个.tr后缀就可以了。
    Text(
      'Cancel'.tr,
      style: TextStyle(
        color: const Color(0xFF333333),
        fontSize: 12.sp,
        fontWeight: FontWeight.w400,
      ),
    );

Locale简介

  • 构造函数需要两个字符串,语言代码和地区代码。其中语言代码是必选的,地区代码是可选的
const Locale(
    this._languageCode, [
    this._countryCode,
  ])
  • iOS的手机的菜单路径: 设置 =》通用 =》语言与地区
企业微信截图_b4c1ae98-aab3-41bc-92c5-d4f6be5b9384.png
  • 可以理解为语言代码是主键,地区代码是副键。比如同样是中文,也区分中文大陆,中文香港,中文新加坡,中文台湾等。
    语言地区代码
企业微信截图_19fd2a9c-c866-414e-9a99-e2f245f2df72.png
  • Translation。s中的key,也是以语言代码为主,地区代码为辅。中划线用下划线代替。
    另外,也可以简单处理,只用语言代码,不要地区代码。就算地区代码对不上,只要语言代码对了,也能选中。
class MultiLanguage extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        /// 英文
        'en_US': en_US,

        /// 中文
        'zh_CN': zh_CN,
      };
}

更新的话,给Get.updateLocale(const Locale('en', 'TT'));仍然能够选出英文。
既然如此,干脆key值只给语言代码,不管区域代码。

简单实践

  • 只管语言代码,不管区域代码,简单化处理。当前的需求,只要求有中文版和英文版,还没有那么细。

  • 初始化取手机的系统设置;

  • 默认语言设定为中文

  • main.dart中的设置

企业微信截图_7ad8d70c-d49a-4abe-9a1d-6a02ace8c5a6.png
  • 字典中只管语言代码,不管区域代码
import 'en.dart';
import 'zh.dart';

class MultiLanguage extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        /// 英文
        'en': en,

        /// 中文
        'zh': zh,
      };
}
/// 中文多语言字典
const Map<String, String> zh = {
  'title': '这是标题',
  'login': '登录用户 @name,邮箱账号 @email',
};
/// 英文多语言字典
const Map<String, String> en = {
  'title': 'This is Title!',
  'login': 'logged in as @name with email @email',
};
  • 切换时只给语言代码,不管区域代码
Get.updateLocale(const Locale('en'));
  • 简单例子
    代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'home_controller.dart';

class HomePage extends GetView<HomeController> {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<HomeController>(
      builder: (context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('HomePage'),
            centerTitle: true,
          ),
          body: Center(
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () {
                Get.updateLocale(const Locale('en'));
              },
              child: Text(
                'login'.trParams(
                    {'name': 'zhang san', 'email': 'zhangsan@baidu.com'}),
                style: const TextStyle(fontSize: 20),
                textAlign: TextAlign.center,
              ),
            ),
          ),
        );
      },
    );
  }
}

初始界面:(手机设置为中文)

企业微信截图_d81b0547-16a3-478b-9eb1-99157ec2fdfa.png

点一下文字,就切换为英文

企业微信截图_3b40853e-876c-4b76-be43-ccbafed21f78.png
  • 关于key的定义
    (1)如果想规范一点,那么就用全局变量,按照组件id的的模式统一规范;
    (2)如果想方便一点,就用英语作为key。那么en.dart绝大部分可以key和value一样;
    zh.dart就算没定义,也会显示有意义的英文,既方便又使用。

我们一开始是用方法(1)的;很规范,但是真的烦。 后来就改成了方法(2);工作量减少很多。

系统日期组件多语言

  • 系统的DateTime组件有一个locale参数,直接加上会崩溃。

  • 如果想要这种系统组件支持多语言,需要额外引入一个库flutter_localizations

  • 参考文章:

Flutter配置国际化localizations

实践结果

以上内容是一开始时的做法,后来做了修改。

  1. 命名采用统一命名的方式: “模块.页面.其他限定”,全部用小写字符,单词间用下划线_分割,也就是Linux命名方式。例如:
  'order.cancel_order.pop.title': 'Cancel Successfully!',
  'order.cancel_order.pop.info1': 'The refund is here',
  'order.cancel_order.pop.info2': 'Pandabuy account balance',

订单模块,取消订单,弹窗的标题和信息

  1. 本地只保留一份语音文件,作为默认值;

  2. 其他多语言文件放后台,到时候通过接口下载。

  3. 做一个编辑后台,让其他部门操作,生成新的语言文件。

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

推荐阅读更多精彩内容