方案選擇
Flutter對多語言是支持的,不夠功能有限,也不是很好用。
GetX中對多語言的支持做得很好,果斷選擇采用。
GetX中關(guān)于多語言介紹
- 繼承類Translations,以key,value的形式實現(xiàn)多語言。第一級的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);
- 通過@標記,還可以帶參數(shù),這個和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);
- 獲取系統(tǒng)語言設(shè)置。也就是獲取系統(tǒng)的語言key,自動設(shè)置。說實話,這作用不大,還不如固定一個默認語言(用在初始化中),然后提供入口進行切換(比如app的設(shè)置頁面)。
如果非要根據(jù)手機的語言設(shè)置,自動設(shè)置程序的默認語言,那么就可以按照下面的方式來寫
return GetMaterialApp(
locale: Get.deviceLocale,
);
修改方案
所有的語言定義都在Translations一個文件中,很容易出現(xiàn)文件過長的問題。所以會把兩級的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 = {
/// 公共部分-基礎(chǔ)
WidgetIds.commonBaseTitle: 'Title',
WidgetIds.commonBaseContent: 'Content',
WidgetIds.commonBaseDescription: 'Description',
WidgetIds.commonBaseOk: 'OK',
WidgetIds.commonBaseCancel: 'Cancel',
WidgetIds.commonBaseSubmit: 'Submit',
}
- 各個語言文件的key是公用的,所以集中在一個地方進行定義。
class WidgetIds {
/// 簡要說明:
/// 這里定義組件的id
/// 變量定義采用小駝峰的命名習慣
/// 變量的值采用小寫加點隔離的方式,類似包名的習慣
/// 可以用來區(qū)分組件: dart中組件統(tǒng)一用Widget表示,所以這里命名為WidgetIds
/// 使用場景有多語言,統(tǒng)計等等需要組件定位的場景。
/// 多語言:使用的時候,需要加上.tr后綴,比如: WidgetIds.commonBaseTitle.tr
/// 多語言:定義文件中,作為字典的key來用,比如: WidgetIds.commonBaseTitle: 'Title',
/// 公共部分-基礎(chǔ)
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';
}
- 相關(guān)的文件可以放在一個文件夾中

簡略方案
將語言定義獨立成單獨的key ,value文件,這個保持不變。
組件id定義,也就是語言文件的key定義,與實際的字符顯示相差較遠。另外定義想名字也是鍵頭疼的事。
使用起來也比較麻煩,像WidgetIds.commonBaseTitle.tr之類的,看上去也不直觀。在這種時候,更容易忘記.tr后綴,導致顯示'common.base.title'之類的內(nèi)容。
如果key對應(yīng)的value沒有定義,那么會直接用key的內(nèi)容來代替,比如Text('hello'.tr); 如果定義了語言文件,會顯示定義過的 'Hello World'或者'Hallo Welt'。但是如果沒有定義對應(yīng)的key,value,那么就會直接顯示'hello'
我們的應(yīng)用,默認語言是英語,所以為圖方便,我們就直接拿英語做key了,這樣還可以少兩個文件。widget_ids.dart這key的定義文件就不需要了。
當然,這樣做對于那種給@參數(shù)的用法就不適用了。凡事有利有弊。當然,要用也是可以的,把兩種思路結(jié)合起來:大部分用英文作為key;帶參數(shù)的,就自定義key。
其他的語言文件,直接以英語當做key
const Map<String, String> zh_language = {
/// 公共部分-基礎(chǔ)
'Title': '標題',
'Content': '內(nèi)容',
'Description': '描述',
'OK': '確定',
'Cancel': '取消',
}
- 適用的地方直接在英語后面加個.tr后綴就可以了。
Text(
'Cancel'.tr,
style: TextStyle(
color: const Color(0xFF333333),
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
);
Locale簡介
- 構(gòu)造函數(shù)需要兩個字符串,語言代碼和地區(qū)代碼。其中語言代碼是必選的,地區(qū)代碼是可選的
const Locale(
this._languageCode, [
this._countryCode,
])
- iOS的手機的菜單路徑: 設(shè)置 =》通用 =》語言與地區(qū)

- 可以理解為語言代碼是主鍵,地區(qū)代碼是副鍵。比如同樣是中文,也區(qū)分中文大陸,中文香港,中文新加坡,中文臺灣等。
語言地區(qū)代碼

- Translation。s中的key,也是以語言代碼為主,地區(qū)代碼為輔。中劃線用下劃線代替。
另外,也可以簡單處理,只用語言代碼,不要地區(qū)代碼。就算地區(qū)代碼對不上,只要語言代碼對了,也能選中。
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值只給語言代碼,不管區(qū)域代碼。
簡單實踐
只管語言代碼,不管區(qū)域代碼,簡單化處理。當前的需求,只要求有中文版和英文版,還沒有那么細。
初始化取手機的系統(tǒng)設(shè)置;
默認語言設(shè)定為中文
main.dart中的設(shè)置

- 字典中只管語言代碼,不管區(qū)域代碼
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',
};
- 切換時只給語言代碼,不管區(qū)域代碼
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,
),
),
),
);
},
);
}
}
初始界面:(手機設(shè)置為中文)

點一下文字,就切換為英文

- 關(guān)于key的定義
(1)如果想規(guī)范一點,那么就用全局變量,按照組件id的的模式統(tǒng)一規(guī)范;
(2)如果想方便一點,就用英語作為key。那么en.dart絕大部分可以key和value一樣;
zh.dart就算沒定義,也會顯示有意義的英文,既方便又使用。
我們一開始是用方法(1)的;很規(guī)范,但是真的煩。 后來就改成了方法(2);工作量減少很多。
系統(tǒng)日期組件多語言
系統(tǒng)的DateTime組件有一個locale參數(shù),直接加上會崩潰。
如果想要這種系統(tǒng)組件支持多語言,需要額外引入一個庫flutter_localizations
參考文章:
- 日期選擇的插件可以考慮試試下面這個(iOS風格的)
flutter_datetime_picker
實踐結(jié)果
以上內(nèi)容是一開始時的做法,后來做了修改。
- 命名采用統(tǒng)一命名的方式: “模塊.頁面.其他限定”,全部用小寫字符,單詞間用下劃線_分割,也就是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',
訂單模塊,取消訂單,彈窗的標題和信息
本地只保留一份語音文件,作為默認值;
其他多語言文件放后臺,到時候通過接口下載。
做一個編輯后臺,讓其他部門操作,生成新的語言文件。