本人初结识Flutter,文章内容如有理解错误,欢迎指正。
为了让不同国家的用户都可以使用我们开发的应用,在应用上架之前需要让应用能够支持多种语言,即应用的国际化。
应用的国际化主要涉及语言和地区差异性配置两个方面,它们是应用程序的组成部分之一。在Flutter开发中,实现国际化大都采用的方案是Intl(如熟悉此方案可跳过)
Intl方案
1.添加依赖项
pubspec.yaml添加依赖项flutter_localizations,然后运行一下flutter packages get
dependencies:
flutter:
sdk: flutter
# 添加下面的依赖项
flutter_localizations:
sdk: flutter
intl: ^0.17.0
intl_translation: ^0.17.10+1
2.编辑dart文件
新建app_strings.dart文件
import 'dart:async';
import 'package:intl/intl.dart';
import 'package:flutter/widgets.dart';
class AppStrings {
AppStrings(Locale locale) : _localeName = locale.toString();
final String _localeName;
static Future<AppStrings> load(Locale locale) {
return initializeMessages(locale.toString())
.then((Object _) {
return new AppStrings(locale);
});
}
static AppStrings of(BuildContext context) {
return Localizations.of<AppStrings>(context, AppStrings);
}
String title() {
return Intl.message(
'Localization Demo',
name: 'title',
desc: '应用标题',
locale: _localeName,
);
}
String click() => Intl.message(
'Click',
name: 'click',
desc: '点击',
locale: _localeName,
);
}
3.生成arb文件
进入项目目录,运行intl的命令。
$ flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/app_strings.dart
生成l10n/intl_messages.arb,内容如下:
{
"@@last_modified": "2018-07-15T22:13:19.218221",
"title": "Localization Demo",
"@title": {
"description": "应用标题",
"type": "text",
"placeholders": {}
},
"click": "Click",
"@click": {
"description": "点击",
"type": "text",
"placeholders": {}
},
}
4.新增和修改arb文件
前面生成了l10n/intl_messages.arb,我们可以把它当成模板。复制粘贴一下,同目录下得到intl_en.arb和intl_zh.arb。如intl_zh.arb:
{
"@@last_modified": "2018-07-15T22:13:19.218221",
"title": "国际化示例App",
"@title": {
"description": "应用标题",
"type": "text",
"placeholders": {}
},
"click": "点击",
"@click": {
"description": "点击",
"type": "text",
"placeholders": {}
},
}
5.根据arb生成dart文件
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n \
--no-use-deferred-loading lib/app_strings.dart lib/l10n/intl_*.ar
此时在app_strings.dart中添加对l10n/intl_messages.arb的引用。
6.创建locallization代理
创建localizations_delegate.dart。新建AppLocalizationsDelegate类继承LocalizationsDelegate
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:localization_demo/app_strings.dart';
class AppLocalizationsDelegate extends LocalizationsDelegate<AppStrings> {
@override
Future<AppStrings> load(Locale locale) {
return AppStrings.load(locale);
}
@override
bool isSupported(Locale locale) =>
['zh', 'en'].contains(locale.languageCode); // 支持的类型要包含App中注册的类型
@override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
7.MaterialApp中添加本地代理和语言类型
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
AppLocalizationsDelegate(), // 我们定义的代理
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [ // 支持的语言类型
const Locale('en', 'US'), // English
const Locale('zh', ''),
],
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
注意事项:
代理isSupported方法中的语言类型最好是和App中supportedLocales的一致。
简单介绍了一下Intl方案实现国际化,由于不是本文的重点,有疑问大家可以google,通过上面的介绍使用,我们总结一下Intl方案的缺点:
- 实现步骤过多,繁琐。
- 中间使用命令生成的arb文件需要复制粘贴,工作量大。
- 需要创建arb,dart,代理等一系列文件,每添加一种语言的支持就得修改所有的文件,严重违反开闭原则。
实践了Intl方案后,一直在想能不能像iOS一样直接通过配置几个json文件的方式来实现国际化呢,GetX的发现验证了我的猜想。
getX方案
GetX的使用场景有很多,比如状态管理,路由,监听,国际化等等。
文章的重点来说国际化:
1.添加依赖项
pubspec.yaml添加依赖项flutter_localizations,然后运行一下flutter packages get :
get: ^4.3.8
2.自定义翻译
创建一个翻译类并扩展 Translations :
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
3.在GetMaterialApp中定义语言和翻译
return GetMaterialApp(
translations: Messages(), // 你的翻译
locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译
fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在
);
4.使用翻译
Text('title'.tr);
5.改变语言
var locale = Locale('en', 'US');
Get.updateLocale(locale);
至此,getX已实现国际化,但上面的实现有两个问题:
- 项目中使用的字符串过多,翻译类代码量很大,阅读不友好。
- 使用翻译硬编码严重。
为了解决上面的问题,我们来进一步优化方案,其实对于程序员来说,第一个问题很好解决,不同语言的翻译提取一个单独的文件去搞就可以了,主要来看第二个问题(硬编码问题):
GetX Cli
GetX Cli是一个命令行脚本,它可以做到:
- 创建项目
- 项目工程化
- 生成Model
- 生成page
- 生成view
- 生成controller
- 自定义controller模板
- 生成翻译文件
本文主要介绍其翻译文件的相关内容:
1.安装get_cli
pub global activate get_cli
# or
flutter pub global activate get_cli
1.生成国家化文件
在 assets/locales 目录创建 json 格式的语言文件
zh_CN.json:
{
"buttons": {
"login": "登录",
"sign_in": "注册",
"logout": "注销",
"sign_in_fb": "用 Facebook 登录",
"sign_in_google": "用 Google 登录",
"sign_in_apple": "用 Apple 登录"
}
}
en_US.json:
{
"buttons": {
"login": "Login",
"sign_in": "Sign-in",
"logout": "Logout",
"sign_in_fb": "Sign-in with Facebook",
"sign_in_google": "Sign-in with Google",
"sign_in_apple": "Sign-in with Apple"
}
}
2.运行命令行
get generate locales assets/locales
输出:
abstract class AppTranslation {
static Map<String, Map<String, String>> translations = {
'en_US' : Locales.en_US,
'zh_CN' : Locales.zh_CN,
};
}
abstract class LocaleKeys {
static const buttons_login = 'buttons_login';
static const buttons_sign_in = 'buttons_sign_in';
static const buttons_logout = 'buttons_logout';
static const buttons_sign_in_fb = 'buttons_sign_in_fb';
static const buttons_sign_in_google = 'buttons_sign_in_google';
static const buttons_sign_in_apple = 'buttons_sign_in_apple';
}
abstract class Locales {
static const en_US = {
'buttons_login': 'Login',
'buttons_sign_in': 'Sign-in',
'buttons_logout': 'Logout',
'buttons_sign_in_fb': 'Sign-in with Facebook',
'buttons_sign_in_google': 'Sign-in with Google',
'buttons_sign_in_apple': 'Sign-in with Apple',
};
static const zh_CN = {
'buttons_login': 'Entrar',
'buttons_sign_in': 'Cadastrar-se',
'buttons_logout': 'Sair',
'buttons_sign_in_fb': '用 Facebook 登录',
'buttons_sign_in_google': '用 Google 登录',
'buttons_sign_in_apple': '用 Apple 登录',
};
}
3.在GetMaterialApp中使用
GetMaterialApp(
...
translationsKeys: AppTranslation.translations,
...
)
4.使用翻译
Text(LocaleKeys.buttons_login.tr);
到此,硬编码和代码分离的问题已解决。
源码
getX是如何实现国际化的,为什么不需要设置代理,点击xx.tr查看源码:
extension Trans on String {
String get tr {
// Returns the key if locale is null.
if (Get.locale?.languageCode == null) return this;
/*
是从translations map查找,是一个map嵌套map的结构,外层key为${Get.locale!.languageCode}_${Get.locale!.countryCode}
内层key:this
*/
if (Get.translations.containsKey(
"${Get.locale!.languageCode}_${Get.locale!.countryCode}") &&
Get.translations[
"${Get.locale!.languageCode}_${Get.locale!.countryCode}"]!
.containsKey(this)) {
return Get.translations[
"${Get.locale!.languageCode}_${Get.locale!.countryCode}"]![this]!;
// Checks if there is a callback language in the absence of the specific
// country, and if it contains that key.
} else if (Get.translations.containsKey(Get.locale!.languageCode) &&
Get.translations[Get.locale!.languageCode]!.containsKey(this)) {
return Get.translations[Get.locale!.languageCode]![this]!;
} else if (Get.fallbackLocale != null) {
final fallback = Get.fallbackLocale!;
final key = "${fallback.languageCode}_${fallback.countryCode}";
if (Get.translations.containsKey(key) &&
Get.translations[key]!.containsKey(this)) {
return Get.translations[key]![this]!;
}
if (Get.translations.containsKey(fallback.languageCode) &&
Get.translations[fallback.languageCode]!.containsKey(this)) {
return Get.translations[fallback.languageCode]![this]!;
}
return this;
} else {
return this;
}
}
举个例子来简单说明一下上面代码的逻辑:
{
'zh_CN':{
'buttons_sign_in_fb': '用 Facebook 登录',
}
}
- languageCode_countryCode当key,去查找出:
{
'buttons_sign_in_fb': '用 Facebook 登录',
}
2.buttons_sign_in_fb当key,查找到具体的显示信息,而translations是在GetMaterialApp传进来的,查看get_material_app.dart:
if (locale != null) Get.locale = locale;
if (fallbackLocale != null) Get.fallbackLocale = fallbackLocale;
//Get接口添加了LocalesIntl extension,里面实现了addTranslations
if (translations != null) {
Get.addTranslations(translations!.keys);
} else if (translationsKeys != null) {
Get.addTranslations(translationsKeys!);
}
注意: 单纯使用getX,在GetMaterialApp传入的是translations,而使用getX+getX Cli 传入的是 translationsKeys。
项目实践
方案虽然落地,但在项目开发中,还是会遇到各种问题,比如我们现有的项目,翻译文件是由翻译人员提供的,其格式为.xlsx,那么由.xlsx到json的转换就是一个必备的过程。
贴出一段项目里的数据:
excel和json的自动化转换
pandas
Pandas是一个强大的分析结构化数据的工具集;它的使用基础是Numpy(提供高性能的矩阵运算);用于数据挖掘和数据分析,同时也提供数据清洗功能。
Pandas提供了两大利器:
- DataFrame:是Pandas中的一个表格型的数据结构,包含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型等),DataFrame即有行索引也有列索引,可以被看做是由Series组成的字典。
- Series:是一种类似于一维数组的对象,是由一组数据以及一组与之相关的数据标签(即索引)组成。仅由一组数据也可产生简单的Series对象。
应用
pandas是python的一个package
//pip是python的包管理工具,如果出现pip commond not found,是因为你当前的python环境为python3,命令改为pip3 install pandas
pip install pandas
脚本具体实现分为两部分:
- excel=>json
import pandas as pd
def excel_to_json(filename):
# read_excel读取excel文件,同时设置列标签为'zh', 'en', 'ja', 'TW', 'de', 'es'
df = pd.read_excel(filename, names=['zh', 'en', 'ja', 'TW', 'de', 'es'])
# 取出每个Series转为json格式
df.zh.to_json('jsons/zh.json')
df.en.to_json("jsons/en.json")
df.ja.to_json("jsons/ja.json")
df.TW.to_json("jsons/tw.json")
df.de.to_json("jsons/de.json")
df.es.to_json("jsons/es.json")
- json=>excel
def json_to_excel(filename):
# 取出目录下所有的json格式文件
# json_files = glob.glob("*.json")
jsons = ['jsons/zh.json','jsons/en.json','jsons/ja.json','jsons/tw.json','jsons/de.json','jsons/es.json']
xlsx_file = []
for json_str in jsons:
language_str = json_str.split(".")[0]
# 读取json文件
file = open(json_str)
text = file.read()
text = json.loads(text)
# 转为DataFrame
df = json_normalize(text)
xlsx_str = language_str+'.xlsx'
# .T行列转置
df.T.to_excel(xlsx_str)
xlsx_file.append(xlsx_str)
li =[]
for i in xlsx_file:
# index_col表示哪列当列号,默认是0-rows
li.append(pd.read_excel(i,index_col=0))
writer = pd.ExcelWriter(filename)
# merge只能两两合并
# axis:0 行合并 1列合并
connact = pd.concat(li, axis=1,)
#重新标记列标签
connact.columns=['Chinese','English','Japanese','Traditional','German','Spanish']
connact.to_excel(writer)
writer.save()
#删除临时文件
for i in xlsx_file:
if os.path.exists(i):
os.remove(i)
注意:
- 执行脚本时出现read_excel报错等信息是因为没有安装openpyxl package,使用pip install openpyxl即可。
- 在使用GetX Cli时出现null类型无法转string的问题,在read_excel添加na_filter=False。
本文暂时没有评论,来添加一个吧(●'◡'●)