手写 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 版完全统一
相关推荐
无聊的老谢4 分钟前
Vue 3 + Leaflet 实现高性能 Web GIS 基站监控平台
前端·javascript·vue.js
之歆5 分钟前
Day23_Bootstrap 前端框架完全指南:从栅格系统到组件化开发
开发语言·前端·javascript·前端框架·bootstrap·ecmascript·less
前端 贾公子6 分钟前
3.响应式系统基础:从发布订阅模式的角度理解 Vue2 的数据响应式原理(上)
前端·javascript·vue.js
2501_940041749 分钟前
纯前端高阶实战:涵盖3D、音频可视化与复杂交互的开发命题
前端
AIFQuant9 分钟前
外汇交易平台技术栈深度解析:行情 API、清算、风控、前端一体化方案
前端·python·websocket·金融·restful
NiceCloud喜云8 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
wordbaby9 小时前
React Native + RNOH:跨页面数据回传的最佳实践与避坑指南
前端·react native
GISer_Jing9 小时前
Three.js着色器编译机制深度解析
javascript·webgl·着色器
丷丩9 小时前
MapLibre GL JS第22课:查看本地GeoJSON
前端·javascript·map·mapbox·maplibre gl js
油炸自行车9 小时前
Claude Code 错误:API Error: 400 Failed to deserialize the JSON body into the
开发语言·javascript·json·trae·claude code·api error 400