Build a Localization Pipeline with YouTube Transcripts
Zied · 8/4/2026 · 8 min read
Build a Localization Pipeline with YouTube Transcripts
Shipping a product globally means your video content needs to travel with it. If you are maintaining YouTube captions, help-center screencasts, or course videos in more than one language, you need a repeatable pipeline, not a one-off export.
This guide walks through the full flow: fetching source transcripts, requesting youtube transcript multiple languages directly from the API, combining that output with a translation step, and exporting to formats your localization team can actually open.

The use case: captions and docs across global markets
Suppose you ship a SaaS product with a library of tutorial videos. You want French, German, and Portuguese captions alongside English. Your options are:
- Pay a human to watch each video and retype captions in every language.
- Export the YouTube auto-captions manually for each video, per language, per locale.
- Build an API-driven pipeline that fetches transcripts programmatically, routes them through a translation engine, and produces ready-to-import subtitle files.
Option three is the only one that scales past a handful of videos. That is what this guide builds.
Fetching the source transcript and detecting the original language
Start by pulling the transcript for your source video. The /api/v2/transcript endpoint returns a clean JSON payload with per-segment text and timing data.
curl "https://getyoutubetranscriber.com/api/v2/transcript?video_url=dQw4w9WgXcQ&include_timestamp=true&send_metadata=true" \
-H "Authorization: Bearer YOUR_API_KEY"
The response looks like this:
{
"video_id": "dQw4w9WgXcQ",
"language": "en",
"transcript": [
{ "text": "Welcome to the onboarding series.", "start": 1200, "duration": 2800 },
{ "text": "Today we cover account setup.", "start": 4100, "duration": 2600 }
],
"metadata": {
"title": "Getting Started with Our Platform",
"author_name": "Acme Corp",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
}
}
The language field tells you what track was actually served. Always read it. If a video's auto-captions are mislabeled (common with code-switched or accent-heavy content), you will catch it here before it poisons your translation step. For tips on handling videos where captions are missing or disabled entirely, see Handle Missing YouTube Transcripts Gracefully.
Requesting transcripts in specific target languages
If the video already has a caption track in your target language, you can fetch it directly with the lang parameter, which accepts standard ISO 639-1 codes and regional variants.
import httpx
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://getyoutubetranscriber.com/api/v2/transcript"
def fetch_transcript(video_url: str, lang: str = None) -> dict:
params = {"video_url": video_url}
if lang:
params["lang"] = lang
headers = {"Authorization": f"Bearer {API_KEY}"}
response = httpx.get(BASE_URL, params=params, headers=headers)
response.raise_for_status()
return response.json()
# Fetch the German caption track if available
data = fetch_transcript("dQw4w9WgXcQ", lang="de")
print(f"Served language: {data['language']}")
When the API serves the track, data["language"] will equal "de". If no German track exists, the API falls back to the video's original track and data["language"] will reflect that. This is the branching point in your pipeline: a mismatch means you route the source transcript through translation rather than using the native track.
source_data = fetch_transcript("dQw4w9WgXcQ") # source language
target_data = fetch_transcript("dQw4w9WgXcQ", lang="de") # attempt German
if target_data["language"] != "de":
# No native German track; translate the source transcript instead
segments_to_translate = source_data["transcript"]
else:
segments_to_translate = None # native track is good
For bulk jobs, such as localizing an entire product channel, the /api/v2/transcripts-bulk endpoint accepts up to 50 video URLs in a single POST request, which keeps your credit usage predictable and avoids per-request overhead.
import httpx
def bulk_fetch(video_urls: list[str], lang: str = "en") -> list[dict]:
headers = {"Authorization": f"Bearer {API_KEY}"}
payload = {"video_urls": video_urls, "lang": lang}
response = httpx.post(
"https://getyoutubetranscriber.com/api/v2/transcripts-bulk",
json=payload,
headers=headers
)
response.raise_for_status()
return response.json()
Combining transcript data with a translation step
When no native track exists for your target language, feed the source transcript segments through a translation engine. The segment-by-segment structure of the API response is an advantage here: you translate each text value independently, preserving the start and duration timestamps exactly.
from openai import OpenAI
client = OpenAI()
def translate_segments(segments: list[dict], target_lang: str) -> list[dict]:
translated = []
for seg in segments:
result = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"Translate the following subtitle text to {target_lang}. "
"Return only the translated text, nothing else. "
"Keep it concise enough for a subtitle caption."
},
{"role": "user", "content": seg["text"]}
]
)
translated.append({
"text": result.choices[0].message.content.strip(),
"start": seg["start"],
"duration": seg["duration"]
})
return translated
For cost efficiency at scale, batch the texts into a single API call rather than one call per segment. But the structure stays the same: timestamps in, translated text out, timing untouched.
Exporting to formats your localization team can use
Once you have translated segments, convert them to a format your localization team or video platform expects. SRT is the safest default because every major platform and CAT tool supports it.
def ms_to_srt_time(ms: int) -> str:
hours, remainder = divmod(ms, 3600000)
minutes, remainder = divmod(remainder, 60000)
seconds, milliseconds = divmod(remainder, 1000)
return f"{hours:02}:{minutes:02}:{seconds:02},{milliseconds:03}"
def segments_to_srt(segments: list[dict]) -> str:
lines = []
for i, seg in enumerate(segments, start=1):
start = ms_to_srt_time(seg["start"])
end = ms_to_srt_time(seg["start"] + seg["duration"])
lines.append(f"{i}\n{start} --> {end}\n{seg['text']}\n")
return "\n".join(lines)
# Write out the German SRT file
srt_content = segments_to_srt(translated_segments)
with open("tutorial_de.srt", "w", encoding="utf-8") as f:
f.write(srt_content)
For web-embedded video, WebVTT is the better choice. The conversion is nearly identical; the main difference is the header and the timestamp separator.
def segments_to_vtt(segments: list[dict]) -> str:
def ms_to_vtt_time(ms: int) -> str:
hours, remainder = divmod(ms, 3600000)
minutes, remainder = divmod(remainder, 60000)
seconds, milliseconds = divmod(remainder, 1000)
return f"{hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}"
lines = ["WEBVTT", ""]
for seg in segments:
start = ms_to_vtt_time(seg["start"])
end = ms_to_vtt_time(seg["start"] + seg["duration"])
lines.append(f"{start} --> {end}\n{seg['text']}\n")
return "\n".join(lines)
If your team works with a professional translation memory system such as Trados or memoQ, convert the SRT to XLIFF using a tool like srt2xliff before sending files to translators. This lets them apply translation memory and glossaries to reduce cost on repeated phrases.

Putting the pipeline together
Here is a minimal end-to-end script that handles the full flow for a list of videos and target languages:
import httpx
from openai import OpenAI
API_KEY = "YOUR_API_KEY"
OPENAI_KEY = "YOUR_OPENAI_KEY"
openai_client = OpenAI(api_key=OPENAI_KEY)
TARGET_LANGUAGES = ["de", "fr", "pt-BR"]
VIDEO_IDS = ["VIDEO_ID_1", "VIDEO_ID_2"]
def run_pipeline(video_ids, target_languages):
for video_id in video_ids:
# Fetch source transcript
source = fetch_transcript(video_id)
source_lang = source["language"]
source_segments = source["transcript"]
for lang in target_languages:
# Try native track first
native = fetch_transcript(video_id, lang=lang)
if native["language"] == lang:
segments = native["transcript"]
else:
# Translate from source
segments = translate_segments(source_segments, lang)
# Export to SRT
srt = segments_to_srt(segments)
filename = f"{video_id}_{lang}.srt"
with open(filename, "w", encoding="utf-8") as f:
f.write(srt)
print(f"Written: {filename}")
This pattern is also a good foundation for the kind of content repurposing workflows covered in Turn YouTube Podcasts into Blog Posts Automatically, where the same transcript JSON feeds multiple downstream processes.
Quality checks and gotchas with machine translation
Machine-translated captions ship with a few predictable failure modes. Here is what to check before sending files to review or uploading to a video platform.
Text expansion. German text runs roughly 30% longer than equivalent English. Spanish runs 20 to 25% longer. When translated text spills past a segment's duration, subtitles overlap on screen. After translation, scan for any segment where the character count exceeds 42 characters per line and manually review the timing on those blocks.
Encoding. Write every output file as UTF-8. A mismatch between the file encoding and the player's expectation will corrupt accented characters and CJK scripts silently. The open(..., encoding="utf-8") call in the examples above is not optional.
Auto-caption quality. YouTube's auto-generated captions are produced by speech recognition and are not copy-edited. Mistranscriptions in the source compound when passed through translation. For any content that will represent your brand, have a native speaker spot-check a sample of the highest-traffic videos before enabling machine-translated captions at scale.
Regional variants. pt-BR and pt-PT are different tracks. So are zh-Hans (simplified) and zh-Hant (traditional). Requesting the wrong variant with the lang parameter will return a fallback rather than the intended locale. Be explicit about regional codes wherever the distinction matters.
Timing drift. If you are applying an external translation and the translated text is significantly shorter or longer than the source, consider re-segmenting rather than using the original timestamps. A sentence that takes three seconds to say in English may take four seconds for a native German speaker to read comfortably.
For a deeper look at how auto-generated and manual caption tracks differ in quality and availability, YouTube Captions API: Auto vs Manual Captions Explained covers that trade-off in detail.
Next steps
The pipeline above covers the core loop. From here, the natural extensions are:
- Cache fetched transcripts to avoid re-fetching on subsequent runs. Credits only charge on successful calls, but a local cache speeds up iteration during development.
- Add a language detection step before the translation call, using a library like
langdetect, to handle videos where thelanguagefield in the response is unreliable. - For large channel backlogs, use
/api/v2/channel/videosto enumerate all uploads first, then pass the video IDs into the bulk transcript endpoint in batches of 50.
The YouTube Transcriber API handles proxy rotation, retry logic, and anti-bot challenges on the server side, so your pipeline code stays focused on business logic rather than infrastructure. You get 100 free credits without a credit card to test the full flow. The API documentation has the complete parameter reference and response schemas.