Obtenir toutes les formes plurielles disponibles dans une locale
Découvrez quelles catégories de pluriel vous devez fournir pour les traductions
Introduction
Lors de la création d'applications multilingues, vous devez fournir différentes formes de texte pour différentes quantités. En anglais, vous écrivez "1 item" et "2 items". Cela semble simple jusqu'à ce que vous commenciez à prendre en charge d'autres langues.
Le russe utilise trois formes différentes selon le nombre. L'arabe en utilise six. Certaines langues utilisent la même forme pour tous les nombres. Avant de pouvoir fournir des traductions pour ces formes, vous devez savoir quelles formes existent dans chaque langue.
JavaScript offre un moyen de découvrir quelles catégories de pluriel une locale utilise. La méthode resolvedOptions() sur une instance de PluralRules renvoie une propriété pluralCategories qui liste toutes les formes plurielles dont la locale a besoin. Cela vous indique exactement quelles traductions fournir sans avoir à deviner ou à maintenir des tableaux de règles spécifiques à chaque langue.
Que sont les catégories de pluriel
Les catégories de pluriel sont des noms standardisés pour différentes formes plurielles utilisées dans les langues. L'Unicode CLDR (Common Locale Data Repository) définit six catégories : zero, one, two, few, many et other.
Toutes les langues n'utilisent pas les six catégories. L'anglais n'en utilise que deux : one et other. La catégorie one s'applique au nombre 1, et other s'applique à tout le reste.
L'arabe utilise les six catégories. La catégorie zero s'applique à 0, one à 1, two à 2, few aux nombres comme 3-10, many aux nombres comme 11-99, et other aux nombres comme 100 et plus.
Le russe utilise trois catégories : one pour les nombres se terminant par 1 (sauf 11), few pour les nombres se terminant par 2-4 (sauf 12-14), et many pour tout le reste.
Le japonais et le chinois n'utilisent que la catégorie other car ces langues ne font pas de distinction entre les formes singulières et plurielles.
Ces catégories représentent les règles linguistiques de chaque langue. Lorsque vous fournissez des traductions, vous créez une chaîne pour chaque catégorie utilisée par la langue.
Obtenir les catégories de pluriel avec resolvedOptions
La méthode resolvedOptions() sur une instance de PluralRules renvoie un objet contenant des informations sur les règles, y compris les catégories de pluriel utilisées par la locale.
const enRules = new Intl.PluralRules('en-US');
const options = enRules.resolvedOptions();
console.log(options.pluralCategories);
// Sortie: ["one", "other"]
La propriété pluralCategories est un tableau de chaînes de caractères. Chaque chaîne correspond à l'un des six noms de catégories standard. Le tableau contient uniquement les catégories que la locale utilise réellement.
Pour l'anglais, le tableau contient one et other car l'anglais distingue les formes singulières et plurielles.
Pour une langue avec des règles plus complexes, le tableau contient davantage de catégories :
const arRules = new Intl.PluralRules('ar-EG');
const options = arRules.resolvedOptions();
console.log(options.pluralCategories);
// Sortie: ["zero", "one", "two", "few", "many", "other"]
L'arabe utilise les six catégories, donc le tableau contient les six valeurs.
Observer les catégories de pluriel pour différentes locales
Différentes langues ont différentes règles de pluriel, ce qui signifie qu'elles utilisent différents ensembles de catégories. Comparons plusieurs langues pour voir les variations :
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(', ')}]`);
});
// Sortie :
// 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]
L'anglais a deux catégories. L'arabe en a six. Le russe et le polonais en ont quatre chacun. Le japonais et le chinois n'en ont qu'une seule car ils ne distinguent pas les formes plurielles du tout.
Cette variation montre pourquoi on ne peut pas supposer que toutes les langues fonctionnent comme l'anglais. Il est nécessaire de vérifier quelles catégories chaque locale utilise et de fournir des traductions appropriées pour chacune.
Comprendre ce que signifient les catégories pour chaque locale
Le même nom de catégorie signifie des choses différentes dans différentes langues. La catégorie "one" en anglais s'applique uniquement au nombre 1. En russe, "one" s'applique aux nombres se terminant par 1 sauf 11, donc cela inclut 1, 21, 31, 101, et ainsi de suite.
Testez quels nombres correspondent à quelles catégories dans différentes locales :
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 anglais, seul le nombre 1 utilise la catégorie "one". En russe, 1 et 21 utilisent tous deux "one" car ils se terminent par 1. Les nombres 2, 3 et 22 utilisent "few" car ils se terminent par 2-4. Les nombres 0, 5, 11 et 100 utilisent "many".
Cela démontre qu'on ne peut pas prédire quelle catégorie s'applique à un nombre sans connaître les règles de la langue. Le tableau pluralCategories vous indique quelles catégories existent, et la méthode select() vous indique quelle catégorie s'applique à chaque nombre.
Obtenir les catégories pour les nombres ordinaux
Les nombres ordinaux comme 1er, 2e, 3e ont leurs propres règles de pluriel qui diffèrent des nombres cardinaux. Créez une instance PluralRules avec type: 'ordinal' pour obtenir les catégories pour les nombres ordinaux :
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"]
Les nombres cardinaux en anglais utilisent deux catégories. Les nombres ordinaux en anglais utilisent quatre catégories car les ordinaux doivent distinguer entre 1st, 2nd, 3rd et tous les autres.
Les catégories ordinales correspondent aux suffixes ordinaux :
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 catégorie "one" correspond au suffixe "st" (1st, 21st), "two" à "nd" (2nd, 22nd), "few" à "rd" (3rd, 23rd), et "other" à "th" (4th, 11th).
Différentes langues ont différentes catégories 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]
L'espagnol utilise une seule catégorie ordinale car les ordinaux espagnols suivent un modèle plus simple. Le français utilise deux catégories pour distinguer le premier de toutes les autres positions.
Utilisation des catégories de pluriel pour construire des mappages de traduction
Lorsque vous savez quelles catégories une locale utilise, vous pouvez construire un mappage de traduction avec exactement le bon nombre d'entrées :
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' => 'عنصر واحد', ... }
Cette fonction vérifie que vous avez fourni des traductions pour toutes les catégories requises et vous avertit si certaines sont manquantes. Cela évite les erreurs d'exécution lorsqu'une catégorie est utilisée mais n'a pas de traduction.
Validation de l'exhaustivité des traductions
Utilisez les catégories de pluriel pour vérifier que vos traductions incluent toutes les formes nécessaires avant le déploiement en production :
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
Cette validation détecte les traductions manquantes pendant le développement au lieu de les découvrir lorsque les utilisateurs rencontrent du texte non traduit.
Construction d'interfaces de traduction dynamiques
Lors de la création d'outils pour les traducteurs, interrogez les catégories de pluriel pour montrer exactement quelles formes nécessitent une traduction :
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);
Cela génère un formulaire avec le nombre correct de champs de saisie pour chaque locale. L'anglais obtient deux champs (one et other), tandis que l'arabe en obtient six (zero, one, two, few, many et other).
Comparaison des catégories entre les locales
Lors de la gestion des traductions pour plusieurs locales, comparez les catégories qu'elles utilisent pour comprendre la complexité de la traduction :
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']
// }
Cela montre que l'anglais et l'espagnol ont les mêmes catégories de pluriel, ce qui facilite la réutilisation des structures de traduction entre eux. L'arabe nécessite beaucoup plus de travail de traduction car il utilise six catégories.
Vérifier si une locale utilise une catégorie spécifique
Avant d'utiliser une catégorie de pluriel spécifique dans votre code, vérifiez si la locale l'utilise réellement :
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
Cela vous évite de supposer que chaque locale possède une catégorie zero ou one. Utilisez cette vérification pour implémenter en toute sécurité une logique spécifique à une catégorie.
Comprendre la catégorie other
Chaque langue utilise la catégorie other. Cette catégorie sert de cas par défaut lorsqu'aucune autre catégorie ne s'applique.
En anglais, other couvre tous les nombres sauf 1. En arabe, other couvre les grands nombres comme 100 et plus. En japonais, other couvre tous les nombres car le japonais ne distingue pas les formes plurielles.
Fournissez toujours une traduction pour la catégorie other. Cette catégorie est garantie d'exister dans chaque locale et sera utilisée lorsqu'aucune catégorie plus spécifique ne correspond.
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
Obtenir toutes les options résolues ensemble
La méthode resolvedOptions() renvoie plus que les seules catégories de pluriel. Elle inclut des informations sur la locale, le type et les options de formatage des nombres :
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 propriété pluralCategories est une information parmi d'autres dans l'objet des options résolues. Les autres propriétés vous indiquent la configuration exacte utilisée par l'instance PluralRules, y compris les options qui ont été définies avec des valeurs par défaut.
Mise en cache des catégories de pluriel pour les performances
La création d'instances PluralRules et l'appel à resolvedOptions() ont un coût. Mettez en cache les résultats pour chaque locale au lieu de les interroger de façon répétée :
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);
// Les appels suivants utilisent les résultats mis en cache
const enCardinal2 = getPluralCategories('en-US', 'cardinal');
// Aucune nouvelle instance PluralRules n'est créée
Ce modèle est particulièrement important dans les applications qui formatent de nombreuses chaînes pluralisées ou qui prennent en charge de nombreuses locales.
Support et compatibilité des navigateurs
La propriété pluralCategories sur resolvedOptions() a été ajoutée à JavaScript en 2020. Elle est prise en charge dans Chrome 106+, Firefox 116+, Safari 15.4+ et Edge 106+.
Les navigateurs plus anciens qui prennent en charge Intl.PluralRules mais pas pluralCategories renverront undefined pour cette propriété. Vérifiez son existence avant de l'utiliser :
function getPluralCategories(locale) {
const rules = new Intl.PluralRules(locale);
const options = rules.resolvedOptions();
if (options.pluralCategories) {
return options.pluralCategories;
}
// Solution de repli pour les navigateurs plus anciens
return ['one', 'other'];
}
Cette solution de repli suppose un système simple à deux catégories, qui fonctionne pour l'anglais et de nombreuses langues européennes, mais peut ne pas être correcte pour les langues avec des règles plus complexes. Pour une meilleure compatibilité, fournissez des solutions de repli spécifiques à la langue ou utilisez un polyfill.