# Translation

**The problem.** A support agent in Paris, a customer speaking Spanish; a conference streamed to an international
audience. Translation turns each sentence into the languages you choose while the person is still speaking: live
subtitles, a bilingual call log, a meeting record in your team's language.

**Where it works.** `solaria-3` only. The translation is made **from the audio** of each utterance, not from its
transcript.

> **Use the SDK.** The Gladia SDKs stream the audio, keep it until the API has transcribed it, and reconnect after a network drop
without losing a word. Most integration issues come from hand-written WebSocket clients: start from the SDK
examples below.

## Step by step

### 1. Choose the target languages

```json
{
  "model": "solaria-3",
  "realtime_processing": {
    "translation": true,
    "translation_config": { "target_languages": ["en", "fr"] }
  }
}
```

### 2. Start a session with it

Each program streams a 16-bit WAV file and prints what comes back. Put your API key in the `GLADIA_API_KEY` environment variable, then run `python translation.py meeting.wav`.

**Python SDK**

```python title="translation.py"
# pip install gladiaio-sdk
import os
import sys
import threading
import wave

from gladiaio_sdk import GladiaClient, LiveV2InitRequest

API_URL = os.environ.get("GLADIA_API_URL", "https://eu1.api.gladia.io")
API_KEY = os.environ["GLADIA_API_KEY"]

with wave.open(sys.argv[1]) as wav:  # a 16-bit PCM WAV file
    rate, channels = wav.getframerate(), wav.getnchannels()
    audio = wav.readframes(wav.getnframes())

config = {
    "encoding": "wav/pcm",
    "sample_rate": rate,
    "bit_depth": 16,
    "channels": channels,
    "model": "solaria-3",
    "language_config": {
        "languages": [
            "es"
        ]
    },
    "realtime_processing": {
        "translation": True,
        "translation_config": {
            "target_languages": [
                "en",
                "fr"
            ]
        }
    }
}

client = GladiaClient(api_key=API_KEY, api_url=API_URL)
session = client.live_v2().start_session(LiveV2InitRequest.from_dict(config))
ended, errors = threading.Event(), []

@session.on("message")
def on_message(message):
    if message.type == "transcript" and message.data.is_final:
        print(message.data.utterance.text)
    elif message.type == "translation" and message.data.translated_utterance:
        translated = message.data.translated_utterance
        print(f"  {translated.language}: {translated.text}")

session.on("error", errors.append)
session.once("ended", lambda _: ended.set())

step = rate * channels * 2 // 10  # 100 ms of audio per chunk
for start in range(0, len(audio), step):
    session.send_audio(audio[start : start + step])  # live audio: send each chunk as it is captured
session.stop_recording()

ended.wait(timeout=60)
sys.exit(1 if errors or not ended.is_set() else 0)
```

**JavaScript SDK**

```js title="translation.mjs"
// npm install @gladiaio/sdk
import { readFileSync } from "node:fs";
import { GladiaClient } from "@gladiaio/sdk";

const { sampleRate, channels, pcm } = readWav(process.argv[2]); // a 16-bit PCM WAV file

const config = {
  encoding: "wav/pcm",
  sample_rate: sampleRate,
  bit_depth: 16,
  channels,
  model: "solaria-3",
  language_config: {
    languages: [
      "es"
    ]
  },
  realtime_processing: {
    translation: true,
    translation_config: {
      target_languages: [
        "en",
        "fr"
      ]
    }
  }
};

const client = new GladiaClient({
  apiKey: process.env.GLADIA_API_KEY,
  apiUrl: process.env.GLADIA_API_URL ?? "https://eu1.api.gladia.io",
});
const session = client.liveV2().startSession(config);

session.on("message", (message) => {
  if (message.type === "transcript" && message.data.is_final) console.log(message.data.utterance.text);
  if (message.type === "translation" && message.data.translated_utterance) {
    const { language, text } = message.data.translated_utterance;
    console.log(`  ${language}: ${text}`);
  }
});
session.on("error", (error) => {
  console.error(error);
  process.exitCode = 1;
});
session.once("ended", () => process.exit());

const step = (sampleRate * channels * 2) / 10; // 100 ms of audio per chunk
for (let start = 0; start < pcm.length; start += step) {
  session.sendAudio(pcm.subarray(start, start + step)); // live audio: send each chunk as it is captured
}
session.stopRecording();

function readWav(path) {
  const file = readFileSync(path);
  let fmt, pcm;
  for (let pos = 12; pos < file.length; ) {
    const id = file.toString("ascii", pos, pos + 4);
    const size = file.readUInt32LE(pos + 4);
    if (id === "fmt ") fmt = { channels: file.readUInt16LE(pos + 10), sampleRate: file.readUInt32LE(pos + 12) };
    if (id === "data") pcm = file.subarray(pos + 8, pos + 8 + size);
    pos += 8 + size + (size % 2);
  }
  return { ...fmt, pcm };
}
```

**HTTP**

Create the session, then stream its audio to the returned `url` ([Live WebSocket](/api-reference/v2/live/websocket/)):

```bash
curl -X POST https://eu1.api.gladia.io/v2/live \
  -H "x-gladia-key: $GLADIA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "encoding": "wav/pcm",
    "sample_rate": 16000,
    "bit_depth": 16,
    "channels": 1,
    "model": "solaria-3",
    "language_config": {
      "languages": [
        "es"
      ]
    },
    "realtime_processing": {
      "translation": true,
      "translation_config": {
        "target_languages": [
          "en",
          "fr"
        ]
      }
    }
  }'
```

### 3. Read the translations

Each final transcript is followed by one `translation` message per target language:

```json
{
  "type": "translation",
  "session_id": "0199d7a2-5c3e-7b41-9f0a-3c2d1e4f5a6b",
  "error": null,
  "data": {
    "utterance_id": "00_00000003",
    "utterance": { "text": "¿Dónde está la estación?", "start": 12.4, "end": 13.9, "language": "es", "channel": 0 },
    "original_language": "es",
    "target_language": "en",
    "translated_utterance": { "text": "Where is the station?", "start": 12.4, "end": 13.9, "language": "en", "channel": 0, "confidence": 1, "words": [] }
  }
}
```

### 4. Get them all at the end

The session's result ([`GET /v2/live/{id}`](/api-reference/v2/live/get/), `post_final_transcript`) has a
`translation` block: one result per target language, with its utterances and full text.

## Limits and gotchas

- **`solaria-3` only.** With another model, no model can serve the session: `POST /v2/live` answers `500` "no
  inference endpoint available".
- **Only the languages below.** Another code passes the creation, then the session is refused when it starts: the
  WebSocket closes with code `1011` and the reason `rejected: translation: target language(s) [...] not served`.
- **Not with PII redaction.** The translation is made from the audio and would carry the redacted data back: a
  session with both `pii` and `translation` is refused when it starts (`1011`, `rejected: ...`).
- **Finals only.** Partials are not translated. Translations arrive shortly after their final, never before it, and
  never delay it.
- **Same language, same text.** An utterance already in a target language comes back as transcribed.
- **Long utterances.** Translation reads up to 60 seconds of an utterance: set `maximum_duration_without_endpointing`
  (at most 60) for monologues without pauses.
- **A translation can fail alone.** It then comes with `error` set and `translated_utterance: null`; the transcript
  is not affected. Show the original text as a fallback.
- **At the end**, the session waits up to 5 seconds for the last translations before `end_session`.
- **Spelling and vocabulary** do not apply to translations (they come from the audio).
- **Accepted, not applied.** Gladia's cloud fields `model`, `context`, `context_adaptation`, `informal`, `lipsync`
  and `match_original_utterances` in `translation_config` are accepted for compatibility and have no effect.

## Target languages

- Afrikaans (`af`)
- Amharic (`am`)
- Arabic (`ar`)
- Assamese (`as`)
- Belarusian (`be`)
- Bulgarian (`bg`)
- Bangla (`bn`)
- Catalan (`ca`)
- Danish (`da`)
- German (`de`)
- English (`en`)
- Spanish (`es`)
- Persian (`fa`)
- French (`fr`)
- Galician (`gl`)
- Indonesian (`id`)
- Italian (`it`)
- Japanese (`ja`)
- Khmer (`km`)
- Korean (`ko`)
- Lingala (`ln`)
- Lao (`lo`)
- Malay (`ms`)
- Burmese (`my`)
- Nepali (`ne`)
- Dutch (`nl`)
- Norwegian (`no`)
- Pashto (`ps`)
- Portuguese (`pt`)
- Romanian (`ro`)
- Russian (`ru`)
- Somali (`so`)
- Swedish (`sv`)
- Thai (`th`)
- Vietnamese (`vi`)
- Wolof (`wo`)
- Yoruba (`yo`)
- Chinese (`zh`)

## Troubleshooting

| symptom | cause | fix |
|---|---|---|
| `500` "no inference endpoint available" at creation | the model is not `solaria-3` | set `"model": "solaria-3"` |
| WebSocket closed `1011` "rejected: translation: target language(s)…" | a target not in the list | use the codes above |
| WebSocket closed `1011` "rejected: … pii redaction" | `pii` and `translation` together | drop one of them |
| no `translation` message | `messages_config.receive_realtime_processing_events: false` | turn it back on, or read the session's result |
