一、 需求分析
多语言需求包括:
● 界面文本:按钮、标题、页面、提示、异常提示等。
● 格式化本地化:日期
● 运行时切换语言:顶部切换语言,并刷新页面即可切换。
● 多语言包管理:产品线按需加载,避免一次性加载全部语言文件。
● 可扩展性:方便新增语言、更新翻译内容。
● 语言类型:中文、英文
● 登陆界面选择多语言,选择租户多语言支持
二、 架构
1. 前端负责统一语言切换入口,并把语言状态写入后端 ThreadContextHolder (后端通过ThreadContextHolder 获取到当前的语言标识,并对异常抛出提示进行多语言切换)
// 添加请求拦截器,设置语言和时区请求头
apiClient.interceptors.request.use(config => {
config.headers['Imom-Language'] = getLocaleFromLocalStorage();
config.headers['Time-Zone'] = getTimeZone();
return config;
}, error => Promise.reject(error));
2. 后端各自维护语言包(DB),支持模块化加载、按模块/按键查询、缓存(Redis)和版本控制。
目标:低耦合、按需加载、体验一致、性能可控。
三、 iMOM架构图
具体流程:1、后端引入多语言依赖,启动项目对项目的异常文本及异常枚举进行收集,收集成功后存入数据库及新增或更新redis和进程缓存;2、前端也会收集前端相关的语言包对数据进行收集,并通过api调用存入数据库,更新redis和进程缓存,最后通过api获取相关的语言包进行多语言处理
四、缓存策略
一、 首选 Redis 做缓存(hash方式),Key 规则:lang:locale}:{lang_key}
二、 缓存 TTL:永不过期,更新数据,同时更新缓存并通过变更事件主动清除
三、 更新流程:当更新语言(POST),服务写 DB -> 清理/更新 Redis -> 清理/更新 进程缓存
四、进程缓存,可以通过I18nCacheHelper获取目标语言的译文
五、后端具体使用方法
1、 引入依赖
<dependency>
<groupId>com.sie.mbm.mom</groupId>
<artifactId>framework-language</artifactId>
</dependency>2、 添加配置文件
language:
enabled: true #是否启用多语言
modules: imom-kernel #扫描的模块名称
translate: #对接翻译api不填入无法翻译
api:
appid:
securityKey: 
3、 手动添加Json文件

3.1、文件组成
Json文件分为【i18n-seed-en_US.json、i18n-seed-zh_CN.json、i18n-seed-en_US-ext.json、i18n-seed-zh_CN-ext.json】四个文件,
其中i18n-seed-en_US.json、i18n-seed-zh_CN.json为【必须创建且不可更改内容】,而i18n-seed-en_US-ext.json、i18n-seed-zh_CN-ext.json文件为扩展Json文件【非必要、主要用于手动添加自定义的语言包,如果扩展文件的语言key跟非扩展文件的语言key重复,那么会优先保存扩展文件的语言信息】
3.2、处理
1.启动项目后会自动扫描项目下所有异常文本以及异常枚举类
2.注意:异常枚举类必须为【*ExtEnum、*ErrorEnum】这两种命名规范才可以扫描得到
3.扫描、处理成功后数据会存入数据库、redis、线程缓存
3.3、异常规范
1、避免写以下异常代码
throw new BusinessException("模版不存在, id=" + id);
throw new BusinessException(String.format("业务包上传nexus失败:%s",response.getBody()));
2、应该写以下异常代码
throw new BusinessException("参数:语言包不能为空");
throw new BusinessException(PrintErrorCodeEnum.TEMPLATE_UPLOAD_IS_FAIL, "1");
4、I18nCacheHelper工具类(获取语言译文)
4.1、检查是否有引入cache依赖
<dependency>
<groupId>com.sie.mbm.mom</groupId>
<artifactId>framework-cache</artifactId>
</dependency>4.2、工具类提供get方法获取相关语言包
/**
* 获取中文或英文指定的模块语言包的key的value值
*
* @param locale 中英文类型,zh_CN / en_US,请规范使用LanguageConstant常量的ZH_LOCALE、EN_LOCALE
* @param langKey 语言key
* @return 语言value值
*/
public static String get(String locale, String langKey)5、启动多语言成功的标识
六、前端使用配置
1、更新工程工作区
打开工作区的imom配置文件,位于工作区根目录下的imom.config.json或imom.config.js。
添加产品模块标识,如mpm产品线则配置:"appModule": "mpm"
{
"appModule": "mpm",
"apps": [
// 模块
]
}2、更新组件库版本
检查工作区下的具体工程的/package.json中的依赖,组件库@dme/snack-ui的版本,最低要求>=v0.0.78,推荐>=0.0.82。
3、更新打包配置
打开工程的vite配置文件/vite.config.js,添加获取模块的相关代码:
// ...
import { existsSync } from "fs";
import { createRequire } from "module";
// ...
let appModule = "imom";
try {
const configFileNames = ["imom.config.json", "imom.config.js"];
for (let i = 0; i < configFileNames.length; i++) {
const imomConfigFileName = configFileNames[i];
const imomConfigFilePath = resolve(__dirname, `../../${imomConfigFileName}`);
// console.log("imomConfigFilePath :>> ", imomConfigFilePath);
if (existsSync(imomConfigFilePath)) {
// 创建 require 函数
const require = createRequire(import.meta.url);
const imomConfigFileData = require(imomConfigFilePath);
// console.log("imomConfigFileData :>> ", imomConfigFileData);
if (imomConfigFileData?.appModule) {
appModule = imomConfigFileData.appModule;
}
break;
}
}
} catch (err) {
console.error("从imom.config获取模块出错:\n", err);
}
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
// ...
const vite_env = {
// ...
appModule,
// ...
};
// ...
return {
// ...
define: {
// ...
"process.vite_env": vite_env
},
// ...
};
});4、更新应用配置
修改工程的应用配置文件/src/config/index.js。
// ...
export const emptyJsonString = '""';
export const defaultLocale = "imom";
// ...
// 多语言的key
const LOCAL_KEY_LANG = "imesLang";
let locale = defaultLocale;
try {
const langStorageObj = localStorage.getItem(LOCAL_KEY_LANG) || emptyJsonString;
if (langStorageObj.startsWith("FOREVER_")) {
locale = JSON.parse(langStorageObj.replace("FOREVER_", ""))?.data || defaultLocale;
}
} catch (err) {
console.error(err);
}
// ...
export default {
// ...
appModule: process.vite_env?.appModule || "",
// ...
LOCAL_KEY_LANG: LOCAL_KEY_LANG, // 多语言的key
LOCAL_KEY_I18N: "KEY_I18N", // i18n语言包的key
LOCAL_KEY_I18N_MISSING: "missingI18nData", // 缺失翻译的i18n数据
locale: locale, // 语言标识
// ...
};
5、添加多语言api
在工程添加多语言api文件/src/api/modules/languagePack.js。
import request from "@/api/index";
import config from "@/config";
const basePath = `/kernel/language-pack`;
export default {
/**
* 获取语言包
* @param {string} locale 语言标识
* @returns {Promise<any>}
*/
getLang(locale = config.locale) {
return request({
url: `${basePath}/get-lang?locale=${encodeURIComponent(locale)}`,
method: "get"
});
},
/**
* 批量更新语言包
* @param {object} data 更新数据
* @returns {Promise<any>}
*/
updateBatchLang(data) {
return request({
url: `${basePath}/update-batch-lang`,
data
});
},
/**
* 批量新增语言包
* @param {object} data 插入数据
* @returns {Promise<any>}
*/
insertBatchLang(data) {
return request({
url: `${basePath}/insert-batch-lang`,
data
});
},
/**
* 批量新增语言文本
* @param {object} data 插入数据
* @param {string} data.locale 语言标识
* @param {string} data.module 模块标识
* @param {object} data.pack 语言包
* @returns {Promise<any>}
*/
insertBatchByLocale(data) {
return request({
url: `${basePath}/insert-batch-by-locale`,
data
});
},
/**
* 获取所有语言包
* @returns {Promise<any>}
*/
getAll() {
return request({
url: `${basePath}/get-all`,
method: "get"
});
},
/**
* 更新语言包
* @param {object} data
* @param {string} data.locale 模块标识
* @param {object} data.pack 语言包
* @returns {Promise<any>}
*/
updateBatchByLocale(data) {
return request({
url: `${basePath}/update-batch-by-locale`,
data
});
}
};
6、更新路由配置
修改工程的路由配置文件/src/router/index.js。
// ...
import { initI18nData, initEnumTreeData, getToken } from "@dme/snack-ui";
import languagePackApi from "@/api/modules/languagePack";
// ...
// ...
/**
* 初始化session数据
*/
async function initSessionData(next) {
try {
await Promise.all([
initEnumTreeData(dictApi.searchAll), // 数据字典
initDataBtnTree(), // 按钮权限
initI18nData(languagePackApi.getLang) // 国际化
]);
} catch (err) {
console.error("initSessionData error >>> ", err);
}
}
// ...