原文链接:The Power Of The Intl API: A Definitive Guide To Browser-Native Internationalization,2025 年 8 月 8 日,by Fuqiao Xue。
导读: 国际化(i18n)不只是文本翻译那么简单。它还涉及根据特定地区的规则格式化日期、处理单词复数形式、对名称进行排序等一系列操作。现代 JavaScript 不再依赖臃肿的第三方库,而是提供了 Intl API------这是一种强大的原生方案,可轻松处理国际化需求。它无声地提醒着我们:Web 真正具备了全球属性。
人们普遍存在一个误解,认为国际化(i18n)仅仅是文本翻译。尽管翻译至关重要,但它只是国际化的一个方面。国际化的复杂性之一,在于要根据不同文化群体的期望调整信息的呈现方式。在日本和德国,日期的显示格式有何不同?在阿拉伯语和英语中,单词复数形式的表达方法又有何差异?如何对不同语言的名称列表进行排序?
过去,许多开发者要么依赖庞大的第三方库,要么就是自己编写格式化函数来应对这些挑战(更糟)。这些解决方案虽然能实现功能,但往往伴随着巨大的额外开销:包体积增大、可能的性能瓶颈,还要不断跟进并更近语言规则和地区数据。
这时,ECMAScript 国际化 API (通常称为 Intl
对象)应运而生。这个直接内置在现代 JavaScript 环境中 API,是处理数据国际化的原生、高效、标准化的解决方案,却常常被低估。它证明了 Web 对"全球化"的承诺------能根据特定地区规则,以统一、高效的方式格式化数字、日期、列表等数据。
Intl 与地区:不只是语言代码
Intl
的核心是 "地区"(locale) 的概念。地区远不只是两个字母的语言代码(比如英语的 en
或西班牙语的 es
),它包含了为特定文化群体恰当呈现信息所需的完整上下文,具体包括:
- 语言(Language) :主要使用的语言(如
en
、es
、fr
)。 - 文字系统(Script) :所使用的文字脚本(如:拉丁语
Latn
、西里尔字母Cyrl
)。例如,简体中文用zh-Hans
,繁体中文用zh-Hant
。 - 区域(Region) :地理区域(如:美国的
US
、英国的GB
、德国的DE
)。这对同一种语言的变体至关重要,例如en-US
(美式英语)和en-GB
(英式英语)在日期、时间和数字格式上都存在差异。 - 偏好/变体(Preferences/Variants) :更具体的文化或语言偏好。更多信息可参考 W3C 的《选择语言标记》("Choosing a Language Tag")。
通常,你需要根据网页语言进行地区设置,可通过 lang
属性获取:
javascript
// 从 HTML 的 lang 属性中获取页面语言
const pageLocale = document.documentElement.lang || 'en-US'; // 默认值设为 'en-US'
有时,你可能需要用特定地区的设置覆盖页面默认语言,比如在展示多语言内容时:
javascript
// 强制使用特定地区的设置,不受页面语言影响
const tutorialFormatter = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });
console.log(`中文示例:${tutorialFormatter.format(199.99)}`); // 输出:¥199.99
在某些场景下,你可能希望使用用户偏好的语言:
javascript
// 使用用户偏好的语言
const browserLocale = navigator.language || 'ja-JP';
const formatter = new Intl.NumberFormat(browserLocale, { style: 'currency', currency: 'JPY' });
实例化 Intl
格式化器时,可选择性地传入一个或多个地区字符串。API 会根据可用性和偏好,自动选择最合适的地区设置。
核心格式化功能
Intl
对象提供了多个构造函数,每个构造函数对应特定的格式化任务。下面我们深入探讨最常用的几个,同时介绍一些功能强大但常被忽略的"宝藏"功能。
1. Intl.DateTimeFormat:全球化的日期与时间格式化
日期和时间格式化是国际化中的经典问题。应该用 MM/DD/YYYY 格式还是 DD.MM.YYYY 格式?月份该用数字还是完整单词表示?Intl.DateTimeFormat
能轻松应对这些问题。
javascript
const date = new Date(2025, 5, 27, 14, 30, 0); // 2025年6月27日14:30:00(注:JavaScript 月份从0开始计数)
// 特定地区和选项(如长日期格式、短时间格式)
const options = {
weekday: 'long', // 完整星期名称(如 Friday)
year: 'numeric', // 四位年份(如 2025)
month: 'long', // 完整月份名称(如 June)
day: 'numeric', // 日期数字(如 27)
hour: 'numeric', // 小时(如 2 或 14)
minute: 'numeric', // 分钟(如 30)
timeZoneName: 'shortOffset' // 短时区偏移(如 "GMT+8")
};
console.log(new Intl.DateTimeFormat('en-US', options).format(date));
// 输出:"Friday, June 27, 2025 at 2:30 PM GMT+8"(美式英语格式)
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));
// 输出:"Freitag, 27. Juni 2025 um 14:30 GMT+8"(德语格式)
// 使用 dateStyle 和 timeStyle 快速应用常用格式
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }).format(date));
// 输出:"Friday 27 June 2025 at 14:30"(英式英语完整日期+短时间)
console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeStyle: 'short' }).format(date));
// 输出:"2025年6月27日 14:30"(日语长日期+短时间)
Intl.DateTimeFormat
的 options
参数灵活性极高,可控制年份、月份、日期、星期、小时、分钟、秒、时区等多个维度的格式。
2. Intl.NumberFormat:带文化差异的数字格式化
数字格式化远不止保留小数位数那么简单:千位分隔符、小数点标记、货币符号、百分号等在不同地区差异巨大。
javascript
const price = 123456.789;
// 货币格式化
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price));
// 输出:"$123,456.79"(自动四舍五入到两位小数)
console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price));
// 输出:"123.456,79 €"(德语区欧元格式)
// 单位格式化
console.log(new Intl.NumberFormat('en-US', { style: 'unit', unit: 'meter', unitDisplay: 'long' }).format(100));
// 输出:"100 meters"(英文完整单位)
console.log(new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilogram', unitDisplay: 'short' }).format(5.5));
// 输出:"5,5 kg"(法语短单位,逗号为小数点)
此外,minimumFractionDigits
(最小小数位数)、maximumFractionDigits
(最大小数位数)、notation
(表示法,如 scientific
科学计数法、compact
简洁计数法)等选项能提供更精细的控制。
3. Intl.ListFormat:自然语言列表格式化
列表展示看似简单,实则暗藏玄机。英语中用"and"表示并列关系,用"or"表示选择关系;而许多语言有不同的连接词,部分语言还对标点有特殊要求。
这个 API 简化了原本需要复杂条件逻辑才能实现的功能:
javascript
const items = ['apples', 'oranges', 'bananas']; // 苹果、橙子、香蕉
// 并列关系列表(用 "and" 连接)
console.log(new Intl.ListFormat('en-US', { type: 'conjunction' }).format(items));
// 输出:"apples, oranges, and bananas"(美式英语并列列表)
console.log(new Intl.ListFormat('de-DE', { type: 'conjunction' }).format(items));
// 输出:"Äpfel, Orangen und Bananen"(德语并列列表)
// 选择关系列表(用 "or" 连接)
console.log(new Intl.ListFormat('en-US', { type: 'disjunction' }).format(items));
// 输出:"apples, oranges, or bananas"(美式英语选择列表)
console.log(new Intl.ListFormat('fr-FR', { type: 'disjunction' }).format(items));
// 输出:"apples, oranges ou bananas"(法语选择列表)
4. Intl.RelativeTimeFormat:人类可读的相对时间戳
在 UI 中显示"2 days ago""或"in 3 months"很常见,但要准确本地化这些表述需要大量语言数据。Intl.RelativeTimeFormat
能自动完成这项工作。
javascript
// 美式英语相对时间格式化器(自动省略数字,如用 "yesterday" 代替 "1 day ago")
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // 输出:"yesterday"(1天前→昨天)
console.log(rtf.format(1, 'day')); // 输出:"tomorrow"(1天后→明天)
console.log(rtf.format(-7, 'day')); // 输出:"7 days ago"(7天前)
console.log(rtf.format(3, 'month')); // 输出:"in 3 months"(3个月后)
console.log(rtf.format(-2, 'year')); // 输出:"2 years ago"(2年前)
// 法语示例(完整表述)
const frRtf = new Intl.RelativeTimeFormat('fr-FR', { numeric: 'auto', style: 'long' });
console.log(frRtf.format(-1, 'day')); // 输出:"hier"(昨天)
console.log(frRtf.format(1, 'day')); // 输出:"demain"(明天)
console.log(frRtf.format(-7, 'day')); // 输出:"il y a 7 jours"(7天前)
console.log(frRtf.format(3, 'month')); // 输出:"dans 3 mois"(3个月后)
若设置 numeric: 'always'
,则会强制显示数字,例如用"1 day ago"代替"yesterday"。
5. Intl.PluralRules:精通复数规则
复数处理可以说是国际化中最关键的环节之一。不同语言的复数规则差异极大(例如:英语有单数和复数两种形式,阿拉伯语则有零、一、二、多等多种形式)。Intl.PluralRules
能根据特定地区设置,确定指定数字对应的"复数类别"。
javascript
// 美式英语复数规则
const prEn = new Intl.PluralRules('en-US');
console.log(prEn.select(0)); // 输出:"other"(0项)
console.log(prEn.select(1)); // 输出:"one"(1项)
console.log(prEn.select(2)); // 输出:"other"(2项)
// 埃及阿拉伯语复数规则
const prAr = new Intl.PluralRules('ar-EG');
console.log(prAr.select(0)); // 输出:"zero"(0项)
console.log(prAr.select(1)); // 输出:"one"(1项)
console.log(prAr.select(2)); // 输出:"two"(2项)
console.log(prAr.select(10)); // 输出:"few"(10项)
console.log(prAr.select(100)); // 输出:"other"(100项)
该 API 不直接处理文本复数形式,而是提供核心的分类结果,以便从消息资源包中选择正确的翻译文本。例如,若消息键(message keys)为 item.one
(1项)和 item.other
(多项),可通过 pr.select(count)
选择对应的消息。
6. Intl.DisplayNames:万物皆可本地化命名
需要用用户偏好的语言显示语言名称、地区名称或文字脚本名称?Intl.DisplayNames
是你的全能解决方案。
javascript
// 用英语显示语言名称
const langNamesEn = new Intl.DisplayNames(['en'], { type: 'language' });
console.log(langNamesEn.of('fr')); // 输出:"French"(法语)
console.log(langNamesEn.of('es-MX')); // 输出:"Mexican Spanish"(墨西哥西班牙语)
// 用法语显示语言名称
const langNamesFr = new Intl.DisplayNames(['fr'], { type: 'language' });
console.log(langNamesFr.of('en')); // 输出:"anglais"(英语)
console.log(langNamesFr.of('zh-Hans')); // 输出:"chinois (simplifié)"(中文(简体))
// 显示地区名称
const regionNamesEn = new Intl.DisplayNames(['en'], { type: 'region' });
console.log(regionNamesEn.of('US')); // 输出:"United States"(美国)
console.log(regionNamesEn.of('DE')); // 输出:"Germany"(德国)
// 显示文字脚本名称
const scriptNamesEn = new Intl.DisplayNames(['en'], { type: 'script' });
console.log(scriptNamesEn.of('Latn')); // 输出:"Latin"(拉丁语脚本)
console.log(scriptNamesEn.of('Arab')); // 输出:"Arabic"(阿拉伯语脚本)
借助 Intl.DisplayNames
,你无需硬编码大量语言、地区或文字脚本的映射关系,能让应用更健壮、更精简。
浏览器支持情况
你可能会关心浏览器兼容性------好消息是,Intl
在现代浏览器中支持度极佳。所有主流浏览器(Chrome、Firefox、Safari、Edge)都完全支持本文讨论的核心功能(DateTimeFormat
、NumberFormat
、ListFormat
、RelativeTimeFormat
、PluralRules
、DisplayNames
)。对于大多数用户群体,你可以放心使用这些 API,无需额外引入 polyfill(兼容补丁)。
结语:用 Intl 拥抱全球网络
对于面向全球用户的现代 Web 开发而言,Intl
API 是基石。它让前端开发者能借助浏览器内置的优化能力,以最少的工作量交付高度本地化的用户体验。
采用 Intl
不仅能减少依赖、缩小包体积、提升性能,还能确保应用尊重并适配全球用户多样化的语言和文化期望。别再纠结于自定义格式化逻辑了,拥抱这个符合标准的工具吧!
需要注意的是,Intl
主要负责数据格式化。尽管它功能强大,但无法解决国际化的所有问题。内容翻译、双向文本(RTL 从右到左/LTR 从左到右)、地区特定排版,以及数据格式化之外的深层文化差异,仍需谨慎处理。但在准确、直观地呈现动态数据方面,Intl
无疑是浏览器原生方案中的最佳选择。