Ogni check è passato. Molte cose erano rotte.

aidocumentationi18n

Qualche giorno fa ho finito di tradurre la documentazione di Suparanku — un prodotto di Supasaito, l’azienda che ho co-fondato — in altre otto lingue. Nove locale in tutto, quaranta pagine ciascuna. Trecentosessanta pagine.

Quando ho finito, ogni check è passato. La build era verde. Il validatore dello schema era verde. Il controllo delle traduzioni diceva che ogni singola label combaciava. Avevo script il cui unico lavoro era prendere esattamente il tipo di errore che fai quando traduci quaranta pagine in una lingua che non sai leggere, e dicevano tutti la stessa cosa: è tutto a posto.

Molte cose erano rotte.

Non rotte nel senso che qualcosa andava in crash. Rotte nel senso che la pagina si carica, la frase si legge benissimo, ed è comunque sbagliata. E la cosa di cui ti voglio parlare non sono i bug. È quello che ci sta sotto, che ci ho messo un tempo imbarazzante a vedere:

Un check verde è la risposta a una domanda molto precisa. I guai iniziano quando la leggi come la risposta alla tua.

Ti faccio vedere cosa intendo, perché detto così sembra astratto finché non lo vedi succedere.

La versione breve

  • Quaranta pagine di documentazione, nove locale, trecentosessanta pagine. Tre check separati, tutti verdi, tutti corretti, e il risultato era pieno di buchi.
  • Un placeholder scritto <your domain> è stato letto come un tag HTML, e il browser l’ha renderizzato come niente. La frase si interrompeva e basta — in tutte le lingue. Preso su staging, prima che lo vedesse un solo lettore.
  • Il grassetto ha smesso di funzionare in dieci frasi giapponesi su sedici, perché le regole che decidono cos’è un marcatore di grassetto danno per scontati gli spazi tra le parole. Un check ce l’avevamo. Cercava **. Quelle rotte usavano un * singolo.
  • Ho verificato che le pagine tradotte fossero pulite cercandoci dentro una parola inglese. Zero risultati. Quarantotto delle cose che stavo cercando erano ancora lì — tradotte, come tutto il resto della pagina. Zero era l’unico numero che quella ricerca potesse restituire.
  • Uno script ha riportato 384 label trovate · 147 associate · 129 ambigue · 108 non associate. Quattro numeri che tornano, una riga inventata in cima alla lista, e un bottone vero che da quella lista mancava.
  • Fare lo stesso lavoro nove volte in parallelo ha trasformato un’incoerenza silenziosa nelle parole del prodotto in un disaccordo visibile tra due traduttori. Che è nettamente meglio.
  • E la parte che nessuno script raggiunge: nessun madrelingua ha letto queste pagine. Nessuna lingua. Lo dico ad alta voce perché un articolo che consiglia una cosa che chi lo scrive non ha ancora fatto dovrebbe dirlo.

Il placeholder che non c’era

La nostra documentazione cita bottoni e label dell’app. Quando deve mostrarti dove vanno le tue informazioni, scrive un placeholder. Così:

Clicca su What we found on <your domain>

Questa convenzione l’hai vista mille volte. Le parentesi angolari vogliono dire “qui va la tua roba”.

Ecco cosa vedevano davvero i nostri lettori:

Clicca su What we found on

Il placeholder era sparito. Non scritto male — sparito. Perché nel formato in cui scriviamo la documentazione le parentesi angolari vogliono dire un’altra cosa: sono il modo in cui si scrive l’HTML. Quindi <your domain> non è stato letto come un placeholder. È stato letto come l’istruzione di creare un elemento chiamato “your domain”, e siccome un elemento del genere non esiste, il browser ha fatto quello che i browser fanno con le cose che non riconoscono.

Non ha renderizzato niente.

La frase si è semplicemente… fermata. E lo faceva in ogni singola lingua, perché il placeholder nel corpo del testo era stato fedelmente tradotto in tutte — <Ihre Marke>, <tu marca>, <votre marque>, <自社ブランド>, <ваш бренд>. Tutte e nove.

L’abbiamo preso su staging, prima che arrivasse a un solo lettore. Non ti sto raccontando la storia di un disastro davanti al cliente — ti sto raccontando le quattro settimane in cui ho creduto che andasse tutto bene, e avevo i check verdi a dimostrarlo.

Ed è qui che arriva la parte che mi ha insegnato davvero qualcosa.

Non ha fallito niente. La pagina era valida. Genuinamente, tecnicamente valida — un elemento sconosciuto che non renderizza niente è legale. La build non aveva motivo di lamentarsi. Il controllo confronta il nostro testo con quello dell’app, e il nostro testo era corretto. Ogni strumento stava facendo il suo lavoro alla perfezione.

E la nostra guida interna consigliava di scriverlo così. Con tanto di spunta verde di fianco. Lo so perché sono io quello che l’avrebbe seguita.

Quindi più seguivamo bene la nostra regola, più lingue venivano sbagliate.

Valido non è la stessa cosa di voluto

La lezione è tutta qui, davvero, e c’è voluto un secondo bug perché mi entrasse in testa.

In giapponese le label dell’app le segniamo in grassetto. Come ovunque. Solo che in un pugno di frasi giapponesi il grassetto ha smesso di funzionare in silenzio, e i lettori vedevano gli asterischi nudi — **come questi** — piantati in mezzo a un paragrafo come un errore di battitura.

Il motivo è davvero oscuro. Le regole che decidono cos’è “inizio del grassetto” e cos’è “fine del grassetto” dipendono da che carattere sta di fianco agli asterischi, e quelle regole sono state scritte pensando agli spazi tra le parole. Il giapponese tra le parole non mette spazi. Quindi in certe combinazioni gli asterischi semplicemente… non sono più marcatori di grassetto. Sono asterischi.

Sedici frasi erano scritte così. Dieci si sono rotte. Le sei che funzionavano erano seguite, per puro caso, da uno spazio o da un punto.

Un check per questa cosa ce l’avevamo. Cercava **.

Quelle rotte usavano un * singolo.

Voglio fermarmi un attimo qui, perché è la cosa più utile di tutto l’articolo. Quel check non era sbagliato. Ha risposto correttamente alla sua domanda tutte le volte che è girato. Solo che la sua domanda non era “il grassetto sta funzionando?” — era “la stringa ** compare?”. Sembrano la stessa domanda fino all’esatto momento in cui non lo sono.

Ho cercato una parola inglese dentro file che non sono in inglese

Questa mi fa ancora ridere.

Mentre scrivevo le pagine inglesi mi ero lasciato dei promemoria. Ognuno diceva da dove veniva un dato, così tra sei mesi avrei saputo se era ancora vero. Erano fatti così, piantati in mezzo alla pagina:

(derived: il file del punteggio, scoreSrpsPlus())

Sono per me, non per chi legge. Quindi la regola era semplice: quando una pagina viene tradotta, quella riga si cancella.

Per confermare che la regola fosse stata seguita ho cercato in tutte le pagine tradotte la parola con cui iniziano quei promemoria — derived — e ho ottenuto zero risultati. Benissimo. Documentato come fatto.

C’erano ancora tutte. Quarantotto, su tre lingue.

Perché i traduttori avevano fatto la cosa ovvia e ragionevole: avevano tradotto il promemoria insieme a tutto il resto. La riga esisteva ancora su ogni pagina. Solo che non iniziava più con derived. Iniziava con 導出元 in giapponese, derivato in italiano, источник in russo.

Quindi avevo costruito una ricerca che poteva solo tornare vuota. Stavo cercando una parola inglese dentro file la cui proprietà distintiva è precisamente che non sono in inglese. Zero non era un risultato. Zero era l’unico numero che quella ricerca fosse in grado di produrre.

E quel numero è finito nella nostra documentazione come un fatto verificato.

Questo non è un bug sottile e cavilloso come quello degli asterischi. Questo sono solo io, sicuro di me.

Il numero preciso e inventato

Questo è il mio preferito, ed è quello che mi tatuerei sulla mano.

All’inizio uno script ha scansionato tutte le pagine e ha riportato:

384 label trovate · 147 associate a una fonte · 129 ambigue · 108 non associate

Guardalo. Quattro numeri, e tornano. È il tipo di output di cui fai uno screenshot per metterlo in un aggiornamento di stato, che è più o meno quello che ho fatto.

Ecco cosa faceva lo script. Nelle nostre pagine tutto ciò che è un bottone o una label dell’app è racchiuso tra doppi asterischi — **Mark done** — quindi lo script scorre il testo, trova una coppia di asterischi di apertura, trova la coppia di chiusura successiva, e chiama label tutto quello che ci sta in mezzo.

Funziona benissimo finché una frase non diventa lunga abbastanza da andare a capo, e la label finisce a cavallo dell’interruzione:

Check it yourself, then click **Mark
done**. If you change your mind, **Mark undone** puts it back.

Lo script leggeva una riga alla volta. Quindi sulla prima riga ha trovato un ** di apertura e non ha mai trovato la sua chiusura. Sulla seconda riga ha trovato quello che ha creduto fosse un’apertura — il ** avanzato da done** — e l’ha accoppiato col primo che ha incontrato dopo.

La label che ne è uscita era . If you change your mind, .

Non è un bottone. È la coda di una frase, che inizia con un punto.

E mentre era occupato a inventarsi quella, si è perso Mark done — che è un bottone, uno vero, nel prodotto, e quindi una delle cose che tutto lo script esisteva per trovare.

Così il report era preciso, sicuro di sé, internamente coerente, e conteneva una riga allucinata seduta di fianco a una vera che mancava. L’avrei preso in cinque secondi se avessi letto la lista invece del riassunto — la riga finta era ordinata in cima a tutto, proprio perché iniziava con un punto.

Quella settimana ce ne sono state altre due così. Una dipendenza che ho dichiarato “solo di sviluppo” basandomi su una lista che era stata tagliata a tre elementi su sessantanove. Uno “zero problemi rimasti” che ho letto sulla seconda esecuzione di un comando che aveva già sistemato tutto alla prima.

Tre errori, ed ecco cos’hanno in comune: nessuno di loro era un errore di ragionamento. Ogni conclusione seguiva correttamente da quello che c’era sullo schermo. Quello che c’era sullo schermo era una fetta. Una regex troppo stretta, una lista tagliata, un comando già speso.

Questo non lo prendi pensando più forte alla tua conclusione. Ci ho provato. La conclusione è giusta. Lo prendi guardando tutto l’output che l’ha prodotta, che è noioso, e che adesso faccio.

Perché “tanto qualcuno se ne accorge” qui non funziona

Magari stai pensando: va bene, ma un essere umano che legge la pagina ‘ste cose le prende di sicuro.

No. Ed è la parte di cui ti voglio convincere più di ogni altra.

Nessuna delle traduzioni sbagliate era un’assurdità. Nessuna. Erano tutte frasi scorrevoli, naturali, del tutto plausibili — che però non corrispondevano a quello che l’app dice davvero.

L’app dice “Example only — illustrative, not your data.” La nostra pagina diceva “Illustrative example, not your data.” Che è una bella frase! Vuol dire la stessa cosa! Un madrelingua che rilegge quella pagina ci passa sopra dritto, e ci passeresti sopra anche tu, e ci sono passato sopra io.

È sbagliata per un motivo che con la lingua non c’entra niente: chi legge deve trovare quel testo esatto sul suo schermo. Se va a caccia di una frase che non esiste, la documentazione ha fallito nell’unico compito che aveva, in un modo che si legge perfettamente.

In un caso il testo dell’app era un po’ goffo, e la nostra pagina l’aveva silenziosamente migliorato. Che è peggio, non meglio. Copialo goffo. Chi legge deve trovarlo.

Il lato buono di farlo nove volte

Chiudo la sfilata di bug con quello che mi ha sorpreso in positivo.

Per le ultime lingue ho fatto girare più traduttori in parallelo, ognuno su un blocco di pagine, tutti a partire dalle stesse istruzioni. E in portoghese due di loro non erano d’accordo: uno scriveva crawlers de IA dove l’altro scriveva rastreadores de IA, per la stessa cosa.

La mia prima reazione è stata che avevo fatto un pasticcio. La seconda è stata andare a guardare — e l’app lo dice in entrambi i modi. Sette stringhe per parte. Tutti e due i traduttori avevano ragionato correttamente su evidenze diverse, perché le evidenze erano discordi.

Se una persona sola avesse fatto tutte e quaranta le pagine, quell’incoerenza sarebbe stata un lancio di moneta che nessuno avrebbe mai notato. Farlo nove volte, in parallelo, ha trasformato un’incoerenza silenziosa in un disaccordo visibile — che è nettamente meglio, perché quello che vedi lo puoi sistemare.

Questo mi ha riformulato tutto il progetto. Tradurre la documentazione nove volte non è stato nove volte il lavoro di scriverla una. È stata una radiografia della cosa che stava descrivendo.

Cosa faccio davvero di diverso adesso

Tre cose, e nessuna è furba.

La renderizzo e la guardo. Ogni singolo bug qui sopra era invisibile a un check che riportava successo. Nessuno di loro è sopravvissuto a qualcuno che guardava davvero la pagina finita. Adesso ho uno script il cui unico lavoro è guardare l’output — la pagina vera, finale — e urlare se qualcosa è visibilmente storto. Ha trovato altri due bug il giorno stesso in cui l’ho scritto.

La parte buffa: l’avevo rimandato per settimane perché pensavo mi servisse uno strumento speciale. Non serviva. Le pagine finite erano già lì, già renderizzate. Mi stavo chiedendo “come faccio a renderizzare questa roba?” quando la risposta era “qualcosa l’ha già fatto”.

Mi chiedo a quale domanda stia rispondendo un check. Non “è passato” — cos’ha guardato. Tre check verdi e una pagina rotta mi hanno insegnato che stavano rispondendo a tre domande diverse, e nessuna era la mia. Detta così sembra filosofia. È l’abitudine più pratica che ho preso quest’anno.

Non mi fido di un check che non ha mai preso niente. Un check nuovo che torna pulito non ti dice ancora niente. Dagli in pasto qualcosa che sai già essere rotto. Se non urla, non è un check, è un soprammobile. Dieci secondi, e mi avrebbe risparmiato un intero numero sbagliato dentro un intero documento.

La cosa che nessuno script può fare al posto tuo

Tutto quello che ho descritto è una macchina che prende un’altra macchina. E c’è un’intera categoria di problemi che sta fuori dalla portata di tutte quante.

Te la dico nella versione scomoda, perché quella comoda sarebbe una bugia per inquadratura: nessun madrelingua ha letto queste pagine. Nessuna lingua. Nessuna pagina.

Ogni check meccanico passa su tutte e nove. Ogni label citata combacia con l’app. Per ogni misura che ho costruito, il set è pulito. Ma “questo combacia con l’app” e “questo si legge come qualcosa che scriverebbe una persona” sono domande diverse, e io ho costruito strumenti solo per la prima.

E so più o meno cosa direbbe un lettore madrelingua, che è la parte che brucia. Metti due lingue qualsiasi una di fianco all’altra e noterai una cosa: sono tutte uno specchio dell’inglese, paragrafo per paragrafo. Stesso ordine. Stessi stacchi. Stesso numero di frasi. Non l’ha deciso nessuno — è semplicemente quello che succede quando traduci invece di riscrivere, ed è il motivo per cui le pagine si leggono come tradotte in tutte e nove le lingue insieme.

Ed è esattamente la cosa che nessun check che io possa mai scrivere segnalerebbe. Ogni frase è accurata. La struttura è un’impronta digitale.

Il portoghese è quello che darei per primo, e non perché sia il peggiore — è uscito il più pulito su ogni misura che ho. È perché porta una scelta di giudizio che gli altri non hanno: tenere il fraseggio naturale per chi legge in Brasile e in Portogallo senza sbilanciarsi né di qua né di là. C’è una regola scritta a riguardo. Nessuno script ha un’opinione su una regola scritta così, quindi è rimasta senza controllo, proprio nella lingua che ha ottenuto il punteggio migliore.

Ecco la conclusione che difenderei davvero, ed è l’unica cosa di questo articolo che ti direi di fare anche se ignori tutto il resto:

Fai girare i check, e poi fai leggere la pagina a un madrelingua lo stesso. Non come formalità alla fine. Come l’altra metà del lavoro.

I due falliscono in direzioni opposte, ed è esattamente perché li vuoi tutti e due. I miei check prendono quello su cui un umano passa dritto — una frase che si legge benissimo e cita un bottone che con quel nome non esiste. Un madrelingua prende quello che nessun check può nemmeno rappresentare: che è tutto tecnicamente corretto e si legge comunque come una traduzione. Nessuno dei due copre l’altro, e fare solo il primo — che è quello che ho fatto io — ti dà nove lingue verificabilmente accurate e probabilmente l’idea di bella scrittura di nessuno.

Se alla traduzione ci tieni, e io ci tengo, perché questa è documentazione che qualcuno legge nell’esatto momento in cui è bloccato e frustrato — allora il check ti dice che è giusta, e solo una persona ti può dire che è buona. Sono due lavori diversi e io mi ero fermato al primo.

Nove letture, quindi. Non una. È sulla lista, tutte e nove, e te lo dico perché un articolo che consiglia una cosa che chi lo scrive non ha ancora fatto dovrebbe almeno dirlo ad alta voce.

Le cose di cui parla questo articolo

Se vuoi vedere cos’è uscito da tutto questo, la documentazione è qui:

suparanku.com/en/help/ →

E c’è un articolo gemello, preso dall’altro lato — lo stesso tipo di errore, rivolto verso di me. Nelle mie lezioni ho pubblicato nove identificatori che non esistono: un prefisso di attributo, una variabile CSS, un valore ARIA. Tutti plausibili. Nessuno copiato dal progetto vero. È Ho pubblicato nove nomi sbagliati →.

Se da qui ti porti via una cosa sola: verde vuol dire che una domanda precisa ha ricevuto una risposta precisa. Prima di rilassarti, scopri qual era la domanda.

E poi vai a chiedere a una persona.

Buon viaggio,

Francesco