Class 版 + Hooks 版(支持:对象插值 + 数组插值 + 懒加载)
通用目录结构
src/
i18n/
class-version/
index.js
lang/
zh-CN.json
en.json
hooks-version/
index.js
lang/
zh-CN.json
en.json
通用语言包(支持两种占位符)
zh-CN.json
json
{
"title": "极简 i18n",
"welcome": "欢迎你,{name}!",
"message": "我是{0},今年{1}岁"
}
en.json
json
{
"title": "Mini I18n",
"welcome": "Welcome, {name}!",
"message": "I'm {0}, {1} years old"
}
一、Class 版(增强:对象 + 数组插值)
src/i18n/class-version/index.js
javascript
class MiniI18n {
constructor(options) {
this.locale = options.locale || 'zh-CN'
this.messages = {}
this.loadedLangs = new Set()
}
setLocaleMessage(lang, messages) {
this.messages[lang] = messages
this.loadedLangs.add(lang)
}
// 🔥 增强:支持 对象 / 数组 两种插值
t(key, params = {}) {
const langPack = this.messages[this.locale]
if (!langPack) return key
let text = langPack[key] || key
// 数组插值:{0} {1}
if (Array.isArray(params)) {
params.forEach((val, index) => {
text = text.replace(new RegExp(`\\{${index}\\}`, 'g'), val)
})
}
// 对象插值:{name} {age}
else if (typeof params === 'object') {
Object.keys(params).forEach(k => {
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), params[k])
})
}
return text
}
changeLocale(lang) {
this.locale = lang
}
async lazyLoadLang(lang) {
if (this.loadedLangs.has(lang)) {
this.changeLocale(lang)
return
}
try {
const mod = await import(`./lang/${lang}.json`)
this.setLocaleMessage(lang, mod.default)
this.changeLocale(lang)
} catch (e) {
console.error(`加载 ${lang} 失败`, e)
}
}
}
const plugin = {
install(app, options = {}) {
const i18n = new MiniI18n(options)
i18n.lazyLoadLang(i18n.locale)
app.config.globalProperties.$t = (k, p) => i18n.t(k, p)
app.config.globalProperties.$i18n = i18n
app.provide('i18n', i18n)
}
}
export default plugin
二、Hooks 版(增强:对象 + 数组插值)
src/i18n/hooks-version/index.js
javascript
import { reactive, readonly } from 'vue'
const state = reactive({
locale: 'zh-CN',
messages: {},
loaded: new Set()
})
export function setLocaleMessage(lang, messages) {
state.messages[lang] = messages
state.loaded.add(lang)
}
// 🔥 增强:支持 对象 / 数组 两种插值
export function t(key, params = {}) {
const pack = state.messages[state.locale]
if (!pack) return key
let text = pack[key] || key
if (Array.isArray(params)) {
params.forEach((val, i) => {
text = text.replace(new RegExp(`\\{${i}\\}`, 'g'), val)
})
}
else if (typeof params === 'object') {
Object.keys(params).forEach(k => {
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), params[k])
})
}
return text
}
export function setLocale(lang) {
state.locale = lang
}
export async function loadLocale(lang) {
if (state.load.has(lang)) {
setLocale(lang)
return
}
try {
const mod = await import(`./lang/${lang}.json`)
setLocaleMessage(lang, mod.default)
setLocale(lang)
} catch (e) {
console.error(`加载 ${lang} 失败`, e)
}
}
export function useI18n() {
return {
t,
locale: readonly(state.locale),
loadLocale
}
}
const plugin = {
install(app) {
app.config.globalProperties.$t = t
app.provide('useI18n', useI18n)
loadLocale(state.locale)
}
}
export default plugin
三、组件使用示例(两种插值都支持)
vue
<template>
<div>
<!-- 对象插值 -->
<p>{{ $t('welcome', { name: '张三' }) }}</p>
<!-- 数组插值 -->
<p>{{ $t('message', ['张三', 25]) }}</p>
<button @click="switchLang('zh-CN')">中</button>
<button @click="switchLang('en')">英</button>
</div>
</template>
<script setup>
import { useI18n } from '@/i18n/hooks-version'
// Class 版用 inject 即可
const { t, loadLocale } = useI18n()
function switchLang(lang) {
loadLocale(lang)
// JS 内使用示例
console.log(t('message', ['李四', 30]))
}
</script>
四、核心增强说明(你要的关键逻辑)
javascript
if (Array.isArray(params)) {
// 数组:{0} {1}
params.forEach((val, index) => {
text = text.replace(`{${index}}`, val)
})
} else if (typeof params === 'object') {
// 对象:{name} {age}
Object.keys(params).forEach(k => {
text = text.replace(`{${k}}`, params[k])
})
}
- 自动判断:数组 / 对象
- 支持
{0}{1}下标式插值 - 支持
{name}{age}键名式插值 - Class 版 & Hooks 版完全统一