如何在首选语言环境不可用时处理语言环境回退
在用户偏好不支持的语言环境时自动选择支持的语言
介绍
并非每个 Web 应用程序都支持世界上的所有语言。当用户偏好一种您的应用程序不支持的语言时,您需要一种回退机制,以显示下一个最佳语言的内容,而不是显示错误或未翻译的文本。
语言环境回退是指在首选语言环境不可用时选择替代语言环境的过程。JavaScript 的 Intl API 会通过接受多个语言环境选项并选择支持的第一个选项来自动处理这一过程。这确保了即使在首选语言环境不可用的情况下,您的应用程序也能始终显示格式正确的内容。
本课程将解释 JavaScript 中语言环境回退的工作原理、如何有效地实现它,以及如何为具有特定语言环境支持要求的应用程序构建自定义回退逻辑。
不支持的语言环境问题
当您将语言环境标识符传递给 Intl API 时,JavaScript 运行时必须支持该语言环境才能正确格式化内容。如果您请求格式化挪威新挪威语,但运行时仅支持挪威书面语,格式化程序需要一种优雅的方式来处理这种情况。
如果没有回退,当遇到不支持的语言环境时,应用程序将无法显示内容或显示未翻译的文本。来自使用较少常见语言变体地区的用户将会遇到界面损坏的问题。
例如,一个讲加拿大法语的用户。如果您的应用程序仅支持欧洲法语,您希望格式化程序使用欧洲法语的约定,而不是完全失败。虽然这并不完美,但比完全没有本地化要好得多。
Intl API 如何自动处理回退
每个 Intl 构造函数都可以接受单个语言环境字符串或一个语言环境字符串数组。当您传递一个数组时,运行时会按顺序评估每个语言环境,并使用它支持的第一个语言环境。
const locales = ["fr-CA", "fr-FR", "en-US"];
const formatter = new Intl.DateTimeFormat(locales);
const date = new Date("2025-03-15");
console.log(formatter.format(date));
// 如果可用,使用 fr-CA
// 如果 fr-CA 不可用,则回退到 fr-FR
// 如果两种法语变体都不可用,则回退到 en-US
运行时从左到右检查数组。如果支持加拿大法语,它将使用该语言环境。如果不支持,它会尝试欧洲法语。如果两种法语变体都不可用,则回退到美式英语。
这种自动回退意味着您无需手动检查支持情况或在请求特定语言环境时处理错误。Intl API 保证会选择一个支持的语言环境或回退到系统默认值。
提供多种语言环境选项
实现回退的最简单方法是按优先顺序传递一个语言环境数组。这适用于所有的 Intl 构造函数,包括 DateTimeFormat、NumberFormat、Collator 等。
const locales = ["es-MX", "es-ES", "es", "en"];
const numberFormatter = new Intl.NumberFormat(locales, {
style: "currency",
currency: "USD"
});
console.log(numberFormatter.format(1234.56));
// 如果可用,使用墨西哥西班牙语
// 回退到欧洲西班牙语
// 回退到通用西班牙语
// 最后回退到英语
这种模式提供了一个优雅的降级路径。用户可以优先获得其首选方言的内容,如果不可用,则获得其语言的更广泛变体,最后是一个通用的回退语言。
顺序很重要。运行时会选择它支持的第一个语言环境,因此请将最具体和首选的语言环境放在前面。
理解语言环境匹配的工作原理
当您提供多个语言环境时,JavaScript 运行时会使用语言环境匹配算法来选择最佳可用选项。该算法会将您请求的语言环境与运行时支持的语言环境集合进行比较。
如果请求的语言环境与支持的语言环境完全匹配,运行时会立即使用它。如果没有完全匹配的语言环境,运行时可能会根据语言和地区代码选择相关的语言环境。
例如,如果您请求 en-AU(澳大利亚英语),但运行时仅支持 en-US 和 en-GB,它将选择这些英语变体之一,而不是回退到完全不同的语言。
const locales = ["en-AU", "en"];
const formatter = new Intl.DateTimeFormat(locales);
const resolvedLocale = formatter.resolvedOptions().locale;
console.log(resolvedLocale);
// 可能显示 "en-US" 或 "en-GB",具体取决于运行时
// 运行时选择了一个支持的英语变体
resolvedOptions() 方法返回格式化器正在使用的实际语言环境。这使您可以在回退后验证选择了哪个语言环境。
检查支持的语言环境
supportedLocalesOf() 静态方法用于检查特定的 Intl 构造函数支持列表中的哪些语言环境。此方法返回一个仅包含支持的语言环境的数组。
const requestedLocales = ["fr-CA", "fr-FR", "de-DE", "ja-JP"];
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(requestedLocales);
console.log(supportedLocales);
// 输出取决于运行时的支持情况
// 示例: ["fr-FR", "de-DE", "ja-JP"]
// 加拿大法语不被支持,其他被支持
此方法会过滤请求的语言环境,显示运行时可以使用的语言环境,而无需回退到默认值。不支持的语言环境会从返回的数组中移除。
您可以在创建格式化器之前使用此方法检查支持情况,或者显示哪些语言选项可供用户使用。
const availableLocales = ["en-US", "es-MX", "fr-FR", "de-DE", "ja-JP"];
const supported = Intl.NumberFormat.supportedLocalesOf(availableLocales);
console.log("此运行时支持:", supported);
// 显示您的应用程序语言环境中哪些在此环境中可用
每个 Intl 构造函数都有自己的 supportedLocalesOf() 方法,因为不同的国际化功能对语言环境的支持可能不同。例如,运行时可能支持法语的数字格式化,但不支持文本分段。
从语言环境标识符构建回退链
当您知道您的应用程序支持特定的语言环境时,可以构建一个逐步变得不那么具体的回退链。此模式从完整的语言环境标识符开始,逐步移除组件,直到找到匹配项。
function buildFallbackChain(locale) {
const chain = [locale];
const parts = locale.split("-");
if (parts.length > 1) {
chain.push(parts[0]);
}
chain.push("en");
return chain;
}
const fallbacks = buildFallbackChain("zh-Hans-CN");
console.log(fallbacks);
// ["zh-Hans-CN", "zh", "en"]
const formatter = new Intl.DateTimeFormat(fallbacks);
// 尝试中国的简体中文
// 回退到通用中文
// 回退到英文
此函数通过从语言环境标识符中提取语言代码并添加最终的英文回退来创建回退链。您可以根据应用程序支持的语言环境扩展此逻辑,以包含更复杂的回退规则。
对于支持多种语言变体的应用程序,您可能希望在回退到英文之前,先回退到相关的方言。
function buildSmartFallbackChain(locale) {
const chain = [locale];
if (locale.startsWith("es-")) {
chain.push("es-MX", "es-ES", "es");
} else if (locale.startsWith("fr-")) {
chain.push("fr-FR", "fr-CA", "fr");
} else if (locale.startsWith("zh-")) {
chain.push("zh-Hans-CN", "zh-Hant-TW", "zh");
}
const parts = locale.split("-");
if (parts.length > 1 && !chain.includes(parts[0])) {
chain.push(parts[0]);
}
if (!chain.includes("en")) {
chain.push("en");
}
return chain;
}
const fallbacks = buildSmartFallbackChain("es-AR");
console.log(fallbacks);
// ["es-AR", "es-MX", "es-ES", "es", "en"]
// 尝试阿根廷西班牙语
// 回退到墨西哥西班牙语
// 回退到欧洲西班牙语
// 回退到通用西班牙语
// 回退到英文
此方法确保用户在回退到英文之前,能够看到其语言的相关方言内容。
选择区域设置匹配算法
Intl API 支持两种区域设置匹配算法:lookup(查找)和 best fit(最佳匹配)。您可以通过创建格式化器时的 localeMatcher 选项指定使用哪种算法。
lookup 算法遵循 BCP 47 Lookup 规范。它通过系统地比较区域设置标识符并选择第一个完全匹配项来执行严格匹配。
const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
localeMatcher: "lookup"
});
console.log(formatter.resolvedOptions().locale);
// 使用严格的查找匹配规则
best fit 算法允许运行时使用其自己的匹配逻辑选择区域设置。即使不是完全匹配,该算法也可以智能地决定哪个区域设置最能满足用户的需求。
const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
localeMatcher: "best fit"
});
console.log(formatter.resolvedOptions().locale);
// 使用运行时的最佳匹配算法
// 可能更智能地选择相关的区域设置
默认算法是 best fit。大多数应用程序应使用默认算法,因为它在不同的 JavaScript 运行时中提供了更好的结果。仅在需要可预测的严格匹配行为时使用 lookup。
使用浏览器语言偏好实现自动回退
navigator.languages 属性返回一个按偏好顺序排列的用户首选语言数组。您可以将此数组直接传递给 Intl 构造函数,以根据浏览器设置实现自动回退。
const formatter = new Intl.DateTimeFormat(navigator.languages);
const date = new Date("2025-03-15");
console.log(formatter.format(date));
// 自动使用用户首选的支持语言
这种方法让浏览器处理所有回退逻辑。如果用户的第一偏好不受支持,Intl API 会自动尝试第二偏好,然后是第三偏好,依此类推。
当您希望尊重用户的所有语言偏好而无需手动构建回退链时,此模式非常有效。
console.log(navigator.languages);
// ["fr-CA", "fr", "en-US", "en"]
const numberFormatter = new Intl.NumberFormat(navigator.languages, {
style: "currency",
currency: "USD"
});
console.log(numberFormatter.format(1234.56));
// 优先尝试加拿大法语
// 通过用户的完整偏好列表回退
Intl API 会评估数组中的每种语言并选择它支持的第一个语言,这使其成为处理多样化用户偏好的强大解决方案。
将应用程序支持与用户偏好相结合
对于支持语言环境有限的应用程序,可以在创建格式化器之前过滤用户偏好以匹配您支持的语言环境。这确保了您只尝试使用您的应用程序能够处理的语言环境。
const supportedLocales = ["en-US", "es-MX", "fr-FR", "de-DE"];
function findBestLocale(userPreferences, appSupported) {
const preferences = userPreferences.map(pref => {
const parts = pref.split("-");
return [pref, parts[0]];
}).flat();
for (const pref of preferences) {
if (appSupported.includes(pref)) {
return pref;
}
}
return appSupported[0];
}
const userLocale = findBestLocale(navigator.languages, supportedLocales);
const formatter = new Intl.DateTimeFormat(userLocale);
console.log(formatter.resolvedOptions().locale);
// 选择的语言环境同时匹配用户偏好和应用程序支持
此函数在用户偏好和支持的语言环境之间找到第一个匹配项,包括尝试不带区域代码的语言代码以实现更广泛的匹配。
对于更复杂的匹配,您可以检查用户偏好中哪些语言环境在此运行时中受 Intl API 支持。
const supportedLocales = ["en-US", "es-MX", "fr-FR", "de-DE"];
function findBestSupportedLocale(userPreferences, appSupported) {
const runtimeSupported = Intl.DateTimeFormat.supportedLocalesOf(appSupported);
for (const pref of userPreferences) {
if (runtimeSupported.includes(pref)) {
return pref;
}
const lang = pref.split("-")[0];
const match = runtimeSupported.find(s => s.startsWith(lang));
if (match) {
return match;
}
}
return runtimeSupported[0] || "en";
}
const userLocale = findBestSupportedLocale(navigator.languages, supportedLocales);
const formatter = new Intl.DateTimeFormat(userLocale);
这种方法确保所选语言环境同时受您的应用程序和 JavaScript 运行时支持。
处理没有语言环境匹配的情况
如果请求的语言环境都不受支持,Intl API 会回退到系统的默认语言环境。此默认值因环境而异,由操作系统、浏览器或 Node.js 配置决定。
const unsupportedLocales = ["non-existent-locale"];
const formatter = new Intl.DateTimeFormat(unsupportedLocales);
console.log(formatter.resolvedOptions().locale);
// 显示系统默认语言环境
// 可能是 "en-US" 或其他语言环境,具体取决于系统
系统默认值确保即使使用完全无效或不受支持的语言环境列表,格式化器也能正常工作。您的应用程序不会因语言环境问题而抛出错误。
为了确保特定的回退,而不是依赖系统默认值,请始终在语言环境数组的末尾包含一个广泛支持的语言环境,例如 en 或 en-US。
const locales = ["xyz-INVALID", "en"];
const formatter = new Intl.DateTimeFormat(locales);
console.log(formatter.resolvedOptions().locale);
// 将使用 "en",因为第一个语言环境无效
// 保证回退到英语而不是系统默认值
此模式使您的应用程序行为在不同环境中更加可预测。
适用于生产应用的实用模式
在构建生产应用时,结合多种回退策略以确保在多样化用户群体中实现稳健的语言环境处理。
一种常见模式是创建具有完整回退链的格式化器,该回退链包括用户偏好、应用支持的语言环境以及一个保证的最终回退。
class LocaleManager {
constructor(supportedLocales) {
this.supportedLocales = supportedLocales;
this.defaultLocale = "en-US";
}
buildLocaleChain(userPreference) {
const chain = [];
if (userPreference) {
chain.push(userPreference);
const lang = userPreference.split("-")[0];
if (lang !== userPreference) {
chain.push(lang);
}
}
chain.push(...navigator.languages);
chain.push(...this.supportedLocales);
chain.push(this.defaultLocale);
const unique = [...new Set(chain)];
return unique;
}
createDateFormatter(userPreference, options = {}) {
const locales = this.buildLocaleChain(userPreference);
return new Intl.DateTimeFormat(locales, options);
}
createNumberFormatter(userPreference, options = {}) {
const locales = this.buildLocaleChain(userPreference);
return new Intl.NumberFormat(locales, options);
}
}
const manager = new LocaleManager(["en-US", "es-MX", "fr-FR", "de-DE"]);
const dateFormatter = manager.createDateFormatter("pt-BR");
console.log(dateFormatter.resolvedOptions().locale);
// 使用全面的回退链
// 尝试巴西葡萄牙语
// 回退到葡萄牙语
// 通过 navigator.languages 回退
// 通过支持的语言环境回退
// 如果没有其他匹配项,保证使用 en-US
此类封装了语言环境回退逻辑,并确保在整个应用中行为一致。
对于需要在运行时响应用户语言环境更改的应用,将语言环境管理器与事件监听器结合使用。
class LocaleAwareFormatter {
constructor(supportedLocales) {
this.supportedLocales = supportedLocales;
this.updateFormatters();
window.addEventListener("languagechange", () => {
this.updateFormatters();
});
}
updateFormatters() {
const locales = [...navigator.languages, ...this.supportedLocales, "en"];
this.dateFormatter = new Intl.DateTimeFormat(locales);
this.numberFormatter = new Intl.NumberFormat(locales);
}
formatDate(date) {
return this.dateFormatter.format(date);
}
formatNumber(number) {
return this.numberFormatter.format(number);
}
}
const formatter = new LocaleAwareFormatter(["en-US", "es-MX", "fr-FR"]);
console.log(formatter.formatDate(new Date()));
// 当用户更改语言偏好时自动更新
此模式创建的格式化器能够与浏览器语言更改保持同步,确保您的应用始终根据当前偏好显示内容。
概要
语言环境回退确保即使用户偏好的语言环境未被明确支持,您的应用程序也能显示格式正确的内容。Intl API 通过接受语言环境偏好数组并选择第一个支持的选项来自动处理回退。
关键概念:
- 将语言环境数组传递给 Intl 构造函数以实现自动回退
- 运行时从数组中选择它支持的第一个语言环境
- 使用
supportedLocalesOf()检查可用的语言环境 - 构建从具体到通用语言环境的回退链
localeMatcher选项控制匹配算法- 直接传递
navigator.languages以自动处理用户偏好 - 始终包括一个广泛支持的最终回退语言,例如英语
- 当没有语言环境匹配时,运行时会回退到系统默认值
对于大多数应用程序,使用带有语言环境数组的自动回退。需要对尝试的语言环境及其顺序进行特定控制时,实施自定义回退逻辑。