# 国际化

# 概述

国际化(internationalization,因在i和n之间共有18个字母,所以国际化也称为i18n) 在项目中主要实现的功能就是按照指定的语言让服务端返回相应语言的内容。本篇主要介绍使用国际化需要做的工作及后端开发内容。

# 表结构管理

表结构管理用来维护需要做国际化的表字段数据信息。
注意:
1、配置了国际化的字段,需对应后端的代码开发支持;详见后端国际化
2、此处也支持非国际化字段的维护,用以支持数据生命周期日志的相关表字段数据展示。(此维护为过度方案,后续HOS会提供元数据建模平台支持表结构及字段的详细维护,敬请等待文档更新。)

表结构管理主要功能包含:表结构的新增编辑查询删除启停导入导出 。以及表字段的查询新增编辑删除导入导出

【菜单路径:】国际化管理–>表结构管理

# 1 新增表结构

点击新增按钮,填写相应信息,点击保存

新增表结构

属性信息说明

属性名     属性说明
编码 表编码,唯一。
表名 sql表名,唯一。例如:菜单表名 ---> hos_sys_resource
名称 表名称
逻辑删除字段 如果该表做了逻辑删除处理,请配置逻辑删除字段。且字段值应当为1:删除;0:未删除。 没有做逻辑删除处理则请忽略。
主键字段 非必填。不填默认为id。支持联合主键。联合主键配置时请以逗号分隔。例如:code,org_id(注意配置字段为表字段,而非实体属性。)
翻译表表名 该表的业务翻译表表名。建议产品组使用时,自建表。不配置则默认为hos_i18n_data。(注:自建表时需要和默认表的表结构保持一致)
是否启用 表结构的启停状态

# 2 编辑表结构

点击表格行操作列编辑按钮,打开相应的详细信息,编辑后,点击保存即可。

编辑表结构

属性信息详细配置参见新增表结构

# 3 查询表结构

支持对表名名称的模糊检索,表格分页展示。

查询表结构

# 4 删除表结构

支持单个删除及批量删除。单个删除可使用表格行操作列删除按钮。批量删除勾选表格第一列后,点击表格上方删除按钮即可。

删除表结构

# 5 启停表结构

支持在表格行是否启用列,直接操作是否启用列内按钮对该行表结构数据进行启用或停用。

启停表结构

# 6 导入表结构

在表格上方点击导入按钮,打开导入弹框进行如下操作:1、下载excel模板;2、填充文件数据;3、选择导入方式;4、下一步后选择第二步准备好的文件即可

导入表结构

# 7 导出表结构

在表格上方点击导出按钮,可见三种导出方式,按需选择一种后。可打开导出列选择配置页面。对需要用到的数据进行勾选并导出即可。

导出表结构1

导出表结构2

# 8 查询表字段

支持对编码名称的模糊检索,表格分页展示。

查询表字段1

查询表字段2

# 9 新增表字段

点击新增按钮,填写相应信息,点击保存

新增表字段

属性信息说明

属性名     属性说明
编码 字段编码,唯一。使用表字段名 例如:菜单名称 ---> name (hos_sys_resource表)
名称 字段名称,描述。
国际化 字段是否开启国际化
排序 字段排序值。按正序从小到大排

(注:字段的排序在数据国际化中字段名称的下拉选项进行排序)

# 10 编辑表字段

点击表格行操作列编辑按钮,打开响应的详细信息,编辑后,点击保存即可。

编辑表字段

属性信息详细配置参见新增表字段

# 11 删除表字段

支持单个删除及批量删除。单个删除可使用表格行操作列删除按钮。批量删除勾选表格第一列后,点击表格上方删除按钮即可。

删除表字段

# 12 导入表字段

在表格上方点击导入按钮,打开导入弹框进行如下操作:1、下载excel模板;2、填充文件数据;3、选择导入方式;4、下一步后选择第二步准备好的文件即可

导入表字段

# 13 导出表字段

在表格上方点击导出按钮,可见三种导出方式,按需选择一种后。可打开导出列选择配置页面。对需要用到的数据进行勾选并导出即可。

导出表字段

# 代码开发

# 存储删除方式

对表字段开启了国际化后,后端代码层面需要在原业务数据进行增、删、改时,对该国际化字段进行存储或删除处理。

# 手动处理

开发人员在业务数据增、删、改操作时,参数为对象本身的。如果操作的数据中存在开启了国际化的字段。则需要手动处理数据国际化。

平台提供I18nUtil工具类。存储、删除处理方法介绍如下:

工具类方法说明

方法名        方法说明
boolean isOpenI18n() 获取当前系统配置:是否开启国际化。true/false
boolean isShowSource() 获取当前系统配置:当翻译值为空时,是否显示原值。true/false
String getRequestLanguage() 获取当前请求头的语言编码。例如:zh、en
String getDefaultLanguage() 获取平台设置的默认语种编码
void saveTransValue(I18nDataManual i18nDataManual) 存储单个国际化数据。
void saveBatchTransValue(List<I18nDataManual> i18nDataManualList) 批量存储国际化数据
void deleteDataBatch(String businessTableName, List<String> businessIdList, String field, String i18nDataTable) 批量删除业务数据指定字段的翻译数据(其中主键集合businessIdList支持联合主键,需注意需要与表结构配置的联合主键顺序一致,同样以逗号,拼接)
void deleteAllFieldBatchByLang(String businessTableName, List<String> businessIdList, String i18nDataTable) 批量删除业务数据所有翻译数据

参数关键字说明:

1. businessIdList : 业务数据主键id集合
2. field : 字段编码。例如:name。

I18nDataManual对象信息如下。

@Data
@AllArgsConstructor
public class I18nDataManual {

    @ApiModelProperty(value = "业务数据主键id(例:菜单表的主键id值,联合主键。需注意联合主键值拼接时需要与表结构配置的联合主键顺序一致,同样以逗号,拼接)")
    private String businessId;

    @ApiModelProperty(value = "表名(例:hos_sys_resource)")
    private String tbName;

    @ApiModelProperty(value = "字段编码(例:name)")
    private String field;

    @ApiModelProperty(value = "值(例:测试菜单1)")
    private String value;
    
    @ApiModelProperty(value = "翻译数据表名,默认为 hos_i18n_data")
    private String i18nDataTable;

}

使用示例:保存或更新language对象操作时,判断是否开启国际化,手动存储需要国际化的name值。

if (isOpenI18n) {
    I18nDataManual i18nDataManual = new I18nDataManual(language.getId(),"hos_i18n_language","name",language.getName(),"hos_i18n_data");
    I18nUtil.saveTransValue(i18nDataManual);
}

使用示例:删除language对象操作时,判断是否开启国际化,根据业务数据的主键 id 手动删除。

if (isOpenI18n) {
    I18nUtil.deleteAllFieldBatchByLang("hos_i18n_language", BaseStringUtils.stringToList(ids));
}

联合主键使用示例:假设部门表使用联合主键,表结构中配置编码和机构编码作为主键字段:code,org_code。判断是否开启国际化,手动存储和删除需要国际化值。

// 保存
if (isOpenI18n) {
    I18nDataManual i18nDataManual = new I18nDataManual(department.getCode()+","+department.getOrgCode(),"hos_org_department","name",language.getName(),"hos_i18n_data");
    I18nUtil.saveTransValue(i18nDataManual);
}
// 删除
if (isOpenI18n) {
    I18nUtil.deleteAllFieldBatchByLang("hos_org_department", BaseStringUtils.stringToList(department.getCode()+","+department.getOrgCode()));
}

# 查询方式

对表字段开启了国际化后,后端代码层面需要在对原业务数据进行查询时,对该国际化字段进行翻译。可以使用注解: @HosI18nAutoTrans@HosI18nHandTrans,自动翻译注解和手动翻译注解。通过配置注解的属性完成翻译。

# @HosI18nAutoTrans用法

当查询的返回对象是实体类对象或者 VO 对象时,在对象属性中需要做国际化翻译的字段上添加此注解。该注解通过统一的拦截,完成自动获得其对应语言的翻译值。 自动注解中的配置属性含义:

属性 解释说明
tableName 当前字段的实体类对应的表名,默认为空
fieldName 需要做国际化翻译的字段名,必须和数据库中字段名保持一致,默认为空
primary 当前主键字段,不配默认为 id。支持联合主键,与表结构的主键字段配置顺序保持一致,同样以逗号,拼接。例:code,orgCode(注意配置字段为实体类属性,而非表字段。)
isDrill 是否有下一级,并获取下一级中需要翻译的字段,默认为 false
i18nDataTable 翻译表的表名,可自行设置翻译表表名。默认为 hos 的数据翻译表

使用示例:通过配置自动翻译注解 @HosI18nAutoTrans 完成自动翻译。

@TableField("name")
@HosI18nAutoTrans(tableName = "hos_sys_resource")
private String name;

联合主键示例:

/**
 * 部门编码
 */
private String code;
/**
 * 部门所属机构编码
 */
private String orgCode;
/**
 * 部门名称
 */
@HosI18nAutoTrans(tableName = "hos_org_department", fieldName = "name", primary = "code,orgCode")
private String deptName;

# @HosI18nHandTrans用法

当查询返回的对象不是实体类对象,或者返回的 VO 对象并不是查询数据得到的 VO 对象,但是对象包含了需要翻译的字段,则添加手动翻译注解并在代码中调用I18nUtil工具类的方法,联合使用完成手动获取对应语言的翻译值。 手动注解中的配置属性含义:

属性 解释说明
tableName 当前实体类对应的表名,默认为空
fieldName 需要做国际化翻译的字段名,必须和数据库中字段名保持一致,默认为空
primary 当前主键字段,不配默认为 id。支持联合主键,与表结构的主键字段配置顺序保持一致,同样以逗号,拼接。例:code,orgCode(注意配置字段为实体类属性,而非表字段。)
isDrill 是否有下一级,并获取下一级中需要翻译的字段,默认为 false
i18nDataTable 翻译表的表名,可自行设置翻译表表名。默认为 hos 的数据翻译表

I18nUtil工具类。查询类方法介绍如下:

方法名 方法说明
void handTranslateObject(Object record) 根据需要翻译的对象配合对象中的手动注解获取对应语言的翻译值
void handTranslateList(List<?> list) 根据需要翻译的对象集合配合对象中的手动注解获取对应语言的翻译值

使用示例:通过配置手动翻译注解 @HosI18nHandTrans ,在代码中调用I18nUtil工具类的方法完成手动翻译。

public class HosPersonDeptVO {
    /**
     * 人员id
     */
    private String personId;
    /**
     * 部门id
     */
    private String deptId;
    /**
     * 部门名称
     */
    @HosI18nHandTrans(tableName = "hos_org_department", fieldName = "name", primary = "deptId")
    private String deptName;
}

public IPage<PersonTableVO> selectCustomPage(Page page, PersonSearchDTO dto,String type) {
    ......
    List<HosPersonDeptVO> deptVos = hosEmpPositionService.selectDeptByPersons(personIdList);
    if (isOpenI18n) {
        I18nUtil.handTranslateList(deptVos);
    }
    ......
}

联合主键示例:

public class HosOrgDeptVO {
    /**
     * 部门编码
     */
    private String code;
    /**
     * 部门所属机构编码
     */
    private String orgCode;
    /**
     * 部门名称
     */
    @HosI18nHandTrans(tableName = "hos_org_department", fieldName = "name", primary = "code,orgCode")
    private String deptName;
}

public IPage<DeptTableVO> selectCustomPage(Page page, searchDTO dto,String type) {
    ......
    List<HosOrgDeptVO> deptVos = hosOrgDeptService.selectDept(dto);
    if (isOpenI18n) {
        I18nUtil.handTranslateList(deptVos);
    }
    ......
}

# 手动查询(不能使用注解的情况)

当查询返回的对象有共同的继承父类,在父类中的字段需要做国际化,此时不能够使用注解,采用手动查询的方式获取对应语言的翻译值。

平台提供I18nUtil工具类。查询类方法介绍如下:

工具类方法说明

方法名        方法说明
String getTransValue(String businessTableName, String businessId, String field, String i18nDataTable) 根据业务数据表名、主键id从指定翻译表中获取指定字段的当前请求语言翻译值
String getTransValueByLang(String businessTableName, String businessId, String field, String language, String i18nDataTable) 根据业务数据表名、主键id从指定翻译表中获取指定字段的指定语言翻译值
Map<String, String> mapTransValue(String businessTableName,List<String> businessIdList, String field, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取指定字段的当前请求语言翻译值
Map<String, String> mapTransValueByLang(String businessTableName,List<String> businessIdList, String field, String language, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取指定字段的指定语言翻译值
Map<String, Map<String, String>> mapMultiTransValueByLang(String businessTableName, List<String> businessIdList, List<String> fieldList, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取多个字段的当前请求语言翻译值
Map<String, Map<String, String>> mapMultiTransValueByLang(String businessTableName, List<String> businessIdList, List<String> fieldList, String language, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取多个字段的指定语言翻译值
Map<String, String> mapBatchTransValue(String businessTableName,List<String> businessIdList, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取所有国际化字段的当前请求语言翻译值
Map<String, String> mapBatchTransValueByLang(String businessTableName,List<String> businessIdList, String language, String i18nDataTable) 根据业务数据表名、主键id集合批量从指定翻译表中获取所有国际化字段的指定语言翻译值
String mapStaticTrans(String code) 根据静态元素编码获取请求语种的后端静态元素信息翻译值
String mapStaticTransByLang(String code, String language) 根据静态元素编码和语种获取后端静态元素信息翻译值
String mapStaticTrans(Class<?> clazz,Object key,boolean joint) 根据静态元素类、元素值获取请求语种的后端静态元素信息翻译值
String mapStaticTrans(Class<?> clazz,Object key,boolean joint,String language) 根据静态元素类、元素值获取指定语种的后端静态元素信息翻译值
Map<String, String> mapMultiStaticTrans(List<String> codeList) 根据静态元素编码集合获取请求语种的后端静态元素信息翻译值
Map<String, String> mapMultiStaticTransByLang(List<String> codeList, String language) 根据静态元素编码集合和语种获取后端静态元素信息翻译值

参数关键字说明:

1. businessTableName : 业务表表名。
2. businessIdList : 业务数据主键集合。支持联合主键。需注意联合主键值拼接时需要与表结构配置的联合主键顺序一致,同样以逗号,拼接。
3. field : 字段编码。例如:name。
4. code : 静态元素的编码。
5. language : 语言编码。例如:zh、en。
6. i18nDataTable : 翻译表表名。
7. 指定字段返回Map : key-> 业务数据主键businessId , value->翻译值
    示例:{"10001":"测试菜单1","10002":"测试菜单2"}
8。不指定字段返回Map : key-> 业务数据主键businessId+":"+字段编码field , value->翻译值
    示例:{"10001:name":"测试菜单1","10002:name":"测试菜单2"}

使用场景示例:查询登录人菜单信息,根据请求语种显示对应菜单名称值

// 获取所有菜单的主键id集合。
List<String> idList = menus.stream().map(ResourceMenuVO::getId).collect(Collectors.toList());
// 根据主键id集合及字段名为"name"查询当前请求语言的国际化数据。
Map<String, String> nameTransMap = I18nUtil.mapTransValue("hos_sys_resource", idList, "name", "hos_i18n_data");
// 对原菜单数据中的name字段进行遍历值替换
menus.forEach(resourceMenuVO -> {
    String value = nameTransMap.get(resourceMenuVO.getId());
    if (StringUtil.isNotBlank(value)) {
        resourceMenuVO.setName(value);
    }
});

联合主键使用场景示例:查询部门名称,联合主键为code,orgCode

// 获取所有部门的联合主键集合。
List<String> idList = depts.stream().map(dept->dept.getCode()+","+dept.getOrgCode()).collect(Collectors.toList());
// 根据联合主键集合及字段名为"name"查询当前请求语言的国际化数据。
Map<String, String> nameTransMap = I18nUtil.mapTransValue("hos_org_department", idList, "name", "hos_i18n_data");
// 对原菜单数据中的name字段进行遍历值替换
depts.forEach(deptVo -> {
    String value = nameTransMap.get(deptVo.getCode()+","+deptVo.getOrgCode());
    if (StringUtil.isNotBlank(value)) {
        deptVo.setName(value);
    }
});

注意:页面搜索查询时,根据名称进行模糊搜索时,由于名称字段做了国际化,因此该字段的搜索也需要支持国际化。

使用场景示例:查询节假日字典列表

将当前请求语言编码和是否开启国际化两个参数置入查询参数中。

public IPage<FestivalDict> selectPage(Page<FestivalDict>page,FestivalDictDTO festivalDictDTO) {
        page.setSize(festivalDictDTO.getSize());
        page.setCurrent(festivalDictDTO.getCurrent());
        return baseMapper.selectPage(festivalDictDTO, page,I18nUtil.getRequestLanguage(),I18nUtil.isOpenI18n());
    }

左连接查询翻译表(hos_i18n_data)数据,连接条件为:xx.id=hid.business_id and hid.field_code = "name" and hid.language = #{language}

<select id="selectPage" resultType="com.mediway.hos.app.festival.model.entity.FestivalDict">
select * from hos_festival_dict hfd
    <if test="isOpenI18n">
            left join hos_i18n_data hid on hfd.id = hid.business_id and hid.field_code = "name" and hid.language = #{language}
    </if>
    <where>
         <if test="dto.code !=null and dto.code!=''">
       and hfd.code like CONCAT('%',CONCAT(#{dto.code},'%'))
         </if>
         <if test="dto.name !=null and dto.name!=''">
         <if test="isOpenI18n">
       and (hfd.name like CONCAT('%',CONCAT(#{dto.name},'%')) or hid.translation like CONCAT('%',CONCAT(#{dto.name},'%')))
         </if>
         <if test="!isOpenI18n">
       and hfd.name like CONCAT('%',CONCAT(#{dto.name},'%'))
         </if>
         </if>
         <if test="dto.isVacation !=null and dto.isVacation==true">
       and hfd.is_vacation = 1
         </if>
         <if test="dto.isVacation !=null and dto.isVacation==false">
       and hfd.is_vacation = 0
         </if>
    </where>
</select>