📚 本篇依然来自于我们的 《前端周刊》 项目! 🧑💻 由团队成员 德育处主任 翻译,欢迎大家 进群 持续追踪全球最新前端资讯!! 📜 原文:The Power Of The Intl API: A Definitive Guide To Browser-Native Internationalization
国际化可不只是翻译文本那么简单。它涉及到根据特定区域设置格式化日期、复数形式转换、名称排序等诸多方面。如今,JavaScript 自带了 Intl API—— 无需依赖臃肿的第三方库,就能原生处理国际化问题。这也在默默提醒我们:网络确实是面向全球的。
很多人觉得国际化(i18n)就是简单的文本翻译,这其实是个误区。翻译固然重要,但只是其中一个方面。真正的复杂之处在于,要根据不同文化的习惯调整信息呈现方式:在日本和德国,日期的显示方式有何不同?在阿拉伯语和英语里,名词的复数形式规则有什么区别?如何对不同语言的名称列表进行排序?
过去,很多开发者会依赖庞大的第三方库,甚至自己手写格式化函数来解决这些问题。这些方案虽然能用,但往往伴随着不少麻烦:包体积增大、可能出现性能瓶颈,还要不断跟进语言规则和区域数据的更新。
这时,ECMAScript 国际化 API(通常称为 Intl
对象)就派上用场了。这个内置在现代 JavaScript 环境中的 “隐形高手”,常常被低估,但其实是处理数据国际化的强大工具 —— 原生支持、性能出色,还符合标准。它证明了网络对全球化的承诺:提供统一高效的方式,根据特定区域设置格式化数字、日期、列表等内容。
Intl
与区域设置:不只是语言代码
Intl
的核心是 “区域设置(locale)” 的概念。区域设置远不止两个字母的语言代码(比如英语是 en
,西班牙语是 es
),它包含了让信息符合特定文化群体习惯所需的完整上下文,具体包括:
-
语言:主要的语言(例如
en
、es
、fr
); -
文字系统:书写体系(例如
Latn
代表拉丁字母,Cyrl
代表西里尔字母)。比如zh-Hans
表示简体中文,zh-Hant
表示繁体中文; -
地区:地理区域(例如
US
代表美国,GB
代表英国,DE
代表德国)。这对同一种语言的变体很重要,比如en-US
(美式英语)和en-GB
(英式英语)在日期、时间和数字格式上都有差异; -
偏好 / 变体:更具体的文化或语言偏好。更多信息可参考 W3C 的《选择语言标签》。
通常,我们会根据网页的语言选择区域设置,这可以通过 lang
属性获取:
// 从HTML的lang属性获取页面语言
const pageLocale = document.documentElement.lang || 'en-US'; // 默认 fallback 为 'en-US'
偶尔,我们可能需要覆盖页面的区域设置,比如在展示多语言内容时:
// 强制使用特定区域设置,忽略页面语言
const tutorialFormatter = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });
console.log(`中文示例:${tutorialFormatter.format(199.99)}`); // 输出:¥199.99
有些场景下,还可能需要使用用户的首选语言:
// 使用用户的首选语言
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
能轻松搞定这些。
const date = new Date(2025, 6, 27, 14, 30, 0); // 2025年6月27日 14:30:00
// 特定区域设置和选项(例如:长日期、短时间)
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
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"
DateTimeFormat
的 options
非常灵活,可以控制年份、月份、日期、星期、小时、分钟、秒、时区等多个维度。
2. Intl.NumberFormat
:带文化特色的数字
数字格式化可不只是保留几位小数那么简单:千位分隔符、小数点符号、货币符号、百分号在不同区域设置中差异很大。
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 能省去复杂的条件判断逻辑:
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 天前” 或 “3 个月后”,但要准确本地化这些表达,需要大量语言数据。Intl.RelativeTimeFormat
能自动处理这一点。
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // "yesterday"(昨天)
console.log(rtf.format(1, 'day')); // "tomorrow"(明天)
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”(1 天前)而不是 “yesterday”(昨天)。
5. Intl.PluralRules
:搞定复数形式
复数规则可能是国际化中最关键的部分之一。不同语言的复数规则差异极大:英语只有单数和复数,阿拉伯语则有零、一、二、多等多种形式。Intl.PluralRules
能帮你确定特定区域设置中某个数字对应的 “复数类别”。
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"
console.log(prAr.select(1)); // "one"
console.log(prAr.select(2)); // "two"
console.log(prAr.select(10)); // "few"
console.log(prAr.select(100)); // "other"
这个 API 不会直接处理文本的复数形式,但能提供关键的分类信息,帮你从消息池中选出正确的翻译文本。比如,如果你的消息键是 item.one
、item.other
,就可以用 pr.select(count)
来匹配对应的键。
6. Intl.DisplayNames
:所有名称的本地化展示
需要用用户的首选语言显示语言、地区或文字系统的名称?Intl.DisplayNames
能全面解决这个问题。
// 用英语显示语言名称
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
拥抱全球网络
Intl
API 是面向全球用户的现代 Web 开发的基石。它让前端开发者能轻松实现高度本地化的用户体验,充分利用浏览器内置的优化能力。
采用 Intl
后,你能减少依赖、缩小包体积、提升性能,同时确保应用尊重全球用户的语言和文化习惯。别再纠结于自定义格式化逻辑了,赶紧用起来这个符合标准的工具吧!
不过要注意,Intl
主要处理数据格式化。虽然功能强大,但它不能解决国际化的所有问题。内容翻译、双向文本(RTL/LTR)、特定区域的排版,以及数据格式化之外的深层文化差异,都还需要额外考虑(说不定我以后会写相关内容!)。但在准确、直观地展示动态数据方面,Intl
绝对是浏览器原生的最佳方案。