使用 spring boot 国际化

This commit is contained in:
Captain.B 2020-04-16 19:16:28 +08:00
parent 23fecbb283
commit 6feefcc23e
9 changed files with 81 additions and 309 deletions

View File

@ -0,0 +1,21 @@
package io.metersphere.controller;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("anonymous")
public class HelloController {
@Resource
MessageSource messageSource;
@GetMapping("hello")
public String hello() {
return messageSource.getMessage("max_thread_insufficient", null, "默认值", LocaleContextHolder.getLocale());
}
}

View File

@ -3,13 +3,13 @@ package io.metersphere.controller;
import io.metersphere.base.domain.UserRole; import io.metersphere.base.domain.UserRole;
import io.metersphere.controller.request.LoginRequest; import io.metersphere.controller.request.LoginRequest;
import io.metersphere.dto.UserDTO; import io.metersphere.dto.UserDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.service.UserService; import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*; import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -26,7 +26,7 @@ public class LoginController {
@GetMapping(value = "/isLogin") @GetMapping(value = "/isLogin")
public ResultHolder isLogin() { public ResultHolder isLogin() {
if (SecurityUtils.getSubject().isAuthenticated()) { if (SecurityUtils.getSubject().isAuthenticated()) {
return ResultHolder.success(Translator.getLangDes()); return ResultHolder.success(LocaleContextHolder.getLocale());
} }
return ResultHolder.error(""); return ResultHolder.error("");
} }

View File

@ -1,271 +1,24 @@
package io.metersphere.i18n; package io.metersphere.i18n;
import com.alibaba.fastjson.JSON; import org.springframework.context.MessageSource;
import com.alibaba.fastjson.serializer.JavaBeanSerializer; import org.springframework.context.i18n.LocaleContextHolder;
import com.alibaba.fastjson.serializer.ObjectSerializer; import org.springframework.stereotype.Component;
import com.alibaba.fastjson.serializer.SerializeConfig;
import io.metersphere.commons.constants.I18nConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.service.SystemParameterService;
import io.metersphere.user.SessionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class Translator { public class Translator {
private static MessageSource messageSource;
public static final String PREFIX = "$[{"; /**
public static final String SUFFIX = "}]"; * 单Key翻译
private static final String JSON_SYMBOL = "\":"; */
private static final HashSet<String> IGNORE_KEYS = new HashSet<>(Arrays.asList("id", "password", "passwd"));
private static Map<String, String> langCache4Thread = Collections.synchronizedMap(new PassiveExpiringMap(1, TimeUnit.MINUTES));
public static String getLangDes() {
return getLang().getDesc();
}
public static Lang getLang() {
HttpServletRequest request = getRequest();
return getLang(request);
}
public static Object gets(Object keys) {
return gets(getLang(), keys);
}
public static Object gets(Lang lang, Object keys) {
Map<String, String> context = I18nManager.getI18nMap().get(lang.getDesc().toLowerCase());
return translateObject(keys, context);
}
// 单Key翻译
public static String get(String key) { public static String get(String key) {
return get(getLang(), key); return messageSource.getMessage(key, null, "Not Support Key", LocaleContextHolder.getLocale());
} }
public static String get(Lang lang, String key) { @Resource
if (StringUtils.isBlank(key)) { public void setMessageSource(MessageSource messageSource) {
return StringUtils.EMPTY; Translator.messageSource = messageSource;
}
return translateKey(key, I18nManager.getI18nMap().get(lang.getDesc().toLowerCase()));
}
public static String toI18nKey(String key) {
return String.format("%s%s%s", PREFIX, key, SUFFIX);
}
private static HttpServletRequest getRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (NullPointerException npe) {
return null;
}
}
private static Lang getLang(HttpServletRequest request) {
String preferLang = Lang.zh_CN.getDesc();
try {
if (request != null) {
Object sessionLang = request.getSession(true).getAttribute(I18nConstants.LANG_COOKIE_NAME);
if (sessionLang != null && StringUtils.isNotBlank(sessionLang.toString())) {
return Lang.getLang(sessionLang.toString());
}
preferLang = getSystemParameterLanguage(preferLang);
if (StringUtils.isNotBlank(request.getHeader(HttpHeaders.ACCEPT_LANGUAGE))) {
String preferLangWithComma = StringUtils.substringBefore(request.getHeader(HttpHeaders.ACCEPT_LANGUAGE), ";");
String acceptLanguage = StringUtils.replace(StringUtils.substringBefore(preferLangWithComma, ","), "-", "_");
if (Lang.getLangWithoutDefault(acceptLanguage) != null) {
preferLang = acceptLanguage;
}
}
if (request.getCookies() != null && request.getCookies().length > 0) {
for (Cookie cookie : request.getCookies()) {
if (StringUtils.equalsIgnoreCase(cookie.getName(), I18nConstants.LANG_COOKIE_NAME)) {
preferLang = cookie.getValue();
}
}
}
if (SessionUtils.getUser() != null && StringUtils.isNotBlank(SessionUtils.getUser().getLanguage())) {
preferLang = SessionUtils.getUser().getLanguage();
}
request.getSession(true).setAttribute(I18nConstants.LANG_COOKIE_NAME, preferLang);
} else {
preferLang = getSystemParameterLanguage(preferLang);
}
} catch (Exception e) {
LogUtil.error("Fail to getLang.", e);
}
return Lang.getLang(preferLang);
}
private static String getSystemParameterLanguage(String defaultLang) {
String result = defaultLang;
try {
String cachedLang = langCache4Thread.get(I18nConstants.LANG_COOKIE_NAME);
if (StringUtils.isNotBlank(cachedLang)) {
return cachedLang;
}
String systemLanguage = Objects.requireNonNull(CommonBeanFactory.getBean(SystemParameterService.class)).getSystemLanguage();
if (StringUtils.isNotBlank(systemLanguage)) {
result = systemLanguage;
}
langCache4Thread.put(I18nConstants.LANG_COOKIE_NAME, result);
} catch (Exception e) {
LogUtil.error(e);
}
return result;
}
private static Object translateObject(Object javaObject, final Map<String, String> context) {
if (MapUtils.isEmpty(context)) {
return javaObject;
}
if (javaObject == null) {
return null;
}
try {
if (javaObject instanceof String) {
String rawString = javaObject.toString();
if (StringUtils.contains(rawString, JSON_SYMBOL)) {
try {
Object jsonObject = JSON.parse(rawString);
Object a = translateObject(jsonObject, context);
return JSON.toJSONString(a);
} catch (Exception e) {
LogUtil.warn("Failed to translate object " + rawString + ". Error: " + ExceptionUtils.getStackTrace(e));
return translateRawString(null, rawString, context);
}
} else {
return translateRawString(null, rawString, context);
}
}
if (javaObject instanceof Map) {
Map<Object, Object> map = (Map<Object, Object>) javaObject;
for (Map.Entry<Object, Object> entry : map.entrySet()) {
if (entry.getValue() != null) {
if (entry.getValue() instanceof String) {
if (StringUtils.contains(entry.getValue().toString(), JSON_SYMBOL)) {
map.put(entry.getKey(), translateObject(entry.getValue(), context));
} else {
map.put(entry.getKey(), translateRawString(entry.getKey().toString(), entry.getValue().toString(), context));
}
} else {
translateObject(entry.getValue(), context);
}
}
}
}
if (javaObject instanceof Collection) {
Collection<Object> collection = (Collection<Object>) javaObject;
for (Object item : collection) {
translateObject(item, context);
}
}
if (javaObject.getClass().isArray()) {
for (int i = 0; i < Array.getLength(javaObject); ++i) {
Object item = Array.get(javaObject, i);
Array.set(javaObject, i, translateObject(item, context));
}
}
ObjectSerializer serializer = SerializeConfig.globalInstance.getObjectWriter(javaObject.getClass());
if (serializer instanceof JavaBeanSerializer) {
JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) serializer;
try {
Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (entry.getValue() != null) {
if (entry.getValue() instanceof String) {
if (StringUtils.contains(entry.getValue().toString(), JSON_SYMBOL)) {
BeanUtils.setFieldValueByName(javaObject, entry.getKey(), translateObject(entry.getValue(), context), String.class);
} else {
BeanUtils.setFieldValueByName(javaObject, entry.getKey(), translateRawString(entry.getKey(), entry.getValue().toString(), context), String.class);
}
} else {
translateObject(entry.getValue(), context);
}
}
}
} catch (Exception e) {
MSException.throwException(e);
}
}
return javaObject;
} catch (StackOverflowError stackOverflowError) {
try {
return JSON.parseObject(translateRawString(null, JSON.toJSONString(javaObject), context).toString(), javaObject.getClass());
} catch (Exception e) {
LogUtil.error("Failed to translate object " + javaObject.toString(), e);
return javaObject;
}
}
}
private static Object translateRawString(String key, String rawString, Map<String, String> context) {
if (StringUtils.isBlank(rawString)) {
return rawString;
}
for (String ignoreKey : IGNORE_KEYS) {
if (StringUtils.containsIgnoreCase(key, ignoreKey)) {
return rawString;
}
}
if (StringUtils.contains(rawString, PREFIX)) {
rawString = new StringSubstitutor(context, PREFIX, SUFFIX).replace(rawString);
if (StringUtils.contains(rawString, PREFIX)) {
String[] unTrans = StringUtils.substringsBetween(rawString, PREFIX, SUFFIX);
if (unTrans != null) {
for (String unTran : unTrans) {
rawString = StringUtils.replace(rawString, PREFIX + unTran + SUFFIX, unTran);
}
}
}
}
if (key != null) {
String desc = context.get(rawString);
if (StringUtils.isNotBlank(desc)) {
return desc;
}
}
return rawString;
}
private static String translateKey(String key, Map<String, String> context) {
if (MapUtils.isEmpty(context)) {
return key;
}
String desc = context.get(StringUtils.replace(StringUtils.replace(key, PREFIX, StringUtils.EMPTY), SUFFIX, StringUtils.EMPTY));
if (StringUtils.isNotBlank(desc)) {
return desc;
}
return key;
} }
} }

View File

@ -34,4 +34,6 @@ spring.flyway.locations=classpath:db/migration
spring.flyway.table=metersphere_version spring.flyway.table=metersphere_version
spring.flyway.baseline-version=0 spring.flyway.baseline-version=0
spring.flyway.encoding=UTF-8 spring.flyway.encoding=UTF-8
spring.flyway.validate-on-migrate=false spring.flyway.validate-on-migrate=false
spring.messages.basename=i18n/messages

View File

@ -1,23 +0,0 @@
{
"error_lang_invalid": "Invalid language parameter",
"load_test_already_exists": "Duplicate load test name",
"project_name_is_null": "Project name cannot be null",
"project_name_already_exists": "The project name already exists",
"workspace_name_is_null": "Workspace name cannot be null",
"workspace_name_already_exists": "The workspace name already exists",
"workspace_does_not_belong_to_user": "The current workspace does not belong to the current user",
"organization_does_not_belong_to_user": "The current organization does not belong to the current user",
"file_cannot_be_null": "File cannot be empty!",
"edit_load_test_not_found": "Cannot edit test, test not found:",
"run_load_test_not_found": "Cannot run test, test not found:",
"run_load_test_file_not_found": "Unable to run test, unable to get test file meta information, test ID:",
"run_load_test_file_content_not_found": "Cannot run test, cannot get test file content, test ID:",
"run_load_test_file_init_error": "Failed to run test, failed to initialize run environment, test ID:",
"load_test_is_running": "Load test is running, please wait.",
"node_deep_limit": "The node depth does not exceed 5 layers!",
"no_nodes_message": "No node message",
"duplicate_node_ip": "Duplicate IPs",
"only_one_k8s": "Only one K8s can be added",
"organization_id_is_null": "Organization ID cannot be null",
"max_thread_insufficient": "The number of concurrent users exceeds"
}

View File

@ -0,0 +1,21 @@
error_lang_invalid=Invalid language parameter
load_test_already_exists=Duplicate load test name
project_name_is_null=Project name cannot be null
project_name_already_exists=The project name already exists
workspace_name_is_null=Workspace name cannot be null
workspace_name_already_exists=The workspace name already exists
workspace_does_not_belong_to_user=The current workspace does not belong to the current user
organization_does_not_belong_to_user=The current organization does not belong to the current user
file_cannot_be_null=File cannot be empty!
edit_load_test_not_found=Cannot edit test, test not found=
run_load_test_not_found=Cannot run test, test not found=
run_load_test_file_not_found=Unable to run test, unable to get test file meta information, test ID=
run_load_test_file_content_not_found=Cannot run test, cannot get test file content, test ID=
run_load_test_file_init_error=Failed to run test, failed to initialize run environment, test ID=
load_test_is_running=Load test is running, please wait.
node_deep_limit=The node depth does not exceed 5 layers!
no_nodes_message=No node message
duplicate_node_ip=Duplicate IPs
only_one_k8s=Only one K8s can be added
organization_id_is_null=Organization ID cannot be null
max_thread_insufficient=The number of concurrent users exceeds

View File

@ -0,0 +1,21 @@
error_lang_invalid=语言参数错误
load_test_already_exists=测试名称不能重复
project_name_is_null=项目名称不能为空
project_name_already_exists=项目名称已存在
workspace_name_is_null=工作空间名不能为空
workspace_name_already_exists=工作空间名已存在
workspace_does_not_belong_to_user=当前工作空间不属于当前用户
organization_does_not_belong_to_user=当前组织不属于当前用户
file_cannot_be_null=文件不能为空!
edit_load_test_not_found=无法编辑测试,未找到测试:
run_load_test_not_found=无法运行测试,未找到测试:
run_load_test_file_not_found=无法运行测试无法获取测试文件元信息测试ID
run_load_test_file_content_not_found=无法运行测试无法获取测试文件内容测试ID
run_load_test_file_init_error=无法运行测试初始化运行环境失败测试ID
load_test_is_running=测试正在运行, 请等待
node_deep_limit=节点深度不超过5层
no_nodes_message=没有节点信息
duplicate_node_ip=节点 IP 重复
only_one_k8s=只能添加一个 K8s
organization_id_is_null=组织 ID 不能为空
max_thread_insufficient=并发用户数超额

View File

@ -1,23 +0,0 @@
{
"error_lang_invalid": "语言参数错误",
"load_test_already_exists": "测试名称不能重复",
"project_name_is_null": "项目名称不能为空",
"project_name_already_exists": "项目名称已存在",
"workspace_name_is_null": "工作空间名不能为空",
"workspace_name_already_exists": "工作空间名已存在",
"workspace_does_not_belong_to_user": "当前工作空间不属于当前用户",
"organization_does_not_belong_to_user": "当前组织不属于当前用户",
"file_cannot_be_null": "文件不能为空!",
"edit_load_test_not_found": "无法编辑测试,未找到测试:",
"run_load_test_not_found": "无法运行测试,未找到测试:",
"run_load_test_file_not_found": "无法运行测试无法获取测试文件元信息测试ID",
"run_load_test_file_content_not_found": "无法运行测试无法获取测试文件内容测试ID",
"run_load_test_file_init_error": "无法运行测试初始化运行环境失败测试ID",
"load_test_is_running": "测试正在运行, 请等待",
"node_deep_limit": "节点深度不超过5层",
"no_nodes_message": "没有节点信息",
"duplicate_node_ip": "节点 IP 重复",
"only_one_k8s": "只能添加一个 K8s",
"organization_id_is_null": "组织 ID 不能为空",
"max_thread_insufficient": "并发用户数超额"
}