単位付きの計測値リストをどのようにフォーマットしますか?

JavaScriptのIntl APIを使用して、5 km、10 km、15 kmなどの複数の計測値をロケールに適したリスト形式で表示する方法

はじめに

測定値を表示するアプリケーションでは、複数の値をまとめて表示する必要がよくあります。フィットネスアプリでは「5 km、10 km、15 km」のようにスプリットタイムを表示するかもしれません。天気アプリでは「20°C、22°C、25°C、23°C」のように週間の気温を表示するかもしれません。レシピでは「2カップ、大さじ1、小さじ3」のように材料の量を一覧表示するかもしれません。

これらのリストには2つの国際化の課題が組み合わさっています。まず、各測定値にはロケールに適した単位フォーマットが必要です。次に、リスト自体には対象言語に適した句読点と区切り記号が必要です。英語ではカンマを使用し、時には「and」のような接続詞を使います。他の言語では異なる区切り記号を使用し、異なる文法規則に従います。

JavaScriptはこの問題を解決するための2つのAPIを提供しています。Intl.NumberFormatは単位付きの個々の測定値をフォーマットします。Intl.ListFormatは複数の値を文法的に正しいリストに結合します。このレッスンでは、両方のAPIを一緒に使用して、どのロケールでもユーザーの期待に合った測定値のリストをフォーマットする方法を説明します。

測定値のリストには2つのフォーマット手順が必要

測定値のリストをフォーマットする場合、どちらのフォーマット手順も省略できません。測定値をフォーマットせずにリストをフォーマットすると、ロケールに適した区切り記号は得られますが、単位の表示が正しくありません。リストをフォーマットせずに測定値をフォーマットすると、正しい単位は得られますが、区切り記号が正しくありません。

const distances = [5, 10, 15];

// 間違い: リストはフォーマットされているが測定値はされていない
console.log(distances.join(', '));
// 出力: "5, 10, 15" (単位が欠けている)

// 間違い: 測定値はフォーマットされているがリストはされていない
const formatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});
console.log(distances.map(d => formatter.format(d)).join(', '));
// 出力: "5 km, 10 km, 15 km" (ハードコードされたカンマは一部のロケールでは間違っている可能性がある)

正しいアプローチは、まず測定値をフォーマットし、次に結果の文字列配列をリストとしてフォーマットすることです。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedMeasurements = distances.map(d => numberFormatter.format(d));
// 結果: ["5 km", "10 km", "15 km"]

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedMeasurements));
// 出力: "5 km, 10 km, 15 km"

このパターンはあらゆる測定タイプとあらゆるロケールで機能します。各測定値をその単位でフォーマットし、次にフォーマットされた文字列の配列をリストとしてフォーマットします。

測定リストにはunit typeを使用する

Intl.ListFormatコンストラクタは、リスト項目の結合方法を制御するtypeオプションを受け付けます。type: 'unit'オプションは、技術的および科学的データの規則に従ってリストをフォーマットします。

const measurements = ['5 km', '10 km', '15 km'];

const unitList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(unitList.format(measurements));
// 出力: "5 km, 10 km, 15 km"

type: 'unit'を持つリストは「and」や「or」などの接続詞を省略します。項目間には単純な区切り文字が使用されます。これは、技術的な文脈で測定値が一般的に記述される方法と一致します。

最後の項目の前に「and」を追加するtype: 'conjunction'と比較してみましょう。

const measurements = ['5 km', '10 km', '15 km'];

const conjunctionList = new Intl.ListFormat('en-US', {
  type: 'conjunction'
});

console.log(conjunctionList.format(measurements));
// 出力: "5 km, 10 km, and 15 km"

接続形式は文章中では自然に読めますが、技術的な文脈では不適切に見えます。複数の測定値を表示する場合は、科学的および技術的な文書の標準的な規則に従うためにtype: 'unit'を使用してください。

リスト内の距離測定値をフォーマットする

距離測定にはkilometermetermilefootなどの単位識別子を使用します。各距離を単位付きでフォーマットした後、それらをリストに結合します。

const distances = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "5 km, 10 km, 15 km, 20 km"

同じパターンはマイルにも適用できます。

const distances = [3, 6, 9];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "3 mi, 6 mi, 9 mi"

数値フォーマットオプションを設定することで、小数点以下の桁数を持つ距離をフォーマットできます。

const distances = [5.2, 10.7, 15.3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "5.2 km, 10.7 km, 15.3 km"

リストフォーマッタが値を結合する前に、数値フォーマッタが丸めと小数点以下の桁数を処理します。

リスト内の重量測定値のフォーマット

重量測定値も同様のパターンに従い、kilogrampoundouncegramなどの単位識別子を使用します。

const weights = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilogram'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// 出力: "50 kg, 75 kg, 100 kg"

重量をポンドで表示することもできます。

const weights = [110, 165, 220];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'pound'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// 出力: "110 lb, 165 lb, 220 lb"

数値フォーマッタは各単位に対して正しい略語を自動的に使用します。

リスト内の温度測定値のフォーマット

温度測定値はcelsiusfahrenheitなどの単位識別子を使用します。

const temperatures = [20, 22, 25, 23, 21];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'celsius'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// 出力: "20°C, 22°C, 25°C, 23°C, 21°C"

温度フォーマッタは出力に度数記号を自動的に含めます。

華氏も同様に機能します。

const temperatures = [68, 72, 77, 73, 70];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'fahrenheit'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// 出力: "68°F, 72°F, 77°F, 73°F, 70°F"

このパターンは異なる測定タイプ間で同一です。変わるのは単位識別子だけです。

リスト内の体積測定値のフォーマット

体積測定には、litergallonmilliliterfluid-ounceなどの単位識別子を使用します。

const volumes = [1, 2, 3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter'
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// 出力: "1 L, 2 L, 3 L"

体積測定は小数値でも機能します。

const volumes = [0.5, 1.5, 2.5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter',
  maximumFractionDigits: 1
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// 出力: "0.5 L, 1.5 L, 2.5 L"

数値フォーマッタはリストフォーマッタが値を処理する前に小数点の精度を処理します。

リスト内の速度測定値のフォーマット

速度測定には、kilometer-per-hourmile-per-hourなどの複合単位を使用します。

const speeds = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// 出力: "50 km/h, 75 km/h, 100 km/h"

マイル毎時も同様に機能します。

const speeds = [30, 45, 60];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// 出力: "30 mph, 45 mph, 60 mph"

複合単位は正しい略語と区切り文字で自動的にフォーマットされます。

ロケールによってリスト区切り文字のフォーマットが決まる

ロケールパラメータは、リスト項目の区切り方や句読点の付け方を制御します。言語によってリストのフォーマット規則が異なります。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const enList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const frList = new Intl.ListFormat('fr-FR', {
  type: 'unit'
});

const deList = new Intl.ListFormat('de-DE', {
  type: 'unit'
});

console.log(enList.format(formattedDistances));
// 出力: "5 km, 10 km, 15 km"

console.log(frList.format(formattedDistances));
// 出力: "5 km, 10 km, 15 km"

console.log(deList.format(formattedDistances));
// 出力: "5 km, 10 km, 15 km"

キロメートルの略語はこれらのロケール間で類似していますが、スペースや区切り文字の規則は異なる場合があります。Intl.ListFormat APIはこれらのロケール固有のフォーマットルールを自動的に処理します。

言語によってはリストの区切り文字や句読点のパターンが異なります。このAPIを使用すると、各ロケールの特定のルールを知らなくても、リストが各ロケールの正しい規則に従うことが保証されます。

リスト形式のロケールと数値のロケールを一致させる

測定値のリストをフォーマットする際は、数値フォーマッタとリストフォーマッタの両方に同じロケールを使用してください。これにより、出力全体で一貫したフォーマットが保証されます。

const distances = [1000, 2000, 3000];

const locale = 'de-DE';

const numberFormatter = new Intl.NumberFormat(locale, {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(locale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "1.000 m, 2.000 m, 3.000 m"

ドイツ語のフォーマットでは、千の位区切りにピリオドを使用します。数値フォーマッタとリストフォーマッタは同じロケールを共有しているため、どちらもドイツ語の規則に従っています。

数値フォーマットとリストフォーマットに異なるロケールを使用すると、一貫性のない出力が生成されます。

const distances = [1000, 2000, 3000];

const numberFormatter = new Intl.NumberFormat('de-DE', {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "1.000 m, 2.000 m, 3.000 m"

これにより、数値はドイツ語の規則を使用し、リストは英語の規則を使用するという混合フォーマットが生成されます。常に両方のフォーマッタに同じロケールを使用してください。

リスト表示スタイルの制御

styleオプションは、リストフォーマットの詳細度を制御します。このオプションは"long""short""narrow"の3つの値を受け付けます。

const measurements = ['5 km', '10 km', '15 km'];

const longList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'long'
});

const shortList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'short'
});

const narrowList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'narrow'
});

console.log(longList.format(measurements));
// 出力: "5 km, 10 km, 15 km"

console.log(shortList.format(measurements));
// 出力: "5 km, 10 km, 15 km"

console.log(narrowList.format(measurements));
// 出力: "5 km 10 km 15 km"

英語では、longshortスタイルは単位リストに対して類似した出力を生成します。narrowスタイルは最小限のスペースを使用し、項目間の区切り文字を省略します。

異なるロケールではスタイル間でより多くの違いが見られます。各スタイルレベルの正確なフォーマットはロケールによって決定されます。

長い単位名との組み合わせ

数値フォーマッターで unitDisplay: 'long' を設定することで、略語ではなく完全な単位名で測定値をフォーマットできます。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "5 kilometers, 10 kilometers, 15 kilometers"

数値フォーマッターは単数形と複数形を自動的に処理します。リストフォーマッターは、略語または完全な名前のどちらを使用しているかに関わらず、フォーマットされた文字列を結合します。

const distances = [1, 5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力: "1 kilometer, 5 kilometers"

数値フォーマッターは1に対して「kilometer」、5に対して「kilometers」を使用します。リストフォーマッターはそれらを適切な区切り文字で結合します。

パフォーマンス向上のためのフォーマッターの再利用

Intl.NumberFormatIntl.ListFormatインスタンスの作成には、ロケールデータの読み込みとオプションの処理が含まれます。複数の測定値リストをフォーマットする場合は、フォーマッターを一度作成して再利用しましょう。

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const distanceLists = [
  [5, 10, 15],
  [20, 25, 30],
  [35, 40, 45]
];

distanceLists.forEach(distances => {
  const formattedDistances = distances.map(d => numberFormatter.format(d));
  console.log(listFormatter.format(formattedDistances));
});
// 出力:
// "5 km, 10 km, 15 km"
// "20 km, 25 km, 30 km"
// "35 km, 40 km, 45 km"

このパターンでは、各フォーマッターを一度作成して複数回使用します。多数のリストをフォーマットする場合、パフォーマンスの差は顕著になります。

再利用可能なフォーマッター関数を作成する

2段階のフォーマットパターンを再利用可能な関数にカプセル化できます。

function formatMeasurementList(values, locale, unit) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList([5, 10, 15], 'en-US', 'kilometer'));
// 出力: "5 km, 10 km, 15 km"

console.log(formatMeasurementList([50, 75, 100], 'en-US', 'kilogram'));
// 出力: "50 kg, 75 kg, 100 kg"

console.log(formatMeasurementList([20, 22, 25], 'en-US', 'celsius'));
// 出力: "20°C, 22°C, 25°C"

この関数はあらゆる測定タイプとロケールを処理します。追加のフォーマットオプションを受け入れるように拡張することもできます。

function formatMeasurementList(values, locale, unit, options = {}) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit,
    ...options
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList(
  [5.123, 10.789, 15.456],
  'en-US',
  'kilometer',
  { maximumFractionDigits: 1 }
));
// 出力: "5.1 km, 10.8 km, 15.5 km"

console.log(formatMeasurementList(
  [1, 5, 10],
  'en-US',
  'kilometer',
  { unitDisplay: 'long' }
));
// 出力: "1 kilometer, 5 kilometers, 10 kilometers"

この関数は追加オプションを数値フォーマッターに渡し、小数点以下の桁数、単位の表示方法、その他のフォーマット設定をコントロールできるようにします。

ユーザーのロケールに合わせたリストのフォーマット

特定のロケールをハードコーディングする代わりに、ユーザーのブラウザの言語設定を使用できます。navigator.languageプロパティはユーザーの優先ロケールを返します。

const userLocale = navigator.language;

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat(userLocale, {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(userLocale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 出力はユーザーのロケールによって異なります

このアプローチでは、各ユーザーのフォーマット期待に従って測定リストを表示します。異なるユーザーは同じデータを自分のロケール規則に従ってフォーマットされた形で見ることができます。

アプリケーションでの計測リストの表示

このパターンは、ユーザーに複数の計測値を表示するあらゆる場所で使用できます。これには、ラップタイムを表示するフィットネスアプリケーション、気温予報を表示する天気アプリケーション、材料の量を表示するレシピアプリケーション、実験データを表示する科学アプリケーションなどが含まれます。

const splitTimes = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat(navigator.language, {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedTimes = splitTimes.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat(navigator.language, {
  type: 'unit'
});

const result = listFormatter.format(formattedTimes);

document.getElementById('split-times').textContent = result;
// 表示例: "5 km, 10 km, 15 km, 20 km"(またはロケールに応じた表示)

フォーマットされた文字列は、他の文字列値と同様に機能します。テキストコンテンツ、属性、またはユーザーに情報を表示するあらゆるコンテキストに挿入できます。