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

YYDev發表於2019-12-09

前言

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

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

接入使用

FlutterLocalization

效果

多語言外掛.gif

多語言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包就可以程式碼擼起

新建package

步驟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

作者

相關文章