---
description: Creates a lightweight i18n (internationalization) system for Vue 3 using dictionary objects with localStorage persistence. Use when adding multi-language support, creating translation dictionaries, or building bilingual (中英文) Vue 3 apps.
---

# Vue 3 i18n System

Create a lightweight i18n system without external dependencies.

## Architecture

Use a simple dictionary object with reactive locale switching. No `vue-i18n` library needed for basic use cases.

## Template: `src/i18n.js`

```js
import { ref, computed } from 'vue'

const STORAGE_KEY = 'app_lang'

// 当前语言，从 localStorage 恢复
const locale = ref(localStorage.getItem(STORAGE_KEY) || 'zh')

// 中英双语字典
const dict = {
  // === 通用 ===
  'app.title':     { zh: '待办清单', en: 'Todo List' },
  'app.add':       { zh: '添加', en: 'Add' },
  'app.delete':    { zh: '删除', en: 'Delete' },
  'app.edit':      { zh: '编辑', en: 'Edit' },
  'app.save':      { zh: '保存', en: 'Save' },
  'app.cancel':    { zh: '取消', en: 'Cancel' },
  'app.confirm':   { zh: '确认', en: 'Confirm' },
  'app.search':    { zh: '搜索...', en: 'Search...' },
  'app.all':       { zh: '全部', en: 'All' },
  'app.active':    { zh: '进行中', en: 'Active' },
  'app.completed': { zh: '已完成', en: 'Completed' },
  'app.empty':     { zh: '暂无数据', en: 'No data' },

  // === 统计 ===
  'stats.total':   { zh: '总计', en: 'Total' },
  'stats.done':    { zh: '已完成', en: 'Done' },
  'stats.progress':{ zh: '进度', en: 'Progress' },

  // === 操作 ===
  'action.clearCompleted': { zh: '清除已完成', en: 'Clear completed' },
  'action.export':  { zh: '导出数据', en: 'Export' },
  'action.import':  { zh: '导入数据', en: 'Import' },

  // === 消息 ===
  'msg.added':     { zh: '已添加', en: 'Added' },
  'msg.deleted':   { zh: '已删除', en: 'Deleted' },
  'msg.importSuccess': { zh: '导入成功', en: 'Import successful' },
  'msg.invalidFormat': { zh: '格式无效', en: 'Invalid format' },

  // === 主题 ===
  'theme.label':   { zh: '主题', en: 'Theme' },
}

/**
 * 翻译函数
 * @param {string} key - 字典键
 * @param {object} params - 替换参数 (e.g., { count: 5 })
 * @returns {string}
 */
export function t(key, params = {}) {
  const entry = dict[key]
  if (!entry) return key
  let text = entry[locale.value] || entry.en || key
  Object.entries(params).forEach(([k, v]) => {
    text = text.replace(`{${k}}`, v)
  })
  return text
}

/** 切换语言 */
export function setLocale(lang) {
  locale.value = lang
  localStorage.setItem(STORAGE_KEY, lang)
}

/** 当前语言 */
export function useI18n() {
  return { locale, t, setLocale }
}
```

## Component Usage

```vue
<script setup>
import { useI18n } from '../i18n.js'
const { t, locale, setLocale } = useI18n()
</script>

<template>
  <h1>{{ t('app.title') }}</h1>
  <input :placeholder="t('app.search')" />

  <!-- 语言切换 -->
  <select :value="locale" @change="setLocale($event.target.value)">
    <option value="zh">中文</option>
    <option value="en">English</option>
  </select>
</template>
```

## Rules

1. **Dictionary keys** — Use dot notation: `'section.item'` (e.g., `'app.title'`, `'stats.total'`)
2. **Always provide both zh and en** — Never leave a language missing
3. **Persistence** — Store language preference in localStorage
4. **No vue-i18n** — For most Vue 3 JS projects, a simple dictionary is sufficient
5. **Params** — Support `{variable}` substitution for dynamic values
