initial method

Future<void> initial({
  1. required Iterable<LanguageDataProvider> data,
  2. Iterable<LanguageDataProvider> dataOverrides = const [LanguageDataProvider.empty()],
  3. Iterable<String> analysisKeys = const {},
  4. LanguageCodes? initialCode,
  5. bool useInitialCodeWhenUnavailable = false,
  6. bool forceRebuild = false,
  7. bool isAutoSave = true,
  8. bool syncWithDevice = true,
  9. bool isOptionalCountryCode = true,
  10. void onChanged(
    1. LanguageCodes code
    )?,
  11. bool isDebug = false,
})

Initialize the plugin with the List of data that you have created, you can set the initialCode for this app or it will get the first language in data, the LanguageCodes.en will be added when the data is empty. You can also set the forceRebuild to true if you want to rebuild all the LanguageBuilder widgets, not only the root widget (it will decreases the performance of the app). The onChanged callback will be called when the language is changed. Set the isDebug to true to show debug log.

analysisKeys is the List of keys of the LanguageData. This data will be used by analyze to get the missing text of the specified language.

useInitialCodeWhenUnavailable : If true, when you change the LanguageCodes by using change method, the app will change to the initialCode if the new code is unavailable. If false, the app will use keep using the last code.

The plugin also supports auto save the LanguageCodes when changed and reload it from memory in the next opening.

Implementation

Future<void> initial({
  /// Data of languages. If this value is empty, a temporary data ([LanguageDataProvider.data({LanguagesCode.en: {}})])
  /// will be added to let make it easier to develop the app.
  required Iterable<LanguageDataProvider> data,

  /// Data of the languages that you want to override the [data]. This feature
  /// will helpful when you want to change just some translations of the language
  /// that are already available in the [data].
  ///
  /// Common case is that you're using the generated [languageData] as your [data]
  /// but you want to change some translations (mostly with [LanguageConditions]).
  Iterable<LanguageDataProvider> dataOverrides = const [
    LanguageDataProvider.empty()
  ],

  /// List of all the keys of text in your project.
  ///
  /// You can maintain it by yourself or using [language_helper_generator](https://pub.dev/packages/language_helper_generator).
  /// This value will be used by `analyze` method to let you know that which
  /// text is missing in your language data.
  Iterable<String> analysisKeys = const {},

  /// Firstly, the app will try to use this [initialCode]. If [initialCode] is null,
  /// the plugin will try to get the current device language. If both of them are
  /// null, the plugin will use the first language in the [data].
  LanguageCodes? initialCode,

  /// If this value is `true`, the plugin will use the [initialCode] if you [change]
  /// to the language that is not in the [data], otherwise it will do nothing
  /// (keeps the last language).
  bool useInitialCodeWhenUnavailable = false,

  /// Use this value as default for all [LanguageBuilder].
  bool forceRebuild = false,

  /// Auto save the current change of the language. The app will use the new
  /// language in the next open instead of [initialCode].
  bool isAutoSave = true,

  /// TODO(lamnhan066): Make sure (add test) the caching feature this feature worked before publishing
  ///
  /// Caches the valid data for later use. Useful when using data from `network`.
  // bool cachesData = true,

  /// Apply the device language when it's changed.
  /// If this value is `true`, update the app language when the device language changes.
  /// Otherwise, keep the current app language even if the device language changes.
  bool syncWithDevice = true,

  /// Attempts to handle Locale codes with optional country specification.
  /// When a full Locale code (including country code) is not available in the data,
  /// this method will fallback to using just the language code.
  ///
  /// For example, if 'zh_CN' (Chinese, China) is not available,
  /// it will try using 'zh' (Chinese) to set the language.
  /// Set 'isOptionalCountryCode' to true to enable this behavior.
  bool isOptionalCountryCode = true,

  /// Callback on language changed.
  void Function(LanguageCodes code)? onChanged,

  /// Print the debug log.
  bool isDebug = false,
}) async {
  _data.clear();
  _dataOverrides.clear();
  _dataProviders = data;
  _dataOverridesProviders = dataOverrides;
  _forceRebuild = forceRebuild;
  _onChanged = onChanged;
  _isDebug = isDebug;
  _useInitialCodeWhenUnavailable = useInitialCodeWhenUnavailable;
  _isAutoSave = isAutoSave;
  _syncWithDevice = syncWithDevice;
  _analysisKeys = analysisKeys;
  _initialCode = initialCode;

  // When the `data` is empty, a temporary data will be added.
  if (_dataProviders.isEmpty) {
    printDebug(
        'The `data` is empty, we will use a temporary `data` for the developing state');
    _dataProviders = [
      LanguageDataProvider.data({LanguageCodes.en: {}})
    ];
  }

  _dataProvider = await _chooseTheBestDataProvider(_dataProviders, false);
  _dataOverridesProvider =
      await _chooseTheBestDataProvider(_dataOverridesProviders, true);

  LanguageCodes finalCode = _initialCode ?? LanguageCode.code;

  _codes = await _dataProvider.getSupportedCodes();
  _codesOverrides = await _dataOverridesProvider.getSupportedCodes();

  assert(
      _codes.isNotEmpty, 'The LanguageData in the `data` must be not empty');

  // Try to reload from memory if `isAutoSave` is `true`
  if (_isAutoSave) {
    final prefs = await SharedPreferences.getInstance();

    if (prefs.containsKey(_autoSaveCodeKey)) {
      final code = prefs.getString(_autoSaveCodeKey);

      if (code != null && code.isNotEmpty) {
        finalCode = LanguageCodes.fromCode(code);
      }
    }
  }

  // Try to get the device language code if `syncWithDevice` is `true`
  if (_syncWithDevice) {
    final prefs = await SharedPreferences.getInstance();
    final prefCodeCode = prefs.getString(_deviceCodeKey);
    final currentCode = LanguageCode.code;

    if (prefCodeCode == null) {
      // Sync with device only track the changing of the device language,
      // so it will not use the device language for the app at the first time.
      prefs.setString(_deviceCodeKey, currentCode.code);
      printDebug(
          'Sync with device saved the current language to local database.');
    } else {
      // We only consider to change the app language when the device language
      // is changed. So it will not affect the app language that is set by the user.
      final prefCode = LanguageCodes.fromCode(prefCodeCode);
      if (currentCode != prefCode) {
        finalCode = currentCode;
        prefs.setString(_deviceCodeKey, currentCode.code);
        printDebug('Sync with device applied the new device language');
      } else {
        printDebug('Sync with device used the current app language');
      }
    }
  }

  if (!codes.contains(finalCode)) {
    LanguageCodes? tempCode;
    if (isOptionalCountryCode && finalCode.locale.countryCode != null) {
      // Try to use the `languageCode` only if the `languageCode_countryCode`
      // is not available
      printDebug(
          'language does not contain the $finalCode => Try to use the `languageCode` only..');
      try {
        tempCode = LanguageCodes.fromCode(finalCode.locale.languageCode);
        if (!codes.contains(tempCode)) {
          tempCode = null;
        }
      } catch (_) {}
    }

    if (tempCode == null) {
      printDebug(
          'Unable to use the `languageCode` only => Change the code to ${codes.first}');
    } else {
      printDebug(
          'Able to use the `languageCode` only => Change the code to $tempCode');
    }

    finalCode = tempCode ?? codes.first;
  }

  printDebug('Set `currentCode` to $finalCode');
  _currentCode = finalCode;

  _data.addAll(await _dataProvider.getData(code));
  _dataOverrides.addAll(await _dataOverridesProvider.getData(code));

  if (_isDebug) {
    analyze();
  }

  if (!_ensureInitialized.isCompleted) {
    _ensureInitialized.complete();
  }
}