単位付き測定値のリストをフォーマットする方法

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];

// Wrong: list formatted but not measurements
console.log(distances.join(', '));
// Output: "5, 10, 15" (missing units)

// Wrong: measurements formatted but not list
const formatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});
console.log(distances.map(d => formatter.format(d)).join(', '));
// Output: "5 km, 10 km, 15 km" (hardcoded comma might be wrong for some locales)

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

const distances = [5, 10, 15];

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

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

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

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

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

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

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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "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));
// Output: "5 km, 10 km, 15 km"

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

console.log(deList.format(formattedDistances));
// Output: "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));
// Output: "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));
// Output: "1.000 m, 2.000 m, 3.000 m"

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

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

styleオプションは、リスト書式の詳細度を制御します。このオプションは3つの値を受け入れます:"long""short""narrow"

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));
// Output: "5 km, 10 km, 15 km"

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

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

英語の単位リストでは、longスタイルとshortスタイルは同様の出力を生成します。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));
// Output: "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));
// Output: "1 kilometer, 5 kilometers"

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

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

Intl.NumberFormatおよびIntl.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));
});
// Output:
// "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'));
// Output: "5 km, 10 km, 15 km"

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

console.log(formatMeasurementList([20, 22, 25], 'en-US', 'celsius'));
// Output: "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 }
));
// Output: "5.1 km, 10.8 km, 15.5 km"

console.log(formatMeasurementList(
  [1, 5, 10],
  'en-US',
  'kilometer',
  { unitDisplay: 'long' }
));
// Output: "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));
// Output varies by user's locale

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

アプリケーションで測定リストを表示する

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

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;
// Displays: "5 km, 10 km, 15 km, 20 km" (or locale equivalent)

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