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

服务端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。