1-3개 항목과 같은 범위에 대한 복수형 선택 방법
범위 숫자를 표시할 때 올바른 복수형을 선택하기 위해 JavaScript 사용하기
소개
범위는 값이 두 끝점 사이에 있음을 나타냅니다. 사용자 인터페이스는 "10-15개의 일치 항목 발견", 재고 시스템에서 "1-3개 항목 사용 가능" 또는 필터에서 "2-5개 옵션 선택"과 같은 맥락에서 범위를 표시합니다. 이러한 범위는 두 숫자와 범위와 문법적으로 일치해야 하는 설명 텍스트를 결합합니다.
단일 개수를 표시할 때는 단수형과 복수형 중에서 선택합니다: "1개 항목" 대 "2개 항목". 언어마다 개수에 따라 어떤 형태가 적용되는지 결정하는 규칙이 있습니다. 이러한 규칙은 언어마다 다릅니다. 영어는 1에는 단수형을, 다른 모든 개수에는 복수형을 사용합니다. 폴란드어는 1, 2-4, 5 이상에 대해 다른 형태를 사용합니다. 아랍어는 개수에 따라 6가지 고유한 형태가 있습니다.
범위는 다른 도전 과제를 제시합니다. 복수형은 단일 숫자가 아닌 시작 값과 끝 값 모두에 따라 달라집니다. 영어에서는 "1-2 items"는 범위가 1에서 시작하더라도 복수형을 사용합니다. 언어마다 범위에 어떤 복수형이 적용되는지 결정하는 규칙이 다릅니다. Intl.PluralRules의 selectRange() 메서드는 이러한 언어별 규칙을 자동으로 처리합니다.
범위에 다른 복수화 규칙이 필요한 이유
범위의 단일 숫자에 select() 메서드를 사용하는 것은 모든 언어에서 올바르게 작동하지 않습니다. 범위의 끝 값을 사용하는 것이 좋을 것 같지만, 많은 언어에서 잘못된 결과를 생성합니다.
0-1 범위의 영어를 고려해보세요. 끝 값에 select()를 사용하면 "one"이 반환되어 "0-1 item"을 표시해야 한다고 제안합니다. 이것은 문법적으로 잘못되었습니다. 올바른 형태는 복수형인 "0-1 items"입니다.
const rules = new Intl.PluralRules("en-US");
console.log(rules.select(1));
// 출력: "one"
// 그러나 "0-1 item"은 잘못됨
// 올바른 형태: "0-1 items"
다른 언어들은 단일 개수에 대한 규칙과 일치하지 않는 범위에 대한 명시적인 규칙을 가지고 있습니다. 슬로베니아어에서는 102-201 범위가 "few" 형태를 사용하지만, 해당 범위 내의 개별 숫자는 다른 형태를 사용합니다.
const slRules = new Intl.PluralRules("sl");
console.log(slRules.select(102));
// 출력: "few"
console.log(slRules.select(201));
// 출력: "few"
console.log(slRules.selectRange(102, 201));
// 출력: "few"
일부 언어는 형태를 결정하기 위해 시작 값을 사용하고, 다른 언어는 끝 값을 사용하며, 또 다른 언어는 두 값을 함께 사용합니다. selectRange() 메서드는 이러한 언어별 규칙을 캡슐화하여 수동으로 구현할 필요가 없도록 합니다.
범위에 대한 PluralRules 인스턴스 생성하기
단일 개수에 대해 생성하는 것과 동일한 방식으로 Intl.PluralRules 인스턴스를 생성합니다. 이 인스턴스는 단일 숫자를 위한 select()와 범위를 위한 selectRange() 모두 제공합니다.
const rules = new Intl.PluralRules("en-US");
인스턴스를 생성할 때 옵션을 지정할 수 있습니다. 이러한 옵션은 단일 개수와 범위 모두에 적용됩니다.
const rules = new Intl.PluralRules("en-US", {
type: "cardinal"
});
type 옵션의 기본값은 "cardinal"이며, 이는 객체 개수를 처리합니다. 위치를 나타내는 숫자에는 "ordinal"을 사용할 수도 있지만, 서수 범위는 사용자 인터페이스에서 덜 일반적입니다.
여러 호출에 걸쳐 동일한 인스턴스를 재사용하세요. 모든 복수화에 대해 새 인스턴스를 생성하는 것은 낭비입니다. 변수에 인스턴스를 저장하거나 로케일별로 캐시하세요.
selectRange를 사용하여 범위에 대한 복수 카테고리 결정하기
'selectRange()' 메서드는 범위의 시작과 끝을 나타내는 두 개의 숫자를 받습니다. 이 메서드는 적용되는 복수 카테고리를 나타내는 문자열을 반환합니다: "zero", "one", "two", "few", "many", 또는 "other".
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(0, 1));
// 출력: "other"
console.log(rules.selectRange(1, 2));
// 출력: "other"
console.log(rules.selectRange(5, 10));
// 출력: "other"
영어에서는 범위가 거의 항상 "other" 카테고리를 사용하며, 이는 복수형에 해당합니다. 이는 영어 사용자가 자연스럽게 복수 명사로 범위를 표현하는 방식과 일치합니다.
더 많은 복수형을 가진 언어들은 특정 규칙에 따라 다른 카테고리를 반환합니다.
const arRules = new Intl.PluralRules("ar-EG");
console.log(arRules.selectRange(0, 0));
// 출력: "zero"
console.log(arRules.selectRange(1, 1));
// 출력: "one"
console.log(arRules.selectRange(2, 2));
// 출력: "two"
console.log(arRules.selectRange(3, 10));
// 출력: "few"
반환 값은 항상 여섯 가지 표준 복수 카테고리 이름 중 하나입니다. 코드에서 이러한 카테고리를 적절한 지역화된 텍스트에 매핑합니다.
범위 카테고리를 현지화된 문자열에 매핑하기
각 복수 카테고리에 대한 텍스트 형식을 데이터 구조에 저장합니다. selectRange()가 반환한 카테고리를 사용하여 적절한 텍스트를 찾습니다.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "item"],
["other", "items"]
]);
function formatRange(start, end) {
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(1, 3));
// 출력: "1-3 items"
console.log(formatRange(0, 1));
// 출력: "0-1 items"
console.log(formatRange(5, 10));
// 출력: "5-10 items"
이 패턴은 복수화 로직과 현지화된 텍스트를 분리합니다. Intl.PluralRules 인스턴스는 언어 규칙을 처리합니다. Map은 번역을 보관합니다. 함수는 이들을 결합합니다.
더 많은 복수 카테고리가 있는 언어의 경우, 해당 언어가 사용하는 각 카테고리에 대한 항목을 추가합니다.
const arRules = new Intl.PluralRules("ar-EG");
const arForms = new Map([
["zero", "عناصر"],
["one", "عنصر"],
["two", "عنصران"],
["few", "عناصر"],
["many", "عنصرًا"],
["other", "عنصر"]
]);
function formatRange(start, end) {
const category = arRules.selectRange(start, end);
const form = arForms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(0, 0));
// 출력: "0-0 عناصر"
console.log(formatRange(1, 1));
// 출력: "1-1 عنصر"
언어가 사용하는 모든 카테고리에 대한 텍스트를 항상 제공하세요. 유니코드 CLDR 복수 규칙을 확인하거나 다양한 범위에서 API로 테스트하여 필요한 카테고리를 식별하세요.
다양한 로케일이 범위 복수화를 처리하는 방법
각 언어는 범위의 복수 형태를 결정하는 고유한 규칙을 가지고 있습니다. 이러한 규칙은 원어민이 자연스럽게 범위를 표현하는 방식을 반영합니다.
const enRules = new Intl.PluralRules("en-US");
console.log(enRules.selectRange(1, 3));
// 출력: "other"
const slRules = new Intl.PluralRules("sl");
console.log(slRules.selectRange(102, 201));
// 출력: "few"
const ptRules = new Intl.PluralRules("pt");
console.log(ptRules.selectRange(102, 102));
// 출력: "other"
const ruRules = new Intl.PluralRules("ru");
console.log(ruRules.selectRange(1, 2));
// 출력: "few"
영어는 범위에 대해 일관되게 "other"를 사용하여 범위를 항상 복수형으로 만듭니다. 슬로베니아어는 범위 내 특정 숫자에 기반한 더 복잡한 규칙을 적용합니다. 포르투갈어는 대부분의 범위에 "other"를 사용합니다. 러시아어는 특정 범위에 "few"를 사용합니다.
이러한 차이점은 국제 애플리케이션에서 복수 로직을 하드코딩하는 것이 실패하는 이유를 보여줍니다. API는 각 언어가 범위를 처리하는 방법에 대한 지식을 캡슐화합니다.
완전한 형식 지정을 위해 Intl.NumberFormat과 결합하기
실제 애플리케이션에서는 숫자와 텍스트 모두를 형식화해야 합니다. Intl.NumberFormat을 사용하여 로케일 규칙에 따라 범위 끝점을 형식화한 다음, selectRange()를 사용하여 올바른 복수형을 선택하세요.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "item"],
["other", "items"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1, 3));
// 출력: "1-3 items"
console.log(formatRange(1000, 5000));
// 출력: "1,000-5,000 items"
숫자 포맷터는 천 단위 구분 기호를 추가합니다. 복수형 규칙은 올바른 형태를 선택합니다. 이 함수는 두 가지를 결합하여 적절하게 형식화된 출력을 생성합니다.
다른 로케일은 다른 숫자 형식 지정 규칙을 사용합니다.
const locale = "de-DE";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "Artikel"],
["other", "Artikel"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1000, 5000));
// 출력: "1.000-5.000 Artikel"
독일어는 쉼표 대신 마침표를 천 단위 구분 기호로 사용합니다. 숫자 포맷터가 이를 자동으로 처리합니다. 복수형 규칙은 "Artikel"의 어떤 형태를 사용할지 결정합니다.
단일 값에 대한 select와 selectRange 비교하기
select() 메서드는 단일 개수를 처리하고, selectRange()는 범위를 처리합니다. 단일 수량을 표시할 때는 select()를 사용하고, 두 값 사이의 범위를 표시할 때는 selectRange()를 사용하세요.
const rules = new Intl.PluralRules("en-US");
// 단일 개수
console.log(rules.select(1));
// 출력: "one"
console.log(rules.select(2));
// 출력: "other"
// 범위
console.log(rules.selectRange(1, 2));
// 출력: "other"
console.log(rules.selectRange(0, 1));
// 출력: "other"
단일 개수의 경우, 규칙은 해당 숫자에만 의존합니다. 범위의 경우, 규칙은 두 끝점 모두를 고려합니다. 영어에서는 1에서 시작하는 범위도 복수형을 사용하며, 단일 개수 1은 단수형을 사용합니다.
일부 언어는 단일 개수 규칙과 범위 규칙 사이에 더 극적인 차이를 보입니다.
const slRules = new Intl.PluralRules("sl");
// 슬로베니아어의 단일 개수
console.log(slRules.select(1));
// 출력: "one"
console.log(slRules.select(2));
// 출력: "two"
console.log(slRules.select(5));
// 출력: "few"
// 슬로베니아어의 범위
console.log(slRules.selectRange(102, 201));
// 출력: "few"
슬로베니아어는 복잡한 규칙에 따라 다른 단일 개수에 대해 "one", "two", "few"를 사용합니다. 범위의 경우, 두 숫자를 함께 고려하는 다른 논리를 적용합니다.
시작과 끝이 동일한 범위 처리하기
시작값과 끝값이 동일할 때, 너비가 없는 범위를 표시하게 됩니다. 일부 애플리케이션에서는 범위가 예상되는 컨텍스트에서 정확한 값을 나타내기 위해 이 방식을 사용합니다.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(5, 5));
// 출력: "other"
console.log(rules.selectRange(1, 1));
// 출력: "one"
두 값이 모두 1일 때, 영어에서는 "one"을 반환하여 단수형을 사용해야 함을 시사합니다. 두 값이 다른 숫자일 경우, 영어에서는 "other"를 반환하여 복수형을 사용해야 함을 시사합니다.
이 동작은 범위를 "1-1 item" 또는 간단히 "1 item"으로 표시하는 경우 합리적입니다. 1이 아닌 값의 경우 "5-5 items" 또는 "5 items"로 표시합니다.
실제로는 시작과 끝이 같을 때 범위 대신 단일 값을 표시하는 것이 좋을 수 있습니다.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "item"],
["other", "items"]
]);
function formatRange(start, end) {
if (start === end) {
const category = rules.select(start);
const form = forms.get(category);
return `${start} ${form}`;
}
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(1, 1));
// 출력: "1 item"
console.log(formatRange(5, 5));
// 출력: "5 items"
console.log(formatRange(1, 3));
// 출력: "1-3 items"
이 접근 방식은 동일한 값에는 select()를 사용하고 실제 범위에는 selectRange()를 사용합니다. "1-1" 또는 "5-5"와 같은 표시를 피하기 때문에 출력이 더 자연스럽게 읽힙니다.
selectRange로 엣지 케이스 처리하기
selectRange() 메서드는 입력값을 검증합니다. 매개변수 중 하나가 undefined, null 또는 유효한 숫자로 변환할 수 없는 경우, 메서드는 오류를 발생시킵니다.
const rules = new Intl.PluralRules("en-US");
try {
console.log(rules.selectRange(1, undefined));
} catch (error) {
console.log(error.name);
// 출력: "TypeError"
}
try {
console.log(rules.selectRange(NaN, 5));
} catch (error) {
console.log(error.name);
// 출력: "RangeError"
}
selectRange()에 전달하기 전에 입력값을 검증하세요. 이는 사용자 입력이나 외부 소스의 데이터를 처리할 때 특히 중요합니다.
function formatRange(start, end) {
if (typeof start !== "number" || typeof end !== "number") {
throw new Error("Start and end must be numbers");
}
if (isNaN(start) || isNaN(end)) {
throw new Error("Start and end must be valid numbers");
}
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
이 메서드는 숫자, BigInt 값 또는 숫자로 파싱할 수 있는 문자열을 허용합니다.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1, 5));
// 출력: "other"
console.log(rules.selectRange(1n, 5n));
// 출력: "other"
console.log(rules.selectRange("1", "5"));
// 출력: "other"
문자열 입력은 숫자로 파싱됩니다. 이를 통해 메서드 호출 방식에 유연성을 제공하지만, 명확성을 위해 가능한 실제 숫자 타입을 전달하는 것이 좋습니다.
소수 범위 처리하기
'selectRange()' 메서드는 소수 숫자와 함께 작동합니다. 이는 측정값이나 통계와 같은 분수 수량의 범위를 표시할 때 유용합니다.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1.5, 2.5));
// 출력: "other"
console.log(rules.selectRange(0.5, 1.0));
// 출력: "other"
console.log(rules.selectRange(1.0, 1.5));
// 출력: "other"
영어에서는 이러한 모든 소수 범위를 복수형으로 취급합니다. 다른 언어에서는 소수 범위에 대한 규칙이 다를 수 있습니다.
소수 범위를 포맷팅할 때는 적절한 소수 정밀도로 구성된 Intl.NumberFormat과 selectRange()를 결합하세요.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
});
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "kilometer"],
["other", "kilometers"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1.5, 2.5));
// 출력: "1.5-2.5 kilometers"
console.log(formatRange(0.5, 1.0));
// 출력: "0.5-1.0 kilometers"
숫자 포맷터는 일관된 소수 표시를 보장합니다. 복수 규칙은 소수 값에 따라 올바른 형태를 결정합니다.
브라우저 지원 및 호환성
'selectRange()' 메서드는 나머지 Intl API에 비해 상대적으로 새로운 기능입니다. 2023년에 Intl.NumberFormat v3 사양의 일부로 사용 가능하게 되었습니다.
브라우저 지원에는 Chrome 106 이상, Firefox 116 이상, Safari 15.4 이상, Edge 106 이상이 포함됩니다. 이 메서드는 Internet Explorer나 이전 브라우저 버전에서는 사용할 수 없습니다.
최신 브라우저를 대상으로 하는 애플리케이션의 경우 폴리필 없이 'selectRange()'를 사용할 수 있습니다. 이전 브라우저를 지원해야 하는 경우 사용하기 전에 메서드의 존재 여부를 확인하세요.
const rules = new Intl.PluralRules("en-US");
if (typeof rules.selectRange === "function") {
// 범위 복수화에 selectRange 사용
console.log(rules.selectRange(1, 3));
} else {
// selectRange를 사용할 수 없을 때 끝 값으로 select로 대체
console.log(rules.select(3));
}
이 대체 접근 방식은 'selectRange()'를 사용할 수 없을 때 끝 값에 'select()'를 사용합니다. 이는 모든 언어에 대해 언어학적으로 완벽하지는 않지만, 이전 브라우저에 대한 합리적인 근사치를 제공합니다.
이전 환경에 대한 포괄적인 지원이 필요한 경우 '@formatjs/intl-pluralrules'와 같은 패키지를 통해 폴리필을 사용할 수 있습니다.
selectRange와 select를 사용하는 경우
사용자에게 시작 값과 끝 값이 모두 표시되는 범위를 UI에 명시적으로 표시할 때는 selectRange()를 사용하세요. 이는 "10-15개의 일치 항목 발견"과 같은 검색 결과, "1-3개 재고 있음"과 같은 재고 표시, 또는 "2-5개 옵션 선택"과 같은 필터 표시와 같은 상황을 포함합니다.
단일 개수를 표시할 때는 해당 개수가 대략적이거나 요약된 값을 나타내더라도 select()를 사용하세요. 예를 들어, "약 10개의 결과"는 단일 숫자를 표시하므로 select(10)을 사용합니다. 범위가 아닙니다.
숫자에 Intl.NumberFormat.formatRange()를 사용하여 범위를 표시하는 경우, 동반되는 텍스트에는 selectRange()를 사용하세요. 이렇게 하면 숫자 형식과 텍스트 복수화 간의 일관성이 보장됩니다.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "result"],
["other", "results"]
]);
function formatSearchResults(start, end) {
const rangeFormatted = numberFormat.formatRange(start, end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `Found ${rangeFormatted} ${form}`;
}
console.log(formatSearchResults(10, 15));
// 출력: "Found 10–15 results"
이 패턴은 Intl.NumberFormat의 formatRange()를 사용하여 숫자를 형식화하고 Intl.PluralRules의 selectRange()를 사용하여 텍스트를 선택합니다. 두 메서드 모두 범위에 작동하므로 모든 언어에 대해 올바르게 처리됩니다.