~/ ?.log $
返回文章列表
8 min read
更新于 2026年3月6日

我不知道的 i18next(03)— 复数规则与 CLDR 标准

很多人以为复数就是"1 个用单数,2 个及以上用复数"——英语确实如此,但放到阿拉伯语里,复数有 6 种形式;放到波兰语里,"2-4"和"5-21"用不同的词形。i18next 的复数处理远不止加个 _plural 后缀那么简单,而且从 v21 开始,连后缀的命名方式都彻底变了。

很多人以为复数就是”1 个用单数,2 个及以上用复数”——英语确实如此,但放到阿拉伯语里,复数有 6 种形式;放到波兰语里,“2-4”和”5-21”用不同的词形。i18next 的复数处理远不止加个 _plural 后缀那么简单,而且从 v21 开始,连后缀的命名方式都彻底变了

一、先看一个会出错的例子

下面这段配置,在 i18next v23+ 环境下运行会发生什么?

i18next.init({
  lng: 'en',
  resources: {
    en: {
      translation: {
        items: 'You have {{count}} item',
        items_plural: 'You have {{count}} items',
      },
    },
  },
});

console.log(i18next.t('items', { count: 1 }));
console.log(i18next.t('items', { count: 5 }));

直觉上,输出应该是 "You have 1 item""You have 5 items"

但实际输出是:

"You have 1 item"
"You have 5 item"    // 没有走 _plural 后缀

_plural 后缀在 i18next v21+ 中已经不是默认格式了。 新版本使用 CLDR 标准的 _one / _other 后缀。上面的代码要改成:

{
  items_one: 'You have {{count}} item',
  items_other: 'You have {{count}} items',
}

这是 i18next 近年来最容易踩的坑之一。如果项目从旧版升级,所有复数翻译键都需要迁移。

二、旧格式 vs 新格式

i18next 历史上有两种复数键名格式:

旧格式(JSON v3,i18next v20 及更早):

{
  "items": "You have {{count}} item",
  "items_plural": "You have {{count}} items"
}

只有 _plural 一个后缀,所有非单数情况都走 _plural

新格式(JSON v4,i18next v21+ 默认):

{
  "items_one": "You have {{count}} item",
  "items_other": "You have {{count}} items"
}

后缀直接对应 CLDR 的复数类别:zeroonetwofewmanyother

换句话说,旧格式是 i18next 自己发明的简化方案,新格式是国际标准。v24 已经移除了旧 JSON 格式的内置支持。 如果老项目暂时无法迁移,需要显式声明兼容模式:

i18next.init({
  compatibilityJSON: 'v3', // 强制使用旧格式
  // ...
});

但这只是过渡方案,不建议长期使用。

三、CLDR 复数规则到底是什么

CLDR(Unicode Common Locale Data Repository)为每种语言定义了复数类别。i18next 的新格式就是基于这套规则。

英语只有两种类别:

数量类别后缀
1one_one
其他other_other

阿拉伯语有六种:

数量类别后缀
0zero_zero
1one_one
2two_two
3-10few_few
11-99many_many
100+other_other

波兰语有四种:

数量类别说明
1one仅数字 1
2-4, 22-24…few末位 2-4(但 12-14 除外)
5-21, 25-31…many其他整数
1.5, 2.7…other非整数

如果只用旧版的 _plural,波兰语的 fewmany 完全无法区分。这就是为什么 i18next 要切换到 CLDR 标准——旧格式从设计上就无法覆盖复杂语言的复数规则

四、PluralResolver 的工作机制

i18next 内部通过 PluralResolver 模块处理复数分类。从 v24 开始,它强制依赖浏览器原生的 Intl.PluralRules API:

// i18next 内部的简化逻辑
function getSuffix(lng, count) {
  const rule = new Intl.PluralRules(lng);
  const category = rule.select(count); // 'one', 'other', 'few', etc.
  return `_${category}`;
}

可以直接在浏览器控制台验证:

new Intl.PluralRules('en').select(1); // 'one'
new Intl.PluralRules('en').select(5); // 'other'
new Intl.PluralRules('ar').select(0); // 'zero'
new Intl.PluralRules('ar').select(2); // 'two'
new Intl.PluralRules('ar').select(5); // 'few'
new Intl.PluralRules('pl').select(3); // 'few'
new Intl.PluralRules('pl').select(5); // 'many'

说白了,i18next 不再自己维护复数规则表,而是把这个工作交给了浏览器的 Intl API。这意味着规则会随浏览器更新而更新,不需要 i18next 自己跟进 CLDR 的版本变化。

t() 函数在处理带 count 参数的调用时,完整路径是:

t('items', { count: 5 })
  → getSuffix('en', 5)
  → Intl.PluralRules('en').select(5) → 'other'
  → 查找 'items_other'
  → 'You have {{count}} items'
  → 插值替换 → 'You have 5 items'

五、实战:为阿拉伯语配置完整复数

以一个计数器场景为例,展示如何正确配置阿拉伯语的全部六种复数形式:

// ar/translation.json
{
  "messages_zero": "لا توجد رسائل",
  "messages_one": "رسالة واحدة",
  "messages_two": "رسالتان",
  "messages_few": "{{count}} رسائل",
  "messages_many": "{{count}} رسالة",
  "messages_other": "{{count}} رسالة"
}
i18next.init({
  lng: 'ar',
  resources: {
    ar: {
      translation: {
        /* 如上 */
      },
    },
  },
});

console.log(i18next.t('messages', { count: 0 }));
// "لا توجد رسائل"

console.log(i18next.t('messages', { count: 1 }));
// "رسالة واحدة"

console.log(i18next.t('messages', { count: 2 }));
// "رسالتان"

console.log(i18next.t('messages', { count: 5 }));
// "5 رسائل"(few 类别)

console.log(i18next.t('messages', { count: 11 }));
// "11 رسالة"(many 类别)

实际项目中,不需要记住每种语言有多少种复数形式。只需要查 CLDR 规范或者用 Intl.PluralRules 验证,然后为每个类别提供对应的翻译键即可。

六、迁移指南:从 v3 格式到 v4 格式

如果项目还在用旧的 _plural 后缀,迁移步骤如下:

(1)确认当前格式

检查 i18next.init 配置中是否有 compatibilityJSON: 'v3'。如果有,说明项目还在用旧格式。

(2)批量替换翻译键

i18next 官方提供了 i18next-v4-format-converter 工具:

npx i18next-v4-format-converter -i ./locales -o ./locales-v4

这个工具会自动将 _plural 后缀转换为 _one / _other

(3)更新初始化配置

// 迁移前
i18next.init({
  compatibilityJSON: 'v3',
  // ...
});

// 迁移后:移除 compatibilityJSON 配置
i18next.init({
  // ...
});

(4)验证特殊语言

英语的迁移很简单(_plural_other),但如果项目支持阿拉伯语、波兰语等复杂语言,需要检查是否补充了 _zero_few_many 等之前缺失的键。旧格式根本没有这些后缀,迁移时可能需要找翻译人员补充。

七、总结

i18next 的复数处理从”单数/复数二分法”演进到了 CLDR 六类别标准。如果你只记住一句话:v21+ 不再用 _plural,改用 _one / _other / _few / _many / _zero,底层靠 Intl.PluralRules 驱动。 旧项目需要尽快完成格式迁移,否则升级 i18next 大版本时会静默丢失复数翻译。


本系列其他文章:

相关主题:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;