如何从语言环境中提取语言、国家和脚本

使用 JavaScript 解析语言环境标识符并访问其各个组件

介绍

en-USfr-CAzh-Hans-CN 这样的语言环境标识符在单个字符串中编码了多种信息。这些组件告诉您所使用的语言、语言使用的地区,有时还包括书写系统。

在构建国际化应用程序时,您通常需要提取这些单独的组件。您可能希望仅向用户显示语言名称、按地区对语言环境进行分组,或检查某个语言环境使用的书写系统。与其使用正则表达式手动解析字符串,不如使用 JavaScript 提供的 Intl.Locale API 来可靠地提取组件。

本指南解释了语言环境标识符中存在哪些组件、如何使用 Intl.Locale API 提取它们,以及在实际应用中何时需要使用这些组件。

语言环境标识符中存在哪些组件

语言环境标识符遵循 BCP 47 标准,该标准定义了描述语言和地区变体的结构。完整的语言环境标识符可以包含多个用连字符分隔的组件。

最常见的三个组件是:

  • 语言:所使用的主要语言,例如英语、西班牙语或中文
  • 地区:使用该语言的地理区域,例如美国、加拿大或中国
  • 书写系统:用于表示语言的书写系统,例如拉丁字母、西里尔字母或汉字

一个简单的语言环境标识符仅包含语言代码:

en

大多数语言环境标识符包括语言和地区:

en-US
fr-CA
es-MX

当一种语言可以用多种书写系统书写时,一些语言环境标识符会包括书写系统:

zh-Hans-CN
zh-Hant-TW
sr-Cyrl-RS
sr-Latn-RS

理解这些组件有助于您在语言回退、内容选择和用户界面定制方面做出决策。

使用 Intl.Locale 提取组件

Intl.Locale API 将语言环境标识符字符串转换为结构化对象。一旦创建了一个语言环境对象,就可以通过属性读取其组件。

通过将标识符传递给构造函数来创建语言环境对象:

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

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

语言环境对象公开了与标识符的每个组件对应的属性。这些属性提供了结构化的访问方式,无需解析字符串。

提取语言代码

language 属性返回语言环境标识符的语言组件。这是标识主要语言的两到三位字母代码。

const english = new Intl.Locale("en-US");
console.log(english.language); // "en"

const french = new Intl.Locale("fr-CA");
console.log(french.language); // "fr"

const chinese = new Intl.Locale("zh-Hans-CN");
console.log(chinese.language); // "zh"

语言代码遵循 ISO 639 标准。常见的代码包括 en 表示英语,es 表示西班牙语,fr 表示法语,de 表示德语,ja 表示日语,以及 zh 表示中文。

在有效的语言环境标识符中,语言代码始终存在。它是唯一必需的组件。

const languageOnly = new Intl.Locale("ja");
console.log(languageOnly.language); // "ja"
console.log(languageOnly.region); // undefined

提取语言代码后,可以用它来选择翻译、确定文本处理规则或为用户构建语言选择器。

提取地区代码

region 属性返回语言环境标识符的地区组件。这是标识语言使用地的两位字母代码。

const americanEnglish = new Intl.Locale("en-US");
console.log(americanEnglish.region); // "US"

const britishEnglish = new Intl.Locale("en-GB");
console.log(britishEnglish.region); // "GB"

const canadianFrench = new Intl.Locale("fr-CA");
console.log(canadianFrench.region); // "CA"

地区代码遵循 ISO 3166-1 标准。它们使用两个大写字母表示国家和地区。常见的代码包括 US 表示美国,GB 表示英国,CA 表示加拿大,MX 表示墨西哥,FR 表示法国,以及 CN 表示中国。

地区代码会影响日期、数字和货币的格式。例如,美国英语使用月-日-年日期格式和小数点作为小数分隔符。英国英语使用日-月-年日期格式和逗号作为千位分隔符。

在语言环境标识符中,地区代码是可选的。当语言环境没有地区时,region 属性返回 undefined

const genericSpanish = new Intl.Locale("es");
console.log(genericSpanish.region); // undefined

提取地区代码后,可以用它来自定义区域格式、选择特定地区的内容或向用户显示位置信息。

提取脚本代码

script 属性返回语言环境标识符中的脚本部分。这是一个四个字母的代码,用于标识表示语言的书写系统。

const simplifiedChinese = new Intl.Locale("zh-Hans-CN");
console.log(simplifiedChinese.script); // "Hans"

const traditionalChinese = new Intl.Locale("zh-Hant-TW");
console.log(traditionalChinese.script); // "Hant"

const serbianCyrillic = new Intl.Locale("sr-Cyrl-RS");
console.log(serbianCyrillic.script); // "Cyrl"

const serbianLatin = new Intl.Locale("sr-Latn-RS");
console.log(serbianLatin.script); // "Latn"

脚本代码遵循 ISO 15924 标准。它们由四个字母组成,第一个字母大写。常见的代码包括 Latn 表示拉丁字母,Cyrl 表示西里尔字母,Hans 表示简体汉字,Hant 表示繁体汉字,以及 Arab 表示阿拉伯字母。

大多数语言环境省略了脚本代码,因为每种语言都有默认的书写系统。例如,英语默认使用拉丁字母,因此写作 en 而不是 en-Latn。俄语默认使用西里尔字母,因此写作 ru 而不是 ru-Cyrl

当一种语言可以用多种方式书写时,脚本代码会出现。例如,中文使用简体和繁体字符,塞尔维亚语使用西里尔字母和拉丁字母。在这些情况下,脚本代码用于区分使用哪种书写系统。

当语言环境没有明确的脚本代码时,script 属性返回 undefined

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

提取脚本代码后,可以用它来选择字体、确定文本渲染方式或按书写系统过滤内容。

理解组件何时为 undefined

并非所有语言环境标识符都包含所有组件。语言代码是必需的,但地区和脚本是可选的。

当标识符中没有某个组件时,相应的属性返回 undefined

const locale = new Intl.Locale("fr");

console.log(locale.language); // "fr"
console.log(locale.region); // undefined
console.log(locale.script); // undefined

这种行为允许您在使用这些值之前检查语言环境是否指定了地区或脚本:

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

if (locale.region) {
  console.log(`Region-specific formatting for ${locale.region}`);
} else {
  console.log("Using default formatting");
}

您可以使用空值合并运算符提供默认值:

const locale = new Intl.Locale("es");
const region = locale.region ?? "ES";

console.log(region); // "ES"

在构建语言环境回退链时,检查 undefined 组件有助于构建替代方案:

function buildFallbackChain(identifier) {
  const locale = new Intl.Locale(identifier);
  const fallbacks = [identifier];

  if (locale.region) {
    fallbacks.push(locale.language);
  }

  return fallbacks;
}

console.log(buildFallbackChain("fr-CA")); // ["fr-CA", "fr"]
console.log(buildFallbackChain("fr")); // ["fr"]

这会创建一个从最具体到最通用的语言环境标识符列表。

提取组件的实际用例

在构建国际化应用程序时,提取语言环境组件可以解决多个常见问题。

按语言对语言环境进行分组

在显示可用语言列表时,将共享相同语言代码的语言环境分组:

const locales = ["en-US", "en-GB", "fr-FR", "fr-CA", "es-ES", "es-MX"];

const grouped = locales.reduce((groups, identifier) => {
  const locale = new Intl.Locale(identifier);
  const language = locale.language;

  if (!groups[language]) {
    groups[language] = [];
  }

  groups[language].push(identifier);
  return groups;
}, {});

console.log(grouped);
// {
//   en: ["en-US", "en-GB"],
//   fr: ["fr-FR", "fr-CA"],
//   es: ["es-ES", "es-MX"]
// }

这种组织方式可以帮助用户在某种语言中找到其首选的区域变体。

构建语言环境选择器

在构建语言选择的用户界面时,提取组件以显示有意义的标签:

function buildLocaleSelector(identifiers) {
  return identifiers.map(identifier => {
    const locale = new Intl.Locale(identifier);

    const languageNames = new Intl.DisplayNames([identifier], {
      type: "language"
    });

    const regionNames = new Intl.DisplayNames([identifier], {
      type: "region"
    });

    return {
      value: identifier,
      language: languageNames.of(locale.language),
      region: locale.region ? regionNames.of(locale.region) : null
    };
  });
}

const options = buildLocaleSelector(["en-US", "en-GB", "fr-FR"]);
console.log(options);
// [
//   { value: "en-US", language: "English", region: "United States" },
//   { value: "en-GB", language: "English", region: "United Kingdom" },
//   { value: "fr-FR", language: "French", region: "France" }
// ]

这为每个语言环境选项提供了人类可读的标签。

按区域过滤

当您需要显示特定于某个区域的内容时,可以提取区域代码以过滤语言环境:

function filterByRegion(identifiers, targetRegion) {
  return identifiers.filter(identifier => {
    const locale = new Intl.Locale(identifier);
    return locale.region === targetRegion;
  });
}

const allLocales = ["en-US", "es-US", "en-GB", "fr-FR", "zh-CN"];
const usLocales = filterByRegion(allLocales, "US");

console.log(usLocales); // ["en-US", "es-US"]

这可以帮助您选择适合特定国家用户的语言环境。

检查脚本兼容性

在选择字体或渲染文本时,检查脚本以确保兼容性:

function selectFont(identifier) {
  const locale = new Intl.Locale(identifier);
  const script = locale.script;

  if (script === "Hans" || script === "Hant") {
    return "Noto Sans CJK";
  } else if (script === "Arab") {
    return "Noto Sans Arabic";
  } else if (script === "Cyrl") {
    return "Noto Sans";
  } else {
    return "Noto Sans";
  }
}

console.log(selectFont("zh-Hans-CN")); // "Noto Sans CJK"
console.log(selectFont("ar-SA")); // "Noto Sans Arabic"
console.log(selectFont("en-US")); // "Noto Sans"

这可以确保每种书写系统的文本都能正确渲染。

实现语言回退

当用户的首选语言环境不可用时,回退到基础语言:

function selectBestLocale(userPreference, supportedLocales) {
  const user = new Intl.Locale(userPreference);

  if (supportedLocales.includes(userPreference)) {
    return userPreference;
  }

  const languageMatch = supportedLocales.find(supported => {
    const locale = new Intl.Locale(supported);
    return locale.language === user.language;
  });

  if (languageMatch) {
    return languageMatch;
  }

  return supportedLocales[0];
}

const supported = ["en-US", "fr-FR", "es-ES"];

console.log(selectBestLocale("en-GB", supported)); // "en-US"
console.log(selectBestLocale("fr-CA", supported)); // "fr-FR"
console.log(selectBestLocale("de-DE", supported)); // "en-US"

这可以在没有精确匹配时提供优雅的回退。