Unicodeエクステンションでロケールをカスタマイズする方法

ロケール識別子に暦システム、数値フォーマット、時間表示設定を追加する

はじめに

ロケール識別子(例:en-US)は、JavaScriptにフォーマットに使用する言語と地域を伝えます。しかし、どのカレンダーシステムを使用するか、どの数字形式を表示するか、時間を12時間形式で表示するか24時間形式で表示するかは指定しません。これらのフォーマット設定は、場所だけでなくユーザーの選択によっても異なります。

Unicode拡張はこの問題を解決します。ロケール識別子に直接フォーマット設定を追加することができます。各フォーマッタに対して個別の設定オプションを使用する代わりに、ロケール文字列自体に設定を一度エンコードします。

このガイドでは、Unicode拡張の仕組み、利用可能な拡張タイプ、および国際化コードでの使用タイミングについて説明します。

Unicode拡張とは

Unicode拡張は、フォーマット設定を指定するためにロケール識別子に追加する追加タグです。これらはBCP 47で定義された標準形式に従います。BCP 47はロケール識別子を定義するのと同じ仕様です。

拡張は -u- で始まり、その後にキーと値のペアが続きます。uはUnicodeを表します。各キーは2文字で、値はキータイプによって異なります。

const locale = "en-US-u-ca-gregory-hc-h12";

このロケール識別子は、グレゴリオ暦と12時間表示を使用するアメリカ英語を指定しています。

ロケール文字列に拡張を追加する方法

拡張は、言語、スクリプト、地域コンポーネントの後、ロケール識別子の末尾に表示されます。-u-マーカーは、コア識別子と拡張を区切ります。

基本構造は次のパターンに従います:

language-region-u-key-value-key-value

各キーと値のペアは1つのフォーマット設定を指定します。1つのロケール文字列に複数のキーと値のペアを含めることができます。

const japanese = new Intl.Locale("ja-JP-u-ca-japanese-nu-jpan");
console.log(japanese.calendar); // "japanese"
console.log(japanese.numberingSystem); // "jpan"

キーと値のペアの順序は重要ではありません。"en-u-ca-gregory-nu-latn""en-u-nu-latn-ca-gregory"はどちらも有効で同等です。

カレンダー拡張機能

ca キーは日付フォーマットに使用するカレンダーシステムを指定します。異なる文化では異なるカレンダーシステムが使用されており、宗教的または文化的な理由からグレゴリオ暦以外のカレンダーを好むユーザーもいます。

一般的なカレンダー値には以下があります:

  • gregory はグレゴリオ暦
  • buddhist は仏教暦
  • islamic はイスラム暦
  • hebrew はヘブライ暦
  • chinese は中国暦
  • japanese は日本の元号暦
const islamicLocale = new Intl.Locale("ar-SA-u-ca-islamic");
const date = new Date("2025-03-15");
const formatter = new Intl.DateTimeFormat(islamicLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.format(date));
// 出力: "٢٠ رمضان ١٤٤٦ هـ"

これはイスラム暦に従って日付をフォーマットします。同じグレゴリオ暦の日付でも、イスラム暦では年、月、日が異なって表示されます。

仏教暦はタイで一般的に使用されています。仏陀の誕生(紀元前543年)から年を数えるため、仏教暦の年はグレゴリオ暦より543年進んでいます。

const buddhistLocale = new Intl.Locale("th-TH-u-ca-buddhist");
const formatter = new Intl.DateTimeFormat(buddhistLocale, {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.format(new Date("2025-03-15")));
// 出力: "15 มีนาคม 2568"

グレゴリオ暦の2025年は、仏教暦では2568年になります。

数字体系拡張機能

nu キーは数字の表示に使用する数字体系を指定します。ほとんどのロケールは西アラビア数字(0-9)を使用していますが、多くの地域には独自の伝統的な数字体系があります。

一般的な数字体系の値には以下があります:

  • latn は西アラビア数字(0-9)
  • arab はアラビア・インド数字
  • hanidec は中国の十進数字
  • deva はデーヴァナーガリー数字
  • thai はタイ数字
const arabicLocale = new Intl.Locale("ar-EG-u-nu-arab");
const number = 123456;
const formatter = new Intl.NumberFormat(arabicLocale);

console.log(formatter.format(number));
// 出力: "١٢٣٬٤٥٦"

アラビア・インド数字は西洋の数字とは見た目が異なりますが、同じ値を表します。数字123456は١٢٣٬٤٥٦と表示されます。

タイ数字の例:

const thaiLocale = new Intl.Locale("th-TH-u-nu-thai");
const formatter = new Intl.NumberFormat(thaiLocale);

console.log(formatter.format(123456));
// 出力: "๑๒๓,๔๕๖"

多くのアラビア語ロケールでは、アラビア・インド数字とラテン数字の両方をサポートしています。ユーザーは個人の好みやコンテキストに基づいて好みのシステムを選択できます。

時間周期の拡張機能

hc キーは時間の表示方法を指定します。地域によっては、AMとPM表示を伴う12時間制を好む場合もあれば、24時間制を好む場合もあります。時間周期は真夜中の表示方法も決定します。

4つの時間周期の値が利用可能です:

  • h12 は1-12時間を使用し、真夜中は12:00 AMとなります
  • h11 は0-11時間を使用し、真夜中は0:00 AMとなります
  • h23 は0-23時間を使用し、真夜中は0:00となります
  • h24 は1-24時間を使用し、真夜中は24:00となります

h12h11の値は12時間制を表し、h23h24は24時間制を表します。違いは時間範囲が0から始まるか1から始まるかにあります。

const us12Hour = new Intl.Locale("en-US-u-hc-h12");
const japan11Hour = new Intl.Locale("ja-JP-u-hc-h11");
const europe23Hour = new Intl.Locale("en-GB-u-hc-h23");

const date = new Date("2025-03-15T00:30:00");

console.log(new Intl.DateTimeFormat(us12Hour, { hour: "numeric", minute: "numeric" }).format(date));
// 出力: "12:30 AM"

console.log(new Intl.DateTimeFormat(japan11Hour, { hour: "numeric", minute: "numeric" }).format(date));
// 出力: "0:30 AM"

console.log(new Intl.DateTimeFormat(europe23Hour, { hour: "numeric", minute: "numeric" }).format(date));
// 出力: "00:30"

h12形式では真夜中は12:30 AMと表示され、h11では0:30 AMと表示されます。h23形式ではAMやPMなしで00:30と表示されます。

ほとんどのアプリケーションではh12またはh23が使用されます。h11形式は主に日本で使用され、h24は実際にはほとんど使用されません。

照合拡張機能

coキーは文字列のソートのための照合ルールを指定します。照合はテキストをソートする際の文字の順序を決定します。言語や地域によって異なるソート規則があります。

一般的な照合値には以下が含まれます:

  • standard 標準のUnicode照合用
  • phonebk 電話帳順序用(ドイツ語)
  • pinyin ピンイン順序用(中国語)
  • stroke 画数順序用(中国語)

ドイツ語の電話帳照合では、ウムラウトの扱いが標準照合とは異なります。電話帳順序ではソートの目的でäをae、öをoe、üをueに展開します。

const names = ["Müller", "Meyer", "Möller", "Mueller"];

const standard = new Intl.Collator("de-DE");
const phonebook = new Intl.Collator("de-DE-u-co-phonebk");

console.log(names.sort((a, b) => standard.compare(a, b)));
// 出力: ["Meyer", "Möller", "Mueller", "Müller"]

console.log(names.sort((a, b) => phonebook.compare(a, b)));
// 出力: ["Meyer", "Möller", "Mueller", "Müller"]

中国語の照合には複数の順序付けシステムがあります。ピンイン順序は発音によってソートし、画数順序は各文字を書くのに使用される筆画の数によってソートします。

const pinyinCollator = new Intl.Collator("zh-CN-u-co-pinyin");
const strokeCollator = new Intl.Collator("zh-CN-u-co-stroke");

照合拡張機能はIntl.Collator APIと、照合器を使用する場合のArray.prototype.sort()のようなメソッドにのみ影響します。

大文字小文字優先拡張

kfキーは照合順序において大文字または小文字のどちらが先にソートされるかを決定します。この設定は言語やユースケースによって異なります。

利用可能な値は3つあります:

  • upper 大文字を小文字より先にソートする
  • lower 小文字を大文字より先にソートする
  • false ロケールのデフォルトの大文字小文字順序を使用する
const words = ["apple", "Apple", "APPLE", "banana"];

const upperFirst = new Intl.Collator("en-US-u-kf-upper");
const lowerFirst = new Intl.Collator("en-US-u-kf-lower");

console.log(words.sort((a, b) => upperFirst.compare(a, b)));
// 出力: ["APPLE", "Apple", "apple", "banana"]

console.log(words.sort((a, b) => lowerFirst.compare(a, b)));
// 出力: ["apple", "Apple", "APPLE", "banana"]

大文字小文字優先順序は、単語が大文字小文字以外は同一である場合のソートに影響します。これは基本文字を比較した後の二次的なソート順序を決定します。

数値照合拡張

knキーは数値照合を有効にします。これにより、数値シーケンスが辞書順ではなく数値として並べ替えられます。数値照合がなければ、「10」は文字順で「1」が「2」より前にくるため、「2」より前にソートされます。

数値照合は2つの値を受け付けます:

  • true 数値照合を有効にする
  • false 数値照合を無効にする(デフォルト)
const items = ["item1", "item10", "item2", "item20"];

const standard = new Intl.Collator("en-US");
const numeric = new Intl.Collator("en-US-u-kn-true");

console.log(items.sort((a, b) => standard.compare(a, b)));
// 出力: ["item1", "item10", "item2", "item20"]

console.log(items.sort((a, b) => numeric.compare(a, b)));
// 出力: ["item1", "item2", "item10", "item20"]

数値照合を有効にすると、2は10より小さいため「item2」が正しく「item10」より前にソートされます。これにより、数字を含む文字列に対して期待通りのソート順序が得られます。

数値照合は、ファイル名、バージョン番号、住所、その他数字が埋め込まれたテキストのソートに役立ちます。

拡張文字列の代わりにオプションオブジェクトを使用する

ロケール文字列に拡張機能をエンコードする代わりに、Intl.Localeコンストラクタにオプションとして渡すことができます。このアプローチでは、基本ロケールとフォーマット設定を分離します。

const locale = new Intl.Locale("ja-JP", {
  calendar: "japanese",
  numberingSystem: "jpan",
  hourCycle: "h11"
});

console.log(locale.toString());
// 出力: "ja-JP-u-ca-japanese-hc-h11-nu-jpan"

コンストラクタはオプションを自動的に拡張タグに変換します。どちらのアプローチも同一のロケールオブジェクトを生成します。

オプションオブジェクトアプローチにはいくつかの利点があります。2文字のコードの代わりに完全なプロパティ名を使用することでコードの可読性が向上します。また、設定データからロケールを動的に構築することも容易になります。

const userPreferences = {
  language: "ar",
  region: "SA",
  calendar: "islamic",
  numberingSystem: "arab"
};

const locale = new Intl.Locale(`${userPreferences.language}-${userPreferences.region}`, {
  calendar: userPreferences.calendar,
  numberingSystem: userPreferences.numberingSystem
});

フォーマッタコンストラクタに直接オプションを渡すこともできます:

const formatter = new Intl.DateTimeFormat("th-TH", {
  calendar: "buddhist",
  numberingSystem: "thai",
  year: "numeric",
  month: "long",
  day: "numeric"
});

これにより、ロケール固有のフォーマットオプションと表示オプションを単一のコンストラクタ呼び出しで組み合わせることができます。

拡張機能とフォーマッタオプションをいつ使用するか

拡張機能とフォーマッタオプションは異なる目的を果たします。それぞれのアプローチをいつ使用するかを理解することで、より明確で保守しやすいコードを書くことができます。

フォーマット設定がユーザーのロケールに固有のものである場合は、ロケール文字列内で拡張機能を使用します。タイのユーザーが常に仏暦とタイ数字を表示したい場合は、それらの設定をロケール識別子にエンコードします。

const userLocale = "th-TH-u-ca-buddhist-nu-thai";

これにより、設定を繰り返すことなく、任意のフォーマッタにロケールを渡すことができます:

const dateFormatter = new Intl.DateTimeFormat(userLocale);
const numberFormatter = new Intl.NumberFormat(userLocale);

両方のフォーマッタは自動的に仏暦とタイ数字を使用します。

フォーマット設定が特定のユースケースに固有のものである場合は、フォーマッタオプションを使用します。アプリケーションの一部ではイスラム暦を表示し、他の部分ではグレゴリオ暦を表示したい場合は、特定のフォーマッタにカレンダーオプションを渡します。

const islamicFormatter = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic"
});

const gregorianFormatter = new Intl.DateTimeFormat("ar-SA", {
  calendar: "gregory"
});

同じロケール識別子でも、カレンダーオプションに基づいて異なるフォーマットが生成されます。

ロケール文字列内の拡張機能はデフォルトとして機能します。フォーマッタオプションは指定された場合、これらのデフォルトを上書きします。これにより、特定のフォーマッタをカスタマイズしながら、ユーザー設定をベースラインとして使用できます。

const locale = "en-US-u-hc-h23";
const formatter12Hour = new Intl.DateTimeFormat(locale, {
  hourCycle: "h12"
});

ユーザーは24時間表示を好みますが、この特定のフォーマッタはその設定を上書きして12時間表示を表示します。

ロケールから拡張値を読み取る

Intl.Localeオブジェクトは拡張値をプロパティとして公開しています。これらのプロパティを読み取ることで、ロケールのフォーマット設定を検査または検証できます。

const locale = new Intl.Locale("ar-SA-u-ca-islamic-nu-arab-hc-h12");

console.log(locale.calendar); // "islamic"
console.log(locale.numberingSystem); // "arab"
console.log(locale.hourCycle); // "h12"

これらのプロパティは、拡張が存在する場合はその値を返し、拡張が指定されていない場合はundefinedを返します。

これらのプロパティを使用して、設定インターフェースを構築したり、ユーザー設定を検証したりできます:

function describeLocalePreferences(localeString) {
  const locale = new Intl.Locale(localeString);

  return {
    language: locale.language,
    region: locale.region,
    calendar: locale.calendar || "default",
    numberingSystem: locale.numberingSystem || "default",
    hourCycle: locale.hourCycle || "default"
  };
}

console.log(describeLocalePreferences("th-TH-u-ca-buddhist-nu-thai"));
// 出力: { language: "th", region: "TH", calendar: "buddhist", numberingSystem: "thai", hourCycle: "default" }

collation、caseFirst、numericプロパティは、cokfkn拡張キーに対応しています:

const locale = new Intl.Locale("de-DE-u-co-phonebk-kf-upper-kn-true");

console.log(locale.collation); // "phonebk"
console.log(locale.caseFirst); // "upper"
console.log(locale.numeric); // true

numericプロパティは文字列ではなくブール値を返すことに注意してください。値trueは数値照合が有効であることを示します。

複数の拡張を組み合わせる

単一のロケール識別子で複数の拡張を組み合わせることができます。これにより、すべてのフォーマット設定を一度に指定できます。

const locale = new Intl.Locale("ar-SA-u-ca-islamic-nu-arab-hc-h12-co-standard");

const dateFormatter = new Intl.DateTimeFormat(locale, {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const date = new Date("2025-03-15T14:30:00");
console.log(dateFormatter.format(date));
// 出力はイスラム暦、アラビア・インド数字、12時間制を使用

各拡張キーはロケール文字列内で一度だけ表示できます。同じキーを複数回指定すると、最後の値が優先されます。

const locale = new Intl.Locale("en-US-u-hc-h23-hc-h12");
console.log(locale.hourCycle); // "h12"

プログラムでロケールを構築する場合は、あいまいさを避けるために各拡張キーが一度だけ表示されるようにしてください。

実用的なユースケース

Unicodeの拡張機能は国際化アプリケーションにおける実際の問題を解決します。一般的なユースケースを理解することで、拡張機能を効果的に適用できるようになります。

ユーザー設定の保存

複数の設定フィールドではなく、単一のロケール文字列にユーザーのフォーマット設定を保存します:

function saveUserPreferences(userId, localeString) {
  const locale = new Intl.Locale(localeString);

  return {
    userId,
    language: locale.language,
    region: locale.region,
    localeString: locale.toString(),
    preferences: {
      calendar: locale.calendar,
      numberingSystem: locale.numberingSystem,
      hourCycle: locale.hourCycle
    }
  };
}

const preferences = saveUserPreferences(123, "ar-SA-u-ca-islamic-nu-arab-hc-h12");

このアプローチでは、個々のコンポーネントへの構造化されたアクセスを提供しながら、フォーマット設定を単一の文字列として保存します。

ロケールセレクターの構築

拡張機能を持つロケール文字列を構築することで、ユーザーがUIを通じてフォーマット設定を選択できるようにします:

function buildLocaleFromUserInput(language, region, preferences) {
  const options = {};

  if (preferences.calendar) {
    options.calendar = preferences.calendar;
  }

  if (preferences.numberingSystem) {
    options.numberingSystem = preferences.numberingSystem;
  }

  if (preferences.hourCycle) {
    options.hourCycle = preferences.hourCycle;
  }

  const locale = new Intl.Locale(`${language}-${region}`, options);
  return locale.toString();
}

const userLocale = buildLocaleFromUserInput("th", "TH", {
  calendar: "buddhist",
  numberingSystem: "thai",
  hourCycle: "h23"
});

console.log(userLocale);
// 出力: "th-TH-u-ca-buddhist-hc-h23-nu-thai"

宗教的カレンダーへの対応

宗教コミュニティにサービスを提供するアプリケーションは、それらのカレンダーシステムをサポートする必要があります:

function createReligiousCalendarFormatter(religion, baseLocale) {
  const calendars = {
    jewish: "hebrew",
    muslim: "islamic",
    buddhist: "buddhist"
  };

  const calendar = calendars[religion];
  if (!calendar) {
    return new Intl.DateTimeFormat(baseLocale);
  }

  const locale = new Intl.Locale(baseLocale, { calendar });
  return new Intl.DateTimeFormat(locale, {
    year: "numeric",
    month: "long",
    day: "numeric"
  });
}

const jewishFormatter = createReligiousCalendarFormatter("jewish", "en-US");
console.log(jewishFormatter.format(new Date("2025-03-15")));
// 出力: "15 Adar II 5785"

カスタムルールによる並べ替え

照合拡張機能を使用してロケール固有の並べ替えを実装します:

function sortNames(names, locale, collationType) {
  const localeWithCollation = new Intl.Locale(locale, {
    collation: collationType
  });

  const collator = new Intl.Collator(localeWithCollation);
  return names.sort((a, b) => collator.compare(a, b));
}

const germanNames = ["Müller", "Meyer", "Möller", "Mueller"];
const sorted = sortNames(germanNames, "de-DE", "phonebk");
console.log(sorted);

伝統的な数字の表示

文化的に適切な表示のために伝統的な数字体系で数値を表示します:

function formatTraditionalNumber(number, locale, numberingSystem) {
  const localeWithNumbering = new Intl.Locale(locale, {
    numberingSystem
  });

  return new Intl.NumberFormat(localeWithNumbering).format(number);
}

console.log(formatTraditionalNumber(123456, "ar-EG", "arab"));
// 出力: "١٢٣٬٤٥٦"

console.log(formatTraditionalNumber(123456, "th-TH", "thai"));
// 出力: "๑๒๓,๔๕๖"

ブラウザサポート

Unicodeの拡張機能はすべての最新ブラウザで動作します。Chrome、Firefox、Safari、およびEdgeはロケール識別子の拡張構文とIntl.Localeオブジェクトの対応するプロパティをサポートしています。

特定の拡張値の利用可能性はブラウザの実装に依存します。すべてのブラウザは、カレンダーのgregory、数字体系のlatn、時間周期のh12またはh23などの一般的な値をサポートしています。伝統的な中国のカレンダーやマイノリティ言語の数字体系などのあまり一般的でない値は、すべてのブラウザで動作しない場合があります。

あまり一般的でない拡張値を使用する場合は、対象ブラウザでロケール識別子をテストしてください。ブラウザが拡張値を認識したかどうかを確認するには、Intl.Localeプロパティを使用します:

const locale = new Intl.Locale("zh-CN-u-ca-chinese");
console.log(locale.calendar);
// ブラウザが中国のカレンダーをサポートしている場合: "chinese"
// ブラウザがサポートしていない場合: undefined

Node.jsはバージョン12からUnicode拡張をサポートし、バージョン18以降ですべてのプロパティの完全なサポートを提供しています。

まとめ

Unicode拡張機能を使用すると、ロケール識別子にフォーマット設定の環境設定を追加できます。各フォーマッタを個別に設定する代わりに、ロケール文字列に環境設定を一度にエンコードします。

主要な概念:

  • 拡張機能は-u-で始まり、その後にキーと値のペアが続きます
  • caキーはカレンダーシステムを指定します
  • nuキーは数字体系を指定します
  • hcキーは時間周期フォーマットを指定します
  • coキーは照合規則を指定します
  • kfキーは大文字小文字優先順序を指定します
  • knキーは数値照合を有効にします
  • 拡張文字列またはオプションオブジェクトを使用できます
  • 拡張機能はフォーマッタオプションでオーバーライドできるデフォルトとして機能します
  • Intl.Localeオブジェクトは拡張機能をプロパティとして公開します

Unicode拡張機能を使用して、ユーザー環境設定を保存し、文化的なカレンダーを尊重し、伝統的な数字を表示し、ロケール固有のソートを実装します。これらはJavaScriptの国際化コードでフォーマット動作をカスタマイズする標準的な方法を提供します。