鑑於Flutter高效能渲染和跨平臺的優勢,閃點清單在移動端APP上,使用了完整的Flutter框架來開發。既然是完整APP,架構搭建完全不受歷史Native APP的影響,沒有歷史包袱的沉澱,設計也能更靈活和健壯。
國際化語言的支援,是很多APP都有的一個強需求,APP無論大小,只要還不想放棄國外的客戶,一般就需要支援國際化。
官方支援
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'];
}
}
複製程式碼
官方方案的缺陷
官方的支援有幾個缺陷:
- 依賴於BuildContext物件,在非Widget中呼叫時,需要層層傳遞BuildContext物件,或儲存全域性BuildContext物件。
- 在MaterialApp初始化前無法使用國際化(原因也是依賴於BuildContext物件)。
- 語言包定義推薦使用Map方式,無法利用靜態語言的優勢(語法提示、錯誤檢查等);而為語言包每個屬性自定義類和類欄位,成本較高、使用和更新靈活性差。
i18n介紹
鑑於Flutter官方支援的缺陷,我們調研了很多第三方庫,最終發現了i18n,並在此基礎上、結合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。具體規則和使用,可以參考這裡:cldr.unicode.org/index/cldr-…
使用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
。
語言包使用
前置編譯後,每個語言包會生成N個Class(語言包的每一個分類或組合會生成一個Class檔案),然後會生成一個根Class,我們可以直接使用根Class(當然也可以使用任何一個分類層級的Class)。
比如兩個語言包檔案: AppMessages.i18n.yaml
和AppMessages_en.i18n.yaml
(未加語言字尾的,會認為是預設語言包,因此AppMessages.i18n.yaml是預設語言包),會生成2個根Dart Class: class AppMessages
和class 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上的開發經驗。閃點清單,一款懸浮清單軟體: