标签

i18next

i18next 是一套用于 Web/Node 端的国际化(i18n)框架,帮助应用把界面文案与代码逻辑解耦,实现多语言翻译、插值、复数、语境等能力的统一管理。它以“资源字典 + 命名空间(namespace)+ key”为核心组织方式,通过 `t('key')` 在运行时根据当前语言与回退策略(fallback)返回对应文案,并支持变量插值(如 `{{name}}`)、复数规则与日期/数字等本地化配合。i18next 生态完善:可结合 `react-i18next` 等适配不同框架,使用后端/浏览器语言检测、按需加载(lazy load)与缓存提升性能,也便于在大型项目中做模块化拆分与动态切换语言。总体而言,i18next 提供了可扩展、工程化的 i18n 基础设施,适合从小型应用到多团队大仓库的多语言需求。

i18next
服务端6月1日 09:35
i18next 是什么?核心特性和插件体系详解i18next 是 JavaScript 生态中最成熟的国际化框架,核心定位是"框架无关的翻译运行时"。它不绑定 React/Vue/Angular,核心库可以在 Node.js、浏览器、React Native 任何环境运行。核心特性包括:**命名空间**——把翻译按模块拆成多个 JSON,按需加载,避免单文件膨胀;**插值**——`{{name}}` 语法在翻译文本中嵌入动态值,支持格式化函数;**复数**——内置各语言的复数规则(英语 1 item / 2 items,阿拉伯语有 6 种复数形式),不需要手动判断;**延迟加载**——配合 `i18next-http-backend` 按语言+命名空间异步加载,首屏只拉当前语言的核心翻译;**插件生态**——语言检测、缓存、后端加载全部通过 `.use()` 注入,核心包保持精简。与 `react-intl`、`vue-i18n` 相比,i18next 的优势在于跨框架复用同一套翻译资源和配置逻辑,以及更灵活的插件体系。 ## 追问 ### i18next 和 react-i18next 是什么关系? i18next 是纯翻译引擎,不关心 UI 框架。`react-i18next` 是 i18next 的 React 绑定层,提供 `useTranslation` hook、`<Trans>` 组件、`withTranslation` HOC。它通过 `initReactI18next` 插件把 i18next 实例挂到 React context 上,组件内调用 `useTranslation()` 时能响应语言切换并触发重渲染。不用 `react-i18next` 直接在 React 里调 `i18next.t()` 也行,但语言切换后组件不会自动更新——你得手动监听 `languageChanged` 事件再 `forceUpdate`。 ### 插值 {{}} 和 <Trans> 组件分别适合什么场景? 简单变量替换用 `{{name}}` 插值:`t('greeting', { name: '张三' })`,翻译文件写 `"greeting": "你好,{{name}}"`。当翻译文本包含 React 组件(比如 `<strong>`、`<Link>`)时必须用 `<Trans>`:`<Trans>阅读<Link to="/terms">条款</Link></Trans>`,i18next 会把组件位置记录为索引占位符,翻译文件里写 `"阅读<1>条款</1>"`。混用没问题,但不要在 `<Trans>` 内部再嵌套 `{{}}` 做复杂逻辑,翻译人员看不懂。 ### 复数处理在不同语言下有什么坑? 英语只需要 `item` 和 `items` 两个 key。斯拉夫语系(俄语、波兰语)有 3-4 种复数形式,阿拉伯语有 6 种,i18next 内置了 CLDR 复数规则能自动处理,但你得在翻译文件里提供完整的 key:`"item_zero"`、`"item_one"`、`"item_two"`、`"item_few"`、`"item_many"`、`"item_other"`。常见坑是只写了 `item` 和 `item_plural`,切换到俄语时所有数量都回退到 `item_other`。另一个坑是 `count` 参数必须传数字类型,传字符串 `"2"` 不会触发复数逻辑。 ### 延迟加载的翻译在组件首次渲染时闪烁怎么办? `i18next-http-backend` 异步加载翻译,组件首次渲染时翻译还没到,`t('key')` 返回 key 本身。解决方案有三种:一是用 React Suspense 包裹根组件,`useTranslation` 内部会 suspend 直到翻译加载完成;二是检查 `useTranslation` 返回的 `ready` 状态,`ready` 为 false 时显示 loading;三是在 SSR 场景下把翻译资源预注入到 HTML,客户端直接从 `window.__I18N_DATA__` 读取,跳过首次请求。方案一最简洁,但需要 React 18+ 的 Suspense 支持。
服务端6月1日 09:35
i18next 怎么初始化配置?resources/命名空间/插件集成指南i18next 初始化配置是前端国际化的核心环节。`i18next.init()` 接收一个配置对象,最关键的几个选项:`lng` 设置当前语言,`fallbackLng` 指定翻译缺失时的回退语言,`resources` 直接内联翻译资源,`ns` 和 `defaultNS` 管理命名空间拆分。React 项目需要通过 `initReactI18next` 插件注入 i18next 实例,否则 `useTranslation` hook 无法工作。语言检测用 `i18next-browser-languagedetector`,可以按 querystring → cookie → localStorage → navigator 的顺序探测用户语言偏好。大型项目推荐用 `i18next-http-backend` 按需加载翻译文件,避免把所有语言的 JSON 打包进主 bundle。`interpolation.escapeValue` 在 React 中要设为 `false`,因为 React 自带 XSS 转义。开发阶段开启 `debug: true` 可以在控制台看到翻译 key 缺失的警告。 ## 追问 ### resources 内联和 HTTP 后端加载怎么选? 小项目(2-3 种语言、翻译总量不超过 50KB)直接内联 `resources`,启动快、无额外请求。中大型项目用 `i18next-http-backend`,翻译文件按语言和命名空间拆成独立 JSON,首屏只加载当前语言的 `translation` 命名空间,其他页面切换时再按需加载。注意 HTTP 后端是异步的,`init()` 返回的 Promise resolve 之前组件渲染会拿到空翻译,需要配合 React Suspense 或 `useTranslation` 的 `ready` 状态处理。 ### 命名空间怎么划分比较合理? 常见做法是 `translation`(公共文案)、`common`(按钮/标签等高频复用)、`validation`(表单校验提示)、`errors`(错误码映射)。按页面拆命名空间(如 `home`、`profile`)也可以,但粒度太细会增加维护成本——每个命名空间对应一个 JSON 文件,翻译人员要在多个文件间切换。实际项目中 3-6 个命名空间基本够用,超过 10 个就该考虑合并了。 ### fallbackLng 配置不当会导致什么问题? `fallbackLng` 默认是 `"dev"`,翻译缺失时 key 会直接显示在页面上。如果设为 `"en"`,中文用户看到的是中英混杂的界面——部分 key 有中文翻译,缺失的回退到英文。更隐蔽的问题:`fallbackLng` 设为数组 `["zh", "en"]` 时,i18next 会按顺序查找,key 查找链变长影响性能。建议设为项目的主要语言(比如 `"zh"`),开发时靠 `debug: true` 发现缺失的 key,而不是依赖 fallback 兜底。 ### init() 的回调写法和 Promise 写法有什么区别? 回调写法 `i18next.init(config, (err, t) => {...})` 在 i18next 早期版本就支持,`t` 函数在回调里直接可用。Promise 写法 `await i18next.init(config)` 更直观,但要注意 HTTP 后端加载时 Promise resolve 不代表所有命名空间都加载完了——如果配置了 `partialBundledLanguages`,部分命名空间可能是异步加载的。React 项目一般用 `initReactI18next` 中间件,内部处理了等待逻辑,不需要手动处理回调。
服务端6月1日 09:25
i18next 插值、复数、上下文怎么用?{{变量}}/CLDR/context 详解i18next 的插值通过 `{{variable}}` 语法实现变量替换,如 `t('hello', { name: 'Tom' })` 对应翻译文件中 `Hello {{name}}`。数组插值用 `{{arr,0}}` 按索引取值。嵌套插值用 `$t(key)` 引用其他翻译条目,实现翻译文本的复用。复数处理根据语言类型自动匹配:英语等单复数语言使用 `key_one`/`key_other` 后缀,俄语等有复杂复数规则的语种遵循 CLDR 规范自动映射对应后缀(如 `key_zero`/`key_one`/`key_few`/`key_many`/`key_other`)。上下文功能通过 `context` 选项让同一 key 根据语境返回不同翻译,如 `t('friend', { context: 'male' })` 匹配 `friend_male`。格式化方面,i18next 内置 `format` 函数,可自定义格式化器处理日期、数字等,插值时通过 `{{value, formatter}}` 调用。转义默认开启,`{{var}}` 输出 HTML 转义后的值,`{{-var}}` 输出原始值,用于嵌入 HTML 片段。 ## 追问 ### 复数后缀的完整规则是什么? 英语只需 `_one` 和 `_other`。斯拉夫语系需要完整的 CLDR 后缀:`_zero`(0个)、`_one`(1个)、`_few`(2-4个)、`many`(5-20个)、`other`(其余)。阿拉伯语还有 `_two` 后缀。i18next 内置了 CLDR 复数规则,只需按规范提供对应后缀的翻译条目即可自动匹配。 ### context 和复数可以组合吗? 可以。格式为 `key_context_plural`,如 `friend_male_one`。i18next 会按 `key_context_plural` → `key_context` → `key_plural` → `key` 的顺序回退查找: ```json { "friend_male_one": "一个男朋友", "friend_male_other": "{{count}}个男朋友" } ``` ### 如何自定义格式化函数? 在 init 时注册: ```js i18n.init({ interpolation: { format: (value, format, lng) => { if (format === 'date') return new Intl.DateTimeFormat(lng).format(value); if (format === 'uppercase') return value.toUpperCase(); return value; } } }); ``` 翻译文件中使用 `{{date, date}}`、`{{name, uppercase}}` 调用。 ### $t 嵌套插值有什么限制? `$t()` 不能循环引用,否则会抛出异常。嵌套深度没有硬限制但不建议超过 2 层,影响可读性和性能。`$t()` 引用的 key 仍可接收插值参数:`$t(greeting, { "name": "Tom" })`。
服务端6月1日 09:25
React 项目怎么用 react-i18next?useTranslation/Trans/HOC 详解`react-i18next` 是 i18next 的 React 绑定层,核心 API 有三个:`useTranslation` Hook、`withTranslation` HOC、`Trans` 组件。`useTranslation` 是最常用的方式,返回 `t` 函数和 `i18n` 实例,组件内调用 `t('key')` 即可获取翻译文本,语言切换时自动重渲染。`withTranslation` 是类组件的对应方案,通过 props 注入 `t` 函数。`Trans` 组件用于处理含 HTML 标签的翻译,如 `Hello <bold>World</bold>`,翻译文件中对应 `Hello <1>World</1>`,避免用 `dangerouslySetInnerHTML`。`I18nextProvider` 用于向组件树注入 i18n 实例,通常在根组件外层包裹一次即可。Suspense 配合方面,当使用异步加载后端时,翻译资源未就绪会触发 Suspense fallback,需在 `init` 时设置 `react.useSuspense: true`。 ## 追问 ### useTranslation 如何指定命名空间? ```js const { t } = useTranslation('common'); t('greeting'); // 读取 common 命名空间的 key // 同时使用多个命名空间 const { t } = useTranslation(['common', 'auth']); t('common:title'); t('auth:login'); ``` ### Trans 组件的索引规则是什么? `Trans` 按子元素出现顺序编号,从 0 开始,纯文本节点不占索引(旧版行为),标签节点占索引。React 中组件包裹也算一个节点: ```jsx <Trans>Read <a href="/doc">the doc</a> for details</Trans> // 翻译文件:Read <1>the doc</1> for details ``` 空字符串节点和换行符是否占索引取决于 i18next 版本,建议用 `i18next-parser` 自动提取。 ### 类组件如何使用 react-i18next? ```jsx import { withTranslation } from 'react-i18next'; class MyComponent extends React.Component { render() { const { t } = this.props; return <p>{t('hello')}</p>; } } export default withTranslation()(MyComponent); ``` `withTranslation` 支持 `withTranslation(['ns1', 'ns2'])` 指定命名空间。 ### Suspense 模式下加载失败怎么处理? 在 Suspense 外层的 ErrorBoundary 中捕获加载异常,展示降级 UI。也可关闭 Suspense 模式,改用 `t()` 的 fallback key 机制:`t('key', '默认文本')`,当资源未加载时直接返回默认值。
服务端6月1日 09:25
i18next-http-backend 怎么远程加载翻译资源?配置/缓存/SSR`i18next-http-backend` 是 i18next 官方的远程翻译资源加载插件,核心配置项 `loadPath` 指定资源 URL 模板,支持 `{{lng}}` 和 `{{ns}}` 占位符,插件会在初始化或切换语言时自动请求对应 JSON 文件。`savePath` 用于回写(如新增 key 时持久化),默认 `/locales/add/{{lng}}/{{ns}}`。自定义请求可通过 `request` 选项替换默认 fetch 实现,适用于需要加鉴权头或自定义错误处理的场景。缓存方面,配合 `i18next-localstorage-backend` 可将已加载资源缓存到浏览器本地,设定过期时间避免重复请求。重试机制通过 `reloadInterval` 控制定时重新加载,也可在加载失败时调用 `i18n.reloadResources()` 手动触发。SSR 场景下使用 `i18next-fs-backend` 从文件系统读取资源,接口与 http-backend 一致。 ## 追问 ### 如何给请求添加自定义 Header? 通过 `request` 选项覆盖默认实现: ```js backend: { loadPath: '/api/locales/{{lng}}/{{ns}}', request: (url, payload, callback) => { fetch(url, { headers: { Authorization: 'Bearer xxx' } }) .then(res => res.json()) .then(data => callback(null, { data, status: 200 })) .catch(err => callback(err)); } } ``` ### 如何实现多语言批量加载? 设置 `allowMultiLoading: true`,`loadPath` 中使用 `{{lng}}` 和 `{{ns}}` 会以 `+` 连接多个值,服务端需支持返回合并后的 JSON。例如请求 `/locales/en+zh/common+admin` 返回对应结构,减少请求次数。 ### 加载失败如何降级? 配置 `backend` 的同时设置 `fallbackLng`,当目标语言资源加载失败时 i18next 会回退到 fallback 语言。也可监听 `failedLoading` 事件做上报和兜底提示: ```js i18n.on('failedLoading', (lng, ns, msg) => { console.error(`${lng}/${ns} 加载失败:`, msg); }); ``` ### SSR 中如何使用 fs-backend? ```js const FsBackend = require('i18next-fs-backend'); i18n.use(FsBackend).init({ backend: { loadPath: './locales/{{lng}}/{{ns}}.json' } }); ``` 读取本地文件系统而非发 HTTP 请求,接口与 http-backend 一致,SSR 预渲染时确保同步拿到翻译内容。
服务端6月1日 09:25
i18next 性能怎么优化?延迟加载/缓存/减少重渲染方案i18next 性能优化的核心思路是减少首次加载体积和降低不必要的重渲染。延迟加载命名空间是最直接的手段,将非关键翻译(如后台管理页面的文案)设为按需加载,首屏只加载 `common` 等必要命名空间。配合 `i18next-http-backend` 实现翻译资源的远程加载,避免将全部语言的 JSON 打进 JS Bundle。语言维度同样按需加载,当前语言加载完毕后再预加载用户可能切换的语言。Bundle 层面,i18next 内部依赖了 lodash,应使用 lodash-es 或手动替换为轻量实现以支持 tree-shaking。React 侧,`react-i18next` 的 `useTranslation` 已做细粒度订阅,只监听当前 key 对应的命名空间变化,但仍需避免在渲染函数中频繁调用 `t()` 生成复杂字符串导致子组件不必要更新,可通过 `memo` 或提取翻译结果到变量来规避。 ## 追问 ### 如何配置命名空间按需加载? ```js i18n.use(HttpBackend).init({ ns: ['common', 'admin'], defaultNS: 'common', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' } }); // 页面中按需加载 const { t } = useTranslation('admin'); ``` 访问组件时 `admin` 命名空间才会被请求。 ### 预加载其他语言怎么做? ```js i18n.loadLanguages(['ja', 'ko']).then(() => { // 日韩资源已缓存,切换无延迟 }); ``` 适合在用户 hover 语言切换按钮时触发。 ### localStorage-backend 缓存策略是什么? `i18next-localstorage-backend` 将请求到的翻译 JSON 存入 localStorage,设置过期时间(默认 7 天)。下次加载直接读缓存,跳过网络请求。需注意缓存失效后的更新策略,可在 `allowMultiLoading` 模式下批量更新。 ### 如何减少 React 重渲染? `useTranslation` 默认只订阅当前命名空间的语言变化。若组件只用到 `t('key')`,可指定 `keyPrefix` 缩小订阅范围。对纯展示组件使用 `React.memo` 包裹,或将翻译结果作为 props 传入,避免因语言上下文变化导致整棵子树重渲染。
服务端6月1日 09:25
i18next 怎么检测和切换语言?浏览器/SSR 方案详解i18next 通过 `i18next-browser-languagedetector` 插件实现语言检测,默认按 querystring → cookie → localStorage → navigator → htmlTag 的顺序依次探测,命中即停止。检测结果默认写入 localStorage 和 cookie 做持久化,可通过 `cacheUserLanguage` 自定义缓存方式。手动切换语言调用 `i18n.changeLanguage('zh')`,该方法内部会重新触发初始化流程并发出 `languageChanged` 事件。在 React 中,`react-i18next` 订阅了该事件,语言切换后自动触发组件重渲染。服务端场景下不使用浏览器检测插件,而是从请求头 `Accept-Language` 或 URL 路径中提取语言标识。 ## 追问 ### 检测顺序可以自定义吗? 可以。配置 `order` 数组即可调整检测顺序或增删检测源: ```js i18n.use(LanguageDetector).init({ detection: { order: ['localStorage', 'navigator'] } }); ``` ### 如何让用户首次访问时默认使用某种语言? 设置 `lookupQuerystring`、`lookupCookie` 等对应的 key,并在缓存中预写该值。更直接的方式是配置 `supportedLngs` 和 `fallbackLng`,当检测到的语言不在支持列表内时回退到 `fallbackLng`。 ### changeLanguage 是异步的吗? 是的,它返回 Promise。切换后需等待资源加载完成再渲染内容,否则会显示旧语言或 fallback 文本: ```js await i18n.changeLanguage('ja'); // 此时新语言资源已就绪 ``` ### 服务端如何实现语言检测? SSR 中不引入浏览器检测插件,一般从 `req.headers['accept-language']` 解析,或根据 URL 前缀(如 `/en/about`)提取。解析结果通过 `i18n.changeLanguage()` 注入当前请求实例。每个请求应使用独立的 i18n 实例,避免语言状态跨请求污染。
服务端6月1日 09:19
i18next 国际化怎么测试?翻译缺失和语言切换如何验证?测试 i18next 分三层:翻译函数本身、React 组件集成、边界情况(缺失 key、懒加载)。翻译函数测试最简单——初始化一个独立的 i18next 实例,注入 mock 翻译资源,断言 `t('key')` 的返回值,覆盖简单 key、插值(`{{name}}`)、复数形式。React 组件测试需要用 `I18nextProvider` 包裹被测组件,传入配置好的 i18n 实例,这样组件内的 `useTranslation` 和 `Trans` 才能正常工作。语言切换测试调用 `i18n.changeLanguage()` 后用 `waitFor` 断言 UI 文案变化。缺失 key 测试开启 `saveMissing: true`,配合 `missingKeyHandler` 断言回调被调用,或直接断言 `t('nonexistent')` 返回 key 本身作为 fallback。 ## 追问 ### 为什么不用真实的翻译文件跑测试,而要 mock 资源? 真实翻译文件会变——翻译团队随时可能改文案,如果测试断言了具体翻译文本,每次文案调整测试就挂了。mock 资源让测试只关心"key 能正确解析"这个逻辑,不绑定具体文案内容。另外 mock 资源体积小、加载快,避免测试里引入大量 JSON 文件。最佳实践:mock 资源只放测试需要的 key,用 `i18next.createInstance()` 隔离,避免测试间互相污染。 ### Trans 组件和 t() 函数测试有什么区别? `t()` 是纯函数,输入 key 和参数返回字符串,测试最简单。`Trans` 组件渲染包含 HTML 标签的翻译内容(如 `Welcome <1>{{name}}</1>`),需要验证组件嵌套是否正确,不能只断言文本内容——`Trans` 可能渲染出 `<strong>John</strong>` 也可能渲染出纯文本 `John`,取决于翻译 key 的定义。测试 Trans 时要用 `container.innerHTML` 或 `within` 查询,确认标签结构正确,而不仅是文本存在。 ### 命名空间怎么测试?懒加载命名空间呢? 命名空间测试关键是每个命名空间独立初始化资源,断言 `t('ns:key')` 能正确解析跨命名空间 key。懒加载命名空间测试用 `useSuspense: false` 模式——组件先渲染 loading 状态(`ready` 为 false),然后通过 `addResourceBundle` 手动注入资源,用 `waitFor` 断言组件渲染出翻译内容。注意懒加载测试不要用 Suspense 包裹,否则 React Testing Library 无法捕获 loading 态。 ### 快照测试适合 i18next 吗?有什么坑? 不太适合。快照会把翻译文案写死到 .snap 文件里,翻译改了快照就挂,维护成本高。而且快照只能告诉你"渲染结果和之前一样",不能告诉你翻译是否正确。如果一定要用,只对组件结构做快照(用 `render` 后取 `asFragment()`),不要断言具体翻译文本。更好的替代方案是用 `t()` 的 mock 验证调用参数是否正确,而不是验证返回值。 ```javascript // 独立实例 + mock 资源的翻译测试 import i18next from 'i18next'; describe('i18next translations', () => { let i18n; beforeEach(() => { i18n = i18next.createInstance(); i18n.init({ lng: 'en', resources: { en: { translation: { hello: 'Hello', greet: 'Hi {{name}}' } }, zh: { translation: { hello: '你好', greet: '你好 {{name}}' } } } }); }); test('简单 key', () => expect(i18n.t('hello')).toBe('Hello')); test('插值', () => expect(i18n.t('greet', { name: 'Li' })).toBe('Hi Li')); test('缺失 key 返回 key 本身', () => expect(i18n.t('missing')).toBe('missing')); }); // React 组件测试(I18nextProvider 包裹) import { I18nextProvider } from 'react-i18next'; import { render, screen, waitFor } from '@testing-library/react'; function Greeting() { const { t, i18n } = useTranslation(); return <> <span>{t('hello')}</span> <button onClick={() => i18n.changeLanguage('zh')}>切换</button> </>; } test('语言切换', async () => { render(<I18nextProvider i18n={i18n}><Greeting /></I18nextProvider>); expect(screen.getByText('Hello')).toBeInTheDocument(); fireEvent.click(screen.getByText('切换')); await waitFor(() => expect(screen.getByText('你好')).toBeInTheDocument()); }); ```
服务端6月1日 02:22
TypeScript 项目中 i18next 怎么做类型安全?安装类型定义 `@types/i18next` 和 `@types/react-i18next`,然后定义翻译资源的类型结构。核心思路是把 JSON 翻译文件的结构映射为 TypeScript 接口,让 `t()` 函数的键名、插值参数、复数形式都获得类型检查。具体做法:定义 `TranslationResources` 接口描述所有命名空间的键值结构,通过泛型将资源类型注入 `useTranslation` 返回的 `t` 函数,使其只接受存在的键名;插值参数用 `GreetingOptions` 等接口约束,缺少必填变量时编译报错;复数和上下文分别用 `PluralOptions`、`ContextOptions` 约束枚举值;多命名空间场景定义 `Namespaces` 接口,通过 `createTypedTranslation<NS>` 工厂函数返回带命名空间类型的安全 Hook。实际项目中推荐用 `i18next-resources-to-ts` 从 JSON 自动生成类型定义,配合 CI 检查翻译键完整性,避免手动维护类型与实际翻译文件脱节。 ```ts interface TranslationResources { common: { greeting: string; farewell: string }; errors: { notFound: string; network: string }; } type TypedT = (key: FlattenKeys<TranslationResources>) => string; ``` ```ts const { t } = useTranslation('common'); // t('greeting') ✅ t('nonexist') ❌ 编译报错 ``` ## 追问 ### 怎么让插值参数也有类型安全? 定义插值参数接口,作为 `t()` 的第二个泛型: ```ts interface GreetingOptions { name: string; count?: number } t<GreetingOptions>('greet', { name: 'Tom' }) // ✅ t<GreetingOptions>('greet', {}) // ❌ name 缺失 ``` ### 复数和上下文的类型怎么约束? 复数用 `_one`/`_other` 后缀的键名,类型上用枚举限制 `count` 值范围;上下文用 `_male`/`_female` 后缀,类型上约束 `context` 只能取合法值: ```ts interface PluralOptions { count: number } interface ContextOptions { context: 'male' | 'female' } ``` ### 多命名空间怎么处理类型? 定义 `Namespaces` 接口映射命名空间名到资源类型,再用工厂函数生成带命名空间约束的 Hook: ```ts interface Namespaces { common: TranslationResources['common']; errors: TranslationResources['errors']; } function createTypedTranslation<NS extends keyof Namespaces>(ns: NS) { return useTranslation(ns as string); } ``` ### Trans 组件怎么用类型? `Trans` 组件的 `i18nKey` 同样接受类型约束,配合 `react-i18next` 的泛型即可: ```tsx <Trans i18nKey="common:greeting" components={{ bold: <strong /> }} /> ``` ### 有哪些最佳实践? 四点:用 `i18next-resources-to-ts` 自动生成类型,避免手写与 JSON 脱节;开启 `i18next` 的 `returnNull: false` 和严格模式;按功能拆分命名空间,按需加载减少包体积;CI 中用脚本校验翻译键完整性,确保多语言文件同步。
服务端6月1日 02:21
i18next 常见坑有哪些?翻译不显示/切换失效/插值失败排查指南i18next 是前端最流行的国际化框架,使用中常遇到的问题按频率排列:**翻译不显示**多数是因为资源未加载完毕就渲染了组件、命名空间拼写错误、或语言代码格式不一致(`zh` vs `zh-CN`);**语言切换不生效**通常是组件未正确监听语言变化或 React 未触发重渲染;**插值失败**多半是 `interpolation` 配置缺失或变量名拼写不对;**复数处理异常**则是未配置对应语言的复数规则。性能方面,一次性加载所有语言资源会导致首屏变慢,应改为按需加载加缓存。缺失翻译可通过 `fallbackLng` 和 `saveMissing` 兜底。SSR 场景(如 Next.js)需注意服务端和客户端实例隔离。调试最直接的方式是开启 `debug: true`。 ## 追问 ### 翻译 key 存在但页面显示 key 原文? 检查三点:资源文件是否在组件渲染前加载完成,可用 `useTranslation` 的 `ready` 状态判断;命名空间是否与 `t()` 调用一致;语言代码是否匹配,i18next 默认区分大小写,`zh-CN` 和 `zh_cn` 是不同 key。 ```js const { t, ready } = useTranslation('common'); if (!ready) return <Loading />; ``` ### 语言切换后组件不更新? 确保使用的是框架绑定库(如 `react-i18next`)提供的 `useTranslation` 或 `withTranslation`,而非直接调用 `i18next.t()`。后者是同步快照,不会响应语言变化。另外检查 `initImmediate` 是否设为 `true`,否则资源加载可能是异步的但切换时未等待完成。 ### 插值变量不替换怎么办? 默认插值格式是 `{{variable}}`,花括号前后不能有空格。确认变量名与模板一致,同时检查 `interpolation.escapeValue` 配置——在 React 中应设为 `false`,因为 React 自身会转义。 ```js // 正确 t('hello', { name: '世界' }) // "你好 {{name}}" → "你好 世界" // 错误:花括号内有空格 "你好 {{ name }}" ``` ### 复数形式不生效? i18next 的复数基于 Unicode CLDR 规则,需要对应语言的复数后缀(如英语 `_plural`,俄语有更多形式)。确认资源 key 拼写正确,且 `interpolation` 中 `skipOnVariables` 未被误设。对于中文这类无复数区分的语言,直接用普通 key 即可。 ```json { "item": "{{count}} item", "item_plural": "{{count}} items" } ``` ### 生产环境如何处理缺失翻译? 三步走:`fallbackLng` 指定回退语言;`saveMissing: true` 配合 `missingKeyHandler` 将缺失 key 上报至翻译管理平台;开发环境开启 `debug: true` 在控制台打印缺失警告。Next.js SSR 场景下,确保服务端和客户端各持独立的 i18next 实例,避免请求间状态污染。
服务端6月1日 02:21
i18next 翻译资源怎么管理?自动化工作流搭建指南i18next 翻译资源管理的核心是「提取 → 翻译 → 校验 → 部署」的自动化流水线。提取阶段,用 i18next-scanner 扫描代码中的 `t()` 调用自动提取键名,或通过 Babel 插件 i18next-extract 在编译时完成,避免手动维护键的遗漏。翻译阶段对接管理平台:i18next-locize-backend 可直接从 locize 云端拉取翻译,实现运行时按需加载;Crowdin 则提供翻译记忆和术语库,适合大规模多语言团队。校验阶段写验证函数检查占位符一致性(如 `{{name}}` 在各语言是否齐全)、HTML 标签闭合、译文长度溢出,配合 i18next 的 `missingKeyHandler` 捕获缺失键。部署阶段将翻译文件纳入 Git 版本控制,CI 中执行键提取和覆盖率检查,增量更新只同步变更的键。整个流程串起来:开发者提交含新键的代码 → CI 提取键并推送至翻译平台 → 译者完成翻译 → CI 拉取翻译合并到仓库 → 自动发布。 ## 追问 ### i18next-scanner 和 Babel 插件怎么选? i18next-scanner 基于 AST 解析,支持 JSX/TSX/Vue,配置灵活可过滤特定目录,适合已有构建流的项目。Babel 插件在编译时提取,零额外构建步骤,但只处理 Babel 管辖的文件。项目若已用 Babel,插件更省事;需要扫描非 JS 文件或精细控制时选 scanner。 ### 翻译缺失键如何在线上兜底? 注册 `missingKeyHandler`,线上遇到缺失键时上报到日志或翻译平台,同时用 fallback 语言返回兜底译文: ```js i18next.init({ saveMissing: true, missingKeyHandler: (lngs, ns, key) => { reportMissingKey({ lngs, ns, key }); }, fallbackLng: 'en', }); ``` ### 如何保证占位符和 HTML 标签在各语言间一致? 写校验函数在 CI 中运行,提取每种语言的占位符和标签集合做差集比对: ```js function validatePlaceholders(base, translations) { const baseVars = (base.match(/\{\{.*?\}\}/g) || []).sort(); for (const [lang, text] of Object.entries(translations)) { const vars = (text.match(/\{\{.*?\}\}/g) || []).sort(); if (JSON.stringify(vars) !== JSON.stringify(baseVars)) { throw new Error(`${lang} 占位符不匹配: ${vars} vs ${baseVars}`); } } } ``` ### 增量更新和全量更新怎么取舍? 翻译文件小时全量更新简单可靠。文件超过 50 个语言或单文件上千条时,增量更新只同步变更键,减少冲突和传输量。实现上用 Git diff 检出本次变更的键名,只推送对应条目到翻译平台,拉取时也只覆盖变更部分。 ### locize 和 Crowdin 何时分别使用? locize 与 i18next 深度集成,支持运行时按需加载语言包,适合中小项目快速接入。Crowdin 提供翻译记忆、术语表、审核流程和供应商管理,适合专业翻译团队协作的大规模项目。团队无专职译者选 locize,有专业本地化流程选 Crowdin。