通常对于一个flutter组件来说,其多语言支持有两种情况:一是仅支持中文不支持多语言,二是多语言仅支持英文。当涉及到组件需要提供给海外业务使用时,就不能满足需求了,但我们在编码时就在组件提供所有语言资源是不现实的,理想情况是像原生一样支持在打包时通过添加多语言资源文件的方式,在不修改组件代码的前提下增加多更多语种的支持。本文提供了一种实现方案,介绍如下。
在flutter宿主module工程的main.dart中:
void main() {
// 方式一:逐个添加多语言资源
CommonIntl.add(Locale('ja'), XXXResourceJA()); //XXXResource是具体组件的资源文件,下节会有介绍
CommonIntl.add(Locale('ja'), YYYResourceJA());
CommonIntl.add(Locale('ja'), ZZZResourceJA());
CommonIntl.add(Locale('tv'), XXXResourceTV());
CommonIntl.add(Locale('tv'), YYYResourceTV());
CommonIntl.add(Locale('tv'), ZZZResourceTV());
// 方式二:批量添加某种语言的资源文件
CommonIntl.addAll(Locale('ja'), [
XXXResourceJA(),
YYYResourceJA(),
ZZZResourceJA(),
]);
CommonIntl.addAll(Locale('tv'), [
XXXResourceTV(),
YYYResourceTV(),
ZZZResourceTV(),
]);
....
runApp(MyApp());
}
MaterialApp
中注册对应的区域及组件多语言代理 @override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: [
// 假设我们原本组件中仅支持zh、en,ja和tv是动态添加的
const Locale('zh'),
const Locale('en'),
const Locale('ja'),
const Locale('tv'),
],
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FallbackCupertinoLocalisationsDelegate.delegate,
XXXLocalizationDelegate.delegate,
],
home: HomePage(
......
),
);
}
由于flutter的intl库及相应插件生成的多语言代码只适用于单体APP,当存在多个有多语言资源的flutter组件时存在资源相互覆盖的问题,因此推荐多语言采用flutter官方的实现方式:
第一步:定义多语言资源类:
abstract class XXXResource {
String get app_ok;
String get app_cancel;
String get app_empty;
String get app_retry_click;
......
}
第二步:实现各个语言的具体资源:
class XXXResourceEn implements XXXResource {
@override
String get app_about => 'About';
@override
String get app_back_tip => 'Exit?';
@override
String get app_cancel => 'cancel';
@override
String get app_retry_click => 'try again';
....
}
class XXXResourceZh implements XXXResource {
@override
String get app_about => '关于';
@override
String get app_back_tip => '确定要退出应用?';
@override
String get app_cancel => '取消';
@override
String get app_retry_click => '点击重试';
......
}
///
/// 组件多语言加载器
///
class XXXResourceLoader {
final Locale locale;
final Map _resourceMap = {'en': XXXResourceEn(), 'zh': XXXResourceZh()};
XXXResourceLoader(this.locale);
XXXResource get localizedResource {
// 在这里增加以下两行,使组件支持动态资源文件
final cr = CommonIntl.findIntlResourceOfType(locale);
if (cr != null) return cr;
// 常规的多语言资源加载
return _resourceMap[locale.languageCode] ?? _resourceMap['en'];
}
/// 获取[XXXResourceLoader]实例
static XXXResourceLoader of(BuildContext context) => Localizations.of(context, XXXResourceLoader);
/// 获取[XXXResource]资源
static XXXResource i10n(BuildContext context) => of(context).localizedResource;
}
///
/// 组件多语言适配代理
///
class XXXLocalizationDelegate extends LocalizationsDelegate{
@override
bool isSupported(Locale locale) =>true;
@override
Future load(Locale locale) {
Log.d(runtimeType.toString(),'load: locale = $locale, ${locale.countryCode}, ${locale.languageCode}');
return SynchronousFuture(XXXResourceLoader(locale));
}
@override
bool shouldReload(LocalizationsDelegate old) => false;
/// 需在app入口注册
static XXXLocalizationDelegate delegate = XXXLocalizationDelegate();
}
上述动态导入和加载动态资源文件的过程主要涉及到两个接口:CommonIntl.add(Locale locale, dynamic resource)
和T CommonIntl.findIntlResourceOfType
。其实原理也比较简单,直接看下源码:
final Map> _additionalIntls = {};
class CommonIntl {
static T findIntlResourceOfType(Locale locale) {
assert(locale != null);
final res = _additionalIntls[locale];
if (res != null && res.isNotEmpty) {
for (var entry in res.entries) {
if (entry.value is T) {
return entry.value;
}
}
}
return null;
}
static T findIntlResourceOfExactType(Locale locale) {
assert(locale != null);
final res = _additionalIntls[locale];
if (res != null && res.isNotEmpty) {
for (var entry in res.entries) {
if (entry.key == T) {
return entry.value;
}
}
}
return null;
}
static Iterable findIntlResourcesOfType(Locale locale) sync* {
assert(locale != null);
final res = _additionalIntls[locale];
if (res != null && res.isNotEmpty) {
for (var entry in res.entries) {
if (entry.value is T) {
yield entry.value;
}
}
}
}
static void addAll(Locale locale, List intlResources) {
assert(locale != null);
var res = _additionalIntls[locale];
if (res == null) {
res = {};
_additionalIntls[locale] = res;
}
for (var intl in intlResources) {
res[intl.runtimeType] = intl;
}
}
static void add(Locale locale, intlResource) {
assert(locale != null);
var res = _additionalIntls[locale];
if (res == null) {
res = {};
_additionalIntls[locale] = res;
}
res[intlResource.runtimeType] = intlResource;
}
}
其实就是用一个map来保存和获取对应语言区域的资源文件,比较简单就不赘述了。