开发一个App,如果我们的App需要面向不同的语种(比如中文、英文、繁体等),那么我们需要对齐进行国际化开发。
国际化的英文称呼:internationalization
(简称为i18n
,取前后两个字母,18表示中间省略字母的个数)
App国际化开发主要包括:文本国际化(包括文本的顺序),Widget显示的国际化:
Flutter给我们提供的Widget默认情况下就是支持国际化,但是在没有进行特别的设置之前,它们无论在什么环境都是以英文的方式显示的。
如果想要添加其他语言,你的应用必须指定额外的 MaterialApp 属性并且添加一个单独的 package,叫做 flutter_localizations
。
想要使用 flutter_localizations 的话,我们需要在 pubspec.yaml
文件中添加它作为依赖:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
在localizationsDelegates中指定哪些Widget需要进行国际化
supportedLocales指定要支持哪些国际化
MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate, // 指定本地化的字符串和一些其他的值
GlobalCupertinoLocalizations.delegate, // 对应的Cupertino风格
GlobalWidgetsLocalizations.delegate // 指定默认的文本排列方向, 由左到右或由右到左
],
supportedLocales: [
Locale("en"),
Locale("zh")
],
)
注意:如果要指定语言代码、文字代码和国家代码,可以进行如下指定方式:
// Full Chinese support for CN, TW, and HK
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'
],
设置完成后,我们在Android上将语言切换为中文,查看结果:
但是对于iOS,将语言切换为中文,依然显示是英文的Widget
修改iOS的info.plist文件配置:
App中除了有默认的Widget,我们也希望对自己的文本进行国际化,如何做到呢?
该类用于定义我们需要进行本地化的字符串等信息:
import 'package:flutter/material.dart';
class HYLocalizations {
final Locale locale;
HYLocalizations(this.locale);
static Map<String, Map<String, String>> _localizedValues = {
"en": {
"title": "home",
"greet": "hello~",
"picktime": "Pick a Time"
},
"zh": {
"title": "首页",
"greet": "你好~",
"picktime": "选择一个时间"
}
};
String get title {
return _localizedValues[locale.languageCode]["title"];
}
String get greet {
return _localizedValues[locale.languageCode]["greet"];
}
String get pickTime {
return _localizedValues[locale.languageCode]["picktime"];
}
}
上面的类定义好后,我们在什么位置或者说如何对它进行初始化呢?
HYLocalizationsDelegate需要继承自LocalizationsDelegate,并且有三个方法必须重写:
Future(SynchronousFuture)
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:i18n_demo/i18n/localizations.dart';
class HYLocalizationsDelegate extends LocalizationsDelegate<HYLocalizations> {
@override
bool isSupported(Locale locale) {
return ["en", "zh"].contains(locale.languageCode);
}
@override
bool shouldReload(LocalizationsDelegate<HYLocalizations> old) {
return false;
}
@override
Future<HYLocalizations> load(Locale locale) {
return SynchronousFuture(HYLocalizations(locale));
}
static HYLocalizationsDelegate delegate = HYLocalizationsDelegate();
}
接着我们可以在代码中使用HYLocalization类。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Localizations.of(context, HYLocalizations).title),
),
body: Center(
child: Column(
children: <Widget>[
Text(Localizations.of(context, HYLocalizations).greet),
RaisedButton(
child: Text(Localizations.of(context, HYLocalizations).pickTime),
onPressed: () {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2019),
lastDate: DateTime(2022)
).then((pickTime) {
});
},
)
],
),
),
);
}
当然,我们可以对Localizations.of(context, HYLocalizations)进行一个优化
class HYLocalizations {
static HYLocalizations of(BuildContext context) {
return Localizations.of(context, HYLocalizations);
}
}
接下来我们就可以通过下面的方式来使用了(其它地方也是一样):
appBar: AppBar(
title: Text(HYLocalizations.of(context).title),
)
假如我们的数据是异步加载的,比如来自Json文件或者服务器,应该如何处理呢?
这里我们可以修改HYLocalizations的数据加载:
static Map<String, Map<String, String>> _localizedValues = {};
Future<bool> loadJson() async {
// 1.加载json文件
String jsonString = await rootBundle.loadString("assets/json/i18n.json");
// 2.转成map类型
final Map<String, dynamic> map = json.decode(jsonString);
// 3.注意:这里是将Map转成Map>类型
_localizedValues = map.map((key, value) {
return MapEntry(key, value.cast<String, String>());
});
return true;
}
在HYLocalizationsDelegate中使用异步进行加载:
@override
Future<HYLocalizations> load(Locale locale) async {
final localization = HYLocalizations(locale);
await localization.loadJson();
return localization;
}
目前我们已经可以通过加载对应的json文件来进行本地化了。
但是还有另外一个问题,我们在进行国际化的过程中,下面的代码依然需要根据json文件手动编写
:
String get title {
return _localizedValues[locale.languageCode]["title"];
}
String get greet {
return _localizedValues[locale.languageCode]["greet"];
}
String get pickTime {
return _localizedValues[locale.languageCode]["picktime"];
}
有没有一种更好的方式,让我们可以快速在本地化文件-dart代码文件直接来转换呢?答案就是arb文件
官方文档推荐可以使用intl package来进行arb和dart文件之间的转换(通过终端指令)
需要在在pubspec.yaml中添加其相关的依赖,具体步骤这里不再详细给出,可以参考官方文档
在之前有一个比较好用的Android Studio的插件:Flutter i18n
目前我们可以使用另外一个插件:Flutter Intl
我们这里以Android Studio为例,讲解其使用过程:
选择工具栏Tools - Flutter Intl - Initialize for the Project
完成上面的操作之后会自动生成如下文件目录:
在localizationsDelegates中配置生成的class,名字是S
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
HYLocalizationsDelegate.delegate,
S.delegate
],
supportedLocales: S.delegate.supportedLocales,
因为我们目前还没有对应的本地化字符串,所以需要在intl_en.arb文件中编写:
{
"title": "home",
"greet": "hello~",
"picktime": "Pick a time"
}
在代码中使用即可
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).title),
),
body: Center(
child: Column(
children: <Widget>[
Text(S.of(context).greet),
RaisedButton(
child: Text(S.of(context).picktime),
onPressed: () {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2019),
lastDate: DateTime(2022)
).then((pickTime) {
});
},
)
],
),
),
);
}
如果希望添加中文支持:add local
我们会发现,会生成对应的intl_zh.arb和messages_zh.dart文件
编写intl_zh.arb文件:
{
"title": "首页",
"greet": "您好~",
"picktime": "选择一个时间"
}
查看界面,会根据当前语言显示对应的语言文本
如果我们希望在使用本地化的过程中传递一些参数:
修改对应的arb文件:
{
"title": "home",
"greet": "hello~",
"picktime": "Pick a time",
"sayHello": "hello {name}"
}
在使用时,传入对应的参数即可:
Text(S.of(context).sayHello("李银河")),
arb还有更多的语法,大家可以在之后慢慢学习和发掘~