# 国际化多语言
在学习本章前,建议先了解一下什么叫国际化、多语言。
平台针对PC、移动端均提供了多语言的开发框架能力。
多语言的基础条件是词条,词条是一个key-value组合,格式如:common.button.ok.label = OK.实际应用中,开发只需要在显示端引用词条key,系统则会根据语种环境自动显示对应语言。
# PC端国际化
# 定义国际化词条
国际化依然要遵守插件化开发规则,插件国际化配置文件位于“cfgHome/plugin/插件id/i18n”目录下,按照JDK国际化文件的配置规则命名。
注意:词条key必须以插件id开始。 默认插件国际化资源配置只能在Java端使用,前端javascript无法使用,如果要开放给前端,可以在插件的i18n文件夹根下创建名为“export_to_js.xml”注册js可以使用的词条key。
PC端标准国际化文件结构:
WEB-INF
cfgHome
plugin
taskmanage
i18n
taskmanage_en.properties
taskmanage_zh_CN.properties
taskmanage_zh_TW.properties
export_to_js.xml
PC端国际化文件编写规范: properties中的资源必须经过编码,不允许出现中文、日文、法文等字符,而必须是ASCII码。 注意:词条key必须以插件id开始。
# taskmanage_zh_CN.properties
taskmanage.create.label = \u521B\u5EFA
taskmanage.create.label1 = \u521B\u5EFA{0}
taskmanage.create.label2 = {0}\u521B\u5EFA{1}
# taskmanage_zh_TW.properties
taskmanage.create.label = \u5275\u5EFA
taskmanage.create.label1 = \u5275\u5EFA{0}
taskmanage.create.label2 = {0}\u5275\u5EFA{1}
# taskmanage_en.properties
taskmanage.create.label = Create
taskmanage.create.label1 = Create {0}
taskmanage.create.label2 = {0} Create {1}
# Java调用国际化
Java端使用平台的ResourceUtil工具类获取国际化内容。
String value = ResourceUtil.getString("taskmanage.create.label");
//一个占位符
String value1 = ResourceUtil.getString("taskmanage.create.label1", "admin");
//两个占位符
String value2 = ResourceUtil.getString("taskmanage.create.label2", "上帝", "人");
# 前端调用国际化
JSP前端调用方式:
<%-- 引入common_header --%>
<%@ include file="/WEB-INF/jsp/common/common_header.jsp"%>
<tr>
<td>ctp:i18n函数</td>
<td>${ctp:i18n('common.button.add.label')}</td>
</tr>
<tr>
<td>ctp:i18n_1函数,一个参数,变量参数</td>
<td>${ctp:i18n_1('common.charactor.limit.label',path)}</td>
</tr>
<tr>
<td>ctp:i18n_1函数,一个参数,数值参数</td>
<td>${ctp:i18n_1('common.charactor.limit.label',10)}</td>
</tr>
<tr>
<td>ctp:i18n_1函数,一个参数,字符串参数</td>
<td>${ctp:i18n_1('common.charactor.limit.label','测试')}</td>
</tr>
<tr>
<td>ctp:i18n_2函数,两个参数</td>
<td>${ctp:i18n_2('common.charactor.limit.label','测试',3)}</td>
</tr>
<tr>
<td>ctp:i18n_3函数,三个参数</td>
<td>${ctp:i18n_3('common.charactor.limit.label','测试',3,3)}</td>
</tr>
<tr>
<td>ctp:i18n_4函数,四个参数</td>
<td>${ctp:i18n_4('common.charactor.limit.label','测试',3,3,3)}</td>
</tr>
<tr>
<td>ctp:i18n_5函数,五个参数</td>
<td>${ctp:i18n_5('common.charactor.limit.label','测试',3,3,3,3)}</td>
</tr>
Javascript中调用国际化:
在javascript文件中默认情况下无法获取国际化词条。 如果要获取国际化词条则需要在/cfgHome/plugin/插件ID/i18n目录下新增export_to_js.xml文件,然后在文件里注册需要被js引用的国际化key。
<?xml version="1.0" encoding="utf-8"?>
<export>
<resKey>taskmanage.create.label</resKey>
<resKey>taskmanage.create.label1</resKey>
<resKey>taskmanage.create.label2</resKey>
</export>
在完成export_to_js.xml的前提下,前端JSP引入common_footer.jsp文件,再通过平台封装好的函数调用国际化内容,示例如下:
前端javascript中:
<%-- 注意在JSP中引入common_footer --%>
<%@ include file="/WEB-INF/jsp/common/common_footer.jsp"%>
<script type="text/javascript">
var value = $.i18n('taskmanage.create.label');
var value1 = $.i18n('taskmanage.create.label1','参数1');
var value2 = $.i18n('taskmanage.create.label2','参数1','参数2');
</script>
# 移动端国际化
# 定义国际化词条
移动端与PC端的国际化词条是分别开发维护的,主要原因是PC国际化词条太过庞大,移动端词条按需使用,需要本地化存储。
mplus-front
src
apps
v5
taskmanage
i18n
taskmanage_en.properties
taskmanage_en.properties
taskmanage_zh_TW.properties
代码存放于移动端工程,各模块下,如:mplus-front\src\apps\v5\addressbook\i18n\Addressbook_en.properties
移动端开发态的国际化是properties文件,而开发在生产部署之后看到的国际化是js格式,这里编译工程做了一个简化操作:
开发态(源码态)下properties文件更利于国际化维护
运行态(生产系统上),html只能引用javascript,所以每次编译都基于properties文件同步一份javascript文件
本地开发如何做到properties转javascript?可以参考M3培训手册中的工具s3js来实现编码从开发态向运行态的转换
# HTML调用国际化
<!-- 默认引入zh_CN的国际化,以加速页面响应效率 -->
<script src="${data:dependencies.attendance}/i18n/attendance_zh_CN.js${data:buildversion}"></script>
<script src="${data:dependencies.commons}/i18n/Commons_zh_CN.js${data:buildversion}"></script>
<script src="${data:dependencies.cmp}/js/cmp-i18n.js${data:buildversion}"></script>
<script>
<!-- 异步调用i18n组件,地址指向本模块的i18n文件夹,这一步组件会自动判断国际化环境加载对应文件 -->
cmp.i18n.init("${data:dependencies.attendance}/i18n/","attendance",function(){
//初始化本页面titile国际化标准写法
document.title = cmp.i18n("Attendance.label.atWork");
});
</script>
<!-- 前端html标签调用国际化方法,使用<i18n>自定义标签来实现:-->
<div ><span><i18n key="Attendance.label.records"></i18n></span></div>
<!-- 前端javascript调用国际化方法:-->
<script type="text/javascript">
//普通写法
var i18n = cmp.i18n("Attendance.label.attendance");
//带占位符的写法
var i18n1 = cmp.i18n("Attendance.label.authAttendTimes","123");
</script>
# 特殊产品线国际化(国际化后缀)
事情来源于政务G6,政务G6与A8的功能相差不大,共用一套代码,但是产品需求有调整:A8的协同应用就叫“协同”,G6的协同应用叫“事务”。
问题来了:这些命名都是通过国际化文件维护,一山不容二虎,G6产品线需要修改标准产品的国际化内容才能达成产品需求。
基于以上原因,平台提供了一套不同产品线维护相同国际化的机制:政务G6产品线只要在原有国际化后面追加一个.GOV的国际化key就优先走政务国际化。
menu.tools.about = 關於
menu.tools.about.GOV = 關於G6
# 实现原理
G6是一个新产品线,我们在产品线定义文件中规定了".GOV"后缀的信息,如下代码所示:
public enum ProductEditionEnum {
// ...
entgroup (2, "A8V5-2", "edition.entgroup.product", ""), // A8+企业集团版
government (3, "G6V5-1", "edition.government.product", ".GOV"), // 政务版
governmentgroup (4, "G6V5-2", "edition.governmentgroup.product", ".GOV") // 政务多组织版
}
在实际运行中,我们的国际化获取显示值的代码实际执行如下:
1)先通过key+国际化后缀获取当前产品线下是否有特殊国际化
2)没有则寻找key的原始国际化内容
// 使用版本特定后缀的国际化KEY
String i18nSuffixUpperCase = ProductEditionEnum.getCurrentProductEditionEnum().getI18nSuffix();
// 使用版本特定后缀的国际化KEY
String suffixKey = key + i18nSuffixUpperCase;
String message = ResourceLoader.getResources(locale, suffixKey);
// 如果没有取到,就使用默认的KEY
if (Strings.isNotBlank(i18nSuffixUpperCase) && (message == null || "".equals(message.trim()))) {
message = ResourceLoader.getResources(locale, key);
}
# 开发注意事项
1)国际化key命名原则:插件名.功能点1.功能点*.动作
# 不推荐(单字母严重不推荐)
detaili18n = Detail
# 不推荐(首字母不是所属模块)
deeSection.name = DeeSection
# 推荐(首字母对应插件名,至少两个点以上)
dee.synchron.detail.label = Detail
2)关于拼接国际化key避开如下拼接方式 有时候我们会写一些与后台枚举变量对应的国际化,如下所示通过1,2,3区分不同的枚举:
attendance.clock.levels.level1 = low
attendance.clock.levels.level2 = medium
attendance.clock.levels.level3 = senior
// 调用的时候采用变量的方式拼接,如下所示
public void getLevel(int num){
ResourceUtil.getString("attendance.clock.levels.level"+num);
}
这种拼接的方式我们是推荐的,但请尽量把变量放国际化最后一个,不要像下面这样:
attendance.level1.label = low
attendance.level2.label = medium
attendance.level3.label = senior
// 不推荐,不推荐
public void getLevel(int num){
ResourceUtil.getString("attendance.level"+num+".label");
}
// 首先上面这种写法是没问题的,但是不推荐,因为我们有一个工具会检测未使用的国际化key
// 像上面这种,工具会认为以"attendance.level"开头的国际化都在被使用,很多未使用的国际化key会被忽略了!
attendance.level.xxx
attendance.level.yyy
attendance.level.zzz
3)properties文件注释使用#,不要出现下面这种注释:
//------------------------\u4E8C\u7EF4\u7801-----------------
4)预置(初始化)的数据要考虑国际化,推荐创建properties,初始化SQL使用国际化key
INSERT INTO MEETING_ROOM_TYPE (ID,NAME,PARENT_ID,SORT) VALUES(-1,'meeting.room.type.unclassified',0,0);
5)规避重复的国际化值定义 高频率出现、重复的翻译(如确定,取消等等等等),必须取平台Common中的国际化key
- 相同的国际化翻译共用一个key,不要重复定义,节约空间
- 废弃的国际化key一定要及时删除,一定要及时删除,节约空间
6)所有新功能在开发版本期内必须完成国际化,不要说下版本支持,不做就不能发版!
7)所有英语翻译首字母必须大写
8)英文下单词需要空格
9)单个单词不要截断换行
//以下显示是错误的
i have a dr
eam.
//以下显示是正确的
i have a
dream.
10)引导类文字,如菜单名称一定要显示全(即使是换行显示也行),不能出...
11)鼠标移动到文字上方时需要有完整名称的tips提示
12)标题类字段不要局限于20个字,尽量提高到250字或更大,因为海外的一些英文信息非常长!
13)不要只使用中文的图片,至少让UE提供中英两套图片
14)一些紧凑布局要提出质疑:英文国际化下是否会显示混乱
15)日期时间不要用Java自带的DateFormat,必须使用平台的Datetimes工具类处理,平台工具类对时区进行了专业处理的
