i18n国际化集成
说明:使用vue-i18n库实现系统国际化,结合@intlify/unplugin-vue-i18n插件实现预加载(开发中国际化显示),同时集成elementplus中en和zh-cn文件的组件国际化。
1.国际化文件
/locales/en.json
json
{
"anything": "anything",
"hello": "Hello"
}
/locales/zh-CN.json
json
{
"hello": "你好"
}
2.插件i18n ally, 使用命令添加文本国际化翻译
.vscode/setting.json
json
{
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.enabledParsers": ["yaml", "js", "json"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.translate.engines": [
"baidu",
"google"
]
}
3.国际化i18n.ts
typescript
import type { App } from 'vue'
import { createI18n, type Locale } from 'vue-i18n'
// Legacy模式(选项式API):语言设置是直接赋值,vue2的赋值方式
// Composition模式(组合式API):语言设置是赋值给响应式变量
// 创建一个i18n实例
const i18n = createI18n({
legacy: false,
locale: '',
messages: {}
})
// 解析locales目录下的所有语言文件,转换为对象,例如:{ en: () => import('../../locales/en.js'), zh-CN: () => import('../../locales/zh-CN.js') }
const localesMap = Object.fromEntries(
Object.entries(import.meta.glob('../../locales/*.json')).map(([path, loadLocale]) => [
path.match(/([\w-]*)\.json$/)?.[1],
loadLocale
])
) as Record<Locale, () => Promise<{ default: Record<string, string> }>>
// 集成elementplus中的国际化文件en和zh-CN
const elementPlusLocaleMap = Object.fromEntries(
Object.entries(import.meta.glob('../../node_modules/element-plus/dist/locale/*.mjs')).map(
([path, loadLocale]) => [path.match(/([\w-]*)\.mjs$/)?.[1], loadLocale]
)
) as Record<Locale, () => Promise<{ default: Record<string, string> }>>
// 获取存在的语言数组
export const availableLocales = Object.keys(localesMap)
// 过滤elementplus中的国际化文件,只保留en和zh-CN
const filterEPLocaleMap = availableLocales.reduce(
(acc: Record<Locale, () => Promise<{ default: Record<string, string> }>>, locale: Locale) => {
return {
...acc,
//locale.toLowerCase()将zh-CN转换为小写,elementplus中的语言文件都是小写的
[locale]: elementPlusLocaleMap[locale.toLowerCase()]
}
},
{}
)
// 记住用户选择的语言
const loadedLanguages: string[] = []
// 设置国际化(i18n)语言的函数
export function setI18nLanguage(locale: string) {
// Composition模式赋值
i18n.global.locale.value = locale
if (typeof document !== 'undefined') {
document.querySelector('html')?.setAttribute('lang', locale)
}
}
// 加载国际化(i18n)语言包的异步函数
export async function loadLocaleMessages(lang: string) {
// 如果语言包i18n已经加载过,则直接设置i18n.locale
if (i18n.global.locale.value === lang || loadedLanguages.includes(lang)) {
return setI18nLanguage(lang)
}
// 通过语言代码从预定义的语言映射中获取对应的语言包加载函数并执行
const messages = await localesMap[lang]()
// 获取elementplus的语言包
const messagesEP = await filterEPLocaleMap[lang]()
// 将加载的语言包设置到i18n实例中
i18n.global.setLocaleMessage(lang, { ...messagesEP.default, ...messages.default })
loadedLanguages.push(lang)
return setI18nLanguage(lang)
}
// 挂载到app上
export default {
install(app: App) {
app.use(i18n)
// 设置默认语言为中文
loadLocaleMessages('zh-CN')
}
}
main.ts中挂载到app上
javascript
import i18n from './modules/i18n'
app.use(i18n)
4.构建和打包优化,因elementplus包中不止en和zh-cn文件,还有其他语言文件,所有在构建时过滤出en和zh-cn文件打包到dist中。
vite.config.ts
javascript
import { fileURLToPath, URL } from 'node:url'
import path from 'node:path'
import fs from 'node:fs'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import UnoCSS from 'unocss/vite'
import VueRouter from 'unplugin-vue-router/vite'
import { VueRouterAutoImports } from 'unplugin-vue-router'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import Layouts from 'vite-plugin-vue-layouts'
import { VitePWA } from 'vite-plugin-pwa'
import { viteMockServe } from 'vite-plugin-mock'
// import dotenv from 'dotenv'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// Load environment variables
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd()) // 加载 `.env.[mode]`
const enablePWADEBUG = env.VITE_PWA_DEBUG === 'true'
const enableMock = env.VITE_MOCK_ENABLE === 'true'
/**
* elementplus国际化文件打包和构建优化,只保留zh-cn和en文件到dist中。
* 过滤elementplus的.mjs文件,不打包不需要的locales
* 判断,/locales中对应的文件名的.mjs文件作为过滤条件->保留
*/
function filterElementPlusLocales(id: string) {
// 返回true表示打包,false表示不打包
// locales文件查找
const localesDir = path.resolve(__dirname, 'locales')
const localesFiles = fs
.readdirSync(localesDir)
.map((file) => file.match(/([\w-]*)\.json$/)?.[1] || '')
if (id.includes('element-plus/dist/locale')) {
// 获取id的basename
const basename = path.basename(id, '.mjs')
// 判断basename是否在localesFiles中
return !localesFiles.some((file) => basename === file.toLowerCase())
}
return false
}
return {
build: {
rollupOptions: {
// id:文件名,external:是否打包
external: (id) => filterElementPlusLocales(id)
}
},
plugins: [
VueRouter(),
vue(),
vueJsx(),
vueDevTools(),
UnoCSS(),
AutoImport({
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/, // .vue
/\.md$/ // .md
],
// global imports to register
imports: [
// presets
'vue',
// 'vue-router'
VueRouterAutoImports,
'@vueuse/core'
],
resolvers: [ElementPlusResolver()]
}),
Components({
directoryAsNamespace: false,
collapseSamePrefixes: true,
resolvers: [ElementPlusResolver()]
}),
Layouts({
layoutsDirs: 'src/layouts',
defaultLayout: 'default'
}),
VitePWA({
injectRegister: 'auto',
manifest: {
name: 'Vite App',
short_name: 'Vite App',
theme_color: '#ffffff',
icons: [
{
src: '/192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: '/512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
},
registerType: 'autoUpdate',
workbox: {
navigateFallback: '/',
// 如果大家有很大的资源文件,wasm bundle.js
globPatterns: ['**/*.*']
},
devOptions: {
enabled: enablePWADEBUG,
suppressWarnings: true,
navigateFallbackAllowlist: [/^\/$/],
type: 'module'
}
}),
viteMockServe({
mockPath: 'mock',
enable: enableMock
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]'
}),
VueI18nPlugin({
include: [path.resolve(__dirname, './locales/**')],
// 组合式赋值方式,契合Vue3.0
compositionOnly: true
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
}
})
5.使用和效果
bash
<div>自定义国际化:{{ $t('hello') }}</div>
<div>elementplus国际化:{{ $t('el.colorpicker.confirm') }}</div>
<div>{{ $t('anything') }}</div>
<el-select v-model="locale" placeholder="请选择" @change="changeLocale"
<el-option label="中文" value="zh-CN" />
<el-option label="英文" value="en" />
</el-select>

