Traverba: un'app Flutter con 3 modelli ASR, un LLM locale e OCR da fotocamera — tutto on-device

Ho costruito Traverba — un traduttore in tempo reale che funziona al 100% sul tuo telefono, senza alcuna API cloud. Riconoscimento vocale, traduzione in 108 lingue, OCR da fotocamera in 92 scritture, overlay di traduzione dello schermo, trascrizione di file e chat di gruppo offline via Bluetooth per un massimo di 7 persone.

L'intera app è realizzata con Flutter e codice nativo per le operazioni più pesanti. Ecco cosa fa l'app, perché Flutter è stata la scelta giusta e le sfide più grandi che ho affrontato nel portarla in produzione su iOS e Android.

Cosa fa l'app

Traverba offre cinque funzionalità principali, tutte disponibili offline:

Traduzione vocale in diretta — Parla in una lingua, ascolta e leggi la traduzione in un'altra. Tre modalità: in-app, acquisizione audio di sistema flottante (traduce l'audio da Zoom/Teams/Meet) e microfono flottante. Lettura automatica ad alta voce nella lingua di destinazione.

Traduzione con fotocamera — Punta la fotocamera su un menu, un cartello, un modulo o un documento. Due motori OCR: modalità Basic (ML Kit) per letture rapide, modalità Professional (PaddleOCR) per scritture complesse come CJK, arabo, cirillico e devanagari. Supporta 92 lingue. L'app sostituisce il testo straniero con il testo tradotto direttamente sull'immagine.

Traduzione dello schermo — Un overlay flottante che traduce il testo all'interno di qualsiasi app. Stai guardando anime, leggendo notizie in coreano, giocando a un gioco in giapponese o ricevendo un messaggio WeChat? Tocca il pulsante dell'overlay e il testo viene tradotto sul posto. Nessun cambio di app.

Trascrizione di file — Importa file audio o video (mp3, m4a, wav, mp4) e ottieni trascrizioni con timestamp, affiancate nella lingua originale e in quella tradotta.

Chat di gruppo offline — Fino a 7 persone si connettono tramite rete mesh Bluetooth. Ognuna parla la propria lingua. Ogni messaggio viene tradotto nella lingua di tutti i partecipanti in tempo reale. Niente internet. Nessun server. Ogni telefono gestisce autonomamente il proprio ASR e la propria traduzione.

Tutto questo viene eseguito localmente sul processore del telefono. Nessuna chiave API, nessun costo server, nessun dato che lascia il dispositivo.

Perché Flutter

All'inizio ho valutato Flutter, React Native e lo sviluppo completamente nativo (codebase separate in Swift e Kotlin).

Una singola codebase cross-platform era imprescindibile. Uno sviluppatore singolo che mantiene due codebase native per un'app di questa complessità non era realistico. Il livello UI in Dart — impostazioni, bolle di chat, viste delle trascrizioni, tour di onboarding, fogli di download, selettori di lingua — rappresenta circa il 60% del codice totale. Scriverlo due volte avrebbe raddoppiato i tempi del progetto.

Il sistema di platform channel di Flutter ha reso pratica l'integrazione nativa. Il lavoro pesante legato all'AI (inferenza ASR, traduzione LLM, OCR, rete mesh Bluetooth, overlay flottanti) viene eseguito in Swift/Kotlin nativo attraverso platform channel e method channel. Flutter non cerca di essere il runtime AI — è il livello UI e di orchestrazione che connette i motori nativi.

Le performance erano sufficientemente buone. La preoccupazione con Flutter per questo tipo di app è il sovraccarico — ogni millisecondo conta quando si concatena ASR → traduzione → TTS in una conversazione. In pratica, il livello Flutter aggiunge una latenza trascurabile perché il calcolo pesante avviene nel codice nativo. Il livello Dart gestisce la gestione dello stato, il routing e gli aggiornamenti dell'UI, operazioni che svolge bene.

L'hot reload ha accelerato notevolmente l'iterazione dell'UI. Con cinque superfici funzionali distinte (voce, fotocamera, schermo, file, chat di gruppo), ciascuna con più stati e modalità, la possibilità di iterare sull'UI senza ricostruire è stata un moltiplicatore di produttività significativo.

L'architettura: Flutter come orchestratore

L'app segue una divisione netta:

Il livello Flutter/Dart gestisce:

  • Tutta l'UI (Material 3, layout adattivi per telefono e tablet)
  • Gestione dello stato e routing delle funzionalità
  • Selezione di lingua/locale e preferenze utente
  • Gestione dei download per i modelli opzionali
  • Economia dei coin e controllo degli abbonamenti
  • Tour guidato di onboarding

Il livello nativo gestisce (tramite platform channel):

  • Inferenza ASR (sherpa-onnx / whisper.cpp)
  • Traduzione LLM (Gemma tramite runtime on-device)
  • Traduzione ML tradizionale (modelli NMT in ONNX)
  • OCR da fotocamera (ML Kit + PaddleOCR)
  • Text-to-speech
  • Rete mesh Bluetooth per la chat di gruppo
  • Overlay flottante (traduzione schermo + acquisizione audio di sistema)
  • Gestione del budget di memoria e caricamento/rimozione dei modelli

Il ponte tra questi livelli è un insieme di platform channel con contratti ben definiti. Il lato Dart invia comandi ("avvia ASR per il cantonese," "traduci questo testo dal giapponese all'inglese," "cattura lo schermo e applica OCR"). Il lato nativo restituisce i risultati in modo asincrono.

Tre modelli ASR, routing automatico

Nessun singolo modello di riconoscimento vocale copre bene tutte le lingue. Invece di accettare una qualità scadente per alcune lingue, l'app include tre modelli specializzati:

  • Parakeet Ottimizzato per l'inglese. Incluso nell'app per un ASR in inglese immediato senza download.
  • Whisper.cpp Ampia copertura multilingue in oltre 30 lingue.
  • Qwen3-ASR Ottimizzato per CJK con supporto dedicato al cantonese.

Quando l'utente seleziona una lingua sorgente, il livello di routing Dart sceglie il modello migliore e indica al lato nativo quale motore attivare. L'utente non vede né gestisce mai la selezione del modello.

La parte complicata è la memoria. Questi modelli vanno da 500MB a 1,9GB in RAM. Caricarli tutti e tre contemporaneamente farebbe crashare la maggior parte dei telefoni. Un pianificatore del budget di memoria (scritto in Dart, che interroga le statistiche di memoria native) tiene traccia di ciò che è caricato e rimuove il modello usato meno recentemente quando ne deve essere caricato uno nuovo. Un archivio delle preferenze di backend ricorda quale backend di calcolo (NPU, GPU, CPU) ha funzionato su ciascun dispositivo specifico, così le esecuzioni successive saltano i backend che hanno fallito.

108 lingue tramite due livelli di traduzione

La traduzione avviene attraverso due percorsi:

Livello 1 — Modelli NMT tradizionali (61 lingue): Veloci, bassa memoria, buona qualità per coppie di lingue ben supportate. Modelli ONNX eseguiti tramite sherpa-onnx su entrambe le piattaforme.

Livello 2 — LLM Gemma on-device (47 lingue aggiuntive): Per le lingue prive di modelli NMT dedicati (yoruba, amarico, laotiano, birmano e altre), un modello Gemma 2B quantizzato gestisce la traduzione localmente. Più lento del Livello 1, ma estende la copertura a lingue che i modelli tradizionali supportano male.

Il livello Dart gestisce quale livello utilizzare in base alla coppia di lingue. L'utente vive un'esperienza unificata.

OCR da fotocamera: doppio motore, 92 lingue

La traduzione da fotocamera usa due motori selezionabili dall'utente:

La modalità Basic utilizza il riconoscimento testo on-device di Google ML Kit. Rapida, gestisce bene latino, cirillico e CJK. Ottima per l'uso in tempo reale con il mirino.

La modalità Professional utilizza PaddleOCR eseguito on-device. Maggiore precisione per layout complessi, scritture miste, testo curvo e scarso contrasto. Usa un gate a coin (5 coin per acquisizione) poiché è più intensiva dal punto di vista computazionale.

Una lezione imparata a caro prezzo: iOS include di default solo il modello di scrittura OCR latino. CJK, devanagari e arabo richiedono l'aggiunta esplicita di pod specifici per scrittura al Podfile. Il fallimento è completamente silenzioso — ML Kit restituisce risultati vuoti per le scritture non supportate senza alcun errore. Se stai costruendo OCR multilingue su iOS con ML Kit, controlla il tuo Podfile.

Overlay flottante: nativo, non Flutter

Le funzionalità di traduzione dello schermo e audio di sistema vengono eseguite come overlay nativi al di fuori della superficie di rendering di Flutter. Su Android, si tratta di un servizio overlay di sistema scritto in Kotlin. Su iOS, utilizza le API di acquisizione schermo in Swift.

Questi overlay comunicano di nuovo con il motore di traduzione Flutter tramite platform channel. La sfida architetturale è la gestione del ciclo di vita — l'overlay deve rimanere attivo mentre l'attività Flutter principale potrebbe essere in background.

Una lezione critica: su iOS, le istanze del bridge nativo devono essere fortemente mantenute (statiche o conservate da un singleton). Se il bridge è una variabile di istanza che nessuno trattiene, ARC la raccoglierà come garbage e tutte le closure degli handler diventeranno silenziosamente no-op. Ho perso giorni a causa di un indicatore di avanzamento del download bloccato allo 0% perché il bridge veniva raccolto. Nessun crash, nessun errore, solo un fallimento silenzioso.

Chat di gruppo Bluetooth

La chat di gruppo offline utilizza un protocollo mesh Bluetooth. Ogni telefono funge sia da ricevitore che da relay. I messaggi vengono trasmessi a tutti i dispositivi connessi. Ogni dispositivo esegue autonomamente il proprio ASR e la propria traduzione — non c'è nessun telefono "host" o "server".

Flutter gestisce l'UI della chat, lo stato dei messaggi e la gestione dei partecipanti. Lo stack Bluetooth è completamente nativo (CoreBluetooth su iOS, Bluetooth API di Android) collegato tramite platform channel.

La latenza totale della pipeline (voce → ASR → traduzione → trasmissione → visualizzazione) è di circa 1 secondo. L'UI Dart usa la visualizzazione ottimistica (mostra subito "traduzione in corso..." all'invio) così la conversazione risulta reattiva.

Numeri

  • Lingue: 108 totali (61 ML + 47 AI)
  • Lingue ASR: 30 con riconoscimento vocale on-device
  • Lingue OCR: 92 con riconoscimento testo da fotocamera
  • Dimensione base dell'app: ~150MB + modelli scaricabili
  • Piattaforme: iOS + Android da una singola codebase Flutter
  • Codice Dart/Flutter: ~60% del codebase totale (UI, stato, routing, logica di business)
  • Codice nativo: ~40% (ASR, LLM, OCR, Bluetooth, overlay, gestione memoria)
  • Dimensione del team: 1

Cosa Flutter ha fatto bene per questo progetto

Singola codebase per un'UI complessa. Cinque superfici funzionali, più modalità ciascuna, fogli di download, tour di onboarding, layout adattivi — scrivere tutto questo una volta invece di due volte è stata la differenza tra pubblicare e non pubblicare come sviluppatore singolo.

I platform channel sono ben progettati. Il ponte tra Dart e il codice nativo è pulito, ben documentato e affidabile. Per un'app in cui il calcolo pesante è nativo ma l'esperienza utente è Flutter, questa è esattamente l'architettura giusta.

Hot reload per un'iterazione rapida. Perfezionare i layout, testare gli stati, iterare sui flussi UX — l'hot reload ha ridotto i tempi di iterazione da minuti a secondi nell'arco di centinaia di modifiche all'UI.

L'ecosistema è maturo. Gestione dello stato (Riverpod), navigazione, localizzazione (117 locale), design adattivo — l'ecosistema Flutter offre soluzioni di qualità produttiva per tutto questo.

Le difficoltà

La pressione sulla memoria è il punto cieco di Flutter. Flutter non espone un controllo granulare della memoria. Quando si caricano contemporaneamente più modelli ML nativi insieme al motore Flutter, è necessario gestire la memoria a livello nativo e riportarla a Dart. Non esiste un'API Flutter integrata per "quanta RAM sta usando la mia app" o "qual è la pressione sulla memoria di sistema".

Il ciclo di vita dell'overlay nativo. Gli overlay flottanti che operano al di fuori della superficie Flutter sono architetturalmente complessi. L'attività Flutter può essere in background mentre l'overlay è attivo. Mantenere il bridge in vita, gestire la sincronizzazione dello stato e gestire gli eventi del ciclo di vita specifici della piattaforma hanno richiesto un codice nativo significativo che Flutter non può astrarre.

La complessità del build iOS. CocoaPods con i pod di script ML Kit, entitlement (increased-memory-limit per il co-caricamento di ASR + LLM), tempistica di App Tracking Transparency, firma del codice — la pipeline di build iOS ha molte componenti in movimento che gli strumenti di Flutter non gestiscono completamente.

Provalo

Traverba è gratuito su entrambe le piattaforme. Voce, fotocamera, traduzione dello schermo e traduzione testuale sono tutti gratuiti e illimitati. Premium ($1.99/mese) sblocca la chat di gruppo, la trascrizione di file e i riassunti delle riunioni.

Disponibile su Google Play e App Store. Scopri di più su traverba.com.

Sono felice di rispondere a domande sull'architettura, i pattern di integrazione Flutter + nativo o l'AI on-device in generale.