借助于 App Store 與 Google Play,我們能夠把應用發布到全世界的任何一個應用商店里。應用的(潛在)使用者可能來自于不同國家、說著不同的語言。如果我們想為全世界的使用者提供統一而標準的體驗,那么首先就需要讓 App 能夠支持多種語言。而這一過程,一般被稱為“國際化”。
提起國際化,你可能會認為這等同于翻譯 App 內所有用戶可見的文本。其實,這個觀點不夠精確。**更為準確地描述國際化的工作職責,應該是“涉及語言及地區差異的適配改造過程”。**
比如,如果我們要顯示金額,同樣的面值,在中國會顯示為¥100,而在美國則會顯示為 $100;又比如,App 的引導圖,在中國我們可能會選用長城作為背景,而在美國我們則可能會選擇金門大橋作為背景。
因此,對一款 App 做國際化的具體過程,除了翻譯文案之外,還需要將貨幣單位和背景圖等資源也設計成可根據不同地區自適應的變量。這也就意味著,我們在設計 App 架構時,需要提前將語言與地區的差異部分獨立出來。
其實,這也是在 Flutter 中進行國際化的整體思路,即語言差異配置抽取 + 國際化代碼生成。而在語言差異配置抽取的過程中,文案、貨幣單位,以及背景圖資源的處理,其實并沒有本質區別。所以在今天的分享中,我會以多語言文案為主,為你講述在 Flutter 中如何實現語言與地區差異的獨立化,相信在學習完這部分的知識之后,對于其他類型的語言差異你也能夠輕松搞定國際化了。
## Flutter i18n
在 Flutter 中,國際化的語言和地區的差異性配置,是應用程序代碼的一部分。如果要在 Flutter 中實現文本的國際化,我們需要執行以下幾步:
* 首先,實現一個 LocalizationsDelegate(即翻譯代理),并將所有需要翻譯的文案全部聲明為它的屬性;
* 然后,依次為需要支持的語言地區進行手動翻譯適配;
* 最后,在應用程序 MaterialApp 初始化時,將這個代理類設置為應用程序的翻譯回調。
如果我們中途想要新增或者刪除某個語系或者文案,都需要修改程序代碼。
看到這里你會發現,如果我們想要使用官方提供的國際化方案來設計 App 架構,不僅工作量大、繁瑣,而且極易出錯。所以,要開始 Flutter 應用的國際化道路,我們不如把官方的解決方案扔到一邊,直接**從 Android Studio 中的 Flutter i18n 插件開始學習**。這個插件在其內部提供了不同語言地區的配置封裝,能夠幫助我們自動地從翻譯稿生成 Dart 代碼。
為了安裝 Flutter i18n 插件,我們需要打開 Android Studio 的 Preference 選項,在左邊的 tab 中,切換到 Plugins 選項,搜索這個插件,點擊 install 即可。安裝完成之后再重啟 Android Studio,這個插件就可以使用了。
:-: 
圖 1 Flutter i18n 插件安裝
Flutter i18n 依賴 flutter\_localizations 插件包,所以我們還需要在 pubspec.yaml 文件里,聲明對它的依賴,否則程序會報錯:
~~~
dependencies:
flutter_localizations:
sdk: flutter
~~~
這時,我們會發現在 res 文件夾下,多了一個 values/strings\_en.arb 的文件。
arb 文件是 JSON 格式的配置,用來存放文案標識符和文案翻譯的鍵值對。所以,我們只要修改了 res/values 下的 arb 文件,i18n 插件就會自動幫我們生成對應的代碼。
strings\_en 文件,則是系統默認的英文資源配置。為了支持中文,我們還需要在 values 目錄下再增加一個 strings\_zh.arb 文件:
:-: 
圖 2 arb 文件格式
試著修改一下 strings\_zh.arb 文件,可以看到,Flutter i18n 插件為我們自動生成了 generated/i18n.dart。這個類中不僅以資源標識符屬性的方式提供了靜態文案的翻譯映射,對于通過參數來實現動態文案的 message\_tip 標識符,也自動生成了一個同名內聯函數:
:-: 
圖 3 Flutter i18n 插件自動生成代碼
我們把 strings\_en.arb 繼續補全,提供英文版的文案。需要注意的是,i18n.dart 是由插件自動生成的,每次 arb 文件有新的變更都會自動更新,所以切忌手動編輯這個文件。
接下來,我們**以 Flutter 官方的工程模板,即計數器 demo 來演示如何在 Flutter 中實現國際化**。
在下面的代碼中,我們在應用程序的入口,即 MaterialApp 初始化時,為其設置了支持國際化的兩個重要參數,即 localizationsDelegates 與 supportedLocales。前者為應用的翻譯回調,而后者則為應用所支持的語言地區屬性。
S.delegate 是 Flutter i18n 插件自動生成的類,包含了所支持的語言地區屬性,以及對應的文案翻譯映射。理論上,通過這個類就可以完全實現應用的國際化,但為什么我們在配置應用程序的翻譯回調時,除了它之外,還加入了 GlobalMaterialLocalizations.delegate 與 GlobalWidgetsLocalizations.delegate 這兩個回調呢?
這是因為 Flutter 提供的 Widget,其本身已經支持了國際化,所以我們沒必要再翻譯一遍,直接用官方的就可以了,而這兩個類則就是官方所提供的翻譯回調。事實上,我們剛才在 pubspec.yaml 文件中聲明的 flutter\_localizations 插件包,就是 Flutter 提供的翻譯套裝,而這兩個類就是套裝中的著名成員。
在完成了應用程序的國際化配置之后,我們就可以在程序中通過 S.of(context),直接獲取 arb 文件中翻譯的文案了。
不過需要注意的是,**提取翻譯文案的代碼需要在能獲取到翻譯上下文的前提下才能生效,也就是說只能針對 MaterialApp 的子 Widget 生效。**因此,在這種配置方式下,我們是無法對 MaterialApp 的 title 屬性進行國際化配置的。不過,好在 MaterialApp 提供了一個回調方法 onGenerateTitle,來提供翻譯上下文,因此我們可以通過它,實現 title 文案的國際化:
~~~
// 應用程序入口
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: const [
S.delegate,// 應用程序的翻譯回調
GlobalMaterialLocalizations.delegate,//Material 組件的翻譯回調
GlobalWidgetsLocalizations.delegate,// 普通 Widget 的翻譯回調
],
supportedLocales: S.delegate.supportedLocales,// 支持語系
//title 的國際化回調
onGenerateTitle: (context){
return S.of(context).app_title;
},
home: MyHomePage(),
);
}
}
~~~
應用的主界面文案的國際化,則相對簡單得多了,直接通過 S.of(context) 方法就可以拿到 arb 聲明的翻譯文案了:
~~~
Widget build(BuildContext context) {
return Scaffold(
// 獲取 appBar title 的翻譯文案
appBar: AppBar(
title: Text(S.of(context).main_title),
),
body: Center(
// 傳入 _counter 參數,獲取計數器動態文案
child: Text(
S.of(context).message_tip(_counter.toString())
)
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,// 點擊回調
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
~~~
在 Android 手機上,分別切換英文和中文系統,可以看到,計數器應用已經正確地處理了多語言的情況。
:-: 
圖 4 計數器示例(Android 英文系統)
:-: 
圖 5 計數器示例(Android 中文系統)
由于 iOS 應用程序有一套自建的語言環境管理機制,默認是英文。為了讓 iOS 應用正確地支持國際化,我們還需要在原生的 iOS 工程中進行額外的配置。我們打開 iOS 原生工程,切換到工程面板。在 Localization 這一項配置中,我們看到 iOS 工程已經默認支持了英文,所以還需要點擊“+”按鈕,新增中文:
:-: 
圖 6 iOS 工程中文配置
完成 iOS 的工程配置后,我們回到 Flutter 工程,選擇 iOS 手機運行程序。可以看到,計數器的 iOS 版本也可以正確地支持國際化了。
:-: 
圖 7 計數器示例(iOS 英文系統)
:-: 
圖 8 計數器示例(iOS 中文系統)
## 原生工程配置
上面介紹的國際化方案,其實都是在 Flutter 應用內實現的。而在 Flutter 框架運行之前,我們是無法訪問這些國際化文案的。
Flutter 需要原生環境才能運行,但有些文案,比如應用的名稱,我們需要在 Flutter 框架運行之前就為它提供多個語言版本(比如英文版本為 computer,中文版本為計數器),這時就需要在對應的原生工程中完成相應的國際化配置了。
**我們先去 Android 工程下進行應用名稱的配置。**
首先,在 Android 工程中,應用名稱是在 AndroidManifest.xml 文件中 application 的 android:label 屬性聲明的,所以我們需要將其修改為字符串資源中的一個引用,讓其能夠根據語言地區自動選擇合適的文案:
~~~
<manifest ... >
...
<!-- 設置應用名稱 -->
<application
...
android:label="@string/title"
...
>
</application>
</manifest>
~~~
然后,我們還需要在 android/app/src/main/res 文件夾中,為要支持的語言創建字符串 strings.xml 文件。這里由于默認文件是英文的,所以我們只需要為中文創建一個文件即可。字符串資源的文件目錄結構,如下圖所示:
:-: 
圖 9 strings.xml 文件目錄結構
values 與 values-zh 文件夾下的 strings.xml 內容如下所示:
~~~
<!-- 英文 (默認) 字符串資源 -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">Computer</string>
</resources>
<!-- 中文字符串資源 -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title"> 計數器 </string>
</resources>
~~~
完成 Android 應用標題的工程配置后,我們回到 Flutter 工程,選擇 Android 手機運行程序,可以看到,計數器的 Android 應用標題也可以正確地支持國際化了。
接下來,我們再看**iOS 工程下如何實現應用名稱的配置**。
與 Android 工程類似,iOS 工程中的應用名稱是在 Info.list 文件的 Bundle name 屬性聲明的,所以我們也需要將其修改為字符串資源中的一個引用,使其能夠根據語言地區自動選擇文案:
:-: 
圖 10 iOS 工程應用名稱配置
由于應用名稱默認是不可配置的,所以工程并沒有提供英文或者中文的可配置項,這些都需要通過新建與字符串引用對應的資源文件去搞定的。
我們右鍵單擊 Runner 文件夾,然后選擇 New File 來添加名為 InfoPlist.strings 的字符串資源文件,并在工程面板的最右側文件檢查器中的 Localization 選項中,添加英文和中文兩種語言。InfoPlist.strings 的英文版和中文版內容如下所示:
~~~
// 英文版
"CFBundleName" = "Computer";
// 中文版
"CFBundleName" = " 計數器 ";
~~~
至此,我們也完成了 iOS 應用標題的工程配置。我們回到 Flutter 工程,選擇 iOS 手機運行程序,發現計數器的 iOS 應用標題也支持國際化了。
## 總結
好了,今天的分享就到這里。我們來總結下核心知識點吧。
在今天的分享中,我與你介紹了 Flutter 應用國際化的解決方案,即在代碼中實現一個 LocalizationsDelegate,在這個類中將所有需要翻譯的文案全部聲明為它的屬性,然后依次進行手動翻譯適配,最后將這個代理類設置為應用程序的翻譯回調。
而為了簡化手動翻譯到代碼轉換的過程,我們通常會使用多個 arb 文件存儲文案在不同語言地區的映射關系,并使用 Flutter i18n 插件來實現代碼的自動轉換。
國際化的核心就是語言差異配置抽取。在原生 Android 和 iOS 系統中進行國際化適配,我們只需為需要國際化的資源(比如,字符串文本、圖片、布局等)提供不同的文件夾目錄,就可以在應用層代碼訪問國際化資源時,自動根據語言地區進行適配。
但,Flutter 的國際化能力就相對原始很多,不同語言和地區的國際化資源既沒有存放在單獨的 xml 或者 JSON 上,也沒有存放在不同的語言和地區文件夾中。幸好有 Flutter i18n 插件的幫助,否則為一個應用提供國際化的支持將會是件極其繁瑣的事情。
我把今天分享所涉及到的知識點打包到了[GitHub](https://github.com/cyndibaby905/32_i18n_demo)中,你可以下載下來,反復運行幾次,加深理解與記憶。
## 思考題
最后,我給你留下一道思考題吧。
在 Flutter 中,如何實現圖片類資源的國際化呢?
- 前言
- 開篇詞
- 預習篇
- 01丨預習篇 · 從0開始搭建Flutter工程環境
- 02丨預習篇 · Dart語言概覽
- Flutter開發起步
- 03丨深入理解跨平臺方案的歷史發展邏輯
- 04丨Flutter區別于其他方案的關鍵技術是什么?
- 05丨從標準模板入手,體會Flutter代碼是如何運行在原生系統上的
- Dart語言基礎
- 06丨基礎語法與類型變量:Dart是如何表示信息的?
- 07丨函數、類與運算符:Dart是如何處理信息的?
- 08丨綜合案例:掌握Dart核心特性
- Flutter基礎
- 09丨Widget,構建Flutter界面的基石
- 10丨Widget中的State到底是什么?
- 11丨提到生命周期,我們是在說什么?
- 12丨經典控件(一):文本、圖片和按鈕在Flutter中怎么用?
- 13丨ListView在Flutter中是什么?
- 14 丨 經典布局:如何定義子控件在父容器中排版位置?
- 15 丨 組合與自繪,我該選用何種方式自定義Widget?
- 16 丨 從夜間模式說起,如何定制不同風格的App主題?
- 17丨依賴管理(一):圖片、配置和字體在Flutter中怎么用?
- 18丨依賴管理(二):第三方組件庫在Flutter中要如何管理?
- 19丨用戶交互事件該如何響應?
- 20丨關于跨組件傳遞數據,你只需要記住這三招
- 21丨路由與導航,Flutter是這樣實現頁面切換的
- Flutter進階
- 22丨如何構造炫酷的動畫效果?
- 23丨單線程模型怎么保證UI運行流暢?
- 24丨HTTP網絡編程與JSON解析
- 25丨本地存儲與數據庫的使用和優化
- 26丨如何在Dart層兼容Android-iOS平臺特定實現?(一)
- 27丨如何在Dart層兼容Android-iOS平臺特定實現?(二)
- 28丨如何在原生應用中混編Flutter工程?
- 29丨混合開發,該用何種方案管理導航棧?
- 30丨為什么需要做狀態管理,怎么做?
- 31丨如何實現原生推送能力?
- 32丨適配國際化,除了多語言我們還需要注意什么
- 33丨如何適配不同分辨率的手機屏幕?
- 34丨如何理解Flutter的編譯模式?
- 35丨HotReload是怎么做到的?
- 36丨如何通過工具鏈優化開發調試效率?
- 37丨如何檢測并優化FlutterApp的整體性能表現?
- 38丨如何通過自動化測試提高交付質量?
- Flutter綜合應用
- 39丨線上出現問題,該如何做好異常捕獲與信息采集?
- 40丨衡量FlutterApp線上質量,我們需要關注這三個指標
- 41丨組件化和平臺化,該如何組織合理穩定的Flutter工程結構?
- 42丨如何構建高效的FlutterApp打包發布環境?
- 43丨如何構建自己的Flutter混合開發框架(一)?
- 44丨如何構建自己的Flutter混合開發框架(二)?
- 結束語
- 結束語丨勿畏難,勿輕略