¿Cómo elegir la forma plural correcta para diferentes idiomas?
Usa Intl.PluralRules de JavaScript para seleccionar entre un elemento, dos elementos, pocos elementos, muchos elementos según las reglas específicas del idioma
Introducción
Cuando muestras texto con cantidades, necesitas mensajes diferentes para diferentes recuentos. En inglés, escribes "1 file" pero "2 files". El enfoque más simple concatena un número con una palabra y añade una "s" cuando es necesario.
function formatFileCount(count) {
return count === 1 ? `${count} file` : `${count} files`;
}
Este enfoque falla de tres maneras. Primero, produce inglés incorrecto para cero ("0 files" debería ser "no files"). Segundo, se rompe con plurales complejos como "1 child, 2 children" o "1 person, 2 people". Tercero, y más crítico, otros idiomas tienen reglas de plural completamente diferentes que este código no puede manejar.
JavaScript proporciona Intl.PluralRules para resolver este problema. Esta API determina qué forma plural usar para cualquier número en cualquier idioma, siguiendo el estándar Unicode CLDR utilizado por sistemas de traducción profesionales en todo el mundo.
Por qué diferentes idiomas necesitan diferentes formas plurales
El inglés usa dos formas plurales. Escribes "1 book" y "2 books". La palabra cambia cuando el recuento es exactamente uno frente a cualquier otro número.
Otros idiomas funcionan de manera diferente. El polaco usa tres formas basadas en reglas complejas. El ruso usa cuatro formas. El árabe usa seis formas. Algunos idiomas usan solo una forma para todas las cantidades.
Aquí hay ejemplos que muestran cómo la palabra para "manzana" cambia según la cantidad en diferentes idiomas:
Inglés: 1 apple, 2 apples, 5 apples, 0 apples
Polaco: 1 jabłko, 2 jabłka, 5 jabłek, 0 jabłek
Ruso: 1 яблоко, 2 яблока, 5 яблок, 0 яблок
Árabe: utiliza seis formas diferentes dependiendo de si tienes cero, uno, dos, algunos, muchos u otras cantidades
El CLDR de Unicode define las reglas exactas para cuándo usar cada forma en cada idioma. No puedes memorizar estas reglas ni codificarlas directamente en tu aplicación. Necesitas una API que las conozca.
Qué son las categorías plurales del CLDR
El estándar CLDR de Unicode define seis categorías plurales que cubren todos los idiomas:
zero: se usa en algunos idiomas para exactamente cero elementosone: se usa para formas singularestwo: se usa en idiomas con forma dualfew: se usa para cantidades pequeñas en algunos idiomasmany: se usa para cantidades mayores o fracciones en algunos idiomasother: la forma predeterminada, se usa cuando no aplica ninguna otra categoría
Todos los idiomas usan la categoría other. La mayoría de los idiomas usan solo dos o tres categorías en total. Las categorías no se corresponden directamente con las cantidades. Por ejemplo, en polaco, el número 5 usa la categoría many, pero también lo hacen 0, 25 y 1,5.
Las reglas específicas sobre qué números se asignan a qué categorías difieren según el idioma. JavaScript maneja esta complejidad a través de la API Intl.PluralRules.
Cómo determinar qué forma plural usar
El objeto Intl.PluralRules determina a qué categoría plural pertenece un número en un idioma específico. Creas un objeto PluralRules con una configuración regional y luego llamas a su método select() con un número.
const rules = new Intl.PluralRules('en-US');
console.log(rules.select(0)); // "other"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "other"
console.log(rules.select(5)); // "other"
En inglés, select() devuelve "one" para el número 1 y "other" para todo lo demás.
El polaco usa tres categorías con reglas más complejas:
const rules = new Intl.PluralRules('pl-PL');
console.log(rules.select(0)); // "many"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "few"
console.log(rules.select(5)); // "many"
console.log(rules.select(22)); // "few"
console.log(rules.select(25)); // "many"
El árabe utiliza seis categorías:
const rules = new Intl.PluralRules('ar-EG');
console.log(rules.select(0)); // "zero"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "two"
console.log(rules.select(3)); // "few"
console.log(rules.select(11)); // "many"
console.log(rules.select(100)); // "other"
El método select() devuelve una cadena que identifica la categoría. Se utiliza esta cadena para elegir el mensaje apropiado a mostrar.
Cómo mapear categorías plurales a mensajes
Después de determinar la categoría plural, es necesario seleccionar el mensaje correcto para mostrar al usuario. Se crea un objeto que mapea cada categoría a su mensaje, luego se utiliza la cadena de categoría para buscar el mensaje.
const messages = {
one: '{count} file',
other: '{count} files'
};
function formatFileCount(count, locale) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category];
return message.replace('{count}', count);
}
console.log(formatFileCount(1, 'en-US')); // "1 file"
console.log(formatFileCount(5, 'en-US')); // "5 files"
Este patrón funciona para cualquier idioma. Para el polaco, se proporcionan mensajes para las tres categorías que utiliza el idioma:
const messages = {
one: '{count} plik',
few: '{count} pliki',
many: '{count} plików'
};
function formatFileCount(count, locale) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category];
return message.replace('{count}', count);
}
console.log(formatFileCount(1, 'pl-PL')); // "1 plik"
console.log(formatFileCount(2, 'pl-PL')); // "2 pliki"
console.log(formatFileCount(5, 'pl-PL')); // "5 plików"
console.log(formatFileCount(22, 'pl-PL')); // "22 pliki"
La estructura del código permanece idéntica en todos los idiomas. Solo cambia el objeto de mensajes. Esta separación permite a los traductores proporcionar los mensajes correctos para su idioma sin modificar el código.
Cómo manejar categorías plurales faltantes
El objeto de mensajes podría no incluir las seis categorías posibles. La mayoría de los idiomas solo utilizan dos o tres. Cuando select() devuelve una categoría que no está en el objeto de mensajes, se recurre a la categoría other.
function formatFileCount(count, locale, messages) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
const englishMessages = {
one: '{count} file',
other: '{count} files'
};
console.log(formatFileCount(1, 'en-US', englishMessages)); // "1 file"
console.log(formatFileCount(5, 'en-US', englishMessages)); // "5 files"
Este patrón garantiza que el código funcione incluso cuando el objeto de mensajes está incompleto. La categoría other siempre existe en todos los idiomas, lo que la convierte en un respaldo seguro.
Cómo utilizar reglas plurales con números ordinales
El constructor Intl.PluralRules acepta una opción type que cambia cómo se determinan las categorías. El tipo predeterminado es "cardinal", utilizado para contar elementos. Se establece type: "ordinal" para determinar formas plurales para números ordinales como "1.º", "2.º", "3.º".
const cardinalRules = new Intl.PluralRules('en-US', { type: 'cardinal' });
console.log(cardinalRules.select(1)); // "one"
console.log(cardinalRules.select(2)); // "other"
console.log(cardinalRules.select(3)); // "other"
const ordinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log(ordinalRules.select(1)); // "one"
console.log(ordinalRules.select(2)); // "two"
console.log(ordinalRules.select(3)); // "few"
console.log(ordinalRules.select(4)); // "other"
Las reglas cardinales determinan "1 elemento, 2 elementos". Las reglas ordinales determinan "1.º lugar, 2.º lugar, 3.º lugar, 4.º lugar". Las categorías devueltas difieren porque los patrones gramaticales difieren.
Cómo formatear cantidades fraccionarias
El método select() funciona con números decimales. Diferentes idiomas tienen reglas específicas sobre cómo las fracciones se asignan a las categorías plurales.
const rules = new Intl.PluralRules('en-US');
console.log(rules.select(1)); // "one"
console.log(rules.select(1.0)); // "one"
console.log(rules.select(1.5)); // "other"
console.log(rules.select(0.5)); // "other"
En inglés, 1.0 usa la forma singular, pero 1.5 usa el plural. Algunos idiomas tienen reglas diferentes para las fracciones, tratándolas como una categoría separada.
const messages = {
one: '{count} file',
other: '{count} files'
};
const rules = new Intl.PluralRules('en-US');
const count = 1.5;
const category = rules.select(count);
const message = messages[category];
console.log(message.replace('{count}', count)); // "1.5 files"
Pasa el número decimal directamente a select(). El método aplica las reglas del idioma correctas automáticamente.
Cómo crear un formateador plural reutilizable
En lugar de repetir el mismo patrón en toda tu aplicación, crea una función reutilizable que encapsule la lógica de selección plural.
class PluralFormatter {
constructor(locale) {
this.locale = locale;
this.rules = new Intl.PluralRules(locale);
}
format(count, messages) {
const category = this.rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
}
const formatter = new PluralFormatter('en-US');
const fileMessages = {
one: '{count} file',
other: '{count} files'
};
const itemMessages = {
one: '{count} item',
other: '{count} items'
};
console.log(formatter.format(1, fileMessages)); // "1 file"
console.log(formatter.format(5, fileMessages)); // "5 files"
console.log(formatter.format(1, itemMessages)); // "1 item"
console.log(formatter.format(3, itemMessages)); // "3 items"
Esta clase crea el objeto PluralRules una vez y lo reutiliza para múltiples operaciones de formato. Puedes extenderla para admitir funciones más avanzadas como formatear el conteo con Intl.NumberFormat antes de insertarlo en el mensaje.
Cómo integrar reglas plurales con sistemas de traducción
Los sistemas de traducción profesionales almacenan mensajes con marcadores de posición para categorías plurales. Cuando traduces texto, proporcionas todas las formas plurales que tu idioma necesita.
const translations = {
'en-US': {
fileCount: {
one: '{count} file',
other: '{count} files'
},
downloadComplete: {
one: 'Download of {count} file complete',
other: 'Download of {count} files complete'
}
},
'pl-PL': {
fileCount: {
one: '{count} plik',
few: '{count} pliki',
many: '{count} plików'
},
downloadComplete: {
one: 'Pobieranie {count} pliku zakończone',
few: 'Pobieranie {count} plików zakończone',
many: 'Pobieranie {count} plików zakończone'
}
}
};
class Translator {
constructor(locale, translations) {
this.locale = locale;
this.translations = translations[locale] || {};
this.rules = new Intl.PluralRules(locale);
}
translate(key, count) {
const messages = this.translations[key];
if (!messages) return key;
const category = this.rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
}
const translator = new Translator('en-US', translations);
console.log(translator.translate('fileCount', 1)); // "1 file"
console.log(translator.translate('fileCount', 5)); // "5 files"
console.log(translator.translate('downloadComplete', 1)); // "Download of 1 file complete"
console.log(translator.translate('downloadComplete', 5)); // "Download of 5 files complete"
const polishTranslator = new Translator('pl-PL', translations);
console.log(polishTranslator.translate('fileCount', 1)); // "1 plik"
console.log(polishTranslator.translate('fileCount', 2)); // "2 pliki"
console.log(polishTranslator.translate('fileCount', 5)); // "5 plików"
Este patrón separa los datos de traducción de la lógica del código. Los traductores proporcionan los mensajes para cada categoría plural que su idioma usa. Tu código aplica las reglas automáticamente.
Cómo verificar qué categorías plurales usa una configuración regional
El método resolvedOptions() devuelve información sobre el objeto PluralRules, pero no lista qué categorías usa la configuración regional. Para encontrar todas las categorías que usa una configuración regional, prueba un rango de números y recopila las categorías únicas devueltas.
function getPluralCategories(locale) {
const rules = new Intl.PluralRules(locale);
const categories = new Set();
for (let i = 0; i <= 100; i++) {
categories.add(rules.select(i));
categories.add(rules.select(i + 0.5));
}
return Array.from(categories).sort();
}
console.log(getPluralCategories('en-US')); // ["one", "other"]
console.log(getPluralCategories('pl-PL')); // ["few", "many", "one"]
console.log(getPluralCategories('ar-EG')); // ["few", "many", "one", "other", "two", "zero"]
Esta técnica prueba enteros y valores medios en un rango. Captura las categorías que tu objeto de mensajes necesita incluir para una configuración regional determinada.