3-5 또는 100-200과 같은 범위를 포맷하는 방법

JavaScript를 사용하여 로케일에 적합한 형식으로 숫자 범위 표시하기

소개

숫자 범위는 사용자 인터페이스 전반에 나타납니다. 가격 범위는 $100-$200으로, 페이지 번호는 1-10으로, 수량 추정치는 3-5 항목으로 표시됩니다. 이러한 범위는 값이 두 끝점 사이에 있음을 나타내며, 사용자에게 정확한 단일 숫자가 아닌 경계가 있는 정보를 제공합니다.

범위 값 사이의 구분 기호를 하드코딩하면 모든 사용자가 동일한 타이포그래피 규칙을 따른다고 가정하는 것입니다. 영어 사용자는 일반적으로 범위에 하이픈이나 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() 메서드는 두 개의 숫자를 받아 범위를 나타내는 형식이 지정된 문자열을 반환합니다. 원하는 로케일과 옵션으로 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"

세 번째 예시는 두 값이 같은 정수로 반올림되는 경우를 보여줍니다. "$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));
// Output: "1,000–5,000"

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

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

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

영어는 천 단위 구분자로 쉼표를 사용하고 범위에는 en 대시를 사용합니다. 독일어는 천 단위에 마침표를 사용하고 en 대시를 사용합니다. 프랑스어는 천 단위에 공백을 사용하고 en 대시를 사용합니다. 일본어는 천 단위에 쉼표를 사용하고 범위에는 물결 대시(~)를 사용합니다.

이러한 차이점은 통화 포맷팅에도 적용됩니다.

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

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

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

각 로케일은 통화 기호 배치, 소수점 구분자 및 범위 구분자에 대한 자체 규칙을 적용합니다. API는 이러한 모든 변형을 자동으로 처리합니다.

formatRange와 간결 표기법 결합하기

범위 포맷팅은 간결 표기법과 함께 작동하여 1K-5K 또는 1M-5M과 같은 범위를 표시할 수 있습니다.

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

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

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

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

포맷터는 범위의 두 값 모두에 간결 표기법을 적용합니다. 이렇게 하면 범위 정보를 전달하면서도 출력이 간결하게 유지됩니다.

범위가 서로 다른 크기 수준에 걸쳐 있을 때, 포맷터는 각 값을 적절하게 처리합니다.

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

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

console.log(formatter.formatRange(900000, 1200000));
// Output: "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);

출력은 각각 type, value, source 속성을 가진 객체 배열입니다.

[
  { 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()를 사용하지 마세요. 비교나 차이를 보여주는 경우에는 범위 포맷팅보다는 해당 컨텍스트에 적합한 포맷팅을 사용하세요.