如何检查某个语言环境使用的日历或编号系统

使用 JavaScript 检测并验证任何语言环境的日历系统和编号格式

介绍

当泰国用户在您的网络应用中查看日期时,他们期望看到的是佛历日期,而不是西方国家使用的公历日期。同样,阿拉伯语使用者期望看到的数字是 ١٢٣ 而不是 123。不同的文化使用不同的日历系统和数字系统,JavaScript 提供了工具来检测特定语言环境适用的系统。

Intl.Locale API 包含揭示某个语言环境使用的日历和数字系统的属性和方法。这些信息可以帮助您正确格式化日期和数字,而无需对不同文化偏好的系统进行硬编码假设。

本指南将解释如何检查语言环境的日历和数字系统,理解不同系统的含义,并使用这些信息来适当地格式化内容。

理解日历系统

日历系统是一种将时间组织为年、月和日的方式。尽管公历广泛使用,但许多文化因宗教、历史或文化原因使用不同的日历系统。

最常见的日历系统包括:

  • gregory 表示公历,广泛用于大多数西方国家
  • buddhist 表示佛历,使用于泰国、柬埔寨和缅甸
  • islamic 表示伊斯兰阴历,用于穆斯林国家的宗教目的
  • hebrew 表示希伯来历,用于以色列和犹太教宗教活动
  • japanese 表示日本历,使用年号纪年
  • chinese 表示中国农历,用于传统节日
  • persian 表示波斯太阳历,用于伊朗和阿富汗
  • indian 表示印度国家历
  • coptic 表示科普特历,用于埃及的科普特基督徒

不同地区默认使用不同的日历,有些地区通常使用多种日历系统。

了解数字系统

数字系统是一组用于表示数字的符号。西方国家使用拉丁数字(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),而其他文化使用不同的符号来表示相同的数值。

常见的数字系统包括:

  • latn 表示拉丁数字:0 1 2 3 4 5 6 7 8 9
  • arab 表示阿拉伯-印度数字:٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩
  • arabext 表示东阿拉伯-印度数字:۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹
  • deva 表示天城文数字:० १ २ ३ ४ ५ ६ ७ ८ ९
  • beng 表示孟加拉数字:০ ১ ২ ৩ ৪ ৫ ৬ ৭ ৮ ৯
  • thai 表示泰国数字:๐ ๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙
  • hanidec 表示中文十进制数字
  • fullwide 表示东亚排版中使用的全角数字

所有数字系统表示相同的数值,但根据语言的书写系统使用不同的视觉符号。

检查某个语言环境的默认日历

getCalendars() 方法返回一个数组,其中包含某个语言环境中常用的日历系统,并按优先级排序。第一个元素是默认日历。

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

泰语语言环境默认使用佛教日历,但也常用公历。这表明在为泰国用户格式化日期时,优先使用佛教日历。

不同的语言环境返回不同的日历偏好:

const usLocale = new Intl.Locale("en-US");
console.log(usLocale.getCalendars());
// ["gregory"]

const saLocale = new Intl.Locale("ar-SA");
console.log(saLocale.getCalendars());
// ["gregory", "islamic", "islamic-civil"]

const jpLocale = new Intl.Locale("ja-JP");
console.log(jpLocale.getCalendars());
// ["gregory", "japanese"]

美式英语仅使用公历。沙特阿拉伯的阿拉伯语通常同时使用公历和伊斯兰历。日语语言环境同时使用公历和日本年号历。

数组包含该语言环境中常用的所有日历,允许在适当时提供多种日历选项。

检查某个语言环境的默认编号系统

getNumberingSystems() 方法返回一个数组,列出了某个语言环境中常用的编号系统。数组的第一个元素是默认的编号系统。

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

埃及阿拉伯语默认使用阿拉伯-印度数字。在为埃及阿拉伯语用户格式化数字时,除非用户另有指定,否则应使用阿拉伯-印度编号系统。

不同的语言环境使用不同的默认编号系统:

const usLocale = new Intl.Locale("en-US");
console.log(usLocale.getNumberingSystems());
// ["latn"]

const inLocale = new Intl.Locale("hi-IN");
console.log(inLocale.getNumberingSystems());
// ["latn"]

const thLocale = new Intl.Locale("th-TH");
console.log(thLocale.getNumberingSystems());
// ["latn"]

美式英语使用拉丁数字。印度的印地语在现代语境中也默认使用拉丁数字,尽管存在天城文数字。泰语在大多数现代语境中也默认使用拉丁数字。

许多现代语言环境即使存在传统编号系统,也默认使用拉丁数字,这反映了当前的使用模式。

从语言环境中读取活动日历

calendar 属性返回为某个语言环境显式设置的日历系统。如果未指定日历,则返回 undefined

const locale = new Intl.Locale("en-US");
console.log(locale.calendar);
// undefined

一个没有日历扩展的基本语言环境会返回 undefined。在格式化日期时,该语言环境将使用其默认日历。

您可以使用 Unicode 扩展创建具有显式日历偏好的语言环境:

const locale = new Intl.Locale("en-US-u-ca-buddhist");
console.log(locale.calendar);
// "buddhist"

-u-ca-buddhist 扩展指定了佛教日历。calendar 属性返回 "buddhist"

您还可以在创建语言环境时设置日历:

const locale = new Intl.Locale("en-US", { calendar: "islamic" });
console.log(locale.calendar);
// "islamic"

选项对象的优先级高于语言环境字符串中指定的任何日历。

从区域设置中读取活动的数字系统

numberingSystem 属性返回为某个区域设置显式设置的数字系统。如果未指定数字系统,则返回 undefined

const locale = new Intl.Locale("en-US");
console.log(locale.numberingSystem);
// undefined

一个没有数字系统扩展的基本区域设置会返回 undefined。在格式化数字时,该区域设置将使用其默认的数字系统。

您可以创建具有显式数字系统偏好的区域设置:

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

-u-nu-arab 扩展指定了阿拉伯-印度数字。numberingSystem 属性返回 "arab"

您还可以在创建区域设置时设置数字系统:

const locale = new Intl.Locale("ar-SA", { numberingSystem: "latn" });
console.log(locale.numberingSystem);
// "latn"

这会创建一个使用拉丁数字而不是默认阿拉伯-印度数字的阿拉伯区域设置。

检测区域设置是否具有显式日历

要检查区域设置是否具有显式设置的日历而不是使用其默认日历,请检查 calendar 属性是否已定义:

function hasExplicitCalendar(localeString) {
  const locale = new Intl.Locale(localeString);
  return locale.calendar !== undefined;
}

console.log(hasExplicitCalendar("en-US"));
// false

console.log(hasExplicitCalendar("en-US-u-ca-buddhist"));
// true

当您需要确定用户是否显式选择了日历偏好,或者是否应该使用区域设置的默认日历时,这种区分很重要。

检测区域设置是否具有显式数字系统

类似地,检查 numberingSystem 属性是否已定义,以检测显式的数字系统偏好:

function hasExplicitNumberingSystem(localeString) {
  const locale = new Intl.Locale(localeString);
  return locale.numberingSystem !== undefined;
}

console.log(hasExplicitNumberingSystem("ar-EG"));
// false

console.log(hasExplicitNumberingSystem("ar-EG-u-nu-latn"));
// true

第一个区域设置将使用埃及阿拉伯语的默认数字系统。第二个区域设置显式请求拉丁数字。

使用日历信息格式化日期

一旦知道某个语言环境使用的日历,可以在使用 Intl.DateTimeFormat 格式化日期时应用它:

const date = new Date("2025-03-15");

const gregorianLocale = new Intl.Locale("en-US");
const gregorianFormatter = new Intl.DateTimeFormat(gregorianLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(gregorianFormatter.format(date));
// "2025年3月15日"

const buddhistLocale = new Intl.Locale("th-TH");
const buddhistFormatter = new Intl.DateTimeFormat(buddhistLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(buddhistFormatter.format(date));
// "2568年3月15日"

泰国佛教日历显示同一日期为 2568 年,比公历早 543 年。

您还可以显式覆盖日历:

const date = new Date("2025-03-15");

const locale = new Intl.Locale("en-US", { calendar: "hebrew" });
const formatter = new Intl.DateTimeFormat(locale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});
console.log(formatter.format(date));
// "5785年阿达尔二月15日"

这会以希伯来日历格式化日期,显示相应的希伯来年份和月份。

使用数字系统信息格式化数字

在使用 Intl.NumberFormat 格式化数字时应用数字系统信息:

const number = 123456;

const latinLocale = new Intl.Locale("ar-EG", { numberingSystem: "latn" });
const latinFormatter = new Intl.NumberFormat(latinLocale);
console.log(latinFormatter.format(number));
// "123,456"

const arabicLocale = new Intl.Locale("ar-EG", { numberingSystem: "arab" });
const arabicFormatter = new Intl.NumberFormat(arabicLocale);
console.log(arabicFormatter.format(number));
// "١٢٣٬٤٥٦"

同一个数字会根据数字系统使用不同的数字符号呈现。

构建日历选择器

使用日历信息构建用户界面,让用户选择他们偏好的日历:

function getCalendarOptions(localeString) {
  const locale = new Intl.Locale(localeString);
  const calendars = locale.getCalendars();

  return calendars.map(calendar => ({
    value: calendar,
    label: calendar.charAt(0).toUpperCase() + calendar.slice(1)
  }));
}

const options = getCalendarOptions("ar-SA");
console.log(options);
// [
//   { value: "gregory", label: "Gregory" },
//   { value: "islamic", label: "Islamic" },
//   { value: "islamic-civil", label: "Islamic-civil" }
// ]

这会为沙特阿拉伯阿拉伯语用户创建一个适当的日历选项列表,他们通常使用多种日历。

构建一个数字系统选择器

为数字系统偏好创建用户界面:

function getNumberingSystemOptions(localeString) {
  const locale = new Intl.Locale(localeString);
  const systems = locale.getNumberingSystems();

  const labels = {
    latn: "西方数字 (0-9)",
    arab: "阿拉伯-印度数字 (٠-٩)",
    arabext: "东阿拉伯数字 (۰-۹)",
    deva: "天城文数字 (०-९)",
    beng: "孟加拉数字 (০-৯)"
  };

  return systems.map(system => ({
    value: system,
    label: labels[system] || system
  }));
}

const options = getNumberingSystemOptions("ar-EG");
console.log(options);
// [{ value: "arab", label: "阿拉伯-印度数字 (٠-٩)" }]

这为数字系统选项提供了清晰的标签,向用户展示每个选项的外观。

验证日历兼容性

在将日历应用于某个语言环境之前,验证该语言环境是否支持该日历:

function supportsCalendar(localeString, calendar) {
  const locale = new Intl.Locale(localeString);
  const supportedCalendars = locale.getCalendars();
  return supportedCalendars.includes(calendar);
}

console.log(supportsCalendar("th-TH", "buddhist"));
// true

console.log(supportsCalendar("en-US", "buddhist"));
// false

泰语语言环境支持佛教日历,但美式英语语言环境通常不使用它。

验证数字系统兼容性

检查某个语言环境是否支持特定的数字系统:

function supportsNumberingSystem(localeString, numberingSystem) {
  const locale = new Intl.Locale(localeString);
  const supportedSystems = locale.getNumberingSystems();
  return supportedSystems.includes(numberingSystem);
}

console.log(supportsNumberingSystem("ar-EG", "arab"));
// true

console.log(supportsNumberingSystem("en-US", "arab"));
// false

埃及阿拉伯语支持阿拉伯-印度数字,但美式英语不使用它们。

确定用于格式化的有效日历

在格式化日期时,确定实际使用的日历:

function getEffectiveCalendar(localeString) {
  const locale = new Intl.Locale(localeString);

  if (locale.calendar) {
    return locale.calendar;
  }

  const defaultCalendars = locale.getCalendars();
  return defaultCalendars[0];
}

console.log(getEffectiveCalendar("th-TH"));
// "buddhist"

console.log(getEffectiveCalendar("en-US-u-ca-islamic"));
// "islamic"

此函数返回显式设置的日历(如果存在),否则返回该语言环境的默认日历。

确定用于格式化的有效编号系统

确定在格式化数字时将使用哪个编号系统:

function getEffectiveNumberingSystem(localeString) {
  const locale = new Intl.Locale(localeString);

  if (locale.numberingSystem) {
    return locale.numberingSystem;
  }

  const defaultSystems = locale.getNumberingSystems();
  return defaultSystems[0];
}

console.log(getEffectiveNumberingSystem("ar-EG"));
// "arab"

console.log(getEffectiveNumberingSystem("ar-EG-u-nu-latn"));
// "latn"

如果存在显式设置的编号系统,则返回该系统,否则返回该语言环境的默认编号系统。

存储用户的日历偏好

当用户选择日历偏好时,将其存储为带有扩展的语言环境字符串:

function setUserCalendarPreference(baseLocale, calendar) {
  const locale = new Intl.Locale(baseLocale, { calendar });
  return locale.toString();
}

const preference = setUserCalendarPreference("en-US", "buddhist");
console.log(preference);
// "en-US-u-ca-buddhist"

这会创建一个完整的语言环境字符串,保留日历偏好。将此字符串存储在用户设置或 Cookie 中。

存储用户的编号系统偏好

以相同的方式存储编号系统偏好:

function setUserNumberingPreference(baseLocale, numberingSystem) {
  const locale = new Intl.Locale(baseLocale, { numberingSystem });
  return locale.toString();
}

const preference = setUserNumberingPreference("ar-SA", "latn");
console.log(preference);
// "ar-SA-u-nu-latn"

语言环境字符串包含编号系统偏好,可以直接与格式化 API 一起使用。

同时处理多个偏好

用户可以同时拥有日历和编号系统偏好:

function createLocaleWithPreferences(baseLocale, options) {
  const locale = new Intl.Locale(baseLocale, {
    calendar: options.calendar,
    numberingSystem: options.numberingSystem
  });
  return locale.toString();
}

const preference = createLocaleWithPreferences("ar-SA", {
  calendar: "islamic",
  numberingSystem: "latn"
});
console.log(preference);
// "ar-SA-u-ca-islamic-nu-latn"

这会将多个格式化偏好合并为一个语言环境字符串。

检查解析后的格式化选项

创建格式化器后,验证它使用了哪个日历和编号系统:

const locale = new Intl.Locale("th-TH");
const formatter = new Intl.DateTimeFormat(locale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const options = formatter.resolvedOptions();
console.log(options.calendar);
// "buddhist"
console.log(options.numberingSystem);
// "latn"

resolvedOptions() 方法显示格式化器在解析默认值和用户偏好后实际使用的日历和编号系统。

浏览器支持

Intl.Locale API 在所有现代浏览器中都受支持。getCalendars()getNumberingSystems() 方法需要较新的浏览器版本。Chrome 99、Firefox 99、Safari 15.4 和 Edge 99 支持这些方法。Node.js 从版本 18 开始支持它们。

calendarnumberingSystem 属性的支持范围更广,自 Intl.Locale 在 Chrome 74、Firefox 75、Safari 14 和 Node.js 12 中引入以来就已可用。

在使用方法之前检查支持情况:

const locale = new Intl.Locale("th-TH");

if (typeof locale.getCalendars === "function") {
  const calendars = locale.getCalendars();
  console.log(calendars);
}

这可以确保您的代码在支持 Intl.Locale 但缺少较新方法的环境中正常运行。

总结

JavaScript 提供了通过 Intl.Locale API 检测某个语言环境使用的日历和数字系统的工具。这些工具可以帮助您为不同文化正确格式化日期和数字,而无需硬编码假设。

关键概念:

  • 使用 getCalendars() 获取某个语言环境常用日历的数组
  • 使用 getNumberingSystems() 获取某个语言环境常用数字系统的数组
  • calendar 属性返回显式设置的日历或 undefined
  • numberingSystem 属性返回显式设置的数字系统或 undefined
  • 不同的语言环境默认使用不同的日历和数字系统
  • 在创建格式化器时应用日历和数字系统信息
  • 将用户偏好存储为带有 Unicode 扩展的语言环境字符串
  • 验证语言环境是否支持特定的日历和数字系统后再应用它们

在构建语言环境选择器、存储用户偏好、验证格式化选项或创建尊重日期和数字文化习惯的国际化应用程序时,请使用这些方法。