使用 spring boot 国际化
This commit is contained in:
parent
23fecbb283
commit
6feefcc23e
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@ package io.metersphere.controller;
|
|||
import io.metersphere.base.domain.UserRole;
|
||||
import io.metersphere.controller.request.LoginRequest;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.service.UserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.*;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -26,7 +26,7 @@ public class LoginController {
|
|||
@GetMapping(value = "/isLogin")
|
||||
public ResultHolder isLogin() {
|
||||
if (SecurityUtils.getSubject().isAuthenticated()) {
|
||||
return ResultHolder.success(Translator.getLangDes());
|
||||
return ResultHolder.success(LocaleContextHolder.getLocale());
|
||||
}
|
||||
return ResultHolder.error("");
|
||||
}
|
||||
|
|
|
@ -1,271 +1,24 @@
|
|||
package io.metersphere.i18n;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.JavaBeanSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
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 org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Component
|
||||
public class Translator {
|
||||
private static MessageSource messageSource;
|
||||
|
||||
public static final String PREFIX = "$[{";
|
||||
public static final String SUFFIX = "}]";
|
||||
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翻译
|
||||
/**
|
||||
* 单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) {
|
||||
if (StringUtils.isBlank(key)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
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;
|
||||
@Resource
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
Translator.messageSource = messageSource;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,6 @@ spring.flyway.locations=classpath:db/migration
|
|||
spring.flyway.table=metersphere_version
|
||||
spring.flyway.baseline-version=0
|
||||
spring.flyway.encoding=UTF-8
|
||||
spring.flyway.validate-on-migrate=false
|
||||
spring.flyway.validate-on-migrate=false
|
||||
|
||||
spring.messages.basename=i18n/messages
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -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=并发用户数超额
|
|
@ -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": "并发用户数超额"
|
||||
}
|
Loading…
Reference in New Issue