La copy della UI resta coerente quando ogni stringa visibile all’utente vive in un unico catalogo nel repo, è referenziata tramite chiave e cambia solo attraverso un diff revisionato. È la stessa disciplina che già applichi alla logica, applicata al wording. Tutto il resto di questa pagina è la pratica che la rende stabile.
Questa è la guida operativa. L’architettura che ci sta dietro, e il ragionamento sul perché la codebase debba essere la fonte di verità per la copy di prodotto, sono in copy di prodotto code-native. Leggila se vuoi il perché. Continua qui se vuoi il come.
Dove deve vivere la copy della UI?
In un file di catalogo per lingua sorgente, dentro il repository, accanto al codice che la renderizza. Non in un foglio di calcolo, non in un file di design, non in una pagina wiki che era corretta a marzo. Il catalogo è il posto in cui sviluppatori, reviewer, traduttori e agent di coding guardano quando vogliono sapere cosa dice una schermata.
Le regole pratiche che ne derivano:
- Un catalogo, un solo locale sorgente. Un singolo
en.json(o.po,.ftl,.strings, a seconda di cosa legge la tua libreria i18n) è la copy di riferimento. Se hai più app in un monorepo, ognuna ha il suo, e le stringhe condivise vanno in un catalogo condiviso che entrambe importano. Quello che eviti sono due file che possono contenere lo stesso messaggio. - Raggruppa per schermata o feature, non per componente. Chiavi come
checkout.summary.totalinvecchiano meglio diSummaryCard.totalLabel, perché i componenti vengono rinominati e divisi, mentre la schermata e il messaggio restano gli stessi. - Nomina la chiave per il messaggio, non per il wording.
checkout.submitsopravvive al passaggio da «Place order» a «Buy now».checkout.placeOrderButtonno, e il disallineamento tra chiave e testo è il motivo per cui un reviewer, più avanti, penserà che siano due stringhe diverse. - Tieni il catalogo nella stessa pull request del codice che lo usa. Una stringa aggiunta in una PR e il suo componente in un’altra è una stringa che verrà persa, dimenticata o duplicata.
Se le tue stringhe sono ancora literal dentro i componenti, il catalogo è la prima cosa da creare. Farlo a mano è già un progetto a sé, ed è il motivo per cui conviene connettere il repository e lasciare che sia la conversione a farlo, come vedremo più avanti.
Perché referenziare per chiave è meglio che cercare il literal?
Perché una chiave può risolvere a una sola stringa, mentre un literal può essere scritto in tanti modi quanti sono gli sviluppatori. «Sign in», «Sign In», «Log in» e «Login» sono quattro stringhe per uno strumento di ricerca e un’unica intenzione per l’utente. Quando ogni componente chiama t("auth.signIn"), la domanda su quale wording sia corretto trova risposta una volta sola, in un solo posto, e la risposta vale ovunque la chiave sia usata.
Due abitudini fanno funzionare tutto:
- Riusa prima di aggiungere. Prima di creare una chiave, cerca il messaggio nel catalogo. Se
common.cancelesiste già, un nuovodialog.cancelButtoncon lo stesso testo è l’inizio del drift, non un duplicato innocuo. I due verranno modificati in modo indipendente nel momento in cui qualcuno ne cambierà uno. - Non concatenare.
t("cart.items") + " " + countproduce un testo che nessuna riga del catalogo descrive. Usa l’interpolazione e le forme di plurale della tua libreria, così l’intera frase è un’unica unità revisionabile.
L’eccezione a cui tutti si aggrappano è «questa stringa compare una volta sola, quindi un literal va bene». Oggi compare una volta sola. Ma il componente verrà copiato, la schermata avrà una gemella, e il literal verrà copiato insieme a loro e poi divergerà.
Come si fa la review delle modifiche alla copy in una pull request?
Tratta una riga di catalogo modificata come tratti la firma di una funzione modificata: qualcosa che un reviewer legge, mette in discussione e approva. È la pratica che trasforma la coerenza da problema di memoria a problema di review, e la review è l’unico momento in cui puoi contare sul fatto che qualcuno stia prestando attenzione.
Cosa fa funzionare la review del catalogo:
- Una modifica di wording è una riga a sé nel diff. Vale solo se la stringa è nel catalogo. Una modifica di wording dentro un componente è anch’essa una riga di diff, ma si nasconde tra le modifiche alla logica e viene saltata come «solo testo».
- Instrada le modifiche al catalogo a un copy owner. Una voce
CODEOWNERSsul path del catalogo fa sì che chi è responsabile del wording — una persona di prodotto, un content designer o lo sviluppatore a cui importa di più — venga richiesto come reviewer a ogni modifica. Tutto il resto della PR segue la review normale. - Fai tre domande per ogni riga modificata. È il termine approvato per questa cosa? È coerente per tono e maiuscole con le stringhe intorno? Il nome della chiave descrive ancora il messaggio? Un reviewer che si fa queste tre domande intercetta gran parte di ciò che una style guide esiste per prevenire.
- Rifiuta i rename silenziosi. Una PR che cambia
auth.signInda «Sign in» a «Log in» senza spiegazioni è una decisione di copy presa da chi stava modificando in quel momento. Serve una frase nella descrizione della PR che dica perché, oppure va annullata.
Il problema che questa pratica previene ha un nome. Il copy drift è il testo visibile all’utente che diverge dalla sua fonte approvata, tra superfici diverse e nel tempo, senza una singola fonte di verità da cui divergere. Cos’è il copy drift ne spiega le cause; la review del catalogo nel diff è la pratica che ne ferma la maggior parte.
Come si evita che entrino nuove stringhe hardcoded?
Fai fallire la build davanti a ogni nuovo literal. Regole e review intercettano ciò che le persone si ricordano di controllare. Una regola di lint intercetta tutto il resto, a ogni pull request, senza che nessuno debba ricordarselo.
Il setup è minimo:
- Abilita una regola no-literal-string.
eslint-plugin-i18nextne include una per i progetti JavaScript e TypeScript. Limitala al testo JSX e agli attributi visibili all’utente, come i suggerimenti degli input,title,aria-labelealt, ed escludi i file di test e le story di Storybook, così la regola resta credibile. - Eseguila dove avvengono i merge. Un hook di pre-commit è comodo; uno step di CI è quello che conta, perché gira sulla pull request a prescindere da come fosse configurato l’editor di chi l’ha scritta.
- Aggiungi un controllo per chiavi inutilizzate e mancanti. La maggior parte delle librerie i18n ha uno strumento complementare che elenca le chiavi referenziate nel codice ma assenti dal catalogo, e le chiavi nel catalogo che non sono referenziate da nessuna parte. Eseguilo anche in CI. Le chiavi inutilizzate sono il posto in cui si nasconde il wording obsoleto.
- Parti rigido, poi ammetti le eccezioni con un commento. Una regola disattivata per l’intera cartella
components/non è una regola. Una regola disattivata su una riga, con un motivo, è documentazione.
I team che sviluppano con strumenti di coding basati su IA ne risentono di più, perché gli strumenti rigenerano i componenti dal contesto locale e reintroducono i literal. Perché Cursor continua ad aggiungere stringhe hardcoded entra nel dettaglio della regola di lint, incluso il setup a tre livelli: file di regole, linter e CI.
Cosa deve contenere una style guide per la copy pensata per gli sviluppatori?
Un file breve nel repository, che sta in una schermata, definisce la voce, fissa i termini e dice dove vanno le stringhe. Le style guide lunghe vivono in una wiki e vengono lette una volta sola. Una breve vive accanto al codice e viene letta da persone e strumenti ogni volta che si aggiunge una stringa.
Un COPY.md funzionante (o una sezione nella tua guida per i contributor) copre:
- La voce, in tre frasi. A chi assomiglia il prodotto quando parla, quanto è formale, se dice «tu» o «l’utente». Quanto basta per chiudere la maggior parte delle discussioni.
- I termini che non variano mai. Il nome che il prodotto dà a ogni feature, i verbi per le azioni principali («Salva» o «Aggiorna»? «Elimina» o «Rimuovi»?) e le parole che hai deciso di non usare.
- Regole su maiuscole e punteggiatura. Sentence case o title case in button e heading, punto finale nei tooltip o no, come si scrivono numeri e date.
- Dove vanno le stringhe e come si nominano le chiavi. Il path del catalogo, la regola di raggruppamento, la regola di naming delle chiavi e «cerca prima di aggiungere».
- Cosa fare nel dubbio. A chi chiedere, o quale chiave riusare nel frattempo.
Tienilo nel repository, così un file di regole può puntarci, un reviewer può linkarlo in un commento e un agent di coding può leggerlo prima di scrivere una label.
Come si evita che gli agent di coding basati su IA rompano la coerenza?
Dai all’agent una fonte di verità da leggere e un controllo che lo fermi quando non la legge. Un agent che genera un form scriverà tranquillamente «Submit» in una schermata e «Send» in quella dopo, perché ognuna era la parola localmente più probabile. Non è disattenzione: non ha nulla rispetto a cui essere coerente.
Due mosse risolvono quasi tutto. Primo, un file di regole (AGENTS.md, .cursor/rules, CLAUDE.md, quello che leggono i tuoi strumenti) che dica: il testo visibile all’utente va nel catalogo, riusa le chiavi esistenti e leggi COPY.md prima di scrivere una label. Secondo, la regola di lint della sezione precedente, perché un file di regole riduce le supposizioni e una regola di lint intercetta quelle che sono sfuggite.
È tutto ciò che questa pagina ha da dire sugli agent. Il problema specifico di un agent che riscrive stringhe già esistenti, e come rendere quella modifica visibile invece che silenziosa, è un tema a parte: come impedire agli agent di coding basati su IA di riscrivere la copy della tua UI.
Che ruolo ha la localizzazione?
Coerenza e localizzazione dipendono dalla stessa premessa, una fonte condivisa, quindi il catalogo che ti dà l’una ti dà anche l’altra. Un team che non sa dire quale di tre formulazioni sia quella approvata non può nemmeno tradurne correttamente nessuna. Sistema la sorgente e la traduzione diventa un artefatto derivato dal catalogo, non un progetto separato con la sua copia della verità.
In pratica significa che il catalogo costruito per la coerenza è già il file su cui lavora un traduttore o uno strumento di traduzione. Ogni chiave ottiene i suoi equivalenti in ogni lingua di destinazione, valgono gli stessi nomi di chiave, e una modifica di wording nel catalogo sorgente è visibile come una modifica che le traduzioni devono seguire. Quando le traduzioni restano indietro rispetto alla sorgente, nasce la classe di problemi descritta in perché i file di traduzione si disallineano, e la soluzione è ancora la stessa disciplina: un catalogo, chiavi, modifiche nel diff.
Dove si inserisce globalize.now
globalize.now è un sistema di gestione della copy code-native con la localizzazione integrata, e ogni pratica di questa pagina è ciò che il sistema presuppone sul tuo repository. Connetti un repository GitHub o GitLab dall’app. Una scansione capisce cosa c’è. Se le stringhe sono literal dentro i componenti, una conversione una tantum le sposta dietro le chiavi in un catalogo e aggiunge il setup i18n se manca. Se usi già next-intl, react-i18next, i18next o Lingui, si integra con ciò che hai già. Il risultato arriva come pull request su un branch dedicato, quindi il primo catalogo passa esattamente dalla review descritta sopra. Dopo di che, le nuove stringhe sorgente che aggiungi vengono tradotte da job di push che aprono una pull request con il risultato. Il repository resta l’unica fonte di verità.
Stiamo lavorando affinché la stessa disciplina valga per il sito marketing, la landing page e il prodotto, a partire da un'unica fonte, così che una funzionalità sia descritta allo stesso modo ovunque l'utente la incontri. È la direzione in cui stiamo andando, non qualcosa da acquistare oggi. I prezzi sono sulla pagina dei prezzi.
Una checklist da adottare questa settimana
- Crea un catalogo per lingua sorgente, nel repository, e decidi la regola di naming delle chiavi.
- Sposta nel catalogo le stringhe delle schermate che tocchi più spesso; richiamale tramite chiave.
- Attiva una regola di lint no-literal-string, limitata al testo visibile all’utente, ed eseguila in CI.
- Aggiungi una riga
CODEOWNERSsul path del catalogo. - Scrivi
COPY.md: voce, termini fissi, regole su maiuscole, dove vanno le stringhe. Una schermata. - Punta il file di regole del tuo agent al catalogo e a
COPY.md. - Respingi in review ogni modifica di formulazione che non abbia una motivazione nella descrizione della PR.
Se il secondo passaggio ti sembra un mese di lavoro, connetti un repository, leggi il piano che propone e fai la review della pull request che apre.
globalize.now trasforma il testo hardcoded dell'app in file di localizzazione pronti e li mantiene aggiornati mentre rilasci.
Prova globalize.now gratis