先记住这个答案
new Intl.PluralRules(locale).select(n) 返回数字 n 在这种语言里的复数类别,一共可能有六种:zero、one、two、few、many、other。每种语言用到其中几种:英语只有 one 和 other,俄语有 one、few、many 和 other,阿拉伯语六种全用,中文只有 other。它不负责翻译,应用要维护一张以类别为键的文案表,拿到类别后去表里取对应的句子。这样换语言时只换文案表,判断逻辑不用改。
select()返回类别名,不返回文案- 不同语言类别数量不同,中文只有
other - 文案表一定要有
other兜底 type: 'ordinal'用来生成 1st、2nd、3rd
按类别从文案表里取句子
「你有 N 个文件」这句话,英语要区分 file 和 files,俄语要区分三种词形。用 count === 1 只能覆盖英语。
const messages = {
en: { one: '# file', other: '# files' },
ru: { one: '# файл', few: '# файла', many: '# файлов', other: '# файла' }
}
function fileCount(locale, count) {
const category = new Intl.PluralRules(locale).select(count)
return messages[locale][category].replace('#', count)
}
console.log([1, 2, 5].map((n) => fileCount('en', n)).join(' / '))
console.log([1, 2, 5, 21].map((n) => fileCount('ru', n)).join(' / '))查看输出与解释
1 file / 2 files / 5 files
1 файл / 2 файла / 5 файлов / 21 файл英语 1 是 one,2 和 5 都是 other。俄语复杂得多:1 是 one,2 到 4 是 few,5 到 20 是 many,而 21 又回到了 one,因为俄语看的是末位数字。这种规则自己写 if 很容易漏,交给 Intl.PluralRules 就对了。
俄语文案里的 other 是给小数用的,比如 1.5。写文案表时可以用 resolvedOptions().pluralCategories 查一下这个语言到底有哪些类别,逐个补齐。
序数:1st、2nd、3rd 怎么来
排行榜、楼层、第几名这种「第几个」的场景,英语后缀是 st、nd、rd、th,规则也有例外。type: 'ordinal' 就是处理这种情况的。
ordinal 生成英语序数后缀JavaScriptconst ordinal = new Intl.PluralRules('en-US', { type: 'ordinal' })
const suffix = { one: 'st', two: 'nd', few: 'rd', other: 'th' }
const rank = (n) => n + suffix[ordinal.select(n)]
console.log([1, 2, 3, 4, 11, 12, 13, 21, 22, 23].map(rank).join(' '))查看输出与解释
1st 2nd 3rd 4th 11th 12th 13th 21st 22nd 23rd11、12、13 是例外,后缀是 th 而不是 st、nd、rd。手写的话通常要先判断 n % 100 是不是在 11 到 13 之间,再看 n % 10。用 ordinal 模式,这些规则都包含在语言数据里了。
默认的 type: 'cardinal' 用于「多少个」,ordinal 用于「第几个」,同一个数字在两种模式下类别不同:英语里 2 在 cardinal 下是 other,在 ordinal 下是 two。
容易答错的地方
- 只判断
count === 1 - 这只对英语这类两分法语言成立。俄语、波兰语、阿拉伯语都有更多类别,小数在很多语言里也有单独的规则。让对应语言的
Intl.PluralRules决定类别。 - 文案表里没有
other other是所有语言都有的兜底类别。只写了one的文案表,碰到 2 就会拿到undefined,页面上直接显示空白或报错。- 把
select()的结果直接显示给用户 one、few这些是程序里的键名,不是文案,用户看到「few」毫无意义。它只用来查表。
面试官还会怎么问?
实际项目里要自己写文案表吗?
一般不用。FormatJS(react-intl)用 ICU MessageFormat,写成 {count, plural, one {# file} other {# files}} 这样的格式;i18next 用 files_one 和 files_other 这样带类别后缀的 key。它们内部都是调 Intl.PluralRules 选分支。理解 Intl.PluralRules 是为了看懂这套机制,排查翻译问题时知道该补哪个类别。
1 和 1.0 的类别一样吗?
直接传数字时一样,select(1.0) 和 select(1) 没有区别,结果都是 one。但如果界面上显示的是「1.0」,英语语法上要用复数「1.0 files」。这时构造时传 minimumFractionDigits: 1,这样 select(1) 会返回 other,和显示格式保持一致。
selectRange() 是做什么的?
它为一个范围选类别,比如「1–3 天」这种区间文案,selectRange(1, 3) 在英语里返回 other。这是比较新的方法,用之前先确认目标浏览器支持。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。