dart-lang / i18n

A general mono-repo for Dart i18n and l10n packages.
BSD 3-Clause "New" or "Revised" License
64 stars 39 forks source link

Intl is not working for multiple packages project #797

Open codelovercc opened 9 months ago

codelovercc commented 9 months ago

I created some packages and one flutter application , these projects use Intl for localizations. Packages and application:

  1. a package named member_end, pure dart package, contains business logic codes, it uses Intl and intl_translation for localizations, it has a custom MemberLocalizations class that defined localization message getters and has a load method.
  2. member_end_flutter, Flutter package, contains common widgets and implements for Flutter, it depends on member_end, it uses intl and intl_utils for localizations, the localizations class is named MemberFlutterLocalizations.
  3. member_end_app, Flutter application, it depends on member_end and member_end_flutter, it uses intl and intl_utils for localizations, the localizations class is default S.

These projects supports en and zh Locales.

Files:

  1. member_end member_end |---lib |---|---l10n |---|---|---intl_en.arb |---|---|---intl_zh.arb |---|---src |---|---|---intl |---|---|---|---messages_all.dart |---|---|---|---messages_en.dart |---|---|---|---messages_zh.dart
  2. member_end_flutter member_end_flutter |---lib |---|---l10n |---|---|---intl_en.arb |---|---|---intl_zh.arb |---|---generated |---|---|---l10n.dart |---|---|---intl |---|---|---|---messages_all.dart |---|---|---|---messages_en.dart |---|---|---|---messages_zh.dart
  3. member_end_app member_end_app |---lib |---|---l10n |---|---|---intl_en.arb |---|---|---intl_zh.arb |---|---generated |---|---|---l10n.dart |---|---|---intl |---|---|---|---messages_all.dart |---|---|---|---messages_en.dart |---|---|---|---messages_zh.dart

Let's say the current locale is zh, the Localizations classes are loaded in order

  1. MemberLocalizations
  2. MemberFlutterLocalizations
  3. S

The problem is only the first MemberLocalizations will load its member_end/lib/src/intl/messages_zh.dart, this cause member_end_flutter and member_end_app can't get the correct locale messages.

In Localizations classes static Future<S> load(Locale locale) method, it use Future<bool> initializeMessages(String localeName) method to init and load messages, Future<bool> initializeMessages(String localeName) use CompositeMessageLookup to add locale messages, let's check CompositeMessageLookup.addLocale method:

  /// If we do not already have a locale for [localeName] then
  /// [findLocale] will be called and the result stored as the lookup
  /// mechanism for that locale.
  @override
  void addLocale(String localeName, Function findLocale) {
    if (localeExists(localeName)) return;
    var canonical = Intl.canonicalizedLocale(localeName);
    var newLocale = findLocale(canonical);
    if (newLocale != null) {
      availableMessages[localeName] = newLocale;
      availableMessages[canonical] = newLocale;
      // If there was already a failed lookup for [newLocale], null the cache.
      if (_lastLocale == newLocale) {
        _lastLocale = null;
        _lastLookup = null;
      }
    }
  }

When the first MemberLocalizations load, the locale zh is not exists, so localeExists(localeName) returns false, and then the member_end package's zh locale message will load. MemberFlutterLocalizations will be loaded by next in the order, when it runs into CompositeMessageLookup.addLocale, localeExists(localeName) returns true, because locale zh MessageLookupByLibrary is already added by MemberLocalizations in member_end package, S will be the same when it's loading.

To solve this issue, I have few ways to do:

  1. Write hardcode local messages in sub Localizations class, like Flutter framework do. But this is not the way to use intl.
  2. Create a subclass of CompositeMessageLookup named CustomCompositeMessageLookup and override method addLocale, check if locale exists and then merge the new MessageLookupByLibrary into the old MessageLookupByLibrary, if the locale message name is already exists then overwrite with the new value that provided by the new MessageLookupByLibrary, then call void initializeInternalMessageLookup(()=>CustomCompositeMessageLookup()) method in the main method to init global field MessageLookup messageLookup. But initializeInternalMessageLookup is not a public API.
  3. As a feature request, maybe you guys can do this awesome work, make intl works in multiple projects.

If there is other better way to solve this, please tell me :)

Douglas-Pontes commented 9 months ago

I have the same problem, can you share with me the solution 2 you did? or the first one?

codelovercc commented 9 months ago

@Douglas-Pontes

Solution 2:

class MultiCompositeMessageLookup extends CompositeMessageLookup {
  @override
  void addLocale(String localeName, Function findLocale) {
    final canonical = Intl.canonicalizedLocale(localeName);
    final newLocale = findLocale(canonical);
    if (newLocale != null) {
      final oldLocale = availableMessages[localeName];
      if (oldLocale != null && newLocale != oldLocale) {
        if (newLocale is! MessageLookupByLibrary) {
          throw Exception('Merge locale messages failed, type ${newLocale.runtimeType} is not supported.');
        }
        // solve issue https://github.com/dart-lang/i18n/issues/798 if you are using intl_translate and intl_util both.
        if (oldLocale.messages is Map<String, Function> && newLocale.messages is! Map<String, Function>) {
          final newMessages = newLocale.messages.map((key, value) => MapEntry(key, value as Function));
          oldLocale.messages.addAll(newMessages);
        } else {
          oldLocale.messages.addAll(newLocale.messages);
        }
        return;
      }
      super.addLocale(localeName, findLocale);
    }
  }
}

Then call initializeInternalMessageLookup(() => MultiCompositeMessageLookup()); before any localizations class load method.

stwarwas commented 8 months ago

I have the same problem. Almost all examples I found use a very simple one-package setup. How do people use this in larger projects?

codelovercc commented 8 months ago

@stwarwas Just call initializeInternalMessageLookup(() => MultiCompositeMessageLookup()); at the first line in your main method.

Luvti commented 6 months ago

simple solution is - https://github.com/Luvti/i18n

dependency_overrides:
  intl: #0.19.0
    git:
      url: https://github.com/Luvti/i18n
      path: pkgs/intl