Indice
- L’obiettivo
- Il vantaggio, concretamente
- Cosa serve
- La struttura in tre frasi
- Il contesto batte la frammentazione
- Tre modelli, tre caratteri
- TranslateGemma non è un interlocutore
- Qwen3.5 9B traduce un campo di troppo
- gpt-oss-20b necessita di un’istruzione in inglese
- L’errore che avremmo quasi ignorato
- Cos’altro ci ha sorpreso
- Per riprodurlo
Un sito web multilingue ha un problema poco appariscente: in tre quarti delle sue lingue invecchia. Una frase sulla pagina tedesca si corregge rapidamente — ma prima che la stessa correzione arrivi in inglese, francese e italiano, passano settimane. Quindi non viene fatta. Dopo un anno, nelle pagine in lingua straniera c’è scritto qualcosa di diverso rispetto alla pagina tedesca, e nessuno sa esattamente cosa.
La via d’uscita abituale si chiama agenzia di traduzione o API di traduzione. Entrambe funzionano, entrambe hanno lo stesso freno: costano per operazione. E ciò che costa per operazione non lo si fa per una mezza frase modificata.
L’obiettivo
Tradurre dovrebbe essere economico e naturale quanto un correttore ortografico. Questo è esattamente ciò che offre un modello eseguito sul proprio computer: Una chiamata non costa nulla oltre all’elettricità. Questo cambia ciò che è praticamente possibile — non la qualità di una singola traduzione, ma la frequenza.
Da noi oggi funziona così: gestiamo esclusivamente il tedesco. Un comando, e inglese, francese e italiano si aggiornano. Chi modifica una sola frase ottiene le tre traduzioni dopo circa venti secondi — perché viene ritradotto solo ciò che è cambiato.
Il vantaggio, concretamente
| prima | con pipeline locale | |
|---|---|---|
| Costi per esecuzione | prezzo per operazione | solo elettricità |
| Applicare una piccola correzione | non ne vale la pena | ~20 secondi |
| Ripetibilità | ogni esecuzione costa di nuovo | quante volte si vuole |
| Sperimentare | costoso | senza conseguenze |
| Dipendenza | contingente, listino prezzi, disponibilità | nessuna |
L’ultimo punto viene facilmente sottovalutato. Quando un’esecuzione non costa nulla, la si può scartare. Abbiamo rielaborato i nostri prompt una dozzina di volte, buttato interi cicli e confrontato tre modelli tra loro — con costi per operazione, dopo il secondo tentativo ci saremmo accontentati del risultato.
Cosa serve
Nulla di esotico, ma nemmeno niente — la memoria RAM è il fattore limitante:
| Requisito | Nel dettaglio | Nota |
|---|---|---|
| Computer | Mac con Apple Silicon, oppure PC con scheda grafica | Testato su un MacBook Pro con 36 GB |
| Memoria RAM | 16 GB bastano per modelli piccoli, 32 GB per quelli grandi | Il modello deve entrarci completamente |
| Software | LM Studio, gratuito | Avvia il server locale |
| Runtime | Node.js dalla versione 18 | Lo script non richiede dipendenze |
| Spazio di archiviazione | 6–15 GB per modello | Vedere la tabella sotto |
| Tempo per la configurazione | mezza giornata | Caricare il modello, adattare lo script, verificare |
Ci sono due cose da sapere prima: un modello che non entra nella memoria RAM non viene nemmeno caricato — LM Studio interrompe con un messaggio chiaro, anziché mettere in ginocchio il computer. E tradurre non richiede pochi secondi: per il nostro sito web completo in tre lingue calcoliamo da una a tre ore. È un’operazione una tantum; ogni esecuzione successiva riguarda solo ciò che è cambiato.
Il risultato è stato meno una questione di scelta del modello che di verifica. Gli errori interessanti, infatti, non erano quelli in cui qualcosa si rompeva visibilmente.
La struttura in tre frasi
LM Studio avvia un server locale che parla la stessa interfaccia dell’API OpenAI. Uno script Node divide i contenuti in blocchi, li invia lì e ricompone le risposte. Una cache memorizza ogni traduzione tramite l’hash dell’originale tedesco, cosicché la seconda esecuzione tocca solo le frasi modificate.
Il passaggio progettuale decisivo riguarda la dimensione dei blocchi.
Il contesto batte la frammentazione
Il primo tentativo divideva tutto in singoli frammenti di testo — 2 279 frammenti per i nostri testi dell’interfaccia. Ciascuno veniva inviato singolarmente al modello. Il risultato era mediocre, e in modo sistematico:
| Tedesco | tradotto singolarmente | tradotto nel blocco |
|---|---|---|
| Zur Übersicht | For a quick overview | Back to overview |
| Kontakt aufnehmen | Contact us. / Alternatively: Get in touch. / Or: Reach out. | Contact us |
| Individuelle Software | Custom software. (The best option depends on the context.) | Custom software |
A un modello che vede “Zur Übersicht” senza alcun contesto manca semplicemente l’informazione che si tratta di un pulsante. Indovina — e per frammenti brevi produce volentieri più proposte una sotto l’altra, cosa che in una cella di tabella rompe il layout.
Quindi il contrario: blocchi grandi anziché frammenti piccoli. Dividiamo i testi dell’interfaccia lungo la loro struttura ad albero in 109 blocchi di circa 2 300 caratteri in media, mentre gli articoli del blog passano interamente in un unico blocco. Da 2 279 richieste si passa a 125. Il taglio avviene sempre su chiavi complete, mai nel mezzo di un valore — un test verifica che non vada perso nulla ricomponendo tutti i blocchi e confrontandoli con l’originale.
Tre modelli, tre caratteri
Testati su un Mac con Apple Silicon, tutti i modelli tramite LM Studio, ogni volta lo stesso blocco di 2 701 caratteri.
| Modello | Su disco | Velocità | Testi dell’interfaccia | Articoli completi |
|---|---|---|---|---|
| TranslateGemma 12B | 12 GB | ~1,9 testi/s in batch | buono | — |
| gpt-oss-20b | 11 GB | ~72 caratteri/s | buono | buono |
| Qwen3.5 9B | 5,6 GB | ~63 caratteri/s | buono | violazione delle regole |
| Qwen3.8 27B | 15 GB | ~53 caratteri/s | buono | buono |
Con Qwen vale la pena guardare la cifra decimale: qwen3.5-9b e
qwen3.8-27b sono due generazioni della stessa famiglia, e le differenze qui
sotto riguardano proprio queste due versioni — non “Qwen3” in generale.
In produzione utilizziamo Qwen3.8 27B.
Le dimensioni indicate sono quelle effettivamente occupate — i modelli vengono distribuiti quantizzati, cioè con precisione di calcolo ridotta. In questo modo un modello da 27 miliardi occupa 15 anziché oltre 50 gigabyte, con una perdita di qualità appena percettibile. Per iniziare basta il più piccolo: per i testi dell’interfaccia, che costituiscono la maggior parte, Qwen3.5 9B ha fornito la stessa qualità del suo fratello tre volte più grande.
Qui si misura in caratteri al secondo, non in token al secondo. È intenzionale: ogni modello ha il proprio tokenizer, e il testo tedesco viene suddiviso in modo diverso. I token sono quindi confrontabili tra modelli solo in misura limitata — non lo è la quantità di testo che alla fine viene prodotta. Tuttavia, per contestualizzare, un numero: Qwen3.8 27B ha generato per un articolo di blog completo 1 714 token in uscita in 120 secondi, quindi circa 14 token al secondo; gpt-oss-20b raggiunge circa il doppio.
I numeri dicono comunque meno di quanto ci si aspetti. Un modello tre volte più piccolo era solo circa il 20 percento più veloce — il collo di bottiglia su questo hardware non è la potenza di calcolo, ma la larghezza di banda della memoria. Per ogni singolo token, tutti i pesi devono passare attraverso il bus di memoria. Anche più richieste simultanee hanno aiutato poco: tra due e venti richieste parallele, la dispersione dei valori misurati era maggiore dell’effetto.
Più interessanti sono le differenze nel comportamento.
TranslateGemma non è un interlocutore
Il modello di traduzione di Google non comprende istruzioni — traduce ciò che riceve. Chi gli invia un oggetto JSON chiedendo di tradurre solo i valori, riceve strutture spezzate. La struttura deve quindi essere preservata programmaticamente.
Ha inoltre una propria forma di chiamata: il suo template chat prevede codici
lingua come campi, non come prompt. Tuttavia LM Studio non inoltra questi campi
tramite l’endpoint chat, perciò ogni chiamata termina con HTTP 400. La strada
passa da /v1/completions con un prompt ricostruito manualmente — il template
si trova nella cartella del modello.
In compenso fa ciò che deve fare e lascia in pace i termini tecnici. Non però i nomi di prodotto: da “Exeoflow” diventava puntualmente “Exeflow” o “Exoflow”. Con un modello di traduzione puro, i nomi propri devono essere mascherati prima della chiamata.
Qwen3.5 9B traduce un campo di troppo
Il piccolo Qwen rispettava quasi tutte le regole — data, autore, elenchi di tecnologie, persino i nomi delle persone nelle citazioni restavano invariati. Solo in un punto falliva in modo riproducibile a ogni esecuzione:
# Originale # Traduzione
tags: ['ki', 'glossar', 'agents'] tags: ['ai', 'glossary', 'agents']
I tag sono identificatori tecnici, non prosa — tradurli compromette il filtraggio. Qwen3.8 27B e gpt-oss non commettevano questo errore. Alla fine è stato risolto con un’unica frase aggiuntiva nell’istruzione: che i tag sono identificatori e non devono mai essere tradotti.
gpt-oss-20b necessita di un’istruzione in inglese
Qui si trovava l’osservazione più istruttiva, e per poco non è passata inosservata. Con un system prompt in tedesco, gpt-oss traduceva diligentemente il frontmatter e lasciava il testo discorsivo interamente invariato in tedesco — in cinque esecuzioni su cinque. Anche quando l’istruzione diceva esplicitamente che l’intero testo doveva essere tradotto.
Lo stesso prompt in inglese: corretto al primo colpo. Il problema non era la chiarezza dell’istruzione, ma la sua lingua. I modelli Instruct vengono addestrati prevalentemente su istruzioni in inglese. Da allora tutti i nostri prompt sono in inglese, anche se né la lingua sorgente né quella di destinazione lo sono — questo rende la pipeline indipendente dal modello in esecuzione.
L’errore che avremmo quasi ignorato
La vicenda del testo non tradotto ha una conclusione sgradevole: La nostra verifica riportava “superato”.
Confrontava titoli, righe delle tabelle, grassetto, destinazioni dei link e
blocchi di codice tra originale e traduzione. Un testo non tradotto supera
questa verifica senza difficoltà — ha infatti una struttura perfettamente
preservata. Tredici file con contenuto completamente tedesco sono così finiti
sul disco come en.md, e in una lingua che non si legge personalmente questo
si nota solo mesi dopo.
Da allora la pipeline risponde anche alla più ovvia delle domande: È stato davvero tradotto? Misura quale quota del testo discorsivo è rimasta uguale parola per parola. Una traduzione reale si attesta attorno al sette percento — singole righe possono legittimamente rimanere uguali, come nomi propri o termini tecnici. Il testo tedesco semplicemente inoltrato era al cento.
Se un blocco non supera la verifica, il file non viene scritto. Può sembrare rigoroso, ma è la variante meno rischiosa: un file linguistico mancante viene semplicemente ignorato dal generatore del sito, e scompare anche dai riferimenti hreflang. Un file danneggiato, invece, viene pubblicato.
L’insegnamento vale anche oltre le traduzioni: una verifica che cerca solo danni trascura i casi in cui semplicemente non accade nulla.
Cos’altro ci ha sorpreso
Qwen3.8 ragiona ad alta voce per impostazione predefinita. Per una
traduzione è puro tempo di attesa — 187 token di reasoning per una frase, 12,4
anziché 1,4 secondi, a risultato identico. Gli interruttori previsti da Qwen
sono gli argomenti del template enable_thinking e preserve_thinking,
entrambi attivi per default. In LM Studio non producono alcun effetto: la
risposta contiene con enable_thinking: false esattamente lo stesso numero di
token di reasoning che senza il campo, e una chiave liberamente inventata nello
stesso punto non produce né errore né effetto — l’intero blocco viene ignorato,
anziché essere passato al template chat. Non è l’interruttore a essere errato,
ma l’aspettativa che ogni ambiente di runtime lo inoltri. Qui ciò che disattiva
è reasoning_effort: 'none' — LM Studio registra persino un avviso secondo cui
l’impostazione non è valida. Funziona comunque.
I tentativi ripetuti non richiedono alcun elemento casuale. Il nostro primo
progetto aumentava la temperatura in caso di errore, per ottenere una risposta
diversa. In un compito vincolato da regole questo è controproducente: con
temperature: 0 entrambi i modelli testati superavano la verifica, con 0.2
entrambi fallivano. La dispersione compromette proprio l’aderenza alle regole
che conta. Un modello Instruct può invece gestire un feedback concreto — il
secondo tentativo riceve ora la contestazione nel prompt.
Gli array sono una trappola silenziosa. In JavaScript anche un array è
typeof 'object'. Chi vi entra ricorsivamente durante la suddivisione
trasforma senza accorgersene ["a","b"] in {"0":"a","1":"b"} — e lo scrive
nel file di destinazione.
Per riprodurlo
Lo script di esempio qui sotto è eseguibile autonomamente, senza dipendenze, e mostra tutti e tre i componenti: dividere i blocchi, verificare la risposta, memorizzare il risultato nella cache.
- Scaricare LM Studio e installarlo, caricare un modello Instruct
(
openai/gpt-oss-20bè un buon punto di partenza) e avviare il server in Developer. - Scaricare ed eseguire lo script:
node lmstudio-translate-demo.mjs
node lmstudio-translate-demo.mjs --model=qwen/qwen3.8-27b --lang=French
node lmstudio-translate-demo.mjs --file=meine-texte.json
- Alla seconda esecuzione interviene la cache — i testi modificati vengono tradotti di nuovo, tutto il resto proviene dal file.
Chi lo utilizza in produzione non dovrebbe omettere tre elementi: la verifica dell’effettiva traduzione, la cache (altrimenti ogni correzione richiede un ciclo completo) e un test che verifichi la suddivisione rispetto all’originale. Tutto il resto può essere aggiunto in seguito.
E la questione di fondo? Per un sito web con quattro lingue e modifiche testuali regolari, l’approccio locale conviene già perché è ripetibile. Si può modificare una frase e venti secondi dopo aggiornare tutte e tre le traduzioni — senza dover pensare ai costi per chiamata.
Al progetto
Informazioni sull'autore
Ingegneria

