JavaScriptでロケール固有の区切り文字を使って配列をフォーマットするにはどうすればよいですか?

Intl.ListFormatを使用して、任意の言語に対して正しいカンマ、スペース、句読点を自動的に適用します。

はじめに

配列を読みやすい文字列に変換する際、項目をカンマやその他の句読点で区切る必要があります。言語によって区切り文字は異なります。英語ではカンマとスペースを使用し、日本語では列挙コンマ「、」を使用し、アラビア語では異なる句読点と語順を使用します。

Intl.ListFormat APIは、ロケールに適した区切り文字を使用して配列を文字列に変換します。これにより、あらゆる言語のユーザーにとって自然なリストの表示が保証されます。

配列の区切り文字がロケールによって異なる理由

すべての言語がリスト項目の区切りにカンマを使用すると思われるかもしれませんが、それは事実ではありません。

英語では項目をカンマとスペースで区切ります。

// 英語: "red, green, blue"

日本語では列挙コンマ「、」を使用し、スペースは入れません。

// 日本語: "赤、緑、青"

中国語も同じ列挙コンマ「、」の文字を使用します。

// 中国語: "红、绿、蓝"

アラビア語では異なるカンマ文字「،」を使用し、右から左へ読みます。

// アラビア語: "أحمر، أخضر، أزرق"

これらの違いはIntl.ListFormatで自動的に処理されます。各言語の句読点ルールを知る必要はありません。

join()の問題点

Array.prototype.join()メソッドは、指定した区切り文字を使用して配列を文字列に変換します。

const colors = ["red", "green", "blue"];
console.log(colors.join(", "));
// "red, green, blue"

これは英語の句読点をハードコードしています。カンマとスペースの区切り文字は他の言語では適切に機能しません。

const colors = ["赤", "緑", "青"];
console.log(colors.join(", "));
// "赤, 緑, 青" (誤り - カンマ「,」ではなく「、」を使用すべき)

ロケールに基づいて区切り文字を手動で切り替えることはできません。なぜなら、すべての言語とその句読点ルールのマッピングを維持する必要があるからです。このマッピングは不完全で維持が困難です。

ロケール対応の区切り文字に Intl.ListFormat を使用する

Intl.ListFormat コンストラクターは、あらゆるロケールに対して正しい区切り文字を適用するフォーマッターを作成します。

const formatter = new Intl.ListFormat("en");
const colors = ["red", "green", "blue"];
console.log(formatter.format(colors));
// "red, green, and blue"

フォーマッターは指定されたロケールに適した句読点を自動的に使用します。最初の引数としてロケールコードを渡します。

const enFormatter = new Intl.ListFormat("en");
const jaFormatter = new Intl.ListFormat("ja");
const arFormatter = new Intl.ListFormat("ar");

const colors = ["red", "green", "blue"];

console.log(enFormatter.format(colors));
// "red, green, and blue"

console.log(jaFormatter.format(["赤", "緑", "青"]));
// "赤、緑、青"

console.log(arFormatter.format(["أحمر", "أخضر", "أزرق"]));
// "أحمر، أخضر، أزرق"

ブラウザが句読点のルールを提供するため、ロケール固有のコードを管理する必要はありません。

言語間で変化する区切り文字

フォーマッターはロケールに基づいて異なる区切り文字を適用します。以下の例は、同じ配列が異なる形式でフォーマットされる様子を示しています。

英語ではカンマ、スペース、および「and」という単語を使用します。

const formatter = new Intl.ListFormat("en");
console.log(formatter.format(["apple", "orange", "banana"]));
// "apple, orange, and banana"

スペイン語ではカンマ、スペース、および「y」という単語を使用します。

const formatter = new Intl.ListFormat("es");
console.log(formatter.format(["manzana", "naranja", "plátano"]));
// "manzana, naranja y plátano"

フランス語ではカンマ、スペース、および「et」という単語を使用します。

const formatter = new Intl.ListFormat("fr");
console.log(formatter.format(["pomme", "orange", "banane"]));
// "pomme, orange et banane"

ドイツ語ではカンマ、スペース、および「und」という単語を使用します。

const formatter = new Intl.ListFormat("de");
console.log(formatter.format(["Apfel", "Orange", "Banane"]));
// "Apfel, Orange und Banane"

日本語では列挙コンマ「、」と文字「、」を使用します。

const formatter = new Intl.ListFormat("ja");
console.log(formatter.format(["りんご", "オレンジ", "バナナ"]));
// "りんご、オレンジ、バナナ"

中国語では列挙コンマ「、」と単語「和」を使用します。

const formatter = new Intl.ListFormat("zh");
console.log(formatter.format(["苹果", "橙子", "香蕉"]));
// "苹果、橙子和香蕉"

韓国語ではカンマと助詞「및」を使用します。

const formatter = new Intl.ListFormat("ko");
console.log(formatter.format(["사과", "오렌지", "바나나"]));
// "사과, 오렌지 및 바나나"

フォーマッターはこれらの違いをすべて自動的に処理します。すべての言語で同じコードを記述するだけです。

ユーザーのロケールの使用

ブラウザの設定からユーザーの優先ロケールを検出し、それを使用してリストをフォーマットすることができます。

const userLocale = navigator.language;
const formatter = new Intl.ListFormat(userLocale);
const items = ["first", "second", "third"];
console.log(formatter.format(items));

これにより、リストはユーザーの期待に合った区切り記号を使用することが保証されます。フランス語のブラウザ設定を持つユーザーはフランス語の句読点を見ることになります。日本語の設定を持つユーザーは日本語の句読点を見ることになります。

接続詞なしで配列をフォーマットする

デフォルトのIntl.ListFormatの動作では、最後の項目の前に「and」のような接続詞が追加されます。unitタイプを使用することでこれを無効にできます。

const formatter = new Intl.ListFormat("en", { type: "unit" });
console.log(formatter.format(["5 km", "12 minutes", "100 calories"]));
// "5 km, 12 minutes, 100 calories"

unitタイプは接続詞を追加せず、区切り記号のみを使用します。これは技術的なリスト、測定値、または接続詞が適切でないデータに役立ちます。

const enFormatter = new Intl.ListFormat("en", { type: "unit" });
const jaFormatter = new Intl.ListFormat("ja", { type: "unit" });

console.log(enFormatter.format(["Item A", "Item B", "Item C"]));
// "Item A, Item B, Item C"

console.log(jaFormatter.format(["項目A", "項目B", "項目C"]));
// "項目A、項目B、項目C"

接続詞がなくても、区切り記号の句読点はロケールのルールに従います。

再利用可能なフォーマッターの作成

フォーマッターを一度作成し、複数の配列に再利用することができます。これは配列ごとに新しいフォーマッターを作成するよりも効率的です。

const formatter = new Intl.ListFormat("en");

console.log(formatter.format(["red", "green"]));
// "red and green"

console.log(formatter.format(["a", "b", "c", "d"]));
// "a, b, c, and d"

console.log(formatter.format(["one"]));
// "one"

同じフォーマッターは任意の長さの配列に対して機能します。配列内の項目数に基づいて、適切な区切り記号と接続詞が適用されます。

空の配列の処理

空の配列をフォーマットすると、フォーマッタは空の文字列を返します。

const formatter = new Intl.ListFormat("en");
console.log(formatter.format([]));
// ""

異なる動作が必要な場合は、フォーマットする前に空の配列をチェックする必要があります。

function formatList(items, locale) {
  if (items.length === 0) {
    return "No items";
  }
  const formatter = new Intl.ListFormat(locale);
  return formatter.format(items);
}

console.log(formatList([], "en"));
// "No items"

console.log(formatList(["apple"], "en"));
// "apple"

これにより、空の配列がユーザーにどのように表示されるかをコントロールできます。

ブラウザサポート

Intl.ListFormat APIはすべての最新ブラウザで利用可能です。Chrome、Firefox、Safari、Edgeで2021年4月以降サポートされています。

使用前にAPIが存在するかどうかを確認できます。

if (typeof Intl.ListFormat !== "undefined") {
  const formatter = new Intl.ListFormat("en");
  console.log(formatter.format(["a", "b", "c"]));
} else {
  console.log("Intl.ListFormat is not supported");
}

古いブラウザでは、join()メソッドにフォールバックできます。これにより、ロケール固有の区切り文字なしで基本的なフォーマットが提供されます。

function formatList(items, locale) {
  if (typeof Intl.ListFormat !== "undefined") {
    const formatter = new Intl.ListFormat(locale);
    return formatter.format(items);
  }
  return items.join(", ");
}

console.log(formatList(["red", "green", "blue"], "en"));
// "red, green, and blue" (または古いブラウザでは "red, green, blue")

これにより、すべてのブラウザでコードが動作し、最新のブラウザでは最高の体験を提供することができます。