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 无疑是浏览器原生方案中的最佳选择。

相关推荐
无羡仙7 分钟前
Webpack 背后做了什么?
javascript·webpack
roamingcode1 小时前
Claude Code NPM 包发布命令
前端·npm·node.js·claude·自定义指令·claude code
码哥DFS1 小时前
NPM模块化总结
前端·javascript
灵感__idea1 小时前
JavaScript高级程序设计(第5版):代码整洁之道
前端·javascript·程序员
唐璜Taro2 小时前
electron进程间通信-IPC通信注册机制
前端·javascript·electron
陪我一起学编程3 小时前
创建Vue项目的不同方式及项目规范化配置
前端·javascript·vue.js·git·elementui·axios·企业规范
LinXunFeng4 小时前
Flutter - 详情页初始锚点与优化
前端·flutter·开源
GISer_Jing4 小时前
Vue Teleport 原理解析与React Portal、 Fragment 组件
前端·vue.js·react.js
Summer不秃4 小时前
uniapp 手写签名组件开发全攻略
前端·javascript·vue.js·微信小程序·小程序·html
coderklaus4 小时前
Base64编码详解
前端·javascript