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

Unhandled Exception: type '_Map<String, dynamic>' is not a subtype of type 'Map<String, Function>' of 'other' #798

Open codelovercc opened 9 months ago

codelovercc commented 9 months ago

About intl_translation: ^0.19.0 and Flutter intl plugin (version 1.18.4-2022.2) on Android Studio.

% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.9, on macOS 12.7.1 21G920 darwin-x64, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] VS Code (version 1.85.1)
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

intl_translation: ^0.19.0 generate messages_en.dart below:

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = MessageLookup();

typedef String? MessageIfAbsent(
    String? messageStr, List<Object>? args);

class MessageLookup extends MessageLookupByLibrary {
  @override
  String get localeName => 'en';

  static m0(name) => "Hello ${name} from test";

  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
      'testArgsMessage': m0,
    'testMessage': MessageLookupByLibrary.simpleMessage('This is a test message for localization.')
  };
}

and Flutter intl plugin generate messages_en.dart below:

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.

// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = new MessageLookup();

typedef String MessageIfAbsent(String messageStr, List<dynamic> args);

class MessageLookup extends MessageLookupByLibrary {
  String get localeName => 'en';

  static String m0(companyName, year) => "Copy right ${companyName} ${year}.";

  final messages = _notInlinedMessages(_notInlinedMessages);
  static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
        "appTitle":
            MessageLookupByLibrary.simpleMessage("member end"),
        "copyRight": m0,
        "home": MessageLookupByLibrary.simpleMessage("Home"),
        "loginBannerText":
            MessageLookupByLibrary.simpleMessage("Your dream starts from here"),
        "loginBannerTitle": MessageLookupByLibrary.simpleMessage("Marketing"),
        "mine": MessageLookupByLibrary.simpleMessage("Mine"),
        "poweredBy": MessageLookupByLibrary.simpleMessage("Powered by"),
        "testMessage": MessageLookupByLibrary.simpleMessage(
            "This is a test message for localization.")
      };
}

The difference between them is method _notInlinedMessages and field messages, intl_translation: ^0.19.0 defined messages type as Map<String, dynamic> and method _notInlinedMessages returns type as Map<String, dynamic>, but Flutter intl plugin defined method _notInlinedMessages returns type as Map<String, Function>, they should be in the same type, this cause the runtime exception when I call MessageLookupByLibrary.message.addAll to merge these two messages map to one map.

eernstg commented 9 months ago

Random observation, may or may not be relevant: Search for @dart, only one of the generated libraries has it.

codelovercc commented 9 months ago

I use intl and intl_translation in dart only package, use intl and intlutil in Flutter project, every generated `messages*.dartfiles have@dart=2.12by intl_translation. In other word, intl_translation generate libraries with@dart=2.12`, and intl_util does not.

intl_translation generated files samples:

messages_all.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// @dart=2.12
export 'messages_all_locales.dart' show initializeMessages;

messages_all_locales.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:implementation_imports, file_names
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
// ignore_for_file:argument_type_not_assignable, invalid_assignment
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
// ignore_for_file:comment_references

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/src/intl_helpers.dart';

import 'messages_en.dart' deferred as messages_en;
import 'messages_zh.dart' deferred as messages_zh;

typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
  'en': messages_en.loadLibrary,
  'zh': messages_zh.loadLibrary,
};

MessageLookupByLibrary? _findExact(String localeName) {
  switch (localeName) {
    case 'en':
      return messages_en.messages;
    case 'zh':
      return messages_zh.messages;
    default:
      return null;
  }
}

/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String? localeName) async {
  var availableLocale =
      Intl.verifiedLocale(localeName, (locale) => _deferredLibraries[locale] != null, onFailure: (_) => null);
  if (availableLocale == null) {
    return Future.value(false);
  }
  var lib = _deferredLibraries[availableLocale];
  await (lib == null ? Future.value(false) : lib());
  initializeInternalMessageLookup(() => CompositeMessageLookup());
  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
  return Future.value(true);
}

bool _messagesExistFor(String locale) {
  try {
    return _findExact(locale) != null;
  } catch (e) {
    return false;
  }
}

MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
  if (actualLocale == null) return null;
  return _findExact(actualLocale);
}

messages_en.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = MessageLookup();

typedef String? MessageIfAbsent(String? messageStr, List<Object>? args);

class MessageLookup extends MessageLookupByLibrary {
  @override
  String get localeName => 'en';

  static m0(name) => "Hello ${name} from test";

  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
        'testArgsMessage': m0,
        'testMessage': MessageLookupByLibrary.simpleMessage('This is a test message for localization.')
      };
}

I solved this error by this:

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.');
        }

        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);
    }
  }
}

With this code it solved the type error issue, I'm not sure there is an other way to solve this error.

 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);
        }
mosuem commented 9 months ago

What is the "Flutter intl plugin"? Is this an issue on their side?

codelovercc commented 8 months ago

I don't think so, intl_translation for dart only packages, intl_util for flutter application and packages. You can see the difference of generated codes between them:

intl_translation: ^0.19.0 ↓

  ...
  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
  ...

Flutter intl plugin (version 1.18.4-2022.2) IntelliJ/Android Studio

  ...
  final messages = _notInlinedMessages(_notInlinedMessages);
  static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
  ...

Map<String, dynamic> and Map<String, Function>, that why MessageLookupByLibrary.messages.addAll() throws the exception.