Skip to main content

Multi-language flows

Every flow has a default language — the source language for all text content. Any node that emits text (Send, Choice prompts, Choice options, Collect Input prompts) can carry additional translations keyed by BCP47 locale code. At runtime the engine picks the contact's locale if present, otherwise falls back to the flow's default language.

Set the default language in Flow settings → Default language. New flows seed this from the operator's UI language. Changing it does not retranslate existing content — it only changes which tab is the source.

Adding translations

In any localized text editor:

  1. The default-language tab is always visible and locked first.
  2. Click + Add language and pick from the curated list of 40 locales (English, German, French, Spanish, Italian, Polish, Portuguese, Russian, Turkish, Arabic, Croatian, Chinese, Japanese, Hindi, Korean, Vietnamese, Indonesian, Thai, Dutch, Swedish, Norwegian, Danish, Finnish, Czech, Greek, Hebrew, Romanian, Hungarian, Ukrainian, Bulgarian, Slovak, Lithuanian, Latvian, Estonian, Slovene, Bengali, Malay, Persian, Urdu, Swahili).
  3. Type the translation manually, or click Translate to all to auto-fill empty tabs from the default-language source using Gemini.

RTL locales (Arabic, Hebrew, Persian, Urdu) render inputs with dir="rtl" automatically.

Translation metadata

Every translatable field on a node carries a sibling *_translations_meta object. For example, a Send node's body_text field is paired with body_text_translations_meta:

{
"body_text": "Welcome to Acme!",
"body_text_translations": {
"de": "Willkommen bei Acme!",
"fr": "Bienvenue chez Acme !"
},
"body_text_translations_meta": {
"de": {
"source_hash": "a17c93b4e2f8...",
"is_manual_edit": false,
"translated_at": "2026-05-12T14:33:21Z"
},
"fr": {
"source_hash": "a17c93b4e2f8...",
"is_manual_edit": true,
"translated_at": "2026-05-13T09:01:44Z"
}
}
}
Meta fieldPurpose
source_hashFirst 32 chars of sha256(base_text), computed by the shared compute_source_hash helper. Used for stale detection.
is_manual_edittrue when the operator typed into this locale tab directly. Protects the translation from being overwritten by Translate to all.
translated_atISO 8601 timestamp of the last write.

Stale detection

When you edit the default-language text, machine translations of that text become outdated. The translation's stored source_hash no longer matches the current base hash (compute_source_hash(base_text) — sha256 truncated to 32 chars), and the tab shows an amber Outdated badge with an inline Retranslate button.

Manual edits

If you type into a non-default tab, that translation is flagged is_manual_edit: true and shows a blue Manually edited badge. Translate to all skips manually-edited tabs by default — use Force retranslate (confirms first) to overwrite them, including manually-edited locales.

Runtime behavior

The flow engine resolves text in this order:

  1. translations[contact.preferred_language] if present and non-empty
  2. translations[flow.default_language] if present
  3. The unlocalized value field (back-compat fallback)

Contacts inherit preferred_language from their channel profile (e.g. WhatsApp display locale, widget language param). Set or override it via the contact's profile.

API

POST /api/v1/flows/translate returns {translated_text, source_hash}. Pass it as {source_text, target_language, source_language?, context?}. The hash lets your client cache results and detect when the source has changed without re-calling the LLM.

The default_language and prefer_originating_channel fields are first-class on the Flow resource — see the Flow API reference.