Traverba: Flutter로 만든 앱 — ASR 모델 3개, 로컬 LLM, 카메라 OCR 모두 기기 내에서 실행

Traverba는 클라우드 API 없이 100% 기기 내에서 동작하는 실시간 번역 앱입니다. 음성 인식, 108개 언어 번역, 92개 문자 카메라 OCR, 화면 번역 오버레이, 파일 전사(轉寫), 그리고 최대 7명이 참여할 수 있는 Bluetooth 오프라인 그룹 채팅까지 — 모두 기기 안에서 처리됩니다.

앱 전체는 Flutter와 네이티브 플랫폼 코드의 조합으로 구축했습니다. 이 글에서는 앱이 무엇을 하는지, 왜 Flutter가 적합한 선택이었는지, 그리고 iOS·Android 양 플랫폼에 출시하면서 맞닥뜨린 가장 어려운 문제들을 공유합니다.

앱이 하는 일

Traverba에는 다섯 가지 핵심 기능이 있으며, 모두 오프라인에서 작동합니다.

실시간 음성 번역 — 한 언어로 말하면 다른 언어로 번역된 텍스트와 음성을 즉시 확인할 수 있습니다. 세 가지 모드를 지원합니다: 앱 내 모드, 플로팅 시스템 오디오 캡처 모드(Zoom·Teams·Meet 등의 오디오를 번역), 플로팅 마이크 모드. 목표 언어로 자동 읽기(TTS) 기능도 포함됩니다.

카메라 번역 — 메뉴판, 간판, 양식, 문서 등에 카메라를 갖다 대기만 하면 됩니다. OCR 엔진은 두 가지입니다: 빠른 읽기에 최적화된 Basic 모드(ML Kit)와 한중일·아랍어·키릴 문자·데바나가리 등 복잡한 문자에 강한 Professional 모드(PaddleOCR). 92개 언어를 지원하며, 이미지 위의 외국어 텍스트를 번역된 텍스트로 직접 교체해 보여줍니다.

화면 번역 — 어떤 앱 위에든 띄울 수 있는 플로팅 오버레이로 텍스트를 번역합니다. 애니메이션을 보거나, 한국어 뉴스를 읽거나, 일본어 게임을 하거나, WeChat 메시지를 받는 상황에서 오버레이 버튼을 탭하면 텍스트가 그 자리에서 번역됩니다. 앱 전환이 필요 없습니다.

파일 전사 — mp3, m4a, wav, mp4 등의 오디오·영상 파일을 불러오면 타임스탬프가 포함된 원문 + 번역문 대조 전사본을 생성합니다.

오프라인 그룹 채팅 — Bluetooth 메시 네트워크로 최대 7명이 연결됩니다. 각자 자신의 언어로 말하면 모든 참가자의 언어로 실시간 번역됩니다. 인터넷도, 서버도 필요 없습니다. 각 기기가 자체적으로 ASR과 번역을 처리합니다.

이 모든 것이 기기의 프로세서에서 로컬로 실행됩니다. API 키도, 서버 비용도, 데이터 외부 전송도 없습니다.

Flutter를 선택한 이유

개발 초기에 Flutter, React Native, 완전 네이티브(Swift + Kotlin 별도 코드베이스)를 두고 비교 검토했습니다.

단일 코드베이스로 크로스플랫폼 지원은 필수 조건이었습니다. 이처럼 복잡한 앱의 네이티브 코드베이스 두 개를 혼자 유지하는 것은 현실적으로 불가능했습니다. 설정 화면, 채팅 버블, 전사 뷰, 온보딩 가이드 투어, 다운로드 시트, 언어 선택기 등 Dart UI 레이어는 전체 코드의 약 60%를 차지합니다. 이걸 두 번 작성했다면 프로젝트 일정이 두 배로 늘어났을 것입니다.

Flutter의 플랫폼 채널 시스템 덕분에 네이티브 통합이 실용적이었습니다. AI 집약적인 작업(ASR 추론, LLM 번역, OCR, Bluetooth 메시, 플로팅 오버레이)은 플랫폼 채널과 메서드 채널을 통해 네이티브 Swift/Kotlin에서 실행됩니다. Flutter는 AI 런타임이 아니라 네이티브 엔진들을 연결하는 UI 및 오케스트레이션 레이어입니다.

성능은 충분했습니다. 이런 종류의 앱에서 Flutter에 대한 가장 큰 우려는 오버헤드입니다 — 대화에서 ASR → 번역 → TTS를 연쇄적으로 처리할 때 1밀리초도 소중합니다. 실제로는 무거운 연산이 모두 네이티브 코드에서 이루어지기 때문에 Flutter 레이어가 추가하는 지연은 무시할 수준이었습니다. Dart 레이어는 상태 관리, 라우팅, UI 업데이트를 담당하며, 이 역할을 훌륭하게 수행합니다.

핫 리로드가 UI 반복 개발 속도를 크게 높였습니다. 각각 여러 상태와 모드를 가진 다섯 개의 주요 기능 화면을 작업할 때, 빌드 없이 UI를 바로 확인할 수 있는 기능은 생산성을 크게 높여주었습니다.

아키텍처: 오케스트레이터로서의 Flutter

앱은 명확하게 두 레이어로 나뉩니다.

Flutter/Dart 레이어가 담당하는 것:

  • 전체 UI (Material 3, 폰·태블릿 적응형 레이아웃)
  • 상태 관리 및 기능 라우팅
  • 언어·로케일 선택 및 사용자 환경설정
  • 선택적 모델 다운로드 관리
  • 코인 경제 및 구독 잠금 처리
  • 온보딩 가이드 투어

네이티브 레이어가 담당하는 것 (플랫폼 채널을 통해):

  • ASR 추론 (sherpa-onnx / whisper.cpp)
  • LLM 번역 (온디바이스 런타임을 통한 Gemma)
  • 전통적 ML 번역 (ONNX NMT 모델)
  • 카메라 OCR (ML Kit + PaddleOCR)
  • 텍스트 음성 변환(TTS)
  • 그룹 채팅용 Bluetooth 메시
  • 플로팅 오버레이 (화면 번역 + 시스템 오디오 캡처)
  • 메모리 예산 관리 및 모델 로드/해제

두 레이어를 잇는 다리는 명확한 계약(contract)을 가진 플랫폼 채널 집합입니다. Dart 측이 명령을 전송하고("광둥어로 ASR 시작", "이 텍스트를 일본어에서 영어로 번역", "화면 캡처 후 OCR"), 네이티브 측이 결과를 비동기로 반환합니다.

ASR 모델 3개와 자동 라우팅

단일 음성 인식 모델로 모든 언어를 잘 처리하기는 어렵습니다. 일부 언어에서 낮은 품질을 감수하는 대신, 특화된 모델 세 개를 탑재했습니다.

  • Parakeet 영어 최적화. 앱과 함께 번들로 제공되어 다운로드 없이 즉시 영어 ASR 가능.
  • Whisper.cpp 30개 이상의 언어를 폭넓게 지원하는 다국어 모델.
  • Qwen3-ASR 광둥어 전용 지원을 포함한 한중일(CJK) 최적화 모델.

사용자가 소스 언어를 선택하면 Dart 라우팅 레이어가 최적 모델을 선택하고, 네이티브 측에 어떤 엔진을 활성화할지 지시합니다. 사용자는 모델 선택을 직접 관리할 필요가 없습니다.

까다로운 부분은 메모리입니다. 이 모델들은 RAM 기준으로 500MB에서 1.9GB까지 차지합니다. 세 개를 동시에 로드하면 대부분의 기기에서 크래시가 발생합니다. Dart로 작성된 메모리 예산 플래너(네이티브 메모리 통계를 조회)가 로드된 모델을 추적하고, 새 모델이 필요할 때 가장 오래 사용하지 않은 모델을 해제합니다. 백엔드 환경설정 저장소는 각 기기에서 어떤 컴퓨팅 백엔드(NPU, GPU, CPU)가 동작했는지 기억해 두어, 이후 실행 시 실패한 백엔드를 건너뜁니다.

두 가지 번역 계층으로 108개 언어 지원

번역은 두 가지 경로를 통해 이루어집니다.

1계층 — 전통적 NMT 모델 (61개 언어): 빠르고 메모리 사용이 적으며, 자원이 풍부한 언어 쌍에서 품질이 우수합니다. ONNX 모델이 양 플랫폼에서 sherpa-onnx를 통해 실행됩니다.

2계층 — 온디바이스 Gemma LLM (추가 47개 언어): 전용 NMT 모델이 없는 언어(요루바어, 암하라어, 라오어, 미얀마어 등)의 경우, 양자화된 Gemma 2B 모델이 로컬에서 번역을 처리합니다. 1계층보다 느리지만, 전통적 모델이 잘 지원하지 못하는 언어까지 커버리지를 확장합니다.

Dart 레이어가 언어 쌍에 따라 어느 계층을 사용할지 결정합니다. 사용자에게는 통합된 하나의 경험으로 보입니다.

카메라 OCR: 듀얼 엔진, 92개 언어

카메라 번역은 사용자가 선택할 수 있는 두 가지 엔진으로 구동됩니다.

Basic 모드는 Google ML Kit의 온디바이스 텍스트 인식을 사용합니다. 빠르고 라틴 문자, 키릴 문자, 한중일(CJK) 처리가 뛰어납니다. 실시간 뷰파인더 사용에 적합합니다.

Professional 모드는 온디바이스 PaddleOCR을 사용합니다. 복잡한 레이아웃, 혼합 문자, 곡선 텍스트, 저대비 환경에서 더 높은 정확도를 보입니다. 연산량이 많아 캡처당 5코인의 코인 게이트가 적용됩니다.

직접 겪고 배운 교훈 하나: iOS는 기본적으로 라틴 OCR 스크립트 모델만 번들로 포함합니다. 한중일(CJK), 데바나가리, 아랍어를 사용하려면 Podfile에 스크립트별 Pod를 명시적으로 추가해야 합니다. 실패 시 아무런 오류도 없이 완전히 침묵합니다 — 지원하지 않는 스크립트에 대해 ML Kit는 에러 없이 빈 결과를 반환합니다. ML Kit로 iOS 다국어 OCR을 구축한다면 Podfile을 반드시 확인하세요.

플로팅 오버레이: Flutter가 아닌 네이티브

화면 번역과 시스템 오디오 기능은 Flutter 렌더링 표면 외부의 네이티브 오버레이로 실행됩니다. Android에서는 Kotlin으로 작성된 시스템 오버레이 서비스, iOS에서는 Swift의 화면 캡처 API를 사용합니다.

이 오버레이들은 플랫폼 채널을 통해 Flutter 번역 엔진과 통신합니다. 아키텍처상의 핵심 과제는 생명주기 관리입니다 — 메인 Flutter 액티비티가 백그라운드로 전환되더라도 오버레이는 살아 있어야 합니다.

iOS에서 얻은 중요한 교훈: 네이티브 브리지 인스턴스는 반드시 강한 참조(static 또는 싱글톤)로 유지해야 합니다. 아무것도 참조를 유지하지 않는 인스턴스 변수로 브리지를 만들면 ARC가 이를 수거하고, 모든 핸들러 클로저가 아무 일도 하지 않는 no-op이 됩니다. 다운로드 진행 표시기가 0%에 멈춰 있어서 며칠을 날렸는데, 브리지가 수거되고 있었기 때문이었습니다. 크래시도, 에러도 없이 그냥 조용히 작동을 멈추었습니다.

Bluetooth 그룹 채팅

오프라인 그룹 채팅은 Bluetooth 메시 프로토콜을 사용합니다. 각 기기는 수신기이자 중계기 역할을 동시에 합니다. 메시지는 연결된 모든 기기로 브로드캐스트됩니다. 각 기기가 자체적으로 ASR과 번역을 실행하므로 "호스트"나 "서버" 기기가 없습니다.

Flutter는 채팅 UI, 메시지 상태, 참가자 관리를 담당합니다. Bluetooth 스택은 플랫폼 채널로 브리지된 완전 네이티브 구현입니다(iOS는 CoreBluetooth, Android는 Android Bluetooth API).

전체 파이프라인 지연 시간(음성 → ASR → 번역 → 브로드캐스트 → 화면 표시)은 약 1초입니다. Dart UI는 낙관적 표시(전송 즉시 "번역 중..."을 보여줌)를 사용해 대화가 즉각 반응하는 것처럼 느껴지게 합니다.

수치로 보는 Traverba

  • 지원 언어: 총 108개 (ML 61개 + AI 47개)
  • ASR 언어: 온디바이스 음성 인식 30개
  • OCR 언어: 카메라 텍스트 인식 92개
  • 앱 기본 크기: 약 150MB + 다운로드 가능 모델
  • 플랫폼: 단일 Flutter 코드베이스로 iOS + Android
  • Dart/Flutter 코드: 전체 코드베이스의 약 60% (UI, 상태, 라우팅, 비즈니스 로직)
  • 네이티브 코드: 약 40% (ASR, LLM, OCR, Bluetooth, 오버레이, 메모리 관리)
  • 팀 규모: 1명

이 프로젝트에서 Flutter가 잘 한 것

복잡한 UI의 단일 코드베이스. 다섯 가지 기능 화면, 각각 여러 모드, 다운로드 시트, 온보딩 투어, 적응형 레이아웃 — 이것을 두 번이 아닌 한 번만 작성한 것이 혼자서 출시할 수 있었던 결정적인 차이였습니다.

플랫폼 채널이 잘 설계되어 있습니다. Dart와 네이티브 코드 사이의 브리지는 깔끔하고, 문서화가 잘 되어 있으며, 안정적입니다. 무거운 연산은 네이티브에서, 사용자 경험은 Flutter에서 처리하는 앱에 딱 맞는 아키텍처입니다.

빠른 반복을 위한 핫 리로드. 레이아웃 조정, 상태 테스트, UX 흐름 개선 — 핫 리로드 덕분에 수백 번의 UI 변경에서 반복 시간이 분 단위에서 초 단위로 줄었습니다.

에코시스템이 성숙해 있습니다. 상태 관리(Riverpod), 내비게이션, 로컬라이제이션(117개 로케일), 적응형 디자인 — Flutter 에코시스템은 이 모든 것에 대해 프로덕션 수준의 솔루션을 갖추고 있습니다.

어려웠던 것

메모리 압박은 Flutter의 맹점입니다. Flutter는 세밀한 메모리 제어를 노출하지 않습니다. Flutter 엔진과 함께 여러 네이티브 ML 모델을 동시에 로드할 때는 네이티브 레벨에서 메모리를 관리하고 그 정보를 Dart로 다시 전달해야 합니다. "앱이 RAM을 얼마나 사용하고 있는가" 또는 "시스템 메모리 압박이 어느 정도인가"를 알려주는 Flutter 내장 API는 없습니다.

네이티브 오버레이 생명주기. Flutter 표면 밖에서 동작하는 플로팅 오버레이는 아키텍처적으로 복잡합니다. 오버레이가 활성화된 동안 Flutter 액티비티는 백그라운드로 전환될 수 있습니다. 브리지를 살아있게 유지하고, 상태 동기화를 관리하고, 플랫폼별 생명주기 이벤트를 처리하는 것은 Flutter가 추상화할 수 없는 상당한 양의 네이티브 코드를 필요로 했습니다.

iOS 빌드 복잡성. ML Kit 스크립트 Pod를 포함한 CocoaPods, 엔타이틀먼트(ASR + LLM 동시 로드를 위한 increased-memory-limit), App Tracking Transparency 타이밍, 코드 서명 — iOS 빌드 파이프라인에는 Flutter 툴링이 완전히 관리하지 못하는 수많은 요소가 있습니다.

직접 사용해 보세요

Traverba는 두 플랫폼 모두 무료입니다. 음성, 카메라, 화면 번역, 텍스트 번역은 모두 무료이며 사용 제한이 없습니다. 프리미엄($1.99/월)을 구독하면 그룹 채팅, 파일 전사, 회의 요약 기능을 이용할 수 있습니다.

Google PlayApp Store에서 다운로드하세요. 자세한 내용은 traverba.com에서 확인할 수 있습니다.

아키텍처, Flutter와 네이티브 통합 패턴, 또는 온디바이스 AI 전반에 대한 질문이 있으시면 편하게 남겨 주세요.