> ## Documentation Index
> Fetch the complete documentation index at: https://docs.camb.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Translated TTS

> Automatically translate text and generate speech in the target language

export const UseCaseCard = ({src, title, description, prompt, badge, icon, href, resetOnPause = true, exclusive = true}) => {
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const uid = React.useMemo(() => 'u' + Math.random().toString(36).slice(2, 8), []);
  const hasAudio = !!src;
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio || !hasAudio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    if (exclusive) window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      if (exclusive) window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, [hasAudio, exclusive, uid]);
  const toggle = e => {
    if (e) e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      if (exclusive) window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      setProgress(0);
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      if (resetOnPause) {
        audio.currentTime = 0;
        setProgress(0);
      }
      setPlaying(false);
    }
  };
  const circleSize = 32;
  const strokeWidth = 2;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div onClick={() => href && (window.location.href = href)} style={{
    borderRadius: '12px',
    border: '1px solid rgba(255,255,255,0.08)',
    background: 'rgba(255,255,255,0.02)',
    padding: '20px',
    display: 'flex',
    flexDirection: 'column',
    gap: '12px',
    cursor: href ? 'pointer' : 'default',
    transition: 'border-color 0.2s ease, background 0.2s ease'
  }} onMouseEnter={e => {
    if (href) {
      e.currentTarget.style.borderColor = 'rgba(236,85,18,0.25)';
      e.currentTarget.style.background = 'rgba(236,85,18,0.03)';
    }
  }} onMouseLeave={e => {
    if (href) {
      e.currentTarget.style.borderColor = 'rgba(255,255,255,0.08)';
      e.currentTarget.style.background = 'rgba(255,255,255,0.02)';
    }
  }}>
      <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    justifyContent: 'space-between',
    gap: '12px'
  }}>
        <div style={{
    flex: 1,
    minWidth: 0
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    marginBottom: '8px'
  }}>
            {icon && <span style={{
    fontSize: '18px'
  }}>{icon}</span>}
            <span style={{
    fontWeight: 600,
    fontSize: '14px',
    letterSpacing: '-0.01em'
  }}>{title}</span>
            {badge && <span style={{
    fontSize: '10px',
    fontWeight: 600,
    padding: '2px 7px',
    borderRadius: '4px',
    background: 'rgba(236,85,18,0.08)',
    color: '#EC5512',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
    border: '1px solid rgba(236,85,18,0.12)',
    whiteSpace: 'nowrap'
  }}>{badge}</span>}
            {href && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    opacity: 0.3,
    marginLeft: 'auto',
    flexShrink: 0
  }}><path d="M7 17L17 7" /><path d="M7 7h10v10" /></svg>}
          </div>
          {description && <div style={{
    fontSize: '13px',
    opacity: 0.55,
    lineHeight: '1.5',
    marginBottom: prompt ? '6px' : '0'
  }}>{description}</div>}
          {prompt && <div style={{
    fontSize: '12px',
    opacity: 0.4,
    lineHeight: '1.4',
    fontStyle: 'italic'
  }}>"{prompt}"</div>}
        </div>
        {hasAudio && <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '32px',
    height: '32px',
    cursor: 'pointer',
    marginTop: '2px'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
            <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
              <circle cx={16} cy={16} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
              <circle cx={16} cy={16} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
              <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
            </svg>
            <button onClick={toggle} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '24px',
    height: '24px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 10px rgba(236,85,18,0.4)' : '0 1px 4px rgba(236,85,18,0.2)',
    transition: 'all 0.2s ease',
    padding: playing ? '0' : '0 0 0 1px'
  }}>
              {playing ? <svg width="9" height="9" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="9" height="9" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
            </button>
          </div>}
      </div>
      {hasAudio && <audio ref={audioRef} preload="none" src={src} />}
    </div>;
};

export const AudioGridItem = ({src, title, prompt, badge, resetOnPause = true, exclusive = true}) => {
  const audioRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [hovering, setHovering] = React.useState(false);
  const [promptHover, setPromptHover] = React.useState(false);
  const uid = React.useMemo(() => 'g' + Math.random().toString(36).slice(2, 8), []);
  React.useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onTime = () => setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    audio.addEventListener('timeupdate', onTime);
    audio.addEventListener('ended', onEnd);
    const onGlobalStop = e => {
      if (e.detail !== uid && !audio.paused) {
        audio.pause();
        audio.currentTime = 0;
        setPlaying(false);
        setProgress(0);
      }
    };
    if (exclusive) window.addEventListener('audio-player-stop', onGlobalStop);
    return () => {
      audio.removeEventListener('timeupdate', onTime);
      audio.removeEventListener('ended', onEnd);
      if (exclusive) window.removeEventListener('audio-player-stop', onGlobalStop);
    };
  }, []);
  const toggle = e => {
    if (e) e.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      if (exclusive) window.dispatchEvent(new CustomEvent('audio-player-stop', {
        detail: uid
      }));
      audio.currentTime = 0;
      setProgress(0);
      audio.play().catch(() => setPlaying(false));
      setPlaying(true);
    } else {
      audio.pause();
      if (resetOnPause) {
        audio.currentTime = 0;
        setProgress(0);
      }
      setPlaying(false);
    }
  };
  const circleSize = 44;
  const strokeWidth = 2.5;
  const radius = (circleSize - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const strokeDashoffset = circumference * (1 - progress);
  return <div style={{
    borderRadius: '12px',
    border: '1px solid rgba(236,85,18,0.10)',
    background: 'linear-gradient(160deg, rgba(236,85,18,0.03) 0%, transparent 50%)',
    overflow: 'hidden'
  }}>
      <div style={{
    height: '2px',
    background: 'linear-gradient(90deg, transparent, #EC5512, transparent)',
    opacity: 0.5
  }} />
      <div style={{
    padding: '14px',
    display: 'flex',
    alignItems: 'center',
    gap: '12px'
  }}>
        <div style={{
    flexShrink: 0,
    position: 'relative',
    width: '44px',
    height: '44px',
    cursor: 'pointer'
  }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
          <svg width={circleSize} height={circleSize} style={{
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)'
  }}>
            <circle cx={22} cy={22} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={strokeWidth} />
            <circle cx={22} cy={22} r={radius} fill="none" stroke={`url(#${uid})`} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset={strokeDashoffset} strokeLinecap="round" style={{
    transition: 'stroke-dashoffset 0.15s ease'
  }} />
            <defs><linearGradient id={uid} x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stopColor="#EC5512" /><stop offset="100%" stopColor="#FF8A5C" /></linearGradient></defs>
          </svg>
          <button onClick={toggle} style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '34px',
    height: '34px',
    borderRadius: '50%',
    background: hovering ? 'linear-gradient(145deg, #F06020, #EC5512)' : 'linear-gradient(145deg, #EC5512, #D44A0F)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    boxShadow: hovering ? '0 2px 10px rgba(236,85,18,0.45)' : '0 1px 4px rgba(236,85,18,0.25)',
    transition: 'all 0.2s ease',
    padding: playing ? '0' : '0 0 0 1.5px'
  }}>
            {playing ? <svg width="11" height="11" viewBox="0 0 24 24" fill="white" stroke="none"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg> : <svg width="11" height="11" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="7 3 21 12 7 21" /></svg>}
          </button>
        </div>
        <div style={{
    flex: 1,
    minWidth: 0
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px',
    flexWrap: 'wrap'
  }}>
            <span style={{
    fontWeight: 600,
    fontSize: '13px',
    letterSpacing: '-0.01em',
    lineHeight: '1.2'
  }}>{title}</span>
            {badge && <span style={{
    fontSize: '10px',
    fontWeight: 600,
    padding: '1px 6px',
    borderRadius: '3px',
    background: 'rgba(236,85,18,0.08)',
    color: '#EC5512',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
    border: '1px solid rgba(236,85,18,0.10)',
    whiteSpace: 'nowrap'
  }}>{badge}</span>}
          </div>
          {prompt && <div onMouseEnter={() => setPromptHover(true)} onMouseLeave={() => setPromptHover(false)} style={{
    position: 'relative',
    fontSize: '11.5px',
    fontStyle: 'italic',
    opacity: 0.4,
    marginTop: '2px',
    lineHeight: '1.35',
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    cursor: 'default'
  }}>
              "{prompt}"
              {promptHover && <div style={{
    position: 'absolute',
    bottom: 'calc(100% + 6px)',
    left: 0,
    right: 0,
    background: 'rgba(20,20,20,0.95)',
    border: '1px solid rgba(236,85,18,0.2)',
    borderRadius: '6px',
    padding: '6px 8px',
    fontSize: '11px',
    fontStyle: 'italic',
    opacity: 1,
    color: 'rgba(255,255,255,0.75)',
    whiteSpace: 'normal',
    lineHeight: '1.4',
    zIndex: 10,
    boxShadow: '0 4px 12px rgba(0,0,0,0.4)'
  }}>"{prompt}"</div>}
            </div>}
        </div>
      </div>
      <audio ref={audioRef} preload="none" src={src} />
    </div>;
};

export const AudioGrid = ({children, cols = 2}) => <div style={{
  display: 'grid',
  gridTemplateColumns: `repeat(${cols}, 1fr)`,
  gap: '10px',
  marginBottom: '16px'
}}>{children}</div>;

## Overview

Translate text and generate speech in one API call. Input text in any language, get audio output in your target language.

<Note>
  This is different from the standard TTS API which only changes accents. Translated TTS actually **translates** your text before speaking.
</Note>

### Hear the Translations

Same English input, automatically translated and spoken in each language:

<AudioGrid cols={2}>
  <AudioGridItem src="/audio/demo-translated-es.wav" title="Spanish" prompt="Hello, welcome to our service. We're glad to have you here." badge="es" />

  <AudioGridItem src="/audio/demo-translated-fr.wav" title="French" prompt="Hello, welcome to our service. We're glad to have you here." badge="fr" />

  <AudioGridItem src="/audio/demo-translated-de.wav" title="German" prompt="Hello, welcome to our service. We're glad to have you here." badge="de" />

  <AudioGridItem src="/audio/demo-translated-ja.wav" title="Japanese" prompt="Hello, welcome to our service. We're glad to have you here." badge="ja" />

  <AudioGridItem src="/audio/demo-translated-pt.wav" title="Portuguese" prompt="Hello, welcome to our service. We're glad to have you here." badge="pt" />

  <AudioGridItem src="/audio/demo-translated-hi.wav" title="Hindi" prompt="Hello, welcome to our service. We're glad to have you here." badge="hi" />
</AudioGrid>

### Prerequisites

<Steps>
  <Step title="Create an account">
    Sign up at [CAMB.AI Studio](https://studio.camb.ai) if you haven't already.
  </Step>

  <Step title="Get your API key">
    Go to **Settings → API Keys** in Studio and copy your key. See [Authentication](/getting-started/authentication) for details.
  </Step>

  <Step title="Install the SDK">
    <CodeGroup>
      ```bash Python theme={null}
      pip install camb-sdk
      ```

      ```bash TypeScript theme={null}
      npm install @camb-ai/sdk
      ```
    </CodeGroup>

    Skip this step if you're using the [direct API](/tutorials/direct-api).
  </Step>

  <Step title="Set your API key to use in your code">
    ```bash theme={null}
    export CAMB_API_KEY="your_api_key_here"
    ```
  </Step>
</Steps>

***

## Code

<CodeGroup>
  ```python Python theme={null}
  import os
  import time
  import requests
  from camb.client import CambAI
  from camb.types.language_enums import Languages

  client = CambAI(api_key=os.getenv("CAMB_API_KEY"))

  def translate_and_speak():
      # Translate English to Spanish and generate speech
      # Language codes: 1 = English (US), 54 = Spanish (Spain)
      response = client.translated_tts.create_translated_tts(
          text="Hello, welcome to our service. We're glad to have you here.",
          source_language=Languages.EN_US,  # English (US)
          target_language=Languages.ES_ES,  # Spanish (Spain)
          voice_id=144300      # Voice ID (required)
      )

      task_id = response.task_id
      print(f"Task created: {task_id}")

      # Poll for completion
      while True:
          status = client.translated_tts.get_translated_tts_task_status(task_id=task_id)
          print(f"Status: {status.status}")

          if status.status == "SUCCESS":
              # Get the audio URL and download
              result = client.text_to_speech.get_tts_run_info(
                  run_id=status.run_id,
                  output_type="file_url"
              )
              audio_response = requests.get(result.output_url)
              with open("translated_output.wav", "wb") as f:
                  f.write(audio_response.content)
              print("Saved to translated_output.wav")
              break
          elif status.status == "FAILED":
              print("Translation failed!")
              break

          time.sleep(2)

  translate_and_speak()
  ```

  ```javascript TypeScript theme={null}
  import { CambClient } from '@camb-ai/sdk';
  import fs from 'fs';

  const client = new CambClient({
      apiKey: process.env.CAMB_API_KEY
  });

  async function translateAndSpeak() {
      // Translate English to Spanish and generate speech
      // Language codes: 1 = English (US), 54 = Spanish (Spain)
      console.log('Translating and generating speech...');
      const response = await client.translatedTts.createTranslatedTts({
          text: "Hello, welcome to our service. We're glad to have you here.",
          source_language: 1,  // English (US)
          target_language: 54,  // Spanish (Spain)
          voice_id: 144300      // Voice ID (required)
      });

      const taskId = response.task_id;
      console.log(`Task created: ${taskId}`);

      // Poll for completion
      while (true) {
          const status = await client.translatedTts.getTranslatedTtsTaskStatus({
              task_id: taskId
          });

          console.log(`Status: ${status.status}`);

          if (status.status === 'SUCCESS') {
              // Small delay to ensure result is available in backend
              await new Promise(r => setTimeout(r, 1000));

              // Get the audio URL and download
              const result = await client.textToSpeech.getTtsRunInfo({
                  run_id: status.run_id,
                  output_type: 'file_url'
              });
              const audioResponse = await fetch(result.output_url);
              const buffer = Buffer.from(await audioResponse.arrayBuffer());
              fs.writeFileSync('translated_output.wav', buffer);
              console.log('Saved to translated_output.wav');
              break;
          } else if (status.status === 'FAILED') {
              console.log('Translation failed!');
              break;
          }

          await new Promise(r => setTimeout(r, 2000));
      }
  }

  translateAndSpeak();
  ```
</CodeGroup>

***

## Parameters

| Parameter         | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `text`            | Input text to translate                                 |
| `source_language` | Numeric language ID of input (e.g., `1` for English)    |
| `target_language` | Numeric language ID for output (e.g., `54` for Spanish) |
| `voice_id`        | Voice ID to use for speech generation (required)        |

### Common Language IDs

| Language            | ID    |
| ------------------- | ----- |
| English (US)        | `1`   |
| Spanish (Spain)     | `54`  |
| French (France)     | `76`  |
| German (Germany)    | `31`  |
| Japanese (Japan)    | `88`  |
| Hindi (India)       | `81`  |
| Portuguese (Brazil) | `111` |
| Chinese (Mandarin)  | `139` |

***

## Use Cases

<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px', marginBottom: '16px' }}>
  <UseCaseCard title="Global Announcements" description="Translate and speak messages in multiple languages" icon="📢" />

  <UseCaseCard title="Content Localization" description="Convert podcasts and videos to other languages" icon="🎬" />

  <UseCaseCard title="Customer Support" description="Respond in your customer's native language" icon="💬" />

  <UseCaseCard title="E-Learning" description="Translate course content automatically" icon="📚" />
</div>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Emotional Voice Control" icon="sparkles" href="/tutorials/emotional-voice-control">
    Add emotional expression to translated speech.
  </Card>

  <Card title="TTS with Accents" icon="globe" href="/tutorials/tts-with-accents">
    Speak in 140+ accents without translating.
  </Card>

  <Card title="Text to Speech" icon="audio-lines" href="/tutorials/tts-with-sdk">
    Generate speech from text using the SDK.
  </Card>

  <Card title="Voice Cloning" icon="mic" href="/tutorials/voice-cloning">
    Clone a voice from reference audio.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/endpoint/create-translated-tts">
    Full Translated TTS API specification.
  </Card>
</CardGroup>
