{T}

国际化开发

要求:根据语言环境需要切换 msg 的值

Vue SFC
<script>
  const messages = {
    en: {
      msg: 'hello world'
    },
    zh: {
      msg: '你好世界'
    }
  }
  
  let locale = 'en'
 
  function t(key) {
    return messages[locale][key]
  }
 
  let msg = t('msg')
  console.log(msg);
  // 修改 locale, 重新执行 t 方法,获取不同语言环境下的值
</script>

总结:

  1. 通过一个变量来 控制 语言环境
  2. 所有语言环境下的数据源要 预先 定义好
  3. 通过一个方法来获取 当前语言指定属性 的值
  4. 该值即为国际化下展示值

5-03:基于 vue-i18n V9 的国际化实现方案分析

vue 的项目中,我们不需要手写这么复杂的一些基础代码,可以直接使用 vue-i18n 进行实现(注意:vue3 下需要使用 V 9.xi18n

vue-i18n 的使用可以分为四个部分:

  1. 创建 messages 数据源
  2. 创建 locale 语言变量
  3. 初始化 i18n 实例
  4. 注册 i18n 实例

那么接下来我们就去实现以下:

  1. 安装 vue-i18n

    plaintext
    npm install vue-i18n@next
  2. 创建 i18n/index.js 文件

  3. 创建 messages 数据源

    js
    const messages = {
      en: {
        msg: {
          test: 'hello world'
        }
      },
      zh: {
        msg: {
          test: '你好世界'
        }
      }
    }
  4. 创建 locale 语言变量

    js
    const locale = 'en'
  5. 初始化 i18n 实例

    js
    import { createI18n } from 'vue-i18n'
     
    const i18n = createI18n({
      // 使用 Composition API 模式,则需要将其设置为false
      legacy: false,
      // 全局注入 $t 函数
      globalInjection: true,
      locale,
      messages
    })
  6. i18n 注册到 vue 实例

    js
    export default i18n
  7. main.js 中导入

    js
    // i18n (PS:导入放到 APP.vue 导入之前,因为后面我们会在 app.vue 中使用国际化内容)
    import i18n from '@/i18n'
    ...
    app.use(i18n)
  8. layout/components/Sidebar/index.vue 中使用 i18n

    html
    <h1 class="logo-title" v-if="$store.getters.sidebarOpened">
            {{ $t('msg.test') }}
    </h1>
  9. 修改 locale 的值,即可改变展示的内容

截止到现在我们已经实现了 i18n 的最基础用法,那么解下来我们就可以在项目中使用 i18n 完成国际化。

项目中完成国际化分成以下几步进行:

  1. 封装 langSelect 组件用于修改 locale
  2. 导入 el-locale 语言包
  3. 创建自定义语言包

5-04:方案落地:封装 langSelect 组件

  1. 定义 store/app.js

    js
    import { LANG } from '@/constant'
    import { getItem, setItem } from '@/utils/storage'
    export default {
      namespaced: true,
      state: () => ({
        ...
        language: getItem(LANG) || 'zh'
      }),
      mutations: {
        ...
        /**
         * 设置国际化
         */
        setLanguage(state, lang) {
          setItem(LANG, lang)
          state.language = lang
        }
      },
      actions: {}
    }
     
  2. constant 中定义常量

    js
    // 国际化
    export const LANG = 'language'
  3. 创建 components/LangSelect/index

    Vue SFC
    <template>
      <el-dropdown
        trigger="click"
        class="international"
        @command="handleSetLanguage"
      >
        <div>
          <el-tooltip content="国际化" :effect="effect">
            <svg-icon icon="language" />
          </el-tooltip>
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :disabled="language === 'zh'" command="zh">
              中文
            </el-dropdown-item>
            <el-dropdown-item :disabled="language === 'en'" command="en">
              English
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </template>
     
    <script setup>
    import { useI18n } from 'vue-i18n'
    import { defineProps, computed } from 'vue'
    import { useStore } from 'vuex'
    import { ElMessage } from 'element-plus'
     
    defineProps({
      effect: {
        type: String,
        default: 'dark',
        validator: function(value) {
          // 这个值必须匹配下列字符串中的一个
          return ['dark', 'light'].indexOf(value) !== -1
        }
      }
    })
     
    const store = useStore()
    const language = computed(() => store.getters.language)
     
    // 切换语言的方法
    const i18n = useI18n()
    const handleSetLanguage = lang => {
      i18n.locale.value = lang
      store.commit('app/setLanguage', lang)
      ElMessage.success('更新成功')
    }
    </script>
  4. navbar 中导入 LangSelect

    Vue SFC
    <template>
      <div class="navbar">
        ...
        <div class="right-menu">
          <lang-select class="right-menu-item hover-effect" />
          <!-- 头像 -->
          ...
        </div>
      </div>
    </template>
     
    <script setup>
    import LangSelect from '@/components/LangSelect'
    ...
    </script>
     
    <style lang="scss" scoped>
    .navbar {
      ...
     
      .right-menu {
        ...
     
        ::v-deep .right-menu-item {
          display: inline-block;
          padding: 0 18px 0 0;
          font-size: 24px;
          color: #5a5e66;
          vertical-align: text-bottom;
     
          &.hover-effect {
            cursor: pointer;
          }
        }
     
        ...
    }
    </style>
     

5-05:方案落地:element-plus 国际化处理

截止到目前,我们的国际化内容已经基本功能已经处理完成了。接下来需要处理的就是对应的语言包,有了语言包就可以实现整个项目中的所有国际化处理了。

那么对于语言包来说,我们整个项目中会分成两部分:

  1. element-plus 语言包:用来处理 element 组件的国际化功能
  2. 自定义语言包:用来处理 element 组件的国际化功能

那么首先我们先来处理 element-plus 语言包:

**按照正常的逻辑,我们是可以通过 element-ui 配合 vue-i18n来实现国际化功能的,但是目前的 element-plus 尚未提供配合 vue-i18n 实现国际化的方式! **

所以说,我们暂时只能先去做临时处理,等到 element-plus 支持 vue-i18n 功能之后,我们再进行对接实现

那么临时处理我们怎么去做呢?

  1. 升级 element-plus 到最新版本

    plaintext
    npm i element-plus

    目前课程中使用的最新版本为:^1.1.0-beta.15

  2. 升级版本之后,左侧 menu 菜单无法正常显示,这是因为 element-plus 修改了 el-submenu 的组件名称

  3. layout/components/Sidebar/SidebarItem 中,修改 el-submenuel-sub-menu

  4. 接下来实现国际化

  5. plugins/index 中导入 element 的中文、英文语言包:

js
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/lib/locale/lang/en'
  1. 注册 element 时,根据当前语言选择使用哪种语言包

    js
    import store from '@/store'
     
    export default app => {
      app.use(ElementPlus, {
        locale: store.getters.language === 'en' ? en : zhCn
      })
    }

5-06:方案落地:自定义语言包国际化处理

处理完 element 的国际化内容之后,接下来我们来处理 自定义语言包

自定义语言包我们使用了 commonJS 导出了一个对象,这个对象就是所有的 自定义语言对象

大家可以在 资料/lang 中获取到所有的语言包

  1. 复制 lang 文件夹到 i18n

  2. lang/index 中,导入语言包

    js
    import mZhLocale from './lang/zh'
    import mEnLocale from './lang/en'
  3. messages 中注册到语言包

    js
    const messages = {
      en: {
        msg: {
          ...mEnLocale
        }
      },
      zh: {
        msg: {
          ...mZhLocale
        }
      }
    }

5-07:方案落地:处理项目国际化内容

在处理好了国际化的语言包之后,接下来我们就可以应用国际化功能到我们的项目中

对于我们目前的项目而言,需要进行国际化处理的地方主要分为:

  1. 登录页面
  2. navbar 区域
  3. sidebar 区域
  4. 面包屑区域

那么这一小节,我们先来处理前两个

登录页面:

login/index

Vue SFC
<template>
  <div class="login-container">
    ...
      <div class="title-container">
        <h3 class="title">{{ $t('msg.login.title') }}</h3>
          <lang-select class="lang-select" effect="light"></lang-select>
      </div>
 
      ...
 
      <el-button
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        :loading="loading"
        @click="handleLogin"
        >{{ $t('msg.login.loginBtn') }}</el-button
      >
      
      <div class="tips" v-html="$t('msg.login.desc')"></div>
    </el-form>
  </div>
</template>
 
<script setup>
import { useI18n } from 'vue-i18n'
...
// 验证规则
const i18n = useI18n()
const loginRules = ref({
  username: [
    {
      ...
      message: i18n.t('msg.login.usernameRule')
    }
  ],
  ...
})
...
</script>
 
 

login/rules

js
import i18n from '@/i18n'
export const validatePassword = () => {
  return (rule, value, callback) => {
    if (value.length < 6) {
      callback(new Error(i18n.global.t('msg.login.passwordRule')))
    } else {
      callback()
    }
  }
}
 

navbar 区域

layout/components/navbar

Vue SFC
<template>
  <div class="navbar">
    ...
        <template #dropdown>
          <el-dropdown-menu class="user-dropdown">
            <router-link to="/">
              <el-dropdown-item> {{ $t('msg.navBar.home') }} </el-dropdown-item>
            </router-link>
            <a target="_blank" href="">
              <el-dropdown-item>{{ $t('msg.navBar.course') }}</el-dropdown-item>
            </a>
            <el-dropdown-item divided @click="logout">
              {{ $t('msg.navBar.logout') }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>

components/LangSelect/index

Vue SFC
<el-tooltip :content="$t('msg.navBar.lang')" :effect="effect">
       ...
    
    
const handleSetLanguage = lang => {
  ...
  ElMessage.success(i18n.t('msg.toast.switchLangSuccess'))
}

5-08:方案落地:sidebar 与 面包屑 区域的国际化处理

sidebar 区域

目前对于 sidebar 而言,显示的文本是我们在定义路由表时的 title

html
<span>{{ title }}</span>

我们可以 title 作为语言包内容的 key 进行处理

创建 utils/i18n 工具模块,用于 title 转化为国际化内容

js
import i18n from '@/i18n'
export function generateTitle(title) {
  return i18n.global.t('msg.route.' + title)
}
 

layout/components/Sidebar/MenuItem.vue 中导入该方法:

Vue SFC
<template>
  ...
  <span>{{ generateTitle(title) }}</span>
</template>
 
<script setup>
import { generateTitle } from '@/utils/i18n'
...
</script>
 

最后修改下 sidebarHeader 的内容

php+HTML
<h1 class="logo-title" v-if="$store.getters.sidebarOpened">
	imooc-admin
</h1>

面包屑区域:

components/Breadcrumb/index

Vue SFC
<template>
...
    <!-- 不可点击项 -->
    <span v-if="index === breadcrumbData.length - 1" class="no-redirect">{{
        generateTitle(item.meta.title)
        }}</span>
    <!-- 可点击项 -->
    <a v-else class="redirect" @click.prevent="onLinkClick(item)">{{
        generateTitle(item.meta.title)
        }}</a>
...
</template>
 
<script setup>
import { generateTitle } from '@/utils/i18n'
...
</script>
 

5-09:方案落地:国际化缓存处理

我们希望在 刷新页面后,当前的国际化选择可以被保留,所以想要实现这个功能,那么就需要进行 国际化的缓存处理

此处的缓存,我们依然通过两个方面进行:

  1. vuex 缓存
  2. LocalStorage 缓存

只不过这里的缓存,我们已经在处理 langSelect 组件时 处理完成了,所以此时我们只需要使用缓存下来的数据即可。

i18n/index 中,创建 getLanguage 方法:

js
import store from '@/store'
/**
 * 返回当前 lang
 */
function getLanguage() {
  return store && store.getters && store.getters.language
}

修改 createI18nlocalegetLanguage()

js
const i18n = createI18n({
  ...
  locale: getLanguage()
})

5-10:国际化方案总结

国际化是前端项目中的一个非常常见的功能,那么在前端项目中实现国际化主要依靠的就是 vue-i18n 这个第三方的包。

关于国际化的实现原理大家可以参照 国际化实现原理 这一小节,这里我们就不再赘述了。

i18n 的使用,整体来说就分为这么四步:

  1. 创建 messages 数据源
  2. 创建 locale 语言变量
  3. 初始化 i18n 实例
  4. 注册 i18n 实例

核心的内容其实就是 数据源的部分,但是大家需要注意,如果你的项目中使用了 第三方组件库 ,那么不要忘记 第三方组件库的数据源 需要 单独 进行处理!