Traverba : une application Flutter avec 3 modèles ASR, un LLM local et OCR par caméra — entièrement on-device

J'ai développé Traverba — un traducteur en temps réel qui fonctionne à 100 % sur votre téléphone, sans aucune API cloud. Reconnaissance vocale, traduction en 108 langues, OCR par caméra dans 92 scripts, superposition de traduction d'écran, transcription de fichiers et chat de groupe hors ligne via Bluetooth pour jusqu'à 7 personnes.

L'application entière est construite avec Flutter et du code natif pour les traitements lourds. Voici ce que fait l'application, pourquoi Flutter était le bon choix, et les principaux défis rencontrés lors du déploiement en production sur iOS et Android.

Ce que fait l'application

Traverba propose cinq fonctionnalités principales, toutes disponibles hors ligne :

Traduction vocale en direct — Parlez dans une langue, écoutez et lisez la traduction dans une autre. Trois modes : in-app, capture audio système flottante (traduit l'audio de Zoom/Teams/Meet) et microphone flottant. Lecture automatique à voix haute dans la langue cible.

Traduction par caméra — Pointez votre caméra vers un menu, un panneau, un formulaire ou un document. Deux moteurs OCR : le mode Basic (ML Kit) pour les lectures instantanées, et le mode Professional (PaddleOCR) pour les scripts complexes comme le CJK, l'arabe, le cyrillique et le devanagari. Prend en charge 92 langues. L'application remplace le texte étranger par le texte traduit directement sur l'image.

Traduction d'écran — Une superposition flottante qui traduit le texte dans n'importe quelle application. Vous regardez un anime, lisez des actualités coréennes, jouez à un jeu japonais ou recevez un message WeChat ? Appuyez sur le bouton de superposition et le texte est traduit sur place. Sans changer d'application.

Transcription de fichiers — Importez des fichiers audio ou vidéo (mp3, m4a, wav, mp4) et obtenez des transcriptions horodatées avec source et traduction côte à côte.

Chat de groupe hors ligne — Jusqu'à 7 personnes se connectent via un maillage Bluetooth. Chacun parle sa langue. Chaque message est traduit dans la langue de chaque participant en temps réel. Pas d'internet. Pas de serveur. Chaque téléphone gère indépendamment sa propre reconnaissance vocale et sa traduction.

Tout cela s'exécute localement sur le processeur du téléphone. Pas de clés API, pas de coûts de serveur, aucune donnée ne quitte l'appareil.

Pourquoi Flutter

J'ai évalué Flutter, React Native et le développement entièrement natif (bases de code Swift + Kotlin séparées) au départ.

Une base de code unique multiplateforme était non négociable. Maintenir en solo deux bases de code natives pour une application aussi complexe n'était pas réaliste. La couche UI en Dart — paramètres, bulles de chat, vues de transcription, visite guidée d'intégration, feuilles de téléchargement, sélecteurs de langue — représente environ 60 % du code total. L'écrire deux fois aurait doublé la durée du projet.

Le système de canaux de plateforme de Flutter rend l'intégration native pratique. Les traitements intensifs en IA (inférence ASR, traduction LLM, OCR, maillage Bluetooth, superpositions flottantes) s'exécutent en Swift/Kotlin natif via des canaux de plateforme et des canaux de méthodes. Flutter n'essaie pas d'être le moteur d'exécution de l'IA — c'est la couche UI et d'orchestration qui connecte les moteurs natifs.

Les performances étaient suffisantes. La préoccupation avec Flutter pour ce type d'application est la surcharge — chaque milliseconde compte lorsqu'on enchaîne ASR → traduction → TTS dans une conversation. En pratique, la couche Flutter ajoute une latence négligeable car les calculs lourds se font en code natif. La couche Dart gère la gestion d'état, le routage et les mises à jour de l'UI, ce qu'elle fait bien.

Le rechargement à chaud a considérablement accéléré l'itération sur l'UI. Avec cinq surfaces de fonctionnalités distinctes (voix, caméra, écran, fichier, chat de groupe), chacune avec plusieurs états et modes, la capacité d'itérer sur l'UI sans recompiler a représenté un multiplicateur de productivité significatif.

L'architecture : Flutter comme orchestrateur

L'application suit une division claire :

La couche Flutter/Dart gère :

  • Toute l'UI (Material 3, layouts adaptatifs pour téléphone et tablette)
  • La gestion d'état et le routage des fonctionnalités
  • La sélection de langue/locale et les préférences utilisateur
  • La gestion des téléchargements pour les modèles optionnels
  • L'économie de jetons et le contrôle d'accès aux abonnements
  • La visite guidée d'intégration

La couche native gère (via les canaux de plateforme) :

  • L'inférence ASR (sherpa-onnx / whisper.cpp)
  • La traduction LLM (Gemma via moteur d'exécution on-device)
  • La traduction ML traditionnelle (modèles NMT ONNX)
  • L'OCR par caméra (ML Kit + PaddleOCR)
  • La synthèse vocale
  • Le maillage Bluetooth pour le chat de groupe
  • La superposition flottante (traduction d'écran + capture audio système)
  • La gestion du budget mémoire et le chargement/éviction des modèles

Le pont entre ces couches est un ensemble de canaux de plateforme avec des contrats bien définis. Le côté Dart envoie des commandes (« démarrer l'ASR pour le cantonais », « traduire ce texte du japonais vers l'anglais », « capturer l'écran et faire l'OCR »). Le côté natif retourne les résultats de manière asynchrone.

Trois modèles ASR, routage automatique

Aucun modèle de reconnaissance vocale unique ne couvre bien toutes les langues. Plutôt que d'accepter une qualité médiocre pour certaines langues, l'application embarque trois modèles spécialisés :

  • Parakeet Optimisé pour l'anglais. Intégré à l'application pour une reconnaissance vocale anglaise instantanée sans téléchargement.
  • Whisper.cpp Couverture multilingue étendue sur 30+ langues.
  • Qwen3-ASR Optimisé pour le CJK avec support dédié du cantonais.

Lorsque l'utilisateur sélectionne une langue source, la couche de routage Dart choisit le meilleur modèle et indique au côté natif quel moteur activer. L'utilisateur ne voit jamais ni ne gère la sélection de modèle.

La partie délicate est la mémoire. Ces modèles occupent de 500 Mo à 1,9 Go en RAM. Charger les trois simultanément ferait planter la plupart des téléphones. Un planificateur de budget mémoire (écrit en Dart, interrogeant les statistiques mémoire natives) suit ce qui est chargé et évince le modèle utilisé le moins récemment quand un nouveau doit être chargé. Un magasin de préférences de backend mémorise quel backend de calcul (NPU, GPU, CPU) a fonctionné sur chaque appareil spécifique, de sorte que les exécutions suivantes sautent les backends défaillants.

108 langues via deux niveaux de traduction

La traduction s'effectue par deux chemins :

Niveau 1 — Modèles NMT traditionnels (61 langues) : Rapides, faible empreinte mémoire, bonne qualité pour les paires de langues bien dotées en ressources. Modèles ONNX s'exécutant via sherpa-onnx sur les deux plateformes.

Niveau 2 — LLM Gemma on-device (47 langues supplémentaires) : Pour les langues sans modèles NMT dédiés (yoruba, amharique, laotien, birman et autres), un modèle Gemma 2B quantifié gère la traduction localement. Plus lent que le niveau 1, mais il étend la couverture aux langues que les modèles traditionnels servent mal.

La couche Dart gère le niveau à utiliser en fonction de la paire de langues. L'utilisateur bénéficie d'une expérience unifiée.

OCR par caméra : double moteur, 92 langues

La traduction par caméra propose deux moteurs sélectionnables par l'utilisateur :

Le mode Basic utilise la reconnaissance de texte on-device de Google ML Kit. Rapide, gère bien le latin, le cyrillique et le CJK. Idéal pour une utilisation en viseur en temps réel.

Le mode Professional utilise PaddleOCR s'exécutant on-device. Meilleure précision pour les mises en page complexes, les scripts mixtes, le texte courbé et le faible contraste. Utilise un système de jetons (5 jetons par capture) car il est plus gourmand en calcul.

Une leçon apprise à la dure : iOS n'intègre par défaut que le modèle de script OCR latin. Le CJK, le devanagari et l'arabe nécessitent l'ajout explicite de pods spécifiques au script dans le Podfile. L'échec est totalement silencieux — ML Kit retourne des résultats vides pour les scripts non pris en charge sans aucune erreur. Si vous développez un OCR multilingue sur iOS avec ML Kit, vérifiez votre Podfile.

Superposition flottante : native, pas Flutter

La traduction d'écran et les fonctionnalités audio système s'exécutent comme des superpositions natives en dehors de la surface de rendu de Flutter. Sur Android, il s'agit d'un service de superposition système écrit en Kotlin. Sur iOS, il utilise les API de capture d'écran en Swift.

Ces superpositions communiquent avec le moteur de traduction Flutter via des canaux de plateforme. Le défi architectural est la gestion du cycle de vie — la superposition doit rester active tandis que l'activité Flutter principale peut être mise en arrière-plan.

Une leçon critique : sur iOS, les instances de pont natif doivent être fortement retenues (statiques ou conservées par un singleton). Si le pont est une variable d'instance que rien ne retient, ARC le collectera par garbage collection et tous les gestionnaires de fermeture deviendront silencieusement des no-ops. J'ai perdu des jours à cause d'un indicateur de progression de téléchargement bloqué à 0 % parce que le pont était collecté. Pas de plantage, pas d'erreur, juste un échec silencieux.

Chat de groupe Bluetooth

Le chat de groupe hors ligne utilise un protocole de maillage Bluetooth. Chaque téléphone agit à la fois comme récepteur et relais. Les messages sont diffusés à tous les appareils connectés. Chaque appareil exécute indépendamment sa propre reconnaissance vocale et traduction — il n'y a pas de téléphone « hôte » ou « serveur ».

Flutter gère l'UI du chat, l'état des messages et la gestion des participants. La pile Bluetooth est entièrement native (CoreBluetooth sur iOS, API Bluetooth Android) connectée via des canaux de plateforme.

La latence totale du pipeline (voix → ASR → traduction → diffusion → affichage) est d'environ 1 seconde. L'UI Dart utilise un affichage optimiste (affiche « traduction en cours… » immédiatement à l'envoi) pour que la conversation paraisse réactive.

Chiffres

  • Langues : 108 au total (61 ML + 47 IA)
  • Langues ASR : 30 avec reconnaissance vocale on-device
  • Langues OCR : 92 avec reconnaissance de texte par caméra
  • Taille de base de l'application : ~150 Mo + modèles téléchargeables
  • Plateformes : iOS + Android depuis une seule base de code Flutter
  • Code Dart/Flutter : ~60 % de la base de code totale (UI, état, routage, logique métier)
  • Code natif : ~40 % (ASR, LLM, OCR, Bluetooth, superpositions, gestion mémoire)
  • Taille de l'équipe : 1

Ce que Flutter a réussi pour ce projet

Base de code unique pour une UI complexe. Cinq surfaces de fonctionnalités, plusieurs modes chacune, feuilles de téléchargement, visite guidée d'intégration, layouts adaptatifs — écrire cela une seule fois au lieu de deux fois était la différence entre livrer et ne pas livrer en tant que développeur solo.

Les canaux de plateforme sont bien conçus. Le pont entre Dart et le code natif est propre, bien documenté et fiable. Pour une application où le calcul intensif est natif mais l'expérience utilisateur est Flutter, c'est exactement la bonne architecture.

Le rechargement à chaud pour une itération rapide. Affiner les layouts, tester les états, itérer sur les flux UX — le rechargement à chaud a réduit le temps d'itération de minutes à secondes sur des centaines de modifications UI.

L'écosystème est mature. Gestion d'état (Riverpod), navigation, localisation (117 locales), conception adaptative — l'écosystème Flutter propose des solutions de qualité production pour tout cela.

Ce qui était difficile

La pression mémoire est l'angle mort de Flutter. Flutter n'expose pas de contrôle mémoire à grain fin. Lorsqu'on charge simultanément plusieurs modèles ML natifs aux côtés du moteur Flutter, il faut gérer la mémoire au niveau natif et la remonter vers Dart. Il n'existe pas d'API Flutter intégrée pour « quelle quantité de RAM mon application utilise » ou « quelle est la pression mémoire du système ».

Cycle de vie de la superposition native. Les superpositions flottantes qui opèrent en dehors de la surface Flutter sont architecturalement complexes. L'activité Flutter peut être mise en arrière-plan pendant que la superposition est active. Maintenir le pont en vie, gérer la synchronisation d'état et gérer les événements de cycle de vie spécifiques à chaque plateforme a nécessité un code natif important que Flutter ne peut pas abstraire.

Complexité de la compilation iOS. CocoaPods avec les pods de scripts ML Kit, les entitlements (increased-memory-limit pour le co-chargement ASR + LLM), le timing d'App Tracking Transparency, la signature de code — le pipeline de compilation iOS comporte de nombreuses pièces mobiles que l'outillage Flutter ne gère pas entièrement.

Essayez-le

Traverba est gratuit sur les deux plateformes. La voix, la caméra, la traduction d'écran et la traduction de texte sont toutes gratuites et illimitées. Le premium ($1,99/mois) débloque le chat de groupe, la transcription de fichiers et les résumés de réunions.

Disponible sur Google Play et l'App Store. Plus d'informations sur traverba.com.

Je serais ravi de répondre à vos questions sur l'architecture, les modèles d'intégration Flutter + natif, ou l'IA on-device en général.