背景
我们的项目有两个模块,使用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是唯一的。