UNPKG

@gulibs/react-vintl

Version:

Type-safe i18n library for React with Vite plugin and automatic type inference

939 lines (717 loc) 26.1 kB
# @gulibs/react-vintl > 🌍 为 React 设计的类型安全 i18n 库,具有自动类型推断功能 - 无需单独的键文件! ## ✨ 特性 - 🎯 **完美的 IDE 自动补全** - 联合类型生成提供无与伦比的自动补全体验(例如,输入 `t('common.` 会显示所有 `common.*` 键) -**极致 API 设计** - `useTranslation()` 直接返回函数,无需解构! - 🚀 **零配置** - 开箱即用,具有合理的默认设置 - 🔥 **热模块替换** - 翻译文件的更改会立即更新类型和 UI - 📦 **Vite 插件** - 与您的 Vite 工作流无缝集成 - 🎨 **React Hooks 和组件** - 适用于所有用例的灵活 API - 🌐 **多种文件格式** - 支持 JSON、TypeScript 和 JavaScript - 💪 **完全类型安全** - 无效翻译键的编译时错误 - 🔄 **命名空间支持** - 使用点记法的有序分层键结构 - 🔄 **自动类型同步** - 添加新字段后编辑器立即识别,无需重启 TypeScript 服务器 - 🌐 **远程翻译加载** - 支持从网络接口动态加载翻译并自动合并 ## 📦 安装 ```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') ``` #### `useLoadRemoteTranslations(source, options?)` **新特性**: 从外部接口加载翻译资源并自动合并到现有资源。 支持三种加载方式: - **URL 字符串**: 从 HTTP 接口加载 - **函数**: 从自定义加载函数获取 - **Promise**: 从 Promise 获取 ```typescript // 从 URL 加载 const { loading, error, data, retry } = useLoadRemoteTranslations( 'https://api.example.com/translations/en', { namespace: 'remote', // 合并到指定的 namespace locale: 'en', merge: true, // 自动合并(默认 true) cache: true, // 启用缓存(默认 true) onSuccess: (resources) => console.log('Loaded:', resources), onError: (error) => console.error('Error:', error), retry: 3, // 重试次数 retryDelay: 1000 // 重试延迟(毫秒) } ) // 从函数加载 const loader = useLoadRemoteTranslations( () => fetch('/api/translations').then(r => r.json()), { namespace: 'remote' } ) // 从 Promise 加载 const promiseLoader = useLoadRemoteTranslations( fetch('/api/translations').then(r => r.json()), { namespace: 'remote' } ) // 使用合并后的翻译(立即可用) const t = useTranslation() t('remote.title' as any) // 动态字段需要使用类型断言 ``` **选项说明:** ```typescript interface LoadRemoteTranslationsOptions { locale?: string // 目标语言,默认使用当前语言 namespace?: string // 合并到指定的 namespace(可选) merge?: boolean // 是否自动合并(默认 true) cache?: boolean // 是否启用缓存(默认 true) onSuccess?: (resources: I18nResources) => void onError?: (error: Error) => void retry?: number // 重试次数(默认 0) retryDelay?: number // 重试延迟(毫秒,默认 1000) } ``` **返回值:** ```typescript interface LoadRemoteTranslationsResult { loading: boolean // 是否正在加载 error: Error | null // 错误信息 data: I18nResources | null // 加载的数据 retry: () => void // 手动重试函数 } ``` **特性:** - ✅ 自动合并到现有资源 - ✅ 支持 namespace 级别的合并 - ✅ 支持根级别合并(不指定 namespace) - ✅ 加载状态管理(loading、error、data) - ✅ 智能缓存机制避免重复请求 - 不同的 `namespace` 配置使用不同的缓存键,避免冲突 - 支持缓存过期时间(默认 5 分钟) - ✅ 请求去重(多个组件同时加载相同资源) - ✅ 错误处理和重试机制 - ✅ 加载后翻译立即可用 - ✅ 资源合并后自动更新翻译函数 **完整示例:** 查看 `test/src/RemoteTranslationDemo.tsx` 获取完整的使用示例。 ### 组件 #### `<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 为所有键提供即时自动补全 ### 自动类型同步 当您添加新的国际化字段时,插件会: 1. **自动检测变化** - 监听 locale 文件夹的文件变化 2. **更新类型定义** - 自动更新 `react-vintl-locales.d.ts` 文件 3. **触发 TypeScript 服务器重新加载** - 通过文件系统事件通知 TypeScript 语言服务器 4. **立即生效** - 编辑器通常在几秒内识别新字段,无需手动重启 这意味着: - ✅ 添加新字段后,编辑器立即提供自动补全 - ✅ 构建时类型检查自动通过,不会因为新字段而失败 - ✅ 无需手动重启 TypeScript 服务器 - ✅ 支持所有主流编辑器(VSCode、WebStorm、Sublime Text 等) ```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' // 来自虚拟模块! ``` ## 🔧 高级用法 ### 远程翻译加载 `react-vintl` 支持从外部接口动态加载翻译资源,实现文件翻译(基础)+ 接口翻译(动态)的混合模式。 #### 基本用法 ```typescript import { useLoadRemoteTranslations, useTranslation } from '@gulibs/react-vintl' function MyComponent() { // 加载远程翻译 const { loading, error } = useLoadRemoteTranslations( 'https://api.example.com/translations/en', { namespace: 'remote', // 合并到 'remote' namespace merge: true // 自动合并 } ) const t = useTranslation() if (loading) return <div>Loading translations...</div> if (error) return <div>Error: {error.message}</div> // 远程翻译立即可用 return <div>{t('remote.title' as any)}</div> } ``` #### 合并策略 **Namespace 级别合并:** ```typescript // 将远程翻译合并到指定的 namespace useLoadRemoteTranslations( fetch('/api/translations').then(r => r.json()), { namespace: 'remote' } ) // 结果结构: // { // en: { // common: { ... }, // 文件翻译 // remote: { ... } // 远程翻译合并到这里 // } // } ``` **根级别合并:** ```typescript // 不指定 namespace,合并到根级别(与现有 namespace 平级) useLoadRemoteTranslations( Promise.resolve({ en: { rootLevel: { message: "This is merged at root level" } }, zh: { rootLevel: { message: "这是在根级别合并的" } } }), { merge: true } // 不指定 namespace ) // 结果结构: // { // en: { // common: { ... }, // 文件翻译 // rootLevel: { ... } // 远程翻译(新 namespace,与 common 平级) // } // } // 使用合并后的翻译 const t = useTranslation() t('rootLevel.message') // ✅ 立即可用 ``` **重要提示:** - 根级别合并时,远程翻译中的每个顶级键都会作为新的 namespace 合并 - 不同的 `namespace` 配置会使用不同的缓存键,即使 `source` 相同也不会冲突 - 资源合并后,翻译函数会在下次渲染时自动更新,确保使用最新资源 #### 手动更新资源 您也可以手动更新资源: ```typescript import { useI18nContext } from '@gulibs/react-vintl' function MyComponent() { const { updateResources } = useI18nContext() const handleLoad = async () => { const remoteTranslations = await fetch('/api/translations').then(r => r.json()) // 合并到指定的 namespace updateResources(remoteTranslations, { namespace: 'remote', merge: true }) } return <button onClick={handleLoad}>Load Translations</button> } ``` #### 工具函数 ```typescript import { mergeResources } from '@gulibs/react-vintl' // 手动合并资源 const merged = mergeResources( baseResources, remoteResources, { namespace: 'remote', // 可选:合并到指定 namespace locale: 'en' // 可选:只合并指定 locale } ) ``` **完整示例:** 查看 `test/src/RemoteTranslationDemo.tsx` 获取完整的使用示例和所有功能演示。 ### 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. **复数规则** - 根据目标语言正确设置复数规则 9. **远程翻译策略** - 使用文件翻译作为基础,远程翻译作为补充 10. **缓存管理** - 合理使用缓存机制,避免不必要的网络请求 - 不同的 `namespace` 配置会自动使用不同的缓存键 - 根级别合并和 namespace 合并不会共享缓存 11. **错误处理** - 为远程翻译加载提供适当的错误处理和重试机制 12. **资源同步** - 资源合并后,翻译函数会在下次渲染时自动更新 - 如果需要在资源合并后立即使用翻译键,可以使用 `useI18n()` 检查资源是否已同步 ## 🔧 日志配置 `react-vintl` 提供了统一的日志系统,允许您控制所有日志输出。所有模块(包括插件)都使用统一的 logger,确保日志行为一致。 ### 日志级别 - **`debug`** - 调试日志,用于开发时的详细调试信息(如 `[useLoadRemoteTranslations]``[mergeResources]`- **`info`** - 一般信息日志,用于重要的运行时信息 - **`warn`** - 警告日志,用于潜在问题或非致命错误 - **`error`** - 错误日志,用于错误信息(始终输出,包括生产环境) ### 默认行为 - **开发环境** (`NODE_ENV=development`): 所有日志级别都启用 - **生产环境** (`NODE_ENV=production`): 仅 `error` 日志启用,`debug``info``warn` 自动禁用 ### 控制日志输出 ```typescript import { logger } from '@gulibs/react-vintl' // 禁用调试日志 logger.setConfig({ debug: false }) // 禁用信息日志 logger.setConfig({ info: false }) // 禁用警告日志 logger.setConfig({ warn: false }) // 禁用错误日志(不推荐,仅在特殊情况下使用) logger.setConfig({ error: false }) // 同时配置多个选项 logger.setConfig({ debug: false, info: true, warn: true, error: true }) ``` ### 日志去重 Logger 默认启用去重机制,防止相同消息在短时间内(默认 5 秒)重复输出: ```typescript // 禁用去重 logger.setConfig({ deduplicate: false }) // 自定义去重时间窗口(毫秒) logger.setConfig({ deduplicationWindow: 10000 }) // 10 秒 ``` ### 调试日志和生产日志的区别 - **调试日志** (`debug`): 包含详细的执行流程信息,如资源加载、合并过程等,默认在生产环境禁用 - **生产日志**: 仅包含错误信息,确保生产环境不会输出过多日志影响性能 ### 插件日志 插件中的日志也通过 logger 统一控制。插件会尊重其 `debug` 选项,但最终输出由 logger 配置决定: ```typescript // vite.config.ts createReactVintlPlugin({ basePath: 'src/locales', debug: true // 插件会使用 logger.debug() 输出日志 }) ``` 即使插件设置了 `debug: true`,如果 logger 的 `debug` 配置为 `false`,日志仍不会输出。 ## 🐛 故障排除 ### 类型在编辑器中不显示? 1. **等待自动同步** - 插件会自动更新类型文件并触发 TypeScript 服务器重新加载,通常几秒内生效 2. **手动重启 TypeScript 服务器** - 如果类型仍未更新,在编辑器中重启 TypeScript 服务器: - **VSCode**: `Cmd/Ctrl + Shift + P` → 输入 "TypeScript: Restart TS Server" - **WebStorm**: `File``Invalidate Caches...` → 选择 "Invalidate and Restart" 3. 检查插件是否在 `vite.config.ts` 中正确配置 4. 确保翻译文件位于正确的位置 5. 检查 `react-vintl-locales.d.ts` 文件是否存在且包含最新的类型定义 ### 添加新字段后编辑器仍显示类型错误? 这是类型同步问题。插件会自动处理,但有时需要额外步骤: 1. **等待几秒钟** - 插件会在文件变化后自动更新类型并触发 TypeScript 服务器重新加载 2. **检查文件是否已更新** - 查看 `react-vintl-locales.d.ts` 文件,确认新字段已包含在类型定义中 3. **重启 TypeScript 服务器** - 如果类型文件已更新但编辑器仍显示错误,手动重启 TypeScript 服务器 4. **检查构建模式** - 在构建时,类型文件会自动更新,确保构建时类型检查通过 5. **查看插件日志** - 如果启用了 `debug: true`,检查控制台是否有类型更新相关的错误信息 **如果问题仍然存在:** - 确保开发服务器正在运行(类型更新需要 HMR 支持) - 检查文件权限,确保插件可以写入类型文件 - 尝试删除 `react-vintl-locales.d.ts` 文件,让插件重新生成 ### 热重载不工作? 1. 检查插件选项中的 `hmr: true` 2. 验证文件路径是否正确 3. 重启开发服务器 ### 参数插值不工作? 1. **检查参数格式** - 确保使用 `{{key}}` 格式 2. **验证参数传递** - 确保参数对象正确传递 3. **检查控制台警告** - 查看是否有参数验证错误 4. **测试简单插值** - 先测试 `{{name}}` 等简单格式 5. **检查嵌套对象** - 确保嵌套对象结构正确 ### 格式化不生效? 1. **检查格式选项** - 确保格式选项拼写正确 2. **验证数据类型** - 确保传入的数据类型正确 3. **测试浏览器支持** - 某些格式化功能需要现代浏览器支持 4. **查看控制台错误** - 格式化失败时会回退到原始值 ## 📄 许可证 MIT ## 🤝 贡献 欢迎贡献!请随时提交 Pull Request。 --- **由 gulibs 用 ❤️ 制作**