從0開始設計Flutter獨立APP | 第二篇: 完整的國際化語言支援

魚子長發表於2020-06-28

鑑於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。具體規則和使用,可以參考這裡: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

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上的開發經驗。閃點清單,一款懸浮清單軟體:

閃點清單,一款懸浮清單軟體

相關文章