手写 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 版完全统一
相关推荐
随风一样自由12 小时前
【前端独角兽】刚进入前端领域的前端开发用jQuery还是Vue3?
前端·javascript·vue3·jquery
IMPYLH12 小时前
HTML 的 <data> 元素
前端·html
YHHLAI13 小时前
[特殊字符] 面试中的 Promise —— 从入门到精通
前端·javascript·面试
weixin_BYSJ198713 小时前
SpringBoot + MySQL 乒乓球运动员信息管理系统项目实战--附源码04954
java·javascript·spring boot·python·django·flask·php
沪飘大军14 小时前
ll-llm:一个零依赖、可插拔的 OpenAI 兼容 LLM 代理
javascript
leoZ23114 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
kyriewen15 小时前
我扒了 GPT-5.6 的全部编程跑分——有一项 OpenAI 没敢放出来
前端·gpt·ai编程
前端H15 小时前
微前端 v3 架构升级:从加载隔离到状态协同
前端·架构·状态模式
山河木马15 小时前
GPU自动处理专题3-NDC怎么映射成屏幕像素坐标(视口变换)
javascript·webgl·图形学
陆枫Larry16 小时前
小程序包体积优化:用 SVG 图片替换 iconfont,并保留 CSS 控色能力
前端