Chiedi oggi a Cursor o Claude Code di aggiungere l'i18n a un'app Next.js e molto probabilmente otterrai middleware.ts, setRequestLocale in ogni layout, e useTranslations chiamato da una pagina async — una configurazione corretta per Next.js 15 e vecchia di una generazione su Next.js 16. globalize.now è un'infrastruttura di localizzazione basata su IA, quindi questo è il livello che monitoriamo: il cablaggio del routing è cambiato, il catalogo che serve no. Di seguito cosa è cambiato, come capire da quale epoca proviene il tuo codice generato, e le tre correzioni che richiedono un pomeriggio e non una riscrittura.
Nessuno di questi è un breaking change. Ed è esattamente per questo che è facile non notarlo.
Cosa è cambiato in Next.js 16 per l'i18n?
Il file che negozia il tuo locale ha cambiato nome. Next.js documenta la convenzione del file middleware come deprecata e rinominata in proxy, introdotta nella v16.0.0, e la spiegazione ufficiale riguarda il vocabolario — "middleware" veniva continuamente interpretato come il middleware di Express, quindi la funzionalità è stata rinominata per descrivere il confine di rete che effettivamente rappresenta.
Due note sul comportamento accompagnano la rinomina nella v16. Proxy usa per impostazione predefinita il runtime Node.js, e l'opzione di configurazione per file runtime non è più disponibile lì — impostala e Next.js genera un errore. Proxy inoltre non è supportato con l'export statico, il che è rilevante se la negoziazione del locale è l'unico motivo per cui non stai esportando.
Per il routing dei locale in particolare, la guida di setup di next-intl mostra ora il file come src/proxy.ts e nota chiaramente che si chiamava middleware.ts fino a Next.js 16. L'import all'interno resta invariato:
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};
Nota l'asimmetria, perché trae in inganno: il file è ora proxy.ts, mentre il percorso di import è ancora next-intl/middleware. Rinominare l'import è una ipercorrezione comune.
Perché il codice i18n generato dall'IA risulta indietro di una versione?
Perché un agente per la scrittura di codice predice il pattern più rappresentato, e il pattern più rappresentato è quello che ha avuto anni per accumularsi. Ogni post di blog, risposta su Stack Overflow ed esempio su GitHub sul routing dei locale con App Router scritto prima della fine del 2026 dice middleware.ts. Un modello che pesa quel corpus contro poche settimane di documentazione sulla v16 propenderà per la forma vecchia, con sicurezza, senza alcun avviso che sia avvenuta una rinomina.
È lo stesso meccanismo dietro un problema di cui abbiamo già scritto, in cui Cursor continua ad aggiungere stringhe hardcoded dopo la configurazione dell'i18n — l'agente riproduce la codebase statisticamente normale, non la tua. È anche il motivo per cui i file di istruzioni risolvono solo parzialmente il problema per GitHub Copilot: le regole che la superficie chat legge non sono necessariamente lette dal completamento inline.
La conseguenza pratica è limitata ma reale. La tua configurazione generata funziona, quindi niente fallisce in modo evidente. Poi incontri un bug, lo cerchi, e ogni risposta attuale descrive file che non hai.
Come capisco da quale epoca proviene la mia configurazione generata?
Quattro grep. Eseguili dalla root del progetto e lo saprai in circa un minuto.
# 1. Pre-16 locale negotiation file
ls middleware.ts src/middleware.ts 2>/dev/null
# 2. Legacy static-rendering API
grep -rn "setRequestLocale" app src 2>/dev/null
# 3. Hooks called inside async components
grep -rn -B3 "useTranslations" app | grep -n "async function"
# 4. Which next-intl era the config reads from
grep -rn "root-params\|await params" src/i18n/request.ts 2>/dev/null
Riscontri su 1 e 2 indicano uno scaffolding pre-16. Un riscontro su 3 è un vero errore runtime in attesa della prima richiesta che renderizza quel componente. Nessun riscontro su 4 significa che la tua configurazione delle richieste legge il locale nel modo vecchio.
Devo rinominare middleware.ts in proxy.ts?
Non con urgenza, e non dovresti farlo a mano. Next.js fornisce un codemod che rinomina sia il file che la funzione esportata:
npx @next/codemod@canary middleware-to-proxy .
La rinomina è una deprecazione, non una rimozione, quindi un middleware.ts esistente continua a funzionare. Il motivo per eseguirla comunque è il costo di manutenzione: una volta che i nomi dei tuoi file corrispondono alla documentazione attuale, ogni futuro risultato di ricerca si applica al tuo repository. Lascialo obsoleto e paghi una piccola tassa a ogni sessione di debug, per sempre.
setRequestLocale è deprecato in next-intl?
È indicato come legacy, un'affermazione più sfumata di deprecato e che vale la pena leggere con precisione. La documentazione di next-intl descrive setRequestLocale come un'API esistita finché non è stato introdotto next/root-params, dice che resta supportata per retrocompatibilità, e raccomanda next/root-params al suo posto.
La forma più recente legge il locale corrispondente direttamente nella configurazione delle richieste, invece di propagarlo manualmente in ogni layout e pagina:
// src/i18n/request.ts
import * as rootParams from 'next/root-params';
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
export default getRequestConfig(async ({locale}) => {
if (!locale) {
const paramValue = await rootParams.locale();
if (hasLocale(routing.locales, paramValue)) {
locale = paramValue;
} else {
notFound();
}
}
return {locale};
});
next/root-params è disponibile per impostazione predefinita in Next.js 16.3 e versioni successive; sulle versioni precedenti va abilitato tramite experimental.rootParams. Segui la configurazione in questo modo e il rendering statico arriva gratuitamente, purché tu continui a esportare generateStaticParams per il segmento [locale].
L'approccio precedente richiedeva di chiamare setRequestLocale in ogni pagina e layout che volevi renderizzare staticamente, prima di qualsiasi altra chiamata a next-intl, perché Next.js renderizza layout e pagine in modo indipendente. È una regola che un agente IA dimentica al quinto file che scrive. Eliminare il requisito elimina l'intera classe di bug.
Perché useTranslations va in crash nel mio Server Component asincrono?
Perché gli hook non possono essere chiamati da componenti async, e useTranslations è un hook. Questo è un vincolo dei React Server Component, non una particolarità di next-intl, e la risposta di next-intl è un insieme parallelo di funzioni awaitable:
// Async component — await the server API
import {getTranslations} from 'next-intl/server';
export default async function ProfilePage() {
const user = await fetchUser();
const t = await getTranslations('ProfilePage');
return <h1>{t('title', {username: user.name})}</h1>;
}
// Non-async component — the hook is correct here
import {useTranslations} from 'next-intl';
export default function UserDetails({user}) {
const t = useTranslations('UserProfile');
return <h2>{t('title')}</h2>;
}
getFormatter, getNow, getTimeZone, getMessages e getLocale seguono lo stesso pattern. Il secondo esempio merita attenzione: un componente non asincrono senza funzionalità interattive è un componente condiviso, e next-intl risolve l'implementazione corretta in base al fatto che venga renderizzato sul server o sul client. Quindi useTranslations in un Server Component non è un errore — chiamarlo da uno async lo è.
Perché ottengo l'errore di contesto NextIntlClientProvider?
Le note di troubleshooting di next-intl indicano due cause, e richiedono soluzioni opposte. O il componente viene realmente eseguito sul client senza un provider sopra di esso, nel qual caso lo avvolgi e gli passi i messaggi necessari, oppure è finito in un grafo di moduli client quando ti aspettavi un rendering server, nel qual caso lo passi tramite children da un Server Component invece di importarlo al suo interno.
Il secondo caso è quello in cui incappano le app generate dall'IA, perché gli agenti applicano 'use client' generosamente per far funzionare l'interattività, e la direttiva è contagiosa lungo il grafo degli import. Il pattern preferito è tradurre sul server e passare le stringhe già pronte attraverso il confine:
import {useTranslations} from 'next-intl';
import Expandable from './Expandable'; // 'use client'
export default function FAQEntry() {
const t = useTranslations('FAQEntry');
return <Expandable title={t('title')}>{t('description')}</Expandable>;
}
Se qualche componente ha davvero bisogno dei messaggi sul client, puoi limitare un provider a quel sottoalbero invece di inviare ogni messaggio al browser — messages={null} sul provider radice non ne passa nessuno.
Cosa non cambia in tutto questo?
I tuoi file di traduzione. Ogni correzione descritta sopra riguarda il cablaggio di routing e rendering; il JSON che la tua app carica resta intoccato da tutto ciò. Ed è questo il punto da ricordare, perché il cablaggio è un lavoro di un pomeriggio che cambia una volta all'anno, mentre il catalogo è la cosa che si deteriora ogni settimana con ogni nuova UI rilasciata.
È questa la divisione per cui costruiamo. next-intl e i suoi vicini servono le traduzioni a runtime — non competiamo con loro, e se stai ancora scegliendo tra questi, next-intl vs react-i18next vs Lingui illustra i compromessi. globalize.now si posiziona un livello più in alto, producendo le chiavi e i file di traduzione che questi runtime leggono, motivo per cui l'integrazione con Next.js è indifferente al fatto che la tua negoziazione risieda in middleware.ts o proxy.ts.
La conversione è unica e avviene nell'app: connetti il repository, globalize.now converte la codebase una sola volta e apre una pull request con il catalogo. Dopodiché, i push job traducono le nuove unità di catalogo non appena appaiono — che è la modalità di fallimento descritta in perché i file di traduzione continuano a perdere la sincronizzazione, e il motivo per cui vale la pena chiudere quel divario prima del terzo locale, non dopo.
Se il tuo progetto ha già una configurazione next-intl scritta a mano, abbiamo scritto esattamente cosa tocchiamo e cosa non tocchiamo. Se non ce l'ha, il tutorial con Cursor è il percorso più breve — con l'avvertenza che il file generato porta il nome della convenzione precedente, quindi esegui il codemod dopo.
Da dove iniziare
Esegui i quattro grep. Se ottieni riscontri sui primi due, esegui il codemod, sposta la tua configurazione delle richieste su next/root-params, e sarai allineato alla versione corrente. Poi guarda i file di traduzione, perché è quella la parte che continuerà a perdere la sincronizzazione il mese prossimo. Se stai rilasciando un'app costruita con l'IA e vuoi che il catalogo venga gestito invece che mantenuto manualmente, la pagina per i vibe coder è la panoramica e i prezzi hanno una pagina dedicata.
globalize.now trasforma il testo hardcoded dell'app in file di localizzazione pronti e li mantiene aggiornati mentre rilasci.
Prova globalize.now gratis