Vai al contenuto
5 luglio 20265 min readGuides

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

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:

CategoriaSignificato (varia per lingua)
zeroquantità 0 (solo alcune lingue)
onequantità di tipo singolare
twoduale (solo alcune lingue)
fewquantità piccola (specifica per lingua)
manyquantità grande (specifica per lingua)
othercategoria generale / plurale generico

Quante ne usa davvero una lingua:

LinguaCategorie usateNumero
Giapponese, cinese, coreanoother1
Inglese, tedesco, spagnolo, italianoone, other2
Franceseone, many, other3
Polacco, russo, ucrainoone, few, many, other4
Arabo, gallesezero, one, two, few, many, other6

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 v3i18next v4 (attuale)
Lingue a due formekey + key_pluralkey_one + key_other
Lingue a più formekey_0, key_1, key_2, …key_one, key_few, key_many, key_other
Origine delle regoleregole JSON incluse nel bundleIntl.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 count come stringa. t('item', { count: '5' }) non seleziona correttamente. Intl.PluralRules ha bisogno di un numero.
  • Categorie mancanti. Fornire solo _one e _other per una lingua che richiede _few e _many (polacco, russo) ricade silenziosamente e risulta sbagliato ai madrelingua.
  • Disallineamento formato v3/v4. JSON nel vecchio formato key_plural/key_0 contro un runtime v4 (o viceversa) si risolve alla chiave base o a nulla. Vedi v3 vs v4.
  • Assumere le regole inglesi ovunque. 0 è _other in inglese ma _zero in arabo. Non trattare count === 0 come 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 _few per il polacco o lasciare vuoto _two per 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

Come gestisce i plurali i18next?

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.

Cosa sono le categorie plurali CLDR?

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.

Quante forme plurali ha una lingua?

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.

Cosa è cambiato nella pluralizzazione di i18next v4?

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.

Perché il mio plurale i18next non funziona?

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.

i18next supporta i plurali ordinali (1st, 2nd, 3rd)?

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.

i18next supporta i plurali ICU MessageFormat?

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.

Come si gestisce un messaggio «zero» / nessun elemento?

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().