【Flutter 实战】国际化及App 内切换语言功能
老孟导读:本文介绍如何实现国际化以及实现 App 内切换语言功能。
使App支持国际化
当应用程序支持不同语言的时候,就需要对应用程序进行国际化,当然国际化不仅仅指文字,也可以是布局、图片等。Flutter 已经提供了组件来实现国际化,下面是实现国际化的步骤:
在 MaterialApp.supportedLocales 中添加支持的语言:
MaterialApp(
title: 'Flutter IntlApp',
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
...
)
比如上面的代码中,只支持英文和中文。
根据不同的语言获取不同的资源:
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of(context, AppLocalizations);
}
static Map<String, Map<String, String>> _localizedValues = {
'en': {
'title': 'Hello World',
},
'zh': {
'title': '你好',
},
};
String get title {
return _localizedValues[locale.languageCode]['title'];
}
}
这里只设置了一个文案,实际项目中建议不同语言存放在不同的文件中。
设置用于加载语言的 Delegate:
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);
@override
Future load(Locale locale) {
return SynchronousFuture(AppLocalizations(locale));
}
@override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
将此 Delegate 添加到 MaterialApp:
MaterialApp(
title: 'Flutter IntlApp',
localizationsDelegates: [
AppLocalizationsDelegate(),
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
)
使用:
Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('国际化:${AppLocalizations.of(context).title}'),
],
),
],
),
)
注意:Scaffold 不要添加 AppBar 数据,否则报错,具体原因下面会给出。
重点是这句:
AppLocalizations.of(context).title
此时,App会根据系统语言作为当前的语言。
系统是如何实现国际化的?
Flutter 的国际化是通过 Localizations 组件实现,上面没有用到 Localizations 组件啊,是的,App 中并没有直接使用,因为 MaterialApp 内部封装了此组件,通过 DevTools 可以查看:
Localizations 组件用于加载本地化资源、获取系统语言,Localizations 组件内部使用了 InheritedWidget 组件,当其属性即 Locale 发生变化时,其子组件将重建。
上面定义的 AppLocalizations 类内部的 of 方法:
static AppLocalizations of(BuildContext context) {
return Localizations.of(context, AppLocalizations);
}
Localizations.of 源代码:
这段代码是获取 Type 类型(App 传入的类型为 AppLocalizations)的资源,看一下 resourcesFor 的源代码:
关键在 _typeToResources :
_typeToResources 是一个 Map 类型, _typeToResources 初始化数据的:
widget.delegates 的类型是:
/// This list collectively defines the localized resources objects that can
/// be retrieved with [Localizations.of].
final Listdynamic>> delegates;
是否还记得 MaterialApp localizationsDelegates 属性,此 delegates 就是在 MaterialApp 中设置的值,到此我们理解了
Localizations.of(context, AppLocalizations)
这句是如何获取 AppLocalizations 实例的,当然中间还有一些其他的判断,具体可自行查看源代码。
添加系统国际化支持
前面说到 Scaffold 不要添加 AppBar 数据,否则报错,填上看其异常信息:
Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('国际化:${AppLocalizations.of(context).title}'),
],
),
)
上面的异常效果不明显,看控制台的异常信息:
提示 MaterialLocalizations 找不到,MaterialLocalizations 是什么呢?其实它是系统组件的国际化资源,所以修复以上异常的方法是引入 MaterialLocalizations,在pubspec.yaml文件中添加包依赖:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
MaterialApp 修改如下:
MaterialApp(
title: 'Flutter IntlApp',
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
)
flutter_localizations 99%的概率会引入,但我们要知道这个并不是必须的。
添加应用程序 title 国际化
按照上面的方式国际化:
MaterialApp(
title: '${AppLocalizations.of(context).title}',
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
)
直接异常了,因为此时使用的 context 是从 build 方法中传入的,而 Localizations 从 context 开始向上查找,国际化资源是在 MaterialApp 组件中的,所以无法找到 AppLocalizations。
修改方式是使用 onGenerateTitle:
MaterialApp(
onGenerateTitle: (context) {
return AppLocalizations.of(context).title;
},
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
)
系统语言为英文:
系统语言为中文:
此方法只在 Android 上有效,iOS 上没有效果。
设置默认语言
如果 App 仅支持英文和中文,其他系统的语言也默认使用中文:
MaterialApp(
onGenerateTitle: (context) {
return AppLocalizations.of(context).title;
},
localeResolutionCallback:
(Locale locale, Iterable supportedLocales) {
var result = supportedLocales
.where((element) => element.languageCode == locale.languageCode);
if (result.isNotEmpty) {
return locale;
}
return Locale('zh');
},
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
)
localeResolutionCallback 回调中 locale 参数表示当前系统语言,supportedLocales 表示支持的语言,即 MaterialApp.supportedLocales 设置的值。
通过这两个参数判断当然系统语言是否在支持的范围内,如果支持则返回系统语言,不支持则返回默认语言。
使用此方法也可以实现所有英语区域的国家使用英语,而国内、香港、澳门等使用中文。
监听系统语言切换
当更改系统语言设置时,Localizations 组件将会重新 build,而用户就看到了语言的切换,这个过程是系统完成的,代码并不需要主动去监听语言切换,但如果想监听语言切换可以通过 localeResolutionCallback 或 localeListResolutionCallback 回调来监听。通常情况下,使用localeListResolutionCallback,localeListResolutionCallback有两个参数:List
supportedLocales为当前应用支持的locale列表,是在MaterialApp中设置supportedLocales的值。localeListResolutionCallback返回一个Locale,此Locale表示最终使用的Locale,一般情况下在App不支持当前语言时返回一个默认值。localeListResolutionCallback的用法如下:
MaterialApp(
supportedLocales: [
Locale('zh'),
Locale('en'),
],
localeListResolutionCallback: (List locales, Iterable supportLocales){
print('locales:$locales');
print('supportLocales:$supportLocales');
},
)
输出如下:
locales:[zh_Hans_CN, ja_JP, en_GB]
supportLocales:[zh, en]
也可以通过如下代码获取当前系统语言:
Locale myLocale = Localizations.localeOf(context);
应用程序内切换语言
应用程序实现切换语言功能只需将 MaterialApp 中 locale 属性作为一个变量,切换不同的 Locale 即可达到切换语言的目的。代码如下:
class IntlApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MyApp();
}
}
class MyApp extends StatefulWidget {
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
static _AppSetting setting = _AppSetting();
@override
void initState() {
super.initState();
setting.changeLocale = (Locale locale) {
setState(() {
setting._locale = locale;
});
};
}
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (context) {
return AppLocalizations.of(context).title;
},
localeResolutionCallback:
(Locale locale, Iterable supportedLocales) {
var result = supportedLocales
.where((element) => element.languageCode == locale.languageCode);
if (result.isNotEmpty) {
return locale;
}
return Locale('zh');
},
locale: setting._locale,
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh'),
const Locale('en'),
],
home: _HomePage(),
);
}
}
_HomePage 代码:
class _HomePage extends StatefulWidget {
@override
__HomePageState createState() => __HomePageState();
}
class __HomePageState extends State<_HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${AppLocalizations.of(context).title}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text('中文'),
onPressed: () {
MyAppState.setting.changeLocale(Locale('zh'));
},
),
RaisedButton(
child: Text('英文'),
onPressed: () {
MyAppState.setting.changeLocale(Locale('en'));
},
),
],
),
],
),
);
}
}
_AppSetting 代码:
class _AppSetting {
_AppSetting();
Null Function(Locale locale) changeLocale;
Locale _locale;
}