Flutter(十七) 實現國際化

一. 國際化的認識

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

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

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

  • 比如我們下面開發的這個App
  • 某些文本在英文環境下應該顯示爲英文;
  • 某些Widget在中文環境下,應該顯示中文(比如彈出的時間選擇器);

國際化

二. 國際化的適配

2.1. Widget的國際化

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

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

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

2.1.1. pubspec添加依賴

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

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

2.1.2. 設置MaterialApp

  • 在localizationsDelegates中指定哪些Widget需要進行國際化

    • 用於生產本地化值集合的工廠
    • 我們這裏指定了Material、Widgets、Cupertino都使用國際化
  • supportedLocales指定要支持哪些國際化

    • 我們這裏指定中文和英文(也可以指定國家編碼)
MaterialApp(
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate, // 指定本地化的字符串和一些其他的值
    GlobalCupertinoLocalizations.delegate, // 對應的Cupertino風格
    GlobalWidgetsLocalizations.delegate // 指定默認的文本排列方向, 由左到右或由右到左
  ],
  supportedLocales: [
    Locale("en"),
    Locale("zh")
  ],
)

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

// Full Chinese support for CN, TW, and HK
supportedLocales: [
  const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
],

2.1.3. 查看Widget結果

設置完成後,我們在Android上將語言切換爲中文,查看結果:
在這裏插入圖片描述

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

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

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

  • 選擇 Information Property List 項;
  • 從 Editor 菜單中選擇 Add Item,然後從彈出菜單中選擇 Localizations
  • 爲array添加一項選擇 Add Item,選擇Chinese;

Xcode中配置info.plist文件
配置完成後,卸載之前的app,重新安裝:
在這裏插入圖片描述

2.2. 其它文本國際化

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

2.2.1. 創建本地化類

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

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

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)
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:i18n_demo/i18n/localizations.dart';

class HYLocalizationsDelegate extends LocalizationsDelegate<HYLocalizations> {
  @override
  bool isSupported(Locale locale) {
    return ["en", "zh"].contains(locale.languageCode);
  }

  @override
  bool shouldReload(LocalizationsDelegate<HYLocalizations> old) {
    return false;
  }

  @override
  Future<HYLocalizations> load(Locale locale) {
    return SynchronousFuture(HYLocalizations(locale));
  }

  static HYLocalizationsDelegate delegate = HYLocalizationsDelegate();
}

2.2.3. 使用本地化類

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

  • 我們可以通過Localizations.of(context, HYLocalizations)獲取到HYLocalizations對象
 Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(Localizations.of(context, HYLocalizations).title),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            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) {
                });
              },
            )
          ],
        ),
      ),
    );
  }

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

  • 給HYLocalizations定義一個of的靜態方法
class HYLocalizations {
  static HYLocalizations of(BuildContext context) {
    return Localizations.of(context, HYLocalizations);
  }
}

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

appBar: AppBar(
  title: Text(HYLocalizations.of(context).title),
)

2.2.4. 異步加載數據

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

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

static Map<String, Map<String, String>> _localizedValues = {};

  Future<bool> loadJson() async {
    // 1.加載json文件
    String jsonString = await rootBundle.loadString("assets/json/i18n.json");
    
    // 2.轉成map類型
    final Map<String, dynamic> map = json.decode(jsonString);
    
    // 3.注意:這裏是將Map<String, dynamic>轉成Map<String, Map<String, String>>類型
    _localizedValues = map.map((key, value) {
      return MapEntry(key, value.cast<String, String>());
    });
    return true;
  }

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

@override
  Future<HYLocalizations> load(Locale locale) async {
    final localization = HYLocalizations(locale);
    await localization.loadJson();
    return localization;
  }

三. 國際化的工具

3.1. 認識arb文件

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

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

String get title {
  return _localizedValues[locale.languageCode]["title"];
}

String get greet {
  return _localizedValues[locale.languageCode]["greet"];
}

String get pickTime {
  return _localizedValues[locale.languageCode]["picktime"];
}

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

  • arb文件全稱Application Resource Bundle,表示應用資源包,目前已經得到Google的支持;
  • 其本質就是一個json文件,但是可以根據該文件轉成對應的語言環境;
  • arb的說明文檔

在這裏插入圖片描述

3.2. intl package

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

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

3.3. 使用IDE插件

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

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

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

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

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

3.3.1. 安裝插件

在Android Studio的Plugins中安裝插件:
插件安裝

3.3.2. 初始化intl

選擇工具欄Tools - Flutter Intl - Initialize for the Project
在這裏插入圖片描述

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

  • generated是自動生成的dart代碼
  • I10n是對應的arb文件目錄
    目錄結構

3.3.3. 使用intl

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

  1. 添加對應的delegate
  2. supportedLocales使用S.delegate.supportedLocales
localizationsDelegates: [
  GlobalMaterialLocalizations.delegate,
  GlobalWidgetsLocalizations.delegate,
  GlobalCupertinoLocalizations.delegate,
  HYLocalizationsDelegate.delegate,
  S.delegate
],

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

  • 編寫後ctrl(command) + s保存即可
{
  "title": "home",
  "greet": "hello~",
  "picktime": "Pick a time"
}

在代碼中使用即可

  • 按照如下格式:S.of(context).title
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(S.of(context).title),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            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) {
                });
              },
            )
          ],
        ),
      ),
    );
  }

3.3.4. 添加中文

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

  • 在彈出框中輸入zh即可
    在這裏插入圖片描述

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

在這裏插入圖片描述

編寫intl_zh.arb文件:

{
  "title": "首頁",
  "greet": "您好~",
  "picktime": "選擇一個時間"
}

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

在這裏插入圖片描述

3.4. arb其它語法

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

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

修改對應的arb文件:

  • {name}:表示傳遞的參數
{
  "title": "home",
  "greet": "hello~",
  "picktime": "Pick a time",
  "sayHello": "hello {name}"
}

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

Text(S.of(context).sayHello("李銀河")),

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章