手写 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 版完全统一
相关推荐
羽沢311 小时前
一篇简单的STOMP教程QAQ
前端·javascript·stomp
code_Bo1 小时前
使用AI完成Swagger接口类型在前端自动生成的工具
前端·后端·架构
加个鸡腿儿2 小时前
从"包裹器"到"确认按钮"——一个组件的三次重构
前端·vue.js·设计模式
Kel2 小时前
深入 OpenAI Node SDK:一个请求的奇幻漂流
javascript·人工智能·架构
子兮曰2 小时前
AI写代码坑了90%程序员!这5个致命bug,上线就炸(附避坑清单)
前端·javascript·后端
猪八宅百炼成仙2 小时前
PanelSplitter 组件:前端左右布局宽度调整的实用解决方案
前端
BUG胡汉三2 小时前
自建在线文档编辑服务:基于 Collabora CODE + Spring Boot + Vue 3 的完整实现
vue.js·spring boot·后端·在线编辑
锋利的绵羊2 小时前
【解决方案】微信浏览器跳出到浏览器打开、跳转到app,安卓&ios
前端
终端鹿2 小时前
Vue3 核心 API 补充解析:toRef / toRefs / unref / isRef
前端·javascript·vue.js