• 假设已经存在一个Vue3.x+ts+sass项目;
  • 由于是基于Vue3.x+ts搭建的示例项目,所以其它部分相关依赖使用的是next版本。

安装依赖

// 导入依赖库
npm install vuex vue-i18n js-cookie element-plus -S
//导入编译依赖
npm install @types/js-cookie -S -D

准备语言包

以中英文为例,创建自定义语言包en.ts和zh-cn.ts,维护应用使用的文字资源。

// src/locales/en.ts
export default {
  name: 'English',
  welcomeMsg: 'Welcome to Your Vue.js + TypeScript App',
  setting: {
    test1: 'test1',
    test2: 'test2',
    note1: 'Full function'
  },
  elPagination: {
    per: 'Per page',
    unit: 'item',
    curPage: 'Current page: '
  },
  hello: {
    test1: 'hello test1',
    test2: 'hello test2'
  }
}
// src/locales/zh-cn.ts
export default {
  name: '中文',
  welcomeMsg: '欢迎来到你的 Vue.js + TypeScript 应用程序!',
  setting: {
    test1: '测试1',
    test2: '测试2',
    note1: '完整功能'
  },
  elPagination: {
    per: '每页',
    unit: '条',
    curPage: '当前页: '
  },
  hello: {
    test1: '你好,测试1',
    test2: '你好,测试2'
  }
}

准备持久化方法

将用户偏爱设置(所选语言)持久化到cookie中,这里借助js-cookie封装的cookie操作工具。
key值维护

// src/constant/key.ts
class Keys {
  static languageKey = 'vue3-typescript-admin-languageKey'
}
   
export default Keys

cookies封装

// src/utils/cookies.ts
import Keys from '@/constant/key'
import Cookies from 'js-cookie'
   
export const getLanguage = () => Cookies.get(Keys.languageKey)
export const setLanguage = (language: string) => Cookies.set(Keys.languageKey, language)

导入语言信息

导入生成Message语言包

// src/locales/index.ts
// 导入element-plus中英文语言包
import elementEnLocale from 'element-plus/lib/locale/lang/en'
import elementZhLocale from 'element-plus/lib/locale/lang/zh-cn'
// 导入自定义语言包
import enLocale from './en'
import zhLocale from './zh-cn'

const messages = {
  en: {
    ...enLocale,
    ...elementEnLocale
  },
  'zh-cn': {
    ...zhLocale,
    ...elementZhLocale
  }
}

获取当前语言

用户语言偏好设置,我们持久化到cookie中,通过getLanguage获取。

// src/locales/index.ts
import { getLanguage } from '@/utils/cookies'
export const getLocale = () => {
  // 读取cookie存入的当前语言
  const cookieLanguage = getLanguage()
  // 如果有返回当前语言
  if (cookieLanguage) {
    return cookieLanguage
  }
  // 如果没有,获取系统语言
  const language = navigator.language.toLowerCase()
  // 获取messages 语言 遍历
  const locales = Object.keys(messages)
  for (const locale of locales) {
    // 如果messsage 包里面有系统语言返回
    if (language.indexOf(locale) > -1) {
      return locale
    }
  }

  // 默认语言 简体中文
  return 'zh-cn'
}

创建i18n实例

// src/locales/index.ts
import { createI18n } from 'vue-i18n'
// 创建i18n实例
const i18n = createI18n({
  locale: getLocale(),
  messages: messages
})

export default i18n

使用i18n实例

一种方式直接在main.js中通过app.use(i18n)使用,一种方式是通过管理插件的方式与其他插件一起引入;后一种方式其实最终也是通过app.use(i18n)使用的,只是与其他插件一起管理起来,便于多人开发时,大家一起修改mian.js容易产生代码冲突。

方式一

// src/main.ts
import i18n from '@/locales'
const app = createApp(App)
app.use(i18n)

方式二

结合webpack提供的require.context接口特性,实现多插件动态导入。在src/plugins下维护所有插件,通过require.context获取所有插件,遍历并使用插件,导出loadAllPlugins方法,并在main.js中调用,实现加载所有插件。以下代码中包含对element-plus的引入。

  1. 准备插件
// src/plugins/i18n.ts
import i18n from '@/locales'
export default function loadComponent (app: any) {
  app.use(i18n)
}
// src/plugins/element.ts
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import i18n from '@/locales'
export default function loadComponent (app: any) {
  // 注意,此处的i18n: i18n.global.t是必须的,如果不设置,切换语言的时候,将会对element-plus组件无效。
  app.use(ElementPlus, { size: 'small', i18n: i18n.global.t })
}
  1. 加载插件
// src/plugins/index.ts
import { createApp } from 'vue'

/**
 * @description 加载所有 Plugins,导出loadAllPlugins
 * @param  {ReturnType<typeofcreateApp>} app 整个应用的实例
 */
export function loadAllPlugins (app: ReturnType<typeof createApp>) {
  // 获取当前目录下所哟.ts文件
  const files = require.context('.', true, /\.ts$/)
  // 遍历,过滤并加载插件
  files.keys().forEach(key => {
    if (typeof files(key).default === 'function') {
      if (key !== './index.ts') files(key).default(app)
    }
  })
}
  1. 在main.js调用,loadAllPlugins,实现插件的加载
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
// import router from './router'
// import { store } from './store'
import { loadAllPlugins } from './plugins'
const app = createApp(App)
loadAllPlugins(app)
app.mount('#app')
// app.use(store).use(router).mount('#app')

至此,已经将导入了多语言,并知道如何使用i18n,实现基本的国际化,但是在实际应用中,我们希望的是能够通过切换入口,自由切换语言,并保证所有模块都显示目标语言。接下来将实现语言切换组件,展示如何使用组件及i18n提供的语言,并提供具体示例。

语言切换组件

Vuex的状态管理

由于语言切换组件需要依赖Vuex的状态管理,才能实现触发切换语言之后同步更新应用所有模块的语言,因此,我们首先来实现Vuex的相关逻辑。这里使用vuex4.x 分包(ts 类型推断type版),重点在于让vuex支持type 枚举类型推断。

  • state
// src/store/modules/app/state.ts
// 驱动应用的数据源,包含了全部的应用层级状态,唯一数据源。
import { getLocale } from '@/locales'

export interface AppState {
  language: string
}

export const state: AppState = {
  language: getLocale()
}
// src/store/modules/app/mutation-types.ts
export enum AppMutationTypes {
  SET_LANGUAGE = 'SET_LANGUAGE',
}
  • mutation
// src/store/modules/app/mutations.ts
// 维护mutation用于更改Vuex的store中的language状态
import { MutationTree } from 'vuex'
import { AppState } from './state'
import { AppMutationTypes } from './mutation-types'
import { setLanguage } from '@/utils/cookies'

export type Mutations<S = AppState> = {
  [AppMutationTypes.SET_LANGUAGE](state: S, language: string): void
}

export const mutations: MutationTree<AppState> & Mutations = {
  [AppMutationTypes.SET_LANGUAGE] (state: AppState, language: string) {
    state.language = language
    setLanguage(state.language)
  }
}
  • action
// src/store/modules/app/action-types.ts
export enum AppActionTypes {
  ACTION_SET_LANGUAGE = 'ACTION_SET_LANGUAGE',
}
// src/store/modules/app/actions.ts
// Action 类似于 mutation,不同在于:
// Action 提交的是 mutation,而不是直接变更状态。
// Action 可以包含任意异步操作。
import { ActionTree, ActionContext } from 'vuex'

// eslint-disable-next-line import/no-cycle
import { RootState } from '@/store'
import { AppState } from './state'
import { Mutations } from './mutations'
import { AppMutationTypes } from './mutation-types'
import { AppActionTypes } from './action-types'
type AugmentedActionContext = {
  commit<K extends keyof Mutations>(
    key: K,
    payload: Parameters<Mutations[K]>[1],
  ): ReturnType<Mutations[K]>
} & Omit<ActionContext<AppState, RootState>, 'commit'>

export interface Actions {
  [AppActionTypes.ACTION_SET_LANGUAGE](
    { commit }: AugmentedActionContext,
    language: string
  ): void
}

export const actions: ActionTree<AppState, RootState> & Actions = {
  [AppActionTypes.ACTION_SET_LANGUAGE] ({ commit }, language: string) {
    commit(AppMutationTypes.SET_LANGUAGE, language)
  }
}
  • 让vuex支持type 枚举类型推断
// src/store/modules/app/index.ts
// 让vuex支持type枚举类型推断
import {
  Store as VuexStore,
  CommitOptions,
  DispatchOptions,
  Module
} from 'vuex'

import { RootState } from '@/store'
import { state } from './state'
import { mutations, Mutations } from './mutations'
import { actions, Actions } from './actions'
import type { AppState } from './state'

export { AppState }

export type AppStore<S = AppState> = Omit<VuexStore<S>, 'getters' | 'commit' | 'dispatch'>
& {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
    key: K,
    payload: P,
    options?: CommitOptions
  ): ReturnType<Mutations[K]>
} & {
  dispatch<K extends keyof Actions>(
    key: K,
    payload: Parameters<Actions[K]>[1],
    options?: DispatchOptions
  ): ReturnType<Actions[K]>
};
export const store: Module<AppState, RootState> = {
  state,
  mutations,
  actions
}
  • 动态加载模块
// src/store/index.ts
// 导入模块
// 使用vuex的createStore创建store实例
// 导出useStore方法,返回值Store,其他地方需要用到store的一定要使用useStore()
import { createStore, createLogger } from 'vuex'
import { store as app, AppStore, AppState } from '@/store/modules/app'

export interface RootState {
    app: AppState
}

export type Store = AppStore<Pick<RootState, 'app'>>

// Plug in logger when in development environment
const debug = process.env.NODE_ENV !== 'production'
const plugins = debug ? [createLogger({})] : []
export const store = createStore({
  plugins,
  modules: {
    app
  }
})

export function useStore (): Store {
  return store as Store
}

实现组件

<!-- src/components/lang_select/Index.vue -->
<template>
  <div>
    <el-dropdown>
      <svg
        class="icon"
        aria-hidden="true"
        font-size="20px"
        :class="{'svg-color': isWhite}"
      >
        <use xlink:href="#icon-language" />
      </svg>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item
            v-for="item in languages"
            :key="item.value"
            :disabled="language===item.value"
          >
            <span @click="handleSetLanguage(item.value)">{{ item.name }}</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>
  </div>
</template>

<script lang="ts">
import { useStore } from '@/store'
import { computed, defineComponent, reactive, toRefs } from 'vue'
import { AppActionTypes } from '@/store/modules/app/action-types'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
type Language = {
    name: string
    value: string
}

export default defineComponent({
  props: {
    isWhite: {
      type: Boolean,
      default: false
    }
  },
  setup () {
    const store = useStore()
    const { locale } = useI18n()

    const state = reactive({
      languages: [{ name: 'en', value: 'en' }, { name: '中文', value: 'zh-cn' }] as Array<Language>,
      handleSetLanguage: (lang: string) => {
        locale.value = lang
        store.dispatch(AppActionTypes.ACTION_SET_LANGUAGE, lang)
        ElMessage({
          message: 'Switch Language Success',
          type: 'success'
        })
      }
    })
    const language = computed(() => {
      return store.state.app.language
    })
    return {
      ...toRefs(state),
      language
    }
  }
})

</script>

<style lang="scss" scoped>
.svg-color{
  fill: #409EFF;
}
</style>

使用组件

<!-- src/views/About.vue -->
<!-- 在About界面中切换语言之后,查看Home界面和所使用的HelloWorld组件,显示的也是目标语言的文字 -->
<template>
  <div class="about">
    <LangSelect :isWhite="true" class="set-language" />
    <div>{{ t("name") }} </div>
    <div>{{ t("setting.test1") }}</div>
    <div>{{ t("setting.test2") }}</div>
    <div class="block">
      <span class="demonstration">{{ t("setting.note1") }}</span>
      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
        :current-page="currentPage" :page-sizes="[100, 200, 300, 400]" :page-size="100"
        layout="total, sizes, prev, pager, next, jumper" :total="400">
      </el-pagination>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue'
import LangSelect from '@/components/lang_select/Index.vue'
import { useI18n } from 'vue-i18n'
export default defineComponent({
  components: {
    LangSelect
  },
  setup() {
    const { t } = useI18n()
    const currentPage = ref(4)

    const methods = reactive({
      handleSizeChange(val: Number) {
        // console.log(`每页 ${val} 条`)
        console.log(t('elPagination.per') + val + t('elPagination.unit'))
      },
      handleCurrentChange(val: Number) {
        // console.log(`当前页: ${val}`)
        console.log(t('elPagination.curPage') + val)
      }
    })
    return {
      t,
      currentPage,
      ...toRefs(methods)
    }
  }
})
</script>
<style scoped lang="scss">
.about {
  height: 100vh;
  width: 100%;
}
.set-language {
  color: #000;
  top: 3px;
  font-size: 18px;
  right: 0px;
  cursor: pointer;
}
</style>
<!-- src/views/Home.vue -->
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld :msg="t('welcomeMsg')" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
import { useI18n } from 'vue-i18n'

export default defineComponent({
  name: 'Home',
  components: {
    HelloWorld
  },
  setup() {
    const { t } = useI18n()
    return {
      t
    }
  }
})
</script>
<!-- src/components/HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div>
      <h1>{{ t("hello.test1") }}</h1>
      <h1>{{ t("hello.test2") }}</h1>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup() {
    const { t } = useI18n()
    return {
      t
    }
  }
})
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>

总结

  1. 安装依赖
  2. 准备语言包
  3. 导入语言包
  4. 使用cookie持久化用户语言偏好
  5. 当前选择locale
  6. 创建i18n实例
  7. Element-plus插件中设置i18n
  8. 加载i18n和element-plus插件
  9. 使用Vuex进行语言切换状态管理
  10. 编写语言切换组件
  11. 使用语言切换组件

示例代码

Logo

前往低代码交流专区

更多推荐