Clients often come with a task of real-time translation in a mobile app — for chat, text input, or documents. Each scenario requires its own approach: 200–400 ms latency for chat, debounce for live input, batch with progress bar for documents. Over many years of experience, we have implemented over 30 projects with translation for medical tourism, e-commerce, and messengers. We guarantee stable performance and compliance with App Store Review Guidelines. We reduce integration costs through thoughtful architecture. In this article, we break down the stack, architecture, and practical techniques.
How to Choose a Translation Provider?
We are certified Google Cloud developers and have experience integrating all three major providers. Comparison table:
| Provider | Languages | Quality | Offline | Price |
|---|---|---|---|---|
| Google Cloud Translation v3 | 130+ | NMT, excellent | No | per character |
| DeepL API | 31 (major) | Best for European | No | per character, free limit 500K/month |
| ML Kit Translate | 58 | Good for offline | Yes | free |
Google Cloud Translation is the standard for production. Supports formal/informal registers, REST and gRPC. DeepL is the choice when translation quality of complex texts (German, French) is critical. ML Kit is the foundation for offline scenarios: model ~15 MB per language pair, downloaded once.
Why Debounce Matters?
The main mistake of live translation is sending a request on every keystroke. At a typing speed of 200 characters per minute, that's 3–4 requests per second, of which 90% will be wasted before getting a response. The correct pattern is debounce with cancellation of previous pending requests.
iOS (Combine) code example
@Published var inputText: String = ""
inputText
.publisher
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.removeDuplicates()
.filter { $0.count >= 3 }
.flatMap(maxPublishers: .max(1)) { [weak self] text -> AnyPublisher<String, Never> in
guard let self else { return Empty().eraseToAnyPublisher() }
return self.translationService.translate(text)
.replaceError(with: "")
.eraseToAnyPublisher()
}
.receive(on: RunLoop.main)
.assign(to: &$translatedText)
flatMap(maxPublishers: .max(1)) — this is switchMap: it cancels the previous request on new input. Without this, old responses can overwrite the current translation.
On Android (Kotlin Flow):
val translatedText: StateFlow<String> = inputText
.debounce(500)
.filter { it.length >= 3 }
.distinctUntilChanged()
.flatMapLatest { text ->
flow { emit(translationRepo.translate(text)) }
.catch { emit("") }
}
.stateIn(viewModelScope, SharingStarted.Lazily, "")
flatMapLatest — equivalent of switchMap, cancels the previous coroutine.
Without cancellation, during fast typing the pipeline gets overwhelmed: each new character triggers a translation, but responses arrive in arbitrary order. The user sees translation "flickering". Using flatMapLatest guarantees that on new input the previous translation is cancelled, and only the latest result reaches the UI.
Integration with Google Cloud Translation v3
suspend fun translate(text: String, targetLang: String = "ru"): String {
val body = JSONObject().apply {
put("q", text)
put("target", targetLang)
put("format", "text")
}
val response = httpClient.post("https://translation.googleapis.com/language/translate/v2") {
header("Authorization", "Bearer $accessToken")
contentType(ContentType.Application.Json)
setBody(body.toString())
}
return response.body<TranslationResponse>().data.translations[0].translatedText
}
For access_token in production — GCP service account, JWT signing on the backend. The mobile client receives a short-lived token via its own /api/translate-token endpoint. No GCP API key in APK/IPA.
ML Kit for Offline Scenarios
// iOS: Google ML Kit Translate
let options = TranslatorOptions(sourceLanguage: .english, targetLanguage: .russian)
let translator = Translator.translator(options: options)
translator.downloadModelIfNeeded { error in
guard error == nil else { return }
translator.translate("Hello world") { result, error in
print(result ?? "")
}
}
The model is downloaded once over Wi-Fi. After that, it works offline. Device latency is 20–50 ms per phrase. Ideal for messengers, offline video subtitles, and travel apps.
Translation Caching
Repeated requests for the same text waste money. Cache at the SQLite level (Room/CoreData) with key sha256(source_text + target_lang). TTL 7 days for regular content, no TTL for static UI strings. At the HTTP level, Cache-Control for GET requests. According to Google Cloud Translation documentation, GET requests with parameters are supported, allowing caching at the URLCache / OkHttp Cache level. This significantly reduces API costs — caching improves efficiency by up to 10x over uncached translation.
| Scenario | Without cache | With cache |
|---|---|---|
| Live translation (100 requests) | 100 API calls | ~10–20 calls (90% hits) |
| Batch document translation (1000 pages) | 1000 calls | ~200 calls (80% hits) |
Case Study
In a project for our client, a medical tourism app (iOS + Android), we translated clinic descriptions and patient reviews (en→ru, ru→en, de→ru). Cloud translation for content on load, ML Kit offline for the consultation interface. Debounce of 700 ms for the search bar. Translation cache in Room reduced API requests by 73% within the first week after launch, and translation display speed increased by 40%. The API budget savings were significant — over $500 per month for our client.
Process
- Analytics: study usage scenarios (chat, live, batch), latency and language requirements.
- Design: select provider and architecture (cloud/offline/hybrid), design debounce and cache.
- Implementation: integration via backend, writing translation module, setting up request cancellation.
- Testing: check latencies under different loads, translation quality, offline behavior.
- Deployment: publish to App Store / Google Play, monitor errors via Crashlytics.
What Is Included
- Source code for iOS and/or Android with integration of the chosen provider.
- Documentation on architecture, access keys, and operation.
- Backend token setup and API access.
- Training your team on using the solution.
- Warranty support for 1 month after delivery.
Timelines and Cost
Basic integration of one provider with debounce and cache takes 5 to 7 days and costs starting from $1,500. Adding ML Kit offline, language auto-detection, and formatted text takes 4 to 6 days. Exact timelines and cost are calculated individually, considering your budget. Get a consultation on provider selection and translation architecture. Contact us to evaluate your project.







