springboot+vue前后端分离国际化
一, 概要
前端使用vue-i18n框架来实现国际化功能,国际化数据存储在数据库里,由后端接口提供,使用pinia缓存。
后端国际化数据使用redis缓存。
方案1.后端使用拦截器对响应中的异常信息做本地化。
方案2.前端使用拦截器对响应中的异常信息做本地化。
特殊业务数据及数据字典的国际化数据在数据库中分类存储,前端在数据字典接口进行本地化,在特殊业务页面做特殊本地化。
二, 前端
1. 安装vue-i18n
yarn add vue-i18n #vue3使用9以上版本 yarn add vue-i18n@9
2.请求后端国际化数据
export function getTranslationTable() { return request({ url: '/openApi/translation/frontTable', method: 'get' }) }
3.注册vue-i18n,当前选择语言从cookie获取
import { getTranslationTable } from '@/api/system/language' import { getLang } from '@/utils/lang'; const lang = getLang(); getTranslationTable().then(resp => { const i18n = createI18n({ locale: lang, messages: resp }); console.log(i18n); app.use(i18n); });
4.当前语言写入cookie
<el-select v-model="$i18n.locale" @change="changeLocale" size="default" placeholder="Select" style="width:120px;margin-top:8px"> <el-option v-for="dict in language_type" :key="dict.value" :label="dict.label" :value="dict.value" /> import { setLang } from '@/utils/lang'; const { proxy } = getCurrentInstance(); const { language_type } = proxy.useDict("language_type"); function changeLocale(newVal){ setLang(newVal); }
5.cookie管理工具
import Cookies from 'js-cookie' const LangKey = 'lang' export function getLang() { if(!Cookies.get(LangKey)){ setLang('zh'); } return Cookies.get(LangKey); } export function setLang(lang) { return Cookies.set(LangKey, lang) } export function removeLang() { return Cookies.remove(LangKey) }
6.对后端请求加入当前选择语言至header
import { getLang } from '@/utils/lang' // 创建axios实例 const service = axios.create({ ... }) // request拦截器 service.interceptors.request.use(config => {
... config.headers['lang'] = getLang(); return config; }, error => { console.log(error) Promise.reject(error) })
7.前端国际化使用
//组件外 {{$t("common.systemName")}} //组件内 $t("common.systemName")
三, 后端
1. 数据库定义
DROP TABLE IF EXISTS tb_language; CREATE TABLE tb_language( `ID` INT NOT NULL AUTO_INCREMENT COMMENT 'ID' , `REVISION` VARCHAR(200) COMMENT '乐观锁' , `CREATED_BY` VARCHAR(200) COMMENT '创建人' , `CREATED_TIME` TIMESTAMP COMMENT '创建时间' , `UPDATED_BY` VARCHAR(200) COMMENT '更新人' , `UPDATED_TIME` TIMESTAMP COMMENT '更新时间' , `PROJECT` VARCHAR(40) COMMENT '系统工程' , `MODULE` VARCHAR(40) COMMENT '模块' , `FORM` VARCHAR(40) COMMENT '形式' , `KEY_NAME` VARCHAR(200) COMMENT '键名' , `ZH` VARCHAR(500) COMMENT '中文' , `JA` VARCHAR(500) COMMENT '日文' , `EN` VARCHAR(500) COMMENT '英文' , `KO` VARCHAR(255) COMMENT '韩文' , `FR` VARCHAR(255) COMMENT '法文' , `DE` VARCHAR(255) COMMENT '德文' , `ES` VARCHAR(255) COMMENT '西班牙文' , `PT` VARCHAR(255) COMMENT '葡萄牙文' , `IT` VARCHAR(255) COMMENT '意大利文' , `RU` VARCHAR(255) COMMENT '俄文' , `AR` VARCHAR(255) COMMENT '阿拉伯文' , PRIMARY KEY (ID) ) COMMENT = '语言';
2.生成从dao至view页面的增删改查功能
基础的增删改查功能,这里不做详述。
增删改时可通过删除缓存实现自动同步缓存,也可提供刷新按钮手动刷新。
3. 提供获取缓存数据接口
针对前端和后端分配提供缓存数据接口,缓存未命中时从数据库更新缓存。
前端接口:
public Map<String, Map<String, Map<String, String>>> getManageFrontLangTable() { List<TbLanguage> list = LanguageUtils.getLanguageCache("manage"); if (StringUtils.isEmpty(list)) { TbLanguage tbLanguage = new TbLanguage(); tbLanguage.setProject("manage"); list = selectTbLanguageList(tbLanguage); if (StringUtils.isNotEmpty(list)) { LanguageUtils.setLanguageCache("manage", list); } } Map<String,Map<String, Map<String,String>>> map = new HashMap<>(); List<SysDictData> language_type = sysDictTypeService.selectDictDataByType("language_type"); for (TbLanguage language : list) { for (SysDictData sysDictData : language_type) { map.putIfAbsent(sysDictData.getDictValue(),new HashMap<>()); map.get(sysDictData.getDictValue()).putIfAbsent(language.getModule(),new HashMap<>()); map.get(sysDictData.getDictValue()).get(language.getModule()).put(language.getKeyName(),language.getValue(sysDictData.getDictValue())); } } return map; }
后端接口:
public String getApiValue(String lang, String key) { Map<String,String> languageMap = LanguageUtils.getLanguageApiCache(key); if (languageMap == null) { TbLanguage tbLanguage = new TbLanguage(); tbLanguage.setProject("api"); List<TbLanguage> list = selectTbLanguageList(tbLanguage); if (StringUtils.isNotEmpty(list)) { List<SysDictData> language_type = sysDictTypeService.selectDictDataByType("language_type"); for (TbLanguage tbLanguage1 : list) { Map<String, String> map = new HashMap<>(); for (SysDictData sysDictData : language_type) { map.put(sysDictData.getDictValue(),tbLanguage1.getValue(sysDictData.getDictValue())); } LanguageUtils.setApiLanguageCache(tbLanguage1.getKeyName(),map); } } languageMap = LanguageUtils.getLanguageApiCache(key); } if (languageMap==null){ return ""; } return languageMap.get(lang); }
4a.(后端翻译后端信息的情况)设置异常拦截器,对需要前端显示的异常进行国际化处理
@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @Autowired private ITbLanguageService tbLanguageService;
...
@ExceptionHandler(ServiceException.class) public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) { String lang = request.getHeader("lang"); if (e.getKey()!=null){ String msg = tbLanguageService.getApiValue(lang, e.getKey().toString()); if (StringUtils.isNotEmpty(msg)){ e.setMessage(msg); } } log.error(e.getMessage(), e); Integer code = e.getCode(); return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); }
4b.(前端翻译后端信息的情况)
4b.1 定义i18n对象并导出,翻译信息从后端获取
import { createI18n } from 'vue-i18n' import { getLang } from '@/utils/lang' import axios from 'axios' const lang = getLang(); const messages = await axios({ baseURL: import.meta.env.VITE_APP_BASE_API, // 超时 timeout: 10000, url: '/openApi/translation/manageFrontTable', method: 'get' }); const i18n = createI18n({ legacy: false, globalInjection: true, locale: lang, messages: messages.data }); export default i18n
4b.2 定义响应拦截器,对后端信息进行翻译后返回
import i18n from '@/utils/i18n'
// 响应拦截器 axios.interceptors.response.use(res => { ...
const msg = errorCode[code] || res.data.msg || errorCode['default']
const msgLocal = i18n.global.t(msg);
if (code !== 200) { ElMessage({ message: msgLocal, type: 'error' }) return Promise.reject(new Error(msgLocal)) } else { return Promise.resolve(res.data) } }, error => { ... } )