手写 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 版完全统一
相关推荐
郭老二2 小时前
【前端】vue3:编译步骤
前端
不在逃避q3 小时前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁3 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One9853 小时前
高进度算法模板速记(待完善)
java·前端·算法
陈随易3 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师4 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户059540174465 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈5 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
kyriewen6 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒6 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端