Perché la tua configurazione i18n vive nella tua testa e nel tuo config, non nel codice che l'agente ha sotto gli occhi. Cursor rigenera un componente a partire dal contesto che ha davanti, e un testo JSX semplice è la cosa più veloce che funziona. Niente nel repo obietta, quindi la stringa letterale viene rilasciata. globalize.now è un'infrastruttura di localizzazione basata su IA che chiude il cerchio: estrae le nuove stringhe, genera le chiavi e sincronizza le traduzioni a ogni push su Git.
Questo è il fallimento di cui nessuno ti avvisa. Configurare i18n è un lavoro di un pomeriggio. Mantenerlo configurato mentre un agente riscrive i tuoi componenti due volte al giorno è il vero problema.
Perché Cursor continua ad aggiungere stringhe hardcoded dopo che i18n è già configurato?
Tre cose sono vere contemporaneamente, e insieme garantiscono la falla.
L'agente vede un file, non una convenzione. Quando chiedi a Cursor di aggiungere un pannello impostazioni, legge il componente che sta modificando e forse un paio di vicini. La tua directory delle lingue, il tuo schema di namespace e la regola secondo cui ogni stringa rivolta all'utente deve passare da una funzione di traduzione non sono in quella finestra, a meno che tu non ce li metta.
Il testo non tradotto è il default statistico. La stragrande maggioranza del codice React nei dati di addestramento di qualsiasi modello scrive le stringhe inline. Quando l'agente sceglie il token più probabile per l'etichetta di un pulsante, Save changes batte t('settings.save') per pura frequenza. Gli stai chiedendo di scegliere il pattern meno comune ogni singola volta, senza alcun feedback quando non lo fa.
Niente si rompe. Questo è il punto decisivo. Una stringa hardcoded supera il type-check. Compila. Viene renderizzata. Passa i tuoi test. Non c'è nessun segnale rosso da nessuna parte nella tua pipeline che dica che un utente in Germania sta per vedere un toast in inglese. Il difetto è invisibile finché qualcuno non cambia lingua e scorre la pagina.
Confrontalo con un import mancante o un prop type sbagliato, che l'agente vede corretti nel giro di secondi. Il testo non tradotto è l'unica classe di bug che la tua toolchain accetta silenziosamente, ed è per questo l'unica che si accumula.
Quali stringhe sfuggono più spesso?
Quelle aggiunte per ultime, sotto il minor controllo.
Percorri una qualsiasi codebase assistita dall'IA che è partita multilingue, e l'inglese sopravvissuto vive in luoghi prevedibili:
- Toast di errore e di successo, specialmente quelli aggiunti mentre si risolveva un bug non correlato
- Stati vuoti e testi di caricamento, scritti una volta e mai più rivisti
- Messaggi di validazione dei form, spesso generati inline dentro uno schema
- Attributi di accessibilità:
aria-label,alt,title - Dialoghi di conferma e testi per azioni distruttive
- Qualsiasi cosa dentro un blocco
catch
Il pattern è coerente. I tuoi flussi principali vengono tradotti perché li guardi. I margini restano hardcoded perché nessuno li apre in un'altra lingua.
Esiste anche una versione di secondo ordine di questo problema. Quando un agente aggiunge una stringa letterale accanto a codice già tradotto, a volte inventa "utilmente" una chiave che non corrisponde al tuo schema di namespace, così ti ritrovi con errors.saveFailed in un file e settings_save_error in un altro. Ora hai sia stringhe non tradotte che uno spazio di chiavi frammentato. Se sei più agli inizi di questo percorso, la nostra guida su localizzare un'app generata dall'IA tratta la pulizia iniziale.
.cursor/rules o AGENTS.md possono fermarlo?
Aiutano, e dovresti scriverne uno, ma sono indicazioni, non un meccanismo di enforcement.
Il livello dei file di istruzioni si è standardizzato di recente. AGENTS.md è nato nel 2025 come convenzione neutra rispetto ai vendor ed è ora gestito dall'Agentic AI Foundation sotto la Linux Foundation. Viene letto nativamente da Cursor, Codex, Copilot, Gemini CLI, Aider, Windsurf e Zed, il che significa che un unico file nella root del tuo repo raggiunge ogni agente usato dal tuo team. Cursor supporta anche regole di progetto con scope, come file .mdc sotto .cursor/rules/, con un frontmatter che controlla descrizione, glob dei file e se la regola si applica sempre. La configurazione pratica nel 2026 è un AGENTS.md nella root per la portabilità, più regole Cursor con scope dove l'auto-attach vale la pena.
Un blocco i18n funzionante ha questo aspetto:
## Internationalization
- Never write user-facing text as a literal in JSX or in a string passed to a UI component.
- All user-facing text goes through the translation function from `next-intl`.
- Keys follow `feature.element.state` in lowerCamelCase, e.g. `settings.save.error`.
- Add the English value to `messages/en.json` only. Never hand-edit other locale files.
- This includes toasts, empty states, validation messages, `aria-label`, `alt` and `title`.
Questo ridurrà in modo misurabile il tasso di fuga. Non lo porterà a zero, perché i file di regole competono per l'attenzione con tutto il resto nella finestra di contesto, e le sessioni lunghe dell'agente tendono a derivare. Considera questo livello come una riduzione della frequenza, non come una garanzia.
Come si fa a far fallire la build su una stringa non tradotta?
Aggiungi una regola di lint che tratti una stringa letterale in JSX come un errore, poi eseguila in integrazione continua.
La scelta standard è la regola no-literal-string di eslint-plugin-i18next. Per impostazione predefinita convalida il testo semplice nel markup JSX, non ogni stringa letterale nel file, ed è proprio questo a renderla utilizzabile su una codebase reale invece che produrre un muro di falsi positivi. Supporta pattern di esclusione tramite espressione regolare, esclusioni per callee su funzioni che sai essere sicure, e una modalità solo-markup.
Una configurazione di partenza:
// eslint.config.js
import i18next from 'eslint-plugin-i18next';
export default [
{
files: ['**/*.{jsx,tsx}'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
'error',
{
markupOnly: true,
ignoreAttribute: ['data-testid', 'className'],
ignore: ['^[0-9]+$', '^\\s*$'],
},
],
},
},
];
Eseguila nella tua pipeline, non solo nell'editor:
npx eslint . --max-warnings=0
Ora il difetto invisibile ha un segnale rosso. L'agente riceve lo stesso ciclo di feedback per una stringa non tradotta che già riceve per un errore di tipo, il che significa che può correggere il proprio errore nella stessa sessione invece di lasciarlo scoprire a un utente in un'altra lingua. Se vuoi il quadro più ampio su come collegare i18n a un progetto Cursor da zero, vedi la nostra guida in 15 minuti su Cursor e Next.js e la pagina di integrazione con Cursor.
Perché una regola di lint che fallisce non basta?
Perché una build rossa ti dice che una stringa esiste. Non la traduce.
Una volta che la regola scatta, qualcuno deve comunque scegliere una chiave coerente con i tuoi namespace esistenti, aggiungere il valore sorgente in inglese e produrre le altre lingue. Su un progetto solo, con più rilasci al giorno, quel lavoro viene rimandato. Rimandato abbastanza a lungo, la soluzione onesta diventa aggiungere il file a una ignore list, e a quel punto sei tornato al punto di partenza con più configurazione.
Questo è il divario che trasforma un problema risolto in un debito ricorrente. I livelli uno e due cambiano quanto spesso il problema si presenta e quanto in fretta te ne accorgi. Nessuno dei due fa il lavoro.
Com'è fatta la configurazione a tre livelli?
Istruzione, enforcement, automazione. Ogni livello cattura ciò che quello precedente si è lasciato sfuggire.
Livello uno, istruzione. Scrivi il blocco i18n in AGENTS.md nella root del tuo repo, e aggiungi un .cursor/rules/i18n.mdc con scope tramite glob per le tue directory dei componenti. Costo: quindici minuti, una tantum. Effetto: meno stringhe letterali scritte fin da subito.
Livello due, enforcement. Attiva no-literal-string e fai fallire l'integrazione continua su di essa. Costo: un pomeriggio a mettere a punto i pattern di esclusione su una codebase esistente. Effetto: nessuna stringa letterale raggiunge il tuo branch principale inosservata, e l'agente può autocorreggersi.
Livello tre, automazione. Lascia che estrazione, generazione delle chiavi e traduzione avvengano senza di te. È il livello che occupa globalize.now. Si posiziona sopra il tuo runtime i18n invece di sostituirlo: next-intl o i18next continuano a renderizzare le stringhe. globalize.now rileva il nuovo testo hardcoded, genera chiavi coerenti con i namespace già presenti nel tuo progetto, le traduce in ogni lingua configurata e fa il commit dei file di traduzione aggiornati a ogni push su Git. Configuralo una volta sola — connetti il repository su globalize.now — e non c'è nessun passaggio manuale da saltare. Puoi anche installarlo nel tuo agente con:
npx skills add globalize-now/globalize-skills
I piani partono da 5 € al mese; lo Starter da 20 € include 20 € di credito di traduzione (circa 200.000 parole) al mese, con l'utilizzo oltre il volume incluso fatturato allo stesso tasso per carattere. Nessun costo per posto e nessun costo per lingua, e la registrazione include un credito di traduzione di 5 €, senza carta. Maggiori dettagli per gli sviluppatori sono sulla pagina per sviluppatori, e la versione più breve per chi rilascia con strumenti IA è sulla pagina per vibe coder.
Il punto dei tre livelli è che nessuno di essi dipende dalla tua disciplina nel reggere per un centinaio di sessioni dell'agente. Il file di regole abbassa il tasso, la regola di lint rende gli errori evidenti, e la sincronizzazione rende la correzione gratuita. Questa combinazione è ciò che mantiene multilingue un'app multilingue mentre un agente continua a riscriverla.
La tua configurazione i18n non è il punto debole. Lo è il ciclo tra l'agente che scrive una stringa e quella stringa che raggiunge un file di traduzione. globalize.now chiude quel ciclo a ogni push.
globalize.now trasforma il testo hardcoded dell'app in file di localizzazione pronti e li mantiene aggiornati mentre rilasci.
Prova globalize.now gratis