優先ロケールが利用できない場合のロケールフォールバックの処理方法

ユーザーがサポートされていないロケールを希望する場合に、サポートされている言語を自動的に選択する

はじめに

すべての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));
// 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は、サポートされているロケールを選択するか、システムのデフォルトにフォールバックすることを保証します。

複数のロケールオプションの提供

フォールバックを実装する最も簡単な方法は、優先順位に従ってロケールの配列を渡すことです。これは、DateTimeFormatNumberFormatCollatorなどを含むすべての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の2つのロケールマッチングアルゴリズムをサポートしています。フォーマッターを作成する際に、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は自動的に第二希望、次に第三希望というように試行します。

このパターンは、フォールバックチェーンを手動で構築することなく、ユーザーのすべての言語設定を尊重したい場合に適しています。

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));
// Tries Canadian French first
// Falls back through the user's complete preference list

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

システムデフォルトにより、完全に無効またはサポートされていないロケールリストであっても、フォーマッターは常に動作します。アプリケーションはロケールの問題によってエラーをスローすることはありません。

システムデフォルトに依存する代わりに特定のフォールバックを確保するには、ロケール配列の最後にenen-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を直接渡す
  • 常に英語のような広くサポートされている最終フォールバックを含める
  • ロケールが一致しない場合、ランタイムはシステムデフォルトにフォールバックする

ほとんどのアプリケーションでは、ロケール配列による自動フォールバックを使用してください。どのロケールをどの順序で試行するかについて特定の制御が必要な場合は、カスタムフォールバックロジックを実装してください。