Zbudowałem Traverbę — translator czasu rzeczywistego działający w 100% na telefonie, bez żadnych API w chmurze. Rozpoznawanie mowy, tłumaczenie na 108 języków, OCR aparatu obsługujący 92 systemy pisma, nakładka do tłumaczenia ekranu, transkrypcja plików i offline czat grupowy przez Bluetooth dla maksymalnie 7 osób.
Cała aplikacja jest zbudowana we Flutter z natywnym kodem platformy do zadań wymagających dużej mocy obliczeniowej. Oto co robi aplikacja, dlaczego Flutter był właściwym wyborem oraz jakie największe wyzwania napotkałem podczas wdrażania jej na iOS i Android.
Co potrafi aplikacja
Traverba ma pięć podstawowych funkcji, wszystkie działające offline:
Tłumaczenie głosowe na żywo — Mów w jednym języku, słysz i czytaj tłumaczenie w innym. Trzy tryby: wbudowany w aplikację, nakładka przechwytująca dźwięk systemowy (tłumaczy audio z Zoom/Teams/Meet) oraz pływający mikrofon. Automatyczne odczytywanie na głos w języku docelowym.
Tłumaczenie aparatem — Skieruj aparat na menu, znak, formularz lub dokument. Dwa silniki OCR: tryb Basic (ML Kit) do szybkiego odczytu oraz tryb Professional (PaddleOCR) dla złożonych systemów pisma takich jak CJK, arabski, cyrylica i dewanagari. Obsługuje 92 języki. Aplikacja zastępuje obcy tekst przetłumaczonym bezpośrednio na obrazie.
Tłumaczenie ekranu — Pływająca nakładka tłumacząca tekst w dowolnej aplikacji. Oglądasz anime, czytasz koreańskie wiadomości, grasz w japońską grę albo dostajesz wiadomość na WeChat? Naciśnij przycisk nakładki, a tekst zostanie przetłumaczony w miejscu. Bez przełączania aplikacji.
Transkrypcja plików — Zaimportuj pliki audio lub wideo (mp3, m4a, wav, mp4) i otrzymaj transkrypcję z sygnaturami czasowymi, z oryginalnym tekstem i tłumaczeniem zestawionymi obok siebie.
Offline czat grupowy — Do 7 osób łączy się przez sieć mesh Bluetooth. Każda osoba mówi w swoim języku. Każda wiadomość jest tłumaczona na język każdego uczestnika w czasie rzeczywistym. Bez internetu. Bez serwera. Każdy telefon obsługuje własne ASR i tłumaczenie niezależnie.
Wszystko to działa lokalnie na procesorze telefonu. Bez kluczy API, bez kosztów serwera, bez danych opuszczających urządzenie.
Dlaczego Flutter
Na początku oceniałem Flutter, React Native oraz w pełni natywne podejście (oddzielne bazy kodu Swift i Kotlin).
Wieloplatformowość z jednej bazy kodu była absolutnie konieczna. Jeden programista utrzymujący dwie natywne bazy kodu dla aplikacji tej złożoności nie był realną opcją. Warstwa UI w Dart — ustawienia, dymki czatu, widoki transkrypcji, przewodnik po wdrożeniu, arkusze pobierania, selektory języków — to mniej więcej 60% całego kodu. Pisanie tego dwa razy podwoiłoby czas realizacji projektu.
System kanałów platformy Flutter sprawił, że integracja z kodem natywnym stała się praktyczna. Praca wymagająca dużej mocy AI (wnioskowanie ASR, tłumaczenie LLM, OCR, sieć mesh Bluetooth, pływające nakładki) działa w natywnym Swift/Kotlin za pośrednictwem kanałów platformy i kanałów metod. Flutter nie próbuje być środowiskiem uruchomieniowym AI — jest warstwą UI i orkiestracji łączącą natywne silniki.
Wydajność była wystarczająca. Obawa dotycząca Fluttera przy tego rodzaju aplikacji to narzut — każda milisekunda ma znaczenie, gdy łączy się ASR → tłumaczenie → TTS w rozmowie. W praktyce warstwa Flutter dodaje zaniedbywalnie małe opóźnienie, bo ciężkie obliczenia odbywają się w kodzie natywnym. Warstwa Dart obsługuje zarządzanie stanem, routing i aktualizacje UI, co robi dobrze.
Hot reload drastycznie przyspieszył iteracje UI. Przy pięciu odrębnych obszarach funkcji (głos, aparat, ekran, pliki, czat grupowy), każdy z wieloma stanami i trybami, możliwość iterowania UI bez przebudowania była znaczącym mnożnikiem produktywności.
Architektura: Flutter jako orkiestrator
Aplikacja opiera się na wyraźnym podziale:
Warstwa Flutter/Dart obsługuje:
- Całe UI (Material 3, adaptacyjne układy dla telefonów i tabletów)
- Zarządzanie stanem i routing funkcji
- Wybór języka/lokalizacji i preferencje użytkownika
- Zarządzanie pobieraniem opcjonalnych modeli
- Gospodarkę monetami i blokady subskrypcyjne
- Przewodnik wdrożeniowy
Warstwa natywna obsługuje (przez kanały platformy):
- Wnioskowanie ASR (sherpa-onnx / whisper.cpp)
- Tłumaczenie LLM (Gemma przez środowisko uruchomieniowe na urządzeniu)
- Tradycyjne tłumaczenie ML (modele ONNX NMT)
- OCR aparatu (ML Kit + PaddleOCR)
- Synteza mowy
- Sieć mesh Bluetooth do czatu grupowego
- Pływająca nakładka (tłumaczenie ekranu + przechwytywanie dźwięku systemowego)
- Zarządzanie budżetem pamięci i ładowanie/usuwanie modeli
Mostem między tymi warstwami jest zestaw kanałów platformy z dobrze zdefiniowanymi kontraktami. Strona Dart wysyła polecenia ("uruchom ASR dla kantońskiego", "przetłumacz ten tekst z japońskiego na angielski", "przechwyć ekran i wykonaj OCR"). Strona natywna zwraca wyniki asynchronicznie.
Trzy modele ASR, automatyczne przełączanie
Żaden pojedynczy model rozpoznawania mowy nie obsługuje wszystkich języków jednakowo dobrze. Zamiast akceptować słabą jakość dla niektórych języków, aplikacja zawiera trzy wyspecjalizowane modele:
- Parakeet Zoptymalizowany pod angielski. Dołączony do aplikacji, zapewnia natychmiastowe rozpoznawanie mowy w języku angielskim bez pobierania.
- Whisper.cpp Szerokie wielojęzyczne pokrycie ponad 30 języków.
- Qwen3-ASR Zoptymalizowany pod CJK z dedykowaną obsługą kantońskiego.
Gdy użytkownik wybiera język źródłowy, warstwa routingu Dart wybiera najlepszy model i informuje stronę natywną, który silnik aktywować. Użytkownik nigdy nie widzi ani nie zarządza wyborem modelu.
Trudna część to pamięć. Te modele zajmują od 500 MB do 1,9 GB RAM. Ładowanie wszystkich trzech jednocześnie spowodowałoby awarię większości telefonów. Planer budżetu pamięci (napisany w Dart, odpytujący natywne statystyki pamięci) śledzi, co jest załadowane i usuwa model używany najrzadziej, gdy trzeba załadować nowy. Sklep preferencji backendu zapamiętuje, który backend obliczeniowy (NPU, GPU, CPU) zadziałał na danym urządzeniu, dzięki czemu kolejne uruchomienia pomijają niedziałające backendy.
108 języków przez dwa poziomy tłumaczenia
Tłumaczenie odbywa się przez dwie ścieżki:
Poziom 1 — Tradycyjne modele NMT (61 języków): Szybkie, mało pamięciożerne, dobra jakość dla dobrze obsługiwanych par językowych. Modele ONNX działające przez sherpa-onnx na obu platformach.
Poziom 2 — Gemma LLM na urządzeniu (47 dodatkowych języków): Dla języków bez dedykowanych modeli NMT (joruba, amharski, laotański, birmański i inne) skwantyzowany model Gemma 2B obsługuje tłumaczenie lokalnie. Wolniejszy niż Poziom 1, ale rozszerza zasięg na języki, które tradycyjne modele obsługują słabo.
Warstwa Dart zarządza wyborem poziomu na podstawie pary językowej. Użytkownik widzi spójne doświadczenie.
OCR aparatu: dwa silniki, 92 języki
Tłumaczenie aparatem działa z dwoma silnikami wybieranymi przez użytkownika:
Tryb Basic używa rozpoznawania tekstu Google ML Kit na urządzeniu. Szybki, dobrze radzi sobie z pismem łacińskim, cyrylicą i CJK. Idealny do podglądu w czasie rzeczywistym.
Tryb Professional używa PaddleOCR działającego na urządzeniu. Lepsza dokładność dla złożonych układów, mieszanych systemów pisma, zakrzywionego tekstu i niskiego kontrastu. Używa bramki monetowej (5 monet za zdjęcie), bo wymaga więcej mocy obliczeniowej.
Jedna lekcja wyuczona na trudny sposób: iOS domyślnie dołącza tylko model OCR dla pisma łacińskiego. CJK, dewanagari i arabski wymagają jawnego dodania podów specyficznych dla skryptu do Podfile. Błąd jest całkowicie cichy — ML Kit zwraca puste wyniki dla nieobsługiwanych skryptów bez żadnego komunikatu o błędzie. Jeśli budujesz wielojęzyczne OCR na iOS z ML Kit, sprawdź swój Podfile.
Pływająca nakładka: natywna, nie Flutter
Funkcje tłumaczenia ekranu i dźwięku systemowego działają jako natywne nakładki poza powierzchnią renderowania Fluttera. Na Android jest to usługa nakładki systemowej napisana w Kotlin. Na iOS używa API przechwytywania ekranu w Swift.
Te nakładki komunikują się z powrotem z silnikiem tłumaczenia Flutter przez kanały platformy. Wyzwaniem architektonicznym jest zarządzanie cyklem życia — nakładka musi pozostać aktywna, gdy główna aktywność Flutter może być w tle.
Jedna kluczowa lekcja: na iOS natywne instancje mostu muszą być silnie przechowywane (statyczne lub trzymane przez singleton). Jeśli most jest zmienną instancji, której nic nie przechowuje, ARC ją zbierze i wszystkie zamknięcia handlerów cicho staną się pustymi operacjami. Straciłem dni na wskaźnik postępu pobierania utknięty na 0%, bo most był zbierany. Żadnego crashu, żadnego błędu, tylko ciche niepowodzenie.
Czat grupowy Bluetooth
Offline czat grupowy używa protokołu mesh Bluetooth. Każdy telefon działa zarówno jako odbiorca, jak i przekaźnik. Wiadomości są rozgłaszane do wszystkich podłączonych urządzeń. Każde urządzenie uruchamia własne ASR i tłumaczenie niezależnie — nie ma telefonu "hosta" ani "serwera".
Flutter obsługuje UI czatu, stan wiadomości i zarządzanie uczestnikami. Stos Bluetooth jest w pełni natywny (CoreBluetooth na iOS, Android Bluetooth API) mostowany przez kanały platformy.
Całkowite opóźnienie pipelinu (głos → ASR → tłumaczenie → transmisja → wyświetlenie) wynosi około 1 sekundy. UI Dart używa optymistycznego wyświetlania (pokazuje "tłumaczenie..." natychmiast po wysłaniu), więc rozmowa wydaje się responsywna.
Liczby
- Języki: 108 łącznie (61 ML + 47 AI)
- Języki ASR: 30 rozpoznawanie mowy na urządzeniu
- Języki OCR: 92 rozpoznawanie tekstu z aparatu
- Podstawowy rozmiar aplikacji: ~150 MB + modele do pobrania
- Platformy: iOS + Android z jednej bazy kodu Flutter
- Kod Dart/Flutter: ~60% całej bazy kodu (UI, stan, routing, logika biznesowa)
- Kod natywny: ~40% (ASR, LLM, OCR, Bluetooth, nakładki, zarządzanie pamięcią)
- Wielkość zespołu: 1
Co Flutter zrobił dobrze w tym projekcie
Jedna baza kodu dla złożonego UI. Pięć obszarów funkcji, wiele trybów każdy, arkusze pobierania, przewodnik wdrożeniowy, adaptacyjne układy — napisanie tego raz zamiast dwa razy było różnicą między wydaniem a niewydaniem aplikacji jako samodzielny programista.
Kanały platformy są dobrze zaprojektowane. Most między Dart a kodem natywnym jest przejrzysty, dobrze udokumentowany i niezawodny. Dla aplikacji, gdzie ciężkie obliczenia są natywne, ale doświadczenie użytkownika jest we Flutter, to dokładnie właściwa architektura.
Hot reload do szybkiej iteracji. Dostrajanie układów, testowanie stanów, iterowanie przepływów UX — hot reload skrócił czas iteracji z minut do sekund przez setki zmian UI.
Ekosystem jest dojrzały. Zarządzanie stanem (Riverpod), nawigacja, lokalizacja (117 lokalizacji), adaptacyjny design — ekosystem Flutter ma rozwiązania produkcyjnej jakości dla wszystkich tych zagadnień.
Co było trudne
Presja pamięci to martwy punkt Fluttera. Flutter nie udostępnia szczegółowej kontroli pamięci. Gdy jednocześnie ładujesz wiele natywnych modeli ML razem z silnikiem Flutter, musisz zarządzać pamięcią na poziomie natywnym i przekazywać informacje z powrotem do Dart. Nie ma wbudowanego API Fluttera do pytania "ile RAM używa moja aplikacja" ani "jaki jest poziom presji pamięci systemu".
Cykl życia natywnej nakładki. Pływające nakładki działające poza powierzchnią Flutter są architektonicznie złożone. Aktywność Flutter może być w tle, gdy nakładka jest aktywna. Utrzymanie mostu przy życiu, zarządzanie synchronizacją stanu i obsługa zdarzeń cyklu życia specyficznych dla platformy wymagały znacznej ilości kodu natywnego, którego Flutter nie może abstrahować.
Złożoność budowania na iOS. CocoaPods z podami ML Kit, uprawnienia (increased-memory-limit do współładowania ASR + LLM), timing App Tracking Transparency, podpisywanie kodu — pipeline budowania iOS ma wiele ruchomych części, którymi narzędzia Fluttera nie zarządzają w pełni.
Wypróbuj
Traverba jest bezpłatna na obu platformach. Tłumaczenie głosowe, aparatem, ekranu i tekstu są darmowe i nieograniczone. Premium ($1,99/miesiąc) odblokowuje czat grupowy, transkrypcję plików i podsumowania spotkań.
Dostępna na Google Play i App Store. Więcej informacji na traverba.com.
Chętnie odpowiem na pytania dotyczące architektury, wzorców integracji Flutter z kodem natywnym lub AI na urządzeniu w ogóle.