Se la tua app Next.js è tradotta ma la scheda del browser, la pagina 404, gli errori dei form e l'anteprima dei link sono ancora in inglese, la causa è quasi sempre la stessa: quelle stringhe vengono prodotte fuori da un componente, quindi una chiamata useTranslations non le ha mai toccate. globalize.now è un'infrastruttura di localizzazione basata su IA, e questo è il livello che vediamo lasciato a metà quando un agente IA aggiunge l'i18n a un progetto App Router: la pagina visibile viene convertita, il resto della superficie no.
Qui sotto trovi dove vive ognuna di queste stringhe e la chiamata di next-intl che la gestisce. Ogni esempio segue la documentazione attuale di next-intl, consultata il 28 ottobre 2026.
Dove si nascondono le stringhe non tradotte in un'app App Router?
Si nascondono in sei punti che non sono componenti React che renderizzano testo. Un agente di coding che aggiunge l'i18n scorrendo i tuoi componenti converte la UI visibile e passa dritto davanti a questi:
- Titoli e descrizioni delle pagine in
generateMetadata. - Errori di validazione e messaggi di risultato nelle Server Action.
not-found.tsx, la pagina 404.error.tsx, la pagina di errore a runtime.- File generati come
opengraph-image.tsx,manifest.tsesitemap.ts. - Route Handler che restituiscono testo.
Il filo comune è che nessuno di questi viene renderizzato nel normale albero di layout [locale], quindi il locale va passato esplicitamente oppure letto in un altro modo. Il post sul routing di Next.js 16 copre la configurazione del routing; questo parla di ciò che quella configurazione non raggiunge.
Come si traducono titoli e descrizioni delle pagine?
Chiama getTranslations da next-intl/server dentro generateMetadata. La funzione si può esportare da una pagina o da un layout ed è asincrona, quindi l'API giusta è quella con await:
// app/[locale]/page.tsx
import {getTranslations} from 'next-intl/server';
export async function generateMetadata() {
const t = await getTranslations('Metadata');
return {title: t('title'), description: t('description')};
}
La trappola è la forma statica. Spesso gli agenti scrivono export const metadata = {title: 'Dashboard'} perché è più breve, ma una costante non può chiamare una funzione di traduzione. Converti nella forma a funzione e aggiungi le stesse chiavi al catalogo per ogni lingua. Questo è anche il testo che vedono i motori di ricerca, ed è per questo che tradurre l'app senza tradurne i metadata serve a poco per il posizionamento.
Come si traducono i messaggi delle Server Action e di validazione?
Passa il locale all'action e chiama getTranslations con un namespace e quel locale. Una Server Action non sa in che lingua è la pagina, quindi la documentazione di next-intl prevede che tu lo fornisca, ad esempio come argomento:
'use server';
import {getTranslations} from 'next-intl/server';
export async function subscribe(locale: string, formData: FormData) {
const t = await getTranslations({locale, namespace: 'Subscribe'});
// ...validate, then return t('success') or t('invalidEmail')
}
Un'avvertenza dalla stessa documentazione: considera cosa succede se un visitatore può cambiare lingua mentre a schermo c'è il messaggio di un'action. Con un segmento [locale] nell'URL il caso è gestito in automatico, perché la route cambia. Senza, conviene cancellare il messaggio da te, ad esempio dando al componente key={locale} così che il suo stato si resetti.
Per la validazione dei form con Zod, l'approccio documentato è una error map. Zod ne accetta una per ogni chiamata a safeParse, e la mappa può chiamare getTranslations. Mettila in un unico helper asincrono e passa il risultato come opzione error a ogni parse. Così i tuoi schemi contengono regole, non frasi in inglese, e i form generati dall'IA smettono di rilasciare "Invalid email" in una pagina in lettone.
Come si traducono le pagine 404 e di errore?
Metti not-found.tsx dentro app/[locale]/, poi aggiungi una catch-all perché scatti davvero. Next.js renderizza quel file quando un segmento chiama notFound(), e un URL sconosciuto non lo chiama da solo:
// app/[locale]/[...rest]/page.tsx
import {notFound} from 'next/navigation';
export default function CatchAllPage() {
notFound();
}
Le richieste che il middleware di next-intl non intercetta non hanno alcun locale, quindi la 404 di [locale] non si applica mai a loro. La risposta della documentazione è app/global-not-found.tsx, che Next.js documenta come sperimentale e che renderizza i propri html e body. Per un valore [locale] sconosciuto, la documentazione dice di chiamare notFound(), in genere in i18n/request.ts.
error.tsx è diverso. Next.js richiede che sia un Client Component e viene renderizzato sotto il tuo layout, quindi serve un NextIntlClientProvider che porti almeno il namespace Error. Passa solo quei messaggi, non l'intero catalogo:
// app/[locale]/layout.tsx (excerpt)
const messages = await getMessages();
// ...
<NextIntlClientProvider locale={locale} messages={pick(messages, 'Error')}>
Poi useTranslations('Error') funziona dentro error.tsx. La pagina non copre global-error, quindi se ce l'hai controlla quel file a mano.
E le immagini di condivisione, il manifest e la sitemap?
Ognuno richiede che il locale venga fornito, perché vivono fuori dall'albero di layout. Per opengraph-image.tsx, leggi locale da params e chiama getTranslations({locale, namespace}). Con un localePrefix personalizzato, la documentazione avverte che la route generata può diventare irraggiungibile, perché Next.js non vede i rewrite del proxy (middleware); la soluzione è far sì che il matcher salti i percorsi che terminano con opengraph-image.
app/manifest.ts sta fuori da [locale], quindi next-intl non può dedurre un locale dal percorso. Scegline uno rappresentativo, di solito quello predefinito, e passalo esplicitamente. Per sitemap.ts, aggiungi voci alternates.languages e costruisci ogni URL con getPathname({locale, href}) asincrono dal tuo modulo di navigazione, con il prefisso dell'host. next-intl invia anche, per impostazione predefinita, un header di risposta link che segnala le versioni linguistiche; la sitemap è l'opzione quando ti serve controllo sugli slug gestiti da un CMS.
Come si gestiscono i Route Handler?
Prendi il locale da un search param, da un segmento o dall'header accept-language, validalo con hasLocale(routing.locales, locale) e restituisci un 400 se fallisce. Poi chiama getTranslations({locale, namespace}). Saltare la validazione significa che una stringa arbitraria arriva alla ricerca nel catalogo.
Come trovi l'inglese che il tuo agente ha lasciato nel codice?
Esegui questi comandi dalla root del progetto. Sono volutamente approssimativi: l'obiettivo è ottenere una lista da leggere, non un punteggio.
# 1. Static metadata that cannot be translated
grep -rn "export const metadata" app
# 2. Titles and descriptions already in generateMetadata
grep -rn "title:" app --include=page.tsx --include=layout.tsx
# 3. Server Actions with literal return messages
grep -rln "use server" app src 2>/dev/null | xargs grep -n "message:\|error:"
# 4. Zod schemas with inline messages
grep -rn "message:\s*['\"]" app src 2>/dev/null
# 5. 404 and error files, and whether a catch-all exists
ls app/\[locale\]/not-found.tsx app/\[locale\]/error.tsx "app/[locale]/[...rest]/page.tsx" 2>&1
Tutto ciò che nei primi quattro risultati è un semplice literal in inglese va nel catalogo. Perché gli agenti continuano ad aggiungere stringhe hardcoded spiega perché succede di continuo: l'agente segue il pattern visibile, e il pattern visibile è il componente. Se usi Copilot, i limiti dei file di istruzioni di Copilot valgono anche qui.
Cosa cambia per il tuo catalogo?
Aggiunge chiavi, ed è dalle chiavi che parte il drift. Ognuno dei punti sopra porta nuove voci nel catalogo, come Metadata, Error, Subscribe e i messaggi di validazione, e ognuna richiede una traduzione in tutte le lingue che rilasci. Un progetto che traduce i componenti una volta e poi aggiunge una Server Action o un nuovo titolo di pagina è lo stesso progetto descritto in perché i file di traduzione vanno fuori sincrono.
next-intl serve le traduzioni a runtime e noi non lo sostituiamo. globalize.now sta un livello sopra: connetti il repository nell'app, la codebase viene convertita una volta in un catalogo, poi i job a ogni push traducono le nuove unità del catalogo man mano che compaiono, consegnate come modifiche da rivedere. L'integrazione con Next.js descrive la configurazione, e cosa tocchiamo e cosa no in un setup i18n esistente copre il caso in cui next-intl sia già configurato a mano.
Da dove iniziare
Esegui i cinque grep, converti ogni export metadata statico, aggiungi la route catch-all e dai a error.tsx il suo provider. Poi controlla nel catalogo i nuovi namespace in ogni lingua. Se hai rilasciato un'app costruita con l'IA e vuoi che conversione e traduzione siano gestite per te anziché doverle mantenere, la pagina per i vibe coder è la panoramica, la documentazione di riferimento per sviluppatori copre le configurazioni degli agenti 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