Cómo elegir la forma plural para rangos como 1-3 artículos
Utiliza JavaScript para seleccionar la forma plural correcta al mostrar rangos de números
Introducción
Los rangos comunican que un valor se encuentra entre dos puntos extremos. Las interfaces de usuario muestran rangos en contextos como resultados de búsqueda que indican "Se encontraron 10-15 coincidencias", sistemas de inventario que muestran "1-3 artículos disponibles", o filtros que muestran "Seleccione 2-5 opciones". Estos rangos combinan dos números con texto descriptivo que debe concordar gramaticalmente con el rango.
Cuando muestras un solo recuento, eliges entre formas singulares y plurales: "1 artículo" versus "2 artículos". Los idiomas tienen reglas que determinan qué forma se aplica según el recuento. Estas reglas varían según el idioma. El inglés usa singular para uno y plural para todos los demás recuentos. El polaco usa diferentes formas para 1, 2-4, y 5 o más. El árabe tiene seis formas distintas basadas en el recuento.
Los rangos introducen un desafío diferente. La forma plural depende tanto del valor inicial como del final, no solo de un número único. En inglés, "1-2 artículos" usa plural aunque el rango comience en 1. Diferentes idiomas tienen diferentes reglas para determinar qué forma plural se aplica a un rango. El método selectRange() en Intl.PluralRules maneja estas reglas específicas del idioma automáticamente.
Por qué los rangos necesitan diferentes reglas de pluralización
Usar el método select() en un solo número de un rango no funciona correctamente para todos los idiomas. Podrías pensar en usar el valor final del rango, pero esto produce resultados incorrectos en muchos idiomas.
Considera el inglés con el rango 0-1. Usar select() en el valor final devuelve "one", sugiriendo que deberías mostrar "0-1 item". Esto es gramaticalmente incorrecto. La forma correcta es "0-1 items" con el plural.
const rules = new Intl.PluralRules("en-US");
console.log(rules.select(1));
// Salida: "one"
// Pero "0-1 item" es incorrecto
// Correcto: "0-1 items"
Diferentes idiomas tienen reglas explícitas para rangos que no coinciden con sus reglas para recuentos individuales. En esloveno, el rango 102-201 usa la forma "few", mientras que los números individuales en ese rango usan diferentes formas.
const slRules = new Intl.PluralRules("sl");
console.log(slRules.select(102));
// Salida: "few"
console.log(slRules.select(201));
// Salida: "few"
console.log(slRules.selectRange(102, 201));
// Salida: "few"
Algunos idiomas usan el valor inicial para determinar la forma, otros usan el valor final, y otros usan ambos valores juntos. El método selectRange() encapsula estas reglas específicas del idioma para que no necesites implementarlas manualmente.
Crear una instancia de PluralRules para rangos
Crea una instancia de Intl.PluralRules de la misma manera que lo haces para conteos individuales. La instancia proporciona tanto select() para números individuales como selectRange() para rangos.
const rules = new Intl.PluralRules("en-US");
Puedes especificar opciones al crear la instancia. Estas opciones se aplican tanto a conteos individuales como a rangos.
const rules = new Intl.PluralRules("en-US", {
type: "cardinal"
});
La opción type tiene como valor predeterminado "cardinal", que maneja el conteo de objetos. También puedes usar "ordinal" para números posicionales, aunque los rangos ordinales son menos comunes en las interfaces de usuario.
Reutiliza la misma instancia en múltiples llamadas. Crear una nueva instancia para cada pluralización es ineficiente. Almacena la instancia en una variable o guárdala en caché por locale.
Usar selectRange para determinar la categoría plural para rangos
El método selectRange() toma dos números que representan el inicio y el fin de un rango. Devuelve una cadena que indica qué categoría plural se aplica: "zero", "one", "two", "few", "many" u "other".
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(0, 1));
// Resultado: "other"
console.log(rules.selectRange(1, 2));
// Resultado: "other"
console.log(rules.selectRange(5, 10));
// Resultado: "other"
En inglés, los rangos casi siempre usan la categoría "other", que corresponde a la forma plural. Esto coincide con la forma en que los hablantes de inglés expresan naturalmente los rangos con sustantivos plurales.
Los idiomas con más formas plurales devuelven diferentes categorías basadas en sus reglas específicas.
const arRules = new Intl.PluralRules("ar-EG");
console.log(arRules.selectRange(0, 0));
// Resultado: "zero"
console.log(arRules.selectRange(1, 1));
// Resultado: "one"
console.log(arRules.selectRange(2, 2));
// Resultado: "two"
console.log(arRules.selectRange(3, 10));
// Resultado: "few"
El valor devuelto es siempre uno de los seis nombres de categorías plurales estándar. Tu código mapea estas categorías al texto localizado apropiado.
Asignar categorías de rangos a cadenas localizadas
Almacena las formas de texto para cada categoría de plural en una estructura de datos. Utiliza la categoría devuelta por selectRange() para buscar el texto apropiado.
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));
// Output: "1-3 items"
console.log(formatRange(0, 1));
// Output: "0-1 items"
console.log(formatRange(5, 10));
// Output: "5-10 items"
Este patrón separa la lógica de pluralización del texto localizado. La instancia de Intl.PluralRules maneja las reglas del idioma. El Map contiene las traducciones. La función las combina.
Para idiomas con más categorías de plurales, añade entradas para cada categoría que utilice el idioma.
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));
// Output: "0-0 عناصر"
console.log(formatRange(1, 1));
// Output: "1-1 عنصر"
Siempre proporciona texto para cada categoría que utilice el idioma. Consulta las reglas de plurales de Unicode CLDR o realiza pruebas con la API en diferentes rangos para identificar qué categorías son necesarias.
Cómo diferentes locales manejan la pluralización de rangos
Cada idioma tiene sus propias reglas para determinar la forma plural de los rangos. Estas reglas reflejan cómo los hablantes nativos expresan naturalmente los rangos en ese idioma.
const enRules = new Intl.PluralRules("en-US");
console.log(enRules.selectRange(1, 3));
// Output: "other"
const slRules = new Intl.PluralRules("sl");
console.log(slRules.selectRange(102, 201));
// Output: "few"
const ptRules = new Intl.PluralRules("pt");
console.log(ptRules.selectRange(102, 102));
// Output: "other"
const ruRules = new Intl.PluralRules("ru");
console.log(ruRules.selectRange(1, 2));
// Output: "few"
El inglés utiliza consistentemente "other" para rangos, haciendo que los rangos sean siempre plurales. El esloveno aplica reglas más complejas basadas en los números específicos del rango. El portugués usa "other" para la mayoría de los rangos. El ruso usa "few" para ciertos rangos.
Estas diferencias muestran por qué codificar de forma rígida la lógica de pluralización falla para aplicaciones internacionales. La API encapsula el conocimiento de cómo cada idioma maneja los rangos.
Combinar con Intl.NumberFormat para un formateo completo
Las aplicaciones reales necesitan formatear tanto los números como el texto. Utiliza Intl.NumberFormat para formatear los extremos del rango según las convenciones locales, luego usa selectRange() para elegir la forma plural correcta.
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));
// Salida: "1-3 items"
console.log(formatRange(1000, 5000));
// Salida: "1,000-5,000 items"
El formateador de números añade separadores de miles. Las reglas de pluralización seleccionan la forma correcta. La función combina ambos para producir una salida correctamente formateada.
Diferentes locales utilizan diferentes convenciones de formato de números.
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));
// Salida: "1.000-5.000 Artikel"
El alemán utiliza puntos como separadores de miles en lugar de comas. El formateador de números maneja esto automáticamente. Las reglas de pluralización determinan qué forma de "Artikel" usar.
Comparar selectRange con select para valores individuales
El método select() maneja conteos individuales, mientras que selectRange() maneja rangos. Utiliza select() cuando muestres una cantidad individual y selectRange() cuando muestres un rango entre dos valores.
const rules = new Intl.PluralRules("en-US");
// Conteo individual
console.log(rules.select(1));
// Salida: "one"
console.log(rules.select(2));
// Salida: "other"
// Rango
console.log(rules.selectRange(1, 2));
// Salida: "other"
console.log(rules.selectRange(0, 1));
// Salida: "other"
Para conteos individuales, las reglas dependen solo de ese número. Para rangos, las reglas consideran ambos extremos. En inglés, un rango que comienza en 1 todavía usa la forma plural, aunque el conteo individual 1 use la forma singular.
Algunos idiomas muestran diferencias más dramáticas entre las reglas de conteo individual y las reglas de rango.
const slRules = new Intl.PluralRules("sl");
// Conteos individuales en esloveno
console.log(slRules.select(1));
// Salida: "one"
console.log(slRules.select(2));
// Salida: "two"
console.log(slRules.select(5));
// Salida: "few"
// Rango en esloveno
console.log(slRules.selectRange(102, 201));
// Salida: "few"
El esloveno utiliza "one", "two" y "few" para diferentes conteos individuales basados en reglas complejas. Para rangos, aplica una lógica diferente que considera ambos números juntos.
Manejar rangos donde el inicio y el fin son iguales
Cuando los valores de inicio y fin son iguales, estás mostrando un rango sin anchura. Algunas aplicaciones utilizan esto para representar un valor exacto en un contexto donde se esperan rangos.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(5, 5));
// Salida: "other"
console.log(rules.selectRange(1, 1));
// Salida: "one"
Cuando ambos valores son iguales a 1, en inglés se devuelve "one", lo que sugiere que deberías usar la forma singular. Cuando ambos valores son cualquier otro número, en inglés se devuelve "other", sugiriendo la forma plural.
Este comportamiento tiene sentido si muestras el rango como "1-1 elemento" o simplemente "1 elemento". Para valores distintos de 1, mostrarías "5-5 elementos" o "5 elementos".
En la práctica, es posible que desees detectar cuando el inicio es igual al fin y mostrar un solo valor en lugar de un rango.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "elemento"],
["other", "elementos"]
]);
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));
// Salida: "1 elemento"
console.log(formatRange(5, 5));
// Salida: "5 elementos"
console.log(formatRange(1, 3));
// Salida: "1-3 elementos"
Este enfoque utiliza select() para valores iguales y selectRange() para rangos reales. La salida se lee de forma más natural porque evita mostrar "1-1" o "5-5".
Manejar casos extremos con selectRange
El método selectRange() valida sus entradas. Si alguno de los parámetros es undefined, null o no puede convertirse a un número válido, el método lanza un error.
const rules = new Intl.PluralRules("en-US");
try {
console.log(rules.selectRange(1, undefined));
} catch (error) {
console.log(error.name);
// Salida: "TypeError"
}
try {
console.log(rules.selectRange(NaN, 5));
} catch (error) {
console.log(error.name);
// Salida: "RangeError"
}
Valida tus entradas antes de pasarlas a selectRange(). Esto es particularmente importante cuando trabajas con entrada de usuario o datos de fuentes externas.
function formatRange(start, end) {
if (typeof start !== "number" || typeof end !== "number") {
throw new Error("El inicio y el fin deben ser números");
}
if (isNaN(start) || isNaN(end)) {
throw new Error("El inicio y el fin deben ser números válidos");
}
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
El método acepta números, valores BigInt o cadenas que pueden analizarse como números.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1, 5));
// Salida: "other"
console.log(rules.selectRange(1n, 5n));
// Salida: "other"
console.log(rules.selectRange("1", "5"));
// Salida: "other"
Las entradas de tipo string se analizan como números. Esto permite flexibilidad en cómo llamas al método, pero deberías preferir pasar tipos de números reales cuando sea posible para mayor claridad.
Manejar rangos decimales
El método selectRange() funciona con números decimales. Esto es útil cuando se muestran rangos de cantidades fraccionarias como mediciones o estadísticas.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1.5, 2.5));
// Salida: "other"
console.log(rules.selectRange(0.5, 1.0));
// Salida: "other"
console.log(rules.selectRange(1.0, 1.5));
// Salida: "other"
El inglés trata todos estos rangos decimales como plurales. Otros idiomas pueden tener reglas diferentes para rangos decimales.
Al formatear rangos decimales, combine selectRange() con Intl.NumberFormat configurado para la precisión decimal apropiada.
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));
// Salida: "1.5-2.5 kilometers"
console.log(formatRange(0.5, 1.0));
// Salida: "0.5-1.0 kilometers"
El formateador de números asegura una visualización decimal consistente. Las reglas de pluralización determinan la forma correcta basada en los valores decimales.
Soporte de navegadores y compatibilidad
El método selectRange() es relativamente nuevo en comparación con el resto de la API Intl. Se hizo disponible en 2023 como parte de la especificación Intl.NumberFormat v3.
El soporte de navegadores incluye Chrome 106 y posteriores, Firefox 116 y posteriores, Safari 15.4 y posteriores, y Edge 106 y posteriores. El método no está disponible en Internet Explorer o versiones antiguas de navegadores.
Para aplicaciones dirigidas a navegadores modernos, puede usar selectRange() sin un polyfill. Si necesita dar soporte a navegadores más antiguos, verifique la existencia del método antes de usarlo.
const rules = new Intl.PluralRules("en-US");
if (typeof rules.selectRange === "function") {
// Usar selectRange para pluralización de rangos
console.log(rules.selectRange(1, 3));
} else {
// Recurrir a select con el valor final
console.log(rules.select(3));
}
Este enfoque alternativo utiliza select() en el valor final cuando selectRange() no está disponible. Esto no es lingüísticamente perfecto para todos los idiomas, pero proporciona una aproximación razonable para navegadores más antiguos.
Hay polyfills disponibles a través de paquetes como @formatjs/intl-pluralrules si necesita soporte completo para entornos más antiguos.
Cuándo usar selectRange versus select
Utiliza selectRange() cuando tu interfaz de usuario muestra explícitamente un rango con valores de inicio y fin visibles para el usuario. Esto incluye contextos como resultados de búsqueda que muestran "Se encontraron 10-15 coincidencias", inventario que muestra "1-3 artículos en stock", o filtros que muestran "Selecciona 2-5 opciones".
Utiliza select() cuando muestres un solo conteo, incluso si ese conteo representa un valor aproximado o resumido. Por ejemplo, "Aproximadamente 10 resultados" usa select(10) porque estás mostrando un solo número, no un rango.
Si tu rango se muestra usando Intl.NumberFormat.formatRange() para los números, utiliza selectRange() para el texto que lo acompaña. Esto asegura la consistencia entre el formato de números y la pluralización del texto.
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));
// Output: "Found 10–15 results"
Este patrón utiliza formatRange() de Intl.NumberFormat para formatear los números y selectRange() de Intl.PluralRules para elegir el texto. Ambos métodos operan en rangos, asegurando un manejo correcto para todos los idiomas.