Pluralizzazione i18n: regole plurali CLDR, i18next e ICU (guida 2026)
La pluralizzazione è una delle parti più sottovalutate dell'internazionalizzazione. In inglese sembra banale, un elemento contro due elementi, quindi la maggior parte delle codebase inizia con qualcosa come count === 1 ? 'item' : 'items'. Quell'unica riga è un bug nella maggior parte delle lingue del mondo.
Questa guida spiega come funzionano davvero i plurali nelle varie lingue (tramite CLDR), come i18next li implementa e i pochi errori che rompono silenziosamente l'output dei plurali in produzione.
Indice
- Perché la pluralizzazione è difficile
- Categorie plurali CLDR
- Come i18next gestisce i plurali
- Interpolare il count
- Plurali i18next v3 vs v4
- Plurali ordinali (1st, 2nd, 3rd)
- Plurali ICU MessageFormat
- Errori comuni
- Gestire i plurali tra le lingue
- Domande frequenti
Perché la pluralizzazione è difficile
L'inglese ha due forme plurali: singolare (1 item) e plurale (0 items, 2 items, 100 items). Questa divisione in due è l'eccezione, non la regola.
- Giapponese, cinese, coreano: una forma. I sostantivi non cambiano con la quantità.
- Francese: il singolare copre 0 e 1, il plurale il resto, più una forma separata per i grandi numeri formattati.
- Polacco, russo, ucraino: la forma per 2, 3, 4 è diversa da quella per 5-21, che a sua volta è diversa da 22-24.
- Arabo: sei forme distinte, comprese forme dedicate per 0, 1 e 2.
Non puoi esprimerlo con if (count === 1). Le regole sono specifiche per lingua, non ovvie e definite dai linguisti, non dagli sviluppatori. Quell'insieme standard di regole è CLDR.
Categorie plurali CLDR
Unicode CLDR definisce fino a sei categorie plurali. Ogni lingua mappa ciascun numero esattamente a una di esse:
| Categoria | Significato (varia per lingua) |
|---|---|
zero | quantità 0 (solo alcune lingue) |
one | quantità di tipo singolare |
two | duale (solo alcune lingue) |
few | quantità piccola (specifica per lingua) |
many | quantità grande (specifica per lingua) |
other | categoria generale / plurale generico |
Quante ne usa davvero una lingua:
| Lingua | Categorie usate | Numero |
|---|---|---|
| Giapponese, cinese, coreano | other | 1 |
| Inglese, tedesco, spagnolo, italiano | one, other | 2 |
| Francese | one, many, other | 3 |
| Polacco, russo, ucraino | one, few, many, other | 4 |
| Arabo, gallese | zero, one, two, few, many, other | 6 |
La buona notizia: non devi memorizzarle. Le porta il browser. Intl.PluralRules restituisce la categoria giusta per qualsiasi numero e 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"Come i18next gestisce i plurali
i18next v4 è costruito direttamente su Intl.PluralRules. Aggiungi una chiave JSON per ogni categoria plurale di cui la lingua ha bisogno, usando il nome della categoria come suffisso:
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}Poi chiami t() con un count:
t('item', { count: 1 }) // "1 item"
t('item', { count: 5 }) // "5 items"i18next esegue Intl.PluralRules(lng).select(count), ottiene la categoria (one, other, …) e risolve item_<category>. Per una lingua che richiede più forme, fornisci semplicemente più chiavi. Polacco:
{
"item_one": "{{count}} produkt",
"item_few": "{{count}} produkty",
"item_many": "{{count}} produktów",
"item_other": "{{count}} produktu"
}Nel codice scrivi la stessa chiamata t('item', { count }); la forma corretta viene scelta in base alla lingua attiva. È tutto qui il senso della pluralizzazione i18n: la logica vive nei dati, non nei tuoi componenti.
Interpolare il count
count funge anche da valore di interpolazione, quindi {{count}} inserisce il numero nella stringa:
{ "cart_one": "You have {{count}} item in your cart", "cart_other": "You have {{count}} items in your cart" }La formattazione del numero (separatori delle migliaia, ecc.) è una questione separata e locale-dipendente, gestita con la funzione format di i18next o con Intl.NumberFormat. Mantieni i file di traduzione come .json; vedi il riferimento file di traduzione JSON per le convenzioni di struttura.
Plurali i18next v3 vs v4
Se stai leggendo tutorial più vecchi o manutieni un progetto legacy, il formato dei plurali è diverso. È di gran lunga la causa più comune di «i miei plurali hanno smesso di funzionare»:
| i18next v3 | i18next v4 (attuale) | |
|---|---|---|
| Lingue a due forme | key + key_plural | key_one + key_other |
| Lingue a più forme | key_0, key_1, key_2, … | key_one, key_few, key_many, key_other |
| Origine delle regole | regole JSON incluse nel bundle | Intl.PluralRules nativo |
Le chiavi v4 sono autoesplicative (il suffisso nomina la categoria) e non includono più una tabella di regole plurali, il che mantiene il runtime più leggero. Migrare un progetto v3 è una ridenominazione meccanica; la guida alla migrazione ufficiale e i18next-cli la coprono.
Plurali ordinali (1st, 2nd, 3rd)
I plurali cardinali (1 item) e i plurali ordinali (1st place) usano regole CLDR diverse. i18next supporta gli ordinali con il flag ordinal e le chiavi _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"Plurali ICU MessageFormat
Se preferisci una singola stringa inline invece delle chiavi con suffisso, il plugin i18next-icu aggiunge il supporto per ICU MessageFormat:
{ "item": "{count, plural, one {# item} other {# items}}" }Entrambi gli stili si risolvono nelle stesse categorie CLDR sotto il cofano. Le chiavi con suffisso sono il default di i18next (diff più semplici, una stringa per forma); ICU tiene tutto in una stringa (più densa, familiare ai team che vengono da FormatJS/react-intl). Scegline uno e resta coerente.
ICU stesso sta per essere sostituito da MessageFormat 2 (MF2), il nuovo standard Unicode (Final Candidate dal 2025), che esprime i plurali tramite selettori .match espliciti e gestisce in modo più pulito le combinazioni di genere e plurale. L'adozione è ancora molto ridotta, quindi non è ancora il default pragmatico. Se convenga adottarlo oggi, vedi MessageFormat 2 in i18next: stato della migrazione.
Per come il formato di i18next si confronta con ICU, Fluent, Gettext e il resto dell'ecosistema JS, vedi I18N in the Multiverse of Formats.
Errori comuni
- Passare
countcome stringa.t('item', { count: '5' })non seleziona correttamente.Intl.PluralRulesha bisogno di un numero. - Categorie mancanti. Fornire solo
_onee_otherper una lingua che richiede_fewe_many(polacco, russo) ricade silenziosamente e risulta sbagliato ai madrelingua. - Disallineamento formato v3/v4. JSON nel vecchio formato
key_plural/key_0contro un runtime v4 (o viceversa) si risolve alla chiave base o a nulla. Vedi v3 vs v4. - Assumere le regole inglesi ovunque.
0è_otherin inglese ma_zeroin arabo. Non trattarecount === 0come caso speciale globale. - Risorse non ancora caricate. Se le traduzioni si caricano in modo asincrono e renderizzi prima che arrivino, vedi chiavi grezze, senza relazione con i plurali. Vedi traduzioni non caricate.
Attiva debug: true nella configurazione di i18next per registrare esattamente quale chiave è stata risolta a ogni chiamata.
Gestire i plurali tra le lingue
La parte difficile non è il codice, è mantenere compilato il giusto insieme di forme plurali per ogni lingua. Quando aggiungi l'arabo, all'improvviso servono sei forme da tradurre per ogni chiave pluralizzata; quando aggiungi il giapponese, cinque di esse sono ridondanti.
È qui che un sistema di gestione delle traduzioni si guadagna il suo posto. Locize è costruito attorno al modello plurale di i18next:
- L'editor mostra esattamente le forme plurali che ogni lingua richiede, né più né meno, così i traduttori non possono dimenticare
_fewper il polacco o lasciare vuoto_twoper l'arabo. - La validazione consapevole dei plurali segnala le chiavi in cui manca una categoria richiesta.
- Le nuove chiavi aggiunte tramite saveMissing arrivano con lo scheletro plurale corretto, pronte per la traduzione umana o con l'AI.
Metti a posto le regole CLDR una volta e aggiungere una lingua diventa un compito di traduzione, non di ingegneria, che è l'obiettivo stesso dell'i18n.
Domande frequenti
Chiami t(key, { count }) e i18next aggiunge un suffisso in base alla categoria plurale CLDR di quel count per la lingua attiva, usando Intl.PluralRules nativo del browser. In inglese significa una chiave _one e una _other; in altre lingue possono essere fino a sei chiavi (_zero, _one, _two, _few, _many, _other). Non scrivi mai tu la logica count === 1.
CLDR (l'Unicode Common Locale Data Repository) definisce fino a sei categorie plurali: zero, one, two, few, many e other. Ogni lingua ne usa un sottoinsieme. L'inglese usa one e other; il polacco one, few, many, other; l'arabo tutte e sei; il giapponese e il cinese solo other. Intl.PluralRules restituisce la categoria corretta per qualsiasi numero e locale.
Varia da una a sei. Giapponese, cinese e coreano ne hanno una (nessun plurale grammaticale). Inglese, tedesco, spagnolo e la maggior parte delle lingue dell'Europa occidentale ne hanno due. Il francese ne ha tre. Le lingue slave come polacco e russo ne hanno quattro. Arabo e gallese ne hanno sei. Per questo un controllo booleano singolare/plurale fallisce fuori dall'inglese.
i18next v4 è passato dai suffissi numerici (key_0, key_1, key_2) e da key_plural ai nomi delle categorie CLDR (key_one, key_two, key_few, key_many, key_other), basati su Intl.PluralRules. Questo rende le chiavi plurali autoesplicative e coerenti con il resto della piattaforma. I progetti v3 esistenti si possono convertire con il tooling di migrazione ufficiale.
Le cause tipiche: hai passato count come stringa anziché come numero; alla chiave manca il suffisso CLDR corretto per la lingua attiva (per esempio solo _one e _other mentre la lingua richiede _few e _many); il tuo JSON è ancora nel formato i18next v3 mentre il runtime è v4 (o viceversa); oppure le risorse della lingua non hanno finito di caricarsi. Attiva debug: true per vedere quale chiave ha risolto i18next.
Sì. Passa ordinal: true a t() e fornisci le chiavi ordinali (key_ordinal_one, key_ordinal_two, key_ordinal_few, key_ordinal_other). In inglese corrispondono a 1st, 2nd, 3rd e 4th+. i18next usa Intl.PluralRules con tipo "ordinal" per scegliere la forma giusta.
Sì, tramite il plugin i18next-icu. Ti permette di scrivere sintassi ICU come {count, plural, one {# item} other {# items}} inline in un'unica stringa invece di usare chiavi con suffisso. Entrambi gli approcci usano le stesse regole CLDR sottostanti; le chiavi con suffisso sono il default di i18next, ICU è opzionale.
Per le lingue le cui regole CLDR includono una categoria zero (arabo, lettone, gallese), definisci una chiave _zero e viene usata automaticamente per count 0. Per le lingue senza categoria zero (come l'inglese, dove 0 si risolve in _other), aggiungi una chiave _zero esplicita per un messaggio dedicato «nessun elemento», oppure ramifica su count === 0 prima di chiamare t().