Obtener todas las formas plurales disponibles en un locale
Descubre qué categorías plurales necesitas proporcionar traducciones
Introducción
Al crear aplicaciones multilingües, necesitas proporcionar diferentes formas de texto para diferentes cantidades. En inglés, escribes "1 item" y "2 items". Esto parece simple hasta que comienzas a soportar otros idiomas.
El ruso utiliza tres formas diferentes dependiendo del conteo. El árabe utiliza seis. Algunos idiomas utilizan la misma forma para todos los conteos. Antes de poder proporcionar traducciones para estas formas, necesitas saber qué formas existen en cada idioma.
JavaScript proporciona una forma de descubrir qué categorías plurales utiliza un locale. El método resolvedOptions() en una instancia de PluralRules devuelve una propiedad pluralCategories que lista todas las formas plurales que el locale necesita. Esto te indica exactamente qué traducciones proporcionar sin adivinar o mantener tablas de reglas específicas del idioma.
Qué son las categorías plurales
Las categorías plurales son nombres estandarizados para diferentes formas plurales utilizadas en todos los idiomas. El CLDR de Unicode (Common Locale Data Repository) define seis categorías: zero, one, two, few, many y other.
No todos los idiomas utilizan las seis categorías. El inglés utiliza solo dos: one y other. La categoría one se aplica al conteo 1, y other se aplica a todo lo demás.
El árabe utiliza las seis categorías. La categoría zero se aplica a 0, one a 1, two a 2, few a conteos como 3-10, many a conteos como 11-99, y other a conteos como 100 y superiores.
El ruso utiliza tres categorías: one para conteos que terminan en 1 (excepto 11), few para conteos que terminan en 2-4 (excepto 12-14), y many para todo lo demás.
El japonés y el chino utilizan únicamente la categoría other porque estos idiomas no distinguen entre formas singulares y plurales.
Estas categorías representan las reglas lingüísticas de cada idioma. Cuando proporcionas traducciones, creas una cadena para cada categoría que el idioma utiliza.
Obtener categorías plurales con resolvedOptions
El método resolvedOptions() en una instancia de PluralRules devuelve un objeto que contiene información sobre las reglas, incluyendo qué categorías plurales utiliza el locale.
const enRules = new Intl.PluralRules('en-US');
const options = enRules.resolvedOptions();
console.log(options.pluralCategories);
// Output: ["one", "other"]
La propiedad pluralCategories es un array de cadenas. Cada cadena es uno de los seis nombres de categoría estándar. El array contiene únicamente las categorías que el locale realmente utiliza.
Para el inglés, el array contiene one y other porque el inglés distingue entre formas singulares y plurales.
Para un idioma con reglas más complejas, el array contiene más categorías:
const arRules = new Intl.PluralRules('ar-EG');
const options = arRules.resolvedOptions();
console.log(options.pluralCategories);
// Output: ["zero", "one", "two", "few", "many", "other"]
El árabe utiliza las seis categorías, por lo que el array contiene los seis valores.
Ver categorías plurales para diferentes locales
Diferentes idiomas tienen diferentes reglas plurales, lo que significa que utilizan diferentes conjuntos de categorías. Compara varios idiomas para ver la variación:
const locales = ['en-US', 'ar-EG', 'ru-RU', 'pl-PL', 'ja-JP', 'zh-CN'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
console.log(`${locale}: [${categories.join(', ')}]`);
});
// Output:
// en-US: [one, other]
// ar-EG: [zero, one, two, few, many, other]
// ru-RU: [one, few, many, other]
// pl-PL: [one, few, many, other]
// ja-JP: [other]
// zh-CN: [other]
El inglés tiene dos categorías. El árabe tiene seis. El ruso y el polaco tienen cuatro cada uno. El japonés y el chino tienen solo una porque no distinguen formas plurales en absoluto.
Esta variación muestra por qué no puedes asumir que todos los idiomas funcionan como el inglés. Necesitas verificar qué categorías utiliza cada locale y proporcionar traducciones apropiadas para cada una.
Entender qué significan las categorías para cada locale
El mismo nombre de categoría significa cosas diferentes en diferentes idiomas. La categoría one en inglés se aplica únicamente al número 1. En ruso, one se aplica a números que terminan en 1 excepto 11, por lo que incluye 1, 21, 31, 101, y así sucesivamente.
Prueba qué números se asignan a qué categorías en diferentes configuraciones regionales:
const enRules = new Intl.PluralRules('en-US');
const ruRules = new Intl.PluralRules('ru-RU');
const numbers = [0, 1, 2, 3, 5, 11, 21, 22, 100];
console.log('English:');
numbers.forEach(n => {
console.log(` ${n}: ${enRules.select(n)}`);
});
console.log('Russian:');
numbers.forEach(n => {
console.log(` ${n}: ${ruRules.select(n)}`);
});
// Output:
// English:
// 0: other
// 1: one
// 2: other
// 3: other
// 5: other
// 11: other
// 21: other
// 22: other
// 100: other
// Russian:
// 0: many
// 1: one
// 2: few
// 3: few
// 5: many
// 11: many
// 21: one
// 22: few
// 100: many
En inglés, solo el 1 usa la categoría one. En ruso, tanto el 1 como el 21 usan one porque terminan en 1. Los números 2, 3 y 22 usan few porque terminan en 2-4. Los números 0, 5, 11 y 100 usan many.
Esto demuestra que no puedes predecir qué categoría se aplica a un número sin conocer las reglas del idioma. El array pluralCategories te indica qué categorías existen, y el método select() te indica qué categoría se aplica a cada número.
Obtener categorías para números ordinales
Los números ordinales como 1.º, 2.º, 3.º tienen sus propias reglas de plural que difieren de los números cardinales. Crea una instancia de PluralRules con type: 'ordinal' para obtener las categorías de números ordinales:
const enCardinalRules = new Intl.PluralRules('en-US', { type: 'cardinal' });
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log('Cardinal:', enCardinalRules.resolvedOptions().pluralCategories);
// Output: Cardinal: ["one", "other"]
console.log('Ordinal:', enOrdinalRules.resolvedOptions().pluralCategories);
// Output: Ordinal: ["one", "two", "few", "other"]
Los números cardinales en inglés usan dos categorías. Los números ordinales en inglés usan cuatro categorías porque los ordinales necesitan distinguir entre 1st, 2nd, 3rd y todos los demás.
Las categorías ordinales se asignan a sufijos ordinales:
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const numbers = [1, 2, 3, 4, 11, 21, 22, 23];
numbers.forEach(n => {
const category = enOrdinalRules.select(n);
console.log(`${n}: ${category}`);
});
// Output:
// 1: one
// 2: two
// 3: few
// 4: other
// 11: other
// 21: one
// 22: two
// 23: few
La categoría one corresponde al sufijo st (1st, 21st), two a nd (2nd, 22nd), few a rd (3rd, 23rd), y other a th (4th, 11th).
Diferentes idiomas tienen diferentes categorías ordinales:
const locales = ['en-US', 'es-ES', 'fr-FR'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
const categories = rules.resolvedOptions().pluralCategories;
console.log(`${locale}: [${categories.join(', ')}]`);
});
// Output:
// en-US: [one, two, few, other]
// es-ES: [other]
// fr-FR: [one, other]
El español usa solo una categoría ordinal porque los ordinales en español siguen un patrón más simple. El francés usa dos categorías para distinguir el primero de todas las demás posiciones.
Usar categorías de plural para construir mapas de traducción
Cuando sabes qué categorías usa una configuración regional, puedes construir un mapa de traducción con exactamente el número correcto de entradas:
function buildTranslationMap(locale, translations) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const map = new Map();
categories.forEach(category => {
if (translations[category]) {
map.set(category, translations[category]);
} else {
console.warn(`Missing translation for category "${category}" in locale "${locale}"`);
}
});
return map;
}
const enTranslations = {
one: 'item',
other: 'items'
};
const arTranslations = {
zero: 'لا توجد عناصر',
one: 'عنصر واحد',
two: 'عنصران',
few: 'عناصر',
many: 'عنصرًا',
other: 'عنصر'
};
const enMap = buildTranslationMap('en-US', enTranslations);
const arMap = buildTranslationMap('ar-EG', arTranslations);
console.log(enMap);
// Output: Map(2) { 'one' => 'item', 'other' => 'items' }
console.log(arMap);
// Output: Map(6) { 'zero' => 'لا توجد عناصر', 'one' => 'عنصر واحد', ... }
Esta función verifica que hayas proporcionado traducciones para todas las categorías requeridas y te advierte si falta alguna. Esto previene errores en tiempo de ejecución cuando se usa una categoría pero no tiene traducción.
Validación de la completitud de las traducciones
Utiliza las categorías de plural para verificar que tus traducciones incluyan todas las formas necesarias antes de desplegar a producción:
function validateTranslations(locale, translations) {
const rules = new Intl.PluralRules(locale);
const requiredCategories = rules.resolvedOptions().pluralCategories;
const providedCategories = Object.keys(translations);
const missing = requiredCategories.filter(cat => !providedCategories.includes(cat));
const extra = providedCategories.filter(cat => !requiredCategories.includes(cat));
if (missing.length > 0) {
console.error(`Locale ${locale} is missing categories: ${missing.join(', ')}`);
return false;
}
if (extra.length > 0) {
console.warn(`Locale ${locale} has unused categories: ${extra.join(', ')}`);
}
return true;
}
const enTranslations = {
one: 'item',
other: 'items'
};
const incompleteArTranslations = {
one: 'عنصر واحد',
other: 'عنصر'
};
validateTranslations('en-US', enTranslations);
// Output: true
validateTranslations('ar-EG', incompleteArTranslations);
// Output: Locale ar-EG is missing categories: zero, two, few, many
// Output: false
Esta validación detecta traducciones faltantes durante el desarrollo en lugar de descubrirlas cuando los usuarios encuentran texto sin traducir.
Construcción de interfaces de traducción dinámicas
Al construir herramientas para traductores, consulta las categorías de plural para mostrar exactamente qué formas necesitan traducción:
function generateTranslationForm(locale, key) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const form = document.createElement('div');
form.className = 'translation-form';
const heading = document.createElement('h3');
heading.textContent = `Translate "${key}" for ${locale}`;
form.appendChild(heading);
categories.forEach(category => {
const label = document.createElement('label');
label.textContent = `${category}:`;
const input = document.createElement('input');
input.type = 'text';
input.name = `${key}.${category}`;
input.placeholder = `Enter ${category} form`;
const wrapper = document.createElement('div');
wrapper.appendChild(label);
wrapper.appendChild(input);
form.appendChild(wrapper);
});
return form;
}
const enForm = generateTranslationForm('en-US', 'items');
const arForm = generateTranslationForm('ar-EG', 'items');
document.body.appendChild(enForm);
document.body.appendChild(arForm);
Esto genera un formulario con el número correcto de campos de entrada para cada locale. El inglés obtiene dos campos (one y other), mientras que el árabe obtiene seis campos (zero, one, two, few, many y other).
Comparación de categorías entre locales
Al gestionar traducciones para múltiples locales, compara qué categorías utilizan para comprender la complejidad de la traducción:
function compareLocalePluralCategories(locales) {
const comparison = {};
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
comparison[locale] = categories;
});
return comparison;
}
const locales = ['en-US', 'es-ES', 'ar-EG', 'ru-RU', 'ja-JP'];
const comparison = compareLocalePluralCategories(locales);
console.log(comparison);
// Output:
// {
// 'en-US': ['one', 'other'],
// 'es-ES': ['one', 'other'],
// 'ar-EG': ['zero', 'one', 'two', 'few', 'many', 'other'],
// 'ru-RU': ['one', 'few', 'many', 'other'],
// 'ja-JP': ['other']
// }
Esto muestra que el inglés y el español tienen las mismas categorías de plural, lo que facilita la reutilización de estructuras de traducción entre ellos. El árabe requiere significativamente más trabajo de traducción porque utiliza seis categorías.
Verificación de si un locale utiliza una categoría específica
Antes de utilizar una categoría de plural específica en tu código, verifica si el locale realmente la utiliza:
function localeUsesCategory(locale, category) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
return categories.includes(category);
}
console.log(localeUsesCategory('en-US', 'zero'));
// Output: false
console.log(localeUsesCategory('ar-EG', 'zero'));
// Output: true
console.log(localeUsesCategory('ja-JP', 'one'));
// Output: false
Esto evita que asumas que cada locale tiene una categoría zero o one. Utiliza esta verificación para implementar lógica específica de categoría de forma segura.
Comprensión de la categoría other
Todos los idiomas utilizan la categoría other. Esta categoría sirve como caso predeterminado cuando no se aplica ninguna otra categoría.
En inglés, other cubre todos los recuentos excepto 1. En árabe, other cubre números grandes como 100 y superiores. En japonés, other cubre todos los recuentos porque el japonés no distingue formas plurales.
Proporciona siempre una traducción para la categoría other. Esta categoría está garantizada que existe en cada locale y se utilizará cuando no coincida ninguna categoría más específica.
const locales = ['en-US', 'ar-EG', 'ru-RU', 'ja-JP'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const hasOther = categories.includes('other');
console.log(`${locale} uses "other": ${hasOther}`);
});
// Output:
// en-US uses "other": true
// ar-EG uses "other": true
// ru-RU uses "other": true
// ja-JP uses "other": true
Obtener todas las opciones resueltas juntas
El método resolvedOptions() devuelve más que solo categorías de plurales. Incluye información sobre el locale, el tipo y las opciones de formato de números:
const rules = new Intl.PluralRules('de-DE', {
type: 'cardinal',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const options = rules.resolvedOptions();
console.log(options);
// Output:
// {
// locale: 'de-DE',
// type: 'cardinal',
// pluralCategories: ['one', 'other'],
// minimumIntegerDigits: 1,
// minimumFractionDigits: 2,
// maximumFractionDigits: 2,
// minimumSignificantDigits: undefined,
// maximumSignificantDigits: undefined
// }
La propiedad pluralCategories es una parte de la información en el objeto de opciones resueltas. Las otras propiedades te indican la configuración exacta que utiliza la instancia de PluralRules, incluyendo cualquier opción que se haya establecido con valores predeterminados.
Almacenar en caché las categorías de plurales para mejorar el rendimiento
Crear instancias de PluralRules y llamar a resolvedOptions() tiene un coste. Almacena en caché los resultados para cada locale en lugar de consultarlos repetidamente:
const categoriesCache = new Map();
function getPluralCategories(locale, type = 'cardinal') {
const key = `${locale}:${type}`;
if (categoriesCache.has(key)) {
return categoriesCache.get(key);
}
const rules = new Intl.PluralRules(locale, { type });
const categories = rules.resolvedOptions().pluralCategories;
categoriesCache.set(key, categories);
return categories;
}
const enCardinal = getPluralCategories('en-US', 'cardinal');
const enOrdinal = getPluralCategories('en-US', 'ordinal');
const arCardinal = getPluralCategories('ar-EG', 'cardinal');
console.log('en-US cardinal:', enCardinal);
console.log('en-US ordinal:', enOrdinal);
console.log('ar-EG cardinal:', arCardinal);
// Subsequent calls use cached results
const enCardinal2 = getPluralCategories('en-US', 'cardinal');
// No new PluralRules instance created
Este patrón es especialmente importante en aplicaciones que formatean muchas cadenas pluralizadas o admiten muchos locales.
Compatibilidad y soporte de navegadores
La propiedad pluralCategories en resolvedOptions() se añadió a JavaScript en 2020. Es compatible con Chrome 106+, Firefox 116+, Safari 15.4+ y Edge 106+.
Los navegadores más antiguos que admiten Intl.PluralRules pero no pluralCategories devolverán undefined para esta propiedad. Verifica su existencia antes de usarla:
function getPluralCategories(locale) {
const rules = new Intl.PluralRules(locale);
const options = rules.resolvedOptions();
if (options.pluralCategories) {
return options.pluralCategories;
}
// Fallback for older browsers
return ['one', 'other'];
}
Este fallback asume un sistema simple de dos categorías, que funciona para inglés y muchos idiomas europeos, pero puede no ser correcto para idiomas con reglas más complejas. Para una mejor compatibilidad, proporciona fallbacks específicos para cada idioma o utiliza un polyfill.