如何检查 locale 标识符是否有效

在使用 locale 标识符格式化日期、数字和货币前先进行验证

简介

当应用程序从用户输入、API 响应或配置文件中接收 locale 标识符时,必须在使用前验证其有效性。无效的 locale 标识符会导致格式化函数抛出错误或产生意外结果。

验证可以确保 en-US 这样的字符串在结构上符合国际标准。这可以防止在将 locale 标识符传递给 Intl API 或其他国际化库时出现运行时错误。

JavaScript 提供了两种内置方法来验证 locale 标识符。这两种方法都会根据 BCP 47 标准检查标识符的结构,该标准定义了语言标签的格式。

什么是有效的 locale 标识符

locale 标识符遵循 BCP 47 语言标签标准。该标准定义了组合语言、脚本、地区和扩展组件的结构和规则。

有效的 locale 标识符使用连字符分隔各个部分,不能使用下划线或其他字符。语言代码必须是公认的 ISO 639 代码,地区代码必须是公认的 ISO 3166-1 代码。

有效 locale 标识符示例:

  • en(英语)
  • en-US(美式英语)
  • zh-Hans(简体中文)
  • zh-Hans-CN(中国大陆使用的简体中文)
  • en-US-u-ca-gregory(带有公历的美式英语)

无效 locale 标识符示例:

  • en_US(使用下划线而不是连字符)
  • english(不是公认的语言代码)
  • en-USA(地区代码必须为两个字母,不能是三个)
  • EN-us(语言代码必须为小写)
  • abc-XY(语言代码不存在)

验证方法会检查这些结构规则,并确认代码是否为公认标准。

使用 Intl.getCanonicalLocales 进行验证

Intl.getCanonicalLocales() 方法用于验证 locale 标识符,并以规范形式返回。该方法可接受单个 locale 标识符或标识符数组。

const result = Intl.getCanonicalLocales("en-US");
console.log(result);
// Output: ["en-US"]

该方法会将输入标准化,转换为标准格式。即使传入大小写不正确的 locale 标识符,也会返回正确的规范形式:

const result = Intl.getCanonicalLocales("EN-us");
console.log(result);
// Output: ["en-US"]

如果传入无效的 locale 标识符,该方法会抛出 RangeError

try {
  Intl.getCanonicalLocales("en_US");
} catch (error) {
  console.error(error.name);
  // Output: "RangeError"
  console.error(error.message);
  // Output: "en_US is not a structurally valid language tag"
}

该错误表示标识符不符合 BCP 47 结构。你可以捕获此错误,以便优雅地处理无效输入。

验证多个 locale 标识符

Intl.getCanonicalLocales() 方法可接受一个数组,一次性验证多个 locale 标识符。该方法会为所有有效标识符返回规范形式的数组。

const locales = ["en-US", "fr-FR", "es-MX"];
const result = Intl.getCanonicalLocales(locales);
console.log(result);
// Output: ["en-US", "fr-FR", "es-MX"]

该方法还会从结果中移除重复的 locale 标识符:

const locales = ["en-US", "EN-us", "en-US"];
const result = Intl.getCanonicalLocales(locales);
console.log(result);
// Output: ["en-US"]

这三个输入值都表示同一个 locale,因此方法只返回一个规范形式。

如果数组中有任何无效的 locale,整个方法会抛出错误:

try {
  Intl.getCanonicalLocales(["en-US", "invalid", "fr-FR"]);
} catch (error) {
  console.error(error.message);
  // Output: "invalid is not a structurally valid language tag"
}

在验证用户提供的列表时,应单独验证每个 locale,以便识别具体哪些标识符无效。

使用 Intl.Locale 构造函数进行验证

Intl.Locale 构造函数提供了另一种验证 locale 标识符的方法。当你用无效标识符创建 locale 对象时,构造函数会抛出 RangeError

try {
  const locale = new Intl.Locale("en-US");
  console.log("Valid locale");
} catch (error) {
  console.error("Invalid locale");
}
// Output: "Valid locale"

构造函数会针对无效的标识符抛出错误:

try {
  const locale = new Intl.Locale("en_US");
  console.log("Valid locale");
} catch (error) {
  console.error("Invalid locale");
  console.error(error.message);
}
// Output: "Invalid locale"
// Output: "invalid language subtag: en_US"

Intl.getCanonicalLocales() 不同,Intl.Locale 构造函数不会自动规范大小写。它要求标识符使用正确的大小写:

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

const locale2 = new Intl.Locale("EN-US");
console.log(locale2.baseName);
// Output: "en-US"

构造函数同时接受大写和小写的语言代码,并会将其规范化为标准形式。

选择验证方法

两种验证方法各自适用于不同场景,并提供不同的功能。

当你需要以下操作时,使用 Intl.getCanonicalLocales()

  • 将 locale 标识符规范化为标准形式
  • 验证并去重 locale 标识符列表
  • 将不一致的大小写转换为标准格式
  • 在不创建对象的情况下验证 locale 标识符

当你需要以下操作时,使用 Intl.Locale 构造函数:

  • 验证 locale 标识符并直接操作其属性
  • 从标识符中提取语言、地区或脚本等组件
  • 创建 locale 对象以配合其他 Intl API 使用
  • 同时验证和处理 locale 标识符

Intl.Locale 构造函数更为强大,因为它可以创建可查询和修改的对象。但如果你只需要验证和规范化,Intl.getCanonicalLocales() 更为简单。

验证用户输入

当接收用户输入的 locale 标识符时,务必在应用中使用前进行验证。这可以防止错误,并在用户输入无效值时提供明确的反馈。

function validateUserLocale(input) {
  try {
    const canonicalLocales = Intl.getCanonicalLocales(input);
    return {
      valid: true,
      locale: canonicalLocales[0]
    };
  } catch (error) {
    return {
      valid: false,
      error: "Please enter a valid locale identifier like en-US or fr-FR"
    };
  }
}

const result1 = validateUserLocale("en-US");
console.log(result1);
// Output: { valid: true, locale: "en-US" }

const result2 = validateUserLocale("english");
console.log(result2);
// Output: { valid: false, error: "Please enter a valid locale..." }

此函数会验证输入,并返回规范化的 locale 或用户友好的错误信息。

验证配置数据

应用程序通常会从配置文件或环境变量加载 locale 标识符。请在应用启动时验证这些值,以便及早发现配置错误。

function loadLocaleConfig(configLocales) {
  const validLocales = [];
  const invalidLocales = [];

  for (const locale of configLocales) {
    try {
      const canonical = Intl.getCanonicalLocales(locale);
      validLocales.push(canonical[0]);
    } catch (error) {
      invalidLocales.push(locale);
    }
  }

  if (invalidLocales.length > 0) {
    console.warn("Invalid locale identifiers found:", invalidLocales);
  }

  return validLocales;
}

const config = ["en-US", "fr-FR", "invalid", "es_MX", "de-DE"];
const locales = loadLocaleConfig(config);
console.log(locales);
// Output: ["en-US", "fr-FR", "de-DE"]
// Warning: Invalid locale identifiers found: ["invalid", "es_MX"]

此函数会过滤掉无效的 locale 标识符并记录警告,从而使应用程序能够继续使用有效的 locale。

验证 API 响应

当从外部 API 接收 locale 标识符时,需在应用中使用前进行验证。API 可能会返回非标准格式或包含错误的 locale 标识符。

function processApiLocale(apiResponse) {
  const locale = apiResponse.preferredLocale;

  try {
    const validated = new Intl.Locale(locale);
    return {
      success: true,
      language: validated.language,
      region: validated.region,
      baseName: validated.baseName
    };
  } catch (error) {
    console.error("API returned invalid locale:", locale);
    return {
      success: false,
      fallback: "en-US"
    };
  }
}

const response1 = { preferredLocale: "fr-CA" };
console.log(processApiLocale(response1));
// Output: { success: true, language: "fr", region: "CA", baseName: "fr-CA" }

const response2 = { preferredLocale: "invalid_locale" };
console.log(processApiLocale(response2));
// Output: { success: false, fallback: "en-US" }

此函数会验证 API 响应并提取有关 locale 的结构化信息,或者在 locale 无效时提供回退值。