策码奔腾

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 => {
    ...
  }
)

 

posted @ 2022-11-09 16:51  策码奔腾  阅读(3149)  评论(0编辑  收藏  举报
console.log('欢迎');