ロケールから言語、国、スクリプトを抽出する方法

JavaScript を使ってロケール識別子を分解し、それぞれの要素にアクセスする

はじめに

en-USfr-CA、そして zh-Hans-CN のようなロケール識別子は、複数の情報をひとつの文字列にまとめています。これらの要素は、どの言語が使われているか、その言語が話されている地域、さらに時にはどの文字体系か(スクリプト)を示します。

国際化対応アプリケーションを開発する際、これらの個別要素を取り出す必要がよくあります。たとえば、ユーザーには言語名だけを表示したり、ロケールを地域ごとにグループ分けしたり、どのスクリプトが使われているかを確認したい場合があります。文字列を正規表現で手動解析する代わりに、JavaScript にはこのような要素を確実に抽出できる Intl.Locale API が用意されています。

このガイドでは、ロケール識別子にどんな要素が含まれているか、Intl.Locale API を使ってどうやってそれらを抽出するか、そして実際にどんな場面でこれらの要素を使うのかを解説します。

ロケール識別子に含まれる要素とは

ロケール識別子は BCP 47 規格に準拠しており、言語や地域のバリエーションを表現するための構造が定義されています。完全なロケール識別子は、複数の要素がハイフンで区切られて並びます。

よく使われる主な要素は次の3つです:

  • 言語:英語、スペイン語、中国語といった主要な言語
  • 地域:アメリカ、カナダ、中国など、その言語が使われる地理的な地域
  • スクリプト:ラテン文字、キリル文字、漢字といった、言語を表すための文字体系

単純なロケール識別子は言語コードだけが含まれています:

en

多くのロケール識別子は言語と地域を含みます:

en-US
fr-CA
es-MX

言語が複数の文字体系で表記可能な場合、スクリプト要素が含まれることもあります:

zh-Hans-CN
zh-Hant-TW
sr-Cyrl-RS
sr-Latn-RS

これらのコンポーネントを理解することで、言語のフォールバック、コンテンツの選択、ユーザーインターフェースのカスタマイズに関する意思決定がしやすくなります。

Intl.Locale を使ってコンポーネントを抽出する

Intl.Locale API はロケール識別子の文字列を構造化されたオブジェクトに変換します。ロケールオブジェクトを作成すると、そのプロパティから各コンポーネントを読み取ることができます。

識別子をコンストラクターに渡してロケールオブジェクトを作成します:

const locale = new Intl.Locale("en-US");

console.log(locale.language); // "en"
console.log(locale.region); // "US"

ロケールオブジェクトは、識別子の各コンポーネントに対応するプロパティを持っています。これらのプロパティを利用することで、文字列を解析せずとも構造化された形でアクセスできます。

言語コードを抽出する

language プロパティは、ロケール識別子内の言語コンポーネントを返します。これは主に使われる言語を表す、2文字または3文字のコードです。

const english = new Intl.Locale("en-US");
console.log(english.language); // "en"

const french = new Intl.Locale("fr-CA");
console.log(french.language); // "fr"

const chinese = new Intl.Locale("zh-Hans-CN");
console.log(chinese.language); // "zh"

言語コードは ISO 639 標準に従います。よく使われるコードには、英語の場合は en、スペイン語は es、フランス語は fr、ドイツ語は de、日本語は ja、中国語は zh があります。

言語コードは有効なロケール識別子に必ず含まれています。唯一必須のコンポーネントです。

const languageOnly = new Intl.Locale("ja");
console.log(languageOnly.language); // "ja"
console.log(languageOnly.region); // undefined

言語コードを抽出したら、それを使って翻訳の選択やテキスト処理ルールの判別、ユーザー向けの言語セレクターの作成などに活用できます。

リージョンコードを抽出する

region プロパティは、ロケール識別子の地域部分を返します。これは言語が使われている地理的領域を示す2文字のコードです。

const americanEnglish = new Intl.Locale("en-US");
console.log(americanEnglish.region); // "US"

const britishEnglish = new Intl.Locale("en-GB");
console.log(britishEnglish.region); // "GB"

const canadianFrench = new Intl.Locale("fr-CA");
console.log(canadianFrench.region); // "CA"

リージョンコードは ISO 3166-1 標準に準拠しています。2文字の大文字で国や地域を表現します。代表例として、アメリカ合衆国は US、イギリスは GB、カナダは CA、メキシコは MX、フランスは FR、中国は CN などがあります。

地域コードによって、日付、数値、通貨の書式が変わります。アメリカ英語では月・日・年の順で日付を書き、小数点にはピリオドを使用します。イギリス英語では日・月・年の順で日付を書き、千単位の区切りにはカンマを使用します。

ロケール識別子の中で地域コードは省略可能です。ロケールに地域が指定されていない場合、region プロパティは undefined を返します:

const genericSpanish = new Intl.Locale("es");
console.log(genericSpanish.region); // undefined

地域コードを抽出すると、その情報を使って地域に合わせた書式設定や、特定地域向けコンテンツの選択、ユーザーへの位置情報表示ができます。

スクリプトコードの抽出

script プロパティは、ロケール識別子のスクリプト要素を返します。これは、言語を表すのに使われる文字体系を示す4文字のコードです。

const simplifiedChinese = new Intl.Locale("zh-Hans-CN");
console.log(simplifiedChinese.script); // "Hans"

const traditionalChinese = new Intl.Locale("zh-Hant-TW");
console.log(traditionalChinese.script); // "Hant"

const serbianCyrillic = new Intl.Locale("sr-Cyrl-RS");
console.log(serbianCyrillic.script); // "Cyrl"

const serbianLatin = new Intl.Locale("sr-Latn-RS");
console.log(serbianLatin.script); // "Latn"

スクリプトコードは ISO 15924 標準に従っています。最初の文字が大文字で合計4文字です。よく使われるコードにはラテン文字用のLatn、キリル文字用のCyrl、簡体字漢字用のHans、繁体字漢字用のHant、アラビア文字用のArab があります。

ほとんどのロケールは、その言語に標準的な文字体系があるためスクリプトコードを省略します。英語はラテン文字が標準なので、en と書くだけでよく、en-Latn のように記述する必要はありません。ロシア語はキリル文字が標準なので、ru だけを書き、ru-Cyrl とはしません。

言語が複数の文字体系で表記できる場合、スクリプトコードが使われます。中国語は簡体字と繁体字両方があり、セルビア語はキリル文字とラテン文字の両方が使われます。このような場合、どの文字体系を使うかを明確にするためにスクリプトコードを指定します。

ロケールに明示的なスクリプトコードがない場合、script プロパティは undefined を返します:

const english = new Intl.Locale("en-US");
console.log(english.script); // undefined

スクリプトコードを抽出することで、フォントの選択、テキストレンダリングの調整、文字体系によるコンテンツのフィルタリングなどが可能です。

コンポーネントが未定義の場合の理解

すべてのロケール識別子にすべてのコンポーネントが含まれているわけではありません。言語コードは必須ですが、地域やスクリプトは任意です。

識別子にコンポーネントが含まれていない場合、対応するプロパティはundefinedを返します。

const locale = new Intl.Locale("fr");

console.log(locale.language); // "fr"
console.log(locale.region); // undefined
console.log(locale.script); // undefined

この動作により、値を使用する前にロケールが地域やスクリプトを指定しているかどうかを確認できます。

const locale = new Intl.Locale("en-US");

if (locale.region) {
  console.log(`Region-specific formatting for ${locale.region}`);
} else {
  console.log("Using default formatting");
}

デフォルト値を指定するには、null合体演算子を使うことができます。

const locale = new Intl.Locale("es");
const region = locale.region ?? "ES";

console.log(region); // "ES"

ロケールのフォールバックチェーンを作成する際、未定義コンポーネントをチェックすることで代替案を組み立てやすくなります。

function buildFallbackChain(identifier) {
  const locale = new Intl.Locale(identifier);
  const fallbacks = [identifier];

  if (locale.region) {
    fallbacks.push(locale.language);
  }

  return fallbacks;
}

console.log(buildFallbackChain("fr-CA")); // ["fr-CA", "fr"]
console.log(buildFallbackChain("fr")); // ["fr"]

これにより、より詳細なものから汎用的なものへと順序付けられたロケール識別子のリストが作成されます。

コンポーネント抽出の実用例

ロケールのコンポーネントを抽出することで、国際化アプリ開発時の一般的な問題をいくつか解決できます。

言語別にロケールをグループ化

利用可能な言語のリストを表示するとき、同じ言語コードを持つロケールごとにグループ化しましょう。

const locales = ["en-US", "en-GB", "fr-FR", "fr-CA", "es-ES", "es-MX"];

const grouped = locales.reduce((groups, identifier) => {
  const locale = new Intl.Locale(identifier);
  const language = locale.language;

  if (!groups[language]) {
    groups[language] = [];
  }

  groups[language].push(identifier);
  return groups;
}, {});

console.log(grouped);
// {
//   en: ["en-US", "en-GB"],
//   fr: ["fr-FR", "fr-CA"],
//   es: ["es-ES", "es-MX"]
// }

この分類により、ユーザーは自分の好みに合った地域バリエーションを見つけやすくなります。

ロケールセレクターの作成

言語選択UIを作成するときは、各コンポーネントを抽出して分かりやすいラベルを表示しましょう。

function buildLocaleSelector(identifiers) {
  return identifiers.map(identifier => {
    const locale = new Intl.Locale(identifier);

    const languageNames = new Intl.DisplayNames([identifier], {
      type: "language"
    });

    const regionNames = new Intl.DisplayNames([identifier], {
      type: "region"
    });

    return {
      value: identifier,
      language: languageNames.of(locale.language),
      region: locale.region ? regionNames.of(locale.region) : null
    };
  });
}

const options = buildLocaleSelector(["en-US", "en-GB", "fr-FR"]);
console.log(options);
// [
//   { value: "en-US", language: "English", region: "United States" },
//   { value: "en-GB", language: "English", region: "United Kingdom" },
//   { value: "fr-FR", language: "French", region: "France" }
// ]

これにより、それぞれのロケールオプションに人が理解しやすいラベルを表示できます。

地域によるフィルタリング

特定の地域向けのコンテンツを表示する場合は、地域コードを抽出してロケールをフィルタリングしましょう。

function filterByRegion(identifiers, targetRegion) {
  return identifiers.filter(identifier => {
    const locale = new Intl.Locale(identifier);
    return locale.region === targetRegion;
  });
}

const allLocales = ["en-US", "es-US", "en-GB", "fr-FR", "zh-CN"];
const usLocales = filterByRegion(allLocales, "US");

console.log(usLocales); // ["en-US", "es-US"]

これにより、特定の国のユーザーに適したロケールを選択できます。

スクリプト互換性の確認

フォントを選択したりテキストを表示したりする際は、対象スクリプトに対応しているか確認しましょう。

function selectFont(identifier) {
  const locale = new Intl.Locale(identifier);
  const script = locale.script;

  if (script === "Hans" || script === "Hant") {
    return "Noto Sans CJK";
  } else if (script === "Arab") {
    return "Noto Sans Arabic";
  } else if (script === "Cyrl") {
    return "Noto Sans";
  } else {
    return "Noto Sans";
  }
}

console.log(selectFont("zh-Hans-CN")); // "Noto Sans CJK"
console.log(selectFont("ar-SA")); // "Noto Sans Arabic"
console.log(selectFont("en-US")); // "Noto Sans"

これにより、各書記体系ごとに正しくテキストが表示されます。

言語フォールバックの実装

ユーザーの希望するロケールが利用できない場合は、ベース言語へフォールバックしましょう。

function selectBestLocale(userPreference, supportedLocales) {
  const user = new Intl.Locale(userPreference);

  if (supportedLocales.includes(userPreference)) {
    return userPreference;
  }

  const languageMatch = supportedLocales.find(supported => {
    const locale = new Intl.Locale(supported);
    return locale.language === user.language;
  });

  if (languageMatch) {
    return languageMatch;
  }

  return supportedLocales[0];
}

const supported = ["en-US", "fr-FR", "es-ES"];

console.log(selectBestLocale("en-GB", supported)); // "en-US"
console.log(selectBestLocale("fr-CA", supported)); // "fr-FR"
console.log(selectBestLocale("de-DE", supported)); // "en-US"

これにより、完全一致が見つからなくてもスムーズにフォールバックできます。