vue后台管理系统——添加i18n国际化功能——技能提升

昨天在写后台管理系统时,遇到一个需求就是需要实现国际化功能。

antdelement-ui这两个框架其实都是有国际化的。

具体展示形式就是如下:

点击右上角头部的语言,切换语言,然后整个系统的文字都改变成对应的语言展示。
在这里插入图片描述
切换成英文的效果如下:
在这里插入图片描述
下面对整个系统的国际化进行介绍:

1.安装i18n插件,如果是使用的vue-admin的框架,则已经安装过了

具体i18n插件是否安装过了,可以在package,json中进行查看。

npm install vue-i18n --save

2.在utils文件夹中添加i18n.js文件——路由的国际化需要单独处理,其他的国际化是可以用公用国际化文件的

文件内容如下:

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import routesI18n from '@/router/i18n'
import {getI18nKey} from '@/utils/routerUtil'
import CommonI18n from '@/locales/common.i18n';

/**
 * 创建 i18n 配置
 * @param locale 本地化语言
 * @param fallback 回退语言
 * @returns {VueI18n}
 */
function initI18n(locale, fallback) {
  Vue.use(VueI18n)
  let i18nOptions = {
    locale,
    fallbackLocale: fallback,
    silentFallbackWarn: true,
    silentTranslationWarn: true,
    ...CommonI18n,
  }
  return new VueI18n(i18nOptions)
}

/**
 * 根据 router options 配置生成 国际化语言
 * @param lang
 * @param routes
 * @param valueKey
 * @returns {*}
 */
function generateI18n(lang, routes, valueKey) {
  routes.forEach(route => {
    let keys = getI18nKey(route.fullPath).split('.')
    let value = valueKey === 'path' ? route[valueKey].split('/').filter(item => !item.startsWith(':') && item != '').join('.') : route[valueKey]
    lang.assignProps(keys, value)
    if (route.children) {
      generateI18n(lang, route.children, valueKey)
    }
  })
  return lang
}

/**
 * 格式化 router.options.routes,生成 fullPath
 * @param routes
 * @param parentPath
 */
function formatFullPath(routes, parentPath = '') {
  routes.forEach(route => {
    let isFullPath = route.path.substring(0, 1) === '/'
    route.fullPath = isFullPath ? route.path : (parentPath === '/' ? parentPath + route.path : parentPath + '/' + route.path)
    if (route.children) {
      formatFullPath(route.children, route.fullPath)
    }
  })
}

/**
 * 从路由提取国际化数据
 * @param i18n
 * @param routes
 */
function mergeI18nFromRoutes(i18n, routes) {
  formatFullPath(routes)
  const CN = generateI18n(new Object(), routes, 'name')
  const US = generateI18n(new Object(), routes, 'path')
  i18n.mergeLocaleMessage('CN', CN)
  i18n.mergeLocaleMessage('US', US)
  const messages = routesI18n.messages
  Object.keys(messages).forEach(lang => {
    i18n.mergeLocaleMessage(lang, messages[lang])
  })
}

export {
  initI18n,
  mergeI18nFromRoutes,
  formatFullPath
}

3.router中添加i18n.js文件——路由的国际化需要单独处理,其他的国际化是可以用公用国际化文件的

文件内容如下:

注意:这个文件中的格式要跟路由配置文件中的格式要保持一致。比如user下面的children子页面有userCenter和changePassword两个,则需要像下面的对象一样做嵌套。

messages中的对象,多种语言就需要写多个对象,对象的key命名最好跟语言中的key保持一致。

module.exports = {
  messages: {
    CN: {
      home: { name: '首页' },
      demo: {
        name: '演示页',
      },
      user: {
        name: '个人中心',
        userCenter: { name: '个人信息' },
        changePassword: { name: '修改账户密码' },
      },
    },
    US: {
      home: { name: 'home' },
      demo: {
        name: 'Demo Page',
      },
       user: {
        name: 'user',
        userCenter: { name: 'userCenter' },
        changePassword: { name: 'changePassword' },
      },
    },
    HK: {
      home: { name: '首頁' },
      demo: {
        name: '演示頁',
      },
       user: {
        name: '個人中心',
        userCenter: { name: '個人信息' },
        changePassword: { name: '修改賬戶密碼' },
      },
    },
  },
};

4.utils中添加routerUtil.js文件

文件内容如下:

import routerMap from '@/router/async/router.map'
import {mergeI18nFromRoutes} from '@/utils/i18n'
import Router from 'vue-router'
import deepMerge from 'deepmerge'
import basicOptions from '@/router/async/config.async'

//应用配置
let appOptions = {
  router: undefined,
  i18n: undefined,
  store: undefined
}

/**
 * 设置应用配置
 * @param options
 */
function setAppOptions(options) {
  const {router, store, i18n} = options
  appOptions.router = router
  appOptions.store = store
  appOptions.i18n = i18n
}

/**
 * 根据 路由配置 和 路由组件注册 解析路由
 * @param routesConfig 路由配置
 * @param routerMap 本地路由组件注册配置
 */
function parseRoutes(routesConfig, routerMap) {
  let routes = []
  routesConfig.forEach(item => {
    // 获取注册在 routerMap 中的 router,初始化 routeCfg
    let router = undefined, routeCfg = {}
    if (typeof item === 'string') {
      router = routerMap[item]
      routeCfg = {path: router.path || item, router: item}
    } else if (typeof item === 'object') {
      router = routerMap[item.router]
      routeCfg = item
    }
    if (!router) {
      console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`)
      router = typeof item === 'string' ? {path: item, name: item} : item
    }
    // 从 router 和 routeCfg 解析路由
    const route = {
      path: routeCfg.path || router.path || routeCfg.router,
      name: routeCfg.name || router.name,
      component: router.component,
      redirect: routeCfg.redirect || router.redirect,
      meta: {
        authority: routeCfg.authority || router.authority || routeCfg.meta?.authority || router.meta?.authority || '*',
        icon: routeCfg.icon || router.icon ||  routeCfg.meta?.icon || router.meta?.icon,
        page: routeCfg.page || router.page ||  routeCfg.meta?.page || router.meta?.page,
        link: routeCfg.link || router.link ||  routeCfg.meta?.link || router.meta?.link
      }
    }
    if (routeCfg.invisible || router.invisible) {
      route.meta.invisible = true
    }
    if (routeCfg.children && routeCfg.children.length > 0) {
      route.children = parseRoutes(routeCfg.children, routerMap)
    }
    routes.push(route)
  })
  return routes
}

/**
 * 加载路由
 * @param routesConfig {RouteConfig[]} 路由配置
 */
function loadRoutes(routesConfig) {
  //兼容 0.6.1 以下版本
  /*************** 兼容 version < v0.6.1 *****************/
  if (arguments.length > 0) {
    const arg0 = arguments[0]
    if (arg0.router || arg0.i18n || arg0.store) {
      routesConfig = arguments[1]
      console.error('the usage of signature loadRoutes({router, store, i18n}, routesConfig) is out of date, please use the new signature: loadRoutes(routesConfig).')
      console.error('方法签名 loadRoutes({router, store, i18n}, routesConfig) 的用法已过时, 请使用新的方法签名 loadRoutes(routesConfig)。')
    }
  }
  /*************** 兼容 version < v0.6.1 *****************/

  // 应用配置
  const {router, store, i18n} = appOptions

  // 如果 routesConfig 有值,则更新到本地,否则从本地获取
  if (routesConfig) {
    store.commit('account/setRoutesConfig', routesConfig)
  } else {
    routesConfig = store.getters['account/routesConfig']
  }
  // 如果开启了异步路由,则加载异步路由配置
  const asyncRoutes = store.state.setting.asyncRoutes
  if (asyncRoutes) {
    if (routesConfig && routesConfig.length > 0) {
      const routes = parseRoutes(routesConfig, routerMap)
      const finalRoutes = mergeRoutes(basicOptions.routes, routes)
      formatRoutes(finalRoutes)
      router.options = {...router.options, routes: finalRoutes}
      router.matcher = new Router({...router.options, routes:[]}).matcher
      router.addRoutes(finalRoutes)
    }
  }
  // 提取路由国际化数据
  mergeI18nFromRoutes(i18n, router.options.routes)
  // 初始化Admin后台菜单数据
  const rootRoute = router.options.routes.find(item => item.path === '/')
  const menuRoutes = rootRoute && rootRoute.children
  if (menuRoutes) {
    store.commit('setting/setMenuData', menuRoutes)
  }
}

/**
 * 合并路由
 * @param target {Route[]}
 * @param source {Route[]}
 * @returns {Route[]}
 */
function mergeRoutes(target, source) {
  const routesMap = {}
  target.forEach(item => routesMap[item.path] = item)
  source.forEach(item => routesMap[item.path] = item)
  return Object.values(routesMap)
}

/**
 * 深度合并路由
 * @param target {Route[]}
 * @param source {Route[]}
 * @returns {Route[]}
 */
function deepMergeRoutes(target, source) {
  // 映射路由数组
  const mapRoutes = routes => {
    const routesMap = {}
    routes.forEach(item => {
      routesMap[item.path] = {
        ...item,
        children: item.children ? mapRoutes(item.children) : undefined
      }
    })
    return routesMap
  }
  const tarMap = mapRoutes(target)
  const srcMap = mapRoutes(source)

  // 合并路由
  const merge = deepMerge(tarMap, srcMap)

  // 转换为 routes 数组
  const parseRoutesMap = routesMap => {
    return Object.values(routesMap).map(item => {
      if (item.children) {
        item.children = parseRoutesMap(item.children)
      } else {
        delete item.children
      }
      return item
    })
  }
  return parseRoutesMap(merge)
}

/**
 * 格式化路由
 * @param routes 路由配置
 */
function formatRoutes(routes) {
  routes.forEach(route => {
    const {path} = route
    if (!path.startsWith('/') && path !== '*') {
      route.path = '/' + path
    }
  })
  formatAuthority(routes)
}

/**
 * 格式化路由的权限配置
 * @param routes 路由
 * @param pAuthorities 父级路由权限配置集合
 */
function formatAuthority(routes, pAuthorities = []) {
  routes.forEach(route => {
    const meta = route.meta
    const defaultAuthority = pAuthorities[pAuthorities.length - 1] || {permission: '*'}
    if (meta) {
      let authority = {}
      if (!meta.authority) {
        authority = defaultAuthority
      }else if (typeof meta.authority === 'string') {
        authority.permission = meta.authority
      } else if (typeof meta.authority === 'object') {
        authority = meta.authority
        const {role} = authority
        if (typeof role === 'string') {
          authority.role = [role]
        }
        if (!authority.permission && !authority.role) {
          authority = defaultAuthority
        }
      }
      meta.authority = authority
    } else {
      const authority = defaultAuthority
      route.meta = {authority}
    }
    route.meta.pAuthorities = pAuthorities
    if (route.children) {
      formatAuthority(route.children, [...pAuthorities, route.meta.authority])
    }
  })
}

/**
 * 从路由 path 解析 i18n key
 * @param path
 * @returns {*}
 */
function getI18nKey(path) {
  const keys = path.split('/').filter(item => !item.startsWith(':') && item != '')
  keys.push('name')
  return keys.join('.')
}

/**
 * 加载导航守卫
 * @param guards
 * @param options
 */
function loadGuards(guards, options) {
  const {beforeEach, afterEach} = guards
  const {router} = options
  beforeEach.forEach(guard => {
    if (guard && typeof guard === 'function') {
      router.beforeEach((to, from, next) => guard(to, from, next, options))
    }
  })
  afterEach.forEach(guard => {
    if (guard && typeof guard === 'function') {
      router.afterEach((to, from) => guard(to, from, options))
    }
  })
}

export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes, formatRoutes, setAppOptions}

5.routerasync文件夹中添加router.map文件

在这里插入图片描述
里面的内容比较多,有需要的可以留个邮箱给我,我打包发给你。

6.重点是commonI18n文件,在locales文件夹中

在这里插入图片描述
common.i18n.js文件中的内容如下:

import CN from './CN';
import US from './US';
import HK from './HK';
//多种语言,则需要有多个文件用于区分

// 全局公共的国际化定义
export default {
  messages: {
    CN,
    US,
    HK,
  },
};

CN.js为例:

// 全局公共的国际化定义 - CN
export default   {
  user:'用户',
  creator:'创建人',
  orderNo: '订单编号',
  search:'搜索',
  cancel:'取消',
  CancelEditing:'取消编辑',
  edit:'编辑',
  submit:'提交',
  reset:'重置',
  ....
}

对应的US.js文件内容如下:

// 全局公共的国际化定义 - US
export default {
  user:'User',
  creator:'Creator',
  orderNo: 'Order No',
  search: 'Search',
  cancel:'Cancel',
  edit:'Edit',
  CancelEditing:'Cancel Editing',
  submit:'Submit',
  reset:'Reset',
  ...
}

这个算是国际化的公共文件,国际化是就近原则,如果是单个页面有单独的i18n文件,则会从单独的i18n文件中查找对应的字段,没有找不到,则会从公共的i18n文件中去查找。

7.以单个文件国际化为例:

在这里插入图片描述
在这里插入图片描述
页面中使用国际化字段的方式$t(xxx)
在这里插入图片描述

8.路由的国际化文件需要跟路由配置文件进行匹配,其他页面的国际化要跟公共国际化文件的格式保持一致即可。

9.通过以上的步骤,菜单+页面中静态的文字都可以实现国际化了,但是接口返回的数据国际化,则需要接口返回不同的文字了。

此时可以在axios请求时,则请求头上添加当前的语言类型。

9.1 在axios拦截器文件中的请求部分添加如下的代码

在这里插入图片描述
我需要在拦截器.js文件中获取vuex中存储的lang字段的值,此时是拿不到vuex中的数据的,因为this是undefined

因此需要在main.js文件中添加如下的内容:
在这里插入图片描述

将vue挂载到window上,则其他页面都可以通过window.vm获取到vue了

...

window.vm = new Vue({
  router,
  store,
  i18n,
  render: (h) => h(App),
}).$mount('#app');

拦截器中的写法:

const reqCommon = {
  /**
   * 发送请求之前做些什么
   * @param config axios config
   * @param options 应用配置 包含: {router, i18n, store, message}
   * @returns {*}
   */
  onFulfilled(config, options) {
    const { message } = options;
    const { url, xsrfCookieName, headers } = config;
    // if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
    //   message.warning('认证 token 已过期,请重新登录')
    // }
    if (
      headers.Authorization &&
      xsrfCookieName &&
      !Cookie.get(xsrfCookieName)
    ) {
      message.warning('认证 token 已过期,请重新登录');
    }
    config.headers['Authorization'] = Cookie.get(xsrfHeaderName);
    window.vm.$store.commit(
      'setting/setLang',
      localStorage.getItem('language') || 'CN'
    );
    console.log('语言', window.vm.$store.state.setting.lang);
    config.headers['language'] = window.vm.$store.state.setting.lang;
    return config;
  },
  .......

上面的代码最重要的就是:

window.vm.$store.commit(
  'setting/setLang',
  localStorage.getItem('language') || 'CN'
);
console.log('语言', window.vm.$store.state.setting.lang);
config.headers['language'] = window.vm.$store.state.setting.lang;

为什么要存储到localStorage中?因为,在切换语言时,接口也需要重新请求,则也就是说整个页面全部刷新,此时最简单的方法就是window.vm.$router.go(0)实现页面的刷新。

页面刷新时,vuex中的setting/lang的默认值是CN简体中文,为了能够存储上次切换的语言类型,可以存储到本地localStorage,这样浏览器不关闭的时候,这个缓存还是有的。

vuex中的setting文件中的setLang方法也需要改变

在这里插入图片描述

setLang(state, lang) {
  state.lang = lang;
  if (localStorage.getItem('language') != lang) {
    window.vm.$router.go(0);
  }
  localStorage.setItem('language', lang);
  console.log('setLang', window.vm.$route);
},

完成!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1557.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

燕山大学-面向对象程序设计实验-实验7 多态性:函数与运算符重载-实验报告

CSDN的各位友友们你们好,今天千泽为大家带来的是燕山大学-面向对象程序设计实验-实验5 派生与继承&#xff1a;单重派生-实验报告,接下来让我们一起进入c的神奇小世界吧,相信看完你也能写出自己的 实验报告!本系列文章收录在专栏 燕山大学面向对象设计报告中 ,您可以在专栏中找…

【C语言进阶】内存函数

天生我材必有用&#xff0c;千金散尽还复来。 ——李白 目录 前言 一.memcpy函数 ​1.实现memcpy函数 2.模拟实现memcpy函数 二.memmove函数 1.实现memmove函数 2.模拟实现memmove函数 三.memcpy函数和memmove函数的关系 四.memcm…

2023金三银四--我们遇到的那些软件测试面试题【功能/接口/自动化/性能等等】

一、面试技巧题(主观题) 序号面试题1怎么能在技术没有那么合格的前提下给面试官留个好印象&#xff1f;2面试时&#xff0c;如何巧妙地避开不会的问题&#xff1f;面试遇到自己不会的问题如何机智的接话&#xff0c;化被动为主动&#xff1f;3对于了解程度的技能&#xff0c;被…

【Docker】什么是Docker?Docker的安装、加速

文章目录Docker出现的背景解决问题docker理念容器与虚拟机比较容器发展简史传统虚拟机技术容器虚拟化技术Docker安装官方网站安装前提Docker的基本组成镜像容器仓库Docker平台架构图解CentOS7安装Docker确定你是CentOS7及以上版本卸载旧版本yum安装gcc相关安装需要的软件包设置…

用 ChatGPT 辅助学好机器学习

文章目录一、前言二、主要内容&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 探索更高效的学习方法可能是有志者共同的追求&#xff0c;用好 ChatGPT&#xff0c;先行于未来。 作为一个人工智能大语言模型&#xff0c;ChatGPT 可以在帮助初…

Pandas 与 PySpark 强强联手,功能与速度齐飞

Pandas做数据处理可以说是yyds&#xff01;而它的缺点也是非常明显&#xff0c;Pandas 只能单机处理&#xff0c;它不能随数据量线性伸缩。例如&#xff0c;如果 pandas 试图读取的数据集大于一台机器的可用内存&#xff0c;则会因内存不足而失败。 另外 pandas 在处理大型数据…

Linux分文件编程:静态库与动态库的生成和使用

目录 一&#xff0c;Linux库引入之分文件编程 ① 简单说明 ② 分文件编程优点 ③ 操作逻辑 ④ 代码实现说明 二&#xff0c;Linux库的基本说明 三&#xff0c;Linux库之静态库的生成与使用 ① 静态库命名规则 ② 静态库制作步骤 ③ 静态库的使用 四&#xff0c;Linu…

django-celery-beat搭建定时任务

一、创建django项目和app 1、安装定时任务第三方包 pip install django-celery-beat # 插件用来动态配置定时任务,一般会配合 django_celery_results 一起使用&#xff0c;所以一起安装 django_celery_results pip install django_celery_results pip install eventlet # win…

Keil MDK6要来了,将嵌入式软件开发水平带到新高度,支持跨平台(2023-03-11)

注&#xff1a;这个是MDK6&#xff0c;不是MDK5 AC6&#xff0c;属于下一代MDK视频版&#xff1a; https://www.bilibili.com/video/BV16s4y157WF Keil MDK6要来了&#xff0c;将嵌入式软件开发水平带到新高度&#xff0c;支持跨平台一年一度的全球顶级嵌入式会展Embedded Wor…

操作系统(1.3)--习题

一、课堂习题 1、一个作业第一 次执行时用了5min ,而第二次执行时用了6min,这说明了操作系统的( )特点。 A、并发性 B、共享性 C、虚拟性 D、不确定性 D 2、在计算机系统中,操作系统是( )。 A、处于裸机之上的第一层软件 B、处于硬件之下的低层软件 C、处于应用软件之上的系统软…

对象的创建以及数组中常见的属性与方法

&#xff08;一&#xff09;对象创建的三种方法 1、利用对象字面量创建对象 const obj{ name:小开心 } 2、利用new Object创建对象 const obj1new Object({ name:小开心 }) 3、利用构造函数创建对象 构造函数&#xff1a;是一种特殊的函数&#xff0c;主要用来初始化对象&…

Vector的扩容机制

到需要扩容的时候&#xff0c;Vector会根据需要的大小&#xff0c;创建一个新数组&#xff0c;然后把旧数组的元素复制进新数组。 我们可以看到&#xff0c;扩容后&#xff0c;其实是一个新数组&#xff0c;内部元素的地址已经改变了。所以扩容之后&#xff0c;原先的迭代器会…

【Spring事务】声明式事务 使用详解

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 声明式事务一、编程式事务二、声明式事务&…

PMSM矢量控制笔记(1.1)——电机的机械结构与运行原理

前言&#xff1a;重新整理以前的知识和文章发现&#xff0c;仍然有许多地方没有学得明白&#xff0c;懵懵懂懂含含糊糊的地方多如牛毛&#xff0c;尤其是到了真正实际写东西或者做项目时&#xff0c;如果不是系统的学习了知识&#xff0c;很容易遇到问题就卡壳&#xff0c;也想…

C语言的灵魂---指针(基础)

C语言灵魂指针1.什么是指针&#xff1f;2.指针的大小3.指针的分类3.1比较常规的指针类型3.2指针的解引用操作3.3野指针野指针的成因&#xff1a;4.指针运算4.1指针加减整数4.2指针-指针1.什么是指针&#xff1f; 这个问题我们通常解释为两种情况&#xff1a; 1.指针本质&#…

Unity学习日记13(画布相关)

目录 创建画布 对画布的目标图片进行射线检测 拉锚点 UI文本框使用 按钮 按钮导航 按钮触发事件 输入框 实现单选框 下拉菜单 多选框选项加图片 创建画布 渲染模式 第一个&#xff0c;保持画布在最前方&#xff0c;画布内的内容显示优先级最高。 第二个&#xff0c;…

GitHub 上有些什么好玩的项目?

前言 各个领域模块的都整理了一下&#xff0c;包含游戏、一些沙雕的工具、实用正经的工具以及一些相关的电商项目&#xff0c;希望他们可以给你学习的路上增加几分的乐趣&#xff0c;我们直接进入正题~ 游戏 1.吃豆人 一款经典的游戏开发案例&#xff0c;包括地图绘制、玩家控…

并发基础之线程池(Thread Pool)

目录前言何为线程池线程池优势创建线程池方式直接实例化ThreadPoolExecutor类JUC Executors 创建线程池线程池挖掘Executors简单介绍ThreadPoolExecutor核心类ThreadPoolExecutor 类构造参数含义线程池运行规则线程设置数量结语前言 相信大家都知道当前的很多系统架构都要求高…

echart图表之highcharts

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、HighCharts是什么&#xff1f;二、使用步骤1.引入库2.前端代码3.展现结果4.后台自动截图总结前言 提示&#xff1a;这里可以添加本文要记录的大概内容&…

linux kernel 5.0 inline hook框架

github:https://github.com/WeiJiLab/kernel-hook-framework 一、项目介绍 Usually we want to hack a kernel function, to insert customized code before or after a certain kernel function been called, or to totally replace a function with new one. How can we…