启动时通过Flutter framework层的ui.window获取到当前系统的local,根据MaterialApp用户配置的locale进行mapping,初始化
Localizations
,并加载LocalizationDelegate
的load
方法(需要在此方法中读取本地对应的locale的翻译),然后将LocalizationDelegate
所代理的具体的Localizations
和内置的_LocalizationsScope
关联,这样就通过内部的InheritedWidget
获取到当前locale
对应的全部翻译数据。
XXXLocalizations
继承WidgetsLocalizations,并在delegate执行load方法时初始化XXXLocalizations
加载本地翻译文件。Localizations
内置的_LocalizationsScope
获取XXXLocalizations
时例子代表了本地区域和语言,组成格式如下 {languageCode}-{scriptCode/optional}-countryCode
例: zh-Hans-CN, zh-Hants-TW,zh-CN,zh-EN
const Locale(
this._languageCode, [
this._countryCode,
])
const Locale.fromSubtags({
String languageCode = ‘und‘,
this.scriptCode,
String countryCode,
})
//拼接语言
String _rawToString(String separator) {
final StringBuffer out = StringBuffer(languageCode);
if (scriptCode != null && scriptCode.isNotEmpty)
out.write(‘$separator$scriptCode‘);
if (_countryCode != null && _countryCode.isNotEmpty)
out.write(‘$separator$countryCode‘);
return out.toString();
}
在MaterialApp
配置合适的Locale
//case1: 指定app支持的语言
MaterialApp(
...
supportedLocales: [
const Locale.fromSubtags(languageCode: ‘zh‘), // generic Chinese ‘zh‘
const Locale.fromSubtags(languageCode: ‘zh‘, scriptCode: ‘Hans‘), // generic simplified Chinese ‘zh_Hans‘
const Locale.fromSubtags(languageCode: ‘zh‘, scriptCode: ‘Hant‘), // generic traditional Chinese ‘zh_Hant‘
const Locale.fromSubtags(languageCode: ‘zh‘, scriptCode: ‘Hans‘, countryCode: ‘CN‘), // ‘zh_Hans_CN‘
const Locale.fromSubtags(languageCode: ‘zh‘, scriptCode: ‘Hant‘, countryCode: ‘TW‘), // ‘zh_Hant_TW‘
const Locale.fromSubtags(languageCode: ‘zh‘, scriptCode: ‘Hant‘, countryCode: ‘HK‘), // ‘zh_Hant_HK‘
],
)
//case2: 指定app支持的语言和区域
MaterialApp(
localizationsDelegates: [
// ... app-specific localization delegate[s] here
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale(‘en‘, ‘‘), // English, no country code
const Locale(‘he‘, ‘‘), // Hebrew, no country code
const Locale.fromSubtags(languageCode: ‘zh‘), // Chinese *See Advanced Locales below*
// ... other locales the app supports
],
// ...
)
locale
_WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver
@override
void initState() {
super.initState();
_updateNavigator();
_locale = _resolveLocales(WidgetsBinding.instance.window.locales, widget.supportedLocales);
WidgetsBinding.instance.addObserver(this);
}
//此处3个属性就是我们在MaterialApp里面所赋值的,在执行`_resolveLocales`方法时会提供方法对Locale重新处理
if (widget.localeListResolutionCallback != null) {
...
if (widget.localeResolutionCallback != null) {
...
widget.supportedLocales,
创建Localizations
类,申明翻译的字符串属性,可参考系统默认的localizations.dart
定义
CupertinoLocalizations (localizations.dart)
DefaultCupertinoLocalizations (localizations.dart)
localizaitions的抽象层定义:
定义字符串属性,方便快速定义
特殊字符串处理,传参类型, 货币,时间,格式化字符串
命名规范可以参照MaterialLocalizations的格式,最后面一般加上控件名字
abstract class MaterialLocalizations {
//下面为几种不同类型的字符串定义
String get openAppDrawerTooltip;
String tabLabel({ int tabIndex, int tabCount });
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false
String get showAccountsLabel;
//提供Context获取方法,get到当前可以提供翻译的具体实例类
static MaterialLocalizations of(BuildContext context) {
return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}
}
localizaitions的具体实现类定义
class DefaultMaterialLocalizations implements MaterialLocalizations {
//存储了部分常用字段
static const List<String> _weekdays = <String>[
//提供了需要格式化的方法,实际使用中根据项目需求定义,常用的有`时间/货币/大小写/数字拼接`
int _getDaysInMonth(int year, int month) {
@override
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }
String get openAppDrawerTooltip => ‘Open navigation menu‘;
//项目里面会有很多翻译,在实际使用过程中,通常是在系统delegeate调用load时,从本地加载数据
static Future<MaterialLocalizations> load(Locale locale) {
return SynchronousFuture<MaterialLocalizations>(const DefaultMaterialLocalizations());
}
//上面的方法可以加工改成如下
static Future<MaterialLocalizations> load(Locale locale) {
final localizations = DefaultMaterialLocalizations();
return SynchronousFuture<MaterialLocalizations>(localizations.loadLocalizationsJsonFromCache());
}
//为系统提供一个delegate,注册locale变化事件,通过delegate.load触发this.load,重新加载翻译文件
static const LocalizationsDelegate<MaterialLocalizations> delegate = _MaterialLocalizationsDelegate();
}
监听locale变更事件,读取本地翻译文件到内存中,具体实现如下,主要提供了Locale的检测,以及加载提供翻译数据的实例类.
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate();
@override
bool isSupported(Locale locale) => locale.languageCode == ‘en‘;
@override
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
@override
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
@override
String toString() => ‘DefaultMaterialLocalizations.delegate(en_US)‘;
}
在启动时,将delegate传递给MaterialApp,MaterialApp在locale信息初始化之后会逐个调用delegate的load方法,将对应local的字符串加载到内存中.
class _WidgetsAppState
...
assert(_debugCheckLocalizations(appLocale));
Widget build(BuildContext context) {
...
child: _MediaQueryFromWindow(
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
),
),
);
}
在locale变更时时如何触发localizationsDelegates
依次执行load
方法的?
class Localizations extends StatefulWidget {
Localizations({
Key key,
@required this.locale,
@required this.delegates,
this.child,
})
final List<LocalizationsDelegate<dynamic>> delegates;
final Widget child;
//获取当前应用设置的locale
static Locale localeOf(BuildContext context, { bool nullOk = false }) {
...
final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
return scope.localizationsState.locale;
}
//获取当前系统设置的localizations的delegate
static List<LocalizationsDelegate<dynamic>> _delegatesOf(BuildContext context) {
final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
return List<LocalizationsDelegate<dynamic>>.from(scope.localizationsState.widget.delegates);
}
//获取localizations实例子类
static T of<T>(BuildContext context, Type type) {
assert(context != null);
assert(type != null);
final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
return scope?.localizationsState?.resourcesFor<T>(type);
}
@override
_LocalizationsState createState() => _LocalizationsState();
...
}
class _LocalizationsState extends State<Localizations> {
final GlobalKey _localizedResourcesScopeKey = GlobalKey();
Map<Type, dynamic> _typeToResources = <Type, dynamic>{};
Locale get locale => _locale;
@override
void initState() {
super.initState();
//在`LocalizationsState`初始化的时候开始加载delegate的load方法
load(widget.locale);
}
//更新变动的localizationDelegate
bool _anyDelegatesShouldReload(Localizations old) {
...
@override
void didUpdateWidget(Localizations old) {
//接上文的initState,获取前的所有delegates,通过`_loadAll`异步加载所有的locale信息
void load(Locale locale) {
final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
Map<Type, dynamic> typeToResources;
final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates)
.then<Map<Type, dynamic>>((Map<Type, dynamic> value) {
return typeToResources = value;
});
if (typeToResources != null) {
_typeToResources = typeToResources;
_locale = locale;
} else {
//通知flutter engine延迟加载第一帧,上面的异步回调,if同步判定,对`typeToResourcesFuture`重新订阅,直到翻译加载完成。(基于这个原理我突然想到了很多优化方案,比如先加载启动所需要的部分翻译,在转圈的时候再加载其余部分翻译)
RendererBinding.instance.deferFirstFrame();
typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
if (mounted) {
setState(() {
_typeToResources = value;
_locale = locale;
});
}
RendererBinding.instance.allowFirstFrame();
});
}
}
T resourcesFor<T>(Type type) {
assert(type != null);
final T resources = _typeToResources[type] as T;
return resources;
}
//这里使用了as强转,所以我们使用的Localizations类需要实现于它,定义文本方向
TextDirection get _textDirection {
final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations] as WidgetsLocalizations;
assert(resources != null);
return resources.textDirection;
}
@override
Widget build(BuildContext context) {
if (_locale == null)
return Container();
return Semantics(
textDirection: _textDirection,
//_LocalizationsScope为InheritedWidget传递当前的state数据和_typeToResources,这样子类就可以通过inheritedXXXOfExtract() 方法获取到数据了
child: _LocalizationsScope(
key: _localizedResourcesScopeKey,
locale: _locale,
localizationsState: this,
typeToResources: _typeToResources,
child: Directionality(
textDirection: _textDirection,
child: widget.child,
),
),
);
}
}
_typeToResources具体实现
在LocalizationsDelegate
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
//1. 保存 Set<Type> types, List<LocalizationsDelegate<dynamic>> delegates
final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending> pendingList;
final Set<Type> types = <Type>{};
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (final LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
//在LocalizationsDelegate<T>Type实现 get type => T;
types.add(delegate.type);
delegates.add(delegate);
}
}
//将types和delegate一一对应,并保证delegate的load方法执行完毕
for (final LocalizationsDelegate<dynamic> delegate in delegates) {
final Future<dynamic> inputValue = delegate.load(locale);
dynamic completedValue;
final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
return completedValue = value;
});
//加这个主要是担心执行太快,没进到pending队列里,漏掉部分数据
if (completedValue != null) { // inputValue was a SynchronousFuture
final Type type = delegate.type;
assert(!output.containsKey(type));
output[type] = completedValue;
} else {
pendingList ??= <_Pending>[];
pendingList.add(_Pending(delegate, futureValue));
}
}
//当所有delegate load的数据加载完毕后同步返回{DelegateType,DelegateInstance}信息
if (pendingList == null)
return SynchronousFuture<Map<Type, dynamic>>(output);
//同步执行每一个feature对象,一次mapping到output字典中.
return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
.then<Map<Type, dynamic>>((List<dynamic> values) {
assert(values.length == pendingList.length);
for (int i = 0; i < values.length; i += 1) {
final Type type = pendingList[i].delegate.type;
assert(!output.containsKey(type));
output[type] = values[i];
}
return output;
});
}
app启动时候通过flutter engine的回调事件获取locale,并根据MaterialApp设置的locale信息和回调方法对系统的locale进行加工和过滤处理得到当前app使用的locale
根视图Localizaitons接受到MaterialApp传递的delegates和_WidgetsApppState
通过上面步骤1获取的locale初始化
在LocalizaitonsState
初始化和变更时进行检测delegates是否更新,并加载delegates,遍历的执行load方法加载本地翻译,同时将 delegate的type和delegate的value以键值对的形式保存在字典中,(这里的value
是 delegate通过load方法返回的对象.type是delegate的属性,而这个对象就是我们的XXXLocalizaitons的实例类)
在LocalizaitonsState
的build(BuildContext context)
方法中,通过内置的_LocalizationsScope
传递LocalizaitonsState
和他对应的{delegateType:delegateValue}
,这样做其实就是为了将数据数据的逻辑封装太Localizations中,方便开发者调用。
它是flutter官方推荐的翻译管理工具,其内部维护了locale
翻译文件的mapping
表,同时也提供了一些常用的格式化翻译工具,省去了大量的步骤,非常完善的轮子拿来即用。
原文:https://www.cnblogs.com/wwoo/p/flutter-localizations-shi-xian-yuan-li.html