API Intl.PluralRules
Comment gérer correctement les formes plurielles en JavaScript
Introduction
La pluralisation est le processus d'affichage de textes différents en fonction d'un nombre. En anglais, vous pourriez afficher "1 item" pour un seul élément et "2 items" pour plusieurs éléments. La plupart des développeurs gèrent cela avec une simple condition qui ajoute un "s" pour les nombres autres que un.
Cette approche ne fonctionne pas pour les langues autres que l'anglais. Le polonais utilise des formes différentes pour 1, 2-4 et 5 ou plus. L'arabe a des formes pour zéro, un, deux, quelques-uns et beaucoup. Le gallois a six formes distinctes. Même en restant en anglais, les pluriels irréguliers comme "person" vers "people" nécessitent un traitement spécial.
L'API Intl.PluralRules résout ce problème en fournissant la catégorie de forme plurielle pour n'importe quel nombre dans n'importe quelle langue. Vous fournissez un nombre, et l'API vous indique quelle forme utiliser en fonction des règles de la langue cible. Cela vous permet d'écrire du code prêt pour l'internationalisation qui fonctionne correctement dans toutes les langues sans encoder manuellement les règles spécifiques à chaque langue.
Comment les langues gèrent les formes plurielles
Les langues diffèrent largement dans la façon dont elles expriment la quantité. L'anglais a deux formes : singulier pour un, pluriel pour tout le reste. Cela semble simple jusqu'à ce que vous rencontriez des langues avec des systèmes différents.
Le russe et le polonais utilisent trois formes. Le singulier s'applique à un élément. Une forme spéciale s'applique aux nombres se terminant par 2, 3 ou 4 (mais pas 12, 13 ou 14). Tous les autres nombres utilisent une troisième forme.
L'arabe utilise six formes : zéro, un, deux, quelques-uns (3-10), beaucoup (11-99) et autre (100+). Le gallois a également six formes avec des limites numériques différentes.
Certaines langues comme le chinois et le japonais ne font aucune distinction entre le singulier et le pluriel. La même forme fonctionne pour n'importe quel nombre.
L'API Intl.PluralRules abstrait ces différences en utilisant des noms de catégories standardisés basés sur les règles de pluralisation Unicode CLDR. Les six catégories sont : zero, one, two, few, many et other. Toutes les langues n'utilisent pas les six catégories. L'anglais utilise uniquement one et other. L'arabe utilise les six.
Créer une instance PluralRules pour une locale
Le constructeur Intl.PluralRules prend un identifiant de locale et retourne un objet qui peut déterminer quelle catégorie de pluriel s'applique à un nombre donné.
const enRules = new Intl.PluralRules('en-US');
Créez une instance par locale et réutilisez-la. Construire une nouvelle instance pour chaque pluralisation est inefficace. Stockez l'instance dans une variable ou utilisez un mécanisme de mise en cache.
Le type par défaut est cardinal, qui gère le comptage d'objets. Vous pouvez également créer des règles pour les nombres ordinaux en passant un objet d'options.
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
Les règles cardinales s'appliquent aux décomptes comme « 1 pomme, 2 pommes ». Les règles ordinales s'appliquent aux positions comme « 1re place, 2e place ».
Utiliser select() pour obtenir la catégorie de pluriel d'un nombre
La méthode select() prend un nombre et retourne la catégorie de pluriel à laquelle il appartient dans la langue cible.
const enRules = new Intl.PluralRules('en-US');
enRules.select(0); // 'other'
enRules.select(1); // 'one'
enRules.select(2); // 'other'
enRules.select(5); // 'other'
La valeur de retour est toujours l'un des six noms de catégories : zero, one, two, few, many ou other. L'anglais retourne uniquement one et other car ce sont les seules formes utilisées en anglais.
Pour l'arabe, qui a des règles plus complexes, vous voyez les six catégories utilisées :
const arRules = new Intl.PluralRules('ar-EG');
arRules.select(0); // 'zero'
arRules.select(1); // 'one'
arRules.select(2); // 'two'
arRules.select(6); // 'few'
arRules.select(18); // 'many'
arRules.select(100); // 'other'
Associer les catégories aux chaînes localisées
L'API vous indique uniquement quelle catégorie s'applique. Vous fournissez le texte réel pour chaque catégorie. Stockez les formes textuelles dans un Map ou un objet, indexé par nom de catégorie.
const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
['one', 'item'],
['other', 'items'],
]);
function formatItems(count) {
const category = enRules.select(count);
const form = enForms.get(category);
return `${count} ${form}`;
}
formatItems(1); // '1 item'
formatItems(5); // '5 items'
Ce modèle sépare la logique des données. L'instance PluralRules gère les règles. Le Map contient les traductions. La fonction les combine.
Pour les langues avec plus de catégories, ajoutez plus d'entrées au Map :
const arRules = new Intl.PluralRules('ar-EG');
const arForms = new Map([
['zero', 'عناصر'],
['one', 'عنصر واحد'],
['two', 'عنصران'],
['few', 'عناصر'],
['many', 'عنصرًا'],
['other', 'عنصر'],
]);
function formatItems(count) {
const category = arRules.select(count);
const form = arForms.get(category);
return `${count} ${form}`;
}
Fournissez toujours des entrées pour chaque catégorie utilisée par la langue. Les catégories manquantes provoquent des recherches undefined. Si vous n'êtes pas sûr des catégories utilisées par une langue, consultez les règles de pluriel Unicode CLDR ou testez avec l'API sur différents nombres.
Gérer les nombres décimaux et fractionnaires
La méthode select() fonctionne avec les nombres décimaux. L'anglais traite les décimales comme plurielles, même pour les valeurs entre 0 et 2.
const enRules = new Intl.PluralRules('en-US');
enRules.select(1); // 'one'
enRules.select(1.0); // 'one'
enRules.select(1.5); // 'other'
enRules.select(0.5); // 'other'
D'autres langues ont des règles différentes pour les décimales. Certaines traitent toute décimale comme plurielle, tandis que d'autres utilisent des règles plus nuancées basées sur la partie fractionnaire.
Si votre interface utilisateur affiche des quantités fractionnaires comme "1,5 Go" ou "2,7 miles", passez le nombre fractionnaire directement à select(). N'arrondissez pas d'abord sauf si votre interface utilisateur arrondit la valeur affichée.
Formater les nombres ordinaux comme 1er, 2e, 3e
Les nombres ordinaux indiquent une position ou un rang. L'anglais forme les ordinaux en ajoutant des suffixes : 1st, 2nd, 3rd, 4th. Le modèle n'est pas simplement "ajouter th" car 1, 2 et 3 ont des formes spéciales, et les nombres se terminant par 1, 2 ou 3 suivent des règles spéciales (21st, 22nd, 23rd) sauf lorsqu'ils se terminent par 11, 12 ou 13 (11th, 12th, 13th).
L'API Intl.PluralRules gère ces règles lorsque vous spécifiez type: 'ordinal'.
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
enOrdinalRules.select(1); // 'one'
enOrdinalRules.select(2); // 'two'
enOrdinalRules.select(3); // 'few'
enOrdinalRules.select(4); // 'other'
enOrdinalRules.select(11); // 'other'
enOrdinalRules.select(21); // 'one'
enOrdinalRules.select(22); // 'two'
enOrdinalRules.select(23); // 'few'
Associez les catégories aux suffixes ordinaux :
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const enOrdinalSuffixes = new Map([
['one', 'st'],
['two', 'nd'],
['few', 'rd'],
['other', 'th'],
]);
function formatOrdinal(n) {
const category = enOrdinalRules.select(n);
const suffix = enOrdinalSuffixes.get(category);
return `${n}${suffix}`;
}
formatOrdinal(1); // '1st'
formatOrdinal(2); // '2nd'
formatOrdinal(3); // '3rd'
formatOrdinal(4); // '4th'
formatOrdinal(11); // '11th'
formatOrdinal(21); // '21st'
D'autres langues ont des systèmes ordinaux entièrement différents. Le français utilise « 1er » pour premier et « 2e » pour tous les autres. L'espagnol a des ordinaux spécifiques au genre. L'API fournit la catégorie, et vous fournissez les formes localisées.
Gérer les plages avec selectRange()
La méthode selectRange() détermine la catégorie plurielle pour une plage de nombres, comme « 1-5 éléments » ou « 10-20 résultats ». Certaines langues ont des règles de pluriel différentes pour les plages que pour les décomptes individuels.
const enRules = new Intl.PluralRules('en-US');
enRules.selectRange(1, 5); // 'other'
enRules.selectRange(0, 1); // 'other'
En anglais, les plages sont presque toujours au pluriel, même lorsque la plage commence à 1. D'autres langues ont des règles de plage plus complexes.
const slRules = new Intl.PluralRules('sl');
slRules.selectRange(102, 201); // 'few'
const ptRules = new Intl.PluralRules('pt');
ptRules.selectRange(102, 102); // 'other'
Utilisez selectRange() lors de l'affichage explicite de plages dans votre interface utilisateur. Pour les décomptes individuels, utilisez select().
Combiner avec Intl.NumberFormat pour l'affichage localisé des nombres
Les formes plurielles apparaissent souvent aux côtés de nombres formatés. Utilisez Intl.NumberFormat pour formater le nombre selon les conventions locales, puis utilisez Intl.PluralRules pour choisir le texte correct.
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 formatCount(count) {
const formattedNumber = numberFormat.format(count);
const category = pluralRules.select(count);
const form = forms.get(category);
return `${formattedNumber} ${form}`;
}
formatCount(1); // '1 item'
formatCount(1000); // '1,000 items'
formatCount(1.5); // '1.5 items'
Pour l'allemand, qui utilise des points comme séparateurs de milliers et des virgules comme séparateurs décimaux :
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 formatCount(count) {
const formattedNumber = numberFormat.format(count);
const category = pluralRules.select(count);
const form = forms.get(category);
return `${formattedNumber} ${form}`;
}
formatCount(1); // '1 Artikel'
formatCount(1000); // '1.000 Artikel'
formatCount(1.5); // '1,5 Artikel'
Ce modèle garantit que le formatage des nombres et la forme textuelle correspondent aux attentes des utilisateurs pour la locale.
Gérer le cas zéro explicitement si nécessaire
La façon dont zéro est pluralisé varie selon la langue. L'anglais utilise généralement la forme plurielle : « 0 items », « 0 results ». Certaines langues utilisent la forme singulière pour zéro. D'autres ont une catégorie zéro distincte.
L'API Intl.PluralRules renvoie la catégorie appropriée pour zéro en fonction des règles linguistiques. En anglais, zéro renvoie 'other', qui correspond à la forme plurielle :
const enRules = new Intl.PluralRules('en-US');
enRules.select(0); // 'other'
En arabe, zéro possède sa propre catégorie :
const arRules = new Intl.PluralRules('ar-EG');
arRules.select(0); // 'zero'
Votre texte doit en tenir compte. Pour l'anglais, vous pourriez vouloir afficher "No items" au lieu de "0 items" pour une meilleure expérience utilisateur. Gérez cela avant d'appeler les règles de pluralisation :
function formatItems(count) {
if (count === 0) {
return 'No items';
}
const category = enRules.select(count);
const form = enForms.get(category);
return `${count} ${form}`;
}
Pour l'arabe, fournissez une forme spécifique pour zéro dans vos traductions :
const arForms = new Map([
['zero', 'لا توجد عناصر'],
['one', 'عنصر واحد'],
['two', 'عنصران'],
['few', 'عناصر'],
['many', 'عنصرًا'],
['other', 'عنصر'],
]);
Cela respecte les conventions linguistiques de chaque langue tout en vous permettant de personnaliser le cas zéro pour une meilleure expérience utilisateur.
Réutiliser les instances PluralRules pour les performances
Créer une instance PluralRules implique d'analyser la locale et de charger les données de règles de pluralisation. Faites-le une fois par locale, pas à chaque appel de fonction ou cycle de rendu.
// Good: create once, reuse
const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
['one', 'item'],
['other', 'items'],
]);
function formatItems(count) {
const category = enRules.select(count);
const form = enForms.get(category);
return `${count} ${form}`;
}
Si vous prenez en charge plusieurs locales, créez des instances pour chaque locale et stockez-les dans une Map ou un cache :
const rulesCache = new Map();
function getPluralRules(locale) {
if (!rulesCache.has(locale)) {
rulesCache.set(locale, new Intl.PluralRules(locale));
}
return rulesCache.get(locale);
}
const rules = getPluralRules('en-US');
Ce pattern amortit le coût d'initialisation sur de nombreux appels.
Compatibilité et prise en charge des navigateurs
Intl.PluralRules est pris en charge dans tous les navigateurs modernes depuis 2019. Cela inclut Chrome 63+, Firefox 58+, Safari 13+ et Edge 79+. Il n'est pas pris en charge dans Internet Explorer.
Pour les applications ciblant les navigateurs modernes, vous pouvez utiliser Intl.PluralRules sans polyfill. Si vous devez prendre en charge des navigateurs plus anciens, des polyfills sont disponibles via des packages comme intl-pluralrules sur npm.
La méthode selectRange() est plus récente et bénéficie d'une prise en charge légèrement plus limitée. Elle est disponible dans Chrome 106+, Firefox 116+, Safari 15.4+ et Edge 106+. Vérifiez la compatibilité si vous utilisez selectRange() et devez prendre en charge des versions de navigateurs plus anciennes.
Éviter de coder en dur les formes plurielles dans la logique
Ne vérifiez pas le nombre et ne créez pas de branchements dans le code pour sélectionner une forme plurielle. Cette approche ne s'adapte pas aux langues avec plus de deux formes et couple votre logique aux règles de l'anglais.
// Avoid this pattern
function formatItems(count) {
if (count === 1) {
return `${count} item`;
}
return `${count} items`;
}
Utilisez Intl.PluralRules et une structure de données pour stocker les formes. Cela permet de garder votre code indépendant de la langue et facilite l'ajout de nouvelles langues en fournissant simplement de nouvelles traductions.
// Prefer this pattern
const rules = new Intl.PluralRules('en-US');
const forms = new Map([
['one', 'item'],
['other', 'items'],
]);
function formatItems(count) {
const category = rules.select(count);
const form = forms.get(category);
return `${count} ${form}`;
}
Ce modèle fonctionne de manière identique pour n'importe quelle langue. Seules l'instance rules et la Map forms changent.
Testez avec plusieurs locales et cas limites
Les règles de pluralisation comportent des cas limites faciles à manquer lorsqu'on teste uniquement en anglais. Testez votre logique de pluralisation avec au moins une langue qui utilise plus de deux formes, comme le polonais ou l'arabe.
Testez les nombres qui déclenchent différentes catégories :
- Zéro
- Un
- Deux
- Quelques-uns (3-10 en arabe)
- Beaucoup (11-99 en arabe)
- Grands nombres (100+)
- Valeurs décimales (0,5, 1,5, 2,3)
- Nombres négatifs si votre interface les affiche
Si vous utilisez des règles ordinales, testez les nombres qui déclenchent différents suffixes : 1, 2, 3, 4, 11, 21, 22, 23. Cela garantit que vous gérez correctement les cas particuliers.
Tester avec plusieurs locales dès le début évite les surprises lorsque vous ajoutez de nouvelles langues plus tard. Cela valide également que votre structure de données inclut toutes les catégories nécessaires et que votre logique les gère correctement.