Intl.Locale API

使用结构化 JavaScript 对象解析、操作和查询本地化标识符

简介

在为多语言和多地区构建应用时,你会遇到像 en-USfr-FRzh-Hans-CN 这样的本地化标识符。这些字符串常见于浏览器 API、HTTP 头和用户偏好设置中,包含了语言、地区、书写系统和格式化偏好等信息。

Intl.Locale API 能将这些不透明的字符串转换为可供检查和操作的结构化对象。你无需手动解析字符串或猜测 zh-Hans-CN 的含义,只需创建一个 locale 对象并直接读取其属性。

本指南将介绍本地化标识符的工作原理、如何使用 Intl.Locale API 进行操作,以及在何种场景下结构化 locale 对象能够帮助你解决实际问题。

了解本地化标识符

本地化标识符是一个字符串,用于指定日期、数字、货币和文本格式的文化偏好。该标识符由多个用连字符分隔的组件组成。

最常见的结构遵循 BCP 47 标准:

language-script-region-variant-extensions

除语言代码外,其他组件均为可选。

语言代码

语言代码采用 ISO 639 的两位或三位小写字母。常见示例:

  • en 表示英语
  • es 表示西班牙语
  • fr 表示法语
  • de 表示德语
  • ja 表示日语
  • zh 表示中文
  • ar 表示阿拉伯语

语言代码始终为小写,并且在标识符中位于首位。

地区代码

地区代码使用 ISO 3166-1 的两个大写字母,指定地理区域,用于指明所用语言的变体:

  • en-US 表示美式英语
  • en-GB 表示英式英语
  • es-ES 表示欧洲西班牙语
  • es-MX 表示墨西哥西班牙语
  • fr-FR 表示法国法语
  • fr-CA 表示加拿大法语

区域代码会影响格式规范。美式英语使用 MM/DD/YYYY 日期格式,而英式英语使用 DD/MM/YYYY

字符集代码

字符集代码用四个字母表示,首字母大写。对于多种书写系统的语言,这一点很重要:

  • zh-Hans 表示简体中文字符
  • zh-Hant 表示繁体中文字符
  • sr-Cyrl 表示塞尔维亚语西里尔字母
  • sr-Latn 表示塞尔维亚语拉丁字母

大多数 locale 会省略字符集代码,因为语言本身就隐含了默认书写系统。英语默认使用拉丁字母,所以你只需写 en,而不是 en-Latn

扩展标签

扩展标签为 locale 标识符添加格式偏好。它们以 -u- 开头,后跟键值对:

en-US-u-ca-gregory-nu-latn-hc-h12

常见扩展键:

  • ca 表示日历系统(gregorybuddhistislamic
  • nu 表示数字系统(latnarabhanidec
  • hc 表示小时制(h12h23h11h24

扩展标签可自定义格式化器的数据展示方式,而不会改变语言或区域。

创建 locale 对象

Intl.Locale 构造函数接受一个 locale 标识符字符串,并返回一个结构化对象:

const locale = new Intl.Locale("en-US");

console.log(locale.language); // "en"
console.log(locale.region); // "US"

你还可以传递一个 options 对象来覆盖或添加属性:

const locale = new Intl.Locale("en", {
  region: "GB",
  hourCycle: "h23"
});

console.log(locale.baseName); // "en-GB"
console.log(locale.hourCycle); // "h23"

如果标识符无效,构造函数会抛出 RangeError

try {
  const invalid = new Intl.Locale("invalid-locale-code");
} catch (error) {
  console.error(error.message); // "invalid language subtag: invalid"
}

此验证可确保你在将格式错误的 locale 标识符传递给格式化器之前及时发现问题。

读取 locale 属性

Locale 对象公开的属性对应于标识符字符串的各个组成部分。所有属性均为只读。

核心属性

language 属性返回语言代码:

const locale = new Intl.Locale("fr-CA");
console.log(locale.language); // "fr"

region 属性返回地区代码:

const locale = new Intl.Locale("fr-CA");
console.log(locale.region); // "CA"

如果存在,script 属性返回脚本代码:

const locale = new Intl.Locale("zh-Hans-CN");
console.log(locale.script); // "Hans"

baseName 属性返回不带扩展的完整核心标识符:

const locale = new Intl.Locale("en-US-u-ca-gregory-nu-latn");
console.log(locale.baseName); // "en-US"

当你只需要语言和地区而不关心格式偏好时,请使用 baseName

扩展属性

扩展属性返回 -u- 扩展标签中的值,或在未指定时返回 undefined

calendar 属性返回日历系统:

const locale = new Intl.Locale("ar-SA-u-ca-islamic");
console.log(locale.calendar); // "islamic"

numberingSystem 属性返回数字系统:

const locale = new Intl.Locale("ar-EG-u-nu-arab");
console.log(locale.numberingSystem); // "arab"

hourCycle 属性返回小时制偏好:

const locale = new Intl.Locale("en-US-u-hc-h23");
console.log(locale.hourCycle); // "h23"

caseFirst 属性返回排序规则的大小写偏好:

const locale = new Intl.Locale("en-US-u-kf-upper");
console.log(locale.caseFirst); // "upper"

numeric 属性指示是否启用了数字排序:

const locale = new Intl.Locale("en-US-u-kn-true");
console.log(locale.numeric); // true

这些属性可让你无需手动解析扩展字符串即可检查格式化偏好。

查询本地化信息

Intl.Locale API 提供返回某个 locale 可用选项数组的方法。这些方法有助于构建用户界面并校验格式化选项。

可用日历

getCalendars() 方法返回该 locale 常用的日历系统:

const locale = new Intl.Locale("th-TH");
const calendars = locale.getCalendars();
console.log(calendars); // ["buddhist", "gregory"]

第一个元素是默认日历。泰语 locale 默认使用佛历,但也支持公历。

可用排序规则

getCollations() 方法返回用于字符串排序的排序类型:

const locale = new Intl.Locale("de-DE");
const collations = locale.getCollations();
console.log(collations); // ["phonebk", "emoji", "eor"]

德语有电话簿排序规则,其字符串排序方式不同于标准 Unicode 排序。

可用小时制

getHourCycles() 方法返回小时制格式:

const locale = new Intl.Locale("en-US");
const hourCycles = locale.getHourCycles();
console.log(hourCycles); // ["h12"]

美式英语默认使用 12 小时制。许多其他 locale 返回 ["h23"] 以表示 24 小时制。

可用数字系统

getNumberingSystems() 方法返回该 locale 常用的数字系统:

const locale = new Intl.Locale("ar-EG");
const numberingSystems = locale.getNumberingSystems();
console.log(numberingSystems); // ["arab", "latn"]

阿拉伯语 locale 通常同时支持阿拉伯-印度数字和拉丁数字。

文字方向

getTextInfo() 方法返回文本排序信息:

const locale = new Intl.Locale("ar-SA");
const textInfo = locale.getTextInfo();
console.log(textInfo.direction); // "rtl"

像阿拉伯语和希伯来语这样的从右到左语言会返回 "rtl"。从左到右的语言会返回 "ltr"

周规范

getWeekInfo() 方法返回该 locale 的周结构:

const locale = new Intl.Locale("en-US");
const weekInfo = locale.getWeekInfo();
console.log(weekInfo.firstDay); // 7 (Sunday)
console.log(weekInfo.weekend); // [6, 7] (Saturday, Sunday)
console.log(weekInfo.minimalDays); // 1

周规范因地区而异。美国的周从星期天开始,而大多数欧洲国家的周从星期一开始。

时区

getTimeZones() 方法返回该 locale 区域的时区:

const locale = new Intl.Locale("en-US");
const timeZones = locale.getTimeZones();
console.log(timeZones); // ["America/New_York", "America/Chicago", ...]

此方法返回该区域代码的 IANA 时区标识符。

最大化 locale 标识符

maximize() 方法会添加可能的子标签以生成完整标识符。它会根据语言数据推断缺失的脚本和区域代码。

当你只指定语言代码时,maximize() 会添加最常用的脚本和区域:

const locale = new Intl.Locale("en");
const maximized = locale.maximize();
console.log(maximized.baseName); // "en-Latn-US"

英语默认使用拉丁脚本和美国区域,因为这是最常见的组合。

中文的最大化取决于所用脚本:

const simplified = new Intl.Locale("zh-Hans");
const maximized = simplified.maximize();
console.log(maximized.baseName); // "zh-Hans-CN"

const traditional = new Intl.Locale("zh-Hant");
const maximizedTrad = traditional.maximize();
console.log(maximizedTrad.baseName); // "zh-Hant-TW"

简体中文通常关联中国,繁体中文则关联台湾。

在规范化用户输入或比较 locale 标识符时,建议使用 maximize()。它可以将隐含信息显式化。

最小化 locale 标识符

minimize() 方法会移除冗余的子标签,生成最短的等效标识符。当脚本和区域与默认值一致时,它会将其移除。

当 locale 使用默认脚本和区域时,minimize() 会将其移除:

const locale = new Intl.Locale("en-Latn-US");
const minimized = locale.minimize();
console.log(minimized.baseName); // "en"

拉丁脚本和美国区域是英语的默认值,因此可以移除而不会丢失信息。

对于具有非默认区域的 locale,minimize() 会保留区域信息:

const locale = new Intl.Locale("en-Latn-GB");
const minimized = locale.minimize();
console.log(minimized.baseName); // "en-GB"

英式英语需要区域代码,因为它与默认值不同。

使用 minimize() 可以在保留语义的同时,为存储或 URL 创建紧凑的标识符。

转换为字符串

toString() 方法会返回包含扩展的完整 locale 标识符:

const locale = new Intl.Locale("en-US", {
  calendar: "gregory",
  numberingSystem: "latn",
  hourCycle: "h12"
});

console.log(locale.toString()); // "en-US-u-ca-gregory-hc-h12-nu-latn"

该字符串表示可用于传递给其他 Intl API 或作为配置数据存储。

你也可以将 locale 对象隐式转换为字符串:

const locale = new Intl.Locale("fr-FR");
const formatter = new Intl.DateTimeFormat(locale);

DateTimeFormat 构造函数可以直接接受 locale 对象,并在内部调用 toString()

实用用例

Intl.Locale API 解决了构建国际化应用时的多个常见问题。

构建 locale 选择器

locale 选择器允许用户选择语言和区域。Intl.Locale API 有助于解析和校验用户选择:

function createLocaleSelector(locales) {
  const options = locales.map(identifier => {
    const locale = new Intl.Locale(identifier);
    const displayName = new Intl.DisplayNames([identifier], { type: "language" });

    return {
      value: locale.toString(),
      label: displayName.of(locale.language),
      region: locale.region
    };
  });

  return options;
}

const options = createLocaleSelector(["en-US", "en-GB", "fr-FR", "es-MX"]);
console.log(options);
// [
//   { value: "en-US", label: "English", region: "US" },
//   { value: "en-GB", label: "English", region: "GB" },
//   { value: "fr-FR", label: "French", region: "FR" },
//   { value: "es-MX", label: "Spanish", region: "MX" }
// ]

校验 locale 输入

当从用户输入或配置文件接收 locale 标识符时,请在使用前进行校验:

function validateLocale(identifier) {
  try {
    const locale = new Intl.Locale(identifier);
    return {
      valid: true,
      locale: locale.toString(),
      language: locale.language,
      region: locale.region
    };
  } catch (error) {
    return {
      valid: false,
      error: error.message
    };
  }
}

console.log(validateLocale("en-US")); // { valid: true, locale: "en-US", ... }
console.log(validateLocale("invalid")); // { valid: false, error: "..." }

提取语言用于回退

实现语言回退链时,提取不带区域的语言代码:

function getLanguageFallback(identifier) {
  const locale = new Intl.Locale(identifier);
  const fallbacks = [locale.toString()];

  if (locale.region) {
    const languageOnly = new Intl.Locale(locale.language);
    fallbacks.push(languageOnly.toString());
  }

  fallbacks.push("en");

  return fallbacks;
}

console.log(getLanguageFallback("fr-CA"));
// ["fr-CA", "fr", "en"]

这样可以创建一个回退链,先尝试特定 locale,再尝试不带区域的语言,最后使用默认语言。

配置格式化器

使用 locale 对象为 Intl 格式化器配置特定偏好:

function createFormatter(baseLocale, options = {}) {
  const locale = new Intl.Locale(baseLocale, {
    calendar: options.calendar || "gregory",
    numberingSystem: options.numberingSystem || "latn",
    hourCycle: options.hourCycle || "h23"
  });

  return {
    date: new Intl.DateTimeFormat(locale),
    number: new Intl.NumberFormat(locale),
    locale: locale.toString()
  };
}

const formatter = createFormatter("ar-SA", {
  calendar: "islamic",
  numberingSystem: "arab"
});

console.log(formatter.locale); // "ar-SA-u-ca-islamic-nu-arab"

检测用户偏好

浏览器 API 提供了可解析的 locale 字符串,用于了解用户偏好:

function getUserPreferences() {
  const userLocale = new Intl.Locale(navigator.language);
  const hourCycles = userLocale.getHourCycles();
  const calendars = userLocale.getCalendars();
  const textInfo = userLocale.getTextInfo();

  return {
    language: userLocale.language,
    region: userLocale.region,
    preferredHourCycle: hourCycles[0],
    preferredCalendar: calendars[0],
    textDirection: textInfo.direction
  };
}

const preferences = getUserPreferences();
console.log(preferences);
// { language: "en", region: "US", preferredHourCycle: "h12", ... }

这会根据用户的浏览器设置创建其偏好配置文件。

浏览器支持

Intl.Locale API 在所有现代浏览器中均可用。Chrome、Firefox、Safari 和 Edge 完全支持本指南中描述的构造函数、属性和方法。

getCalendars()getCollations()getHourCycles()getNumberingSystems()getTextInfo()getTimeZones()getWeekInfo() 这样的新方法需要较新的浏览器版本。如果需要支持旧版浏览器,请在使用这些方法前查阅浏览器兼容性表。

Node.js 从 12 版本开始支持 Intl.Locale API,18 及以上版本支持全部方法。

总结

Intl.Locale API 可将 locale 标识符字符串转换为可供检查和操作的结构化对象。无需手动解析字符串,只需创建 locale 对象并读取其属性。

核心概念:

  • locale 标识符包含语言、脚本、区域和扩展部分
  • Intl.Locale 构造函数会验证标识符并创建对象
  • languageregioncalendar 等属性可结构化访问
  • getCalendars()getHourCycles() 等方法返回可用选项
  • maximize()minimize() 方法可规范化标识符
  • locale 对象可直接与其他 Intl API 协同工作

在构建 locale 选择器、验证用户输入、实现回退链或配置格式化器时,建议使用 Intl.Locale API。它为 JavaScript 应用程序中处理 locale 标识符提供了标准化方式。