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

面试题手册

如何在 React 项目中使用 react-i18next?

react-i18next 简介react-i18next 是 i18next 的 React 专用绑定库,提供了 React Hooks 和组件,使国际化在 React 应用中更加便捷。安装npm install react-i18next i18next# 或yarn add react-i18next i18next基本配置初始化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;在应用入口引入import React from 'react';import ReactDOM from 'react-dom';import App from './App';import './i18n'; // 引入 i18n 配置ReactDOM.render(<App />, document.getElementById('root'));使用 useTranslation Hook基本用法import React from 'react';import { useTranslation } from 'react-i18next';function Welcome() { const { t } = useTranslation(); return <h1>{t('welcome')}</h1>;}指定命名空间function MyComponent() { const { t } = useTranslation('common'); return <button>{t('save')}</button>;}多个命名空间function MyComponent() { const { t } = useTranslation(['common', 'errors']); return ( <div> <button>{t('common:save')}</button> <p>{t('errors:notFound')}</p> </div> );}使用 Trans 组件基本用法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"嵌套组件<Trans i18nKey="description"> Click <Link to="/about">here</Link> to learn more</Trans>使用 withTranslation HOCimport { withTranslation } from 'react-i18next';class MyComponent extends React.Component { render() { const { t } = this.props; return <h1>{t('title')}</h1>; }}export default withTranslation()(MyComponent);使用 I18nextProviderimport { I18nextProvider } from 'react-i18next';import i18n from './i18n';function App() { return ( <I18nextProvider i18n={i18n}> <MyComponent /> </I18nextProvider> );}语言切换创建语言切换器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> );}监听语言变化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>;}延迟加载命名空间function LazyComponent() { const { t, ready } = useTranslation('lazyNamespace', { useSuspense: false }); if (!ready) { return <div>Loading...</div>; } return <p>{t('content')}</p>;}最佳实践按需加载: 使用命名空间和延迟加载优化性能类型安全: TypeScript 项目使用类型定义错误处理: 处理缺失的翻译测试: 编写国际化相关的单元测试代码分割: 将翻译资源与应用代码分离
阅读 0·2月18日 22:07

如何为 i18next 编写测试?

基本测试测试翻译功能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 的组件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(); });});测试语言切换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 组件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创建测试工具函数// test-utils/i18n.jsimport { 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> );};使用测试工具import { renderWithI18n } from './test-utils/i18n';test('should render with mocked translations', () => { renderWithI18n(<MyComponent />, { welcome: 'Welcome', goodbye: 'Goodbye' }); expect(screen.getByText('Welcome')).toBeInTheDocument();});测试延迟加载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(); }); });});测试缺失翻译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'); });});集成测试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(); });});最佳实践隔离测试: 每个测试应该独立运行,不依赖其他测试Mock 资源: 使用 mock 的翻译资源,避免依赖实际文件测试工具: 创建可复用的测试工具函数异步测试: 正确处理异步操作,如语言切换和延迟加载覆盖边界: 测试缺失翻译、错误情况等边界条件快照测试: 对翻译组件使用快照测试确保一致性性能测试: 测试大量翻译时的性能表现
阅读 0·2月18日 22:07

如何在项目中初始化和配置 i18next?

基本初始化i18next 的初始化非常简单,使用 i18next.init() 方法进行配置: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: 支持的语言列表i18next.init({ lng: 'zh', fallbackLng: 'en', supportedLngs: ['en', 'zh', 'fr', 'de']});资源相关resources: 直接定义翻译资源ns: 默认命名空间defaultNS: 默认使用的命名空间i18next.init({ resources: { en: { translation: { "welcome": "Welcome" }, common: { "save": "Save", "cancel": "Cancel" } } }, ns: ['translation', 'common'], defaultNS: 'translation'});插值相关interpolation: 配置插值行为i18next.init({ interpolation: { escapeValue: false, // 不转义 HTML format: function(value, format, lng) { if (format === 'uppercase') return value.toUpperCase(); return value; } }});检测相关detection: 语言检测配置i18next.init({ detection: { order: ['querystring', 'cookie', 'localStorage', 'navigator'], caches: ['localStorage', 'cookie'], lookupQuerystring: 'lng' }});异步初始化i18next.init() 返回一个 Promise,可以使用 async/await:async function initI18n() { try { await i18next.init({ lng: 'zh', resources: { zh: { translation: { "hello": "你好" } } } }); console.log('i18next 初始化成功'); } catch (error) { console.error('初始化失败:', error); }}最佳实践分离配置: 将 i18next 配置单独放在一个文件中环境区分: 开发和生产环境使用不同配置错误处理: 始终处理初始化错误延迟加载: 大型应用使用延迟加载提高性能类型安全: TypeScript 项目使用类型定义
阅读 0·2月18日 22:07

如何在 TypeScript 项目中使用 i18next?

基本类型定义安装类型定义npm install --save-dev @types/i18next# 对于 react-i18nextnpm install --save-dev @types/react-i18next定义翻译资源类型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基本用法import { useTranslation } from 'react-i18next';function MyComponent() { const { t } = useTranslation(); return <h1>{t('welcome')}</h1>;}指定命名空间function MyComponent() { const { t } = useTranslation('common'); return <button>{t('save')}</button>;}类型安全的翻译键// 定义翻译键类型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>;}创建类型安全的翻译函数使用泛型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生成类型定义npm install --save-dev i18next-resources-to-ts// scripts/generate-types.jsconst { 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 中添加脚本{ "scripts": { "generate:i18n-types": "node scripts/generate-types.js" }}插值类型安全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> );}复数处理类型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> );}上下文类型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> );}命名空间类型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 组件类型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 /> }} /> );}最佳实践生成类型: 使用工具自动生成翻译资源类型严格模式: 启用 TypeScript 严格模式命名空间: 使用命名空间组织翻译类型守卫: 使用类型守卫确保类型安全持续集成: 在 CI/CD 中生成和检查类型文档: 记录类型定义和使用方法测试: 编写类型测试确保类型正确性
阅读 0·2月18日 22:07

i18next 中如何使用插值、复数和上下文功能?

基本翻译使用 t() 函数进行翻译:// 简单翻译t('welcome'); // "Welcome"// 嵌套键t('header.title'); // "Dashboard"插值功能变量插值在翻译文本中使用 {{variable}} 语法:// 翻译资源{ "greeting": "Hello {{name}}", "userCount": "Total users: {{count}}"}// 使用t('greeting', { name: 'John' }); // "Hello John"t('userCount', { count: 100 }); // "Total users: 100"数组插值{ "list": "Items: {{items}}"}t('list', { items: ['apple', 'banana'].join(', ') });复数处理i18next 内置对复数的支持:// 翻译资源{ "item": "one item", "item_plural": "{{count}} items"}// 使用t('item', { count: 1 }); // "one item"t('item', { count: 5 }); // "5 items"自定义复数规则i18next.init({ lng: 'ru', resources: { ru: { translation: { "item_one": "один предмет", "item_few": "{{count}} предмета", "item_many": "{{count}} предметов", "item_other": "{{count}} предмет" } } }});上下文处理根据上下文显示不同的翻译:// 翻译资源{ "friend": "A friend", "friend_male": "A boyfriend", "friend_female": "A girlfriend"}// 使用t('friend', { context: 'male' }); // "A boyfriend"t('friend', { context: 'female' }); // "A girlfriend"命名空间使用命名空间i18next.init({ ns: ['common', 'errors'], defaultNS: 'common', resources: { en: { common: { "save": "Save" }, errors: { "notFound": "Not found" } } }});// 使用t('save'); // 使用默认命名空间t('errors:notFound'); // 指定命名空间动态加载命名空间i18next.loadNamespaces(['admin', 'settings'], () => { // 命名空间加载完成});格式化自定义格式化函数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' } } });嵌套翻译// 翻译资源{ "user": { "profile": { "name": "Name: {{name}}" } }}// 使用t('user.profile.name', { name: 'John' });缺失翻译处理i18next.init({ saveMissing: true, missingKeyHandler: (lng, ns, key) => { console.log(`Missing translation: ${lng}.${ns}.${key}`); }});性能优化// 批量翻译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);}
阅读 0·2月18日 22:06

i18next 常见问题及解决方案有哪些?

常见问题1. 翻译不显示问题: 调用 t() 函数返回键名而不是翻译文本原因:翻译资源未正确加载命名空间配置错误语言代码不匹配解决方案:// 检查翻译资源是否加载console.log(i18next.store.data);// 确保语言代码匹配i18next.init({ lng: 'en', // 确保与资源中的键匹配 resources: { en: { translation: { welcome: 'Welcome' } } }});// 检查命名空间i18next.init({ ns: ['translation', 'common'], defaultNS: 'translation'});2. 语言切换不生效问题: 调用 changeLanguage() 后翻译没有更新原因:组件没有监听语言变化翻译资源未加载React 组件没有重新渲染解决方案:// React 组件中使用 useTranslationfunction 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}} 插值时变量没有被替换原因:插值配置错误变量名拼写错误转义设置问题解决方案:// 检查插值配置i18next.init({ interpolation: { escapeValue: false, // React 中通常设为 false prefix: '{{', suffix: '}}' }});// 确保变量名正确t('greeting', { name: 'John' });// 翻译资源{ "greeting": "Hello {{name}}"}4. 复数处理不正确问题: 复数形式显示不正确原因:复数规则配置错误语言不支持默认复数规则解决方案:// 为特定语言配置复数规则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. 性能问题问题: 翻译加载缓慢,影响应用性能原因:加载了过多翻译资源没有使用缓存网络请求过多解决方案:// 使用延迟加载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. 缺失翻译处理问题: 缺失的翻译显示键名而不是回退文本解决方案:// 配置缺失翻译处理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 问题问题: 服务端渲染时翻译不显示解决方案:// Next.js 示例import { appWithTranslation } from 'next-i18next';export default appWithTranslation(MyApp);// 服务端获取翻译export async function getServerSideProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, ['common', 'home'])) } };}8. 内存泄漏问题: 组件卸载后事件监听器未清理解决方案: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>;}调试技巧启用调试模式i18next.init({ debug: true, // 启用调试日志 initImmediate: false});检查翻译资源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]);监听事件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);});最佳实践错误处理: 始终处理初始化和加载错误调试模式: 开发环境启用调试模式日志记录: 记录关键事件和错误性能监控: 监控翻译加载性能单元测试: 编写测试覆盖常见问题文档: 记录配置和使用方法版本控制: 使用版本号管理翻译资源
阅读 0·2月18日 22:06

如何优化 i18next 的性能?

延迟加载按需加载命名空间// 初始化时只加载必要的命名空间i18next.init({ ns: ['translation', 'common'], defaultNS: 'translation'});// 需要时加载其他命名空间i18next.loadNamespaces(['admin', 'settings'], (err, t) => { if (!err) { console.log('命名空间加载完成'); }});按需加载语言// 初始化时只加载默认语言i18next.init({ lng: 'en', preload: ['en'] // 只预加载英语});// 用户切换语言时加载i18next.loadLanguages(['zh', 'fr'], () => { console.log('语言资源加载完成');});React 中的 Suspense 模式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> );}缓存策略本地存储缓存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' } ] } });内存缓存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;}缓存失效策略backend: { loadPath: '/locales/{{lng}}/{{ns}}.json?v={{version}}', queryStringParams: { version: process.env.TRANSLATION_VERSION || '1.0.0' }}资源优化压缩翻译资源// 使用 gzip 压缩// 服务器配置示例 (Express)const express = require('express');const compression = require('compression');const app = express();app.use(compression());app.use('/locales', express.static('locales'));按功能拆分翻译文件// 文件结构// 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'});移除未使用的翻译// 构建时分析代码中使用的翻译键const usedKeys = analyzeTranslationKeysInCode();const allTranslations = loadAllTranslations();const optimizedTranslations = filterTranslations(allTranslations, usedKeys);saveOptimizedTranslations(optimizedTranslations);请求优化批量加载// 一次性加载多个命名空间Promise.all([ i18next.loadNamespaces(['admin', 'settings', 'reports']), i18next.loadLanguages(['en', 'zh'])]).then(() => { console.log('所有翻译资源加载完成');});预加载关键资源i18next.init({ preload: ['en', 'zh'], // 预加载的语言 ns: ['translation', 'common'], // 预加载的命名空间 partialBundledLanguages: true // 允许部分加载});使用 CDNbackend: { loadPath: 'https://cdn.example.com/locales/{{lng}}/{{ns}}.json', crossDomain: true}运行时优化避免频繁翻译// 不好的做法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 优化import { memo } from 'react';import { useTranslation } from 'react-i18next';const TranslatedComponent = memo(({ text }) => { const { t } = useTranslation(); return <span>{t(text)}</span>;});虚拟滚动优化长列表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> );}监控和分析性能监控i18next.on('loaded', (loaded) => { console.log('翻译资源加载完成:', loaded); // 发送性能指标到监控系统 trackPerformance('i18n_loaded', { namespaces: Object.keys(loaded), timestamp: Date.now() });});错误监控i18next.on('failedLoading', (lng, ns, msg) => { console.error('翻译资源加载失败:', { lng, ns, msg }); // 发送错误到监控系统 trackError('i18n_load_failed', { lng, ns, msg });});最佳实践总结延迟加载: 只加载当前需要的翻译资源缓存策略: 使用本地存储和内存缓存减少网络请求资源优化: 压缩、拆分和清理翻译文件请求优化: 批量加载、预加载和使用 CDN运行时优化: 避免频繁翻译、使用 memo 和虚拟滚动监控: 监控加载性能和错误率版本控制: 使用版本号管理翻译资源更新
阅读 0·2月18日 22:05

如何管理 i18next 的翻译资源和工作流?

翻译管理平台集成i18next-locize-backendimport Locize from 'i18next-locize-backend';i18next .use(Locize) .init({ backend: { projectId: 'your-project-id', apiKey: 'your-api-key', referenceLng: 'en', version: 'latest' } });Crowdin 集成// 使用 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-scannernpm install --save-dev i18next-scanner// gulpfile.jsconst 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 插件// .babelrc{ "plugins": [ ["i18next-extract", { "locales": ["en", "zh"], "outputPath": "locales/{{locale}}/{{ns}}.json", "keyAsDefaultValue": true }] ]}翻译工作流开发流程提取翻译键: 使用扫描工具从代码中提取翻译键添加翻译: 在翻译文件中添加或更新翻译提交代码: 将翻译文件提交到版本控制发送翻译: 将需要翻译的内容发送给翻译团队合并翻译: 将翻译好的内容合并回代码库自动化工作流// 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');翻译质量保证翻译验证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;}翻译覆盖率检查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;}翻译更新策略增量更新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);}版本控制// 使用 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());}翻译平台集成Locizeimport 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 });PhraseAppimport PhraseBackend from 'i18next-phraseapp-backend';i18next .use(PhraseBackend) .init({ backend: { projectId: 'your-project-id', apiKey: 'your-api-key', version: 'latest' } });最佳实践自动化提取: 使用工具自动提取翻译键版本控制: 将翻译文件纳入版本控制持续集成: 在 CI/CD 中验证翻译质量翻译管理: 使用专业的翻译管理平台质量保证: 实施翻译验证和覆盖率检查团队协作: 建立清晰的翻译工作流程文档维护: 保持翻译文档的更新
阅读 0·2月18日 22:04

i18next-http-backend 如何实现翻译资源的远程加载?

i18next-http-backend 简介i18next-http-backend 是 i18next 的一个插件,用于从远程服务器加载翻译资源。它支持 HTTP 请求、缓存和延迟加载等功能。安装npm install i18next-http-backend# 或yarn add i18next-http-backend基本配置简单配置import i18next from 'i18next';import Backend from 'i18next-http-backend';i18next .use(Backend) .init({ lng: 'en', fallbackLng: 'en', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' } });完整配置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}}: 自定义项目 IDbackend: { loadPath: '/api/translations/{{lng}}/{{ns}}?projectId={{projectId}}', queryStringParams: { projectId: 'my-project-123' }}多路径配置backend: { loadPath: (lngs, namespaces) => { return namespaces.map(ns => `/locales/${lngs[0]}/${ns}.json`); }}延迟加载按需加载命名空间// 初始化时只加载默认命名空间i18next .use(Backend) .init({ ns: ['translation'], defaultNS: 'translation' });// 需要时加载其他命名空间i18next.loadNamespaces(['admin', 'settings'], () => { console.log('命名空间加载完成');});React 中的延迟加载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>;}缓存机制使用本地存储缓存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' } ] } });自定义缓存逻辑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); }}错误处理加载失败处理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' }); });重试机制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); }}性能优化预加载关键翻译i18next .use(Backend) .init({ preload: ['en', 'zh'], // 预加载的语言 ns: ['translation', 'common'] // 预加载的命名空间 });批量加载// 一次性加载多个命名空间Promise.all([ i18next.loadNamespaces(['admin', 'settings', 'reports']), i18next.loadLanguages(['en', 'zh', 'fr'])]).then(() => { console.log('所有翻译资源加载完成');});最佳实践版本控制: 在 URL 中添加版本参数,避免缓存问题错误处理: 实现完善的错误处理和回退机制性能优化: 使用缓存和延迟加载减少网络请求监控: 监控翻译资源加载性能和错误率CDN: 使用 CDN 加速翻译资源加载
阅读 0·2月18日 18:14