Pluralisation i18n : règles de pluriel CLDR, i18next et ICU (guide 2026)
La pluralisation est l'une des parties les plus sous-estimées de l'internationalisation. En anglais, elle paraît triviale, un élément contre deux éléments, si bien que la plupart des bases de code commencent par quelque chose comme count === 1 ? 'item' : 'items'. Cette seule ligne est un bug dans la plupart des langues du monde.
Ce guide explique comment les pluriels fonctionnent réellement d'une langue à l'autre (via CLDR), comment i18next les implémente, et la poignée d'erreurs qui cassent silencieusement l'affichage des pluriels en production.
Sommaire
- Pourquoi la pluralisation est difficile
- Catégories de pluriel CLDR
- Comment i18next gère les pluriels
- Interpoler le count
- Pluriels i18next v3 vs v4
- Pluriels ordinaux (1st, 2nd, 3rd)
- Pluriels ICU MessageFormat
- Erreurs fréquentes
- Gérer les pluriels d'une langue à l'autre
- Foire aux questions
Pourquoi la pluralisation est difficile
L'anglais a deux formes plurielles : le singulier (1 item) et le pluriel (0 items, 2 items, 100 items). Cette répartition en deux est l'exception, pas la règle.
- Japonais, chinois, coréen : une forme. Les noms ne changent pas avec la quantité.
- Français : le singulier couvre 0 et 1, le pluriel le reste, plus une forme distincte pour les grands nombres formatés.
- Polonais, russe, ukrainien : la forme pour 2, 3, 4 diffère de celle pour 5-21, qui diffère à son tour de 22-24.
- Arabe : six formes distinctes, dont des formes dédiées pour 0, 1 et 2.
Vous ne pouvez pas exprimer cela avec if (count === 1). Les règles sont propres à chaque langue, non évidentes et définies par des linguistes, pas par des développeurs. Cet ensemble standard de règles, c'est CLDR.
Catégories de pluriel CLDR
Unicode CLDR définit jusqu'à six catégories de pluriel. Chaque langue associe chaque nombre à exactement l'une d'elles :
| Catégorie | Signification (varie selon la langue) |
|---|---|
zero | quantité 0 (seulement certaines langues) |
one | quantité de type singulier |
two | duel (seulement certaines langues) |
few | petite quantité (propre à la langue) |
many | grande quantité (propre à la langue) |
other | catégorie fourre-tout / pluriel général |
Combien une langue en utilise réellement :
| Langue | Catégories utilisées | Nombre |
|---|---|---|
| Japonais, chinois, coréen | other | 1 |
| Anglais, allemand, espagnol, italien | one, other | 2 |
| Français | one, many, other | 3 |
| Polonais, russe, ukrainien | one, few, many, other | 4 |
| Arabe, gallois | zero, one, two, few, many, other | 6 |
La bonne nouvelle : vous n'avez pas à les mémoriser. Le navigateur les fournit. Intl.PluralRules renvoie la bonne catégorie pour n'importe quel nombre et n'importe quelle locale :
new Intl.PluralRules('en').select(1) // "one"
new Intl.PluralRules('en').select(2) // "other"
new Intl.PluralRules('pl').select(2) // "few"
new Intl.PluralRules('pl').select(5) // "many"
new Intl.PluralRules('ar').select(0) // "zero"Comment i18next gère les pluriels
i18next v4 est bâti directement sur Intl.PluralRules. Vous ajoutez une clé JSON par catégorie de pluriel dont la langue a besoin, en utilisant le nom de la catégorie comme suffixe :
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}Puis vous appelez t() avec un count :
t('item', { count: 1 }) // "1 item"
t('item', { count: 5 }) // "5 items"i18next exécute Intl.PluralRules(lng).select(count), obtient la catégorie (one, other, …) et résout item_<category>. Pour une langue qui a besoin de plus de formes, il suffit de fournir plus de clés. Polonais :
{
"item_one": "{{count}} produkt",
"item_few": "{{count}} produkty",
"item_many": "{{count}} produktów",
"item_other": "{{count}} produktu"
}Dans votre code, vous écrivez le même appel t('item', { count }) ; la bonne forme est choisie selon la langue active. C'est tout l'intérêt de la pluralisation i18n : la logique vit dans les données, pas dans vos composants.
Interpoler le count
count sert aussi de valeur d'interpolation, donc {{count}} insère le nombre dans la chaîne :
{ "cart_one": "You have {{count}} item in your cart", "cart_other": "You have {{count}} items in your cart" }Le formatage du nombre (séparateurs de milliers, etc.) est une question distincte et dépendante de la locale, traitée avec la fonction format d'i18next ou avec Intl.NumberFormat. Gardez les fichiers de traduction en .json ; voir la référence fichiers de traduction JSON pour les conventions de structure.
Pluriels i18next v3 vs v4
Si vous lisez d'anciens tutoriels ou maintenez un projet legacy, le format des pluriels est différent. C'est de loin la cause la plus fréquente de « mes pluriels ont cessé de fonctionner » :
| i18next v3 | i18next v4 (actuel) | |
|---|---|---|
| Langues à deux formes | key + key_plural | key_one + key_other |
| Langues à plusieurs formes | key_0, key_1, key_2, … | key_one, key_few, key_many, key_other |
| Source des règles | règles JSON embarquées | Intl.PluralRules natif |
Les clés v4 sont auto-documentées (le suffixe nomme la catégorie) et n'embarquent plus de table de règles plurielles, ce qui allège le runtime. Migrer un projet v3 est un renommage mécanique ; le guide de migration officiel et i18next-cli le couvrent.
Pluriels ordinaux (1st, 2nd, 3rd)
Les pluriels cardinaux (1 item) et les pluriels ordinaux (1st place) utilisent des règles CLDR différentes. i18next prend en charge les ordinaux avec le drapeau ordinal et les clés _ordinal_* :
{
"place_ordinal_one": "{{count}}st place",
"place_ordinal_two": "{{count}}nd place",
"place_ordinal_few": "{{count}}rd place",
"place_ordinal_other": "{{count}}th place"
}t('place', { count: 1, ordinal: true }) // "1st place"
t('place', { count: 3, ordinal: true }) // "3rd place"
t('place', { count: 11, ordinal: true }) // "11th place"Pluriels ICU MessageFormat
Si vous préférez une seule chaîne en ligne aux clés suffixées, le plugin i18next-icu ajoute la prise en charge d'ICU MessageFormat :
{ "item": "{count, plural, one {# item} other {# items}}" }Les deux styles se résolvent aux mêmes catégories CLDR en coulisses. Les clés suffixées sont le comportement par défaut d'i18next (diffs plus simples, une chaîne par forme) ; ICU garde tout dans une seule chaîne (plus dense, familière aux équipes venant de FormatJS/react-intl). Choisissez-en un et restez cohérent.
ICU lui-même est en train d'être remplacé par MessageFormat 2 (MF2), le nouveau standard Unicode (Final Candidate depuis 2025), qui exprime les pluriels via des sélecteurs .match explicites et gère plus proprement les combinaisons genre-plus-pluriel. Son adoption est encore très précoce, ce n'est donc pas encore le choix pragmatique par défaut. Pour savoir s'il faut l'adopter aujourd'hui, voir MessageFormat 2 dans i18next : état de la migration.
Pour comparer le format d'i18next à ICU, Fluent, Gettext et au reste de l'écosystème JS, voir I18N in the Multiverse of Formats.
Erreurs fréquentes
- Passer
countsous forme de chaîne.t('item', { count: '5' })ne sélectionnera pas correctement.Intl.PluralRulesa besoin d'un nombre. - Catégories manquantes. Ne fournir que
_oneet_otherpour une langue qui a besoin de_fewet_many(polonais, russe) provoque un repli silencieux et se lit mal pour les locuteurs natifs. - Incohérence de format v3/v4. Un JSON dans l'ancien format
key_plural/key_0face à un runtime v4 (ou l'inverse) se résout à la clé de base ou à rien. Voir v3 vs v4. - Supposer les règles anglaises partout.
0est_otheren anglais mais_zeroen arabe. Ne traitez pascount === 0comme un cas particulier global. - Ressources pas encore chargées. Si les traductions se chargent de manière asynchrone et que vous affichez avant leur arrivée, vous voyez des clés brutes, sans rapport avec les pluriels. Voir traductions non chargées.
Activez debug: true dans votre configuration i18next pour journaliser exactement quelle clé a été résolue à chaque appel.
Gérer les pluriels d'une langue à l'autre
Le plus difficile n'est pas le code, c'est de garder rempli le bon ensemble de formes plurielles pour chaque langue. Quand vous ajoutez l'arabe, six formes par clé pluralisée doivent soudain être traduites ; quand vous ajoutez le japonais, cinq d'entre elles sont redondantes.
C'est là qu'un système de gestion de traduction justifie sa place. Locize est construit autour du modèle de pluriel d'i18next :
- L'éditeur montre exactement les formes plurielles que chaque langue exige, ni plus ni moins, pour que les traducteurs ne puissent pas oublier
_fewpour le polonais ni laisser_twovide pour l'arabe. - La validation consciente des pluriels signale les clés où une catégorie requise manque.
- Les nouvelles clés ajoutées via saveMissing arrivent avec le bon squelette de pluriel, prêtes pour une traduction humaine ou par IA.
Réglez correctement les règles CLDR une fois, et ajouter une langue devient une tâche de traduction, pas d'ingénierie, ce qui est tout l'objectif de l'i18n.
Foire aux questions
Vous appelez t(key, { count }) et i18next ajoute un suffixe en fonction de la catégorie de pluriel CLDR de ce count pour la langue active, à l'aide de Intl.PluralRules natif du navigateur. En anglais, cela signifie une clé _one et une clé _other ; dans d'autres langues, cela peut aller jusqu'à six clés (_zero, _one, _two, _few, _many, _other). Vous n'écrivez jamais vous-même de logique count === 1.
CLDR (l'Unicode Common Locale Data Repository) définit jusqu'à six catégories de pluriel : zero, one, two, few, many et other. Chaque langue en utilise un sous-ensemble. L'anglais utilise one et other ; le polonais one, few, many, other ; l'arabe les six ; le japonais et le chinois uniquement other. Intl.PluralRules renvoie la bonne catégorie pour n'importe quel nombre et n'importe quelle locale.
Cela varie de une à six. Le japonais, le chinois et le coréen en ont une (pas de pluriel grammatical). L'anglais, l'allemand, l'espagnol et la plupart des langues d'Europe occidentale en ont deux. Le français en a trois. Les langues slaves comme le polonais et le russe en ont quatre. L'arabe et le gallois en ont six. C'est pourquoi un test booléen singulier/pluriel échoue en dehors de l'anglais.
i18next v4 est passé des suffixes numériques (key_0, key_1, key_2) et de key_plural aux noms de catégories CLDR (key_one, key_two, key_few, key_many, key_other), propulsés par Intl.PluralRules. Cela rend les clés plurielles auto-documentées et cohérentes avec le reste de la plateforme. Les projets v3 existants peuvent être convertis avec l'outillage de migration officiel.
Les causes habituelles : vous avez passé count sous forme de chaîne au lieu d'un nombre ; il manque à la clé le bon suffixe CLDR pour la langue active (par exemple seulement _one et _other alors que la langue a besoin de _few et _many) ; votre JSON est encore au format i18next v3 alors que le runtime est en v4 (ou l'inverse) ; ou les ressources de langue n'ont pas fini de se charger. Activez debug: true pour voir quelle clé i18next a résolue.
Oui. Passez ordinal: true à t() et fournissez des clés ordinales (key_ordinal_one, key_ordinal_two, key_ordinal_few, key_ordinal_other). En anglais, elles correspondent à 1st, 2nd, 3rd et 4th+. i18next utilise Intl.PluralRules avec le type "ordinal" pour choisir la bonne forme.
Oui, via le plugin i18next-icu. Il vous permet d'écrire une syntaxe ICU comme {count, plural, one {# item} other {# items}} en ligne dans une seule chaîne au lieu d'utiliser des clés suffixées. Les deux approches utilisent les mêmes règles CLDR sous-jacentes ; les clés suffixées sont le comportement par défaut d'i18next, ICU est optionnel.
Pour les langues dont les règles CLDR incluent une catégorie zero (arabe, letton, gallois), définissez une clé _zero et elle est utilisée automatiquement pour count 0. Pour les langues sans catégorie zero (comme l'anglais, où 0 se résout en _other), ajoutez une clé _zero explicite pour un message « aucun élément » dédié, ou faites un branchement sur count === 0 avant d'appeler t().