如何从 locale 中提取语言、国家和书写系统
使用 JavaScript 解析 locale 标识符并获取其各个组成部分
简介
像 en-US、fr-CA 和 zh-Hans-CN 这样的 locale 标识符,在一个字符串中编码了多种信息。这些组成部分可以告诉你所用的语言、使用该语言的地区,有时还包括书写系统。
在开发国际化应用时,通常需要提取这些单独的组成部分。例如,你可能只想向用户显示语言名称,按地区对 locale 进行分组,或检查 locale 使用的书写系统。与其手动用正则表达式解析字符串,不如使用 JavaScript 的 Intl.Locale API 来可靠地提取各个组成部分。
本指南将介绍 locale 标识符中包含哪些组成部分,如何使用 Intl.Locale API 提取它们,以及在实际开发中何时需要用到这些组成部分。
locale 标识符包含哪些组成部分
locale 标识符遵循 BCP 47 标准,该标准定义了描述语言及其区域变体的结构。完整的 locale 标识符可以包含多个用连字符分隔的组成部分。
最常见的三个组成部分是:
- 语言:主要使用的语言,如英语、西班牙语或中文
- 地区:该语言使用的地理区域,如美国、加拿大或中国
- 书写系统:用于表示该语言的书写系统,如拉丁字母、西里尔字母或汉字
一个简单的 locale 标识符只包含语言代码:
en
大多数 locale 标识符包含语言和地区:
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 属性返回 locale 标识符中的书写系统部分。该部分是一个四字母代码,用于标识用于表示该语言的书写系统。
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(阿拉伯字母)。
大多数 locale 都省略了书写系统代码,因为每种语言都有默认的书写系统。例如,英语默认使用拉丁字母,因此你只需写 en,而不是 en-Latn。俄语默认使用西里尔字母,所以你只需写 ru,而不是 ru-Cyrl。
当一种语言可以用多种书写方式时,会出现脚本代码。例如,中文有简体和繁体两种字符,塞尔维亚语则有西里尔字母和拉丁字母。在这些情况下,脚本代码用于区分应使用哪种书写系统。
当 locale 没有显式脚本代码时,script 属性会返回 undefined:
const english = new Intl.Locale("en-US");
console.log(english.script); // undefined
提取脚本代码后,可以用它来选择字体、确定文本渲染方式,或按书写系统筛选内容。
了解组件未定义的情况
并非所有 locale 标识符都包含所有组件。语言代码是必需的,但地区和脚本是可选的。
当标识符中没有某个组件时,相应的属性会返回 undefined:
const locale = new Intl.Locale("fr");
console.log(locale.language); // "fr"
console.log(locale.region); // undefined
console.log(locale.script); // undefined
这种行为可以让你在使用地区或脚本值之前,检查 locale 是否指定了这些内容:
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"
在构建 locale 回退链时,检查未定义的组件有助于你构建替代方案:
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"]
这样可以创建一个从最具体到最通用排序的 locale 标识符列表。
提取组件的实际应用场景
在构建国际化应用时,提取 locale 组件可以解决多个常见问题。
按语言分组 locale
在显示可用语言列表时,将具有相同语言代码的 locale 分组:
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"]
// }
这种组织方式有助于用户找到自己偏好的语言区域变体。
构建 locale 选择器
在构建语言选择界面时,提取组件以显示有意义的标签:
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" }
// ]
这为每个 locale 选项提供了易于理解的标签。
按地区筛选
当需要显示特定地区的内容时,可以提取地区代码以筛选 locale:
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"]
这样有助于为特定国家/地区的用户选择合适的 locale。
检查脚本兼容性
在选择字体或渲染文本时,请检查脚本以确保兼容性:
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"
这样可以确保每种书写系统的文本都能正确显示。
实现语言回退
当用户的首选 locale 不可用时,回退到基础语言:
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"
当无法精确匹配时,这样可以实现平滑的回退。