6月1日 02:22

TypeScript 项目中 i18next 怎么做类型安全?

安装类型定义 @types/i18next@types/react-i18next,然后定义翻译资源的类型结构。核心思路是把 JSON 翻译文件的结构映射为 TypeScript 接口,让 t() 函数的键名、插值参数、复数形式都获得类型检查。具体做法:定义 TranslationResources 接口描述所有命名空间的键值结构,通过泛型将资源类型注入 useTranslation 返回的 t 函数,使其只接受存在的键名;插值参数用 GreetingOptions 等接口约束,缺少必填变量时编译报错;复数和上下文分别用 PluralOptionsContextOptions 约束枚举值;多命名空间场景定义 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 脱节;开启 i18nextreturnNull: false 和严格模式;按功能拆分命名空间,按需加载减少包体积;CI 中用脚本校验翻译键完整性,确保多语言文件同步。

标签:i18next