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

ロケール識別子に暦システム、数値形式、時刻表示設定を追加する

はじめに

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

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

このガイドでは、Unicodeエクステンションの仕組み、利用可能なエクステンションタイプ、国際化コードでの使用タイミングについて説明します。

Unicodeエクステンションとは

Unicodeエクステンションは、フォーマット設定を指定するためにロケール識別子に追加する追加タグです。ロケール識別子を定義する仕様と同じ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));
// Output: "٢٠ رمضان ١٤٤٦ هـ"

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

仏暦はタイで一般的に使用されています。仏暦は紀元前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")));
// Output: "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));
// Output: "١٢٣٬٤٥٦"

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

タイ数字も別の例を示しています。

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

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

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

時刻表示形式の拡張

hcキーは時刻の表示方法を指定します。一部の地域では午前・午後表示付きの12時間制を好み、他の地域では24時間制を好みます。時刻表示形式は、午前0時の表示方法も決定します。

4つの時刻表示形式の値が利用可能です。

  • h12は1〜12時を使用し、午前0時を12:00 AMと表示します
  • h11は0〜11時を使用し、午前0時を0:00 AMと表示します
  • h23は0〜23時を使用し、午前0時を0:00と表示します
  • h24は1〜24時を使用し、午前0時を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));
// Output: "12:30 AM"

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

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

h12形式では午前0時を12:30 AMと表示し、h11では0:30 AMと表示します。h23形式では、午前・午後表示なしで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)));
// Output: ["Meyer", "Möller", "Mueller", "Müller"]

console.log(names.sort((a, b) => phonebook.compare(a, b)));
// Output: ["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)));
// Output: ["APPLE", "Apple", "apple", "banana"]

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

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

数値照合順序拡張

knキーは数値照合順序を有効にし、数値シーケンスを辞書順ではなく数値として並べ替えます。数値照合順序がない場合、「10」は「2」より前に並べ替えられます。これは文字順序で「1」が「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)));
// Output: ["item1", "item10", "item2", "item20"]

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

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

数値照合は、ファイル名、バージョン番号、住所、および数値が埋め込まれたテキストのソートに便利です。

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

ロケール文字列に拡張を埋め込む代わりに、Intl.Localeコンストラクタにoptionsとして渡すことができます。このアプローチは、基本ロケールとフォーマット設定を分離します。

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

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

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

optionsオブジェクトアプローチにはいくつかの利点があります。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
});

フォーマッタコンストラクタに直接optionsを渡すこともできます。

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

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

拡張とフォーマッタoptionsをいつ使用するか

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

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

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"));
// Output: { 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));
// Output uses Islamic calendar, Arabic-Indic numerals, and 12-hour time

各拡張キーは、ロケール文字列内に1回のみ出現できます。同じキーを複数回指定した場合、最後の値が優先されます。

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);
// Output: "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")));
// Output: "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"));
// Output: "١٢٣٬٤٥٦"

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

ブラウザサポート

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);
// If browser supports Chinese calendar: "chinese"
// If browser does not support it: undefined

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

まとめ

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

主要な概念:

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

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