3-5や100-200のような範囲の書式設定方法

JavaScriptを使用してロケールに適した書式で数値範囲を表示する

はじめに

数値範囲はユーザーインターフェース全体に表示されます。価格範囲は$100~$200として、ページ番号は1~10として、数量の見積もりは3~5アイテムとして表示されます。これらの範囲は、値が2つのエンドポイント間にあることを伝え、単一の正確な数値ではなく、境界のある情報をユーザーに提供します。

範囲値の間の区切り記号をハードコードすると、すべてのユーザーが同じ印刷規則に従っていると仮定することになります。英語話者は通常、範囲にハイフンやenダッシュを使用しますが、他の言語では異なる記号や単語を使用します。ドイツ語では数字の間に「bis」を使用し、一部の言語では区切り記号の周りにスペースを配置します。

JavaScriptはIntl.NumberFormatformatRange()メソッドを提供し、範囲のフォーマットを自動的に処理します。このメソッドは、数値と区切り記号の両方にロケール固有の規則を適用し、世界中のユーザーに対して範囲が正しく表示されるようにします。

数値範囲がロケール固有のフォーマットを必要とする理由

異なる文化では、範囲を表現するための異なる規則が発展してきました。これらの規則には、区切り記号とその周りのスペースの両方が含まれます。

米国英語では、範囲は通常、スペースなしのenダッシュを使用します:3~5、100~200。一部のスタイルガイドでは、ダッシュの周りにスペースが表示されます:3 - 5。正確な規則は、コンテキストと出版基準によって異なります。

ドイツ語では、範囲はしばしば「bis」を区切り記号として使用します:3 bis 5、100 bis 200。この単語ベースのアプローチは、句読点に頼るのではなく、範囲関係を明示的にします。

スペイン語では、範囲は英語のようにダッシュを使用するか、「a」という単語を使用することがあります:3-5または3 a 5。選択は、特定のスペイン語圏の地域とコンテキストによって異なります。

通貨や単位を含む範囲をフォーマットする場合、複雑さが増します。価格範囲は米国英語では$100~$200と表示されますが、ドイツ語では100 €~200 €、または通貨記号が一度だけ表示される100~200 €となることがあります。異なるロケールでは、通貨記号の配置が異なり、繰り返しの扱い方も異なります。

手動での範囲フォーマットには、これらの規則を知り、ロケール固有のロジックを実装する必要があります。Intl APIはこの知識をカプセル化し、ロケールに基づいて適切なフォーマットを適用します。

formatRangeを使用して数値範囲をフォーマットする

formatRange()メソッドは2つの数値を受け取り、その範囲を表す整形された文字列を返します。希望するロケールとオプションでIntl.NumberFormatインスタンスを作成し、開始値と終了値を指定してformatRange()を呼び出します。

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatRange(3, 5));
// 出力: "3–5"

console.log(formatter.formatRange(100, 200));
// 出力: "100–200"

console.log(formatter.formatRange(1000, 5000));
// 出力: "1,000–5,000"

フォーマッターは範囲内の両方の数値に千の位区切り記号を適用し、それらの間に適切な区切り記号を使用します。米国英語の場合、これはスペースなしのenダッシュです。

ロケール識別子を変更することで、同じ範囲を異なるロケール用にフォーマットできます。

const usFormatter = new Intl.NumberFormat("en-US");
console.log(usFormatter.formatRange(100, 200));
// 出力: "100–200"

const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(100, 200));
// 出力: "100–200"

const esFormatter = new Intl.NumberFormat("es-ES");
console.log(esFormatter.formatRange(100, 200));
// 出力: "100-200"

各ロケールは区切り記号とスペースに独自の規則を適用します。APIはロケールの印刷標準に基づいてこれらの詳細を自動的に処理します。

通貨範囲のフォーマット

範囲フォーマットは通貨を含むあらゆる数値フォーマットオプションで機能します。通貨範囲をフォーマットする場合、フォーマッターは通貨記号の配置と範囲区切り記号の両方を処理します。

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(formatter.formatRange(100, 200));
// 出力: "$100 – $200"

console.log(formatter.formatRange(1000, 5000));
// 出力: "$1,000 – $5,000"

フォーマッターは範囲内の各数値の前に通貨記号を配置します。これにより、両方の値が通貨金額を表していることが明確になります。

異なるロケールでは通貨記号の配置が異なります。

const usFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(usFormatter.formatRange(100, 200));
// 出力: "$100 – $200"

const deFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR",
  maximumFractionDigits: 0
});

console.log(deFormatter.formatRange(100, 200));
// 出力: "100–200 €"

ドイツ語のフォーマッターは各数値の前ではなく、範囲の後にユーロ記号を配置します。これはドイツ語の通貨範囲の印刷規則に従っています。

範囲の値がほぼ等しい場合の動作

開始値と終了値がフォーマット後に同じ数値に丸められる場合、フォーマッタは範囲を省略し、近似記号を追加することがあります。

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

console.log(formatter.formatRange(100, 200));
// 出力: "$100 – $200"

console.log(formatter.formatRange(100, 120));
// 出力: "$100 – $120"

console.log(formatter.formatRange(100.2, 100.8));
// 出力: "~$100"

3番目の例では、2つの値が同じ整数に丸められています。「$100 – $100」と表示すると範囲情報が伝わらないため、フォーマッタは「~$100」を出力します。チルダ記号は値が近似値であることを示しています。

この動作は、フォーマットオプションによって開始値と終了値が同一に見える場合に適用されます。

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 1
});

console.log(formatter.formatRange(2.9, 3.1));
// 出力: "~3"

console.log(formatter.formatRange(2.94, 2.96));
// 出力: "~2.9"

フォーマッタは必要な場合にのみ近似記号を挿入します。値が異なる数値に丸められる場合は、標準的な範囲として表示されます。

小数点以下の桁数を含む範囲のフォーマット

範囲フォーマットはフォーマッタオプションから小数点以下の桁数設定を保持します。範囲内の両方の値の精度を制御できます。

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(formatter.formatRange(3.5, 5.7));
// 出力: "3.50–5.70"

console.log(formatter.formatRange(100, 200));
// 出力: "100.00–200.00"

フォーマッタは範囲内の両方の数値に小数点以下の桁数設定を適用します。これにより、範囲表示全体で一貫した精度が確保されます。

小数点フォーマットを通貨やその他のスタイルと組み合わせることもできます。

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(formatter.formatRange(99.99, 199.99));
// 出力: "$99.99 – $199.99"

異なる言語での数値範囲のフォーマット

範囲フォーマットは各ロケールの数値、区切り文字、間隔の規則に適応します。

const enFormatter = new Intl.NumberFormat("en-US");
console.log(enFormatter.formatRange(1000, 5000));
// 出力: "1,000–5,000"

const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(1000, 5000));
// 出力: "1.000–5.000"

const frFormatter = new Intl.NumberFormat("fr-FR");
console.log(frFormatter.formatRange(1000, 5000));
// 出力: "1 000–5 000"

const jaFormatter = new Intl.NumberFormat("ja-JP");
console.log(jaFormatter.formatRange(1000, 5000));
// 出力: "1,000~5,000"

英語では千の位の区切り文字にカンマを使用し、範囲にはenダッシュを使用します。ドイツ語では千の位にピリオドを使用し、enダッシュを使います。フランス語では千の位に空白を使用し、enダッシュを使います。日本語では千の位にカンマを使用し、範囲には波ダッシュ(~)を使用します。

これらの違いは通貨フォーマットにも適用されます。

const enFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});
console.log(enFormatter.formatRange(100, 200));
// 出力: "$100.00 – $200.00"

const deFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR"
});
console.log(deFormatter.formatRange(100, 200));
// 出力: "100,00–200,00 €"

const jaFormatter = new Intl.NumberFormat("ja-JP", {
  style: "currency",
  currency: "JPY"
});
console.log(jaFormatter.formatRange(100, 200));
// 出力: "¥100~¥200"

各ロケールは通貨記号の配置、小数点の区切り文字、範囲の区切り文字に独自のルールを適用します。APIはこれらのバリエーションをすべて自動的に処理します。

formatRangeとコンパクト表記の組み合わせ

範囲フォーマットはコンパクト表記と連携し、1K-5Kや1M-5Mのような範囲を表示できます。

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

console.log(formatter.formatRange(1000, 5000));
// 出力: "1K–5K"

console.log(formatter.formatRange(1000000, 5000000));
// 出力: "1M–5M"

console.log(formatter.formatRange(1200, 4800));
// 出力: "1.2K–4.8K"

フォーマッターは範囲内の両方の値にコンパクト表記を適用します。これにより、範囲情報を伝えながらも出力を簡潔に保ちます。

範囲が異なる桁数レベルにまたがる場合、フォーマッターは各値を適切に処理します。

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

console.log(formatter.formatRange(500, 1500));
// 出力: "500–1.5K"

console.log(formatter.formatRange(900000, 1200000));
// 出力: "900K–1.2M"

開始値はコンパクト表記を使用せず、終了値は使用する場合や、異なる桁数表示を使用する場合があります。フォーマッターは各値のサイズに基づいてこれらの決定を行います。

カスタムスタイリングのための formatRangeToParts の使用

formatRangeToParts() メソッドは、フォーマットされた範囲の部分を表すオブジェクトの配列を返します。これにより、範囲の個々のコンポーネントをスタイリングしたり操作したりすることができます。

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

const parts = formatter.formatRangeToParts(100, 200);
console.log(parts);

出力は、typevaluesource プロパティを持つオブジェクトの配列です。

[
  { type: "currency", value: "$", source: "startRange" },
  { type: "integer", value: "100", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "currency", value: "$", source: "endRange" },
  { type: "integer", value: "200", source: "endRange" }
]

type プロパティは、その部分が何を表すかを識別します:通貨記号、整数、小数点区切り、またはリテラルテキストなどです。value プロパティにはフォーマットされたテキストが含まれます。source プロパティは、その部分が開始値、終了値、またはそれらの間で共有されているかを示します。

これらの部分を使用して、異なるコンポーネントに対して異なるスタイリングを持つカスタムHTMLを作成できます。

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  maximumFractionDigits: 0
});

const parts = formatter.formatRangeToParts(100, 200);
let html = "";

parts.forEach(part => {
  if (part.type === "currency") {
    html += `<span class="currency-symbol">${part.value}</span>`;
  } else if (part.type === "integer") {
    html += `<span class="amount">${part.value}</span>`;
  } else if (part.type === "literal") {
    html += `<span class="separator">${part.value}</span>`;
  } else {
    html += part.value;
  }
});

console.log(html);
// 出力: <span class="currency-symbol">$</span><span class="amount">100</span><span class="separator"> – </span><span class="currency-symbol">$</span><span class="amount">200</span>

この技術により、ロケール固有の正確なフォーマットを保持しながら、CSSクラスの適用、ツールチップの追加、その他のカスタム動作の実装が可能になります。

formatRangeによるエッジケースの処理

formatRange()メソッドには無効な入力に対するエラー処理が含まれています。いずれかのパラメータがundefinedの場合、TypeErrorがスローされます。いずれかのパラメータがNaNまたは数値に変換できない場合、RangeErrorがスローされます。

const formatter = new Intl.NumberFormat("en-US");

try {
  console.log(formatter.formatRange(100, undefined));
} catch (error) {
  console.log(error.name);
  // Output: "TypeError"
}

try {
  console.log(formatter.formatRange(NaN, 200));
} catch (error) {
  console.log(error.name);
  // Output: "RangeError"
}

ユーザー入力や外部ソースからのデータを扱う場合は、値をformatRange()に渡す前に有効な数値であることを検証してください。

このメソッドは数値、BigInt値、または有効な数値を表す文字列を受け入れます。

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatRange(100, 200));
// Output: "100–200"

console.log(formatter.formatRange(100n, 200n));
// Output: "100–200"

console.log(formatter.formatRange("100", "200"));
// Output: "100–200"

文字列入力は数値として解析され、浮動小数点変換の問題なく精度が保持されます。

formatRangeと手動フォーマットの使い分け

ユーザーに範囲を表示する場合はformatRange()を使用してください。これは価格範囲、数量範囲、測定範囲、ページ番号、またはその他の有界値に適用されます。このメソッドは、区切り文字のロジックを実装する必要なく、ロケール固有の正しいフォーマットを保証します。

範囲として意味的に関連していない複数の個別の値を表示する必要がある場合は、formatRange()を避けてください。例えば、「$100、$150、$200」のような価格リストを表示する場合は、それらを範囲として扱うのではなく、各値に対して通常のformat()呼び出しを使用するべきです。

また、値間の関係が数値範囲でない場合もformatRange()を避けてください。比較や差異を示す場合は、範囲フォーマットではなく、そのコンテキストに適したフォーマットを使用してください。