# 国际化多语言

在学习本章前,建议先了解一下什么叫国际化、多语言。

平台针对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工具类处理,平台工具类对时区进行了专业处理的

编撰人:het、admin