一、背景
接触到的项目,使用国际化越来越多,系统页面实现中英文环境,记录下,前端使用vue + vue-i8n
后端java自己封装
前端翻译对象:页面涉及到的所有按钮(包括新增、删除、导出、下载、导入、上一页、下一页等)、文本所有弹框提示(包括请输入内容、您确定要删除吗?、新增修改列名、列表头名、提交、取消、必填项不能为空等)总结为:所有页面定义的中文内容 切换为英文环境时 页面需切换英文。
后端翻译对象:因菜单采用动态菜单,故菜单翻译放到后端,字典数据,接口返回提示信息(操作成果、操作失败、原因)
此处前端自行百度,只记录后端部分
spring-boot maven 此处只贴关键性代码 项目其他封装 代码 未粘贴
配置类
package com.cloud.framework.i18n;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
@ConditionalOnProperty(name = {"spring.messages.basename"})
@EnableConfigurationProperties({TranslatorProperties.class})
@Configuration
public class InternationalConfiguration extends AcceptHeaderLocaleResolver implements WebMvcConfigurer {
@Autowired
private TranslatorProperties translatorProperties;
public InternationalConfiguration() {
}
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource rs = new ResourceBundleMessageSource();
rs.setDefaultEncoding(this.translatorProperties.getEncoding());
rs.setBasenames(new String[]{this.translatorProperties.getBasename()});
rs.setUseCodeAsDefaultMessage(true);
rs.setCacheSeconds(this.translatorProperties.getCacheSeconds());
return rs;
}
@Bean
public Translator myTranslator(ResourceBundleMessageSource messageSource) {
Translator result = new Translator(messageSource);
result.setDefaultLang(this.translatorProperties.getDefaultLang());
return result;
}
}
package com.cloud.framework.i18n;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.messages")
public class TranslatorProperties {
private String encoding;
private String basename;
private int cacheSeconds;
private boolean isClientLang;
private String defaultLang;
public TranslatorProperties() {
}
public String getEncoding() {
return this.encoding;
}
public String getBasename() {
return this.basename;
}
public int getCacheSeconds() {
return this.cacheSeconds;
}
public boolean isClientLang() {
return this.isClientLang;
}
public String getDefaultLang() {
return this.defaultLang;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setBasename(String basename) {
this.basename = basename;
}
public void setCacheSeconds(int cacheSeconds) {
this.cacheSeconds = cacheSeconds;
}
public void setClientLang(boolean isClientLang) {
this.isClientLang = isClientLang;
}
public void setDefaultLang(String defaultLang) {
this.defaultLang = defaultLang;
}
protected boolean canEqual(Object other) {
return other instanceof TranslatorProperties;
}
public TranslatorProperties(String encoding, String basename, int cacheSeconds, boolean isClientLang, String defaultLang) {
this.encoding = encoding;
this.basename = basename;
this.cacheSeconds = cacheSeconds;
this.isClientLang = isClientLang;
this.defaultLang = defaultLang;
}
}
翻译工具类
package com.cloud.framework.i18n;
import com.alibaba.fastjson.JSONObject;
import com.cloud.framework.cache.RedisUtils;
import com.cloud.framework.util.CodeStyleConvert;
import com.cloud.framework.web.SysContext;
import com.google.common.base.Preconditions;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.util.CollectionUtils;
public class Translator {
private static final Logger log = LoggerFactory.getLogger(Translator.class);
private static final String TIP = "mutli--lang--%s--%s";
@Autowired
private SysContext context;
@Autowired
private RedisUtils redisUtils;
private ResourceBundleMessageSource messageSource;
private String defaultLang;
@Autowired
Translator(ResourceBundleMessageSource messageSource) {
this.messageSource = messageSource;
}
public String dataToLocale(String field, String msg) {
String lang = this.initDefaultLang();
String[] l = lang.split("_");
Preconditions.checkArgument(l.length == 2, "error lang");
String codeStyle = field.indexOf("i18n_") < 0 ? CodeStyleConvert.underscoreName(field) : field;
log.info(String.format("mutli--lang--%s--%s", "CodeStyle", codeStyle));
List<Object> items = this.redisUtils.lRange(String.format("language:%s", codeStyle), 0L, -1L);
if (CollectionUtils.isEmpty(items)) {
return msg;
} else {
Iterator var8 = items.iterator();
JSONObject joItem;
do {
if (!var8.hasNext()) {
return msg;
}
Object item = var8.next();
log.info(String.format("mutli--lang--%s--%s", "RedisItem", item.toString()));
joItem = (JSONObject)item;
} while(!joItem.containsKey("typeKey") || !joItem.get("typeKey").equals(msg + "_" + l[0]));
return joItem.get("value").toString();
}
}
private String initDefaultLang() {
String lang = this.context.getLang();
if (StringUtils.isEmpty(lang) && !StringUtils.isEmpty(this.defaultLang)) {
lang = this.defaultLang;
}
return lang;
}
public String sysToLocale(String msg) {
String lang = this.initDefaultLang();
String[] l = lang.split("_");
Preconditions.checkArgument(l.length == 2, "error lang");
Locale locale = new Locale(l[0], l[1]);
return this.messageSource.getMessage(msg, new Object[0], locale);
}
public String sysToLocale(String lang, String msg) {
String[] l = lang.split("_");
Preconditions.checkArgument(l.length == 2, "error lang");
Locale locale = new Locale(l[0], l[1]);
return this.messageSource.getMessage(msg, new Object[0], locale);
}
public String getDefaultLang() {
return this.defaultLang;
}
public void setDefaultLang(String defaultLang) {
this.defaultLang = defaultLang;
}
}
META-INF 加载配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.cloud.framework.datasource.hbase.HBaseConfiguration
spring-boot配置文件 国际化默认语言配置 前端不传取默认 登录后语言记录符存redis 每次从redis取 切换语言时 修改redis存值
#国际化配置 defaultLang 默认语言 zh_CN 中文 en_US英文 messages: basename: i18n/messages encoding: UTF-8 cacheSeconds: 0 isClientLang: false defaultLang: zh_CN
配置资源文件
资源文件就是 key-value 对,每个资源文件中的 key 是不变的,但是value 随着不同的国家语言而改变。
如下图所示:提供了中文、英语俩种语言
英文
#public PUBLIC_QUERY_SUCCESS=The query is successful PUBLIC_QUERY_FAIL=The query fails
中文
#public PUBLIC_QUERY_SUCCESS=查询成功 PUBLIC_QUERY_FAIL=查询失败
测试
package com.cloud.user.controller;
import com.cloud.framework.bean.Result;
import com.cloud.framework.util.ResponseUtils;
import com.cloud.user.base.BaseResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* i18 国际化 中英文 翻译
*/
@RestController
@RequestMapping("/i18")
public class I18TransController extends BaseResource {
/**
*默认翻译
*/
@GetMapping("/defaultTran")
public Result<Object> defaultTran() {
return ResponseUtils.make(Result.OK, sysToLocale("PUBLIC_QUERY_SUCCESS"));
}
/**
* 模拟登录 前端 传翻译类型 根据前端传值进行翻译 zh_CN 中文 en_US英文
* @param lang
* @return
*/
@GetMapping("/login")
public Result<Object> login(@RequestParam(name = "lang") String lang) {
String errMsg="PUBLIC_QUERY_SUCCESS";
errMsg = sysToLocale(lang, errMsg);
return ResponseUtils.make(Result.OK, errMsg);
}
/**
* 根据字典存值翻译 比如 下拉框类型翻译
* @return
*/
@GetMapping("/tranByDictSelect")
public Result<Object> tranByDictSelect() {
//strategyCategoryReserve模拟数据库字典表 存的需要翻译的值
String name="strategyCategoryReserve";//下拉框数据 i18n_value字段对应存值
String transName = languageSwitch(name);
return ResponseUtils.make(Result.OK, transName);
}
/**
* 根据字典存值翻译 菜单翻译
* @return
*/
@GetMapping("/tranByDictMenu")
public Result<Object> tranByDictMenu() {
//jurisdiction 模拟数据库字典表 存的需要翻译的值
String name="jurisdiction";//菜单数据 i18n_value字段对应存值
String typeKey="i18n_menu_name";//菜单数据 i18n_value字段对应存值
String transName = languageSwitch(typeKey,name);
return ResponseUtils.make(Result.OK, transName);
}
}
启动项目时需把字典数据加入缓存 只贴关键性代码
package com.cloud.user;
import com.alibaba.fastjson.JSONObject;
import com.cloud.framework.cache.RedisUtils;
import com.cloud.framework.util.SpringContextUtils;
import com.cloud.user.entity.db1.XbDictionary;
import com.cloud.user.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication
@Slf4j
public class CloudUserApplication implements CommandLineRunner {
private final ApplicationContext appContext;
private final TestService testService;
private final RedisUtils redisUtils;
public CloudUserApplication(ApplicationContext appContext, TestService testService, RedisUtils redisUtils) {
this.appContext = appContext;
this.testService = testService;
this.redisUtils = redisUtils;
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(CloudUserApplication.class, args);
SpringContextUtils.setApplicationContext(context);
}
@Override
public void run(String... args) throws Exception {
String[] beans = appContext.getBeanDefinitionNames();
Arrays.sort(beans);
for (String bean : beans) {
System.out.println(bean);
}
// 初始化字典表数据到redis 翻译时候从redis取
redisDicCache();
}
/**
* 存储结构 字典数据存储类型 key:value
* 一级 root:TypeKey
* 二级 父级TypeKey:二级TypeKey
* 三级 二级TypeKey:三级TypeKey
* 翻译数据 存储类型为list
* language:二级TypeKey 下拉框数据 language:i18n_value 菜单数据language:i18n_menu_name
*/
public void redisDicCache() {
List<XbDictionary> dicList = testService.findAll();
List<XbDictionary> list = testService.findParentIdisNull();
//redisUtils.removePattern("language:*");
for (XbDictionary item : list) {
//若字典为一级记为root:TypeKey
String pTypeKey = item.getTypeKey();
//redisUtils.set("root" + ":" + pTypeKey, item);
log.info("一级pTypeKey:{},item:{}", pTypeKey, item);
for (XbDictionary dic : dicList) {
if (item.getId().equals(dic.getParentId())) {
String typeKeyTwo = dic.getTypeKey();
log.info("一级{}二级{}:typeKeyTwo:{},dic:{}", pTypeKey, typeKeyTwo, dic);
//redisUtils.set(pTypeKey + ":" + typeKeyTwo, dic);
for (XbDictionary dictionary : dicList) {
String typeKeyThree = dictionary.getTypeKey();
if (!typeKeyTwo.equals(typeKeyThree) &&
typeKeyThree.contains(typeKeyTwo)) {
// redisUtils.set(typeKeyTwo + ":" + typeKeyThree, dictionary);
log.info("二级{}三级{}:typeKeyThree:{},dictionary:{}", typeKeyTwo, typeKeyThree, dictionary);
}
if (typeKeyTwo.contains("i18n_") && dic.getId().equals(dictionary.getParentId())) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", dictionary.getId());
jsonObject.put("name", dictionary.getName());
jsonObject.put("parentId", dictionary.getParentId());
jsonObject.put("typeKey", dictionary.getTypeKey());
jsonObject.put("value", dictionary.getI18nValue());
// redisUtils.lPush("language:" + dic.getTypeKey(), jsonObject);
log.info("翻译数据typeKey{},数据:{}", "language:" + dic.getTypeKey(), jsonObject);
}
}
}
}
}
}
}
postman访问 默认值配置的英文en_US
配置的改为中文 zh_CN
根据前端传值 进行翻译
http://localhost:9000/i18/login?lang=zh_CN
根据数据库字典表存值翻译 对应前端下拉框
根据数据库字典表存值翻译 对应前端菜单