Intl API:浏览器原生国际化API入门指南

原文链接: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) :主要使用的语言(如 enesfr)。
  • 文字系统(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.DateTimeFormatoptions 参数灵活性极高,可控制年份、月份、日期、星期、小时、分钟、秒、时区等多个维度的格式。

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)都完全支持本文讨论的核心功能(DateTimeFormatNumberFormatListFormatRelativeTimeFormatPluralRulesDisplayNames)。对于大多数用户群体,你可以放心使用这些 API,无需额外引入 polyfill(兼容补丁)。

结语:用 Intl 拥抱全球网络

对于面向全球用户的现代 Web 开发而言,Intl API 是基石。它让前端开发者能借助浏览器内置的优化能力,以最少的工作量交付高度本地化的用户体验

采用 Intl 不仅能减少依赖、缩小包体积、提升性能,还能确保应用尊重并适配全球用户多样化的语言和文化期望。别再纠结于自定义格式化逻辑了,拥抱这个符合标准的工具吧!

需要注意的是,Intl 主要负责数据格式化。尽管它功能强大,但无法解决国际化的所有问题。内容翻译、双向文本(RTL 从右到左/LTR 从左到右)、地区特定排版,以及数据格式化之外的深层文化差异,仍需谨慎处理。但在准确、直观地呈现动态数据方面,Intl 无疑是浏览器原生方案中的最佳选择。

相关推荐
EndingCoder4 分钟前
函数基础:参数和返回类型
linux·前端·ubuntu·typescript
码客前端10 分钟前
理解 Flex 布局中的 flex:1 与 min-width: 0 问题
前端·css·css3
Komorebi゛10 分钟前
【CSS】圆锥渐变流光效果边框样式实现
前端·css
工藤学编程23 分钟前
零基础学AI大模型之CoT思维链和ReAct推理行动
前端·人工智能·react.js
徐同保23 分钟前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
怕浪猫24 分钟前
React从入门到出门第四章 组件通讯与全局状态管理
前端·javascript·react.js
博主花神25 分钟前
【React】扩展知识点
javascript·react.js·ecmascript
欧阳天风32 分钟前
用setTimeout代替setInterval
开发语言·前端·javascript
EndingCoder35 分钟前
箭头函数和 this 绑定
linux·前端·javascript·typescript
郑州光合科技余经理36 分钟前
架构解析:同城本地生活服务o2o平台海外版
大数据·开发语言·前端·人工智能·架构·php·生活