如何将区域标识符标准化为标准形式
将区域标识符转换为规范格式,确保正确的大小写和组件顺序
介绍
区域标识符可以用多种不同的方式书写,同时指代相同的语言和地区。用户可能会写成 EN-us、en-US 或 en-us,这三种形式都表示美式英语。在存储、比较或显示区域标识符时,这些变体会导致不一致。
规范化将区域标识符转换为标准的规范形式。此过程调整组件的大小写,将扩展关键字按字母顺序排列,并生成一致的表示形式,您可以在整个应用程序中依赖它。
JavaScript 提供了内置方法,可以自动规范化区域标识符。本指南解释了规范化的含义、如何在代码中应用它,以及规范化标识符何时能改进您的国际化逻辑。
区域标识符规范化的含义
规范化根据 BCP 47 标准和 Unicode 规范将区域标识符转换为其规范形式。规范形式对大小写、排序和结构有特定规则。
规范化的区域标识符遵循以下约定:
- 语言代码为小写
- 脚本代码首字母大写,其余小写
- 地区代码为大写
- 变体代码为小写
- 扩展关键字按字母顺序排列
- 扩展属性按字母顺序排列
这些规则为每个区域创建了一个统一的标准表示形式。无论用户如何书写区域标识符,规范化形式始终保持一致。
理解规范化规则
区域标识符的每个组件在规范形式中都有特定的大小写约定。
语言大小写
语言代码始终使用小写字母:
en(正确)
EN(不正确,但会规范化为 en)
eN(不正确,但会规范化为 en)
这适用于两字母和三字母的语言代码。
脚本大小写
脚本代码使用首字母大写,其余三个字母小写的形式:
Hans(正确)
hans(错误,但会规范化为 Hans)
HANS(错误,但会规范化为 Hans)
常见的脚本代码包括 Latn 表示拉丁字母,Cyrl 表示西里尔字母,Hans 表示简体汉字,Hant 表示繁体汉字。
区域大小写
区域代码始终使用大写字母:
US(正确)
us(错误,但会规范化为 US)
Us(错误,但会规范化为 US)
这适用于大多数语言环境标识符中使用的两位字母国家代码。
扩展顺序
Unicode 扩展标签包含指定格式偏好的关键字。在规范形式中,这些关键字按其键的字母顺序排列:
en-US-u-ca-gregory-nu-latn(正确)
en-US-u-nu-latn-ca-gregory(错误,但会规范化为第一种形式)
日历键 ca 在字母顺序上位于数字系统键 nu 之前,因此在规范化形式中 ca-gregory 先出现。
使用 Intl.getCanonicalLocales 进行规范化
Intl.getCanonicalLocales() 方法规范化语言环境标识符,并以规范形式返回。这是 JavaScript 中用于规范化的主要方法。
const normalized = Intl.getCanonicalLocales("EN-us");
console.log(normalized);
// ["en-US"]
该方法接受任何大小写形式的语言环境标识符,并返回正确大小写的规范形式。
规范化语言代码
该方法将语言代码转换为小写:
const result = Intl.getCanonicalLocales("FR-fr");
console.log(result);
// ["fr-FR"]
语言代码 FR 在输出中变为 fr。
规范化脚本代码
该方法将脚本代码转换为首字母大写形式:
const result = Intl.getCanonicalLocales("zh-HANS-cn");
console.log(result);
// ["zh-Hans-CN"]
脚本代码 HANS 变为 Hans,区域代码 cn 变为 CN。
规范化区域代码
该方法将区域代码转换为大写:
const result = Intl.getCanonicalLocales("en-gb");
console.log(result);
// ["en-GB"]
区域代码 gb 在输出中变为 GB。
规范化扩展关键字
该方法按字母顺序对扩展关键字进行排序:
const result = Intl.getCanonicalLocales("en-US-u-nu-latn-hc-h12-ca-gregory");
console.log(result);
// ["en-US-u-ca-gregory-hc-h12-nu-latn"]
关键字从 nu-latn-hc-h12-ca-gregory 重新排序为 ca-gregory-hc-h12-nu-latn,因为按字母顺序 ca 在 hc 之前,hc 在 nu 之前。
规范化多个语言环境标识符
Intl.getCanonicalLocales() 方法接受一个语言环境标识符数组,并对它们全部进行规范化:
const locales = ["EN-us", "fr-FR", "ZH-hans-cn"];
const normalized = Intl.getCanonicalLocales(locales);
console.log(normalized);
// ["en-US", "fr-FR", "zh-Hans-CN"]
数组中的每个语言环境都会被转换为其规范形式。
删除重复项
该方法在规范化后删除重复的语言环境标识符。如果多个输入值规范化为相同的规范形式,结果中只包含一个副本:
const locales = ["en-US", "EN-us", "en-us"];
const normalized = Intl.getCanonicalLocales(locales);
console.log(normalized);
// ["en-US"]
这三个输入都表示相同的语言环境,因此输出中只包含一个规范化的标识符。
在处理用户输入或合并来自多个来源的语言环境列表时,这种去重功能非常有用。
处理无效标识符
如果数组中的任何语言环境标识符无效,该方法会抛出一个 RangeError:
try {
Intl.getCanonicalLocales(["en-US", "invalid", "fr-FR"]);
} catch (error) {
console.error(error.message);
// "invalid is not a structurally valid language tag"
}
在规范化用户提供的列表时,可以为每个语言环境单独验证或捕获错误,以识别哪些具体标识符无效。
使用 Intl.Locale 进行规范化
Intl.Locale 构造函数在创建语言环境对象时也会规范化语言环境标识符。您可以通过 toString() 方法访问规范化形式。
const locale = new Intl.Locale("EN-us");
console.log(locale.toString());
// "en-US"
构造函数接受任何有效的大小写形式,并生成一个规范化的语言环境对象。
访问标准化组件
locale 对象的每个属性都会返回该组件的标准化形式:
const locale = new Intl.Locale("ZH-hans-CN");
console.log(locale.language);
// "zh"
console.log(locale.script);
// "Hans"
console.log(locale.region);
// "CN"
console.log(locale.baseName);
// "zh-Hans-CN"
language、script 和 region 属性都使用了规范形式的正确大小写。
使用选项进行标准化
当您使用选项创建 locale 对象时,构造函数会同时标准化基础标识符和选项:
const locale = new Intl.Locale("EN-us", {
calendar: "gregory",
numberingSystem: "latn",
hourCycle: "h12"
});
console.log(locale.toString());
// "en-US-u-ca-gregory-hc-h12-nu-latn"
扩展关键字在输出中按字母顺序排列,即使选项对象未指定任何特定顺序。
为什么标准化很重要
标准化为您的应用程序提供了一致性。当您存储、显示或比较 locale 标识符时,使用规范形式可以防止细微的错误并提高可靠性。
一致的存储
在数据库、配置文件或本地存储中存储 locale 标识符时,标准化形式可以防止重复:
const userPreferences = new Set();
function saveUserLocale(identifier) {
const normalized = Intl.getCanonicalLocales(identifier)[0];
userPreferences.add(normalized);
}
saveUserLocale("en-US");
saveUserLocale("EN-us");
saveUserLocale("en-us");
console.log(userPreferences);
// Set { "en-US" }
如果没有标准化,集合中会包含同一 locale 的三个条目。通过标准化,它正确地只包含一个。
可靠的比较
比较 locale 标识符需要标准化。仅大小写不同的两个标识符表示相同的 locale:
function isSameLocale(locale1, locale2) {
const normalized1 = Intl.getCanonicalLocales(locale1)[0];
const normalized2 = Intl.getCanonicalLocales(locale2)[0];
return normalized1 === normalized2;
}
console.log(isSameLocale("en-US", "EN-us"));
// true
console.log(isSameLocale("en-US", "en-GB"));
// false
直接对未标准化的标识符进行字符串比较会产生错误的结果。
一致的显示
在向用户显示语言环境标识符或调试输出时,规范化形式提供了一致的格式:
function displayLocale(identifier) {
try {
const normalized = Intl.getCanonicalLocales(identifier)[0];
return `当前语言环境:${normalized}`;
} catch (error) {
return "无效的语言环境标识符";
}
}
console.log(displayLocale("EN-us"));
// "当前语言环境:en-US"
console.log(displayLocale("zh-HANS-cn"));
// "当前语言环境:zh-Hans-CN"
无论输入格式如何,用户都能看到格式正确的语言环境标识符。
实际应用
在实际应用中,规范化解决了处理语言环境标识符时的常见问题。
规范化用户输入
当用户在表单或设置中输入语言环境标识符时,应在存储之前对输入进行规范化:
function processLocaleInput(input) {
try {
const normalized = Intl.getCanonicalLocales(input)[0];
return {
success: true,
locale: normalized
};
} catch (error) {
return {
success: false,
error: "请输入有效的语言环境标识符"
};
}
}
const result = processLocaleInput("fr-ca");
console.log(result);
// { success: true, locale: "fr-CA" }
这确保了数据库或配置中的格式一致性。
构建语言环境查找表
在为翻译或特定语言环境的数据创建查找表时,使用规范化的键:
const translations = new Map();
function addTranslation(locale, key, value) {
const normalized = Intl.getCanonicalLocales(locale)[0];
if (!translations.has(normalized)) {
translations.set(normalized, {});
}
translations.get(normalized)[key] = value;
}
addTranslation("en-us", "hello", "Hello");
addTranslation("EN-US", "goodbye", "Goodbye");
console.log(translations.get("en-US"));
// { hello: "Hello", goodbye: "Goodbye" }
两次调用 addTranslation 使用相同的规范化键,因此翻译存储在同一个对象中。
合并语言环境列表
当从多个来源合并语言环境标识符时,应对其进行规范化并去重:
function mergeLocales(...sources) {
const allLocales = sources.flat();
const normalized = Intl.getCanonicalLocales(allLocales);
return normalized;
}
const userLocales = ["en-us", "fr-FR"];
const appLocales = ["EN-US", "de-de"];
const systemLocales = ["en-US", "es-mx"];
const merged = mergeLocales(userLocales, appLocales, systemLocales);
console.log(merged);
// ["en-US", "fr-FR", "de-DE", "es-MX"]
此方法去除了重复项,并在所有来源中规范了大小写。
创建语言环境选择界面
在构建下拉菜单或选择界面时,规范化语言环境标识符以便显示:
function buildLocaleOptions(locales) {
const normalized = Intl.getCanonicalLocales(locales);
return normalized.map(locale => {
const localeObj = new Intl.Locale(locale);
const displayNames = new Intl.DisplayNames([locale], {
type: "language"
});
return {
value: locale,
label: displayNames.of(localeObj.language)
};
});
}
const options = buildLocaleOptions(["EN-us", "fr-FR", "DE-de"]);
console.log(options);
// [
// { value: "en-US", label: "English" },
// { value: "fr-FR", label: "French" },
// { value: "de-DE", label: "German" }
// ]
规范化的值为表单提交提供了一致的标识符。
验证配置文件
在从配置文件加载语言环境标识符时,在初始化期间对其进行规范化:
function loadLocaleConfig(config) {
const validatedConfig = {
defaultLocale: null,
supportedLocales: []
};
try {
validatedConfig.defaultLocale = Intl.getCanonicalLocales(
config.defaultLocale
)[0];
} catch (error) {
console.error("无效的默认语言环境:", config.defaultLocale);
validatedConfig.defaultLocale = "en-US";
}
config.supportedLocales.forEach(locale => {
try {
const normalized = Intl.getCanonicalLocales(locale)[0];
validatedConfig.supportedLocales.push(normalized);
} catch (error) {
console.warn("跳过无效的语言环境:", locale);
}
});
return validatedConfig;
}
const config = {
defaultLocale: "en-us",
supportedLocales: ["EN-us", "fr-FR", "invalid", "de-DE"]
};
const validated = loadLocaleConfig(config);
console.log(validated);
// {
// defaultLocale: "en-US",
// supportedLocales: ["en-US", "fr-FR", "de-DE"]
// }
这可以及早捕获配置错误,并确保您的应用程序使用有效的规范化标识符。
规范化与语言环境匹配
规范化对于语言环境匹配算法非常重要。在为用户偏好找到最佳语言环境匹配时,比较规范化形式:
function findBestMatch(userPreference, availableLocales) {
const normalizedPreference = Intl.getCanonicalLocales(userPreference)[0];
const normalizedAvailable = Intl.getCanonicalLocales(availableLocales);
if (normalizedAvailable.includes(normalizedPreference)) {
return normalizedPreference;
}
const preferenceLocale = new Intl.Locale(normalizedPreference);
const languageMatch = normalizedAvailable.find(available => {
const availableLocale = new Intl.Locale(available);
return availableLocale.language === preferenceLocale.language;
});
if (languageMatch) {
return languageMatch;
}
return normalizedAvailable[0];
}
const available = ["en-us", "fr-FR", "DE-de"];
console.log(findBestMatch("EN-GB", available));
// "en-US"
规范化确保匹配逻辑无论输入大小写如何都能正确工作。
规范化不会改变含义
规范化仅影响区域标识符的表示形式。它不会改变标识符所代表的语言、脚本或地区。
const locale1 = new Intl.Locale("en-us");
const locale2 = new Intl.Locale("EN-US");
console.log(locale1.language === locale2.language);
// true
console.log(locale1.region === locale2.region);
// true
console.log(locale1.toString() === locale2.toString());
// true
这两个标识符都指代美式英语。规范化只是确保它们以相同的方式书写。
这与 maximize() 和 minimize() 等操作不同,后者会添加或移除组件,并可能改变标识符的具体性。
浏览器支持
Intl.getCanonicalLocales() 方法在所有现代浏览器中都可以使用。Chrome、Firefox、Safari 和 Edge 提供全面支持。
Node.js 从版本 9 开始支持 Intl.getCanonicalLocales(),在版本 10 及更高版本中提供完整支持。
Intl.Locale 构造函数及其规范化行为在支持 Intl.Locale API 的所有浏览器中都可用。这包括现代版本的 Chrome、Firefox、Safari 和 Edge。
总结
规范化通过应用标准大小写规则和对扩展关键字进行排序,将区域标识符转换为其规范形式。这创建了一种一致的表示形式,您可以可靠地存储、比较和显示。
关键概念:
- 规范形式对语言使用小写,对脚本使用首字母大写,对地区使用大写
- 扩展关键字在规范形式中按字母顺序排序
Intl.getCanonicalLocales()方法规范化标识符并移除重复项Intl.Locale构造函数也会生成规范化的输出- 规范化不会改变区域标识符的含义
- 使用规范化的标识符进行存储、比较和显示
规范化是任何处理区域标识符的应用程序的基础操作。它可以防止因大小写不一致引起的错误,并确保您的国际化逻辑可靠地处理区域标识符。