如何移除区域设置标识符中的冗余信息

使用 minimize 方法创建精简且不丢失语义的区域设置标识符

简介

en-Latn-USzh-Hans-CN 这样的区域设置标识符包含多个组件,用于指定语言、书写系统和地区。但并非所有组件都是唯一标识一个 locale 所必需的。有些组件是冗余的,因为它们可以从标识符的其他部分推断出来。

minimize() 方法会移除这些冗余组件,从而生成最短等价的区域设置标识符。这样可以在保留语义的同时,减少存储空间并提升可读性。

理解区域设置标识符中的冗余

当区域设置标识符中明确声明的信息已经被其他组件隐含时,就会出现冗余。冗余的原因在于每种语言通常都有默认的书写系统和地区。

以标识符 en-Latn-US 为例。该标识符包含:

  • 语言:英语(en
  • 书写系统:拉丁(Latn
  • 地区:美国(US

英语只使用拉丁书写系统,并且在未指定地区时,默认地区为美国。因此,书写系统和地区组件是冗余的,因为它们与英语的默认值一致。标识符 en 已经表达了相同的信息。

同样的原则也适用于其他语言。例如,韩语(ko)使用谚文书写系统(Kore),主要在韩国(KR)使用。标识符 ko-Kore-KR 包含了冗余信息,因为仅 ko 就已隐含这些默认值。

minimize 方法的工作原理

minimize() 方法可用于 Intl.Locale 实例。它会分析区域设置标识符,并移除与可能默认值相符的组件。

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

console.log(minimized.baseName);
// Output: "en"

该方法返回一个新的 Intl.Locale 实例,并移除冗余的子标签。它不会修改原始的 locale 对象。

最小化过程遵循 Unicode CLDR 的“移除可能的子标签”算法。该算法利用可能子标签关联数据库,判断哪些组件可以在不丢失信息的前提下被移除。

受 minimize 影响的组件

minimize() 方法仅影响 locale 的核心组件:语言、脚本和地区。它不会移除或修改指定格式偏好的 Unicode 扩展子标签。

const locale = new Intl.Locale("en-Latn-US-u-ca-gregory-nu-latn");
const minimized = locale.minimize();

console.log(minimized.toString());
// Output: "en-u-ca-gregory-nu-latn"

日历(ca-gregory)和数字系统(nu-latn)扩展保持不变。仅冗余的脚本(Latn)和地区(US)组件会被移除。

最小化示例

不同的 locale 标识符根据冗余组件的不同,最小化后长度也不同。

移除脚本和地区

当脚本和地区都与默认值匹配时,两者都会被移除:

const english = new Intl.Locale("en-Latn-US");
console.log(english.minimize().baseName);
// Output: "en"

const korean = new Intl.Locale("ko-Kore-KR");
console.log(korean.minimize().baseName);
// Output: "ko"

const japanese = new Intl.Locale("ja-Jpan-JP");
console.log(japanese.minimize().baseName);
// Output: "ja"

保留非默认地区

当地区与默认值不同,则会在最小化标识符中保留:

const britishEnglish = new Intl.Locale("en-Latn-GB");
console.log(britishEnglish.minimize().baseName);
// Output: "en-GB"

const canadianFrench = new Intl.Locale("fr-Latn-CA");
console.log(canadianFrench.minimize().baseName);
// Output: "fr-CA"

const mexicanSpanish = new Intl.Locale("es-Latn-MX");
console.log(mexicanSpanish.minimize().baseName);
// Output: "es-MX"

脚本组件因与默认值匹配被移除,但地区被保留,因为它指定了该语言的非默认变体。

保留非默认脚本

当脚本与默认值不同,则会在最小化标识符中保留:

const simplifiedChinese = new Intl.Locale("zh-Hans-CN");
console.log(simplifiedChinese.minimize().baseName);
// Output: "zh-Hans"

const traditionalChinese = new Intl.Locale("zh-Hant-TW");
console.log(traditionalChinese.minimize().baseName);
// Output: "zh-Hant"

const serbianCyrillic = new Intl.Locale("sr-Cyrl-RS");
console.log(serbianCyrillic.minimize().baseName);
// Output: "sr-Cyrl"

中文需要脚本组件以区分简体和繁体变体。塞尔维亚语需要脚本组件以区分西里尔和拉丁字母。

已经是最小化的标识符

当 locale 标识符已经是最小化状态时,该方法返回等效的 locale,不做任何更改:

const minimal = new Intl.Locale("fr");
console.log(minimal.minimize().baseName);
// Output: "fr"

与 maximize 的关系

minimize() 方法是 maximize() 的逆操作。maximize() 方法会添加可能的子标签以生成完整标识符,而 minimize() 则移除冗余子标签以生成紧凑标识符。

这些方法构成了一对,可以在完整形式和紧凑形式之间进行双向转换:

const compact = new Intl.Locale("en");
const complete = compact.maximize();
console.log(complete.baseName);
// Output: "en-Latn-US"

const compactAgain = complete.minimize();
console.log(compactAgain.baseName);
// Output: "en"

从紧凑到完整再回到紧凑的往返操作会生成原始形式。

但并非所有区域标识符在往返后都能恢复到完全相同的原始形式。该方法会生成规范的最小化形式,而不是保留原始结构:

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

const minimized = maximized.minimize();
console.log(minimized.baseName);
// Output: "en"

原始标识符 en-US 包含了一个非冗余的区域,但经过最大化和最小化后,变为 en。这是因为美国是 English 的默认区域。

何时使用 minimize

当你需要保持唯一性的紧凑区域标识符时,建议使用 minimize()。多种场景都适合最小化处理。

存储区域偏好

最小化标识符可减少数据库、本地存储或配置文件中的存储空间:

function saveUserLocale(localeString) {
  const locale = new Intl.Locale(localeString);
  const minimized = locale.minimize().toString();

  localStorage.setItem("userLocale", minimized);
}

saveUserLocale("en-Latn-US");
// Stores "en" instead of "en-Latn-US"

这样可以在不丢失信息的情况下减少存储数据量。

创建可读性更高的 URL

最小化标识符可生成更简洁的语言选择 URL:

function createLocalizedURL(path, localeString) {
  const locale = new Intl.Locale(localeString);
  const minimized = locale.minimize().baseName;

  return `/${minimized}${path}`;
}

const url = createLocalizedURL("/products", "en-Latn-US");
console.log(url);
// Output: "/en/products"

URL /en/products/en-Latn-US/products 更易读。

比较区域标识符

最小化有助于判断两个区域标识符是否表示同一地区:

function areLocalesEquivalent(locale1String, locale2String) {
  const locale1 = new Intl.Locale(locale1String).minimize();
  const locale2 = new Intl.Locale(locale2String).minimize();

  return locale1.toString() === locale2.toString();
}

console.log(areLocalesEquivalent("en", "en-Latn-US"));
// Output: true

console.log(areLocalesEquivalent("en-US", "en-Latn-US"));
// Output: true

console.log(areLocalesEquivalent("en-US", "en-GB"));
// Output: false

最小化会生成规范形式,便于直接比较。

规范化用户输入

当接收用户或外部系统的区域标识符时,可将其最小化为标准形式:

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

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

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

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

该函数可接受同一地区的多种形式,并返回一致的表示。

将 minimize 与其他区域操作结合使用

minimize() 方法可与其他 Intl.Locale 功能配合使用,实现灵活的 locale 处理。

修改 locale 属性后进行最小化

从各个组件构建 locale 时,建议进行最小化以移除不必要的部分:

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

const minimized = locale.minimize();
console.log(minimized.baseName);
// Output: "en"

这样可以确保最终的标识符尽可能精简,仅包含输入组件允许的内容。

最小化时保留扩展

在最小化过程中,扩展部分会被保留,这样你可以在精简核心组件的同时保留格式偏好设置:

function createCompactLocaleWithPreferences(language, region, preferences) {
  const locale = new Intl.Locale(language, {
    region: region,
    ...preferences
  });

  return locale.minimize().toString();
}

const localeString = createCompactLocaleWithPreferences("en", "US", {
  hourCycle: "h23",
  calendar: "gregory"
});

console.log(localeString);
// Output: "en-u-ca-gregory-hc-h23"

核心组件会被最小化为 en,但 calendar 和 hour cycle 扩展仍然保留。