JavaScript 英文序数词:名次末尾都是一,为什么 11th 和 21st 不同

前天 3阅读

排行榜要显示英文名次,程序按个位数字给一、二、三分别加 st、nd、rd,其余加 th。第一页看起来没问题,到第十一、十二、十三名却出错。英语序数词不能只看最后一位,Intl.PluralRules 可以按指定语言和 ordinal 类型选择类别,再由应用映射成对应后缀。

这个 API 名字里有复数规则,但也支持序数用途。默认的 cardinal 主要服务数量表达,不能直接代替名次规则。下面明确指定英语和 ordinal,把例子保存为 demo.mjs 后用 node demo.mjs 执行,运行前也检查当前环境是否支持所需语言。

类别是文案选择键,不是最终字符串

JavaScript 英文序数词:名次末尾都是一,为什么 11th 和 21st 不同

AI概念示意图:名次缎带按不同类别排列,旁边托盘保留需要单独处理的例外组。图片不是运行截图。

import assert from "node:assert/strict";

assert.deepEqual(Intl.PluralRules.supportedLocalesOf(["en"]), ["en"]);
const ordinal = new Intl.PluralRules("en", {type: "ordinal"});
const cardinal = new Intl.PluralRules("en");
assert.equal(ordinal.resolvedOptions().type, "ordinal");
assert.equal(cardinal.resolvedOptions().type, "cardinal");
assert.equal(cardinal.select(3), "other");
assert.equal(ordinal.select(3), "few");

const suffixes = {one: "st", two: "nd", few: "rd", other: "th"};
function formatRank(value) {
  if (!Number.isSafeInteger(value) || value <= 0) {
    throw new RangeError("positive safe integer rank required");
  }
  const suffix = suffixes[ordinal.select(value)];
  assert.equal(typeof suffix, "string");
  return String(value) + suffix;
}
const ranks = [1, 2, 3, 4, 11, 12, 13, 21, 22, 23, 111, 121];
const actual = ranks.map(formatRank);
assert.deepEqual(actual, ["1st", "2nd", "3rd", "4th", "11th", "12th",
  "13th", "21st", "22nd", "23rd", "111th", "121st"]);
for (const bad of [0, -1, 1.5, "2", Number.MAX_SAFE_INTEGER + 1]) {
  assert.throws(() => formatRank(bad), RangeError);
}
assert.equal(String(11) + suffixes[ordinal.select(1)], "11st");
assert.notEqual(String(11) + suffixes[ordinal.select(1)], formatRank(11));
console.log("categories:", ranks.map(n => ordinal.select(n)).join(","));
console.log("ranks:", actual.join(", "));
console.log("cardinal 3:", cardinal.select(3));

例子中一、二、三分别得到 one、two、few,四得到 other;十一、十二、十三都得到 other,二十一又回到 one。映射表再把这些类别变成 st、nd、rd、th,因此最后输出 1st、2nd、3rd、4th、11th、12th、13th、21st 等已知结果。

one、two、few 并不是把数字翻译成英语单词,也不是可直接显示给用户的完整文案。它们是本地化规则返回的类别名称,不同语言和规则类型使用的集合可能不同。应用仍需准备对应模板,并决定数字、空格、标点和后缀怎样组合。

默认 cardinal 会让第三名选错类别

代码另建一个只指定 en 的实例,resolvedOptions 显示类型为 cardinal。它对三返回 other,而 ordinal 实例对三返回 few。如果遗漏 type,程序不会报错,英语第三名却会被错误拼成 th 后缀。选项错误与运行错误的区别,再次说明测试应该检查最终文字,而不只是函数能否调用。

本例通过 resolvedOptions 核验实际采用的类型,supportedLocalesOf 检查语言支持。如果运行环境缺少请求语言的数据,不能在没有检查的情况下把回退语言的结果当成目标语言的规则。服务器与浏览器若使用不同的国际化数据,应在两边运行同一组代表性样本。

把输入限制为这份界面真正需要的名次

formatRank 只接收正的安全整数。零、负数、小数、字符串和超出安全整数范围的数都会被拒绝。这是排行榜的业务约定,而非声称 PluralRules 自身只支持整数。先限制名次定义,能避免把小数的语法类别误用到一个本来不允许并列平均名次的界面。

示例不自动把字符串 "2" 转成数字,也不先四舍五入。若表单允许数字文字,应在独立入口完成解析;若产品需要“并列第二”,则准备相应文案并明确排序规则。类别选择无法代替业务对名次从何而来的定义。

英文后缀映射不能直接推广到其他语言

这里只为英语设计了后缀表。换成其他语言时,应重新准备适合该语言的完整名次表达,不能只改 locale 再继续拼 st、nd、rd、th。某些语言的表达还会依赖名词、性别或句子环境,单个数字类别可能不足以完成整句翻译。

最后的断言覆盖个位常规情况、十一到十三、二十一到二十三、以及一百一十一与一百二十一。这样的样本能同时发现“只看个位”和“误用数量规则”两种错误。本文验证的是当前运行时的数据与例子,不把类别选择描述为万能语法生成器;完整界面仍应由对应语言的读者审阅。

资料核对日期:2026年10月2日(北京时间)。示例在 Node.js 24.19.0 中独立运行。

参考资料

ECMAScript 国际化规范:Intl.PluralRules

Unicode CLDR:英语数量与序数类别

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。