Skip to main content
RapidDev - Software Development Agency
App Featuresai-features20 min read

How to Add Real-Time Translation to Your App — Copy-Paste Prompts Included

Real-time translation needs four pieces: a debounced text input (500ms pause before API fires), a DeepL or Google Translate Edge Function with auto-detect, a translation cache to avoid re-billing identical text, and a TTS play button for the translated output. With Lovable you can ship a working translator in 4–6 hours. Costs run $0/month at 100 users — DeepL's free tier covers 500K characters per month — and $0–20/month at 1,000 users once you approach that limit.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Intermediate

Category

ai-features

Build with AI

4–8 hours with Lovable or FlutterFlow

Custom build

1–2 weeks custom dev

Running cost

$0/mo up to 100 users; $0–20/mo at 1K users with caching

Works on

Mobile

Everything it takes to ship Real-Time Translation — parts, prompts, and real costs.

TL;DR

Real-time translation needs four pieces: a debounced text input (500ms pause before API fires), a DeepL or Google Translate Edge Function with auto-detect, a translation cache to avoid re-billing identical text, and a TTS play button for the translated output. With Lovable you can ship a working translator in 4–6 hours. Costs run $0/month at 100 users — DeepL's free tier covers 500K characters per month — and $0–20/month at 1,000 users once you approach that limit.

What Real-Time Translation Actually Is

Real-time translation converts typed or spoken text to another language as the user pauses — not on every keystroke, and not after they press a button. The 500ms debounce is what makes it feel instant rather than laggy. The engine is a DeepL or Google Translate API call routed through a Supabase Edge Function, which keeps your API key out of the client. The product differentiators that matter in 2026 are language auto-detect (so users don't have to select their source language), a swap button for two-person conversation mode, TTS playback of the translated output, and a translation cache that prevents re-billing the same sentence twice. History per session completes the feature for users who want to review or copy previous translations.

What users consider table stakes in 2026

  • Translation appears within 500ms of the user pausing input — debounced, not on every keystroke and not requiring a submit button
  • Language auto-detect option so users don't need to know or select their source language
  • At least 20 supported language pairs in the target selector
  • Copy-to-clipboard button on the translated output — the primary action after translating is to paste it somewhere
  • Swap languages button that exchanges source and target dropdowns and moves translated text into the input field for quick conversation turns
  • Translation history saved per session so users can review and copy previous pairs without re-translating

Anatomy of the Feature

Eight components across UI, backend, service, and data layers. The debounce logic and the DeepL auto-detect wiring are where most first builds get it wrong.

Layers:UIDataBackendService

Language Selector

UI

Two dropdowns side by side: source language and target language. The source dropdown includes an 'Auto-detect' option as the first item. Populated from a static list of DeepL or Google Translate supported language codes with display names (English, Spanish, French, German, Japanese, etc.) — at minimum 20 options. Selection is stored in component state and persisted to localStorage for returning users.

Note: When Auto-detect is selected, the source_lang field must be entirely absent from the DeepL API request body — not set to 'auto'. Sending source_lang: 'auto' is an invalid parameter that DeepL rejects.

Text Input with Debounce

UI

A textarea with a useDebounce hook (500ms delay) that fires the translation call only when the user pauses typing. Shows a character count in the bottom-right corner. The translation call fires on the debounced value change, not on onChange. Maximum input: 5,000 characters for DeepL (API limit per request).

Note: The debounce hook must cancel the pending timer on component unmount to prevent state updates after navigation. A standard useDebounce implementation with useEffect and clearTimeout handles this cleanly.

Translation Engine

Backend

A Supabase Edge Function calling DeepL API (/v2/translate) or Google Cloud Translation API. The request body includes: text (the source string), target_lang (user-selected code), and source_lang only when a specific language is chosen (omit entirely for auto-detect). The response includes translated_text and detected_source_language from DeepL. The API key lives in Cloud tab → Secrets.

Note: Check the translation_cache table before calling the external API. If a matching hash exists, return the cached translation immediately without an API call. Insert to cache after a fresh API call.

Voice Input Integration

Service

Optional but commonly requested: a mic button next to the text input that uses SpeechRecognition API (web) or speech_to_text (Flutter) to populate the input field with spoken text. The transcript feeds into the same debounced translation pipeline — no separate code path needed.

Note: Use the source language code as the lang parameter for SpeechRecognition to improve recognition accuracy when the user has selected a specific source language.

TTS Output

Service

A play button next to the translated output text. On click, creates a SpeechSynthesisUtterance with the translated text and the target language code (e.g. 'fr-FR', 'es-ES'). Uses Web Speech API SpeechSynthesis on web or flutter_tts on Flutter. The voice is selected from available voices matching the target language code.

Note: Not all target language voices are available on all devices. Call flutter_tts.isLanguageAvailable(locale) before speak() and show a 'Language pack not available' message if false, rather than speaking in the wrong language or silently failing.

Translation Cache

Data

A Supabase table with a SHA-256 hash of (source_text + source_lang + target_lang) as the primary key. Before each API call, the Edge Function queries this table. Cache hits save both the API cost and the ~200ms round-trip to the translation service.

Note: The cache is a global table (not per-user) because identical text translates identically for all users. Use service_role for INSERT; authenticated users can SELECT. Optionally expire entries after 30 days for content that may change.

Swap Languages Button

UI

An icon button (two arrows in a circle) between the source and target selectors. On click: exchanges source and target language codes, moves the current translated output text into the input textarea, clears the translation output, and triggers the debounced translation call immediately (override the 500ms debounce on swap).

Note: The swap should also update the mic button's recognition language if voice input is active. Swap is the core UX for two-person conversation mode.

History List

Data

A Supabase table storing each completed translation with source text, translated text, language pair, and timestamp. Shown in a scrollable list below or in a side panel. RLS ensures users see only their own history. Users can tap a history item to load it back into the input/output fields.

Note: History rows are inserted after a successful translation completes, not on every debounce trigger. Only insert when the translation is final (user has paused for more than 500ms and a result came back).

The data model

Two tables: a global translation cache (deduplicates API calls for identical content) and per-user translation history. Run this in the Supabase SQL editor:

schema.sql
1create table public.translation_cache (
2 text_hash text primary key,
3 source_lang text not null,
4 target_lang text not null,
5 translated_text text not null,
6 created_at timestamptz not null default now()
7);
8
9create table public.translation_history (
10 id uuid primary key default gen_random_uuid(),
11 user_id uuid references auth.users(id) on delete cascade not null,
12 source_text text not null,
13 translated_text text not null,
14 source_lang char(5) not null,
15 target_lang char(5) not null,
16 created_at timestamptz not null default now()
17);
18
19alter table public.translation_cache enable row level security;
20alter table public.translation_history enable row level security;
21
22create policy "Authenticated users can read cache"
23 on public.translation_cache for select
24 to authenticated
25 using (true);
26
27create policy "Service role inserts cache"
28 on public.translation_cache for insert
29 to service_role
30 with check (true);
31
32create policy "Users see own history"
33 on public.translation_history for select
34 using (auth.uid() = user_id);
35
36create policy "Users insert own history"
37 on public.translation_history for insert
38 with check (auth.uid() = user_id);
39
40create index translation_history_user_idx
41 on public.translation_history (user_id, created_at desc);

Heads up: The Edge Function uses the service_role key to write to translation_cache (global dedup table). The frontend client uses the anon key to read cache and write to translation_history. The text_hash primary key on translation_cache ensures duplicate translations are never stored even under concurrent requests.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Hand-built by developersFit for this feature:

Required for streaming word-by-word output, two-person conversation mode with speaker detection, offline translation via on-device NLLB-200 model, or custom glossary enforcement for brand terms.

Step by step

  1. 1Implement streaming translation using Google Cloud Translation's Streaming API or DeepL's streaming endpoint for word-by-word output as the user types
  2. 2Build conversation mode: alternate UI showing Person A / Person B turns with automatic language swap after each turn; TTS reads each turn aloud in the target language
  3. 3Integrate NLLB-200 (Meta's multilingual model) via WASM for offline translation of the top 10 most common language pairs; cache model files locally on first use
  4. 4Implement custom glossary: define brand terms that must never be translated (product names, trademarks); inject glossary into DeepL API calls via the glossary_id parameter

Where this path bites

  • NLLB-200 models for offline translation are 100MB–600MB each depending on compression — shipping them in a Flutter app requires careful size management and conditional downloading
  • Streaming from Google Cloud Translation requires a WebSocket connection; Supabase Edge Functions have a 150-second timeout, which limits very long streaming sessions

Third-party services you'll need

Four viable translation APIs at different accuracy, language coverage, and cost levels. Caching makes all of them affordable at scale:

ServiceWhat it doesFree tierPaid from
DeepL APIHighest accuracy translation for European languages; auto-detect; 30+ languages500,000 characters/month freeDeepL API Pro: $6.99+/mo based on usage at $0.0000025/character (approx)
Google Cloud Translation API130+ languages; reliable auto-detect; good for Asian and less common languages500,000 characters/month free$20 per 1M characters beyond free tier (approx)
Microsoft Azure Cognitive Services Translator100+ languages; 2M free chars/month; good Azure ecosystem integration2,000,000 characters/month freeStandard $10 per 1M characters (approx)
LibreTranslateOpen-source self-hosted translation engine; zero API cost; lower accuracy than commercial optionsUnlimited (self-hosted)Free (server hosting cost only)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

DeepL or Google free tier (500K chars/month) covers typical 100-user usage. Average 2,000 chars/user/month = 200K chars total, well under the free threshold. Translation cache deduplicates repeat requests to zero additional cost.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Translation fires on every keystroke causing 20+ API calls per sentence

Symptom: Without a debounce, the onChange handler fires on every character typed. A 20-word sentence generates 80+ character events, each triggering an API call. At DeepL pricing this is still cheap, but the UI flickers on every character as translations stream in — making the feature feel broken even when it's technically working.

Fix: Wrap the API call in a useDebounce hook (500ms). The hook cancels the previous timer whenever the input changes and only fires the API call when the user pauses for 500ms. On Flutter, use a dart:async Timer with cancel() on each new input event. This single change is the difference between a laggy feature and a smooth one.

Translated text is always in English regardless of target language selection

Symptom: This is the most common first-build failure: the target_lang parameter is hardcoded as 'EN' in the Edge Function from an early test, and the AI didn't wire the dropdown selection to the API call correctly. The translation appears to work — it translates — but always to English. Users assume the feature is broken.

Fix: Log the Edge Function request payload to verify target_lang is being received with the correct value. In the Supabase Dashboard, go to Edge Functions → Logs and check the request body for each call. Ensure the frontend state variable holding the target language is being sent in the request body, not a hardcoded value.

Language auto-detect always returns English even for French or Spanish input

Symptom: DeepL's auto-detect only activates when the source_lang parameter is absent from the API request. If the frontend sends source_lang: 'auto', DeepL returns a 400 Bad Request. If it sends source_lang: '' or source_lang: null, some implementations still include the key. The result is no auto-detection — DeepL defaults to English or rejects the request entirely.

Fix: When the user selects 'Auto-detect', build the request body without the source_lang key at all: {text: [source_text], target_lang: target}. Use conditional object spreading: const body = {text: [text], target_lang, ...(sourceLang !== 'auto' && {source_lang: sourceLang})}. Verify by omitting source_lang in a direct API test and confirming detectedSourceLanguage is returned.

Translation history loads for all users, not just the current user

Symptom: If RLS is not enabled on the translation_history table, or the policy is set to allow all authenticated users to read all rows, every logged-in user sees everyone's translation history. This is a data privacy issue and a confusing UX — users see translations they didn't make.

Fix: Enable RLS on translation_history and add the policy: CREATE POLICY 'Users see own history' ON translation_history FOR SELECT USING (auth.uid() = user_id). Verify in the Supabase Table Editor by logging in as two different test users and confirming each sees only their own rows.

flutter_tts fails to play translated Arabic or Hebrew text

Symptom: RTL language TTS (Arabic, Hebrew, Persian) requires specific locale codes (ar-SA, he-IL) and the corresponding TTS language pack installed on the device. On Android, many devices don't ship with Arabic or Hebrew TTS. The flutter_tts speak() call completes without error but produces no audio, which looks like a silent crash.

Fix: Call flutterTts.isLanguageAvailable(targetLang) before any speak() call. If the result is false, show an inline message: 'Tap to download the Arabic language pack in your device settings.' On Android, link to Settings → Language and Input → Text-to-speech options. Never call speak() if the language is unavailable.

Best practices

1

Implement debounce at 500ms — wire the translation call to a debounced value, never to onChange directly

2

Omit source_lang from the DeepL request entirely when Auto-detect is selected — sending 'auto' or null is invalid and breaks detection

3

Cache translations by SHA-256(source_text + source_lang + target_lang) in a global Supabase table — cache hit rate of 60–70% is realistic for common phrases and eliminates most API cost

4

Check flutter_tts.isLanguageAvailable(locale) before every speak() call for non-English languages — silently fails on devices without the TTS pack installed

5

Log Edge Function request payloads during development to verify target_lang and source_lang are being sent with correct values, not hardcoded strings

6

Limit history inserts to completed translations (500ms pause + API response received) — not every debounce trigger, or history fills with partial mid-sentence text

7

Set a 5,000-character limit on the source textarea (DeepL's per-request limit) and show a character counter so users don't hit the limit mid-sentence without warning

When You Need Custom Development

AI tools handle standard two-language translation with auto-detect and history well. These requirements push past the prompted build:

  • Conversation mode with two-person real-time translation — speaker detection, alternating output panels, and automatic language swap on turn change requires a custom stateful conversation architecture
  • Industry-specific translation accuracy for medical, legal, or technical vocabulary — requires custom DeepL glossaries or fine-tuned translation models trained on domain-specific parallel text
  • On-device offline translation for all 50+ supported languages without API dependency — requires bundling NLLB-200 or similar multilingual models, significant app size management
  • Custom glossary enforcement where brand terms, product names, or trademarks must never be translated — requires DeepL API glossary_id parameter and a glossary management interface

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Which translation API is most accurate?

DeepL consistently outperforms Google Translate and Microsoft Translator for European languages (English, French, German, Spanish, Italian, Portuguese, Dutch, Polish) based on independent benchmarks. For Asian languages (Japanese, Chinese, Korean) and less common languages, Google Cloud Translation has broader coverage and comparable quality. For budget-conscious apps, Azure Cognitive Services Translator offers a generous 2M free character tier monthly.

How do I add auto-detect language?

For DeepL: add 'Auto-detect' as the first option in the source language dropdown. When selected, build the API request body without the source_lang key entirely — use conditional spreading to omit it. DeepL's response includes detectedSourceLanguage which you display below the source dropdown. For Google Translate: omit the source parameter from the request body. Never send source_lang: 'auto' — that string is not a valid language code and returns a 400 error.

Can users translate speech in real time?

Yes, by combining voice input with the translation pipeline. Add a mic button using the Web Speech API SpeechRecognition with interimResults: true. The live transcript populates the source textarea, and the 500ms debounce fires the translation Edge Function on each pause. Users hear their speech transcribed and see the translation appear within half a second of finishing a phrase. Add flutter_tts or SpeechSynthesis to read the translation aloud for a full hands-free translation experience.

How much does the translation API cost?

DeepL: free for 500K characters/month, then approximately $0.0000025/character. A 100-word paragraph is ~600 characters — that's 833 free paragraphs per month. Google Cloud Translation: free 500K chars/month, then $20/1M chars. At 1,000 users averaging 2,000 characters of translation per month, you're at 2M chars — $40 on Google or $5 on DeepL with caching. Caching identical translations typically reduces effective API usage by 60–70%.

How do I support offline translation?

Two options: (1) Cache the most common translations in the translation_cache Supabase table and pre-load them locally in a service worker or SQLite database on first app use. This covers the top 500–1,000 phrases offline without ML models. (2) Bundle Meta's NLLB-200 model compiled to WebAssembly (web) or TensorFlow Lite (Flutter) for true on-device translation of any text. NLLB-200 supports 200 languages but the model files are 150MB–600MB. For most apps, option 1 (phrase caching) is the practical choice.

Can I add a swap languages button?

Yes, and it's essential UX for conversation mode. The swap button should: exchange source and target language state values, move the current translated output text into the source textarea, clear the translation output, and trigger an immediate translation call (bypassing the 500ms debounce so the response feels instant). On FlutterFlow, implement this in a Custom Action that updates all relevant Page State variables sequentially and then calls the translation action.

How do I save translation history?

Insert a row to the translation_history table after a successful translation completes — when the user has paused typing for 500ms and the Edge Function has returned a result. Include user_id (from auth), source_text, translated_text, source_lang, and target_lang. With RLS enabled (auth.uid() = user_id), users only ever see their own history. Display the last 10–20 rows in a scrollable list sorted by created_at descending.

Does this work for right-to-left languages like Arabic?

The translation API handles Arabic, Hebrew, and Persian correctly. The UI requires text direction handling: set dir='rtl' on the output textarea when target_lang is an RTL language code (ar, he, fa). On Flutter, use TextDirection.rtl in the Text widget for the translated output. For TTS, check flutter_tts.isLanguageAvailable('ar-SA') before calling speak() — Arabic TTS requires a language pack that many Android devices don't ship with by default.

RapidDev

Need this feature production-ready?

RapidDev builds real-time translation into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.