Skip to content

Translated captions

Stream can translate live closed captions into several languages at once. The speaker talks in one language, and every participant on the call receives the captions in the original language plus each configured target language. Each participant then picks the language to display.

This page covers how to enable translated captions, what the events look like, and what a client needs to do to display them correctly. It assumes you are already familiar with transcriptions and closed captions.

Translated captions are in beta. Contact support before relying on this feature in production, so we can enable it for your application and confirm the languages you need.

How it works

Translated captions build on closed captions. When closed captions are running on a call and translation is configured, each caption is translated into every configured target language and sent to all participants as additional call.closed_caption events.

A few consequences follow from this design:

  • Closed captions must be running on the call. Enabling translation on its own does nothing, and transcription without closed captions does not produce translations.
  • Translation happens once per call, not per participant. Every participant receives every configured language, and the client shows the one the participant wants.
  • The set of languages is a call setting. It can be changed while the call is running.

Enabling translated captions

Translation is configured under the transcription settings of a call type or of a single call, with transcription.translation:

  • enabled: turns translation on for closed captions.
  • languages: the list of target language codes. The original caption is always delivered, so you do not need to include the spoken language.

The target language codes are listed under supported target languages below. This list is separate from the spoken-language list for transcription.

Because the translation source is the transcription language of the call, make sure the transcription language setting matches what the speakers actually speak.

You can set translation on the call type, so every call of that type gets it, or on an individual call with settings_override. The example below enables Spanish and French captions for one call and then starts closed captions on it.

// enable translation for a single call
const call = client.video.call("default", callId);
await call.getOrCreate({
  data: {
    created_by_id: "host-id",
    settings_override: {
      transcription: {
        mode: "available",
        closed_caption_mode: "available",
        language: "en",
        translation: {
          enabled: true,
          languages: ["es", "fr"],
        },
      },
    },
  },
});

// closed captions must be running for translations to be produced
await call.startClosedCaptions();

// enable translation for all calls of a call type instead
await client.video.updateCallType("default", {
  settings: {
    transcription: {
      closed_caption_mode: "available",
      language: "en",
      translation: {
        enabled: true,
        languages: ["es", "fr"],
      },
    },
  },
});

Changing languages during a call

The language list can be updated while closed captions are running. Update the call's settings with the complete list you want active, and the new languages start flowing within a few seconds without interrupting the existing ones.

// add German to a call that already translates to Spanish and French
await call.update({
  settings_override: {
    transcription: {
      translation: {
        enabled: true,
        languages: ["es", "fr", "de"],
      },
    },
  },
});

Caption events

Captions are delivered to participants as call.closed_caption events over the call's WebSocket connection, exactly as without translation. With translation enabled, each caption in the spoken language is followed by one event per target language.

The fields that matter for translation are:

  • id: identifies the piece of speech the caption belongs to. The original caption and all of its translations share the same id.
  • language: the language code of the text in this event.
  • translated: false for the caption in the spoken language, true for a translation.

The speaker_id, user, start_time and end_time fields are copied from the original caption onto its translations.

A caption and one of its translations look like this:

{
  "type": "call.closed_caption",
  "call_cid": "default:my-call",
  "closed_caption": {
    "id": "4d0b3c2e-3f7a-5c9e-8a41-2b7d0e6f1a90",
    "text": "Welcome everyone, and thank you for joining today's session.",
    "language": "en",
    "translated": false,
    "speaker_id": "host-id",
    "start_time": "2026-09-01T16:07:24.628Z",
    "end_time": "2026-09-01T16:07:30.128Z",
    "user": { "id": "host-id", "name": "Host" }
  }
}
{
  "type": "call.closed_caption",
  "call_cid": "default:my-call",
  "closed_caption": {
    "id": "4d0b3c2e-3f7a-5c9e-8a41-2b7d0e6f1a90",
    "text": "Bienvenidos a todos y gracias por unirse a la sesión de hoy.",
    "language": "es",
    "translated": true,
    "speaker_id": "host-id",
    "start_time": "2026-09-01T16:07:24.628Z",
    "end_time": "2026-09-01T16:07:30.128Z",
    "user": { "id": "host-id", "name": "Host" }
  }
}

Displaying translated captions

Because every participant receives every language, and because a caption can be delivered more than once, a client has two responsibilities.

Filter to the participant's language

Show only the events whose language matches what the participant has selected. Keep the original caption as a fallback: if the selected language is not in the call's list, show the events with translated: false.

Language selection is entirely client-side, so switching languages is instant and does not involve any API call.

Key captions by id and language, and replace on repeat

The same caption can arrive more than once. A caption is emitted while the speaker is still talking, and can be emitted again for the same id with revised or extended text and a later end_time once more of the sentence has been recognized. Translations follow each version of the original. Occasionally an identical event is delivered twice.

Treat the pair (id, language) as the key of a caption. When an event arrives for a key you are already showing, replace the displayed text with the new one instead of appending a second line. This handles both the revisions and the repeats.

Putting the two together, a minimal handler looks like this:

import type {
  CallClosedCaption,
  ClosedCaptionEvent,
} from "@stream-io/video-client";

let viewerLanguage = "es"; // whatever the participant selected
const captions = new Map<string, CallClosedCaption>();

call.on("call.closed_caption", (event: ClosedCaptionEvent) => {
  const caption = event.closed_caption;
  if (caption.language !== viewerLanguage) return;

  // same id and language: a revision of a caption we already have
  captions.set(caption.id, caption);
  render([...captions.values()]);
});

Expire captions from your display after a few seconds, or keep a fixed number of the most recent ones, as you would for untranslated captions.

The closed caption state kept by the JavaScript SDK (call.state.closedCaptions$) keeps the first caption it sees for a given speaker and start time and does not distinguish languages, so use the event directly, as above, when translation is enabled.

What to expect

Design your captions UI around the following behavior:

  • Captions trail speech by a few seconds. The caption in the spoken language typically appears a couple of seconds after the speaker finishes a sentence. This is the same as for untranslated captions.
  • Translations follow the original closely. A translation typically arrives well under a second after the original caption. Occasionally one takes a few seconds longer, so do not assume the languages arrive in lockstep.
  • Captions can split mid-sentence. During continuous speech a caption is finalized at most every few seconds, and long sentences are split across captions. Translations are made per caption, so they split at the same points. The speech segment settings control the cadence.
  • More languages mean more events. Each configured language adds one event per caption for every participant. Keep the list to the languages your participants actually need.

Supported target languages

Captions can be translated into the following languages. Use the code in parentheses in the languages list.

  • Afrikaans (af)
  • Albanian (sq)
  • Amharic (am)
  • Arabic (ar)
  • Armenian (hy)
  • Azerbaijani (az)
  • Bengali (bn)
  • Bosnian (bs)
  • Bulgarian (bg)
  • Catalan (ca)
  • Chinese (Simplified) (zh)
  • Chinese (Traditional) (zh-TW)
  • Croatian (hr)
  • Czech (cs)
  • Danish (da)
  • Dutch (nl)
  • English (en)
  • Estonian (et)
  • Filipino (Tagalog) (tl)
  • Finnish (fi)
  • French (fr)
  • French (Canada) (fr-CA)
  • Georgian (ka)
  • German (de)
  • Greek (el)
  • Gujarati (gu)
  • Haitian Creole (ht)
  • Hausa (ha)
  • Hebrew (he)
  • Hindi (hi)
  • Hungarian (hu)
  • Icelandic (is)
  • Indonesian (id)
  • Irish (ga)
  • Italian (it)
  • Japanese (ja)
  • Kannada (kn)
  • Kazakh (kk)
  • Korean (ko)
  • Latvian (lv)
  • Lithuanian (lt)
  • Macedonian (mk)
  • Malay (ms)
  • Malayalam (ml)
  • Maltese (mt)
  • Marathi (mr)
  • Mongolian (mn)
  • Norwegian (no)
  • Pashto (ps)
  • Persian (Farsi) (fa)
  • Polish (pl)
  • Portuguese (Brazil) (pt)
  • Portuguese (Portugal) (pt-PT)
  • Punjabi (pa)
  • Romanian (ro)
  • Russian (ru)
  • Serbian (sr)
  • Sinhala (si)
  • Slovak (sk)
  • Slovenian (sl)
  • Somali (so)
  • Spanish (es)
  • Swahili (sw)
  • Swedish (sv)
  • Tamil (ta)
  • Telugu (te)
  • Thai (th)
  • Turkish (tr)
  • Ukrainian (uk)
  • Urdu (ur)
  • Uzbek (uz)
  • Vietnamese (vi)
  • Welsh (cy)

Codes are case sensitive, and a regional variant such as fr-CA must be given exactly as listed.

Current limitations

  • Translations are live only. Transcription files contain the text in the spoken language. Translated captions are not stored, so a translation that a participant did not receive cannot be retrieved afterwards.
  • HLS viewers do not receive captions. Captions, translated or not, are delivered to participants connected to the call over WebRTC. They are not embedded in HLS streams.
  • Beta. The feature is in beta, and the details on this page can change. Contact support to have it enabled for your application.