Pose una domanda e ottieni un riassunto del documento facendo riferimento a questa pagina e al provider AI di tua scelta
Il contenuto di questa pagina è stato tradotto con un'IA.
Vedi l'ultima versione del contenuto originale in ingleseSe hai un’idea per migliorare questa documentazione, non esitare a contribuire inviando una pull request su GitHub.
Collegamento GitHub alla documentazioneCopia il Markdown del documento nella porta-documenti
next-intl VS @intlayer/next-intl | Stessa API, Bundle Diverso
@intlayer/next-intl è un compat adapter: espone l'API di next-intl (useTranslations, getTranslations, useLocale, t.rich(), ICU plurals, NextIntlClientProvider...) e la serve da dizionari compilati da Intlayer. Il codice dell'applicazione non cambia. Il bundle sì.
Questo articolo confronta i due sulla stessa applicazione Next.js, costruita una volta con next-intl e una volta con l'adapter. I numeri provengono da Benchmark Bloom, una suite open-source che registra ciò che il browser effettivamente scarica. Se vuoi il confronto next-intl vs Intlayer come librerie, leggi next-intl vs Intlayer. Questo riguarda ciò che l'adapter cambia quando mantieni i tuoi componenti come sono.
tl;dr: Sulla stessa app Next.js, sostituirenext-intlcon@intlayer/next-intlha ridotto il JavaScript per pagina da 153.6 KB a 147.5 KB gzip, il componente medio da 21.8 KB a 8.1 KB, la perdita di stringhe da pagine estere da ~90% a 0%, e l'idratazione da 14.7 ms a 12.8 ms, senza modificare alcun componente. Su TanStack Start, l'equivalenteuse-intl(@intlayer/use-intl) ha ridotto i componenti da 76-87 KB a 9-11 KB e il cambio di locale da 7-21 ms a 4-9 ms. L'adapter costa 8.0 KB di runtime rispetto ai 14.7 KB dinext-intle ai 5.5 KB dinext-intlayernativo. La navigazione e il middleware vengono reimplementati sulla configurazione di routing di Intlayer; ipathnameslocalizzati sono l'unica feature non trasferita.
Cos'è @intlayer/next-intl
next-intl è un runtime: getRequestConfig carica un messages/{locale}.json per richiesta, NextIntlClientProvider lo invia al client, e useTranslations("about") legge le chiavi da quell'oggetto al momento del render. Ogni ottimizzazione (namespace, pick(messages, [...]) per pagina, lazy loading) è a tuo carico.
@intlayer/next-intl mantiene la prima e l'ultima parte di quella catena e sostituisce quella centrale. I tuoi componenti continuano a chiamare useTranslations("about"; quello che ricevono proviene da un dizionario Intlayer compilato al momento della build, limitato a quel componente, solo nella locale attiva.
Tre meccanismi lo rendono possibile:
- Import aliasing.
createNextIntlPlugin()da@intlayer/next-intl/pluginavvolgewithIntlayere aggiunge alias Webpack / Turbopack in modo chenext-intl,next-intl/server,next-intl/navigationenext-intl/middlewaresi risolvano a@intlayer/next-intl. Nessun import nella tua codebase viene rinominato. - JSON come fonte di verità. Il plugin
syncJSONlegge il tuomessages/{locale}.jsonesistente, divide le sue chiavi di primo livello in un dizionario per namespace, e scrive le traduzioni negli stessi file quando CLI o CMS li aggiorna. Il workflow dei tuoi traduttori rimane invariato. - Call-site binding. La pass di ottimizzazione di Intlayer (Babel o SWC) riscrive
useTranslations("about")in una chiamata che riceve direttamente il dizionarioabout. Il componente non raggiunge più un albero di messaggi globale; raggiunge il suo stesso contenuto.
Copiare il codice nella clipboard
Copiare il codice nella clipboard
Questo riscrittura è il motivo per cui le colonne dimensioni dei componenti e page-leakage qui sotto si spostano: una pagina estrae solo i dizionari dei componenti che renderizza, e solo nella locale servita.
Ciò che l'adapter mantiene, ignora e non sostituisce
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
next-intl API | Con @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ Mantenuto. Vincolato al dizionario ns al build time. Le chiavi sono tipizzate rispetto ai tuoi contenuti. |
getTranslations({ locale, namespace }) | ✅ Mantenuto |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ Mantenuto. I plurali ICU, select, selectordinal, #, {ts, date, long} vengono elaborati tramite il resolver ICU di Intlayer |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ Mantenuto |
useFormatter() | ✅ Mantenuto. dateTime, number, relativeTime, list, dateTimeRange si collegano alle API native Intl |
NextIntlClientProvider | ✅ Mantenuto. Le props messages, timeZone e now sono accettate ma ignorate (un avviso di sviluppo te lo comunica) |
getMessages() | ✅ Mantenuto per compatibilità; non più necessario |
getRequestConfig() in src/i18n.ts | ⚠️ Non necessario. I dizionari sono compilati al momento della build; non c'è caricamento di messaggi per-request |
defineRouting() | ✅ Mantenuto. I campi omessi (locales, defaultLocale, localePrefix) sono letti da intlayer.config.ts |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ Mantenuto. Re-implementato sulla configurazione di routing di Intlayer; l'argomento routing è accettato ma ignorato |
pathnames (nomi di rotte localizzate) | ❌ Accettato per la tipizzazione, non interpolato. Mantieni i percorsi semplici o sposta quel mapping al rewrite di Intlayer |
createMiddleware() | ✅ Mantenuto. Restituisce il proxy di Intlayer; imposta il cookie NEXT_LOCALE in modo che useLocale() e il tuo switcher continuino a funzionare |
Cookie NEXT_LOCALE | ✅ Letto per impostazione predefinita (a meno che tu non configuri routing.storage da solo) |
Bare useTranslations() con nessuno namespace | ⚠️ Funziona, ma il sito di chiamata non è vincolato: si risolve attraverso il registro runtime. Passa un namespace per ottenere i guadagni di bundle |
Il benchmark
Cosa è stato misurato
La suite Benchmark Bloom costruisce la stessa applicazione con ogni setup: 10 pagine (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locale (en, fr, es, de, it, pt, zh, ja, ko, ru), componenti identici e contenuto identico. Le pagine sono misurate in en e fr.
next-intl è stato costruito con quattro strategie di caricamento, dalla configurazione ingenua (messages/{locale}.json caricato interamente) a quella ottimale (uno namespace per route + pick() per pagina). L'adapter è stato costruito sugli stessi componenti della configurazione ingenua, con solo next.config.ts e intlayer.config.ts modificati. Non ha una variante "scoped": il compiler scopa il contenuto per componente, quindi le righe static e dynamic sono già scoped.
Per ogni build, la suite registra:
- Lib size: dimensione gzip di un componente vuoto che importa solo la libreria i18n. Il costo fisso del runtime.
- Page JS: JavaScript gzip scaricato per pagina, mediato su tutte le pagine e le locale.
- Locale leak %: percentuale di stringhe tradotte trovate nel JS scaricato che appartengono a una locale che l'utente non sta visualizzando.
- Page leak %: percentuale di stringhe tradotte trovate nel JS scaricato che appartengono a una pagina su cui l'utente non si trova.
- Component avg: dimensione gzip media di ogni componente compilato isolatamente. Mostra quanto runtime i18n e catalogo un singolo componente comporta.
- E2E reactivity: tempo trascorso tra la selezione di una nuova locale e l'aggiornamento di
html[lang]nel DOM (Playwright, 5 iterazioni). - Hydration: durata della fase di hydration di React.
I numeri sottostanti provengono dall'esecuzione datata 2026-09-12 connext-intl/use-intl4.14.2 e@intlayer/*9.5.1. L'applicazione di test è deliberatamente piccola (poche decine di stringhe per locale), quindi le percentuali di leakage descrivono un pattern: crescono con i tuoi contenuti mentre il costo del runtime rimane fisso.
Risultati su Next.js
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Come leggerlo
- Stessi componenti, 6 KB in meno per pagina. La build dell'adapter dell'app naive atterra a 147.5 KB, al di sotto di ogni configurazione di
next-intlinclusa quella completamente ottimizzata (153.6 KB). Il runtime stesso è la differenza: 8.0 KB versus 14.7 KB, pagati su ogni pagina. - La perdita di dati va a 0% senza toccare un componente. La configurazione naive di
next-intlspedisce ~90% delle stringhe di pagine straniere su ogni pagina. Raggiungere 0% connext-intlsignifica le configurazioniscoped-*: uno namespace per route epick(messages, [...])in ogni pagina. L'adapter raggiunge 0% dal codice naive perché il pass di ottimizzazione lega ogniuseTranslations("ns")al proprio dizionario. - I componenti si riducono 2.7x. Un componente compilato in isolamento media 21.8 KB con
next-intl(raggiunge il provider e l'albero dei messaggi) e 8.1 KB con l'adapter. Nella configurazionescoped-staticdinext-intlquel numero sale fino a 80 KB, perché il file namespace di ogni route diventa raggiungibile dalla pagina che lo seleziona. - L'idratazione è 2 ms più veloce (12.8 vs 14.7 ms): non c'è nessun oggetto messaggio da deserializzare dal payload RSC prima che React possa idrare.
- L'adapter non è il runtime nativo.
next-intlayersi posiziona a 141.3 KB, +0.3 KB sopra l'app base, con un runtime di 5.5 KB. L'adapter trasporta la superficie API dinext-intl(useFormatter,t.rich, il risolutore ICU) in cima al core di Intlayer, da cui 8.0 KB e +6 KB per pagina. È il ponte, non la destinazione.
Risultati su TanStack Start (use-intl)
use-intl è il core framework-agnostico di next-intl. Il suo adapter, @intlayer/use-intl, segue lo stesso design con un plugin Vite (@intlayer/use-intl/plugin).
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
Come leggerlo
- I byte per pagina sono equivalenti rispetto a
use-intlottimizzato.@intlayer/use-intlin modalitàdynamic(129.7 KB) è entro 1 KB dause-intl'sscoped-dynamic(128.7 KB), e 10 KB più grande diuse-intl's plaindynamic(119.4 KB). Quella rigadynamicplain ha ancora una perdita del 90% di stringhe da pagine estere; il conteggio dei byte è basso perché il contenuto dell'app di test è piccolo. Lo 0% dell'adapter è quello che rimane piatto man mano che il contenuto cresce. - I componenti sono 7-9 volte più piccoli. I componenti
use-intlhanno una media di 76-87 KB in ogni strategia, perchéuseTranslationsè associato all'intero oggetto messaggio del provider. L'adapter ha una media di 9-11 KB. - Lo switching delle locale è più veloce. Gli setup
use-intlottimizzati impiegano 13-21 ms per aggiornarehtml[lang]; l'adapter impiega 4-9 ms. Meno componenti vengono renderizzati di nuovo, e niente viene ripreso da un albero di messaggi. staticmantiene ogni locale. La rigastaticdell'adapter mostra una perdita di locale del 49,7%, la stessa di Intlayer nativo in modalitàstatic: tutte le locale vengono raggruppate, solo i dizionari della pagina. Una riga di configurazione (importMode: 'dynamic') la rimuove.
Perché i numeri cambiano
Poiché niente nel componente è cambiato, i guadagni provengono interamente da ciò a cui useTranslations è associato.
Con next-intl, il binding è il provider. NextIntlClientProvider riceve l'intero oggetto messages per la locale; ogni useTranslations("about") legge da esso. Il bundler vede un componente che importa un hook che legge un context, e non può sapere che solo il ramo about è utilizzato. Le route di seguito condividono tutti lo stesso oggetto message, quindi la colonna page-leak legge ~90% fino a quando non dividi il file tu stesso.
Copiare il codice nella clipboard
Con @intlayer/next-intl, il binding è il dizionario. syncJSON trasforma messages/en.json in un dizionario per ogni chiave di primo livello; il compilatore risolve quale componente chiama useTranslations("about") e gli passa about direttamente, nella locale attiva, come un import che il bundler può tracciare e splittare.
Copiare il codice nella clipboard
src/i18n.ts e il prop messages scompaiono. Tutto il resto è identico.
Migrazione in tre passaggi
Installa
bashCopiare il codiceCopiare il codice nella clipboard
Il comando rileva
next-intle installaintlayer,next-intlayer,@intlayer/next-intle@intlayer/sync-json-plugin. Mantieninext-intlinstallato: è una peer dependency dell'adapter e fornisce i types.Punta Intlayer ai tuoi messaggi
intlayer.config.tsCopiare il codiceCopiare il codice nella clipboard
messages/{locale}.jsonrimane dov'è. Ogni chiave di primo livello diventa un dictionary;useTranslations("about")viene mappato al dictionaryabout.Avvolgi next.config.ts
next.config.tsCopiare il codiceCopiare il codice nella clipboard
createNextIntlPlugin()componewithIntlayer(content watching, compilazione del dizionario, il passaggio di ottimizzazione) e gli aliasnext-intl→@intlayer/next-intlper Webpack e Turbopack. Compila, e i numeri nelle tabelle sopra sono tuoi.
Cosa puoi eliminare successivamente
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| File / pattern | Perché |
|---|---|
getRequestConfig in src/i18n.ts | Nessun caricamento di messaggi per-request. Mantieni il file solo se esporta anche gli helper createNavigation |
messages={...} su NextIntlClientProvider | L'adapter legge l'output compilato; il prop viene ignorato e registra un avviso in sviluppo |
await getMessages() nei layout | Stesso motivo |
Per-page pick(messages, [...]) | Il compiler fa il picking, per componente |
Cosa guadagni oltre ai byte
- Typed keys.
useTranslations("about")è tipizzato rispetto al dizionario compilatoabout.t("does.not.exist")è un errore TypeScript, non un fallback runtime. npx intlayer testinterrompe la CI quando a una locale manca una chiave.npx intlayer filltraduce le chiavi mancanti con il provider di tua scelta (OpenAI, Anthropic, Mistral, Gemini...) utilizzando la tua chiave personale e scrive il risultato inmessages/{locale}.json.- Visual Editor e CMS funzionano sugli stessi dizionari, quindi gli sviluppatori non svuoti possono modificare
messages/fr.jsonattraverso un'interfaccia utente e il file si aggiorna. - Migrazione incrementale a
.content.ts. Qualsiasi componente può passare dauseTranslations("about")auseIntlayer("about")con un file di contenuto colocato, uno alla volta. I dizionari JSON e.content.tscoesistono e si uniscono.
Limiti da conoscere prima di iniziare
- La configurazione del routing si sposta in
intlayer.config.ts.createNavigation(routing)ecreateMiddleware(routing)mantengono la loro firma ma ignorano l'argomento: le locale, la locale predefinita e la strategia di prefisso provengono dalla configurazioneroutingdi Intlayer. Se utilizzi ipathnameslocalizzati dinext-intl(/about→/a-propos), l'adapter non li interpola;routing.rewritedi Intlayer copre quel caso ma è una modifica separata. useTranslations()senza namespace non è vincolato. Il pass di ottimizzazione ha bisogno di uno spazio dei nomi statico per sapere quale dizionario importare. Una chiamata nuda funziona ancora, attraverso un registro di runtime che fa riferimento a ogni dizionario, che è esattamente la perdita che stavi cercando di rimuovere. Passa il namespace.- L'adapter non è gratuito. 8.0 KB di runtime rispetto a 5.5 KB per
next-intlayer, e +6-7 KB per pagina rispetto alla build nativa. Questo copre la superficie API dinext-intl. Se raggiungi il punto in cui ogni componente è stato spostato auseIntlayer, elimina l'adapter. messages,timeZone,nowsul provider vengono ignorati. I formatter sono supportati daIntlnativo e solo la locale ne influenza l'output; se dipendi da un fuso orario forzato o da unnowfisso per date stabili durante l'hydration, gestiscilo nel sito di chiamata.
Quando usare quale?
- Rimani su
next-intlse la tua app è piccola, il tuo bundle non è una preoccupazione, e il tuo team è a suo agio nel gestire i namespace epick()per pagina. - Usa
@intlayer/next-intlse sei attualmente sunext-intle desideri i vantaggi di bundle, leakage e hydration, chiavi tipizzate e gli strumenti CLI / CMS senza una riscrittura. Questo è il punto di ingresso consigliato per qualsiasi codebasenext-intlesistente. - Vai nativo (
next-intlayer) per i nuovi progetti, o una volta che l'adapter ha svolto il suo lavoro. È il più leggero dei tre (5.5 KB, +0.3 KB per pagina) e sblocca i server component sincroni, i file.content.tsper componente e l'intero set di funzionalità.
Confronti correlati
- next-intl vs Intlayer (le librerie, lo stesso benchmark)
- i18next vs @intlayer/i18next (stessa serie di adapter)
- Lingui vs @intlayer/lingui (stessa serie di adapter)
- vue-i18n vs @intlayer/vue-i18n (stessa serie di adapter)
- Guida alla migrazione: next-intl to Intlayer
- Riferimento adapter di compatibilità: next-intl
Conclusione
@intlayer/next-intl fa una cosa sola: cambia a cosa è associato useTranslations, da un provider che contiene ogni messaggio a un dizionario compilato per quel componente. Sulla stessa app Next.js che vale 6 KB per pagina, componenti 2,7x più piccoli, 0% leakage e 2 ms di hydration, prima che chiunque apra un file di componente. Navigation e middleware mantengono la loro API in cima alla configurazione di routing di Intlayer, e il runtime nativo next-intlayer rimane ancora più leggero.
Tutti i dati grezzi, le app di test e gli script sono nel repository Benchmark Bloom. Eseguilo tu stesso.
Fai riferimento al doc 'Why Intlayer?' per ulteriori dettagli.
Commenti
Ancora nessun commento. Sii il primo a condividere i tuoi pensieri.
