(十五) Flutter入門學習 之 實現國際化

一. 國際化的認識

開發一個App,如果我們的App需要面向不同的語種(比如中文、英文、繁體等),那麼我們需要對其進行國際化開發。

國際化的英文稱呼:internationalization(簡稱為i18n,取前後兩個字母,18表示中間省略字母的個數)

App國際化開發主要包括:文本國際化(包括文本的順序),Widget顯示的國際化:

  • 比如我們下面開發的這個App
  • 某些文本在英文環境下應該顯示為英文;
  • 某些Widget在中文環境下,應該顯示中文(比如彈出的時間選擇器);
(十五) Flutter入門學習 之 實現國際化

國際化


二. 國際化的適配

2.1. Widget的國際化

Flutter給我們提供的Widget默認情況下就是支持國際化,但是在沒有進行特別的設置之前,它們無論在什麼環境都是以英文的方式顯示的。

如果想要添加其他語言,你的應用必須指定額外的 MaterialApp 屬性並且添加一個單獨的 package,叫做 flutter_localizations。

  • 截至到 2020 年 2 月份,這個 package 已經支持大約 77 種語言。

2.1.1. pubspec添加依賴

想要使用 flutter_localizations 的話,我們需要在 pubspec.yaml 文件中添加它作為依賴:

<code>

dependencies

:   

flutter

:     

sdk

: flutter   

flutter_localizations

:     

sdk

: flutter/<code>

2.1.2. 設置MaterialApp

  • 在localizationsDelegates中指定哪些Widget需要進行國際化
    • 用於生產本地化值集合的工廠
    • 我們這裡指定了Material、Widgets、Cupertino都使用國際化
  • supportedLocales指定要支持哪些國際化
    • 我們這裡指定中文和英文(也可以指定國家編碼)
<code>MaterialApp(
  localizationsDelegates: [
    GlobalMaterialLocalizations.

delegate

,      GlobalCupertinoLocalizations.

delegate

,      GlobalWidgetsLocalizations.

delegate

    ],   supportedLocales: [     Locale(

"en"

),     Locale(

"zh"

)   ], )/<code>

注意:如果要指定語言代碼、文字代碼和國家代碼,可以進行如下指定方式:

<code> 

supportedLocales

: [   const Locale.fromSubtags(

languageCode

'zh'

),    const Locale.fromSubtags(

languageCode

'zh'

scriptCode

'Hans'

),    const Locale.fromSubtags(

languageCode

'zh'

scriptCode

'Hant'

),    const Locale.fromSubtags(

languageCode

'zh'

scriptCode

'Hans'

countryCode

'CN'

),    const Locale.fromSubtags(

languageCode

'zh'

scriptCode

'Hant'

countryCode

'TW'

),    const Locale.fromSubtags(

languageCode

'zh'

scriptCode

'Hant'

countryCode

'HK'

),  ],/<code>

2.1.3. 查看Widget結果

設置完成後,我們在Android上將語言切換為中文,查看結果:

(十五) Flutter入門學習 之 實現國際化

Android運行效果


但是對於iOS,將語言切換為中文,依然顯示是英文的Widget

  • 這是因為iOS定義了一些應用的元數據,其中包括支持的語言環境;
  • 我們必須將其對應的元數據中支持的語言添加進去;
  • 元數據的設置在iOS項目中對應的info.plist文件中;

修改iOS的info.plist文件配置:

  • 選擇 Information Property List 項;
  • 從 Editor 菜單中選擇 Add Item,然後從彈出菜單中選擇 Localizations
  • 為array添加一項選擇 Add Item,選擇Chinese;
(十五) Flutter入門學習 之 實現國際化

Xcode中配置info.plist文件


配置完成後,卸載之前的app,重新安裝:

(十五) Flutter入門學習 之 實現國際化

iOS運行效果


2.2. 其它文本國際化

App中除了有默認的Widget,我們也希望對自己的文本進行國際化,如何做到呢?

2.2.1. 創建本地化類

該類用於定義我們需要進行本地化的字符串等信息:

  • 1.我們需要一個構造器,並且傳入一個Locale對象(後續會使用到)
  • 2.定義一個Map,其中存放我們不同語言對應的顯示文本
  • 3.定義它們對應的getter方法,根據語言環境返回不同的結果
<code>

import

 

'package:flutter/material.dart'

;

class

 

HYLocalizations

 

{   final Locale locale;   HYLocalizations(

this

.locale);   

static

 

Map

<

String

Map

<

String

String

>> _localizedValues = {     

"en"

: {       

"title"

"home"

,       

"greet"

"hello~"

,       

"picktime"

"Pick a Time"

    },     

"zh"

: {       

"title"

"首頁"

,       

"greet"

"你好~"

,       

"picktime"

"選擇一個時間"

    }   };   

String

 

get

 title {     

return

 _localizedValues[locale.languageCode][

"title"

];   }   

String

 

get

 greet {     

return

 _localizedValues[locale.languageCode][

"greet"

];   }   

String

 

get

 pickTime {     

return

 _localizedValues[locale.languageCode][

"picktime"

];   } }/<code>

2.2.2. 自定義Delegate

上面的類定義好後,我們在什麼位置或者說如何對它進行初始化呢?

  • 答案是我們可以像Flutter Widget中的國際化方式一樣對它們進行初始化;
  • 也就是我們也定義一個對象的Delegate類,並且將其傳入localizationsDelegates中;
  • Delegate的作用就是當Locale發生改變時,調用對應的load方法,重新加載新的Locale資源;

HYLocalizationsDelegate需要繼承自LocalizationsDelegate,並且有三個方法必須重寫:

  • isSupported:用於當前環境的Locale,是否在我們支持的語言範圍
  • shouldReload:當Localizations Widget重新build時,是否調用load方法重新加載Locale資源
    • 一般情況下,Locale資源只應該在Locale切換時加載一次,不需要每次Localizations重新build時都加載一遍;
    • 所以一般情況下返回false即可;
  • load方法:當Locale發生改變時(語言環境),加載對應的HYLocalizations資源
    • 這個方法返回的是一個Future,因為有可能是異步加載的;
    • 但是我們這裡是直接定義的一個Map,因此可以直接返回一個同步的Future(SynchronousFuture)
<code>

import

 

'package:flutter/cupertino.dart'

;

import

 

'package:flutter/foundation.dart'

;

import

 

'package:i18n_demo/i18n/localizations.dart'

;

class

 

HYLocalizationsDelegate

 

extends

 

LocalizationsDelegate

<

HYLocalizations

{      

bool 

isSupported

(Locale locale)

 

{     

return

 [

"en"

"zh"

].contains(locale.languageCode);   }      

bool 

shouldReload

(LocalizationsDelegate old)

 

{     

return

 

false

;   }      

Future 

load

(Locale locale)

 

{     

return

 SynchronousFuture(HYLocalizations(locale));   }   

static

 HYLocalizationsDelegate delegate = HYLocalizationsDelegate(); }/<code>

2.2.3. 使用本地化類

接著我們可以在代碼中使用HYLocalization類。

  • 我們可以通過Localizations.of(context, HYLocalizations)獲取到HYLocalizations對象
<code>  

Widget

 

build

(BuildContext context) {     

return

 

Scaffold

(       

appBar

: AppBar(         

title

: Text(Localizations.of(context, HYLocalizations).title),       ),       

body

: Center(         

child

: Column(           

children

: [             Text(Localizations.of(context, HYLocalizations).greet),             RaisedButton(               

child

: Text(Localizations.of(context, HYLocalizations).pickTime),               

onPressed

: () {                 showDatePicker(                     

context

: context,                     

initialDate

: DateTime.now(),                     

firstDate

: DateTime(

2019

),                     

lastDate

: DateTime(

2022

)                 ).then((pickTime) {                 });               },             )           ],         ),       ),     );   }/<code>

當然,我們可以對Localizations.of(context, HYLocalizations)進行一個優化

  • 給HYLocalizations定義一個of的靜態方法
<code>

class

 

HYLocalizations

 {

  

static

 HYLocalizations 

of

(BuildContext context)

 

{     

return

 Localizations.of(context, HYLocalizations);   } }/<code>

接下來我們就可以通過下面的方式來使用了(其它地方也是一樣):

<code>

appBar

AppBar

(   

title

Text

(

HYLocalizations

.of

(

context

)

.title

), )/<code>

2.2.4. 異步加載數據

假如我們的數據是異步加載的,比如來自Json文件或者服務器,應該如何處理呢?

這裡我們可以修改HYLocalizations的數據加載:

<code>  

static

 

Map

<

String

Map

<

String

String

>> _localizedValues = {};   Future loadJson() 

async

 {          

String

 jsonString = 

await

 rootBundle.loadString(

"assets/json/i18n.json"

);               final 

Map

<

String

, dynamic> map = json.decode(jsonString);               _localizedValues = map.map((key, value) {       

return

 MapEntry(key, value.cast<

String

String

>());     });     

return

 

true

;   }/<code>

在HYLocalizationsDelegate中使用異步進行加載:

<code>  @

override

  Future 

load

(

Locale locale

async

 {     final localization = HYLocalizations(locale);     

await

 localization.loadJson();     

return

 localization;   }/<code>

三. 國際化的工具

3.1. 認識arb文件

目前我們已經可以通過加載對應的json文件來進行本地化了。

但是還有另外一個問題,我們在進行國際化的過程中,下面的代碼依然需要根據json文件手動編寫:

<code>  String get title {
    return _localizedValues[

locale.languageCode

][

"title"

];   }   String get greet {     return _localizedValues[

locale.languageCode

][

"greet"

];   }   String get pickTime {     return _localizedValues[

locale.languageCode

][

"picktime"

];   }/<code>

有沒有一種更好的方式,讓我們可以快速在本地化文件-dart代碼文件直接來轉換呢?答案就是arb文件

  • arb文件全稱Application Resource Bundle,表示應用資源包,目前已經得到Google的支持;
  • 其本質就是一個json文件,但是可以根據該文件轉成對應的語言環境;
  • arb的說明文檔:https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification
(十五) Flutter入門學習 之 實現國際化

dart和arb轉換


3.2. intl package

官方文檔推薦可以使用intl package來進行arb和dart文件之間的轉換(通過終端指令)

  • https://flutter.dev/docs/development/accessibility-and-localization/internationalization#appendix-using-the-dart-intl-tools

需要在在pubspec.yaml中添加其相關的依賴,具體步驟這裡不再詳細給出,可以參考官方文檔

3.3. 使用IDE插件

在之前有一個比較好用的Android Studio的插件:Flutter i18n

  • 但是這個插件已經很久不再維護了,所以不再推薦給大家使用

目前我們可以使用另外一個插件:Flutter Intl

  • 該插件更新維護頻率很高,並且廣受好評;
  • 另外,在Android Studio和VSCode中都是支持的

我們這裡以Android Studio為例,講解其使用過程:

3.3.1. 安裝插件

在Android Studio的Plugins中安裝插件:

(十五) Flutter入門學習 之 實現國際化

插件安裝


3.3.2. 初始化intl

選擇工具欄Tools - Flutter Intl - Initialize for the Project

(十五) Flutter入門學習 之 實現國際化

初始化intl


完成上面的操作之後會自動生成如下文件目錄:

  • generated是自動生成的dart代碼
  • I10n是對應的arb文件目錄
(十五) Flutter入門學習 之 實現國際化

目錄結構


3.3.3. 使用intl

在localizationsDelegates中配置生成的class,名字是S

  • 1.添加對應的delegate
  • 2.supportedLocales使用S.delegate.supportedLocales
<code>localizationsDelegates: [
  GlobalMaterialLocalizations.

delegate

,   GlobalWidgetsLocalizations.

delegate

,   GlobalCupertinoLocalizations.

delegate

,   HYLocalizationsDelegate.

delegate

,   S.

delegate

], supportedLocales: S.

delegate

.supportedLocales,/<code>

因為我們目前還沒有對應的本地化字符串,所以需要在intl_en.arb文件中編寫:

  • 編寫後ctrl(command) + s保存即可
<code>{
  

"title"

"home"

,   

"greet"

"hello~"

,   

"picktime"

"Pick a time"

}/<code>

在代碼中使用即可

  • 按照如下格式:S.of(context).title
<code>  

@override

  Widget build(BuildContext context) {     

return

 

Scaffold

(       

appBar

: AppBar(         

title

: Text(S.of(context).title),       ),       

body

: Center(         

child

: Column(           

children

: [             Text(S.of(context).greet),             RaisedButton(               

child

: Text(S.of(context).picktime),               

onPressed

: () {                 showDatePicker(                     

context

: context,                     

initialDate

: DateTime.now(),                     

firstDate

: DateTime(

2019

),                     

lastDate

: DateTime(

2022

)                 ).then((pickTime) {                 });               },             )           ],         ),       ),     );   }/<code>

3.3.4. 添加中文

如果希望添加中文支持:add local

  • 在彈出框中輸入zh即可
(十五) Flutter入門學習 之 實現國際化

添加中文


我們會發現,會生成對應的intl_zh.arb和messages_zh.dart文件

(十五) Flutter入門學習 之 實現國際化

目錄結構


編寫intl_zh.arb文件:

<code>{
  

"title"

"首頁"

,   

"greet"

"您好~"

,   

"picktime"

"選擇一個時間"

}/<code>

查看界面,會根據當前語言顯示對應的語言文本

(十五) Flutter入門學習 之 實現國際化

顯示結果


3.4. arb其它語法

如果我們希望在使用本地化的過程中傳遞一些參數:

  • 比如hello kobe或hello james
  • 比如你好啊,李銀河 或 你好啊,王小波

修改對應的arb文件:

  • {name}:表示傳遞的參數
<code>{
  

"title"

"home"

,   

"greet"

"hello~"

,   

"picktime"

"Pick a time"

,   

"sayHello"

"hello {name}"

}/<code>

在使用時,傳入對應的參數即可:

<code> 

Text

(

S

.of

(

context

)

.sayHello

("李銀河")),/<code>

arb還有更多的語法,大家可以在之後慢慢學習和發掘~


分享到:


相關文章: