乐闻世界logo
搜索文章和话题

i18next

i18next 是一套用于 Web/Node 端的国际化(i18n)框架,帮助应用把界面文案与代码逻辑解耦,实现多语言翻译、插值、复数、语境等能力的统一管理。它以“资源字典 + 命名空间(namespace)+ key”为核心组织方式,通过 `t('key')` 在运行时根据当前语言与回退策略(fallback)返回对应文案,并支持变量插值(如 `{{name}}`)、复数规则与日期/数字等本地化配合。i18next 生态完善:可结合 `react-i18next` 等适配不同框架,使用后端/浏览器语言检测、按需加载(lazy load)与缓存提升性能,也便于在大型项目中做模块化拆分与动态切换语言。总体而言,i18next 提供了可扩展、工程化的 i18n 基础设施,适合从小型应用到多团队大仓库的多语言需求。
i18next
查看更多相关内容
如何在 React 项目中使用 react-i18next?## react-i18next 简介 react-i18next 是 i18next 的 React 专用绑定库,提供了 React Hooks 和组件,使国际化在 React 应用中更加便捷。 ## 安装 ```bash npm install react-i18next i18next # 或 yarn add react-i18next i18next ``` ## 基本配置 ### 初始化 ```javascript import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import Backend from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; i18n .use(Backend) // 加载翻译资源 .use(LanguageDetector) // 检测用户语言 .use(initReactI18next) // 绑定 react-i18next .init({ fallbackLng: 'en', debug: true, interpolation: { escapeValue: false // React 已经处理了 XSS } }); export default i18n; ``` ### 在应用入口引入 ```javascript import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './i18n'; // 引入 i18n 配置 ReactDOM.render(<App />, document.getElementById('root')); ``` ## 使用 useTranslation Hook ### 基本用法 ```javascript import React from 'react'; import { useTranslation } from 'react-i18next'; function Welcome() { const { t } = useTranslation(); return <h1>{t('welcome')}</h1>; } ``` ### 指定命名空间 ```javascript function MyComponent() { const { t } = useTranslation('common'); return <button>{t('save')}</button>; } ``` ### 多个命名空间 ```javascript function MyComponent() { const { t } = useTranslation(['common', 'errors']); return ( <div> <button>{t('common:save')}</button> <p>{t('errors:notFound')}</p> </div> ); } ``` ## 使用 Trans 组件 ### 基本用法 ```javascript import { Trans } from 'react-i18next'; function MyComponent() { return ( <Trans i18nKey="user.profile"> Welcome <strong>{{name}}</strong> to your profile </Trans> ); } // 翻译资源 // "user.profile": "Welcome <1>{{name}}</1> to your profile" ``` ### 嵌套组件 ```javascript <Trans i18nKey="description"> Click <Link to="/about">here</Link> to learn more </Trans> ``` ## 使用 withTranslation HOC ```javascript import { withTranslation } from 'react-i18next'; class MyComponent extends React.Component { render() { const { t } = this.props; return <h1>{t('title')}</h1>; } } export default withTranslation()(MyComponent); ``` ## 使用 I18nextProvider ```javascript import { I18nextProvider } from 'react-i18next'; import i18n from './i18n'; function App() { return ( <I18nextProvider i18n={i18n}> <MyComponent /> </I18nextProvider> ); } ``` ## 语言切换 ### 创建语言切换器 ```javascript import { useTranslation } from 'react-i18next'; function LanguageSwitcher() { const { i18n } = useTranslation(); const changeLanguage = (lng) => { i18n.changeLanguage(lng); }; return ( <div> <button onClick={() => changeLanguage('en')}>English</button> <button onClick={() => changeLanguage('zh')}>中文</button> </div> ); } ``` ### 监听语言变化 ```javascript function MyComponent() { const { t, i18n } = useTranslation(); const [currentLanguage, setCurrentLanguage] = useState(i18n.language); useEffect(() => { const handleLanguageChange = (lng) => { setCurrentLanguage(lng); }; i18n.on('languageChanged', handleLanguageChange); return () => { i18n.off('languageChanged', handleLanguageChange); }; }, [i18n]); return <p>Current language: {currentLanguage}</p>; } ``` ## 延迟加载命名空间 ```javascript function LazyComponent() { const { t, ready } = useTranslation('lazyNamespace', { useSuspense: false }); if (!ready) { return <div>Loading...</div>; } return <p>{t('content')}</p>; } ``` ## 最佳实践 1. **按需加载**: 使用命名空间和延迟加载优化性能 2. **类型安全**: TypeScript 项目使用类型定义 3. **错误处理**: 处理缺失的翻译 4. **测试**: 编写国际化相关的单元测试 5. **代码分割**: 将翻译资源与应用代码分离
服务端 · 2月18日 22:07
如何为 i18next 编写测试?## 基本测试 ### 测试翻译功能 ```javascript import i18next from 'i18next'; describe('i18next translations', () => { beforeEach(() => { i18next.init({ lng: 'en', resources: { en: { translation: { welcome: 'Welcome', greeting: 'Hello {{name}}' } } } }); }); test('should translate simple key', () => { expect(i18next.t('welcome')).toBe('Welcome'); }); test('should translate with interpolation', () => { expect(i18next.t('greeting', { name: 'John' })).toBe('Hello John'); }); }); ``` ## React 组件测试 ### 测试使用 useTranslation 的组件 ```javascript import { render, screen } from '@testing-library/react'; import { useTranslation } from 'react-i18next'; import { I18nextProvider } from 'react-i18next'; import i18next from 'i18next'; function Welcome() { const { t } = useTranslation(); return <h1>{t('welcome')}</h1>; } describe('Welcome component', () => { const renderWithI18n = (component) => { return render( <I18nextProvider i18n={i18next}> {component} </I18nextProvider> ); }; beforeEach(() => { i18next.init({ lng: 'en', resources: { en: { translation: { welcome: 'Welcome' } } } }); }); test('should display welcome message', () => { renderWithI18n(<Welcome />); expect(screen.getByText('Welcome')).toBeInTheDocument(); }); }); ``` ### 测试语言切换 ```javascript import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { useTranslation } from 'react-i18next'; import { I18nextProvider } from 'react-i18next'; import i18next from 'i18next'; function LanguageSwitcher() { const { t, i18n } = useTranslation(); return ( <div> <p>{t('currentLang')}</p> <button onClick={() => i18n.changeLanguage('zh')}>中文</button> <button onClick={() => i18n.changeLanguage('en')}>English</button> </div> ); } describe('LanguageSwitcher', () => { beforeEach(() => { i18next.init({ lng: 'en', resources: { en: { translation: { currentLang: 'English' } }, zh: { translation: { currentLang: '中文' } } } }); }); test('should switch language', async () => { render( <I18nextProvider i18n={i18next}> <LanguageSwitcher /> </I18nextProvider> ); expect(screen.getByText('English')).toBeInTheDocument(); fireEvent.click(screen.getByText('中文')); await waitFor(() => { expect(screen.getByText('中文')).toBeInTheDocument(); }); }); }); ``` ## 测试 Trans 组件 ```javascript import { render, screen } from '@testing-library/react'; import { Trans } from 'react-i18next'; import { I18nextProvider } from 'react-i18next'; import i18next from 'i18next'; function MyComponent() { return ( <Trans i18nKey="user.greeting"> Welcome <strong>{{name}}</strong> </Trans> ); } describe('Trans component', () => { beforeEach(() => { i18next.init({ lng: 'en', resources: { en: { translation: { 'user.greeting': 'Welcome <1>{{name}}</1>' } } } }); }); test('should render translated content with interpolation', () => { render( <I18nextProvider i18n={i18next}> <MyComponent /> </I18nextProvider> ); expect(screen.getByText(/Welcome/i)).toBeInTheDocument(); }); }); ``` ## Mock i18next ### 创建测试工具函数 ```javascript // test-utils/i18n.js import { initReactI18next } from 'react-i18next'; import i18next from 'i18next'; export const createI18nInstance = (resources = {}) => { const instance = i18next.createInstance(); instance.use(initReactI18next).init({ lng: 'en', resources: { en: { translation: resources } } }); return instance; }; export const renderWithI18n = (component, resources = {}) => { const i18n = createI18nInstance(resources); return render( <I18nextProvider i18n={i18n}> {component} </I18nextProvider> ); }; ``` ### 使用测试工具 ```javascript import { renderWithI18n } from './test-utils/i18n'; test('should render with mocked translations', () => { renderWithI18n(<MyComponent />, { welcome: 'Welcome', goodbye: 'Goodbye' }); expect(screen.getByText('Welcome')).toBeInTheDocument(); }); ``` ## 测试延迟加载 ```javascript import { render, screen, waitFor } from '@testing-library/react'; import { useTranslation } from 'react-i18next'; import { I18nextProvider } from 'react-i18next'; import i18next from 'i18next'; function LazyComponent() { const { t, ready } = useTranslation('lazy', { useSuspense: false }); if (!ready) { return <div>Loading...</div>; } return <p>{t('content')}</p>; } describe('Lazy loading', () => { beforeEach(() => { i18next.init({ lng: 'en', resources: { en: { translation: {} } } }); }); test('should show loading state initially', () => { render( <I18nextProvider i18n={i18next}> <LazyComponent /> </I18nextProvider> ); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); test('should load namespace and show content', async () => { render( <I18nextProvider i18n={i18next}> <LazyComponent /> </I18nextProvider> ); i18next.addResourceBundle('en', 'lazy', { content: 'Loaded content' }); await waitFor(() => { expect(screen.getByText('Loaded content')).toBeInTheDocument(); }); }); }); ``` ## 测试缺失翻译 ```javascript describe('Missing translations', () => { beforeEach(() => { i18next.init({ lng: 'en', fallbackLng: 'en', saveMissing: true, missingKeyHandler: (lng, ns, key) => { console.warn(`Missing translation: ${lng}.${ns}.${key}`); }, resources: { en: { translation: { existing: 'Existing translation' } } } }); }); test('should return key when translation is missing', () => { const result = i18next.t('nonexistent'); expect(result).toBe('nonexistent'); }); test('should use fallback translation', () => { i18next.addResourceBundle('en', 'translation', { fallback: 'Fallback' }); const result = i18next.t('fallback'); expect(result).toBe('Fallback'); }); }); ``` ## 集成测试 ```javascript describe('i18next integration tests', () => { test('should handle language change across components', async () => { const { rerender } = render( <I18nextProvider i18n={i18next}> <App /> </I18nextProvider> ); expect(screen.getByText('English')).toBeInTheDocument(); await i18next.changeLanguage('zh'); rerender( <I18nextProvider i18n={i18next}> <App /> </I18nextProvider> ); expect(screen.getByText('中文')).toBeInTheDocument(); }); }); ``` ## 最佳实践 1. **隔离测试**: 每个测试应该独立运行,不依赖其他测试 2. **Mock 资源**: 使用 mock 的翻译资源,避免依赖实际文件 3. **测试工具**: 创建可复用的测试工具函数 4. **异步测试**: 正确处理异步操作,如语言切换和延迟加载 5. **覆盖边界**: 测试缺失翻译、错误情况等边界条件 6. **快照测试**: 对翻译组件使用快照测试确保一致性 7. **性能测试**: 测试大量翻译时的性能表现
服务端 · 2月18日 22:07
如何在项目中初始化和配置 i18next?## 基本初始化 i18next 的初始化非常简单,使用 `i18next.init()` 方法进行配置: ```javascript import i18next from 'i18next'; i18next.init({ lng: 'en', // 默认语言 debug: true, // 开发模式下开启调试 resources: { en: { translation: { "key": "Hello World" } }, zh: { translation: { "key": "你好世界" } } } }, (err, t) => { if (err) { console.error('i18next 初始化失败:', err); return; } // 初始化完成后的回调 console.log(t('key')); // 输出: Hello World }); ``` ## 关键配置选项 ### 语言相关 - **lng**: 设置默认语言(如 'en', 'zh', 'fr') - **fallbackLng**: 当翻译不存在时的回退语言 - **supportedLngs**: 支持的语言列表 ```javascript i18next.init({ lng: 'zh', fallbackLng: 'en', supportedLngs: ['en', 'zh', 'fr', 'de'] }); ``` ### 资源相关 - **resources**: 直接定义翻译资源 - **ns**: 默认命名空间 - **defaultNS**: 默认使用的命名空间 ```javascript i18next.init({ resources: { en: { translation: { "welcome": "Welcome" }, common: { "save": "Save", "cancel": "Cancel" } } }, ns: ['translation', 'common'], defaultNS: 'translation' }); ``` ### 插值相关 - **interpolation**: 配置插值行为 ```javascript i18next.init({ interpolation: { escapeValue: false, // 不转义 HTML format: function(value, format, lng) { if (format === 'uppercase') return value.toUpperCase(); return value; } } }); ``` ### 检测相关 - **detection**: 语言检测配置 ```javascript i18next.init({ detection: { order: ['querystring', 'cookie', 'localStorage', 'navigator'], caches: ['localStorage', 'cookie'], lookupQuerystring: 'lng' } }); ``` ## 异步初始化 i18next.init() 返回一个 Promise,可以使用 async/await: ```javascript async function initI18n() { try { await i18next.init({ lng: 'zh', resources: { zh: { translation: { "hello": "你好" } } } }); console.log('i18next 初始化成功'); } catch (error) { console.error('初始化失败:', error); } } ``` ## 最佳实践 1. **分离配置**: 将 i18next 配置单独放在一个文件中 2. **环境区分**: 开发和生产环境使用不同配置 3. **错误处理**: 始终处理初始化错误 4. **延迟加载**: 大型应用使用延迟加载提高性能 5. **类型安全**: TypeScript 项目使用类型定义
服务端 · 2月18日 22:07
如何在 TypeScript 项目中使用 i18next?## 基本类型定义 ### 安装类型定义 ```bash npm install --save-dev @types/i18next # 对于 react-i18next npm install --save-dev @types/react-i18next ``` ### 定义翻译资源类型 ```typescript interface TranslationResources { en: { translation: EnTranslation; }; zh: { translation: ZhTranslation; }; } interface EnTranslation { welcome: string; greeting: string; user: { name: string; profile: string; }; } interface ZhTranslation { welcome: string; greeting: string; user: { name: string; profile: string; }; } ``` ## 使用 useTranslation Hook ### 基本用法 ```typescript import { useTranslation } from 'react-i18next'; function MyComponent() { const { t } = useTranslation(); return <h1>{t('welcome')}</h1>; } ``` ### 指定命名空间 ```typescript function MyComponent() { const { t } = useTranslation('common'); return <button>{t('save')}</button>; } ``` ### 类型安全的翻译键 ```typescript // 定义翻译键类型 type TranslationKeys = 'welcome' | 'greeting' | 'user.name'; function useTypedTranslation() { const { t } = useTranslation(); return { t: (key: TranslationKeys, options?: any) => t(key, options) }; } function MyComponent() { const { t } = useTypedTranslation(); return <h1>{t('welcome')}</h1>; } ``` ## 创建类型安全的翻译函数 ### 使用泛型 ```typescript import i18next from 'i18next'; type TranslationFunction<T> = (key: keyof T, options?: any) => string; function createTypedTranslation<T>(ns: string): TranslationFunction<T> { return (key: keyof T, options?: any) => { return i18next.t(`${ns}:${String(key)}`, options); }; } // 使用 interface CommonTranslations { save: string; cancel: string; delete: string; } const tCommon = createTypedTranslation<CommonTranslations>('common'); function MyComponent() { return ( <div> <button>{tCommon('save')}</button> <button>{tCommon('cancel')}</button> </div> ); } ``` ## 使用 i18next-resources-to-ts ### 生成类型定义 ```bash npm install --save-dev i18next-resources-to-ts ``` ```javascript // scripts/generate-types.js const { generateTypes } = require('i18next-resources-to-ts'); const fs = require('fs'); generateTypes('./locales', { indent: 2, sort: true, lineEnding: 'lf' }).then(types => { fs.writeFileSync('./src/types/i18n.d.ts', types); console.log('Types generated successfully'); }); ``` ### 在 package.json 中添加脚本 ```json { "scripts": { "generate:i18n-types": "node scripts/generate-types.js" } } ``` ## 插值类型安全 ```typescript interface GreetingOptions { name: string; title?: string; } function useGreeting() { const { t } = useTranslation(); return (options: GreetingOptions) => { return t('greeting', options); }; } function MyComponent() { const greet = useGreeting(); return ( <div> {greet({ name: 'John' })} {greet({ name: 'Jane', title: 'Dr.' })} </div> ); } ``` ## 复数处理类型 ```typescript interface PluralOptions { count: number; item: string; } function usePlural() { const { t } = useTranslation(); return (options: PluralOptions) => { return t('item_count', options); }; } function MyComponent() { const plural = usePlural(); return ( <div> {plural({ count: 1, item: 'apple' })} {plural({ count: 5, item: 'apple' })} </div> ); } ``` ## 上下文类型 ```typescript type Context = 'male' | 'female' | 'other'; interface ContextOptions { context: Context; name: string; } function useContextTranslation() { const { t } = useTranslation(); return (options: ContextOptions) => { return t('friend', options); }; } function MyComponent() { const tFriend = useContextTranslation(); return ( <div> {tFriend({ context: 'male', name: 'John' })} {tFriend({ context: 'female', name: 'Jane' })} </div> ); } ``` ## 命名空间类型 ```typescript interface Namespaces { common: CommonTranslations; errors: ErrorTranslations; user: UserTranslations; } function useNamespacedTranslation<K extends keyof Namespaces>( namespace: K ) { const { t } = useTranslation(namespace); return { t: (key: keyof Namespaces[K], options?: any) => t(key, options) }; } function MyComponent() { const { t: tCommon } = useNamespacedTranslation('common'); const { t: tErrors } = useNamespacedTranslation('errors'); return ( <div> <button>{tCommon('save')}</button> <p>{tErrors('notFound')}</p> </div> ); } ``` ## Trans 组件类型 ```typescript import { Trans } from 'react-i18next'; interface TransProps { i18nKey: string; values?: Record<string, string | number>; components?: Record<string, React.ReactNode>; } function TypedTrans({ i18nKey, values, components }: TransProps) { return ( <Trans i18nKey={i18nKey} values={values} components={components} /> ); } function MyComponent() { return ( <TypedTrans i18nKey="user.greeting" values={{ name: 'John' }} components={{ strong: <strong /> }} /> ); } ``` ## 最佳实践 1. **生成类型**: 使用工具自动生成翻译资源类型 2. **严格模式**: 启用 TypeScript 严格模式 3. **命名空间**: 使用命名空间组织翻译 4. **类型守卫**: 使用类型守卫确保类型安全 5. **持续集成**: 在 CI/CD 中生成和检查类型 6. **文档**: 记录类型定义和使用方法 7. **测试**: 编写类型测试确保类型正确性
服务端 · 2月18日 22:07
i18next 中如何使用插值、复数和上下文功能?## 基本翻译 使用 `t()` 函数进行翻译: ```javascript // 简单翻译 t('welcome'); // "Welcome" // 嵌套键 t('header.title'); // "Dashboard" ``` ## 插值功能 ### 变量插值 在翻译文本中使用 `{{variable}}` 语法: ```javascript // 翻译资源 { "greeting": "Hello {{name}}", "userCount": "Total users: {{count}}" } // 使用 t('greeting', { name: 'John' }); // "Hello John" t('userCount', { count: 100 }); // "Total users: 100" ``` ### 数组插值 ```javascript { "list": "Items: {{items}}" } t('list', { items: ['apple', 'banana'].join(', ') }); ``` ## 复数处理 i18next 内置对复数的支持: ```javascript // 翻译资源 { "item": "one item", "item_plural": "{{count}} items" } // 使用 t('item', { count: 1 }); // "one item" t('item', { count: 5 }); // "5 items" ``` ### 自定义复数规则 ```javascript i18next.init({ lng: 'ru', resources: { ru: { translation: { "item_one": "один предмет", "item_few": "{{count}} предмета", "item_many": "{{count}} предметов", "item_other": "{{count}} предмет" } } } }); ``` ## 上下文处理 根据上下文显示不同的翻译: ```javascript // 翻译资源 { "friend": "A friend", "friend_male": "A boyfriend", "friend_female": "A girlfriend" } // 使用 t('friend', { context: 'male' }); // "A boyfriend" t('friend', { context: 'female' }); // "A girlfriend" ``` ## 命名空间 ### 使用命名空间 ```javascript i18next.init({ ns: ['common', 'errors'], defaultNS: 'common', resources: { en: { common: { "save": "Save" }, errors: { "notFound": "Not found" } } } }); // 使用 t('save'); // 使用默认命名空间 t('errors:notFound'); // 指定命名空间 ``` ### 动态加载命名空间 ```javascript i18next.loadNamespaces(['admin', 'settings'], () => { // 命名空间加载完成 }); ``` ## 格式化 ### 自定义格式化函数 ```javascript i18next.init({ interpolation: { format: function(value, format, lng) { if (format === 'uppercase') { return value.toUpperCase(); } if (format === 'currency') { return new Intl.NumberFormat(lng, { style: 'currency', currency: 'USD' }).format(value); } return value; } } }); // 使用 t('price', { price: 100, formatParams: { price: { format: 'currency' } } }); ``` ## 嵌套翻译 ```javascript // 翻译资源 { "user": { "profile": { "name": "Name: {{name}}" } } } // 使用 t('user.profile.name', { name: 'John' }); ``` ## 缺失翻译处理 ```javascript i18next.init({ saveMissing: true, missingKeyHandler: (lng, ns, key) => { console.log(`Missing translation: ${lng}.${ns}.${key}`); } }); ``` ## 性能优化 ```javascript // 批量翻译 const keys = ['welcome', 'goodbye', 'save']; const translations = keys.map(key => t(key)); // 缓存翻译结果 const cachedTranslations = new Map(); function getCachedTranslation(key, options) { const cacheKey = `${key}_${JSON.stringify(options)}`; if (!cachedTranslations.has(cacheKey)) { cachedTranslations.set(cacheKey, t(key, options)); } return cachedTranslations.get(cacheKey); } ```
服务端 · 2月18日 22:06
i18next 常见问题及解决方案有哪些?## 常见问题 ### 1. 翻译不显示 **问题**: 调用 `t()` 函数返回键名而不是翻译文本 **原因**: - 翻译资源未正确加载 - 命名空间配置错误 - 语言代码不匹配 **解决方案**: ```javascript // 检查翻译资源是否加载 console.log(i18next.store.data); // 确保语言代码匹配 i18next.init({ lng: 'en', // 确保与资源中的键匹配 resources: { en: { translation: { welcome: 'Welcome' } } } }); // 检查命名空间 i18next.init({ ns: ['translation', 'common'], defaultNS: 'translation' }); ``` ### 2. 语言切换不生效 **问题**: 调用 `changeLanguage()` 后翻译没有更新 **原因**: - 组件没有监听语言变化 - 翻译资源未加载 - React 组件没有重新渲染 **解决方案**: ```javascript // React 组件中使用 useTranslation function MyComponent() { const { t, i18n } = useTranslation(); useEffect(() => { const handleLanguageChange = () => { // 语言变化时重新渲染 }; i18n.on('languageChanged', handleLanguageChange); return () => { i18n.off('languageChanged', handleLanguageChange); }; }, [i18n]); return <h1>{t('welcome')}</h1>; } // 确保翻译资源已加载 await i18next.loadLanguages(['zh', 'fr']); ``` ### 3. 插值不工作 **问题**: 使用 `{{variable}}` 插值时变量没有被替换 **原因**: - 插值配置错误 - 变量名拼写错误 - 转义设置问题 **解决方案**: ```javascript // 检查插值配置 i18next.init({ interpolation: { escapeValue: false, // React 中通常设为 false prefix: '{{', suffix: '}}' } }); // 确保变量名正确 t('greeting', { name: 'John' }); // 翻译资源 { "greeting": "Hello {{name}}" } ``` ### 4. 复数处理不正确 **问题**: 复数形式显示不正确 **原因**: - 复数规则配置错误 - 语言不支持默认复数规则 **解决方案**: ```javascript // 为特定语言配置复数规则 i18next.init({ lng: 'ru', resources: { ru: { translation: { "item_one": "один предмет", "item_few": "{{count}} предмета", "item_many": "{{count}} предметов", "item_other": "{{count}} предмет" } } } }); // 使用正确的复数键 t('item', { count: 1 }); // один предмет t('item', { count: 2 }); // 2 предмета t('item', { count: 5 }); // 5 предметов ``` ### 5. 性能问题 **问题**: 翻译加载缓慢,影响应用性能 **原因**: - 加载了过多翻译资源 - 没有使用缓存 - 网络请求过多 **解决方案**: ```javascript // 使用延迟加载 i18next.init({ ns: ['translation'], // 只加载必要的命名空间 defaultNS: 'translation' }); // 需要时加载其他命名空间 i18next.loadNamespaces(['admin', 'settings']); // 使用缓存 import LocalStorageBackend from 'i18next-localstorage-backend'; i18next .use(LocalStorageBackend) .init({ backend: { expirationTime: 7 * 24 * 60 * 60 * 1000 // 7天 } }); // 预加载关键资源 i18next.init({ preload: ['en', 'zh'] }); ``` ### 6. 缺失翻译处理 **问题**: 缺失的翻译显示键名而不是回退文本 **解决方案**: ```javascript // 配置缺失翻译处理 i18next.init({ fallbackLng: 'en', saveMissing: true, missingKeyHandler: (lng, ns, key) => { console.warn(`Missing translation: ${lng}.${ns}.${key}`); // 发送到翻译管理系统 reportMissingTranslation(lng, ns, key); }, parseMissingKeyHandler: (key) => { return `Translation missing: ${key}`; } }); ``` ### 7. SSR 问题 **问题**: 服务端渲染时翻译不显示 **解决方案**: ```javascript // Next.js 示例 import { appWithTranslation } from 'next-i18next'; export default appWithTranslation(MyApp); // 服务端获取翻译 export async function getServerSideProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, ['common', 'home'])) } }; } ``` ### 8. 内存泄漏 **问题**: 组件卸载后事件监听器未清理 **解决方案**: ```javascript function MyComponent() { const { i18n } = useTranslation(); useEffect(() => { const handleLanguageChange = () => { console.log('Language changed'); }; i18n.on('languageChanged', handleLanguageChange); // 清理事件监听器 return () => { i18n.off('languageChanged', handleLanguageChange); }; }, [i18n]); return <div>Content</div>; } ``` ## 调试技巧 ### 启用调试模式 ```javascript i18next.init({ debug: true, // 启用调试日志 initImmediate: false }); ``` ### 检查翻译资源 ```javascript console.log('Current language:', i18next.language); console.log('Loaded languages:', i18next.languages); console.log('Translation data:', i18next.store.data); console.log('Namespaces:', i18next.store.data[i18next.language]); ``` ### 监听事件 ```javascript i18next.on('loaded', (loaded) => { console.log('Resources loaded:', loaded); }); i18next.on('failedLoading', (lng, ns, msg) => { console.error('Failed to load:', { lng, ns, msg }); }); i18next.on('languageChanged', (lng) => { console.log('Language changed to:', lng); }); ``` ## 最佳实践 1. **错误处理**: 始终处理初始化和加载错误 2. **调试模式**: 开发环境启用调试模式 3. **日志记录**: 记录关键事件和错误 4. **性能监控**: 监控翻译加载性能 5. **单元测试**: 编写测试覆盖常见问题 6. **文档**: 记录配置和使用方法 7. **版本控制**: 使用版本号管理翻译资源
服务端 · 2月18日 22:06
如何优化 i18next 的性能?## 延迟加载 ### 按需加载命名空间 ```javascript // 初始化时只加载必要的命名空间 i18next.init({ ns: ['translation', 'common'], defaultNS: 'translation' }); // 需要时加载其他命名空间 i18next.loadNamespaces(['admin', 'settings'], (err, t) => { if (!err) { console.log('命名空间加载完成'); } }); ``` ### 按需加载语言 ```javascript // 初始化时只加载默认语言 i18next.init({ lng: 'en', preload: ['en'] // 只预加载英语 }); // 用户切换语言时加载 i18next.loadLanguages(['zh', 'fr'], () => { console.log('语言资源加载完成'); }); ``` ### React 中的 Suspense 模式 ```javascript import { useTranslation } from 'react-i18next'; import { Suspense } from 'react'; function LazyComponent() { const { t } = useTranslation('lazyNamespace'); return <p>{t('content')}</p>; } function App() { return ( <Suspense fallback={<div>Loading translations...</div>}> <LazyComponent /> </Suspense> ); } ``` ## 缓存策略 ### 本地存储缓存 ```javascript import LocalStorageBackend from 'i18next-localstorage-backend'; import HttpBackend from 'i18next-http-backend'; i18next .use(HttpBackend) .use(LocalStorageBackend) .init({ backend: { backends: [ LocalStorageBackend, HttpBackend ], backendOptions: [ { expirationTime: 7 * 24 * 60 * 60 * 1000, // 7天过期 defaultVersion: 'v1.0.0', store: window.localStorage }, { loadPath: '/locales/{{lng}}/{{ns}}.json' } ] } }); ``` ### 内存缓存 ```javascript const translationCache = new Map(); function getCachedTranslation(key, options) { const cacheKey = `${key}_${JSON.stringify(options)}`; if (translationCache.has(cacheKey)) { return translationCache.get(cacheKey); } const translation = i18next.t(key, options); translationCache.set(cacheKey, translation); return translation; } ``` ### 缓存失效策略 ```javascript backend: { loadPath: '/locales/{{lng}}/{{ns}}.json?v={{version}}', queryStringParams: { version: process.env.TRANSLATION_VERSION || '1.0.0' } } ``` ## 资源优化 ### 压缩翻译资源 ```javascript // 使用 gzip 压缩 // 服务器配置示例 (Express) const express = require('express'); const compression = require('compression'); const app = express(); app.use(compression()); app.use('/locales', express.static('locales')); ``` ### 按功能拆分翻译文件 ```javascript // 文件结构 // locales/ // ├── en/ // │ ├── common.json // │ ├── admin.json // │ ├── user.json // │ └── settings.json // └── zh/ // ├── common.json // ├── admin.json // ├── user.json // └── settings.json // 配置 i18next.init({ ns: ['common', 'admin', 'user', 'settings'], defaultNS: 'common' }); ``` ### 移除未使用的翻译 ```javascript // 构建时分析代码中使用的翻译键 const usedKeys = analyzeTranslationKeysInCode(); const allTranslations = loadAllTranslations(); const optimizedTranslations = filterTranslations(allTranslations, usedKeys); saveOptimizedTranslations(optimizedTranslations); ``` ## 请求优化 ### 批量加载 ```javascript // 一次性加载多个命名空间 Promise.all([ i18next.loadNamespaces(['admin', 'settings', 'reports']), i18next.loadLanguages(['en', 'zh']) ]).then(() => { console.log('所有翻译资源加载完成'); }); ``` ### 预加载关键资源 ```javascript i18next.init({ preload: ['en', 'zh'], // 预加载的语言 ns: ['translation', 'common'], // 预加载的命名空间 partialBundledLanguages: true // 允许部分加载 }); ``` ### 使用 CDN ```javascript backend: { loadPath: 'https://cdn.example.com/locales/{{lng}}/{{ns}}.json', crossDomain: true } ``` ## 运行时优化 ### 避免频繁翻译 ```javascript // 不好的做法 function renderList(items) { return items.map(item => ( <div key={item.id}> <h3>{t('item.title', { name: item.name })}</h3> <p>{t('item.description', { desc: item.description })}</p> </div> )); } // 好的做法 - 缓存翻译 function renderList(items) { const titleTemplate = t('item.title'); const descTemplate = t('item.description'); return items.map(item => ( <div key={item.id}> <h3>{titleTemplate.replace('{{name}}', item.name)}</h3> <p>{descTemplate.replace('{{desc}}', item.description)}</p> </div> )); } ``` ### 使用 React.memo 优化 ```javascript import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const TranslatedComponent = memo(({ text }) => { const { t } = useTranslation(); return <span>{t(text)}</span>; }); ``` ### 虚拟滚动优化长列表 ```javascript import { FixedSizeList } from 'react-window'; function TranslatedList({ items }) { const { t } = useTranslation(); const Row = ({ index, style }) => ( <div style={style}> {t(items[index].key)} </div> ); return ( <FixedSizeList height={400} itemCount={items.length} itemSize={35} > {Row} </FixedSizeList> ); } ``` ## 监控和分析 ### 性能监控 ```javascript i18next.on('loaded', (loaded) => { console.log('翻译资源加载完成:', loaded); // 发送性能指标到监控系统 trackPerformance('i18n_loaded', { namespaces: Object.keys(loaded), timestamp: Date.now() }); }); ``` ### 错误监控 ```javascript i18next.on('failedLoading', (lng, ns, msg) => { console.error('翻译资源加载失败:', { lng, ns, msg }); // 发送错误到监控系统 trackError('i18n_load_failed', { lng, ns, msg }); }); ``` ## 最佳实践总结 1. **延迟加载**: 只加载当前需要的翻译资源 2. **缓存策略**: 使用本地存储和内存缓存减少网络请求 3. **资源优化**: 压缩、拆分和清理翻译文件 4. **请求优化**: 批量加载、预加载和使用 CDN 5. **运行时优化**: 避免频繁翻译、使用 memo 和虚拟滚动 6. **监控**: 监控加载性能和错误率 7. **版本控制**: 使用版本号管理翻译资源更新
服务端 · 2月18日 22:05
如何管理 i18next 的翻译资源和工作流?## 翻译管理平台集成 ### i18next-locize-backend ```javascript import Locize from 'i18next-locize-backend'; i18next .use(Locize) .init({ backend: { projectId: 'your-project-id', apiKey: 'your-api-key', referenceLng: 'en', version: 'latest' } }); ``` ### Crowdin 集成 ```javascript // 使用 i18next-http-backend 从 Crowdin 加载 i18next .use(HttpBackend) .init({ backend: { loadPath: 'https://cdn.crowdin.com/api/v2/projects/{{projectId}}/translations/{{lng}}/{{ns}}?apiKey={{apiKey}}', queryStringParams: { projectId: 'your-project-id', apiKey: 'your-api-key' } } }); ``` ## 自动翻译提取 ### 使用 i18next-scanner ```bash npm install --save-dev i18next-scanner ``` ```javascript // gulpfile.js const gulp = require('gulp'); const scanner = require('i18next-scanner'); gulp.task('i18next', function() { return gulp.src(['src/**/*.{js,jsx,ts,tsx}']) .pipe(scanner({ lngs: ['en', 'zh', 'fr'], resource: { loadPath: 'locales/{{lng}}/{{ns}}.json', savePath: 'locales/{{lng}}/{{ns}}.json' }, keySeparator: '.', nsSeparator: ':', defaultValue: '__TRANSLATION__' })) .pipe(gulp.dest('locales')); }); ``` ### 使用 Babel 插件 ```javascript // .babelrc { "plugins": [ ["i18next-extract", { "locales": ["en", "zh"], "outputPath": "locales/{{locale}}/{{ns}}.json", "keyAsDefaultValue": true }] ] } ``` ## 翻译工作流 ### 开发流程 1. **提取翻译键**: 使用扫描工具从代码中提取翻译键 2. **添加翻译**: 在翻译文件中添加或更新翻译 3. **提交代码**: 将翻译文件提交到版本控制 4. **发送翻译**: 将需要翻译的内容发送给翻译团队 5. **合并翻译**: 将翻译好的内容合并回代码库 ### 自动化工作流 ```javascript // CI/CD 集成示例 const { execSync } = require('child_process'); // 提取翻译 execSync('npm run extract:translations'); // 检查是否有新增的翻译键 const newKeys = checkNewTranslationKeys(); if (newKeys.length > 0) { console.log('发现新的翻译键:', newKeys); // 发送通知或创建 issue notifyTranslationTeam(newKeys); } // 运行测试 execSync('npm test'); ``` ## 翻译质量保证 ### 翻译验证 ```javascript function validateTranslations(translations) { const errors = []; Object.keys(translations).forEach(lang => { const langTranslations = translations[lang]; Object.keys(langTranslations).forEach(key => { const translation = langTranslations[key]; // 检查是否包含插值占位符 if (translation.includes('{{') && !translation.includes('}}')) { errors.push(`${lang}.${key}: 未闭合的插值占位符`); } // 检查是否包含 HTML 标签 if (/<[^>]*>/g.test(translation)) { errors.push(`${lang}.${key}: 包含 HTML 标签`); } // 检查长度 if (translation.length > 500) { errors.push(`${lang}.${key}: 翻译过长`); } }); }); return errors; } ``` ### 翻译覆盖率检查 ```javascript function checkTranslationCoverage(translations) { const referenceLang = 'en'; const referenceKeys = Object.keys(translations[referenceLang]); const coverage = {}; Object.keys(translations).forEach(lang => { if (lang === referenceLang) return; const langKeys = Object.keys(translations[lang]); const missingKeys = referenceKeys.filter(key => !langKeys.includes(key)); coverage[lang] = { total: referenceKeys.length, translated: langKeys.length, missing: missingKeys, percentage: (langKeys.length / referenceKeys.length) * 100 }; }); return coverage; } ``` ## 翻译更新策略 ### 增量更新 ```javascript async function updateTranslations(newTranslations) { const currentTranslations = await loadCurrentTranslations(); Object.keys(newTranslations).forEach(lang => { if (!currentTranslations[lang]) { currentTranslations[lang] = {}; } Object.assign(currentTranslations[lang], newTranslations[lang]); }); await saveTranslations(currentTranslations); } ``` ### 版本控制 ```javascript // 使用 Git 追踪翻译变更 const { execSync } = require('child_process'); function commitTranslations(message) { execSync('git add locales/'); execSync(`git commit -m "${message}"`); execSync('git tag translations-v' + Date.now()); } ``` ## 翻译平台集成 ### Locize ```javascript import locize from 'locize'; import locizeBackend from 'i18next-locize-backend'; const locizeOptions = { projectId: 'your-project-id', apiKey: 'your-api-key', referenceLng: 'en', version: 'latest' }; i18next .use(locizeBackend) .use(locize.plugin()) .init({ backend: locizeOptions, locizeLastUsed: locizeOptions, saveMissing: true, debug: true }); ``` ### PhraseApp ```javascript import PhraseBackend from 'i18next-phraseapp-backend'; i18next .use(PhraseBackend) .init({ backend: { projectId: 'your-project-id', apiKey: 'your-api-key', version: 'latest' } }); ``` ## 最佳实践 1. **自动化提取**: 使用工具自动提取翻译键 2. **版本控制**: 将翻译文件纳入版本控制 3. **持续集成**: 在 CI/CD 中验证翻译质量 4. **翻译管理**: 使用专业的翻译管理平台 5. **质量保证**: 实施翻译验证和覆盖率检查 6. **团队协作**: 建立清晰的翻译工作流程 7. **文档维护**: 保持翻译文档的更新
服务端 · 2月18日 22:04
i18next-http-backend 如何实现翻译资源的远程加载?## i18next-http-backend 简介 i18next-http-backend 是 i18next 的一个插件,用于从远程服务器加载翻译资源。它支持 HTTP 请求、缓存和延迟加载等功能。 ## 安装 ```bash npm install i18next-http-backend # 或 yarn add i18next-http-backend ``` ## 基本配置 ### 简单配置 ```javascript import i18next from 'i18next'; import Backend from 'i18next-http-backend'; i18next .use(Backend) .init({ lng: 'en', fallbackLng: 'en', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' } }); ``` ### 完整配置 ```javascript i18next .use(Backend) .init({ lng: 'en', fallbackLng: 'en', ns: ['translation', 'common', 'errors'], defaultNS: 'translation', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', addPath: '/locales/add/{{lng}}/{{ns}}', parse: (data, lng, ns) => { return JSON.parse(data); }, stringify: (data, lng, ns) => { return JSON.stringify(data); }, request: (options, url, payload, callback) => { // 自定义请求逻辑 fetch(url, options) .then(response => response.json()) .then(data => callback(null, { data, status: 200 })) .catch(error => callback(error, null)); }, reloadInterval: false, // 重新加载间隔(毫秒) queryStringParams: { v: '1.0.0' } // 添加查询参数 } }); ``` ## 路径配置 ### 动态路径变量 - `{{lng}}`: 当前语言代码 - `{{ns}}`: 当前命名空间 - `{{projectId}}`: 自定义项目 ID ```javascript backend: { loadPath: '/api/translations/{{lng}}/{{ns}}?projectId={{projectId}}', queryStringParams: { projectId: 'my-project-123' } } ``` ### 多路径配置 ```javascript backend: { loadPath: (lngs, namespaces) => { return namespaces.map(ns => `/locales/${lngs[0]}/${ns}.json`); } } ``` ## 延迟加载 ### 按需加载命名空间 ```javascript // 初始化时只加载默认命名空间 i18next .use(Backend) .init({ ns: ['translation'], defaultNS: 'translation' }); // 需要时加载其他命名空间 i18next.loadNamespaces(['admin', 'settings'], () => { console.log('命名空间加载完成'); }); ``` ### React 中的延迟加载 ```javascript import { useTranslation } from 'react-i18next'; function AdminPanel() { const { t, ready } = useTranslation('admin', { useSuspense: false }); if (!ready) { return <div>Loading translations...</div>; } return <h1>{t('dashboard.title')}</h1>; } ``` ## 缓存机制 ### 使用本地存储缓存 ```javascript import LocalStorageBackend from 'i18next-localstorage-backend'; import Backend from 'i18next-http-backend'; i18next .use(Backend) .use(LocalStorageBackend) .init({ backend: { backends: [ LocalStorageBackend, // 主后端 Backend // 回退后端 ], backendOptions: [ { expirationTime: 7 * 24 * 60 * 60 * 1000, // 7天 defaultVersion: 'v1.0.0' }, { loadPath: '/locales/{{lng}}/{{ns}}.json' } ] } }); ``` ### 自定义缓存逻辑 ```javascript backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', request: (options, url, payload, callback) => { const cacheKey = `i18n_${url}`; const cached = localStorage.getItem(cacheKey); if (cached) { const { data, timestamp } = JSON.parse(cached); const isExpired = Date.now() - timestamp > 3600000; // 1小时 if (!isExpired) { return callback(null, { data, status: 200 }); } } fetch(url, options) .then(response => response.json()) .then(data => { localStorage.setItem(cacheKey, JSON.stringify({ data, timestamp: Date.now() })); callback(null, { data, status: 200 }); }) .catch(callback); } } ``` ## 错误处理 ### 加载失败处理 ```javascript i18next .use(Backend) .init({ backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' } }) .catch(err => { console.error('翻译资源加载失败:', err); // 使用回退翻译 i18next.addResourceBundle('en', 'translation', { welcome: 'Welcome', error: 'An error occurred' }); }); ``` ### 重试机制 ```javascript backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', request: (options, url, payload, callback) => { let retries = 3; const attemptFetch = (attempt) => { fetch(url, options) .then(response => response.json()) .then(data => callback(null, { data, status: 200 })) .catch(error => { if (attempt < retries) { setTimeout(() => attemptFetch(attempt + 1), 1000 * attempt); } else { callback(error, null); } }); }; attemptFetch(0); } } ``` ## 性能优化 ### 预加载关键翻译 ```javascript i18next .use(Backend) .init({ preload: ['en', 'zh'], // 预加载的语言 ns: ['translation', 'common'] // 预加载的命名空间 }); ``` ### 批量加载 ```javascript // 一次性加载多个命名空间 Promise.all([ i18next.loadNamespaces(['admin', 'settings', 'reports']), i18next.loadLanguages(['en', 'zh', 'fr']) ]).then(() => { console.log('所有翻译资源加载完成'); }); ``` ## 最佳实践 1. **版本控制**: 在 URL 中添加版本参数,避免缓存问题 2. **错误处理**: 实现完善的错误处理和回退机制 3. **性能优化**: 使用缓存和延迟加载减少网络请求 4. **监控**: 监控翻译资源加载性能和错误率 5. **CDN**: 使用 CDN 加速翻译资源加载
服务端 · 2月18日 18:14