根据资料选择了大众的两种方式进行调研和对比,主要是 Map方式和intl方式。
Map方式
- yaml添加本地化依赖
flutter_localizations:
sdk: flutter
- 定义资源包:
import 'package:flutter/material.dart';
class DemoLocalizations {
DemoLocalizations(this.locale);
final Locale locale;
static DemoLocalizations of(BuildContext context) {
return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
}
static Map<String, Map<String, String>> _localizedValues = {
'en': {
'title': 'Hello World',
},
'es': {
'title': 'Hola Mundo',
},
'zh': {
'title': '你好呀',
},
};
String get title {
return _localizedValues[locale.languageCode]['title'];
}
}
- 创建代理:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:international_demo/localization_src.dart';
class DemoLocalizationsDelegate
extends LocalizationsDelegate<DemoLocalizations> {
const DemoLocalizationsDelegate();
@override
bool isSupported(Locale locale) =>
['en', 'es', 'zh'].contains(locale.languageCode);
@override
Future<DemoLocalizations> load(Locale locale) {
return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale));
}
@override
bool shouldReload(DemoLocalizationsDelegate old) => false;
}
- 使用代理:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
DemoLocalizationsDelegate(),// 这是我们新建的代理
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'), // English
const Locale('he', 'IL'), // Hebrew
const Locale('zh', ''), // 新添中文,后面的countryCode暂时不指定
],
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
- 获取字符串:
DemoLocalizations localizations = DemoLocalizations.of(context);
DemoLocalizations.of(context).title
优点:定义简单,使用简单。
缺点:翻译协作不是太友好,语言包没有单独隔离。
intl
- yaml添加本地化依赖
flutter_localizations:
sdk: flutter
- 安装Flutter Intl插件
Flutter Intl
-
使用插件初始化项目和添加语言
初始化
- 定义翻译字段以及生成对应的文件
l10n/intl_en.arb intl_pt_BR.arb intl_zh_CN.arb:语言包定义
generated/intl/messages_en.dart,messages_pt_BR.dart,messages_zh_CN.dart:具体的语言包实现类
generated/intl/messages_all.dart: 负责分发语言包的具体实现类
generated/l10n.dart:生成的语言代理类,负责load资源包和使用字符串
- 使用代理:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
//...
localizationsDelegates: [
S.delegate,// 这是我们的代理
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
//...
);
}
}
- 获取字符串:
S.of(context).cacheClean
S.current.cacheClean
优点:语言包管理独立清晰,方便协作,使用简介,有插件配合简化使用。
缺点:文件结构稍显复杂。
对比 | intl | Map |
---|---|---|
复杂度 | 稍微难一点,但是有插件协作 | 低 |
协作性 | 友好 | 不友好 |
容错率 | 因为语言包和调用分开,容错高 | 因为语言包和调用分开,容错低 |
出于翻译人员,开发人员协作方面考虑,最终采用intl方式
遇到的问题:
多package设置localizationsDelegates只有第一个生效
在设置localizationsDelegates
的时候,只能加载第一个顺序配置的语言代理,后续配置的代理将会失效。这个多模块各自管理各自的语言包是有很大的阻碍的。
问题点深入挖掘:
l10n.dart:
static Future<S> load(Locale locale) {
final name = (locale.countryCode?.isEmpty ?? false) ? locale.languageCode : locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
S.current = S();
return S.current;
});
}
//messages_all.dart:
/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String localeName) async {
var availableLocale = Intl.verifiedLocale(
localeName,
(locale) => _deferredLibraries[locale] != null,
onFailure: (_) => null);
if (availableLocale == null) {
return new Future.value(false);
}
var lib = _deferredLibraries[availableLocale];
await (lib == null ? new Future.value(false) : lib());
initializeInternalMessageLookup(() => new CompositeMessageLookup());
//这里 messageLookup:CompositeMessageLookup
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
return new Future.value(true);
}
message_lookup_by_library.dart:
/// If we do not already have a locale for [localeName] then
/// [findLocale] will be called and the result stored as the lookup
/// mechanism for that locale.
void addLocale(String localeName, Function findLocale) {
//
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;
}
}
}
/// Return true if we have a message lookup for [localeName].
bool localeExists(localeName) => availableMessages.containsKey(localeName);
最终是调用实现的MessageLookup里面的addLocale函数有判断,如果这个语言已经添加过,那么就不会添加了,就造成了localizationsDelegates
配置只有第一个生效的现象。
解决方式
我们只需要自己实现一个MessageLookup
维护多模块的语言包映射关系,然后将这个MessageLookup
替换掉原来的CompositeMessageLookup
即可。具体实现也有人实现了出来,参考:multiple_localization
class MultipleLocalizations {
static _MultipleLocalizationLookup? _lookup;
static void _init() {
assert(intl_private.messageLookup is intl_private.UninitializedLocaleData);
_lookup = _MultipleLocalizationLookup();
intl_private.initializeInternalMessageLookup(() => _lookup);
}
/// Load messages for localization and create localization instance.
///
/// Use [setDefaultLocale] to set loaded locale as [Intl.defaultLocale].
static Future<T> load<T>(InitializeMessages initializeMessages, Locale locale,
FutureOr<T> Function(String locale) builder,
{bool setDefaultLocale = false}) {
if (_lookup == null) _init();
final name = locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
if (setDefaultLocale) {
Intl.defaultLocale = localeName;
}
return builder(localeName);
});
}
}
class _MultipleLocalizationLookup implements intl_private.MessageLookup {
final Map<Function, CompositeMessageLookup> _lookups = {};
@override
void addLocale(String localeName, Function findLocale) {
final lookup = _lookups.putIfAbsent(
findLocale,
() => CompositeMessageLookup(),
);
lookup.addLocale(localeName, findLocale);
}
@override
String? lookupMessage(String? messageStr, String? locale, String? name,
List<Object>? args, String? meaning,
{MessageIfAbsent? ifAbsent}) {
for (final lookup in _lookups.values) {
var isAbsent = false;
final res = lookup.lookupMessage(messageStr, locale, name, args, meaning,
ifAbsent: (s, a) {
isAbsent = true;
return '';
});
if (!isAbsent) return res;
}
return ifAbsent == null ? messageStr : ifAbsent(messageStr, args);
}
}
将生成的l10n.dart的load函数更改即可:
static Future<S> load(Locale locale) {
// final name = (locale.countryCode?.isEmpty ?? false) ? locale.languageCode : locale.toString();
// final localeName = Intl.canonicalizedLocale(name);
// return initializeMessages(localeName).then((_) {
// Intl.defaultLocale = localeName;
// S.current = S();
//
// return S.current;
// });
return MultipleLocalizations.load(initializeMessages, locale, (String l) {
S.current = S();
return S.current;
}, setDefaultLocale: true);
}
和native保持一致的语言
主要使用native维护语言名称,flutter通过MethodChannel获取即可
flutter切换语言
在runApp
里面添加监听,然后setstate即可。
void main() async{
//从native获取语言
String lang = await ApplicationPlugin.currentLocal();
runApp(FocoApp(init, appStyle, lang));
}
class FocoApp extends StatefulWidget {
String lang;
App( String lang) {
this.lang = lang;
}
// This widget is the root of your application.
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
@override
void initState() {
super.initState();
EventManager.shared().on(Event.langChangeEvent, this, (params) async {
/// 更新当前语言环境 (启动时执行),从native层获取语言
widget.lang = await ApplicationPlugin.currentLocal();
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
super.dispose();
EventManager.shared().off(Event.langChangeEvent, this);
}
@override
Widget build(BuildContext context) {
List<LocalizationsDelegate> localizationsDelegates = [];
//package 的delegate
for (LocalizationsDelegate delegate in _localizationsDelegates) {
localizationsDelegates.add(delegate);
}
localizationsDelegates.add(S.delegate);
localizationsDelegates.add(GlobalMaterialLocalizations.delegate);
localizationsDelegates.add(GlobalWidgetsLocalizations.delegate);
localizationsDelegates.add(GlobalCupertinoLocalizations.delegate);
List<Locale> supportedLocales = S.delegate.supportedLocales;
return MaterialApp(
//..
locale: Locale(widget.lang),
localizationsDelegates: localizationsDelegates,
supportedLocales: supportedLocales,
home: home,
);
}
}
参考链接:
国际化Flutter App
multiple_localization
Localization for Dart package
Flutter 基于intl的国际化多语言
Flutter International 国际化,Localization 本地化, 使用字符串Map
网友评论