Hai chiesto a Claude Code di aggiungere uno stato di caricamento al form di accesso. Lo stato di caricamento funziona. Il pulsante ora dice anche «Log in» invece di «Sign in», il messaggio di errore è diventato più amichevole e un titolo ha improvvisamente adottato il sentence case. Nessuno ha chiesto nulla di tutto questo, tutti i test passano e, a meno che qualcuno non legga l'intero diff parola per parola, il cambiamento finisce in produzione.

Questo post parla di quel problema specifico: copy esistente e approvato che viene riscritto silenziosamente da un agente di coding basato su IA, e del flusso che lo impedisce. Le stringhe che gli agenti creano hardcoded e senza chiavi sono un problema diverso, trattato in perché gli strumenti di coding basati su IA continuano ad aggiungere stringhe hardcoded. Dove dovrebbe vivere in generale il copy di prodotto è una questione di architettura, trattata in copy di prodotto nativo del codice. Questo post resta sull’incidente: il lunedì mattina in cui il testo è cambiato senza che nessuno decidesse di cambiarlo. globalize.now è un sistema di gestione del copy, nativo del codice, con la localizzazione integrata, e questo incidente è uno dei motivi per cui esiste.

Perché gli agenti di coding basati su IA riscrivono il copy della UI?

Gli agenti di coding basati su IA riscrivono il copy della UI perché vedono testo discorsivo direttamente nei componenti; quando quel copy vive dietro chiavi in un file locale sorgente, modificarlo diventa un cambiamento esplicito e verificabile, non un effetto collaterale di un’altra attività.

Dentro un componente, «Sign in» non ha uno status speciale. È una stringa letterale in un attributo JSX, indistinguibile da un className o da un test id. L’agente è stato addestrato su milioni di diff in cui migliorare il codice circostante era un comportamento positivo e, per un modello, rendere un’etichetta «più chiara» equivale a rinominare una variabile. Non c’è un errore di tipo in agguato, né un test fallito o un’annotazione del proprietario associata a quelle parole. Per l’agente, riscriverle non costa nulla.

Il cambiamento supera la revisione per lo stesso motivo. Chi fa review legge la logica. Una modifica al testo si trova nel mezzo di un diff che riguarda legittimamente uno stato di caricamento, compila, viene renderizzata e, se un test snapshot segnala un problema, la risposta abituale è aggiornare lo snapshot — che registra il nuovo testo come se fosse stato approvato.

I file di regole come AGENTS.md impediscono davvero che succeda?

I file di regole per gli agenti riducono la frequenza del problema; non rendono il copy della UI non modificabile. È l’architettura a rendere visibile il cambiamento.

Usa comunque il file di regole: è il livello più economico e funziona abbastanza spesso da valere trenta secondi. Questo blocco funziona in AGENTS.md, CLAUDE.md e nelle regole di Cursor (.cursorrules o .cursor/rules/):

## UI copy policy — do not modify user-facing text

- Never change user-visible strings (button labels, headings, empty states,
  error messages, tooltips, placeholder text) unless the task explicitly
  asks for a copy change.
- User-visible text lives in the source locale file (e.g. locales/en.json).
  Reference it by key. Never inline a new user-facing string in a component.
- If a task needs new user-facing text, add a key to the source locale file,
  reference it, and list the new key in your summary for copy review.
- Any diff to the source locale file is a copy change. Call it out
  separately — it needs its own approval, apart from code review.

Ora la parte onesta. Una regola è contesto e il contesto compete. In un’attività lunga, l’istruzione è una riga tra migliaia di token di codice e a volte perde — per una compattazione, per un sub-agent che non l’ha mai vista o per un pattern forte nel file circostante. Peggio ancora, l’agente che infrange la regola non lo annuncia. Non ricevi alcuna notifica del cambiamento del testo: ricevi un diff in cui il testo è semplicemente cambiato. Una convenzione non è un controllo.

Quindi il file di regole è il primo livello, ma non quello su cui dovresti fare affidamento da solo.

Come rendere visibile una modifica al copy invece di lasciarla passare inosservata?

Sposta ogni stringa visibile all’utente fuori dai componenti e dietro chiavi in un file locale sorgente. Da quel momento, un agente che vuole cambiare il testo deve modificare un file il cui scopo è proprio contenere il testo.

È il passaggio che il file di regole non può fare, e il meccanismo è semplice. Quando i componenti fanno riferimento a t('auth.signIn') invece di ripetere «Sign in», cambiano due cose contemporaneamente. I diff dei componenti smettono di contenere copy, quindi l’agente che lavora sullo stato di caricamento non ha nulla da riscrivere nel punto in cui sta lavorando. E ogni modifica al copy, fatta da un agente o da una persona, finisce in un unico file — dove una riga cambiata è l’evento che ti interessa, non rumore intorno al cambiamento. Scansionare il diff di un file locale richiede pochi secondi. Annullare una riscrittura sbagliata significa ripristinare una riga.

Per essere precisi su ciò che ottieni: l’agente può comunque modificare il file di traduzione. Il punto non è rendere le parole intoccabili, ma fare in modo che modificarle smetta di essere un effetto collaterale e diventi un’azione leggibile e verificabile. È la differenza tra scoprirlo dal diff e scoprirlo da un cliente.

La tesi completa a favore del mantenimento della fonte di verità nella codebase — invece che in uno strumento di design che si sincronizza con essa — è il tema centrale di copy di prodotto nativo del codice. A questo post serve solo una conseguenza: il copy con chiavi trasforma il file di regole da richiesta a elemento verificabile.

Che aspetto ha il gate di revisione?

Tratta ogni diff del file locale sorgente come una modifica al copy e indirizzalo a chi è responsabile dei testi.

In concreto: inserisci la directory dei file locale in CODEOWNERS, così ogni modifica richiede la revisione di una persona responsabile del copy. Aggiungi una riga al template della PR: «Questa PR modifica il testo visibile agli utenti? Era questo l’obiettivo dell’attività?». Mantieni anche il requisito nel file di regole secondo cui l’agente deve elencare nel riepilogo le chiavi modificate; quando lo fa, la review è immediata, e quando non lo fa, l’hook di CODEOWNERS interviene comunque.

In un team piccolo è meno pesante di quanto sembri. Il diff del file locale è breve, si legge in linguaggio naturale e «questo pulsante dovrebbe dire “Log in now”?» è una decisione che si può prendere in cinque secondi — a patto che la domanda venga effettivamente sottoposta a qualcuno. È questo l’unico compito del gate.

E se l’agente ha già riscritto il tuo copy?

Ripristina prima il testo approvato, poi assegna le chiavi alle stringhe, così la prossima riscrittura comparirà come diff.

Trovare il danno è un esercizio di Git: confronta i componenti interessati con l’ultimo commit di cui ti fidi e leggi solo le stringhe letterali. Ripristina ciò che è cambiato. Se non puoi dire con certezza quale fosse il testo approvato — il pulsante dice «Log in», la pagina marketing dice «Sign in» e nessun artefatto registra quale sia quello corretto — hai raggiunto lo stato più profondo alimentato da questo incidente. Il copy drift è la divergenza del testo visibile agli utenti rispetto alla fonte approvata, tra superfici e nel tempo, senza un’unica fonte di verità da cui divergere. Il termine, le sue cause e le conseguenze a valle sono trattati in cos’è il copy drift.

Poi assegna le chiavi a ciò che hai ripristinato, iniziando dalle stringhe toccate dall’agente. Sono chiaramente quelle sulla linea del fuoco.

Che ruolo ha la localizzazione?

Una riscrittura silenziosa è un fastidio in una lingua e una responsabilità in dieci: ogni traduzione derivata dal testo precedente è ormai obsoleta e nulla ti segnala che devi ritradurla.

È qui che l’incidente dell’agente si aggrava. Rilasci il testo inglese riscritto, mentre tedesco, giapponese e spagnolo continuano a dire la cosa vecchia: ora le superfici non sono allineate tra le lingue, non solo tra le schermate. I cataloghi che perdono l’allineamento sono una modalità di errore distinta, con correzioni proprie, trattata in file di traduzione fuori sincrono.

Il copy con chiavi chiude anche questo ciclo, perché una stringa sorgente modificata diventa un evento visibile da cui dipendono le traduzioni, invece di essere un letterale spostato in silenzio. È questo il design alla base di globalize.now: il copy vive nella codebase, ha un’unica fonte dietro chiavi e la localizzazione è la stessa operazione, non un’aggiunta successiva — connetti un repository e ogni lingua deriva da quella singola fonte. I team che rilasciano con strumenti di coding basati su IA incontrano per primi questo problema, ed è per questo che scriviamo per gli sviluppatori e per chi costruisce con strumenti di IA.

La prossima volta che un agente entra nei tuoi componenti, la domanda è se un’etichetta modificata comparirà come una decisione o come una sorpresa. globalize.now mantiene un’unica fonte per le stringhe visibili agli utenti nel repository che connetti e deriva ogni lingua da quel singolo copy.

globalize.now trasforma il testo hardcoded dell'app in file di localizzazione pronti e li mantiene aggiornati mentre rilasci.

Prova globalize.now gratis