手写 Vue3 极简 i18n

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 版完全统一
相关推荐
nujnewnehc1 天前
ps, ai, ae插件都可以用html和js开发了
前端·javascript
Jagger_1 天前
整洁架构三连问:是什么,怎么做,为什么要用
前端
一个处女座的程序猿O(∩_∩)O1 天前
React 完全入门指南:从基础概念到组件协作
前端·react.js·前端框架
前端摸鱼匠1 天前
Vue 3 的defineEmits编译器宏:详解<script setup>中defineEmits的使用
前端·javascript·vue.js·前端框架·ecmascript
里欧跑得慢1 天前
Flutter 测试全攻略:从单元测试到集成测试的完整实践
前端·css·flutter·web
Jagger_1 天前
前端整洁架构详解
前端
徐小夕1 天前
我花一天时间Vibe Coding的开源AI工具,一键检测你的电脑能跑哪些AI大模型
前端·javascript·github
英俊潇洒美少年1 天前
Vue3 企业级封装:useEventListener + 终极版 BaseEcharts 组件
前端·javascript·vue.js
嵌入式×边缘AI:打怪升级日志1 天前
使用JsonRPC实现前后台
前端·后端
小码哥_常1 天前
深度剖析:为什么Android选择了Binder
前端