如何处理像 en-US 和 fr-CA 这样的语言标签

了解 BCP 47 语言标签的结构和含义,以做出更好的国际化决策

简介

语言标签是用于标识特定语言及其区域变体的标准化代码。这些标签贯穿于国际化工作中。当您检测用户的首选语言时,浏览器会返回语言标签。当您格式化日期或数字时,您会将语言标签传递给 Intl API。当您加载翻译时,您会使用语言标签来确定显示的内容。

了解这些标签的工作原理有助于您在语言选择、回退行为和内容组织方面做出更好的决策。本课程将解释语言标签的结构,并向您展示如何在 JavaScript 中使用它们。

什么是语言标签

语言标签是一个字符串,例如 enen-USzh-Hans-CN,用于标识一种语言,并可选地指定其书写系统和区域。这些标签遵循 BCP 47 标准,该标准由互联网工程任务组 (IETF) 和互联网号码分配机构 (IANA) 维护。

BCP 47 代表“最佳当前实践 47”。该标准定义了如何从称为子标签的小组件构建语言标签。每个子标签表示语言的特定方面,例如使用的语言、使用的书写系统或与之关联的国家。

每种编程语言和国际化库都使用 BCP 47 标签。这种一致性意味着您可以在整个应用程序中使用相同的语言标识符,从浏览器检测到服务器端格式化再到翻译文件名。

语言标签的结构

语言标签由用连字符分隔的子标签组成。最常见的三个子标签是语言、书写系统和区域。当这些子标签同时存在时,它们总是以特定的顺序出现。

语言子标签是第一个出现的,也是唯一必需的组件。它使用来自 ISO 639 的两位或三位字母代码。例如,en 代表英语,fr 代表法语,zh 代表中文。

书写系统子标签在存在时位于第二位。它使用来自 ISO 15924 的四位字母代码,用于标识书写系统。例如,Latn 代表拉丁字母,Cyrl 代表西里尔字母,Hans 代表简体中文字符。

区域子标签在存在时位于最后。它使用来自 ISO 3166-1 的两位字母代码,通常表示一个国家。例如,US 代表美国,CA 代表加拿大,CN 代表中国。

常见语言标签的示例

以下是一些示例,展示了使用语言标签可以表达的不同具体程度。

仅包含语言的简单标签:

  • en - 英语(无特定地区或书写系统)
  • fr - 法语(无特定地区或书写系统)
  • es - 西班牙语(无特定地区或书写系统)

包含语言和地区的标签:

  • en-US - 美国使用的英语
  • en-GB - 英国使用的英语
  • fr-CA - 加拿大使用的法语
  • es-MX - 墨西哥使用的西班牙语

包含语言、书写系统和地区的标签:

  • zh-Hans-CN - 中国使用的简体中文
  • zh-Hant-TW - 台湾使用的繁体中文
  • sr-Latn-RS - 塞尔维亚使用的拉丁字母书写的塞尔维亚语
  • sr-Cyrl-RS - 塞尔维亚使用的西里尔字母书写的塞尔维亚语

您需要的具体程度取决于您的应用。如果您仅翻译文本,可能只需要语言和地区。如果您处理使用多种书写系统的语言,则需要书写系统子标签。

语言标签的大小写约定

语言标签对大小写不敏感。标签 en-USEN-USen-usEn-Us 都表示相同的语言。然而,有一些约定的大小写模式可以使标签更易读。

语言子标签通常使用小写字母。应写为 en,而不是 ENEn

书写系统子标签通常使用首字母大写的标题格式。应写为 Latn,而不是 latnLATN

地区子标签通常使用大写字母。应写为 US,而不是 usUs

遵循这些约定可以使您的标签更易读,并与文档和规范中使用的格式保持一致。然而,您的代码应接受任何大小写的语言标签,因为该格式在官方上对大小写不敏感。

使用 JavaScript 解析语言标签

JavaScript 提供了 Intl.Locale 构造函数,用于解析语言标签并提取其组件。该构造函数接受一个语言标签字符串,并返回一个包含每个子标签属性的对象。

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

console.log(locale.language);
// 输出: "en"

console.log(locale.region);
// 输出: "US"

Intl.Locale 对象具有语言标签每个组件的属性。如果原始标签中没有对应的子标签,这些属性将返回 undefined。

const simple = new Intl.Locale("fr");
console.log(simple.language);
// 输出: "fr"

console.log(simple.region);
// 输出: undefined

您可以以相同的方式解析带有脚本子标签的标签。

const complex = new Intl.Locale("zh-Hans-CN");

console.log(complex.language);
// 输出: "zh"

console.log(complex.script);
// 输出: "Hans"

console.log(complex.region);
// 输出: "CN"

这种解析功能在需要根据语言标签的特定组件做出决策时非常有用。例如,您可能需要根据脚本加载不同的字体,或者根据地区显示不同的内容。

何时使用特定标签与通用标签

选择语言标签的具体程度取决于您的应用程序需要处理的语言和文化方面。

当您有一个适用于该语言所有使用者的单一翻译时,请使用仅包含语言的标签,例如 enfr。这在本地化预算有限或语言区域差异较小的应用程序中很常见。

当您需要考虑词汇、拼写或文化习惯的区域差异时,请使用语言和地区标签,例如 en-USfr-CA。例如,英式英语和美式英语在许多单词的拼写上有所不同。加拿大法语和欧洲法语在词汇和表达上也存在差异。

当您处理使用多种书写系统的语言时,请使用语言、脚本和地区标签,例如 zh-Hans-CN。例如,中文可以用简体或繁体字符书写。塞尔维亚语可以用拉丁字母或西里尔字母书写。脚本子标签用于区分这些变体。

提取翻译文件的语言代码

许多翻译系统通过语言代码组织文件。您可以从完整的语言标签中提取语言和地区,以确定加载哪个翻译文件。

const userLanguage = "zh-Hans-CN";
const locale = new Intl.Locale(userLanguage);

const translationKey = `${locale.language}-${locale.region}`;
console.log(translationKey);
// 输出: "zh-CN"

即使用户的语言标签包含您在文件选择中不需要的组件,此方法也能正常工作。

某些应用程序仅使用语言代码而不包含地区。

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

const translationKey = locale.language;
console.log(translationKey);
// 输出: "fr"

您选择的翻译文件名结构应与从语言标签中提取组件的方式相匹配。

在 Intl API 中使用语言标签

Intl API 在其所有构造函数中直接接受语言标签。除非需要检查特定组件,否则您无需自行解析标签。

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

const usFormat = new Intl.DateTimeFormat("en-US").format(date);
console.log(usFormat);
// 输出: "3/15/2025"

const gbFormat = new Intl.DateTimeFormat("en-GB").format(date);
console.log(gbFormat);
// 输出: "15/03/2025"

Intl API 使用语言标签来确定应用哪些格式化约定。即使在讲相同语言的情况下,不同地区对日期、数字和货币的格式化方式也不同。

您可以将从浏览器获取的语言标签直接传递给 Intl 构造函数。

const userLanguage = navigator.language;
const formatter = new Intl.NumberFormat(userLanguage);

console.log(formatter.format(1234.5));
// 输出因语言而异
// 对于 "en-US": "1,234.5"
// 对于 "de-DE": "1.234,5"

这是客户端国际化中最常见的模式。检测用户的语言,然后在整个应用程序中使用该语言标签以适当地格式化内容。

处理无效的语言标签

Intl.Locale 构造函数在传入无效的语言标签时会抛出一个 RangeError。当处理来自不可信来源的语言标签时,您应该处理此错误。

try {
  const locale = new Intl.Locale("invalid-tag-format");
} catch (error) {
  console.log(error.name);
  // 输出: "RangeError"

  console.log(error.message);
  // 输出: "invalid language tag: invalid-tag-format"
}

大多数来自浏览器的语言标签是有效的,但用户输入或外部数据源可能包含格式错误的标签。将构造函数包装在错误处理代码中可以防止这些无效标签导致应用程序崩溃。