# 国际化 (i18n) 规范

## 核心原则

1. **零硬编码** - 所有用户可见文本必须使用翻译键
2. **结构化键名** - 使用点分层级（如 `user.profile.title`）
3. **参数化文本** - 使用占位符而非字符串拼接

---

## 🎨 单文件 i18n 配置（轻量方案）

> **适用于不想引入 vue-i18n 插件的项目，使用自定义实现**

### 配置文件结构
```
src/
├── locales/
│   └── messages.ts       # 翻译字典（键值对数组格式）
├── stores/
│   └── state/
│       └── locale.ts     # 语言状态管理（Pinia Store）
└── main.ts               # 全局 $t 方法注册
```

### 1. 翻译文件格式
```typescript
// src/locales/messages.ts
// 索引 0 为英文，索引 1 为中文

const messages: Record<string, [string, string]> = {
  // ========== 通用 ==========
  是: ['Yes', '是'],
  否: ['No', '否'],
  确定: ['OK', '确定'],
  取消: ['Cancel', '取消'],
  保存: ['Save', '保存'],
  提交: ['Submit', '提交'],
  搜索: ['Search', '搜索'],
  查询: ['Query', '查询'],
  重置: ['Reset', '重置'],
  新增: ['Add', '新增'],
  编辑: ['Edit', '编辑'],
  删除: ['Delete', '删除'],
  操作: ['Operation', '操作'],
  状态: ['Status', '状态'],
  
  // ========== 业务模块 ==========
  订单号: ['Order No', '订单号'],
  金额: ['Amount', '金额'],
  // ... 按模块分组添加
}

export default messages
```

### 2. 语言状态管理
```typescript
// src/stores/state/locale.ts
import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useLocaleStore = defineStore('locale', () => {
  const locale = ref<'zh-cn' | 'en'>(
    (localStorage.getItem('locale') as 'zh-cn' | 'en') || 'zh-cn'
  )

  const setLocale = (lang: 'zh-cn' | 'en') => {
    locale.value = lang
    localStorage.setItem('locale', lang)
  }

  const toggleLocale = () => {
    setLocale(locale.value === 'zh-cn' ? 'en' : 'zh-cn')
  }

  return { locale, setLocale, toggleLocale }
})
```

### 3. main.ts 全局注册
```typescript
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import messages from '@/locales/messages'
import { useLocaleStore } from '@/stores'

const app = createApp(App)
const pinia = createPinia()

// ⚠️ 必须先注册 pinia，再使用 store
app.use(pinia)

const localeStore = useLocaleStore()

// 注册全局 $t 方法
app.config.globalProperties.$t = (key: string): string => {
  const translation = messages[key]
  if (!translation) {
    console.warn(`[i18n] Missing translation: ${key}`)
    return key
  }
  return translation[localeStore.locale === 'zh-cn' ? 1 : 0] || key
}

app.mount('#app')
```

### 4. 在组件中使用

**模板中直接使用：**
```vue
<template>
  <el-button>{{ $t('保存') }}</el-button>
  <el-table-column :label="$t('订单号')" prop="orderNo" />
</template>
```

**script 中使用（需获取实例）：**
```typescript
<script setup lang="ts">
import { getCurrentInstance } from 'vue'

const { appContext } = getCurrentInstance()!
const $t = appContext.config.globalProperties.$t

// 消息提示
ElMessage.success($t('保存成功'))

// 确认对话框
ElMessageBox.confirm($t('确认删除吗？'), $t('提示'))

// 表单验证
const rules = {
  name: [{ required: true, message: $t('请输入名称'), trigger: 'blur' }]
}
</script>
```

### ❌ 禁止事项
```typescript
// ❌ 不要引入 useI18n（项目未使用 vue-i18n）
import { useI18n } from 'vue-i18n'

// ❌ 不要硬编码文本
<el-button>保存</el-button>

// ❌ 不要字符串拼接
const msg = '删除' + name + '成功'
```

---

## Vue I18n 标准配置（其他项目）

### 基础设置
```typescript
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import zhCN from './locales/zh-CN'
import enUS from './locales/en-US'

const i18n = createI18n({
  legacy: false, // 使用 Composition API
  locale: 'zh-CN',
  fallbackLocale: 'en-US',
  messages: {
    'zh-CN': zhCN,
    'en-US': enUS
  }
})

export default i18n
```

### 翻译文件
```typescript
// src/i18n/locales/zh-CN.ts
export default {
  common: {
    save: '保存',
    cancel: '取消',
    confirm: '确认',
    delete: '删除'
  },
  user: {
    profile: {
      title: '个人资料',
      name: '姓名',
      email: '邮箱'
    }
  },
  message: {
    saveSuccess: '保存成功',
    deleteConfirm: '确认删除 {name} 吗？',
    itemCount: '{count} 项'
  }
}
```

## 在组件中使用

### Setup 语法
```vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

const { t, locale } = useI18n()

const switchLocale = () => {
  locale.value = locale.value === 'zh-CN' ? 'en-US' : 'zh-CN'
}

const deleteUser = (name: string) => {
  const message = t('message.deleteConfirm', { name })
  console.log(message) // 确认删除 张三 吗？
}
</script>

<template>
  <!-- ✅ 好 -->
  <el-button>{{ t('common.save') }}</el-button>
  <h1>{{ t('user.profile.title') }}</h1>
  
  <!-- 参数插值 -->
  <p>{{ t('message.deleteConfirm', { name: userName }) }}</p>
  
  <!-- ❌ 坏 - 硬编码 -->
  <el-button>保存</el-button>
</template>
```

### TypeScript 中使用
```typescript
import { useI18n } from 'vue-i18n'

export function useUserActions() {
  const { t } = useI18n()
  
  const showMessage = () => {
    ElMessage.success(t('message.saveSuccess'))
  }
  
  return { showMessage }
}
```

## Element Plus 国际化

```typescript
// main.ts
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'

const locale = ref('zh-CN')

const elLocale = computed(() => 
  locale.value === 'zh-CN' ? zhCn : en
)

app.use(ElementPlus, {
  locale: elLocale.value
})
```

## 复数和格式化

```typescript
// 翻译文件
export default {
  message: {
    // 复数
    itemCount: 'no items | one item | {count} items',
    
    // 日期
    lastLogin: '最后登录: {date}',
    
    // 数字
    price: '价格: {amount}'
  }
}

// 使用
const count = ref(5)
t('message.itemCount', count.value) // "5 items"

t('message.lastLogin', { 
  date: new Date().toLocaleDateString() 
})

t('message.price', { 
  amount: new Intl.NumberFormat('zh-CN').format(1234.56) 
})
```

## 最佳实践

### 键名规范
```typescript
// ✅ 好 - 结构化、语义化
'user.profile.edit'
'product.list.title'
'message.validation.required'

// ❌ 坏
'edit'
'title'
'msg1'
```

### 避免硬编码
```vue
<!-- ❌ 坏 -->
<el-table-column label="姓名" />
<el-button>提交</el-button>

<!-- ✅ 好 -->
<el-table-column :label="t('user.name')" />
<el-button>{{ t('common.submit') }}</el-button>
```

### 参数化文本
```typescript
// ❌ 坏 - 字符串拼接
const message = '欢迎，' + userName + '!'

// ✅ 好 - 使用参数
const message = t('welcome.message', { name: userName })

// 翻译文件
export default {
  welcome: {
    message: '欢迎，{name}！'
  }
}
```

## TypeScript 类型支持

```typescript
// i18n.d.ts
import 'vue-i18n'

declare module 'vue-i18n' {
  export interface DefineLocaleMessage {
    common: {
      save: string
      cancel: string
    }
    user: {
      profile: {
        title: string
      }
    }
  }
}
```

---

## ⚡ Flutter ARB 国际化

> 适用于 Flutter 项目的 ARB（Application Resource Bundle）国际化方案

### 项目结构

```
lib/
├── l10n/
│   ├── app_en.arb        # 英文翻译（主语言）
│   └── app_zh.arb        # 中文翻译
├── generated/
│   └── l10n.dart          # 自动生成的 S 类（flutter gen-l10n）
└── l10n.yaml              # 国际化配置
```

### l10n.yaml 配置

```yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: S
```

### ARB 文件格式

```json
// app_en.arb
{
  "@@locale": "en",
  "submit": "Submit",
  "enterAmount": "Enter amount",
  "networkError": "Network error, please try again",
  "transferTo": "Transfer to {name}",
  "@transferTo": {
    "placeholders": {
      "name": { "type": "String" }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "placeholders": {
      "count": { "type": "int" }
    }
  }
}
```

```json
// app_zh.arb
{
  "@@locale": "zh",
  "submit": "提交",
  "enterAmount": "请输入金额",
  "networkError": "网络错误，请重试",
  "transferTo": "转账给{name}",
  "itemCount": "{count, plural, =0{没有项目} =1{1 个项目} other{{count} 个项目}}"
}
```

### 在代码中使用

```dart
// Widget 层（有 BuildContext）
Text(S.of(context).submit)
Text(S.of(context).enterAmount)
Text(S.of(context).transferTo('张三'))

// 无 BuildContext 时（Controller / UseCase）
// 使用 GetX 的 context 或 S.current
Text(S.current.networkError)

// 领域层使用（谨慎使用，优先通过参数传递）
final msg = S.of(Get.context!).transferTo(recipientName);
```

### ARB Key 命名规范

```json
// ✅ 好 — 语义化、驼峰命名
"submitButton": "Submit",
"enterAmount": "Enter amount",
"transferSuccess": "Transfer successful",
"recipientName": "Recipient name"

// ❌ 坏 — 无意义或缩写
"btn1": "Submit",
"msg": "Enter amount",
"s1": "Transfer successful"
```

### 禁止模式

```dart
// ❌ 1. 硬编码中文
Text('请输入金额')
AppToast.error('网络错误')
hintText: '搜索'

// ❌ 2. 字符串拼接
Text('转账给' + name)

// ✅ 正确
Text(S.of(context).enterAmount)
AppToast.error(S.current.networkError)
hintText: S.of(context).search
Text(S.of(context).transferTo(name))
```

### 新增 ARB Key 流程

1. 在 `app_en.arb`（主语言）中添加 key
2. 在 `app_zh.arb` 中添加对应翻译
3. 运行 `flutter gen-l10n`（通常自动执行）
4. 在代码中使用 `S.of(context).keyName` 或 `S.current.keyName`
