# 国际化

# 概述

# 目的

为应对公司海外拓展市场的需求,公司的产品需要具备基于非汉语语种的交互能力,为保证设计和开发过程满足产品的国际化要求,特制订本规定。

# 目标读者

本开发规范适用于基于HOS基础平台开发标准产品、定制化应用的开发人员。

# 术语定义

页面国际化:又称前端国际化,核心是管理页面中各类元素(如文本、按钮名称等)在不同语言环境下的翻译信息,确保前端界面展示符合目标语言规范。
数据国际化:用于维护业务数据中特定字段的多语言翻译值,使其能根据语言环境展示对应内容。该功能依赖于表结构管理模块,需预先配置待翻译的业务表及具体字段。
提示语及常量国际化:专注于管理后端错误提示、后端常量类、枚举类等的多语言翻译值,保障这类数据在不同语言环境下的准确展示。

# HOS对国际化的支持

HOS基础平台框架开发体系下,国际化支持主要通过页面国际化、数据国际化以及提示语和常量国际化三个核心部分来实现。

img_1.png

# 页面国际化

页面国际化主要包含以下内容:

  1. 页面文本:包括标题、段落、按钮标签、链接文本等;
  2. 提示信息:如错误提示、加载提示、操作成功或失败的反馈等;
  3. 日期与时间格式:确保日期和时间的显示支持多语言切换;
  4. 图片与图标:确保图片中的文本(如验证码、图表标签等)支持多语言切换,或者使用不包含文本的图片或图标;
  5. 导航与菜单:确保导航栏、下拉菜单等用户交互界面中的选项支持多语言切换,使用户能够在不同语言环境下轻松浏览和操作页面内容。

# 数据国际化

数据国际化主要包含以下内容:

  1. 导航菜单;
  2. 各个列表中的数据;
  3. 从后端获取的字典数据;
  4. 下拉框或者下拉列表中的数据;
  5. 导出的Excel表格中的数据;
  6. 打印内容中非手工输入的数据;
  7. 消息中心发送给用户的数据等功能。

# 提示语及常量国际化

提示语及常量国际化主要包含以下内容:

  1. 后端代码中的常量类;
  2. 后端代码中的枚举类;
  3. 后端程序返回给页面的错误提示语;
  4. 日志(操作日志、登录日志等)中的标题和内容。

# 开发配置

# 后端依赖说明

在pom.xml引入hos-framework-i18n-starter.jar包

<dependency>
    <groupId>com.mediway.hos</groupId>
    <artifactId>hos-framework-i18n-starter</artifactId>
</dependency>

# 后端yml配置

hos:
  i18n:
    baiduAppId:            ### 自动翻译API接口账号
    baiduSecurityKey:      ### 自动翻译API接口密码
    isOpen:          true  ### 国际化功能启停配置。true-开启国际化功能;false-关闭国际化功能
    isShowSource:    true  ### 国际化功能开启状态下,数据翻译值不存在时是否显示为原值。true-显示原值;false-显示查到的值

# 开启国际化

修改yml配置hos.i18n.isOpen值为true即可。

# 自动翻译配置

后续文档中会提供自动翻译功能,此功能为开发阶段初始化数据辅助用途,生产环境时请专业人士进行翻译。

自动翻译功能使用百度翻译API实现,因此需要各产品组自行申请注册百度开发者账号的APPID和密钥,并且需要公网条件才可以进行接口调用。具体地址详见: https://api.fanyi.baidu.com/api/trans/product/desktop?req=developer (opens new window)

img_1.png

注意:百度翻译api注册账号完成后,使用请将翻译服务激活,不使用时可停用(免费使用有字节限制,请注意官网信息)。需要自动翻译时将APPID和密钥对应配入后端yml中,不配置不生效。

img_2.png

img_3.png

# 页面国际化

# 开发流程

  1. 开发页面时内置$t("元素key")埋点;
  2. 在【菜单路径】:国际化管理–>页面国际化 中维护所属菜单页面的元素信息。
    上述开发完成后,进入页面时会根据当前页面编码获取该路由下所有页面元素(包含按钮、标题、label等)及公共元素的翻译值集合;渲染页面时$t方法会根据元素key匹配翻译值进行替换展示。

# 元素key值命名规范

key值默认为中文,以确保在未开启国际化时,页面也能正常展示元素内容。

# 使用说明

# 页面开发

1. Vue中使用

<template #form>
  <hos-row :gutter="20">
    <hos-col :span="4">
      <hos-form-item :label="$t('登录名')">
        <hos-input v-model="form.model.code" clearable :placeholder="$t('请输入') + $t('登录名')"></hos-input>
      </hos-form-item>
    </hos-col>
    <hos-col :span="4">
      <hos-form-item :label="$t('名称')">
        <hos-input v-model="form.model.name" clearable :placeholder="$t('请输入名称')"></hos-input>
      </hos-form-item>
    </hos-col>
    <hos-col :span="4">
      <hos-form-item :label="$t('是否启用')">
        <hos-checkbox v-model="form.model.activity1" :label="$t('是')"></hos-checkbox>
        <hos-checkbox v-model="form.model.activity2" :label="$t('否')"></hos-checkbox>
      </hos-form-item>
    </hos-col>
    <hos-col :span="8">
      <hos-form-item label-width="0">
        <hos-biz-button run="form.search" type="primary">{{ $t('查询') }}</hos-biz-button>
        <hos-biz-button run="form.reset" @click="reset()">{{ $t('重置') }}</hos-biz-button>
        <hos-button v-if="showButton" v-has-permi="{key: 'sys:account:list:tab:log'}" @click="bizLog()" type="success" class="fr">{{ $t('数据日志') }}</hos-button>
      </hos-form-item>
    </hos-col>
  </hos-row>
</template>

2. script中使用

deletion() {
  if (this.ids != undefined) {
    if (this.ids.length > 0) {
      this.$confirm(this.$t('是否删除已选中的数据?'), this.$t('提示'), { type: 'error' })
        .then(() => {
          this.$api('base.account.deletion', {
            ids: this.ids.join(',')
          }).then((res) => {
            if (res && res.code == '200') {
              this.$message.success(res.msg)
              this.$store.commit('UPDATE_TABLE')
            } else {
              this.$message.error(res.msg)
            }
          })
        })
        .catch(() => {
          this.$message.error(this.$t("操作失败!"));
        })
    } else {
      this.$message.warning(this.$t('未选中数据,请先选中需要删除的数据'))
    }
  } else {
    this.$message.warning(this.$t('未选中数据,请先选中需要删除的数据'))
  }
}

# 配置页面

【菜单路径:】国际化管理–>页面国际化

img.png

确定页面是动态路由还是静态路由(动态路由:由菜单管理定义;静态路由:前端项目里定义,无需权限控制的,如:登录页、忘记密码、数据日志等页面)

1.如果是动态路由,去【菜单路径】:系统管理–>菜单管理 中建菜单,配置此页面路由,在【菜单路径】:国际化管理–>页面国际化 中维护所属菜单的元素信息。

img_1.png

img_1.png

2. 如果是静态路由,在【菜单路径】:国际化管理–>页面国际化 中维护页面的元素信息。

img_2.png

# 配置页面元素

选中左侧页面,在右侧列表中维护页面中的元素翻译,编码对应的就是页面中埋点 $t里的元素key值

img_3.png

# 翻译数据

# 自动翻译

在确保开启国际化及配置了可用的百度翻译api信息后,再对业务数据进行翻译。

再次声明:该功能为初始化辅助用途,只会对单词进行直译,存在语境问题而导致翻译不准确。自动翻译完成后,请人工校对。

img_4.png

# 手动数据

在初始数据使用完自动翻译后,对翻译数据进行校正或者后续新内容的维护,推荐使用手动翻译,以确保语义更加精准。操作如下图所示:

img_5.png

# 前端使用

框架提供了获取当前语言配置的方法:getLocale

框架提供了获取当前语言配置的方法:getLocale

获取当前语言

语言配置存放到了storage里了,如下图:

storage里获取当前语言

# 数据国际化

# 开发流程

  1. 维护需要进行数据国际化的表及字段配置信息;
  2. 在后端java代码中对返回到前端的数据进行国际化处理;
  3. 在数据国际化菜单或使用数据国际化翻译组件中进行数据翻译。

# 表结构维护

表结构管理用来维护需要进行数据国际化的表配置信息及字段信息。

# 表维护

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

新增表结构

点击新增按钮,填写表结构信息,点击保存即可。

新增表结构

属性信息说明

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

# 表字段维护

点击表结构列表中对应行的操作列-表字段维护按钮。

查询表字段1

点击新增按钮,填写相表字段信息,点击保存。

新增表字段

属性信息说明

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

# 代码预置

# 保存与删除

# 1.保存操作

业务数据保存时调用。将需要做国际化的属性字段值存入数据国际化翻译表中。

使用示例如下:

场景一:保存或更新人员对象操作,存储需要国际化的name值。person为人员实体对象。

public int insertPerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPerson(person);
    I18nUtil.standardEntityTranslation(person, "hos_org_person", "name");
    return result;
}

场景二:如果有多个字段需要国际化,例如name、remark字段保存。person为人员实体对象。

public int insertPerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPerson(person);
    I18nUtil.standardEntityTranslation(person, "hos_org_person", Arrays.asList("name", "remark"));
    return result;
}

场景三:如果有多个实体需要国际化。personList为人员实体集合。

public int insertPersonBatch(List<Person> personList) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPersonBatch(personList);
    I18nUtil.standardEntityListTranslation(personList, "hos_org_person", "name");
    return result;
}

场景四:如果有多个实体且多个字段需要国际化,例如name、remark字段保存。personList为人员实体集合。

public int insertPersonBatch(List<Person> personList) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPersonBatch(personList);
    I18nUtil.standardEntityListTranslation(personList, "hos_org_person", Arrays.asList("name", "remark"));
    return result;
}

场景五:如果有联合主键的情况。例如:人员编码+机构id作为主键,使用逗号进行拼接

public int insertPerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPerson(person);
    I18nUtil.individuationEntityTranslation(person, "hos_org_person", "name", null, Arrays.asList("personCode", "orgId"), ",", "", "");
    return result;
}

其他场景:其他多实体、多字段场景与以上列举方法使用基本类似。详细信息请查看I18nUtil工具类源码注释,不在此一一赘述。

注意:2.6.6.3.10版本之前需要手动拼装I18nDataManual对象再进行存储

示例如下:

public int insertPerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPerson(person);
    if (isOpenI18n) {
        I18nDataManual i18nDataManual = new I18nDataManual(person.getId(),"hos_org_person","name",person.getName(),"hos_i18n_data");
        I18nUtil.saveTransValue(i18nDataManual);
    }
    return result;
}

如果使用了自建的数据国际化表(支持业务自建数据国际化表,需与默认的翻译表hos_i18n_data表字段一致)。例如自建表名为"hos_portal_i18n_data"。

public int insertPerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.insertPerson(person);
    if (isOpenI18n) {
        I18nDataManual i18nDataManual = new I18nDataManual(person.getId(),"hos_org_person","name",person.getName(),"hos_portal_i18n_data");
        I18nUtil.saveTransValue(i18nDataManual);
    }
    return result;
}

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;

}

# 2.删除操作

业务数据删除时调用。删除对应的国际化翻译数据。

场景1:根据主键id删除人员操作,根据业务数据的主键id删除。

public int deletePerson(String id) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.deleteById(id);
    if (isOpenI18n) {
        I18nUtil.deleteAllFieldBatchByLang("hos_org_person", Arrays.asList(id), "hos_i18n_data");
    }
    return result;
}

场景2:删除使用联合主键的翻译数据,例如:人员编码+机构id属性作为主键,使用逗号进行拼接(与存储时拼接顺序保持一致)。

public int deletePerson(Person person) {
    // 业务逻辑省略
    // ...
    int result = baseMapper.deleteByParam(person.getPersonCode(), person.getOrgId());
    if (isOpenI18n) {
        I18nUtil.deleteAllFieldBatchByLang("hos_org_person", Arrays.asList(person.getPersonCode()+","+person.getOrgId()), "hos_i18n_data");
    }
    return result;
}

其他场景:批量操作时同理,传入主键id集合或联合主键的集合即可。

注意:如果使用自建的国际化存储表,同保存操作一致,需要将默认的翻译表名"hos_i18n_data"替换为自建表名。

# 查询

# 1.自动翻译注解

自动翻译注解:@HosI18nAutoTrans

应用场景:自动注解的实现逻辑为全局拦截controller层返回对象并按注解处理需要翻译的字段。所以要触发自动注解的前提是controller层返回的泛型对象中使用了自动注解。否则不会进行自动翻译。

注解属性说明:

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

应用示例1:查询人员详情,自动翻译人员姓名。

@Data
public class Person { 
    private String id;
    
    @HosI18nAutoTrans(tableName = "hos_org_person")
    private String name;
}

@RestController
public class PersonController { 
    @Autowired
    private PersonService personService;
    
    @GetMapping("/person/{id}")
    public BaseResponse<Person> getPerson(@PathVariable String id) {
        return BaseResponse.success(personService.getPerson(id));
    }
}

应用示例2:查询人员详情,自动翻译姓名,使用了联合主键。

@Data
public class PersonVO { 
    private String personCode;
    
    private String orgId;
    
    @HosI18nAutoTrans(tableName = "hos_org_person", primary = "personCode,orgId")
    private String name;
}

@RestController
public class PersonController { 
    @Autowired
    private PersonService personService;
    
    @GetMapping("/person/select-by-name")
    public BaseResponse<PersonVO> getPersonByName(@RequestParam String name) {
        return BaseResponse.success(personService.getPersonByName(name));
    }
}

# 2.半自动翻译注解

半自动翻译注解:@HosI18nHandTrans

应用场景:当查询对象信息比较复杂,不能应用自动翻译注解处理且需要在service中单独处理时,则添加手动翻译注解并在代码中调用I18nUtil工具类的方法,联合使用完成手动获取当前请求语言的翻译值。

注解属性说明:

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

应用示例1:人员列表分页接口,翻译所属部门名称。(返回泛型实体并非需要翻译的实体,仅为service层数据拼接的一步逻辑。)

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);
    }
    ......
}

应用示例2:人员列表分页接口,部门名称需要联合主键查询的场景。

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);
    }
    // ......
}

# 3.手动工具类

工具类: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集合批量从指定翻译表中获取所有国际化字段的指定语言翻译值

参数关键字说明

   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"}

应用场景: 不能通过翻译注解直接完成,且应用于service层翻译的场景。

应用示例:某些场景返回的人员信息需要显示性别或职称等场景,如”张三(男)”

public List<Person> listPerson() {
    // 获取性别翻译map
    Map<String, String> sexNameTransMap = ...;
    // 获取人员id集合
    List<String> idList = personList.stream().map(person::getId).collect(Collectors.toList());
    // 根据主键id集合及字段名为"name"查询当前请求语言的国际化数据。
    Map<String, String> nameTransMap = I18nUtil.mapTransValue("hos_org_person", idList, "name", "hos_i18n_data");
    // 对原数据中的name字段进行遍历值替换
    personList.forEach(person -> {
        String value = nameTransMap.get(person.getId());
        if (StringUtil.isNotBlank(value)) {
            person.setName(value + "(" + sexNameTransMap.get(person.getSexCode()) + ")");
        }
    });
}

# 4.检索

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

应用示例

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

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());
}
<select id="selectPage" resultType="com.mediway.hos.app.festival.model.entity.FestivalDict">
select * from hos_festival_dict hfd
    <if test="isOpenI18n and dto.name !=null and dto.name!=''">
            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>

# 数据翻译处理

# 1.维护入口

【菜单路径:】国际化管理–>数据国际化

img.png

左侧表格为表结构维护的数据。支持按名称检索。 点击左侧表名右侧可查询该表的所有开启国际化的字段原值及翻译值。

# 2.历史数据生成

应用场景:当点击左侧表名后,右侧翻译列表的原值有值,但翻译值为空及翻译语言数量值为0时,首先需要对该表进行历史数据生成,即将业务原值存入到数据国际化表默认语种中。

img_1.png

这种场景出现的原因是:某表业务数据产生后,才针对该表进行了数据国际化存储、查询的相关配置及开发。此时,点击历史数据生成按钮即可。生成成功后该模块表格的翻译语言数量不再为0。

img_1.png

# 3.自动翻译

在确保开启国际化及配置了可用的百度翻译api信息后,再对业务数据进行翻译。

再次声明:该功能为初始化辅助用途,只会对单词进行直译,存在语境问题而导致翻译不准确。自动翻译完成后,请人工校对。

示例如下:

img_1.png

选中表的名称字段的原值有值,但检索语种的翻译值为空。则可以对其进行自动翻译(存在翻译值时不会对其更新)。支持以下两种方式:

img_2.png

第一种方式:对选中数据进行批量翻译。勾选表格左侧勾选框,再点击自动翻译选择当前所选数据和需要翻译的语种。

第二种方式:对当前业务表所有未翻译的数据进行翻译。无需勾选,直接点击自动翻译选择当前表和需要翻译的语种。

翻译完成后,即可看到英语翻译值。

img_3.png


重要提示
百度在线翻译接口支持的QPS有限,连续调用会出现限制导致报错。平台为减少调用频率,在批量翻译时对数据使用θ字符对数据做了拼接处理,翻译后又按θ字符进行了拆解赋值。所以如果业务数据中包含该字符,请手动翻译,防止数据翻译错乱。

# 4.手动翻译

在初始数据使用完自动翻译后,对翻译数据进行校正或者后续新内容的维护,推荐使用手动翻译,以确保语义更加精准。操作如下图所示:

img_3.png

# 翻译插件处理

平台提供表格数据国际化翻译组件,可以对单行数据需要做国际化的字段翻译不同语言的值

  1. 使用效果

img.png

  1. 前端代码改动
<!-- tableUid: 表格的uid值, tbCode:为国际化-表结构管理中定义的编码,由后端开发配置后指定提供。ids:表格中选中的数据id数组-->
<hosI18n :tableUid="'table'" tbCode="config" :ids="ids"></hosI18n>

其中tableUid需要和对应table的uid保持一致,tbCode和业务表名保持一致,示例见下图:

国际化翻译组件使用

国际化翻译组件使用1

# 提示语及常量国际化

提示语及常量国际化分为四类,对这四类数据的维护和代码开发详见下表:

分类 用途
日志类 维护日志的信息
常量类 维护自定义常量信息
枚举类 维护自定义枚举信息
提示信息类 维护系统提示信息

# 开发流程

  1. 在【菜单路径】:国际化管理–>静态数据国际化 中按照规范维护翻译数据;
  2. 在相应的静态数据返回的代码处进行国际化处理。

# 维护数据

# 维护入口

【菜单路径】:国际化管理–>静态数据国际化

img.png

首先点击左侧元素分类树的叶子节点。再点击新增按钮,否则会有提示信息不能打开。填写表单属性信息后,点击保存即可完成编辑。

img.png

# 日志

# 编码命名规范

日志的编码为日志的原值,以基础平台操作日志为例:日志标题title为账号管理,日志内容content为解锁账号,维护日志数据时的编码也应为账号管理解锁账号

@ApiOperation(value = "解锁账号")
@PostMapping("/unlock")
@OperLog(title = "账号管理", content = "解锁账号")
public BaseResponse recover(@RequestBody DelDTO delDTO) {
    return BaseResponse.success(hosUserAccountService.unlock(delDTO.getIds(),false));
}

static-2.png

# 代码编写规范

开发过程中,在进行日志数据的国际化时,可以使用国际化工具类提供的方法对日志数据进行翻译。

通过传入上一步维护的编码,即可根据请求的语言类型返回对应的翻译值。

String transValue = I18nUtil.mapStaticTrans("系统管理");

通过传入编码是否显示原值两个参数,根据请求的语言类型返回对应翻译值。
其中是否显示原值的含义:传true即当查询到的翻译值为空时将原值返回;传false即当查询到翻译值为空时会返回空值。

String transValue = I18nUtil.mapStaticTrans("系统管理", true);

通过传入编码语言类型两个参数返回翻译值。其中语言类型是在语言管理中维护了的语言的编码,可根据指定的语言返回翻译值。

String transValue = I18nUtil.mapStaticTrans("系统管理", "zh"); 

# 常量

# 编码命名规范

常量编码的应根据常量类的类名加"_"各常量的属性名来命名,保证编码的唯一性。例如: OrgGlobalConstant_CONDITION 表示常量类OrgGlobalConstant中,属性为CONDITION的静态数据翻译编码。

# 代码编写规范

由于常量特殊的命名规则,因此国际化工具类提供获取翻译值的方法。

public class PostConstant {
    public static final String ADMINISTRATOR_UNIT_NAME="超级系统管理员岗位单元";
}
String unitName = I18nUtil.mapStaticTrans(PostConstant.ADMINISTRATOR_UNIT_NAME);
if (StringUtil.isNotBlank(unitName)) {
    accountAboutVO.setPostUnitName(unitName);
}

通过传入以上参数可根据请求的语言类型获取到当前常量对应属性的翻译值。
入参分别为常量类常量类的属性是否拼接

String transValue = I18nUtil.mapStaticTrans(PostConstant.class, PostConstant.ADMINISTRATOR_UNIT_NAME, true);

通过传入以上参数可获取到当前常量对应属性指定语言的翻译值。
入参分别为常量类常量类的属性是否拼接语言类型

String transValue = I18nUtil.mapStaticTrans(PostConstant.class, PostConstant.ADMINISTRATOR_UNIT_NAME, true, "zh");

# 枚举

# 编码命名规范

枚举编码的应根据枚举类的类名+_+各枚举项的code值来命名,例如:ChannelTypeEnum_mail 表示枚举类ChannelTypeEnum中,枚举项mail的静态数据翻译编码。

# 代码编写规范

枚举的编码命名和常量的命名规则原理上是一致的,因此可以使用国际化工具类中对常量提供的翻译方法获取其翻译值。

String name = I18nUtil.mapStaticTrans(PrimaryStrategyTypeEnum.class, primaryStrategyTypeEnum.getCode(), true);
if (StringUtil.isNotBlank(name)) {
    primaryStrategyTypeEnum.setName(name);
}

通过传入以上参数可根据请求的语言类型获取到当前枚举项的翻译值。
入参分别为枚举类枚举项的code值是否拼接

String transValue = I18nUtil.mapStaticTrans(ChannelTypeEnum.class, ChannelTypeEnum.mail.getCode(), true);

通过传入以上参数可获取到当前枚举项指定语言的翻译值。
入参分别为枚举类枚举项的code值是否拼接语言类型

String transValue = I18nUtil.mapStaticTrans(ChannelTypeEnum.class, ChannelTypeEnum.mail.getCode(), true, "zh");

# 提示信息

# 编码命名规范

提示信息的编码应根据系统-模块-功能-异常信息序号四部分组成的序列来命名。例如:100-002-002-001 表示基础平台的hos-app-config模块中系统参数功能下序列为001的错误提示信息的静态数据翻译编码。其中100 代表基础平台的序列,其他系统可自行命名。

# 代码编写规范

提示信息可以分为三种场景,针对不同的场景提供了翻译方法来获取提示信息的翻译值。

实体类入参校验

在需要校验的实体类属性上添加校验注解,实体入参的接口形参前添加@validated校验注解即可。若入参校验不通过会通过统一拦截去处理翻译对应的提示信息。

@PostMapping
public BaseResponse<Integer> saveFestivalDict(@Validated @RequestBody HosSysFestivalDict hosSysFestivalDict) {
    return  BaseResponse.success(festivalDictService.create(hosSysFestivalDict));
}

@Data
public class HosSysFestivalDict {
    
    @NotBlank(message = "名称不能为空")
    @Length(min = 1, max = 75, message = "名称长度不能超过75个字符")
    private String name;
    // 其他字段
    // ...
}  

Exception类

第一种:默认使用异常统一拦截翻译返回的提示信息。

throw new BaseCommonBusinessException("100-002-002-002", "导入失败!");

第二种:不使用异常统一拦截翻译,通过自主调用工具类的方法翻译对应的提示信息。

throw new BaseCommonBusinessException("100-002-002-002", I18nUtil.mapStaticTrans("导入失败!"), false);

Response类

第一种:默认使用异常统一拦截翻译返回的提示信息。

return BaseResponse.error("100-002-002-001", "id 不能为空");

第二种:不使用异常统一拦截翻译,通过自主调用工具类的方法翻译对应的提示信息。

return BaseResponse.error("100-002-002-001", I18nUtil.mapStaticTrans("id 不能为空"), false);