선호하는 로케일을 사용할 수 없을 때 로케일 폴백을 처리하는 방법

사용자가 지원되지 않는 로케일을 선호할 때 지원되는 언어를 자동으로 선택

소개

모든 웹 애플리케이션이 세계의 모든 언어를 지원하는 것은 아닙니다. 사용자가 애플리케이션에서 지원하지 않는 언어를 선호하는 경우, 오류나 번역되지 않은 텍스트를 표시하는 대신 차선책 언어로 콘텐츠를 표시하기 위한 폴백 메커니즘이 필요합니다.

로케일 폴백은 선호하는 로케일을 사용할 수 없을 때 대체 로케일을 선택하는 프로세스입니다. 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));
// Uses fr-CA if available
// Falls back to fr-FR if fr-CA is not available
// Falls back to en-US if neither French variant is available

런타임은 배열을 왼쪽에서 오른쪽으로 검사합니다. 캐나다 프랑스어를 지원하면 이를 사용합니다. 그렇지 않으면 유럽 프랑스어를 시도합니다. 두 프랑스어 변형을 모두 사용할 수 없는 경우 미국 영어로 폴백합니다.

이러한 자동 폴백은 특정 로케일을 요청할 때 수동으로 지원을 확인하거나 오류를 처리할 필요가 없음을 의미합니다. 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));
// Uses Mexican Spanish if available
// Falls back to European Spanish
// Falls back to generic Spanish
// Falls back to English as final option

이 패턴은 우아한 성능 저하 경로를 제공합니다. 사용자는 가능한 경우 선호하는 방언으로 콘텐츠를 받고, 그 다음 더 광범위한 언어 변형을 받으며, 마지막으로 공통 폴백 언어를 받습니다.

순서가 중요합니다. 런타임은 지원하는 첫 번째 로케일을 선택하므로 가장 구체적이고 선호되는 로케일을 먼저 배치하십시오.

로케일 매칭 작동 방식 이해

여러 로케일을 제공하면 JavaScript 런타임은 로케일 매칭 알고리즘을 사용하여 사용 가능한 최상의 옵션을 선택합니다. 이 알고리즘은 요청된 로케일을 런타임이 지원하는 로케일 세트와 비교합니다.

요청된 로케일이 지원되는 로케일과 정확히 일치하면 런타임은 즉시 이를 사용합니다. 정확히 일치하는 항목이 없으면 런타임은 언어 및 지역 코드를 기반으로 관련 로케일을 선택할 수 있습니다.

예를 들어, en-AU(호주 영어)를 요청했지만 런타임이 en-USen-GB만 지원하는 경우, 완전히 다른 언어로 폴백하는 대신 해당 영어 변형 중 하나를 선택합니다.

const locales = ["en-AU", "en"];
const formatter = new Intl.DateTimeFormat(locales);

const resolvedLocale = formatter.resolvedOptions().locale;
console.log(resolvedLocale);
// Might show "en-US" or "en-GB" depending on the runtime
// The runtime selected a supported English variant

resolvedOptions() 메서드는 포매터가 사용 중인 실제 로케일을 반환합니다. 이를 통해 폴백 후 선택된 로케일을 확인할 수 있습니다.

지원되는 로케일 확인하기

supportedLocalesOf() 정적 메서드는 목록의 로케일 중 특정 Intl 생성자가 지원하는 로케일을 확인합니다. 이 메서드는 지원되는 로케일만 포함하는 배열을 반환합니다.

const requestedLocales = ["fr-CA", "fr-FR", "de-DE", "ja-JP"];
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(requestedLocales);

console.log(supportedLocales);
// Output depends on runtime support
// Example: ["fr-FR", "de-DE", "ja-JP"]
// Canadian French was not supported, others were

이 메서드는 요청된 로케일을 필터링하여 런타임이 기본값으로 폴백하지 않고 사용할 수 있는 로케일을 표시합니다. 지원되지 않는 로케일은 반환된 배열에서 제거됩니다.

이 메서드를 사용하여 포매터를 생성하기 전에 지원 여부를 확인하거나, 사용자에게 사용 가능한 언어 옵션을 표시할 수 있습니다.

const availableLocales = ["en-US", "es-MX", "fr-FR", "de-DE", "ja-JP"];
const supported = Intl.NumberFormat.supportedLocalesOf(availableLocales);

console.log("This runtime supports:", supported);
// Shows which of your application locales work in this environment

각 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);
// Tries Simplified Chinese for China
// Falls back to generic Chinese
// Falls back to English

이 함수는 로케일 식별자에서 언어 코드를 추출하고 최종 영어 폴백을 추가하여 폴백 체인을 생성합니다. 애플리케이션이 지원하는 로케일을 기반으로 더 정교한 폴백 규칙을 포함하도록 이 로직을 확장할 수 있습니다.

언어의 여러 변형을 지원하는 애플리케이션의 경우, 영어로 전환하기 전에 관련 방언으로 폴백하는 것이 좋습니다.

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"]
// Tries Argentinian Spanish
// Falls back to Mexican Spanish
// Falls back to European Spanish
// Falls back to generic Spanish
// Falls back to English

이 접근 방식은 사용자가 영어로 대체되기 전에 자신의 언어와 관련된 방언으로 콘텐츠를 볼 수 있도록 보장합니다.

로케일 매칭 알고리즘 선택

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);
// Uses strict lookup matching rules

best fit 알고리즘은 런타임이 자체 매칭 로직을 사용하여 로케일을 선택할 수 있도록 합니다. 이 알고리즘은 정확히 일치하지 않더라도 사용자의 요구 사항에 가장 적합한 로케일에 대해 지능적인 결정을 내릴 수 있습니다.

const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
  localeMatcher: "best fit"
});

console.log(formatter.resolvedOptions().locale);
// Uses runtime's best fit algorithm
// Might select a related locale more intelligently

기본 알고리즘은 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));
// Automatically uses user's preferred supported language

이 접근 방식은 브라우저가 모든 대체 로직을 처리하도록 합니다. 사용자의 첫 번째 기본 설정이 지원되지 않으면 Intl API는 자동으로 두 번째 기본 설정, 그다음 세 번째 기본 설정 등을 시도합니다.

이 패턴은 대체 체인을 수동으로 구축하지 않고 사용자의 모든 언어 기본 설정을 존중하려는 경우에 적합합니다.

{/* CODE_PLACEHOLDER_ee6192779f6c3d9e2ce7a9c8c0ea7117 */)}

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);
// Selected locale matches both user preference and app support

이 함수는 사용자 기본 설정과 지원되는 로케일 간의 첫 번째 일치 항목을 찾으며, 더 광범위한 매칭을 위해 지역 코드 없이 언어 코드만으로도 시도합니다.

더 정교한 매칭을 위해 현재 런타임에서 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);
// Shows the system default locale
// Might be "en-US" or another locale depending on the system

시스템 기본값은 완전히 유효하지 않거나 지원되지 않는 로케일 목록이 있더라도 포매터가 항상 작동하도록 보장합니다. 애플리케이션은 로케일 문제로 인해 오류를 발생시키지 않습니다.

시스템 기본값에 의존하는 대신 특정 폴백을 보장하려면 로케일 배열의 끝에 en 또는 en-US와 같이 널리 지원되는 로케일을 항상 포함하세요.

const locales = ["xyz-INVALID", "en"];
const formatter = new Intl.DateTimeFormat(locales);

console.log(formatter.resolvedOptions().locale);
// Will use "en" since the first locale is invalid
// Guaranteed fallback to English instead of system default

이 패턴은 다양한 환경에서 애플리케이션의 동작을 더 예측 가능하게 만듭니다.

프로덕션 애플리케이션을 위한 실용적인 패턴

프로덕션 애플리케이션을 구축할 때는 여러 폴백 전략을 결합하여 다양한 사용자 기반에서 강력한 로케일 처리를 보장하세요.

일반적인 패턴은 사용자 기본 설정, 애플리케이션 지원 로케일 및 보장된 최종 폴백을 포함하는 완전한 폴백 체인으로 포매터를 생성합니다.

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);
// Uses comprehensive fallback chain
// Tries Portuguese for Brazil
// Falls back to Portuguese
// Falls back through navigator.languages
// Falls back through supported locales
// Guaranteed to use en-US if nothing else matches

이 클래스는 로케일 폴백 로직을 캡슐화하고 애플리케이션 전체에서 일관된 동작을 보장합니다.

런타임에 사용자 로케일 변경에 응답해야 하는 애플리케이션의 경우, 로케일 관리자를 이벤트 리스너와 결합하세요.

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()));
// Automatically updates when user changes language preferences

이 패턴은 브라우저 언어 변경과 동기화된 상태를 유지하는 포매터를 생성하여, 애플리케이션이 항상 현재 기본 설정에 따라 콘텐츠를 표시하도록 보장합니다.

요약

로케일 폴백은 사용자가 명시적으로 지원하지 않는 로케일을 선호하는 경우에도 애플리케이션이 올바르게 포맷된 콘텐츠를 표시하도록 보장합니다. Intl API는 로케일 기본 설정 배열을 수락하고 지원되는 첫 번째 옵션을 선택하여 폴백을 자동으로 처리합니다.

주요 개념:

  • 자동 폴백을 위해 Intl 생성자에 로케일 배열을 전달하세요
  • 런타임은 배열에서 지원하는 첫 번째 로케일을 선택합니다
  • supportedLocalesOf()를 사용하여 사용 가능한 로케일을 확인하세요
  • 특정 로케일에서 일반 로케일로 진행되는 폴백 체인을 구축하세요
  • localeMatcher 옵션은 매칭 알고리즘을 제어합니다
  • 자동 사용자 선호도 처리를 위해 navigator.languages를 직접 전달하세요
  • 영어와 같이 널리 지원되는 최종 폴백을 항상 포함하세요
  • 일치하는 로케일이 없으면 런타임은 시스템 기본값으로 폴백합니다

대부분의 애플리케이션에서는 로케일 배열과 함께 자동 폴백을 사용하세요. 어떤 로케일을 어떤 순서로 시도할지 특정 제어가 필요한 경우 사용자 정의 폴백 로직을 구현하세요.