@gulibs/react-vintl
Version:
Type-safe i18n library for React with Vite plugin and automatic type inference
608 lines (459 loc) • 15.2 kB
Markdown
# @gulibs/react-vintl
> 🌍 为 React 设计的类型安全 i18n 库,具有自动类型推断功能 - 无需单独的键文件!
## ✨ 特性
- 🎯 **完美的 IDE 自动补全** - 联合类型生成提供无与伦比的自动补全体验(例如,输入 `t('common.` 会显示所有 `common.*` 键)
- ⚡ **极致 API 设计** - `useTranslation()` 直接返回函数,无需解构!
- 🚀 **零配置** - 开箱即用,具有合理的默认设置
- 🔥 **热模块替换** - 翻译文件的更改会立即更新类型和 UI
- 📦 **Vite 插件** - 与您的 Vite 工作流无缝集成
- 🎨 **React Hooks 和组件** - 适用于所有用例的灵活 API
- 🌐 **多种文件格式** - 支持 JSON、TypeScript 和 JavaScript
- 💪 **完全类型安全** - 无效翻译键的编译时错误
- 🔄 **命名空间支持** - 使用点记法的有序分层键结构
## 📦 安装
```bash
npm install @gulibs/react-vintl
# 或
pnpm add @gulibs/react-vintl
# 或
yarn add @gulibs/react-vintl
```
## 🚀 快速开始
### 1. 配置 Vite 插件
```typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { createReactVintlPlugin } from '@gulibs/react-vintl'
export default defineConfig({
plugins: [
react(),
createReactVintlPlugin({
basePath: 'src/locales',
debug: true
})
]
})
```
### 2. 创建翻译文件
```
src/locales/
├── en/
│ ├── common.json
│ └── auth.json
└── zh/
├── common.json
└── auth.json
```
**翻译文件示例:**
```json
// src/locales/en/common.json
{
"welcome": "Welcome",
"buttons": {
"save": "Save",
"cancel": "Cancel"
}
}
```
```json
// src/locales/zh/common.json
{
"welcome": "欢迎",
"buttons": {
"save": "保存",
"cancel": "取消"
}
}
```
### 3. 在您的应用中使用
```tsx
// src/App.tsx
import { I18nProvider, useTranslation, useI18n, Translation } from '@gulibs/react-vintl'
import { resources, supportedLocales } from '@gulibs/react-vintl-locales'
function ExampleComponent() {
// ✅ 极致 API:useTranslation 直接返回翻译函数
const t = useTranslation()
// ✅ 用于全局状态的独立钩子
const { locale, setLocale } = useI18n()
return (
<div>
<h1>{t('common.welcome')}</h1>
<button>{t('common.buttons.save')}</button>
{/* 使用 Translation 组件 */}
<Translation keyPath="common.welcome" />
{/* 语言切换器 */}
<select value={locale} onChange={e => setLocale(e.target.value)}>
{supportedLocales.map(loc => (
<option key={loc} value={loc}>{loc}</option>
))}
</select>
</div>
)
}
export default function App() {
return (
<I18nProvider
resources={resources}
defaultLocale="en"
supportedLocales={supportedLocales}
>
<ExampleComponent />
</I18nProvider>
)
}
```
## 📖 API 参考
### 插件选项
```typescript
interface I18nPluginOptions {
basePath?: string // 默认: 'src/locales'
extensions?: string[] // 默认: ['.json', '.ts', '.js']
localePattern?: 'directory' | 'filename' // 默认: 'directory'
hmr?: boolean // 默认: true
debug?: boolean // 默认: false
}
```
### 钩子
#### `useTranslation(namespace?)`
**新特性**: 直接返回翻译函数 - 无需解构!
```typescript
// 无命名空间 - 完整键路径
const t = useTranslation()
t('common.welcome') // ✅ 完整自动补全
t('auth.login.title') // ✅ 所有键都可用
// 带命名空间 - 相对键路径
const tCommon = useTranslation('common')
tCommon('welcome') // ✅ 仅 common.* 键
tCommon('buttons.save') // ✅ 作用域自动补全
const tAuth = useTranslation('auth')
tAuth('login.title') // ✅ 仅 auth.* 键
// 带参数
const text = t('messages.greeting', { name: 'World' })
```
**类型签名:**
```typescript
function useTranslation(): TranslationFunction
function useTranslation<N extends I18nNamespaces>(namespace: N): NamespacedTranslationFunction<N>
```
#### `useI18n()`
**新特性**: 从翻译函数中独立获取全局 i18n 状态。
```typescript
const { locale, supportedLocales, setLocale, resources } = useI18n()
// 现在您可以多次调用 useTranslation 而不会产生冗余状态
const t1 = useTranslation('common')
const t2 = useTranslation('auth')
const { locale } = useI18n() // 只获取状态一次
```
#### `useLocale()`
用于语言切换的 `useI18n()` 简化版本。
```typescript
const { locale, supportedLocales, setLocale, isSupportedLocale } = useLocale()
```
#### `useTranslationKey(key, params?)`
返回带有记忆化的特定翻译。
```typescript
const { translation, t } = useTranslationKey('common.welcome')
```
### 组件
#### `<Translation>`
渲染带有可选参数和命名空间支持的翻译。
```tsx
{/* 无命名空间 - 完整键路径 */}
<Translation keyPath="common.welcome" />
<Translation keyPath="auth.login.title" params={{ name: 'React' }} />
{/* 带命名空间 - 相对键路径 */}
<Translation namespace="common" keyPath="welcome" />
<Translation namespace="auth" keyPath="login.title" />
{/* 自定义渲染 */}
<Translation keyPath="common.welcome" fallback="Welcome">
{text => <strong>{text}</strong>}
</Translation>
```
#### `<T>`
`<Translation>` 组件的别名。
```tsx
<T keyPath="common.welcome" />
<T namespace="auth" keyPath="logout" />
```
#### `<LocaleSwitcher>`
内置语言切换器组件。
```tsx
<LocaleSwitcher />
```
## ⚡ 极致 API 设计
`react-vintl` 遵循现代 React Hooks 约定,通过**直接返回函数**而不是对象。
### 对象返回的问题
```typescript
// ❌ 传统方法 - 需要解构
const { t } = useTranslation()
const { t: tCommon } = useTranslation('common')
const { t: tAuth, locale } = useTranslation('auth')
// 问题:冗长,冗余的状态访问
```
### 我们的解决方案:直接函数返回
```typescript
// ✅ 极致 API - 直接函数返回
const t = useTranslation() // 无需解构!
const tCommon = useTranslation('common')
const tAuth = useTranslation('auth')
// ✅ 独立的状态访问
const { locale, setLocale } = useI18n() // 仅在需要时
```
### 优势
1. **更简洁的代码**
```typescript
const t = useTranslation() // 对比 const { t } = useTranslation()
```
2. **清晰的关注点分离**
- `useTranslation()` → 翻译函数
- `useI18n()` → 全局状态管理
- `useLocale()` → 语言切换
3. **更好的性能**
- 使用多个命名空间时不会创建冗余对象
- 减少内存占用
4. **遵循 React 约定**
- 像 `useRef()` - 直接返回 ref
- 像 `useCallback()` - 直接返回函数
- 像 `useMemo()` - 直接返回值
### 实际示例
```typescript
function MyComponent() {
// 多个翻译函数 - 无冗余
const tCommon = useTranslation('common')
const tAuth = useTranslation('auth')
const tProducts = useTranslation('products')
// 全局状态 - 只访问一次
const { locale, setLocale } = useI18n()
return (
<div>
<h1>{tCommon('welcome')}</h1>
<p>{tAuth('login.title')}</p>
<button>{tProducts('addToCart')}</button>
<p>Current: {locale}</p>
</div>
)
}
```
## 🎯 类型推断魔法
`react-vintl` 的核心创新是**自动类型推断**,无需生成单独的类型文件!
### 工作原理
1. **启动时生成** - 插件扫描翻译文件并在 `react-vintl-locales.d.ts` 中生成联合类型
2. **联合类型推断** - 所有翻译键都被提取为字符串字面量联合类型
3. **热模块替换** - 文件更改会自动更新类型并触发 HMR
4. **完美的 IDE 集成** - TypeScript 为所有键提供即时自动补全
```typescript
// 插件自动在 react-vintl-locales.d.ts 中生成:
export type I18nKeys =
| 'common.welcome'
| 'common.buttons.save'
| 'common.buttons.cancel'
| 'auth.login.title'
| 'auth.login.username'
| ... // 所有您的翻译键作为联合类型!
// 您的编辑器获得完美的自动补全!
const text = t('common.') // ✅ 显示:welcome, buttons.save, buttons.cancel...
const text = t('auth.login.') // ✅ 显示:title, username, password...
```
**为什么使用联合类型?**
- ✅ 完美的 IDE 自动补全体验
- ✅ 即使有数千个键也能快速 TypeScript 编译
- ✅ 使用无效键时的清晰类型错误
- ✅ 命名空间感知建议(输入 `common.` 只显示 `common.*` 键)
### 无需单独的键文件!
与需要生成单独类型定义文件的传统 i18n 库不同,`react-vintl` 直接从虚拟模块推断类型:
```typescript
// ❌ 传统方法 - 需要单独生成
import { I18nKeys } from './i18n-keys' // 单独文件
// ✅ react-vintl - 自动推断
import { type I18nKeys } from '@gulibs/react-vintl-locales' // 来自虚拟模块!
```
## 🔧 高级用法
### TypeScript 翻译文件
您可以使用 TypeScript 进行动态翻译:
```typescript
// src/locales/en/dynamic.ts
export default {
currentYear: new Date().getFullYear(),
greeting: (name: string) => `Hello, ${name}!`,
environment: process.env.NODE_ENV === 'development' ? 'Dev' : 'Prod'
} as const
```
### 命名空间翻译
按功能组织翻译:
```
src/locales/
├── en/
│ ├── common.json
│ ├── auth/
│ │ ├── login.json
│ │ └── register.json
│ └── dashboard/
│ └── stats.json
```
使用点记法访问:
```typescript
t('auth.login.title')
t('dashboard.stats.users')
```
### 高级参数插值
`react-vintl` 支持强大的参数插值系统,包括多种格式化选项:
#### 简单插值
```json
{
"greeting": "Hello, {{name}}!",
"user_info": "User {{user.name}} has {{user.count}} messages"
}
```
```tsx
t('greeting', { name: 'John' })
// 结果: "Hello, John!"
t('user_info', { user: { name: 'Jane', count: 3 } })
// 结果: "User Jane has 3 messages"
```
#### 复数形式
```json
{
"item_count": "{{count, plural, one=1 item, other=# items}}",
"message_count": "{{count, plural, zero=No messages, one=1 message, other=# messages}}"
}
```
```tsx
t('item_count', { count: 1 }) // "1 item"
t('item_count', { count: 5 }) // "5 items"
t('message_count', { count: 0 }) // "No messages"
```
#### 选择形式
```json
{
"gender_greeting": "{{gender, select, male=He, female=She, other=They}} is here",
"status_message": "{{status, select, active=Online, inactive=Offline, other=Unknown}}"
}
```
```tsx
t('gender_greeting', { gender: 'male' }) // "He is here"
t('gender_greeting', { gender: 'female' }) // "She is here"
t('status_message', { status: 'active' }) // "Online"
```
#### 数字格式化
```json
{
"price": "Price: {{amount, number}}",
"decimal": "Value: {{value, number, minimumFractionDigits=2}}",
"currency": "Cost: {{amount, currency, currency=USD}}"
}
```
```tsx
t('price', { amount: 1234.56 }) // "Price: 1,234.56"
t('decimal', { value: 1234.5 }) // "Value: 1,234.50"
t('currency', { amount: 99.99 }) // "Cost: $99.99"
```
#### 日期格式化
```json
{
"created_date": "Created on {{date, date}}",
"full_date": "{{date, date, year=numeric, month=long, day=numeric}}",
"time": "Time: {{date, date, hour=numeric, minute=2-digit}}"
}
```
```tsx
t('created_date', { date: new Date() })
// 结果: "Created on 1/15/2024"
t('full_date', { date: new Date('2024-01-15') })
// 结果: "January 15, 2024"
```
#### 文本格式化
```json
{
"text_format": "{{text, uppercase}} - {{text, lowercase}} - {{text, capitalize}}"
}
```
```tsx
t('text_format', { text: 'hello world' })
// 结果: "HELLO WORLD - hello world - Hello world"
```
#### 嵌套对象支持
```json
{
"user_profile": "{{user.profile.name}} ({{user.profile.age}} years old)"
}
```
```tsx
t('user_profile', {
user: {
profile: {
name: 'John',
age: 25
}
}
})
// 结果: "John (25 years old)"
```
#### 错误处理和验证
`react-vintl` 提供了强大的错误处理机制:
```tsx
// 参数验证
t('greeting', { name: 'John' }) // ✅ 正常
t('greeting', { invalid: 'data' }) // ⚠️ 警告:参数不匹配
t('greeting', { name: null }) // ✅ 正常:null 值会被转换为字符串
// 循环引用检测
const circular = { name: 'John' }
circular.self = circular
t('greeting', circular) // ⚠️ 警告:检测到循环引用
// 格式错误处理
t('price', { amount: 'invalid' }) // ✅ 正常:返回原始值
t('date', { date: 'invalid-date' }) // ✅ 正常:返回原始值
```
## 🎨 与其他解决方案的对比
### vs vgrove-i18n
- ✅ 相同的类型推断方法
- ✅ React 特定的钩子和组件
- ✅ 无需 `keysOutput` 选项
- ✅ **更强大的参数插值系统** - 支持复数、选择、格式化等高级功能
### vs react-i18next
- ✅ 更好的 TypeScript 支持
- ✅ Vite 原生虚拟模块
- ✅ 无运行时开销
- ✅ 自动类型推断
- ✅ **内置格式化功能** - 无需额外插件即可使用数字、日期、货币格式化
### vs formatjs
- ✅ 更简单的 API
- ✅ 无需 ICU 消息格式
- ✅ 更好的开发者体验与自动补全
- ✅ **轻量级实现** - 无需复杂的 ICU 解析器
- ✅ **更好的性能** - 原生 JavaScript 实现,无外部依赖
## 📝 最佳实践
1. **使用目录模式进行本地化** - 更容易组织和维护
2. **保持翻译文件小巧** - 按功能或模块分割
3. **对动态内容使用 TypeScript** - 更好的类型安全
4. **利用自动补全** - 让您的编辑器指导您
5. **合理使用参数插值** - 避免过度复杂的插值表达式
6. **参数验证** - 始终验证传入的参数,避免运行时错误
7. **格式化一致性** - 在项目中保持数字、日期格式的一致性
8. **复数规则** - 根据目标语言正确设置复数规则
## 🐛 故障排除
### 类型在编辑器中不显示?
1. 在编辑器中重启 TypeScript 服务器
2. 检查插件是否在 `vite.config.ts` 中正确配置
3. 确保翻译文件位于正确的位置
### 热重载不工作?
1. 检查插件选项中的 `hmr: true`
2. 验证文件路径是否正确
3. 重启开发服务器
### 参数插值不工作?
1. **检查参数格式** - 确保使用 `{{key}}` 格式
2. **验证参数传递** - 确保参数对象正确传递
3. **检查控制台警告** - 查看是否有参数验证错误
4. **测试简单插值** - 先测试 `{{name}}` 等简单格式
5. **检查嵌套对象** - 确保嵌套对象结构正确
### 格式化不生效?
1. **检查格式选项** - 确保格式选项拼写正确
2. **验证数据类型** - 确保传入的数据类型正确
3. **测试浏览器支持** - 某些格式化功能需要现代浏览器支持
4. **查看控制台错误** - 格式化失败时会回退到原始值
## 📄 许可证
MIT
## 🤝 贡献
欢迎贡献!请随时提交 Pull Request。
---
**由 gulibs 用 ❤️ 制作**