Blog
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)

HiuZing

🍑
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)
  • 前端面试题

  • JavaScript

  • Vue2

  • port

  • CSS

  • Node.js

  • JavaScript优化

  • uniapp

  • Mini Program

  • TypeScript

  • 面向对象编程

  • UI组件

  • Plugin

  • Vue3

    • 教程

    • Vue Router

    • API

    • Vuex

    • 实例处理方案

      • 图标处理方案
      • 本地缓存处理方案
      • 响应拦截器处理方案
      • 登录鉴权处理方案
      • 退出登录处理方案
      • 国际化处理方案
        • 基于 vue-i18n V9 实现方案
          • 代码实现
        • 方案落地:封装 langSelect 组件
        • 方案落地:element-plus 国际化处理
        • 自定义语言包国际化处理
        • 处理项目国际化内容
        • sidebar 与 面包屑 区域的国际化处理
        • 国际化缓存处理
        • 更新:关于 element-plus 国际化问题更新
        • 更新:关于登录页面表单校验提示无法自动国际化的问题
        • 国际化方案总结
        • 接口国际化:处理接口国际化问题
      • 动态换肤处理方案
      • Screenfull全屏处理方案
      • HeaderSearch 处理方案
      • TagsView处理方案
      • Guide 处理方案
      • Excel 导入处理方案
      • 打印详情处理方案
      • 权限受控处理方案
      • 动态表格处理方案
      • 富文本和markdown处理方案
      • 项目部署处理方案
      • 可视化处理方案
    • 文档

    • 用法

  • 性能优化

  • Axios

  • 状态管理

  • React

  • Mock

  • Icon

  • Template

  • 构建工具

  • 项目规范配置

  • Taro

  • SVG

  • React Native

  • 前端
  • Vue3
  • 实例处理方案
HiuZing
2023-03-14
目录

国际化处理方案

# 需求

我们有一个变量 msg ,但是这个 msg 有且只能有两个值:

  1. hello world
  2. 你好世界

要求:根据需要切换 msg 的值

# 代码实现

<script>
  // 1. 定义 msg 值的数据源
  const messages = {
    en: {
      msg: 'hello world'
    },
    zh: {
      msg: '你好世界'
    }
  }
  // 2. 定义切换变量
  let locale = 'en'
  // 3. 定义赋值函数
  function t(key) {
    return messages[locale][key]
  }
  // 4. 为 msg 赋值 
  let msg = t('msg')
  console.log(msg);
  // 修改 locale, 重新执行 t 方法,获取不同语言环境下的值
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 总结

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

# 基于 vue-i18n V9 实现方案

注意

vue3下需要使用V 9.x的i18n

vue-i18n (opens new window) 使用可以分为四个部分:

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

# 代码实现

  1. 安装 vue-i18n

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

  3. 创建 messages 数据源

    const messages = {
      en: {
        msg: {
          test: 'hello world'
        }
      },
      zh: {
        msg: {
          test: '你好世界'
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  4. 创建 locale 语言变量

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

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

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

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

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

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

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

# 方案落地:封装 langSelect 组件

  1. 定义 store/app.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: {}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 在 constant 中定义常量

    // 国际化
    export const LANG = 'language'
    
    1
    2
  3. 在store/getters.js中定义

    language: state => state.app.language
    
    1
  4. 创建 components/LangSelect/index

    <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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
  5. 在 navbar 中导入 LangSelect





     


































    <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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

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

对于语言包来说,整个项目会分成两部分:

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

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

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

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

    npm i element-plus
    
    1

    注意

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

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

  3. 到 layout/components/Sidebar/SidebarItem 中,修改 el-submenu 为 el-sub-menu

  4. 接下来实现国际化

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

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

    import store from '@/store'
    
    export default app => {
      app.use(ElementPlus, {
        locale: store.getters.language === 'en' ? en : zhCn
      })
    }
    
    1
    2
    3
    4
    5
    6
    7

# 自定义语言包国际化处理

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

  1. 在 lang/index 中,导入语言包

    import mZhLocale from './lang/zh'
    import mEnLocale from './lang/en'
    
    1
    2
  2. 在 messages 中注册到语言包

    const messages = {
      en: {
        msg: {
          ...mEnLocale
        }
      },
      zh: {
        msg: {
          ...mZhLocale
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

# 处理项目国际化内容

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

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

登录页面:

login/index

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

login/rules

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()
    }
  }
}
1
2
3
4
5
6
7
8
9
10

navbar 区域

layout/components/navbar

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

components/LangSelect/index

<el-tooltip :content="$t('msg.navBar.lang')" :effect="effect">
       ...
const handleSetLanguage = lang => {
  ...
  ElMessage.success(i18n.t('msg.toast.switchLangSuccess'))
}
1
2
3
4
5
6

# sidebar 与 面包屑 区域的国际化处理

sidebar 区域

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

<span>{{ title }}</span>
1

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

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

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

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

<template>
  ...
  <span>{{ generateTitle(title) }}</span>
</template>

<script setup>
import { generateTitle } from '@/utils/i18n'
...
</script>
1
2
3
4
5
6
7
8
9

最后修改下 sidebarHeader 的内容

<h1 class="logo-title" v-if="$store.getters.sidebarOpened">
	imooc-admin
</h1>
1
2
3

面包屑区域:

在 components/Breadcrumb/index

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 国际化缓存处理

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

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

  1. vuex 缓存
  2. LocalStorage 缓存

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

在 i18n/index 中,创建 getLanguage 方法:

import store from '@/store'
/**
 * 返回当前 lang
 */
function getLanguage() {
  return store && store.getters && store.getters.language
}
1
2
3
4
5
6
7

修改 createI18n 的 locale 为 getLanguage()

const i18n = createI18n({
  ...
  locale: getLanguage()
})
1
2
3
4

# 更新:关于 element-plus 国际化问题更新

现在 element-plus 已经提供了 国际化的处理方案 (opens new window),我们可以直接通过 el-config-provider 组件中的 locale 属性来指定当前国际化环境。

具体代码如下:

  1. 在 src/plugins/element 中 image-20211209105124435
  2. 在 src/App.vue 中 image-20211209105150006

# 更新:关于登录页面表单校验提示无法自动国际化的问题

此问题来源于一个反馈:

https://coding.imooc.com/learn/questiondetail/254087.html

复现方式:

  1. 在一种语言环境下,展示表单校验提示信息
  2. 切换语言环境
    1. 预期:提示信息进行国际化转换
    2. 现实:提示信息未进行国际化转换

GIF 2021-12-14 16-33-19

分析原因:

表单校验提示信息的内容取决于 loginRules 中具体选项的 message 属性,我们对该 message 属性进行了初始赋值:

image-20211214162353339

初始赋值时, i18n 会根据当前语言环境获取到对应的国际化内容。

但是当语言环境改变时,message 属性的 value 未重新获取。(即:依然为 初始赋值 内容)。

因此会出现以上 bug

解决方案:

那么想要解决这个问题,就需要从问题的原因入手。

方案具体分为两步:

  1. message 属性的 value 应该是动态获取的:

    1. 针对 username,它应该为一个计算属性:

      const loginRules = ref({
        username: [
          {
            ...
      -      message: i18n.t('msg.login.usernameRule')  
      +      message: computed(() => {
      +        return i18n.t('msg.login.usernameRule')
      +      })
          }
        ],
        password: [...]
      })
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
    2. 针对 password,因为它的验证为 validator,本身就会重新计算,所以无需改动

  2. 监听语言的变化,主动触发校验,以便 message 属性的 value 进行重新获取

    import { watchSwitchLang } from '@/utils/i18n'
    
    watchSwitchLang(() => {
      loginFromRef.value.validate()
    })
    
    1
    2
    3
    4
    5

至此,该问题得到处理。

GIF 2021-12-14 16-33-19

# 国际化方案总结

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

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

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

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

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

# 接口国际化:处理接口国际化问题

项目介绍 中,数据存在两种:

  1. 本地写死的国际化数据
  2. 接口获取到的数据

那么针对于第一种数据是可以直接完成国际化展示的。

但是第二种数据因为是从服务端获取到的,所以说,服务端返回什么内容,那么前端就会展示什么内容。

所以说如果我们想要完成接口的国际化,那么就需要让服务端返回对应国际化的数据。

在接口请求的 headers 中增加 Accept-Language 表明当前我们所需要的语言类型,在 支持国际化 的接口服务中,可以直接获取到国际化数据

  1. 在 utils/request.js 的请求拦截器中增加 headers 配置

    // 请求拦截器
    service.interceptors.request.use(
      config => {
        // 在这个位置需要统一的去注入token
        if (store.getters.token) {
         ...
        }
        // 配置接口国际化
        config.headers['Accept-Language'] = store.getters.language
        return config // 必须返回配置
      },
      error => {
        return Promise.reject(error)
      }
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

在我们切换了语言之后,刷新 页面即可获取到 国际化返回数据

但是每次都刷新页面的操作未免不太友好,那么有没有什么办法可以跳过刷新这个步骤呢?

我们之前定义过的 watchSwitchLang 方法,该方法可以 监听到语言的变化,然后指定操作。依赖这个方法得出以下代码:

  1. 在 views/profile/index 中

    import { watchSwitchLang } from '@/utils/i18n'
    // 监听语言切换
    watchSwitchLang(getFeatureData)
    
    1
    2
    3

剩下的就是 用户信息 数据的国际化实现,在 app.js 中监听语言变化,重新制定获取用户信息的动作

  1. 在src/App.vue中

    import { watchSwitchLang } from '@/utils/i18n'
    
    /**
     * 监听 语言变化,重新获取个人信息
     */
    watchSwitchLang(() => {
      if (store.getters.token) {
        store.dispatch('user/getUserInfo')
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
上次更新: 2025/06/23, 07:26:12
退出登录处理方案
动态换肤处理方案

← 退出登录处理方案 动态换肤处理方案→

最近更新
01
CodePush
06-22
02
打包发布
03-09
03
常用命令
03-09
更多文章>
Theme by Vdoing | Copyright © 2021-2025 WeiXiaojing | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式