You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
14 KiB
Markdown

2 years ago
# 32 | 适配国际化,除了多语言我们还需要注意什么?
你好我是陈航。今天我们来聊聊Flutter应用的国际化。
借助于App Store与Google Play我们能够把应用发布到全世界的任何一个应用商店里。应用的潜在使用者可能来自于不同国家、说着不同的语言。如果我们想为全世界的使用者提供统一而标准的体验那么首先就需要让App能够支持多种语言。而这一过程一般被称为“国际化”。
提起国际化你可能会认为这等同于翻译App内所有用户可见的文本。其实这个观点不够精确。**更为准确地描述国际化的工作职责,应该是“涉及语言及地区差异的适配改造过程”。**
比如如果我们要显示金额同样的面值在中国会显示为¥100而在美国则会显示为$100又比如App的引导图在中国我们可能会选用长城作为背景而在美国我们则可能会选择金门大桥作为背景。
因此对一款App做国际化的具体过程除了翻译文案之外还需要将货币单位和背景图等资源也设计成可根据不同地区自适应的变量。这也就意味着我们在设计App架构时需要提前将语言与地区的差异部分独立出来。
其实这也是在Flutter中进行国际化的整体思路即语言差异配置抽取+国际化代码生成。而在语言差异配置抽取的过程中文案、货币单位以及背景图资源的处理其实并没有本质区别。所以在今天的分享中我会以多语言文案为主为你讲述在Flutter中如何实现语言与地区差异的独立化相信在学习完这部分的知识之后对于其他类型的语言差异你也能够轻松搞定国际化了。
## Flutter i18n
在Flutter中国际化的语言和地区的差异性配置是应用程序代码的一部分。如果要在Flutter中实现文本的国际化我们需要执行以下几步
* 首先实现一个LocalizationsDelegate即翻译代理并将所有需要翻译的文案全部声明为它的属性
* 然后,依次为需要支持的语言地区进行手动翻译适配;
* 最后在应用程序MaterialApp初始化时将这个代理类设置为应用程序的翻译回调。
如果我们中途想要新增或者删除某个语系或者文案,都需要修改程序代码。
看到这里你会发现如果我们想要使用官方提供的国际化方案来设计App架构不仅工作量大、繁琐而且极易出错。所以要开始Flutter应用的国际化道路我们不如把官方的解决方案扔到一边直接**从Android Studio中的Flutter i18n插件开始学习**。这个插件在其内部提供了不同语言地区的配置封装能够帮助我们自动地从翻译稿生成Dart代码。
为了安装Flutter i18n插件我们需要打开Android Studio的Preference选项在左边的tab中切换到Plugins选项搜索这个插件点击install即可。安装完成之后再重启Android Studio这个插件就可以使用了。
![](https://static001.geekbang.org/resource/image/59/03/59676dae6d5f710da6a428eb2d0fd603.png)
图1 Flutter i18n插件安装
Flutter i18n依赖flutter\_localizations插件包所以我们还需要在pubspec.yaml文件里声明对它的依赖否则程序会报错
```
dependencies:
flutter_localizations:
sdk: flutter
```
这时我们会发现在res文件夹下多了一个values/strings\_en.arb的文件。
arb文件是JSON格式的配置用来存放文案标识符和文案翻译的键值对。所以我们只要修改了res/values下的arb文件i18n插件就会自动帮我们生成对应的代码。
strings\_en文件则是系统默认的英文资源配置。为了支持中文我们还需要在values目录下再增加一个strings\_zh.arb文件
![](https://static001.geekbang.org/resource/image/d3/64/d3ac46709b5ca24cd193b06def732864.png)
图2 arb文件格式
试着修改一下strings\_zh.arb文件可以看到Flutter i18n插件为我们自动生成了generated/i18n.dart。这个类中不仅以资源标识符属性的方式提供了静态文案的翻译映射对于通过参数来实现动态文案的message\_tip标识符也自动生成了一个同名内联函数
![](https://static001.geekbang.org/resource/image/21/c5/2178c10594f5759c8195b19a7c7e00c5.png)
图3 Flutter i18n插件自动生成代码
我们把strings\_en.arb继续补全提供英文版的文案。需要注意的是i18n.dart是由插件自动生成的每次arb文件有新的变更都会自动更新所以切忌手动编辑这个文件。
接下来,我们**以Flutter官方的工程模板即计数器demo来演示如何在Flutter中实现国际化**。
在下面的代码中我们在应用程序的入口即MaterialApp初始化时为其设置了支持国际化的两个重要参数即localizationsDelegates与supportedLocales。前者为应用的翻译回调而后者则为应用所支持的语言地区属性。
S.delegate是Flutter i18n插件自动生成的类包含了所支持的语言地区属性以及对应的文案翻译映射。理论上通过这个类就可以完全实现应用的国际化但为什么我们在配置应用程序的翻译回调时除了它之外还加入了GlobalMaterialLocalizations.delegate与GlobalWidgetsLocalizations.delegate这两个回调呢
这是因为Flutter提供的Widget其本身已经支持了国际化所以我们没必要再翻译一遍直接用官方的就可以了而这两个类则就是官方所提供的翻译回调。事实上我们刚才在pubspec.yaml文件中声明的flutter\_localizations插件包就是Flutter提供的翻译套装而这两个类就是套装中的著名成员。
在完成了应用程序的国际化配置之后我们就可以在程序中通过S.of(context)直接获取arb文件中翻译的文案了。
不过需要注意的是,**提取翻译文案的代码需要在能获取到翻译上下文的前提下才能生效也就是说只能针对MaterialApp的子Widget生效。**因此在这种配置方式下我们是无法对MaterialApp的title属性进行国际化配置的。不过好在MaterialApp提供了一个回调方法onGenerateTitle来提供翻译上下文因此我们可以通过它实现title文案的国际化
```
//应用程序入口
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: const [
S.delegate,//应用程序的翻译回调
GlobalMaterialLocalizations.delegate,//Material组件的翻译回调
GlobalWidgetsLocalizations.delegate,//普通Widget的翻译回调
],
supportedLocales: S.delegate.supportedLocales,//支持语系
//title的国际化回调
onGenerateTitle: (context){
return S.of(context).app_title;
},
home: MyHomePage(),
);
}
}
```
应用的主界面文案的国际化则相对简单得多了直接通过S.of(context)方法就可以拿到arb声明的翻译文案了
```
Widget build(BuildContext context) {
return Scaffold(
//获取appBar title的翻译文案
appBar: AppBar(
title: Text(S.of(context).main_title),
),
body: Center(
//传入_counter参数获取计数器动态文案
child: Text(
S.of(context).message_tip(_counter.toString())
)
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,//点击回调
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
```
在Android手机上分别切换英文和中文系统可以看到计数器应用已经正确地处理了多语言的情况。
![](https://static001.geekbang.org/resource/image/5f/ab/5f352551211ae6e063b8561ce02254ab.gif)
图4 计数器示例Android英文系统
![](https://static001.geekbang.org/resource/image/f3/e0/f3b249143095cf4210183f995f71f6e0.gif)
图5 计数器示例Android中文系统
由于iOS应用程序有一套自建的语言环境管理机制默认是英文。为了让iOS应用正确地支持国际化我们还需要在原生的iOS工程中进行额外的配置。我们打开iOS原生工程切换到工程面板。在Localization这一项配置中我们看到iOS工程已经默认支持了英文所以还需要点击“+”按钮,新增中文:
![](https://static001.geekbang.org/resource/image/06/14/0688c4a91a4606eec5ca3d14f170cf14.png)
图6 iOS工程中文配置
完成iOS的工程配置后我们回到Flutter工程选择iOS手机运行程序。可以看到计数器的iOS版本也可以正确地支持国际化了。
![](https://static001.geekbang.org/resource/image/6c/11/6cbacb27d1b8009b982e6a2468f79111.gif)
图7 计数器示例iOS英文系统
![](https://static001.geekbang.org/resource/image/83/d8/83c948476dabc20c23cf06d6220721d8.gif)
图8 计数器示例iOS中文系统
## 原生工程配置
上面介绍的国际化方案其实都是在Flutter应用内实现的。而在Flutter框架运行之前我们是无法访问这些国际化文案的。
Flutter需要原生环境才能运行但有些文案比如应用的名称我们需要在Flutter框架运行之前就为它提供多个语言版本比如英文版本为computer中文版本为计数器这时就需要在对应的原生工程中完成相应的国际化配置了。
**我们先去Android工程下进行应用名称的配置。**
首先在Android工程中应用名称是在AndroidManifest.xml文件中application的android:label属性声明的所以我们需要将其修改为字符串资源中的一个引用让其能够根据语言地区自动选择合适的文案
```
<manifest ... >
...
<!-- 设置应用名称 -->
<application
...
android:label="@string/title"
...
>
</application>
</manifest>
```
然后我们还需要在android/app/src/main/res文件夹中为要支持的语言创建字符串strings.xml文件。这里由于默认文件是英文的所以我们只需要为中文创建一个文件即可。字符串资源的文件目录结构如下图所示
![](https://static001.geekbang.org/resource/image/95/ff/959ec47a7916cc6f1b44817bd5ddaaff.png)
图9 strings.xml文件目录结构
values与values-zh文件夹下的strings.xml内容如下所示
```
<!--英文(默认)字符串资源-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">Computer</string>
</resources>
<!--中文字符串资源-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">计数器</string>
</resources>
```
完成Android应用标题的工程配置后我们回到Flutter工程选择Android手机运行程序可以看到计数器的Android应用标题也可以正确地支持国际化了。
接下来,我们再看**iOS工程下如何实现应用名称的配置**。
与Android工程类似iOS工程中的应用名称是在Info.list文件的Bundle name属性声明的所以我们也需要将其修改为字符串资源中的一个引用使其能够根据语言地区自动选择文案
![](https://static001.geekbang.org/resource/image/1d/eb/1d4a885a83c2297d1c8d1fe6118e46eb.png)
图10 iOS工程应用名称配置
由于应用名称默认是不可配置的,所以工程并没有提供英文或者中文的可配置项,这些都需要通过新建与字符串引用对应的资源文件去搞定的。
我们右键单击Runner文件夹然后选择New File来添加名为InfoPlist.strings的字符串资源文件并在工程面板的最右侧文件检查器中的Localization选项中添加英文和中文两种语言。InfoPlist.strings的英文版和中文版内容如下所示
```
//英文版
"CFBundleName" = "Computer";
//中文版
"CFBundleName" = "计数器";
```
至此我们也完成了iOS应用标题的工程配置。我们回到Flutter工程选择iOS手机运行程序发现计数器的iOS应用标题也支持国际化了。
## 总结
好了,今天的分享就到这里。我们来总结下核心知识点吧。
在今天的分享中我与你介绍了Flutter应用国际化的解决方案即在代码中实现一个LocalizationsDelegate在这个类中将所有需要翻译的文案全部声明为它的属性然后依次进行手动翻译适配最后将这个代理类设置为应用程序的翻译回调。
而为了简化手动翻译到代码转换的过程我们通常会使用多个arb文件存储文案在不同语言地区的映射关系并使用Flutter i18n插件来实现代码的自动转换。
国际化的核心就是语言差异配置抽取。在原生Android和iOS系统中进行国际化适配我们只需为需要国际化的资源比如字符串文本、图片、布局等提供不同的文件夹目录就可以在应用层代码访问国际化资源时自动根据语言地区进行适配。
Flutter的国际化能力就相对原始很多不同语言和地区的国际化资源既没有存放在单独的xml或者JSON上也没有存放在不同的语言和地区文件夹中。幸好有Flutter i18n插件的帮助否则为一个应用提供国际化的支持将会是件极其繁琐的事情。
我把今天分享所涉及到的知识点打包到了[GitHub](https://github.com/cyndibaby905/32_i18n_demo)中,你可以下载下来,反复运行几次,加深理解与记忆。
## 思考题
最后,我给你留下一道思考题吧。
在Flutter中如何实现图片类资源的国际化呢
欢迎你在评论区给我留言分享你的观点,我会在下一篇文章中等待你!感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。