add:国际化
This commit is contained in:
@@ -17,7 +17,6 @@ package org.nl.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nl.common.logging.annotation.Log;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.nl.common.utils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.nl.config.SpringContextHolder;
|
||||
import org.nl.config.language.I18nManagerService;
|
||||
|
||||
public class I18nUtil {
|
||||
private static I18nManagerService i18nManagerService = SpringContextHolder.getBean(I18nManagerService.class);
|
||||
|
||||
public static String msg(String key,String...args) {
|
||||
if(StringUtils.isBlank(key)){
|
||||
return "";
|
||||
}
|
||||
String msg = i18nManagerService.getCurrentMessage(key);
|
||||
for(String arg: args){
|
||||
msg += arg;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package org.nl.config.language;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@Service
|
||||
public class I18nManagerService implements InitializingBean, DisposableBean {
|
||||
private final I18nProperties properties;
|
||||
private final Map<String, JSONObject> langCache = new HashMap<>();
|
||||
private final Map<String, Long> lastModifiedTimes = new HashMap<>();
|
||||
private final Map<String, String> filePaths = new HashMap<>();
|
||||
private ScheduledExecutorService scheduler;
|
||||
// 默认语言(从配置中获取)
|
||||
private String defaultLang;
|
||||
|
||||
public I18nManagerService(I18nProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 初始化默认语言
|
||||
if (!properties.getSupportedLanguages().isEmpty()) {
|
||||
defaultLang = properties.getSupportedLanguages().get(0);
|
||||
}
|
||||
|
||||
// 加载所有语言文件
|
||||
for (String lang : properties.getSupportedLanguages()) {
|
||||
loadLangFile(lang);
|
||||
}
|
||||
|
||||
// startRefreshScheduler();
|
||||
}
|
||||
|
||||
// 新增:不带语言参数的方法,从上下文获取当前语言
|
||||
public JSONObject getCurrentLangConfig() {
|
||||
String lang = LangContextHolder.getLangOrDefault(defaultLang);
|
||||
return getLangConfig(lang);
|
||||
}
|
||||
|
||||
// 新增:不带语言参数的方法,从上下文获取当前语言
|
||||
public String getCurrentMessage(String key) {
|
||||
String lang = LangContextHolder.getLangOrDefault(defaultLang);
|
||||
return getMessage(lang, key);
|
||||
}
|
||||
|
||||
// 以下为原有方法(保持不变)
|
||||
private void loadLangFile(String lang) throws IOException {
|
||||
String fileName = lang + ".js";
|
||||
String fullPath = properties.getLocation() + fileName;
|
||||
Resource resource = getResource(fullPath);
|
||||
|
||||
if (!resource.exists() && properties.isFallbackToClasspath()) {
|
||||
fullPath = "language/i18n/lang_" + fileName;
|
||||
resource = new ClassPathResource(fullPath);
|
||||
}
|
||||
|
||||
if (!resource.exists()) {
|
||||
throw new IOException("Language file not found: " + fullPath);
|
||||
}
|
||||
|
||||
String content = new String(Files.readAllBytes(Paths.get(resource.getURI())),
|
||||
StandardCharsets.UTF_8);
|
||||
JSONObject config = parseJsConfig(content);
|
||||
|
||||
langCache.put(lang, config);
|
||||
lastModifiedTimes.put(lang, getLastModifiedTime(resource));
|
||||
filePaths.put(lang, fullPath);
|
||||
}
|
||||
|
||||
private Resource getResource(String path) {
|
||||
if (path.startsWith("file:")) {
|
||||
return new FileSystemResource(path.substring("file:".length()));
|
||||
} else if (path.startsWith("classpath:")) {
|
||||
return new ClassPathResource(path.substring("classpath:".length()));
|
||||
} else {
|
||||
return new FileSystemResource(path);
|
||||
}
|
||||
}
|
||||
|
||||
private long getLastModifiedTime(Resource resource) throws IOException {
|
||||
if (resource instanceof FileSystemResource) {
|
||||
return ((FileSystemResource) resource).getFile().lastModified();
|
||||
} else if (resource instanceof ClassPathResource) {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private JSONObject parseJsConfig(String jsContent) {
|
||||
String jsonStr = jsContent.replace("var config = ","").trim();
|
||||
return JSON.parseObject(jsonStr);
|
||||
}
|
||||
|
||||
public JSONObject getLangConfig(String lang) {
|
||||
if (!langCache.containsKey(lang)) {
|
||||
lang = defaultLang;
|
||||
}
|
||||
return langCache.get(lang);
|
||||
}
|
||||
|
||||
public String getMessage(String lang, String key) {
|
||||
JSONObject config = getLangConfig(lang);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] keyParts = key.split("\\.");
|
||||
JSONObject current = config;
|
||||
|
||||
for (int i = 0; i < keyParts.length; i++) {
|
||||
if (i == keyParts.length - 1) {
|
||||
return StringUtils.isEmpty(current.getString(keyParts[i])) ? key : current.getString(keyParts[i]);
|
||||
}
|
||||
current = current.getJSONObject(keyParts[i]);
|
||||
if (current == null) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
// private void startRefreshScheduler() {
|
||||
// scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
// scheduler.scheduleAtFixedRate(() -> {
|
||||
// try {
|
||||
// checkAndRefreshFiles();
|
||||
// } catch (Exception e) {
|
||||
// System.err.println("Error checking for language file updates: " + e.getMessage());
|
||||
// }
|
||||
// }, 0, properties.getRefreshInterval(), TimeUnit.SECONDS);
|
||||
// }
|
||||
|
||||
// private void checkAndRefreshFiles() throws IOException {
|
||||
// for (String lang : properties.getSupportedLanguages()) {
|
||||
// String fullPath = filePaths.get(lang);
|
||||
// if (StringUtils.isEmpty(fullPath)) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// Resource resource = getResource(fullPath);
|
||||
// if (!resource.exists()) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// long currentLastModified = getLastModifiedTime(resource);
|
||||
// if (currentLastModified > lastModifiedTimes.getOrDefault(lang, 0L)) {
|
||||
// loadLangFile(lang);
|
||||
// System.out.println("Language file updated: " + lang + " (" + fullPath + ")");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
langCache.clear();
|
||||
lastModifiedTimes.clear();
|
||||
filePaths.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.nl.config.language;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "i18n")
|
||||
public class I18nProperties {
|
||||
// 语言文件存放路径,可以是外部路径或classpath
|
||||
private String location = "D:\\i18n\\";
|
||||
// 热更新检查间隔(秒)
|
||||
private long refreshInterval = 60;
|
||||
// 支持的语言列表
|
||||
private List<String> supportedLanguages = Arrays.asList("en", "zh", "ja");
|
||||
// 当外部文件不存在时,是否回退到classpath中的默认文件
|
||||
private boolean fallbackToClasspath = true;
|
||||
|
||||
// getter和setter方法
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public long getRefreshInterval() {
|
||||
return refreshInterval;
|
||||
}
|
||||
|
||||
public void setRefreshInterval(long refreshInterval) {
|
||||
this.refreshInterval = refreshInterval;
|
||||
}
|
||||
|
||||
public List<String> getSupportedLanguages() {
|
||||
return supportedLanguages;
|
||||
}
|
||||
|
||||
public void setSupportedLanguages(List<String> supportedLanguages) {
|
||||
this.supportedLanguages = supportedLanguages;
|
||||
}
|
||||
|
||||
public boolean isFallbackToClasspath() {
|
||||
return fallbackToClasspath;
|
||||
}
|
||||
|
||||
public void setFallbackToClasspath(boolean fallbackToClasspath) {
|
||||
this.fallbackToClasspath = fallbackToClasspath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.nl.config.language;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class LangContextHolder {
|
||||
// 线程本地变量,存储当前线程的语言标识
|
||||
private static final ThreadLocal<String> LANG_HOLDER = new ThreadLocal<>();
|
||||
|
||||
// 设置当前线程的语言
|
||||
public static void setLang(String lang) {
|
||||
LANG_HOLDER.set(lang);
|
||||
}
|
||||
|
||||
// 获取当前线程的语言
|
||||
public static String getLang() {
|
||||
return LANG_HOLDER.get();
|
||||
}
|
||||
|
||||
// 清除当前线程的语言设置(防止内存泄漏)
|
||||
public static void clear() {
|
||||
LANG_HOLDER.remove();
|
||||
}
|
||||
|
||||
// 获取当前语言,如果未设置则返回默认语言
|
||||
public static String getLangOrDefault(String defaultLang) {
|
||||
String lang = getLang();
|
||||
return StringUtils.hasText(lang) ? lang : defaultLang;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.nl.config.language;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Component
|
||||
public class LangInterceptor implements HandlerInterceptor {
|
||||
|
||||
// 支持的语言列表(实际项目中可从配置获取)
|
||||
private final I18nProperties properties;
|
||||
|
||||
public LangInterceptor(I18nProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 1. 从请求参数获取语言(如 ?lang=zh)
|
||||
String lang = request.getParameter("lang");
|
||||
|
||||
// 2. 如果参数不存在,可从Header获取(如 Accept-Language)
|
||||
if (lang == null || lang.isEmpty()) {
|
||||
lang = request.getHeader("Accept-Language");
|
||||
// 简单处理,只取前两位(如 zh-CN -> zh)
|
||||
if (lang != null && lang.length() >= 2) {
|
||||
lang = lang.substring(0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 验证语言是否在支持的列表中
|
||||
if (lang != null && properties.getSupportedLanguages().contains(lang)) {
|
||||
LangContextHolder.setLang(lang);
|
||||
} else {
|
||||
// 不支持的语言使用默认语言
|
||||
LangContextHolder.setLang(properties.getSupportedLanguages().get(0));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
// 清除线程变量,防止内存泄漏
|
||||
LangContextHolder.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.nl.config.language;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final LangInterceptor langInterceptor;
|
||||
|
||||
public WebConfig(LangInterceptor langInterceptor) {
|
||||
this.langInterceptor = langInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 对所有请求生效
|
||||
registry.addInterceptor(langInterceptor).addPathPatterns("/**");
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,20 @@ login:
|
||||
# 是否限制单用户登录
|
||||
single-login: false
|
||||
|
||||
i18n:
|
||||
# 语言文件存放路径(支持外部路径和classpath)
|
||||
location: D:/i18n/lang_
|
||||
# 热更新检查间隔(秒)
|
||||
refresh-interval: 30
|
||||
# 支持的语言列表
|
||||
supported-languages:
|
||||
- zh
|
||||
- en
|
||||
- in
|
||||
- ja
|
||||
# 当外部文件不存在时,是否回退到classpath中的默认文件
|
||||
fallback-to-classpath: true
|
||||
|
||||
#密码加密传输,前端公钥加密,后端私钥解密
|
||||
rsa:
|
||||
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
var config = {
|
||||
"lang": "English222",
|
||||
"platform": {
|
||||
"title": "NOBLELIFT Platform",
|
||||
"tip1": "The user name cannot be empty",
|
||||
"tip2": "The password cannot be empty",
|
||||
"tip3": "当前语言,111111英语"
|
||||
},
|
||||
"common": {
|
||||
"home": "Dashboard",
|
||||
"Layout_setting": "Layout Setting"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var config = {
|
||||
"lang": "English222",
|
||||
"platform": {
|
||||
"title": "NOBLELIFT Platform",
|
||||
"tip1": "The user name cannot be empty",
|
||||
"tip2": "The password cannot be empty",
|
||||
"tip3": "当前语言,1111印度尼西3333333亚语"
|
||||
},
|
||||
"common": {
|
||||
"home": "Dashboard",
|
||||
"Layout_setting": "Layout Setting"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var config = {
|
||||
"lang": "English222",
|
||||
"platform": {
|
||||
"title": "NOBLELIFT Platform",
|
||||
"tip1": "The user name cannot be empty",
|
||||
"tip2": "The password cannot be empty",
|
||||
"tip3": "The verification code cannot be empty"
|
||||
},
|
||||
"common": {
|
||||
"home": "Dashboard",
|
||||
"Layout_setting": "Layout Setting"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var config = {
|
||||
"lang": "English222",
|
||||
"platform": {
|
||||
"title": "NOBLELIFT Platform",
|
||||
"tip1": "The user name cannot be empty",
|
||||
"tip2": "The password cannot be empty",
|
||||
"tip3": "当前语言中文"
|
||||
},
|
||||
"common": {
|
||||
"home": "Dashboard",
|
||||
"Layout_setting": "Layout Setting"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user