如何通过组件构建本地化标识符

在 JavaScript 中通过组合语言、书写系统和地区代码来构建本地化标识符

简介

en-USzh-Hans-CN 这样的本地化标识符编码了语言、书写系统和地区等信息。有时你需要以编程方式构建这些标识符,而不是使用固定字符串。例如,你可能让用户分别选择语言和地区,然后将它们组合成一个有效的本地化标识符。

JavaScript 的 Intl.Locale 构造函数允许你从各个组件构建本地化标识符。你可以分别指定语言、书写系统和地区,构造函数会将它们组装成格式正确的标识符。

本指南将解释如何通过组件构建本地化标识符、何时使用这种方法,以及如何处理特殊情况。

了解本地化标识符的组成部分

本地化标识符由连字符分隔的各个组件组成。每个组件代表文化偏好的不同方面。

语言代码指定要使用的语言。它采用 ISO 639 的两个或三个小写字母:

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

书写系统代码指定书写体系。它采用 ISO 15924 的四个字母,首字母大写:

  • Hans 表示简体中文字符
  • Hant 表示繁体中文字符
  • Cyrl 表示西里尔字母
  • Latn 表示拉丁字母

地区代码指定地理区域。它采用 ISO 3166-1 的两个大写字母或 UN M.49 的三位数字:

  • US 表示美国
  • GB 表示英国
  • CN 表示中国
  • MX 表示墨西哥

这些组件按照特定顺序组合:先语言,再脚本,最后地区。例如,zh-Hans-CN 表示中文语言、简体脚本、中国地区。

仅使用语言和地区构建 locale

最常见的场景是组合语言代码和地区代码。大多数应用无需指定脚本,因为每种语言都有默认脚本。

你可以通过将语言代码作为第一个参数,并传递包含地区的 options 对象来构建 locale:

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

console.log(locale.toString());
// Output: "en-US"

这将创建一个美式英语的 locale 标识符。

你可以构建同一语言的不同地区变体:

const usEnglish = new Intl.Locale("en", { region: "US" });
const britishEnglish = new Intl.Locale("en", { region: "GB" });
const canadianEnglish = new Intl.Locale("en", { region: "CA" });

console.log(usEnglish.toString()); // "en-US"
console.log(britishEnglish.toString()); // "en-GB"
console.log(canadianEnglish.toString()); // "en-CA"

每个变体使用相同的语言,但采用不同的地区格式规范。

使用语言、脚本和地区构建 locale

有些语言需要显式指定脚本代码。中文、塞尔维亚语等少数语言使用多种书写系统。你必须指定脚本以避免歧义。

你可以在 options 对象中添加脚本组件:

const simplifiedChinese = new Intl.Locale("zh", {
  script: "Hans",
  region: "CN"
});

console.log(simplifiedChinese.toString());
// Output: "zh-Hans-CN"

这将创建一个在中国使用的简体中文 locale。

繁体中文使用不同的脚本和地区:

const traditionalChinese = new Intl.Locale("zh", {
  script: "Hant",
  region: "TW"
});

console.log(traditionalChinese.toString());
// Output: "zh-Hant-TW"

脚本代码用于区分两种书写系统。

塞尔维亚语同时使用西里尔字母和拉丁字母。你需要指定使用哪种脚本:

const serbianCyrillic = new Intl.Locale("sr", {
  script: "Cyrl",
  region: "RS"
});

const serbianLatin = new Intl.Locale("sr", {
  script: "Latn",
  region: "RS"
});

console.log(serbianCyrillic.toString()); // "sr-Cyrl-RS"
console.log(serbianLatin.toString()); // "sr-Latn-RS"

这两个 locale 都表示塞尔维亚语(地区为塞尔维亚),但书写系统不同。

根据用户选择构建 locale

用户界面通常允许用户分别选择语言和地区。你可以将这些选择组合成一个 locale 标识符。

考虑一个包含两个下拉菜单的设置表单:

function buildLocaleFromSelections(languageCode, regionCode) {
  const locale = new Intl.Locale(languageCode, {
    region: regionCode
  });

  return locale.toString();
}

const userLocale = buildLocaleFromSelections("es", "MX");
console.log(userLocale);
// Output: "es-MX"

这样可以通过独立的选择创建一个 locale 标识符。

你可以通过捕获构造函数抛出的错误来校验这些选择:

function buildLocaleFromSelections(languageCode, regionCode) {
  try {
    const locale = new Intl.Locale(languageCode, {
      region: regionCode
    });
    return {
      success: true,
      locale: locale.toString()
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
}

const valid = buildLocaleFromSelections("fr", "CA");
console.log(valid);
// Output: { success: true, locale: "fr-CA" }

const invalid = buildLocaleFromSelections("invalid", "XX");
console.log(invalid);
// Output: { success: false, error: "..." }

如果某个组件无效,构造函数会抛出 RangeError

使用可选组件构建 locale

并非每个 locale 都需要所有组件。你可以省略不需要的部分。

仅包含语言的 locale 会省略地区和书写系统:

const locale = new Intl.Locale("fr");
console.log(locale.toString());
// Output: "fr"

这表示法语,但没有指定具体的地区或书写系统。

你可以根据用户输入有条件地包含组件:

function buildLocale(language, options = {}) {
  const localeOptions = {};

  if (options.region) {
    localeOptions.region = options.region;
  }

  if (options.script) {
    localeOptions.script = options.script;
  }

  const locale = new Intl.Locale(language, localeOptions);
  return locale.toString();
}

console.log(buildLocale("en"));
// Output: "en"

console.log(buildLocale("en", { region: "US" }));
// Output: "en-US"

console.log(buildLocale("zh", { script: "Hans", region: "CN" }));
// Output: "zh-Hans-CN"

该函数会根据可用信息构建最简有效的 locale 标识符。

覆盖已有 locale 的组件

你可以基于现有的 locale 标识符覆盖特定组件。当你只需更改某一部分而保留其他部分时,这非常有用。

构造函数的第二个参数会覆盖第一个参数中的组件:

const baseLocale = new Intl.Locale("en-US");
const withDifferentRegion = new Intl.Locale(baseLocale, {
  region: "GB"
});

console.log(withDifferentRegion.toString());
// Output: "en-GB"

新 locale 保留了语言,但更改了地区。

你可以同时覆盖多个组件:

const original = new Intl.Locale("zh-Hans-CN");
const modified = new Intl.Locale(original, {
  script: "Hant",
  region: "TW"
});

console.log(modified.toString());
// Output: "zh-Hant-TW"

这会同时更改书写系统和地区,同时保留语言。

为构建的 locale 添加格式化偏好

除了语言、书写系统和地区,locale 还可以包含格式化偏好。这些偏好控制日期、数字等值的显示方式。

在构建 locale 时,可以添加日历偏好设置:

const locale = new Intl.Locale("ar", {
  region: "SA",
  calendar: "islamic"
});

console.log(locale.toString());
// Output: "ar-SA-u-ca-islamic"

console.log(locale.calendar);
// Output: "islamic"

日历偏好会作为 Unicode 扩展出现在标识符字符串中。

你可以指定多个格式化偏好:

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

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

构造函数会按字母顺序排列扩展键。

这些偏好会影响格式化器的数据展示方式:

const locale = new Intl.Locale("ar", {
  region: "EG",
  numberingSystem: "arab"
});

const formatter = new Intl.NumberFormat(locale);
console.log(formatter.format(12345));
// Output: "١٢٬٣٤٥" (Arabic-Indic numerals)

数字系统偏好控制显示的数字样式。

验证组件组合

并非所有语言、书写系统和地区的组合都是有意义的。构造函数接受任何语法上有效的组件,但有些组合可能并不代表真实的 locale。

构造函数只验证语法正确性,不验证语义正确性:

// Syntactically valid but semantically questionable
const locale = new Intl.Locale("en", {
  script: "Arab",
  region: "JP"
});

console.log(locale.toString());
// Output: "en-Arab-JP"

这会构建一个在日本使用阿拉伯文书写的英语 locale。该标识符符合 BCP 47 标准,但并不代表真实世界中的 locale。

你可以使用 maximize() 方法检查 locale 是否符合常见模式:

const locale = new Intl.Locale("en", { region: "JP" });
const maximized = locale.maximize();

console.log(maximized.toString());
// Output: "en-Latn-JP"

该方法会为语言添加最可能的书写系统。如果结果符合预期模式,则该组合是合理的。

从已构建的 locale 读取组件

构建 locale 后,可以通过属性读取其各个组件。

language 属性返回语言代码:

const locale = new Intl.Locale("fr", { region: "CA" });
console.log(locale.language);
// Output: "fr"

region 属性返回地区代码:

const locale = new Intl.Locale("fr", { region: "CA" });
console.log(locale.region);
// Output: "CA"

如果指定,script 属性返回书写系统代码:

const locale = new Intl.Locale("zh", {
  script: "Hans",
  region: "CN"
});

console.log(locale.script);
// Output: "Hans"

如果未指定 script,该属性将返回 undefined

const locale = new Intl.Locale("en", { region: "US" });
console.log(locale.script);
// Output: undefined

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

const locale = new Intl.Locale("ar", {
  region: "SA",
  calendar: "islamic",
  numberingSystem: "arab"
});

console.log(locale.baseName);
// Output: "ar-SA"

这样可以获得不含格式偏好的语言-脚本-区域部分。

将 locale 标识符转换为字符串

toString() 方法会以字符串形式返回完整的 locale 标识符:

const locale = new Intl.Locale("es", { region: "MX" });
const identifier = locale.toString();

console.log(identifier);
// Output: "es-MX"

你可以将该字符串与其他 Intl API 一起使用:

const locale = new Intl.Locale("de", { region: "DE" });
const formatter = new Intl.NumberFormat(locale.toString());

const price = 1234.56;
console.log(formatter.format(price));
// Output: "1.234,56"

格式化器可以接受字符串表示形式。

大多数 Intl API 也可以直接接受 locale 对象:

const locale = new Intl.Locale("de", { region: "DE" });
const formatter = new Intl.NumberFormat(locale);

在需要时,API 会在内部调用 toString()

实际应用场景

通过组件构建 locale 标识符可以解决国际化应用中的多个常见问题。

创建 locale 选择器

用户界面通常允许用户分别选择语言和地区。你可以将这些选择组合起来:

function createLocaleFromPicker(languageSelect, regionSelect) {
  const language = languageSelect.value;
  const region = regionSelect.value;

  const locale = new Intl.Locale(language, { region });
  return locale.toString();
}

// User selects "Spanish" and "Mexico"
const selectedLocale = createLocaleFromPicker(
  { value: "es" },
  { value: "MX" }
);

console.log(selectedLocale);
// Output: "es-MX"

生成 locale 变体

你可以通过单一语言代码生成多个地区变体:

function generateRegionalVariants(languageCode, regionCodes) {
  return regionCodes.map(regionCode => {
    const locale = new Intl.Locale(languageCode, {
      region: regionCode
    });
    return locale.toString();
  });
}

const englishVariants = generateRegionalVariants("en", [
  "US",
  "GB",
  "CA",
  "AU",
  "NZ"
]);

console.log(englishVariants);
// Output: ["en-US", "en-GB", "en-CA", "en-AU", "en-NZ"]

这会为不同的英语地区创建 locale 标识符列表。

通过 URL 参数构建 locale

URL 通常将 locale 偏好作为独立参数进行编码。你可以通过这些参数构建 locale:

function getLocaleFromURL(url) {
  const params = new URL(url).searchParams;
  const language = params.get("lang");
  const region = params.get("region");

  if (!language) {
    return null;
  }

  const options = {};
  if (region) {
    options.region = region;
  }

  try {
    const locale = new Intl.Locale(language, options);
    return locale.toString();
  } catch (error) {
    return null;
  }
}

const locale1 = getLocaleFromURL("https://example.com?lang=fr&region=CA");
console.log(locale1);
// Output: "fr-CA"

const locale2 = getLocaleFromURL("https://example.com?lang=ja");
console.log(locale2);
// Output: "ja"

规范化 locale 标识符

你可以通过解析并重构 locale 标识符来实现规范化:

function normalizeLocale(identifier) {
  try {
    const locale = new Intl.Locale(identifier);
    return locale.toString();
  } catch (error) {
    return null;
  }
}

console.log(normalizeLocale("EN-us"));
// Output: "en-US"

console.log(normalizeLocale("zh_Hans_CN"));
// Output: null (invalid separator)

构造函数会规范化大小写并验证结构。

按用户偏好配置格式化器

你可以根据用户设置,结合格式化偏好来构建 locale 标识符:

function buildFormatterLocale(language, region, preferences) {
  const locale = new Intl.Locale(language, {
    region,
    hourCycle: preferences.use24Hour ? "h23" : "h12",
    numberingSystem: preferences.numberingSystem
  });

  return locale;
}

const userPreferences = {
  use24Hour: true,
  numberingSystem: "latn"
};

const locale = buildFormatterLocale("fr", "FR", userPreferences);

const timeFormatter = new Intl.DateTimeFormat(locale, {
  hour: "numeric",
  minute: "numeric"
});

const now = new Date("2025-10-15T14:30:00");
console.log(timeFormatter.format(now));
// Output: "14:30" (24-hour format)

locale 包含了来自用户设置的格式化偏好。

何时从组件构建 locale

在特定场景下,从组件构建 locale 非常有用。当你拥有独立的语言和地区数据、处理用户输入或以编程方式生成 locale 变体时,建议采用此方法。

对于固定的 locale,请使用字面量字符串:

// Good for fixed locales
const locale = new Intl.Locale("en-US");

当值来自变量时,从组件构建:

// Good for dynamic locales
const locale = new Intl.Locale(userLanguage, {
  region: userRegion
});

构造函数会验证组件并创建格式正确的标识符。

浏览器支持

Intl.Locale 构造函数在所有现代浏览器中均可用。Chrome、Firefox、Safari 和 Edge 均支持该构造函数及其用于从组件构建 locale 的 options 对象。

Node.js 从 12 版本开始支持 Intl.Locale,14 及更高版本对所有构造函数选项提供完整支持。

总结

Intl.Locale 构造函数可通过各个组件构建 locale 标识符。你需要将语言代码作为第一个参数,并在 options 对象中提供脚本、地区和格式化偏好。

关键概念:

  • locale 标识符由语言、脚本和地区组件组成
  • 构造函数接受包含 languagescriptregion 属性的 options 对象
  • 你可以通过将现有 locale 作为第一个参数来覆盖组件
  • calendarhourCycle 等格式化偏好会以 Unicode 扩展形式出现
  • toString() 方法返回完整的标识符字符串
  • languageregionscript 等属性可用于读取组件
  • 构造函数只验证语法,不验证语义正确性

当需要根据用户输入构建 locale、生成区域变体,或组合单独的语言和地区选择时,请使用此方法。对于固定的 locale,建议直接使用字符串字面量。