解决flutter_Intl多模块问题

1,451 阅读2分钟

背景

我们的项目有两个模块,使用flutter_Intl做国际化的时候是这么初始化的,在localizationsDelegates添加了ModuleA.S.delegate和S.delegate两个模块的delegate

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: '${appConfigSvc.appName}',
      localizationsDelegates: [
        ModuleA.S.delegate,
        S.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate
      ],
      supportedLocales: S.delegate.supportedLocales,
      localeListResolutionCallback: (locales, supportedLocales) {
        return;
      },
      ....
  }
}

运行起来后,发现ModuleA.S下的语言的可以随着系统语言变化,而S不可以。

源码分析

语言国际化使用的方法是S.current.test,所以我们可以从它入手看下底层实现,跟踪方法调用,我们可以看到最终是调用到了intl_helps.dart中的抽象方法lookupMessage

//messageLookup在初始化后会是CompositeMessageLookup对象,存储配置的语言,后续获取name对应的语言都是从这个入口获取
MessageLookup messageLookup =
    UninitializedLocaleData('initializeMessages(<locale>)', null);

abstract class MessageLookup {
  String? lookupMessage(String? messageText, String? locale, String? name,
      List<Object>? args, String? meaning,
      {MessageIfAbsent? ifAbsent});
  void addLocale(String localeName, Function findLocale);
}

这个抽象类的实现有两个,主要是看CompositeMessageLookup,addLocale其实就是把指定localeName的MessageLookupByLibrary加到availableMessages map中,lookupMessage就是从MessageLookupByLibrary中获取到对应的语言

class CompositeMessageLookup implements MessageLookup {
  Map<String, MessageLookupByLibrary> availableMessages = Map();

  bool localeExists(localeName) => availableMessages.containsKey(localeName);

  String? _lastLocale;

  MessageLookupByLibrary? _lastLookup;

  //根据locale和name获取对应语言,messageText是兜底的语言
  String? lookupMessage(String? messageText, String? locale, String? name,
      List<Object>? args, String? meaning,
      {MessageIfAbsent? ifAbsent}) {
    var knownLocale = locale ?? Intl.getCurrentLocale();
    var messages = (knownLocale == _lastLocale)
        ? _lastLookup
        : _lookupMessageCatalog(knownLocale);

    if (messages == null) {
      return ifAbsent == null ? messageText : ifAbsent(messageText, args);
    }
    return messages.lookupMessage(messageText, locale, name, args, meaning,
        ifAbsent: ifAbsent);
  }

  MessageLookupByLibrary? _lookupMessageCatalog(String locale) {
    var verifiedLocale = Intl.verifiedLocale(locale, localeExists,
        onFailure: (locale) => locale);
    _lastLocale = locale;
    _lastLookup = availableMessages[verifiedLocale];
    return _lastLookup;
  }

//findLocales是用于生成指定localeName的MessageLookupByLibrary
  void addLocale(String localeName, Function findLocale) {
    //添加语言,如果已经添加过就不再添加,这里是导致运行起来后,发现ModuleA.S下的语言的可以随着系统语言变化,而S不可以的问题的根本原因
    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;
      }
    }
  }
}

由于addLocale时候会判断localeName是否存在,如果存在就return,所以我们第二个模块的语言就不会被添加进来,那我们该怎么解决这个问题呢? 先看下MessageLookupByLibrary的源码,所有的语言的kv映射都存在messages中,所以我们可以考虑在初始化之前,把module2的messages添加到module1中就可以了。

abstract class MessageLookupByLibrary {

  String? lookupMessage(String? messageText, String? locale, String? name,
      List<Object>? args, String? meaning,
      {MessageIfAbsent? ifAbsent}) {
    var actualName = computeMessageName(name, messageText, meaning);
    Object? translation;
    if (actualName != null) {
      translation = this[actualName];
    }
    if (translation == null) {
      return ifAbsent == null ? messageText : ifAbsent(messageText, args);
    } else {
      args = args ?? const [];
      return evaluateMessage(translation, args);
    }
  }

  String? evaluateMessage(translation, List<dynamic> args) {
    return Function.apply(translation, args);
  }

  /// Return our message with the given name
  dynamic operator [](String messageName) => messages[messageName];

  Map<String, dynamic> get messages;

  String get localeName;

  String toString() => localeName;

  static String Function() simpleMessage(translatedString) =>
      () => translatedString;
}

解决方法

基于以上的分析,我用了一个比较挫的解决方法,在app启动时,把两个module的语言合并。

import 'package:xxxx1/generated/intl/messages_en.dart' as messages_en;
import 'package:xxxx1/generated/intl/messages_zh.dart' as messages_zh;
import 'package:xxxx2/generated/intl/messages_th.dart'
    as mp_messages_en;
import 'package:xxxx2/generated/intl/messages_zh.dart'
    as mp_messages_zh;

void main() {
  messages_en.messages.messages.addAll(mp_messages_en.messages.messages);
  messages_zh.messages.messages.addAll(mp_messages_zh.messages.messages);
  ....
}

总结

多模块的时候,语言无法切换主要问题是MessageLookupByLibrary是唯一的。