多年来,开发者一直依赖JavaScript库、字符串处理或服务端逻辑,来确保全球用户看到的日期、数字和文本格式既自然又正确。虽然这些方案能用,但往往会显著增加网页体积,而且还带来维护上的挑战。
不过,现代浏览器现在内置了一个标准方案:ECMAScript国际化API。你可以通过JavaScript全局可用的Intl对象来使用它。这个API可以原生处理语言区域与文化敏感数据和操作,确保你的应用能够高效且准确地“说用户的语言”。
本文将对Intl API中最核心的部分做一个实用的概览,并提供可直接上手的示例,帮助你从今天开始为Web应用实现国际化。
在深入具体的格式化工具之前,先理解几乎每个Intl构造函数都会接收的两个基础参数很重要:
locales:表示语言标签的字符串(遵循BCP 47标准),例如'en-US'(美式英语)、'fr-FR'(法国法语),或'ja'(日语)。你也可以传入语言区域数组,如['fr-CA', 'fr-FR'],浏览器会使用其支持的第一个值。如果省略该参数,则使用浏览器默认语言区域。options:用于自定义格式化行为的对象。这个API的强大之处主要体现在这里,你可以从货币符号到日期样式进行细粒度控制。显示日期和时间是最常见的国际化任务之一。像 “10/12/2025” 这样的日期,在美国可能表示10月12日,而在欧洲的许多地区则表示12月10日。Intl.DateTimeFormat可以轻松消除这种歧义。
基本用法很简单:先创建格式化器实例,再调用它的.format()方法:
const eventDate = new Date();
// 面向美国用户
const usFormatter = new Intl.DateTimeFormat('en-US');
console.log(usFormatter.format(eventDate));
// 输出:10/26/2025
// 面向德国用户
const deFormatter = new Intl.DateTimeFormat('de-DE');
console.log(deFormatter.format(eventDate));
// 输出:26.10.2025
需要注意的是,纯数字的日期常常让读者困惑。更好的做法是将月份写完整,从而避免歧义。具体做法见使用选项进行精细控制。
要显示当前日期,请使用new Date():
const fmt = new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' });
// 当前日期(今天)
fmt.format(new Date()); // 例如:26 October 2025
要显示指定日期,请给new Date()传入参数。月份从 0 开始计数:
// 指定日历日期:2025 年 6 月 27 日(本地时间)
const june27Local = new Date(2025, 5, 27);
fmt.format(june27Local); // 27 June 2025
通过options对象,你可以得到更详细、更易读的格式。现代写法通常使用dateStyle和timeStyle。
const options = {
dateStyle: 'full',
timeStyle: 'long',
};
const formatter = new Intl.DateTimeFormat('es-ES', options);
console.log(formatter.format(eventDate));
// 输出:viernes, 15 de agosto de 2025, 10:30:00 UTC
你还可以指定时区:
const japanFormatter = new Intl.DateTimeFormat('ja-JP', {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'Asia/Tokyo',
});
console.log(japanFormatter.format(eventDate));
// 示例输出:2025年10月27日(注意:日期可能因时区而变化)
不同地区对数字的格式化方式并不相同。例如,小数分隔符可能是点,也可能是逗号。Intl.NumberFormat可以无缝处理这些差异。
const largeNumber = 1234567.89;
// 美国
console.log(new Intl.NumberFormat('en-US').format(largeNumber));
// 输出:1,234,567.89
// 德国
console.log(new Intl.NumberFormat('de-DE').format(largeNumber));
// 输出:1.234.567,89
// 印度
console.log(new Intl.NumberFormat('en-IN').format(largeNumber));
// 输出:12,34,567.89
// 泰国(使用泰文数字)
console.log(new Intl.NumberFormat('th-TH-u-nu-thai').format(largeNumber));
// 输出:๑,๒๓๔,๕๖๗.๘๙
货币格式化对电商场景至关重要,而且不只是是加一个符号的问题,符号的位置和间距都和语言、区域有关。使用Intl.NumberFormat时,需将style设为'currency',并提供ISO 4217 货币代码。
const price = 99.95;
// 美元
console.log(new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price)); // 输出:$99.95
// 面向德国客户的欧元
console.log(new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(price)); // 输出:99,95 €
// 面向爱尔兰客户的欧元
console.log(new Intl.NumberFormat('en-IE', {
style: 'currency',
currency: 'EUR'
}).format(price)); // 输出:€99.95
API还支持单位格式化,以及大数字的紧凑表示法:
// 单位格式化
console.log(new Intl.NumberFormat('en-GB', {
style: 'unit',
unit: 'kilometer-per-hour'
}).format(100)); // 输出:100 km/h
// 紧凑表示法
console.log(new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short'
}).format(2500000)); // 输出:2.5M
如果你曾在带重音符号的语言里对字符串数组排序,就会知道JavaScript默认的Array.prototype.sort()可能失效。它按码位排序,这常常会导致字母顺序不正确。
Intl.Collator提供了语言区域敏感的字符串比较函数。
const names = ['Émilie', 'Zoe', 'Elodie', 'Stéphane', 'Åsa', 'Örjan'];
// 默认排序
console.log([...names].sort());
// 输出:['Elodie', 'Stéphane', 'Zoe', 'Åsa', 'Émilie', 'Örjan']
// 使用Intl.Collator(法语)
const frCollator = new Intl.Collator('fr');
console.log(names.sort(frCollator.compare));
// 输出:['Åsa', 'Elodie', 'Émilie', 'Örjan', 'Stéphane', 'Zoe']
// 使用Intl.Collator(瑞典语)
const svCollator = new Intl.Collator('sv');
console.log(names.sort(svCollator.compare));
// 输出:['Elodie', 'Émilie', 'Stéphane', 'Zoe', 'Åsa', 'Örjan']
你甚至可以通过options实现不区分大小写排序,或正确处理包含数字的字符串排序(例如 “Chapter 2” 与 “Chapter 10”)。
const files = ['item 10', 'item 2'];
const numericCollator = new Intl.Collator(undefined, { numeric: true });
console.log(files.sort(numericCollator.compare));
// 输出:[ 'item 2', 'item 10' ]
在这个例子里,undefined实际上表示:“我不想强制指定某个排序语言区域。请使用用户的默认语言区域,但要用numeric: true选项。”
这样做能让代码更健壮、更友好:它会自动适配用户,同时仍然提供我们需要的特定排序行为(按数字排序)。
Intl.RelativeTimeFormat非常适合生成类似“2天前”或“3个月后”这样的自然语言表达。
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // "yesterday"
console.log(rtf.format(2, 'week')); // "in 2 weeks"
console.log(rtf.format(3, 'month')); // "in 3 months"
const rtf_es = new Intl.RelativeTimeFormat('es');
console.log(rtf_es.format(-1, 'day')); // "hace 1 día"
Intl.RelativeTimeFormat中的numeric选项有两个可选值:
always(默认):始终使用数字。auto:如果该语言区域对该相对时间有专门词语(如 “yesterday” 或 “tomorrow”),就用这个词;否则回退为数字表达。本文只是抛砖引玉。Intl API还包括Intl.PluralRules(处理复数)、Intl.ListFormat(处理“A、B和C”这类列表)、Intl.DisplayNames(翻译地区和语言的名字)等更多能力。
采用ECMAScript国际化API后,你可以把本地化逻辑从臃肿的库迁移到浏览器原生引擎中。这样不仅代码更少,也能为全球用户提供更准确、性能更好的体验。