선호하는 로케일을 사용할 수 없을 때 로케일 폴백 처리 방법
사용자가 지원되지 않는 로케일을 선호할 때 자동으로 지원되는 언어 선택
소개
모든 웹 애플리케이션이 세계의 모든 언어를 지원하는 것은 아닙니다. 사용자가 애플리케이션이 지원하지 않는 언어를 선호할 경우, 오류를 표시하거나 번역되지 않은 텍스트를 보여주는 대신 차선책 언어로 콘텐츠를 표시하는 대체 메커니즘이 필요합니다.
로케일 폴백은 선호하는 로케일을 사용할 수 없을 때 대체 로케일을 선택하는 프로세스입니다. JavaScript의 Intl API는 여러 로케일 옵션을 수락하고 지원하는 첫 번째 로케일을 선택함으로써 이를 자동으로 처리합니다. 이를 통해 정확한 선호 로케일을 사용할 수 없는 경우에도 애플리케이션이 항상 적절하게 형식이 지정된 콘텐츠를 표시할 수 있습니다.
이 강의에서는 JavaScript에서 로케일 폴백이 작동하는 방식, 효과적으로 구현하는 방법, 그리고 특정 로케일 지원 요구 사항이 있는 애플리케이션을 위한 사용자 정의 폴백 로직을 구축하는 방법을 설명합니다.
지원되지 않는 로케일의 문제점
Intl API에 로케일 식별자를 전달할 때, JavaScript 런타임은 콘텐츠를 올바르게 형식화하기 위해 해당 로케일을 지원해야 합니다. 노르웨이어 니노르스크(Norwegian Nynorsk)에 대한 형식을 요청했지만 런타임이 노르웨이어 보크몰(Norwegian Bokmål)만 지원하는 경우, 포맷터는 이를 원활하게 처리할 방법이 필요합니다.
폴백이 없다면, 애플리케이션은 지원되지 않는 로케일을 만났을 때 콘텐츠를 표시하지 못하거나 번역되지 않은 텍스트를 보여줄 것입니다. 덜 일반적인 언어 변형을 사용하는 지역의 사용자들은 손상된 인터페이스를 경험하게 될 것입니다.
캐나다 프랑스어를 사용하는 사용자를 고려해 보세요. 애플리케이션이 유럽 프랑스어만 지원한다면, 완전히 실패하는 대신 포맷터가 유럽 프랑스어 규칙을 사용하도록 하고 싶을 것입니다. 완벽하지는 않지만, 이는 로컬라이제이션이 전혀 없는 것보다 더 나은 경험을 제공합니다.
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는 지원되는 로케일을 선택하거나 시스템 기본값으로 폴백할 것을 보장합니다.
다양한 로케일 옵션 제공하기
폴백을 구현하는 가장 간단한 방법은 선호도 순서대로 로케일 배열을 전달하는 것입니다. 이는 DateTimeFormat, NumberFormat, Collator 등 모든 Intl 생성자에서 작동합니다.
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);
// 엄격한 lookup 매칭 규칙 사용
best fit 알고리즘은 런타임이 자체 매칭 로직을 사용하여 로케일을 선택할 수 있게 합니다. 이 알고리즘은 정확히 일치하지 않더라도 사용자의 요구에 가장 적합한 로케일에 대해 지능적인 결정을 내릴 수 있습니다.
const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
localeMatcher: "best fit"
});
console.log(formatter.resolvedOptions().locale);
// 런타임의 best fit 알고리즘 사용
// 관련 로케일을 더 지능적으로 선택할 수 있음
기본 알고리즘은 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를 직접 전달 - 영어와 같이 널리 지원되는 최종 폴백을 항상 포함
- 일치하는 로케일이 없을 때 런타임은 시스템 기본값으로 폴백
대부분의 애플리케이션에서는 로케일 배열과 함께 자동 폴백을 사용하세요. 어떤 로케일을 어떤 순서로 시도할지에 대한 특정 제어가 필요한 경우 사용자 정의 폴백 로직을 구현하세요.