基于原intl和flutter_intl自动生成插件,使原先固定的arb变为可动态化更新的模式。也可以不用flutter_intl
,默认的只要格式正确也是一样的。
支持通过版本变更来更新,以及arb增量更新。并且可以动态增加支持的语言并及时应用。
dependencies:
dynamic_intl: ^0.1.0
Import it
import 'package:dynamic_intl/dynamic_intl.dart';
继承LanguageSetting
来配置对应的设置。首要就是设置对应的资源远程链接,还可以配置缓存的目录、文件名等。
import 'package:dynamic_intl/dynamic_intl.dart';
class TestSetting extends LanguageSetting {
@override
Future<String> languageApi(String locale) async {
return 'https://jomin-web.web.app/language/intl_$locale.arb';
}
@override
String get defaultLocale => 'en';
@override
Map<String, LibraryLoader> get deferredLibraries => {
'zh': () => Future.value(null),
'en': () => Future.value(null),
};
}
defaultLocale
为默认的语言,最好跟flutter_intl
中设置的保持一致。会根据这个默认语言来决定各个文案的占位符,很重要。
deferredLibraries
是支持的语言列表,格式跟官方intl
中自动生成的一样,下边的checkAndDownload
可以放在这里,那么在加载对应语言时就可以触发检查下载。
还可以设置在远程资源失效或没有正常获得的时候,备用的本地语言包,格式也是跟官方的一致。
import 'package:lib/generated/intl/messages_zh.dart' as messages_zh;
import 'package:lib/generated/intl/messages_en.dart' as messages_en;
@override
MessageLookupByLibrary? defaultLocaleMessages(String localeName) {
switch (localeName) {
case 'zh':
return messages_zh.messages;
case 'en':
return messages_en.messages;
default:
return null;
}
}
flutter_intl
的配置:
flutter_intl:
enabled: true
按照官方的来即可,也可以自己配置路径什么的(example中的配置了language路径下)。
自动生成的文件(generated
下的)不需要去手动修改,messages_all
已经不会用到。messages_en
等,可以作为本地备用资源,在远程资源失败时可做备用。l10n.dart
需要记住其中的类,后续需要注册到delegate
中,但不要用它自己里面的delegate
。
l10n
目录下的为语言包,即本地的,尽量跟远程同步,修改后及时更新到远程中或随版本更新。格式就简单的json
,根据文件名区分语言。
{
"test": "test title"
}
目前arb的转译仅支持普通的Intl.message
,且最多十个占位符(各规则可参考ApplicationResourceBundleSpecification),如果有需要想支持plural
、gender
等,或者需要更多占位符,可以继承ArbTraslation
,并重写parseFile
。相应的写法可自行查看代码。然后设置到LanguageSetting
中。
@override
ArbTranslation arbTranslation = NewArbTranslation();
若是觉得语言包体积过于庞大,可以配合服务端做增量更新。即服务端把‘增量’返回,客户端会进行merge
并更新(不会移除旧的)。
默认为false
,需设置为true
。
@override
bool get arbMergeEnable => true;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LanguageUtil.instance.init(TestSetting());
runApp(const MyApp());
}
使用多语言,WidgetsFlutterBinding.ensureInitialized()
也是需要的。
locale
可自行控制,需要配置的主要是localizationsDelegates
和supportedLocales
。可继承BaseLocalizationsDynamicDelegate<T>
(T
即为flutter_intl
自动生成的类,默认是S
, 具体由你决定)来获取最简单的Delegate
。有自己想法的也可以自行编写,并重写supportedLocales
和在load
中使用initializeDynamicMessages
。
class AppLocalizationDynamicDelegate
extends BaseLocalizationsDynamicDelegate<S> {
const AppLocalizationDynamicDelegate();
static const AppLocalizationDynamicDelegate delegate =
AppLocalizationDynamicDelegate();
@override
Future<S> loadS() {
final instance = S();
return Future.value(instance);
}
}
按照上边的写法即可。然后设置到MaterialApp
中
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
/// 当前语言
locale: MyApp.locale,
localizationsDelegates: const [
/// Delegate 注册
AppLocalizationDynamicDelegate.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
/// 支持的语言列表
supportedLocales:
AppLocalizationDynamicDelegate.delegate.supportedLocales,
home: MyHomePage(
title: 'Flutter Demo Home Page', updateLocale: updateLocale),
);
}
在自定支持语言列表时,这三个delegate
也是需要的,系统功能的多语言支持,记得也加上。
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
它们需要flutter_localizations
flutter_localizations: # Add this line
sdk: flutter
通过Future checkAndDownload(String locale, [String? newVersion])
可下载指定语言指定版本的语言包(版本不同则会更新)。下载完成之后可刷新UI来展示最新文案。切换语言也应该调用该方法,已存在语言包则会跳过。
// 下载语言包,可以加版本, 无版本则每次都更新
LanguageUtil.instance
.checkAndDownload(MyApp.locale.languageCode, '1002')
.then((value) {
// 因为下载完成前会使用默认文案,下载完成后应刷新下UI
setState(() {});
});
若当前下载的不是默认语言,且默认语言包也没有下载保存过,那么会先下载默认语言包。
版本管理需要自行设计,这里仅根据传入的版本对比来更新。
可通过updateLanguageLibrary
动态新增支持的语言列表。
updateLanguageLibrary([const Locale('it'), const Locale('de')]);
若Setting
中没有,则可以先检查远程列表然后通过该方法新增,新增后才可以有效切换。
setLocalLanguage
设置为true
,则会无视远程语言包,直接使用本地的语言包,确认defaultLocaleMessages
正常配置。
LanguageUtil.instance.setLocalLanguage(true);
即普通的S.of(context).text
,觉得需要context
太麻烦,可以在MaterialApp
下build
的时候注册一个全局context
(参考GetX的全局context),一样用。
Text(S.of(context).test)
/// 带占位符
S.of(context).textPlace(‘123’)
修改MaterialApp
的locale
,然后检查语言包下载,最后刷新UI即可。需要刷新到MaterialApp
这一层,可以看看example
中的示例。
void _changeLocale() async {
var locale = MyApp.locale.languageCode == 'en'
? const Locale('zh')
: const Locale('en');
LanguageUtil.instance
.checkAndDownload(locale.languageCode, '1002')
.then((_) {
// 因为下载前会使用默认文案,下载完成后应刷新下UI
widget.updateLocale(locale);
});
}
翻译的文件有@开头的注释消息,也没有问题,只不过默认的ArbTranslation
不会处理。LanguageSetting
可以在你的管理类里面继承,建议S
也重新再套一层再使用,方便维护。检查下载和切换可以根据实际需求来,全部都一起下载也不是不可以,也可以配置在deferredLibraries
中随系统切换时自动同步检查下载。
example
中有个比较简单的可以直接运行的例子,可供参考。有什么问题的话可以直接提issue。