如何封裝一個flutter的多語言plugin

NO IMAGE

前言

關於flutter多語言的使用之前已經寫過文章介紹過了,有興趣的可以移步以$t形式使用flutter多語言,關於flutter國際化的具體介紹,大家可以移步國際化Flutter App

本文主要介紹在flutter如何封裝一個插件,以減少開發者接人flutter多語言的重複工作量

接入使用

FlutterLocalization

效果

如何封裝一個flutter的多語言plugin

多語言plugin功能

  • flutter 接入Efox多語言平臺,支持加載本地和Efox平臺切換,語言切換
  • 支持語言自定義,支持中文簡繁體區分
  • 開放配置項:
    • 支持的語種
    • 默認語言
    • 語種映射關係
    • 本地語言路徑
    • Efox平臺語言路徑
    • 是否加載本地多語言
  • 可獲取方法
    • 獲取是否加載本地多語言: AppLocalizations.isLocale
    • 修改是否加載本地多語言: AppLocalizations.changeIsLocale(true|false);
    • 獲取當前語種: AppLocalizations.localeLang
    • 獲取多語言翻譯: AppLocalizations.$t(‘title_page’)

Plugin packages和Dart packages的區別

  • 插件包(Plugin packages)是當你需要暴露Native API給別人的時候使用的形式,內部需要使用Platform Channels幷包含Androiod/iOS原生邏輯,並且內部有example目錄下的flutter項目可直接運行,進行代碼測試
  • Dart包(Dart packages)是當你需要開發一個純Dart組件(比如一個自定義的Weidget)的時候使用的形式,內部沒有Native代碼,代碼測試可通過自己新建的flutter項目通過本地路徑引入進行測試

新建package

多語言插件新建一個Dart包就可以代碼擼起

如何封裝一個flutter的多語言plugin

步驟1:

接入flutter多語言需指定MaterialApp的localizationsDelegates和supportedLocales,而localeResolutionCallback是在應用獲取用戶設置的語言區域時會被回調

localizationsDelegates: AppLocal.localizationsDelegates,
supportedLocales: AppLocal.supportedLocales,
localeResolutionCallback: AppLocal.localeResolutionCallback,

所以需提供一個AppLocal類實現上述三個實例

class AppLocal {
static Iterable<LocalizationsDelegate<dynamic>> _localizationsDelegates = [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizationsDelegate()
];
// 支持的語言
static Iterable<Locale> _supportedLocales = ConfigLanguage.supportedLocales;
// 應用獲取語言區域時會被回調
static Locale _localeResolutionCallback(deviceLocale, supportedLocal) {
print(
'當前設備語種 deviceLocale: $deviceLocale, 支持語種 supportedLocale: $supportedLocal}');
// null [] [en_US] [en_US, zh_CH] Locale('en', 'US') 不同手機對於語言的獲取返回參數不同,所以需要根據返回做對應的處理
var useDeciceLocale;
if (deviceLocale != null &&
deviceLocale.runtimeType.toString().contains('List') &&
deviceLocale.isNotEmpty) {
useDeciceLocale = deviceLocale[0];
} else {
if (deviceLocale.runtimeType.toString() == 'Locale') {
useDeciceLocale = deviceLocale;
} else {
useDeciceLocale = null;
}
}
print(
'手機獲取匹配後的語種:$useDeciceLocale, ${useDeciceLocale.runtimeType.toString()} ${useDeciceLocale.runtimeType.toString() == 'Locale'}');
Locale _locale;
bool hasLanguage = false;
if (useDeciceLocale != null) {
for (num i = 0; i < supportedLocal.length; i++) {
if (useDeciceLocale.scriptCode == 'Hant') {
if (useDeciceLocale.languageCode == supportedLocal[i].languageCode &&
useDeciceLocale.scriptCode == supportedLocal[i].scriptCode) {
hasLanguage = true;
useDeciceLocale = supportedLocal[i];
print('繁體語言匹配上了: $useDeciceLocale');
break;
}
} else {
if (useDeciceLocale.languageCode == supportedLocal[i].languageCode) {
hasLanguage = true;
useDeciceLocale = supportedLocal[i];
print('普通語言碼匹配上了: $useDeciceLocale');
break;
}
}
}
_locale = hasLanguage
? useDeciceLocale
: Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code'],
);
print(
'${hasLanguage ? '手機系統語言本app支持,使用系統指定語言: $_locale' : '手機系統語言本app不支持,使用app規定默認語言: $_locale'}');
} else {
_locale = Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code'],
);
print('手機系統語言本app不支持,使用app規定默認語言: $_locale');
}
return _locale;
}
static get supportedLocales => _supportedLocales;
static get localizationsDelegates => _localizationsDelegates;
static get localeResolutionCallback => _localeResolutionCallback;
}

步驟2:

在localizationsDelegates除了需要指定flutter本身提供的delegate外,我們還需要指定一個本地化代理,如下,作用是在語言加載時判斷是否支持加載和重新加載時我們需要的加載邏輯處理以及語言包加載後保存在AppLocalizations類中,AppLocalizations是我們的數據存儲和controller類

class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
return ConfigLanguage.supportedLocales.contains(locale);
}
@override
Future<AppLocalizations> load(Locale locale) async {
LocalStorage.get('lang').then((lang) async {
print('本地緩存的語言: $lang');
AppLocalizations._localeLang = lang;
if (lang == null) {
print('將要加載的語言locale: $locale');
return await AppLocalizations.init(locale);
} else {
print('將要加載的語言lang: $lang');
List<String> langCode = lang.split('-');
if (langCode.length > 1) {
return await AppLocalizations.init(Locale.fromSubtags(
languageCode: langCode[0], scriptCode: langCode[1]));
} else {
return await AppLocalizations.init(Locale(langCode[0]));
}
}
});
}
@override
bool shouldReload(LocalizationsDelegate<AppLocalizations> old) {
// false時,不執行上述重寫函數
return false;
}
}

步驟3:

AppLocalizations類,我們保存多語言數據以及數據處理、數據初始化的一個類,提供給開發者調用的就是該類下的方法,需要實現的方法:

  • 接收參數配置(支持語種,默認語言,語種映射關係,本地路徑,efox平臺路徑,是否加載本地)的addSupportLanguage方法
  • 獲取語言的localeLang方法
  • 在語言切換時需要頁面刷新,因此需要保存setState的setProxy方法(設置語言切換代理)
  • 內部init方法以及加載語言包的getLanguageJson方法
  • 需要是否加載本地語言的changeIsLocale方法
  • 修改當前語言的changeLanguage方法
  • 讀取的語言的$t方法
class AppLocalizations {
Locale _locale;
static AppLocalizations _inst; // AppLocalizations實例
static Map<String, dynamic> _jsonLanguage = {}; // 語言包
static Function _setState; // 頂層父節點setState
static BuildContext _context;
static String _localePath; // 本地多語言路徑
static String _I18nHost; // Efox平臺多語言路徑
static bool _isLocale = true; // 是否加載本地多語言
static bool _hasConfigLocale = false; // 是否手動配置是否加載本地多語言
static String _localeLang; // 緩存本地的多語言語種
static bool get isLocale => _isLocale;
static String get localeLang {
return _localeLang ?? Localizations.localeOf(_context).toString();
}
AppLocalizations(this._locale);
// 添加支持語種
static void addSupportLanguage({
List<Locale> supportedLocales,
Map<String, String> defaultLanguage,
Map<String, String> mapLanguage,
String localePath,
String I18nHost,
bool isLocale,
}) {
if (supportedLocales != null) {
ConfigLanguage.supportedLocales.addAll(supportedLocales);
}
if (defaultLanguage != null) {
ConfigLanguage.defaultLanguage.addAll(defaultLanguage);
}
if (mapLanguage != null) {
ConfigLanguage.mapLanguage.addAll(mapLanguage);
}
if (isLocale != null) {
_hasConfigLocale = true;
_isLocale = isLocale;
}
_localePath = localePath;
_I18nHost = I18nHost;
}
// 設置語言切換代理
static void setProxy(Function setState, BuildContext context) async {
_setState = setState;
_context = context;
}
// 初始化 localizations
static Future<AppLocalizations> init(Locale locale) async {
_inst = AppLocalizations(locale);
await getLanguageJson();
_setState(() {}); // 多語言包更新
return _inst;
}
// 獲取語言包
static Future getLanguageJson() async {
if (!_hasConfigLocale) {
_isLocale =
(await LocalStorage.get('isLocale')) == 'false' ? false : true;
}
Locale _tmpLocale = _inst._locale;
String jsonLang;
String lang = ConfigLanguage.mapLanguage[_tmpLocale.toString()] ??
_tmpLocale.toString();
if (_isLocale) {
try {
print('語言包路徑: $_localePath/$lang.json');
jsonLang = await rootBundle.loadString('$_localePath/$lang.json');
// print('多語言本地加載數據:${json.decode(jsonLang)}');
} catch (e) {
print('多語言本地路徑: $_localePath/$lang.json');
print('多語言本地加載路徑不存在,加載默認語言數據:$e');
_inst._locale = Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code']);
_tmpLocale = _inst._locale;
lang = ConfigLanguage.mapLanguage[_tmpLocale.toString()] ??
_tmpLocale.toString();
jsonLang = await rootBundle.loadString('$_localePath/$lang.json');
}
} else {
try {
print('語言包路徑: $_I18nHost/$lang.json');
jsonLang = (await Http.get(url: '$_I18nHost/$lang.json')).toString();
// print('多語言平臺加載數據:${(json.decode(jsonLang))}');
} catch (e) {
print('多語言平臺路徑: $_I18nHost/$lang.json');
print('多語言平臺加載路徑不存在,加載默認語言數據:$e');
_inst._locale = Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code']);
_tmpLocale = _inst._locale;
lang = ConfigLanguage.mapLanguage[_tmpLocale.toString()] ??
_tmpLocale.toString();
jsonLang = (await Http.get(url: '$_I18nHost/$lang.json')).toString();
}
}
_jsonLanguage['$_tmpLocale'] = json.decode(jsonLang.toString());
print('是否加載本地語言: $_isLocale');
print(
'獲取語言包的語種信息: ${_inst._locale}, $lang, $_tmpLocale, ${_tmpLocale.languageCode}, ${_tmpLocale.scriptCode}');
print("多語言加載的數據: $_jsonLanguage");
}
// 修改是否加載本地多語言
static void changeIsLocale(isLocale) async {
_isLocale = isLocale;
LocalStorage.set('isLocale', isLocale.toString());
init(_inst._locale);
}
// 切換語言
static void changeLanguage([Locale locale]) {
if (locale == null || locale.languageCode == null) {
print('修改語言語言碼不能為空');
locale = Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code']);
}
if (locale.scriptCode != null) {
_localeLang = '${locale.languageCode}-${locale.scriptCode}';
} else {
_localeLang = '${locale.languageCode}';
}
LocalStorage.set('lang', _localeLang);
init(Locale.fromSubtags(
languageCode: locale.languageCode,
scriptCode: locale.scriptCode)); // 根據語言獲取對應的國際化文件
}
static String $t(String key) {
Locale _tmpLocale = _inst == null
? Locale.fromSubtags(
languageCode: ConfigLanguage.defaultLanguage['language_code'],
scriptCode: ConfigLanguage.defaultLanguage['script_code'])
: _inst._locale;
var _array = key.split('.');
var _dict = _jsonLanguage['$_tmpLocale'] ?? {};
var retValue = '';
try {
_array.forEach((item) {
if (!_dict.containsKey(item) || _dict[item].runtimeType == Null) {
retValue = key;
return;
}
if (_dict[item].runtimeType != String) {
_dict = _dict[item];
} else {
retValue = _dict[item];
}
});
retValue = retValue.isEmpty ? _dict : retValue;
} catch (e) {
print('i18n exception');
print(e);
retValue = key;
}
return retValue ?? '';
}
}

最後

歡迎更多學習flutter的小夥伴加入QQ群 Flutter UI: 798874340

敬請關注我們github: YYDev

作者

相關文章

2020年史上最全Vue框架整理從基礎到實戰(二)

雲原生基礎及調研

一文搞懂V8引擎的垃圾回收

精讀《正交的React組件》