Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intl is not working for multiple packages project #797

Open
codelovercc opened this issue Feb 8, 2024 · 5 comments
Open

Intl is not working for multiple packages project #797

codelovercc opened this issue Feb 8, 2024 · 5 comments
Labels
type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@codelovercc
Copy link

codelovercc commented Feb 8, 2024

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 :)

@codelovercc codelovercc added the type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) label Feb 8, 2024
@Douglas-Pontes
Copy link

Douglas-Pontes commented Feb 19, 2024

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

@codelovercc
Copy link
Author

@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
Copy link

stwarwas commented Mar 6, 2024

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
Copy link
Author

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

@Luvti
Copy link

Luvti commented May 16, 2024

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

4 participants