文章目录
前言
i18n的简单定义
i18n是internationalization的简称。指让产品无需做出大的改变就能适应不同语言和地区的需要。
一、vue-i18n的使用
只专注于Vue3的vue-18n,即v9版本
vue-i18n是为vue.js而开发的国际化插件,使得开发者在Vue应用内插i18n更加便捷。
1. 安装i18n插件
// npm
npm install vue-i18n@9
// yarn
yarn add vue-i18n@9
2. 配置i18n插件
新建i18n文件夹后,新建index.ts
在i18n/index.ts做i18n的配置工作。
createI18n(options)方法的参数options
options指定了语言包、语言(名称)、是否使用传统API、是否全局注入、是否抑制回退警告、是否抑制本地化警告、日期时间格式等等
语言包messages和语言locale是最重要的配置,也是通常需要用户手动注入的动态参数
const i18n = createI18n({
legacy: false, // 是否使用传统API模式,设为false指定使用Composition API模式(vue3设为false)
globalInjection: true, // 为每个组件注入全局属性和函数
silentTranslationWarn: true, // 抑制本地化失败警告
silentFallbackWarn: true, // 抑制回退失败警告
locale: 'en', // 语言
fallbackLocale: 'en', // 回退语言
messages, // 语言包(配置语言包的教程在下方)
})
export default i18n;
配置语言包
在i18n文件夹下建立语言包文件夹lang,中新建zn-cn.ts, en.ts等
语言包文件实际上是一个可嵌套的键值对对象,不同语言的key值相同,value便是本区语言翻译。
示例如下:
// zh-cn.ts
export default {
common: {
add: '新建',
edit: '编辑‘
}
}
// en.ts
export default {
common: {
add: 'Add',
edit: 'Edit'
}
}
在i18n/index.ts中引入语言包
最简单示例
import en from './lang/en.ts'
import zhCn from './lang/zh-cn.ts'
const messages = {
en: { name: 'en', ...en},
'zh-cn': { name: 'zh-cn', ...zhCn}
}
引入语言包后的操作就是上方的createI18n()了
语言包文件分包
不只是在i18n/lang下建立语言包文件,可以在组件文件夹或任意地方建立语言包文件,避免lang下文件包体积太大,开发时频繁切换
可以在开发的任意组件文件夹下设立语言包,such as views/admin/user(用户列表文件夹)下建立i18n文件夹,新建zh-cn.ts和en.ts等,这些语言包是如何让i18n知道呢?
// i18n/index.ts
const itemilizes = { 'zh-cn': [], 'en': []}
const modules = import.meta.global('./**/*.ts', {eager: true})
const pages = import.meta.global('./../../**/**/**/i18n/*.ts', {eager: true})
const regexp = /(\S+)\/(\S+).ts/
for(const path in modules) {
const key = path.match(regexp)
if (itemilizes[key[2]]) itemilizes[key[2].push(modules[path].default
else itemilizes[key[2]] = [modules[path.default]
}
for (const path in pages){
const key = path.match(regexp)
if (itemilizes[key[2]]) itemilizes[key[2].push(pages[path].default
else itemilizes[key[2]] = [pages[path.default]
}
for(const [name, el] of Object.entries(itemilizes) {
messages[name] = {
name,
...mergeArrObj(el)
}
}
const mergeArrObj = (arr) => {
const obj = {}
arr.forEach(item => Object.assign({}, obj, item))
return obj
}
引入element-plus语言包
若应用使用了element-plus组件库,需要引入组件库的语言包
import enLocale from 'element-plus/lib/locale/lang/en'
import zhCnLocale from 'element-plus/lib/locale/lang/zh-cn'
Object.assign(messages, {
'en': enLocale,
'zh-cn': zhCnLocale
}
上面只是引入了element-plus的语言包
但是如何让element-plus知道正在应用的是那个语言呢?
element-plus是支持通过 Config Provider 来配置多语言的
所以,为了更改element-plus的语言
在App.vue中包裹el-config-provider
// App.vue
<template>
<el-config-provider :locale="getGlobalI18n">
</el-config-provider>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
const { message, locale } = useI18n()
// 实际传入的是某个语言包,而不是语言(名称)
const getGlobalI18n = computed(() => message.value[locale.value])
</script>
获取用户配置的语言包
若应用支持用户手动配置语言包,则语言包需要交给后台管理,但是每次应用刷新时都需要请求来获取后台管理的语言包
同样,这段代码写在i18n/index.ts中即可
fetchI18n()
const fetchI18n = async () => {
// 假设后端返回的数据格式是:{ zh-cn: [{'delete: '删除'}, 'search': '查询'], en: [{'delete': 'Delete', 'search': 'Search'}]
const infoI18n = await info(); //info是向后端请求的接口
const messagesLocal = {}
for(const [name, data] of Object.entries(infoI18n.data) {
messagesLocal[name] = {
name,
...mergeArrObj(data)
}
// 需要注意的是,后台保存的语言包合并到已经创建的i18n对象的语言包上,而不是注入到messages(注入到messages就需要等待后台返回后才可以创建i18n对象)
i18n.global.mergeLocaleMessage(name, messagesLocal[name])
}
}
配置语言
语言状态是持久化存储数据
应用到底使用哪种语言应该交给用户来决定,而语言的状态应该交给存储库(pinai/vuex)来管理(它不是单一组件的状态,起码i18n/index和语言切换组件不在同一个地方),为了每次刷新都不会更改用户的语言选择结果,而应该将语言持久化保存
// 主题设置Store,除了i18n,还可以设置其他内容,比如主题色、主题模式等
import {defineStore} from 'pinia'
export const useThemeConfig = defineStore('themeConfig', {
state: () => {
themeConfig: {
globalI18n: 'zh-cn'
}
},
persist: true
})
// i18n/index.ts
import { useThemeConfig } from '/@/stores/themeConfig'
const { themeConfig } = storeToRefs(useThemeConfig(pinia))
// 修改createI18n
createI18n({
// balabala --- 看上文
locale: themeConfig.value.globalI18n
})
// 如果有动态语言包,则修改fetchI18n ()
const fetchI18n = async () => {
// balabala...
i18n.global.locale.value = themeConfig.value.globalI18n
}
// 上文中的pinia是什么呢?
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
// 创建
const pinia = createPinia();
pinia.use(piniaPluginPersist);
// 导出
export default pinia;
语言切换组件就不说了,无非就是更改themeConfig中的globalI18n状态而已
3. 引用i18n插件
需要将i18n挂载到vue实例
// main.js
import i18n from '/@/i18n'
const app = createApp(App);
app.use(i18n)
4. 使用i18n
单文件组件
可以在<template>或<script>中使用i18n
template中可以直接使用$t()来使用i18n
<div class="content">{{ $t('common.add') }}</div>
// 最终在浏览器上显示i18n的翻译结果
script中需要引入t
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
任意ts/js文件
useI18n只可在组件中使用,否则会抛出错误
如果非要使用useI18n(), 那么t的声明就必须放在某个函数中,且此函数会由组件触发
下面的使用方式会报错✖️:
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
可以用以下方案替代✔️:
import i18n from '/@/i18n'
const { t } = i18n.global
二、element-plus中i18n的使用
element-plus中并没有引入vue-i18n,useI18n是自己编写的函数
通过eleement-plus中useI18n的编写,也许会对i18n的t有一个更深入的了解
1.语言包
element-plus也是有自己的语言包的,并且不只是中英文,一共支持55种语言
位于packages/locale/lang文件夹中
语言包格式如下:
2.useLocale
useLocale放在hooks/use-locale文件夹下的index.ts中
// hooks/use-locale/index.ts
// 就是语言包下的英文语言包,也是element-plus的默认语言包
import English from '@element-plus/locale/lang/en'
import { get } from 'lodash-unified'
// 真正的t方法,可接收两个参数(locale来自于父函数)第二个参数是一个对象,用于替换翻译文本中{}中的内容
const translate = (path, option, locale) => {
get(locale,path,path).replace(/\{(\w+)\}/g, (_, key) => `${option?.[key] ?? `{${key}}`}`)
}
// locale一定是一个响应式语言包, 一个闭包函数,目的是避免用户传入当前使用的语言包
export const buildTranslator = (locale) => (path,option) => translate(path,option, unref(locale))
// locale可能是一个响应式语言包, lang确保是响应式数据,开发者可以在任意地方引用而不担心数值变更
// 必须确保传入t方法的语言包是当前应用的语言包,所以传入
export const buildLocaleContext = (locale) => {
const lang = computed(() => unref(locale).name)
const localeRef = isRef(locale) ? locale : ref(locale)
return {
lang,
locale: localeRef,
t: buildTranslator(locale)
}
}
// localeOverrides是可选参数,响应式Ref数据,指向某个具体的语言包
// 可通过inject接收祖辈组件传入的语言包,且响应其变化
// 需要把locale must to be 响应式数据,因为用户在使用过程中可能会修改语言类型,为了能够实时切换lang和t方法的语言包,所以传入buildLocaleContext方法的也必须是响应式语言包
export const useLocale = (localeOverrides) => {
const locale = localOverrides || inject(localContextKey, ref())
return buildLocaleContext(computed(() => locale.value || English))
}
特别巧妙的点在于生成t函数的闭包函数b
3.element-plus组件使用国际化
import { useLocale } from '@element-plus/hooks'
const { t } = useLocale()