Intl.Locale API

使用结构化的 JavaScript 对象解析、操作和查询区域标识符

介绍

在为多语言和多地区构建应用程序时,您会遇到诸如 en-USfr-FRzh-Hans-CN 之类的区域标识符。这些字符串出现在浏览器 API、HTTP 头和用户偏好设置中。它们编码了关于语言、地区、书写系统和格式化偏好的信息。

Intl.Locale API 将这些不透明的字符串转换为结构化的对象,您可以对其进行检查和操作。与其手动解析字符串或猜测 zh-Hans-CN 的含义,不如创建一个区域对象并直接读取其属性。

本指南解释了区域标识符的工作原理、如何使用 Intl.Locale API 处理它们,以及在何种情况下结构化的区域对象可以帮助解决实际问题。

理解区域标识符

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

最常见的结构遵循 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 表示使用拉丁字母的塞尔维亚语

大多数语言环境省略脚本代码,因为语言本身暗示了默认的书写系统。例如,英语默认使用拉丁字母,因此写作 en 而不是 en-Latn

扩展标签

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

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

常见的扩展键:

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

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

创建语言环境对象

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

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

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

您还可以传递一个选项对象来覆盖或添加属性:

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"
}

此验证确保您在将语言环境标识符传递给格式化器之前捕获格式错误的标识符。

读取语言环境属性

语言环境对象公开的属性对应于标识符字符串的组件。所有属性都是只读的。

核心属性

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 提供了一些方法,这些方法返回某个语言环境的可用选项数组。这些方法有助于构建用户界面并验证格式化选择。

可用的日历

getCalendars() 方法返回该语言环境常用的日历系统:

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

第一个元素是默认日历。泰语语言环境默认使用佛教日历,但也使用公历。

可用的排序规则

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 小时制。许多其他语言环境返回 ["h23"] 表示 24 小时制。

可用的数字系统

getNumberingSystems() 方法返回该语言环境常用的数字系统:

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

阿拉伯语环境通常支持阿拉伯-印度数字和拉丁数字。

文本方向

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

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

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

周的惯例

getWeekInfo() 方法返回该语言环境的周结构:

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

周的惯例因地区而异。美国的周从星期日开始,而大多数欧洲地区的周从星期一开始。

时区

getTimeZones() 方法返回该语言环境区域的时区:

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

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

最大化语言环境标识符

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"

简体中文与中国相关联,而繁体中文与台湾相关联。

在规范化用户输入或比较语言环境标识符时使用 maximize()。它将隐式信息显式化。

最小化语言环境标识符

minimize() 方法通过移除冗余的子标签来创建最短的等效标识符。当脚本和地区代码与可能的默认值匹配时,它会将其移除。

当语言环境使用默认的脚本和地区时,minimize() 会移除它们:

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

拉丁脚本和美国地区是英语的默认值,因此可以在不丢失信息的情况下移除它们。

对于使用非默认地区的语言环境,minimize() 会保留地区:

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

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

使用 minimize() 来创建用于存储或 URL 的紧凑标识符,同时保留其含义。

转换为字符串

toString() 方法返回完整的语言环境标识符,包括扩展:

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 或存储为配置数据。

您还可以隐式地将语言环境对象转换为字符串:

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

DateTimeFormat 构造函数直接接受语言环境对象,并在内部调用 toString()

实际使用案例

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

构建语言环境选择器

语言环境选择器允许用户选择他们的语言和地区。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" }
// ]

验证语言环境输入

在接受来自用户输入或配置文件的语言环境标识符时,在使用之前验证它们:

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 对象为 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 标识符提供了标准化的方法。