JavaScript 国际化 API (Intl)

探索如何利用 JavaScript 原生的 Intl API 高效完成数字、日期、列表格式化及复数处理,无需依赖第三方重型库。

JavaScript 国际化 API (Intl)
3 分钟阅读
本页内容

摘要

JavaScript 中的国际化(i18n)过去一直高度依赖 Moment.js 或 date-fns 等大型外部库。现代浏览器原生的 Intl API 解决了这一难题,它直接在 JavaScript 引擎层面提供了数字格式化、日期处理、字符串排序和文本分词等功能。

本文深入探讨 Intl 命名空间下的核心构造函数,展示如何在零依赖的前提下让应用无缝适配多种文化与语言习惯。


1. 全局 Intl 对象

全局 Intl 对象充当命名空间(类似于 Math),无法通过 new 进行实例化,而是内置了多个专用的构造函数。

几乎所有构造函数都接收两个核心参数:

  • locales:符合 BCP 47 标准的语言标签字符串(如 "zh-CN"、"en-US")或包含它们的数组。若省略,则使用系统的默认语言环境。
  • options:用于微调格式化行为的配置对象。

2. 数字与货币格式化 (Intl.NumberFormat)

Intl.NumberFormat 根据地区习惯格式化数字(如千位分隔符、小数点等)。

2.1 货币与百分比

通过配置 style 属性,无需手动拼接字符串即可准确格式化财务数字或单位:

const amount = 123456.789;

// 中文(中国)格式:使用 comma 作为千位分隔符
const formatCN = new Intl.NumberFormat('zh-CN');
console.log(formatCN.format(amount)); // "123,456.789"

// 货币格式(人民币)
const formatCNY = new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY'
});
console.log(formatCNY.format(amount)); // "¥123,456.79"

// 美元会计格式(负数显示在括号内)
const formatAccounting = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  currencySign: 'accounting'
});
console.log(formatAccounting.format(-amount)); // "($123,456.79)"

2.2 物理单位

原生支持标准化物理单位(公里、升、字节等)的格式化:

const speed = new Intl.NumberFormat('zh-CN', {
  style: 'unit',
  unit: 'kilometer-per-hour',
  unitDisplay: 'long'
});
console.log(speed.format(120)); // "每小时 120 公里"

3. 复数规则 (Intl.PluralRules)

不同语言拥有极其复杂的复数语法规则。Intl.PluralRules 不直接返回格式化后的字符串,而是将数字归类为特定的复数类别("zero"、"one"、"two"、"few"、"many"、"other"),可将其用作翻译字典中的 Key。

const arabicRules = new Intl.PluralRules('ar-EG');
console.log(arabicRules.select(0));  // "zero"
console.log(arabicRules.select(1));  // "one"
console.log(arabicRules.select(2));  // "two"
console.log(arabicRules.select(6));  // "few"

// 英语序数词规则 (1st, 2nd, 3rd)
const ordinalRulesEN = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log(ordinalRulesEN.select(1)); // "one"(对应后缀 "st")
console.log(ordinalRulesEN.select(2)); // "two"(对应后缀 "nd")
console.log(ordinalRulesEN.select(3)); // "few"(对应后缀 "rd")
console.log(ordinalRulesEN.select(4)); // "other"(对应后缀 "th")

4. 时间与日期处理

Intl API 从三个维度提供了时间处理能力:

4.1 绝对日期时间格式化:Intl.DateTimeFormat

根据地区习惯和时区将日期转换为人类可读文本。支持 dateStyle 和 timeStyle 简写配置。

const date = new Date(Date.UTC(2026, 0, 10, 10, 0, 0));

const fullFormat = new Intl.DateTimeFormat('zh-CN', {
  dateStyle: 'full',
  timeStyle: 'long',
  timeZone: 'Asia/Shanghai'
});
console.log(fullFormat.format(date));
// "2026年1月10日星期六 CST 18:00:00"

// 支持无冗余的时间范围表达
const endDate = new Date(Date.UTC(2026, 0, 12, 10, 0, 0));
const rangeFormat = new Intl.DateTimeFormat('zh-CN', { dateStyle: 'long' });
console.log(rangeFormat.formatRange(date, endDate));
// "2026年1月10日–12日"

4.2 相对时间格式化:Intl.RelativeTimeFormat

适用于在界面中显示相对时间(如“5 分钟前”、“昨天”)。

const relativeTime = new Intl.RelativeTimeFormat('zh-CN', { numeric: 'auto' });

console.log(relativeTime.format(-1, 'day')); // "昨天"
console.log(relativeTime.format(2, 'day'));  // "后天"
console.log(relativeTime.format(-3, 'month')); // "3 个月前"

4.3 时间跨度格式化:Intl.DurationFormat

独立于具体日期上下文展示一段时长。接收 Temporal 规范中的 Duration 对象。

const span = { hours: 2, minutes: 4, seconds: 35 };

const stopwatch = new Intl.DurationFormat('en-US', {
  style: 'digital',
  hoursDisplay: 'always',
  minutes: '2-digit'
});
console.log(stopwatch.format(span)); // "2:04:35"

5. 字符串与文本数据处理

5.1 自然语言排序:Intl.Collator

解决了数组默认 .sort() 的诸多缺陷,能够正确按数字大小排序并忽略拼音/重音影响。

const docs = ['file10.txt', 'file2.txt', 'Apple.txt', 'Zebra.txt'];

// 默认 Array.sort() 会将 "10" 排在 "2" 之前
// Intl.Collator 可以正确处理自然排序:
const collator = new Intl.Collator('zh-CN', { numeric: true, sensitivity: 'base' });
docs.sort(collator.compare);

console.log(docs);
// ["Apple.txt", "file2.txt", "file10.txt", "Zebra.txt"]

5.2 列表连接格式化:Intl.ListFormat

根据语言规则生成恰当的并列或选择连接词(如中文的“和”、“或”,英文中的 Oxford Comma)。

const ingredients = ['面粉', '鸡蛋', '白糖'];

const formatterCN = new Intl.ListFormat('zh-CN', { type: 'conjunction' });
console.log(formatterCN.format(ingredients)); // "面粉、鸡蛋和白糖"

const formatterEN = new Intl.ListFormat('en-US', { type: 'conjunction' });
console.log(formatterEN.format(ingredients)); // "面粉, 鸡蛋, and 白糖"

5.3 智能文本分词:Intl.Segmenter

使用 .split(' ') 按空格拆分文本在东亚表意文字(如中文、日语)或由多个 Glyph 构成的复杂 Emoji 中会失效。Intl.Segmenter 提供了原生的语言学分词能力。

const japaneseText = "吾輩は猫である。名前はたぬき。";

const segmenter = new Intl.Segmenter('ja-JP', { granularity: 'word' });
const segments = segmenter.segment(japaneseText);

for (const chunk of segments) {
  if (chunk.isWordLike) {
    console.log(chunk.segment);
    // 正确输出 "吾輩", "は", "猫", "で", "ある", "名前", "は", "たぬき"
  }
}

5.4 国家、地区与语言名称:Intl.DisplayNames

无需加载庞大的本地化 JSON 字典,即可直接获得国家、地区或语言的本地化名称。

const regionTranslator = new Intl.DisplayNames(['zh-CN'], { type: 'region' });
console.log(regionTranslator.of('US')); // "美国"
console.log(regionTranslator.of('JP')); // "日本"

const languageTranslator = new Intl.DisplayNames(['en-US'], { type: 'language' });
console.log(languageTranslator.of('es-419')); // "Latin American Spanish"

6. 性能与缓存最佳实践

实例化 Intl 命名空间下的构造函数是相对昂贵的性能开销。

如果应用需要频繁执行某种格式化(例如在一个包含数千条记录的列表中进行映射渲染),绝不要在循环体内重复实例化格式化对象。

推荐的缓存模式:

const prices = [10.5, 20.9, 100, 450.2];

// ✅ 正确做法:在循环外部实例化格式化器对象
const formatCNY = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });

const renderOutput = prices.map(price => formatCNY.format(price));

7. 总结

ECMAScript Intl API 是 Web 与 Node.js 应用实现国际化的标准方案。通过将特定于区域设置的算法交由浏览器或服务器引擎处理,Intl 免去了额外引入第三方库的需要,使应用更加高效、稳健,并能适应区域惯例的持续变化。


参考来源:MDN Web 文档、ECMA-402 国际化 API 规范。

拖动可移动图示。按 + 和 − 缩放,方向键移动,0 重置视图。

一起合作

软件工程师、高级全栈开发工程师,常驻玻利维亚Santa Cruz,远程办公。欢迎新的工作机会。