【Vue3+Element Plus】从0-1搭建后台管理系统(05)-多语言国际化vue I18n

1,675 阅读6分钟

【Vue3+Element Plus】从0-1搭建后台管理系统(05)-多语言国际化vue I18n

什么是 Vue I18n?

Vue I18n 是 Vue.js 的国际化插件。它可以轻松地将一些本地化功能集成到您的 Vue.js 应用程序中。

vue-i18n官网

安装vue-i18n

npm install vue-i18n@9

引入i18n并创建实例对象

src/创建locales文件夹,在该目录下创建index.ts以及两个语言包zh.tsen.ts文件:

locales/en.ts:

export default {
  common: {
    home: 'home',
    about: 'about',
    signIn: 'sign in',
    signUp: 'sign up'
  },
  route: {
    doshboard: 'Doshboard',
    workbench: 'Workbench',
    ...
  },
  ...
}

locales/zh.ts:

export default {
  common: {
    home: '首页',
    about: '关于',
    signIn: '登录',
    signUp: '注册'
  },
  route: {
    doshboard: '仪表盘',
    workbench: '工作台',
    ...
  },
  ...
}

locales/index.ts:

import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
// 导入语言
import en from './en'
import zh from './zh'

/** 获取浏览器界面语言,默认语言。 navigator.language: zh-CN
 * 我们只需要它的前缀:zh,所以使用replace将zh后面的替换为空
 */
let currentLanguage = navigator.language.replace(/-[A-Za-z]*/, '')
console.log(currentLanguage)

/** 从本地缓存localStorage中获取语言环境 */
const locales = localStorage.getItem('locales')

/** 如果本地缓存中记录了语言环境,则使用本地缓存记录的语言环境  */
if (locales !== null) {
  currentLanguage = JSON.parse(locales)?.locale
}

/** 创建i18n实例对象 */
const i18n = createI18n({
  locale: currentLanguage,
  legacy: false, // 防止组件引入i18n,vite脚手架报错
  messages: {
    zh,
    en
  }
})

/**
 * 安装i8n
 * @param app vue实例对象
 */
export const setupI18n = (app: App): void => {
  app.use(i18n)
}

/** Nav.vue头部导航栏中英文切换下拉菜单使用 */
export const langs = [
  { key: 'zh', title: '中文' },
  { key: 'en', title: 'English' }
]

main.ts中引入安装i18n:

import { createApp } from 'vue'
/** 导入安装i18n函数 */
import { setupI18n } from './locales'

/** 创建vue实例对象 */
const app = createApp(App)

/** 安装i18n国际化语言 */
setupI18n(app)

在layout/index.vue组件中测试使用

直接在组件的模板中的插值表达式中使用i18n提供的$t()函数渲染不同语言包下对应的中英文,我们只需要传入一个类似获取对象属性值的字符串(在messages中有的)给$t(),就可以渲染不同语言包下对应的内容。

<el-main>
  <span class="text-3xl font-bold underline dark:text-white"> {{ $t('common.main') }} </span>
</el-main>

image.png

image.png

因为我的浏览器默认语言是中文,所以使用的是中文的语言包。

效果:

image.png

渲染已有语言列表

基础的准备我们已经做好了,那么接下来就是将我们现有的语言在语言切换下拉菜单中渲染出来。

在我们做准备的时候,在src/lacales/index.ts中向外暴露了下面这个对象:

/** Nav.vue头部导航栏中英文切换下拉菜单使用 */
export const langs = [
  { key: 'zh', title: '中文' },
  { key: 'en', title: 'English' }
]

现在我们在layout/Header/components/Language.vue组件中将它引入进来并对中英文切换下拉菜单进行渲染:

<template>
  <!-- 中英文切换 -->
  <el-dropdown trigger="click">
    <span class="el-dropdown-link">中文</span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item v-for="lang in langs" :key="lang.key">{{ lang.title }}</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
/** 导入已有的语言列表 */
import { langs } from '@/locales'
defineOptions({
  name: 'Language'
})
</script>

实现中英文切换功能

将现有语言列表进行渲染完成以后,接下来就是要对中英文切换功能的实现了。

因为中英文切换是影响到全局的,所以得将中英文切换的状态,存放到状态管理pinia中。

具体步骤如下:

  1. src/store/modules目录下新建index.ts文件:

    image.png

  2. 定义locales仓库:

    /** 导入定义store的函数,类型 */
    import { defineStore, type Store } from 'pinia'
    /** 导入pinia实例对象 */
    import { store } from '../index.ts'
    
    /** 定义locales仓库 */
    const useLocalesStore = defineStore(
        'locales', 
        () => {
            /** 返回数据 */
            return {}
        }
    )
    
    /**
     * 在 setup() 外部使用 useLocalesStore
     * 详情见:https://pinia.vuejs.org/zh/ssr/
    */
    const useLocalesStoreHook = (): Store => {
        return useLocalesStore(store)
    }
    
  3. 初始化一个当前语言状态

    export const useLocalesStore = defineStore(
      'locales',
      () => {
        // 当前语言
        const locale = ref(i18n.global.locale.value)
        
        return { locale }
      }
    )
    

    这里locale的初始值取i18n初始化时的值,因为i18n初始化时的值是当前浏览器的语言或者是本地缓存中记录的,这样我们一进入后台就可以得到当前的语言。

  4. 设置当前语言的方法

    /** setLocale函数的lang参数的类型 */
    type lang = 'zh' | 'en'
    
    export const useLocalesStore = defineStore(
      'locales',
      () => {
        // 当前语言
        const locale = ref(i18n.global.locale.value)
        
        /**
         * 设置当前语言
         * @param lang 设置成的语言
         */
        function setLocale(lang: lang): void {
          /** 改变当前所处语言环境的状态,刷新的时候i18n会取这个值作为当前的语言环境 */
          locale.value = lang
          /** 点击的时候立马修改语言(切换语言) */
          i18n.global.locale.value = lang
        }
    
        return { locale, setLocale }
      }
    )
    
  5. layout/Header/components/Language.vue中引入locales仓库并使用仓库中的locale属性setLocale方法

    <template>
      <!-- 中英文切换 -->
      <el-dropdown trigger="click" @command="changeLanguage">
        <span class="el-dropdown-link">中文</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item v-for="lang in langs" :key="lang.key" :command="lang.key">
                {{ lang.title }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </template>
    
    <script setup lang="ts">
    /** 导入已有的语言列表 */
    import { langs } from '@/locales'
    /** 导入locales仓库 */
    import { useLocalesStore } from '@/store/modules/locales'
    defineOptions({
      name: 'Language'
    })
    
    /** 初始化locales仓库 */
    const localesStore = useLocalesStore()
    
    /** 切换当前语言环境 */
    const changeLanguage = (locale: any): void => {
      localesStore.setLocale(locale)
    }
    </script>
    
    <style lang="scss" scoped></style>
    

    绑定el-dronpdowncommand事件,从事件处理函数中获取到点击的el-dropdown-item绑定的command的值传给locales仓库的setLocale函数,从而改变当前的语言环境。

    效果:

    locales.gif 从上图发现一个问题,就是不管当我们切换英文或者是中文也好,头部语言下拉菜单一直显示的都是我们写死的“中文”,所以接下来我们要把它换成是动态的。

头部语言动态显示

初始化一个currentLocales变量用来动态展示头部语言动态显示,初始值为现有语言列表中的当前语言环境:

<!-- 中英文切换 -->
<el-dropdown trigger="click" @command="changeLanguage">
    <span class="el-dropdown-link">{{ currentLocale }}</span>
    ...
</el-dropdown>
<script setup lang="ts">
import { ref } from 'vue'
/** 导入解构pinia状态保持响应式的函数 */
import { storeToRefs } from 'pinia'
/** 导入已有的语言列表 */
import { langs } from '@/locales'
/** 导入locales仓库 */
import { useLocalesStore } from '@/store/modules/locales'
defineOptions({
  name: 'Language'
})

/** 初始化locales仓库 */
const localesStore = useLocalesStore()

/** 从localesStore仓库中解构属性,并保持响应式 */
const { locale: curLocale } = storeToRefs(localesStore)

/** 头部导航栏语言下拉菜单显示的当前语言环境 */
const currentLocale = ref(langs.find((locale) => locale.key === curLocale.value)?.title ?? '')

/** 切换当前语言环境 */
const changeLanguage = (locale: any): void => {
  /** 当切换语言环境的时候,同时切换显示的当前语言 */
  currentLocale.value = locale.title
  ...
}
</script>

效果:

locales.gif

以为这就彻底完成了吗?那就有点和我一样草率了,试着刷新看看~是不是发现了什么?

对的,刷新又变回以前的语言环境了。这时候我们就得将修改的语言环境记录到本地缓存中啦!!

本地持久化

当我们将语言设置为English后,如果刷新页面的话,语言环境又会回到中文。所以我们需要做一点操作来保持国际化状态。

locales.gif

这里采用的是Pinia搭配localStorage来完成保持国际化状态。

所以使用到pinia的持久化插件pinia-plugin-persistedstate

  • 安装:

    npm install pinia-plugin-persistedstate
    
  • 将插件添加到pinia实例中:

    import { createPinia } from 'pinia'
    /** 导入pinia持久化插件 */
    import piniaPluginpersistedState from 'pinia-plugin-persistedstate'
    import { type App } from 'vue'
    
    /** 向外暴露pinia实例,方便再setup语法糖意外的地方使用store */
    export const store = createPinia()
    /** 在pinia的实例上添加持久化插件 */
    store.use(piniaPluginpersistedState)
    /**
     * 在vue实例对象上注册pinia
     * @param app vue实例对象
     */
    export function setupStore(app: App): void {
      app.use(store)
    }
    
  • 用法:

    1. Option Store:
      import { defineStore } from 'pinia' 
      export const useStore = defineStore('main', { 
          state: () => { 
              return { 
                  someState: 'hello pinia', 
              } 
          }, 
          persist: true, 
      })
      
    2. Setup Store:
      import { defineStore } from 'pinia'
      export const useStore = defineStore( 
          'main', 
          () => { 
              const someState = ref('hello pinia') 
              return { someState } 
          }, 
          { 
              persist: true, 
          }, 
      )
      
  • store/modules/locales.ts中使用:

    ...
    /** 创建 locales Store */
    export const useLocalesStore = defineStore(
      'locales',
      () => {
        ...
      },
      // 本地持久化缓存
      {
        persist: true
      }
    )
    ...
    
  • 效果:

    locales.gif