Original vs Translated YouTube Captions: How to Tell Them Apart
An auto-translated YouTube caption is not a caption track. The player builds one by adding tlang to a real track's URL, so the request keeps lang=en and returns Turkish. The spoken language lives in audioTracks[defaultAudioTrackIndex], and that field, not the viewer's selection, is what a caller should select on.
TL;DR
An auto-translation is not a track. The player appends tlang to a real track's URL, so the request keeps lang=en while the body is Turkish. Read the spoken language from audioTracks[defaultAudioTrackIndex], strip tlang from every fetch, and prefer a manual track over the automatic one. Measured on 19 September 2026 on aircAruvnKk.
Try it on your own script
Paste your draft below. You get your hook, structure, and pacing scores, a script-level attention-risk map, and the single biggest issue quoted from your own lines. Free, no login.
Free · No login · See a sample audit first if you prefer.
Key Takeaways
- An auto-translated YouTube caption is not a track: the player builds one by adding tlang to a real track's URL
- The request keeps lang=en while the body is Turkish, so a scraper that trusts the lang parameter stores Turkish text under an English label
- Read the spoken language from audioTracks[defaultAudioTrackIndex], strip tlang from every fetch, and prefer a manual track over the automatic one
- The player listed 31 caption tracks for aircAruvnKk on 19 September 2026, ordered alphabetically so Arabic came first, and pre-selected the auto-translation labelled English to Turkish for a viewer whose interface language was Turkish
- yt-dlp reported 162 automatic_captions keys for that video, of which exactly one key ended in -orig and 155 carried tlang= in their URL
Key Statistics
- •The player listed 31 caption tracks for aircAruvnKk on 19 September 2026, ordered alphabetically so Arabic came first, and pre-selected the auto-translation labelled English >> Turkish for a viewer whose interface language was Turkish.
- •The translated request kept lang=en and added tlang=tr, and the body it returned was Turkish, so a scraper that trusts the lang parameter stores Turkish text under an English label, measured 19 September 2026.
- •yt-dlp 2026.08.19 reported 30 creator-uploaded tracks and 162 automatic_captions keys for aircAruvnKk on 19 September 2026, of which exactly one key ended in -orig and 155 carried tlang= in their URL.
- •After the selection rule was applied, a pull made while the player showed English >> Turkish returned English with isOriginalLanguage: true and source: manual; a language that existed only as a translation returned the error code NO_TRACK.
- •The public transcript API returned 3,368 words, 18,543 characters, 180 wpm, source asr and language en for aircAruvnKk, from prepublish-be/internal/domain/service/video_transcript_service.go.
In This Guide
- Original vs Translated YouTube Captions: How to Tell Them Apart
- How to tell original language captions from translated YouTube captions
- What the tlang parameter does to a caption URL
- The measured example: 31 tracks, Arabic first, and Turkish behind an English label
- The selection rule Prepublish uses, in order
- The same distinction in yt-dlp output
- A detection checklist for a pipeline that may be collecting translations
- What this does not do
- What changes for script analysis: word counts, pacing and quotes
Original vs Translated YouTube Captions: How to Tell Them Apart
An auto-translated YouTube caption is not a track. The player builds one by adding tlang=<code> to a real track's URL, so the request keeps lang=en while the body is Turkish. Read the spoken language from audioTracks[defaultAudioTrackIndex], strip tlang from every fetch, and prefer a manual track over the automatic one. Measured on 19 September 2026 on aircAruvnKk.
How to tell original language captions from translated YouTube captions
Three signals separate a real track from a translation, and all three are visible before you fetch anything.
The first is the URL. A real track's URL carries lang and, for auto-generated tracks, kind=asr. A translation carries lang, kind=asr and tlang. The lang value names the track the translation was derived from, not the language of the body, so two requests that both say lang=en can return two different languages.
The second is the list. captionTracks[] in ytInitialPlayerResponse holds real tracks only, and an auto-translation is not an entry there. Counting the array gives the number of real tracks and says nothing about how many translations exist.
The third is the player's current selection. That value is a viewer preference set from the interface language, so on a Turkish interface it preselects the Turkish translation of an English video. A client that reads the current selection as the video's language adopts a UI setting as a fact about the audio.
| Signal | What it looks like for a real track | What it looks like for a translation |
|---|---|---|
| URL parameter | lang=en&kind=asr | lang=en&tlang=tr&kind=asr |
Presence in captionTracks[] | present | absent |
| Player selection | the track itself | the track plus translationLanguage on the entry |
| yt-dlp key | en or en-orig | tr |
| Body language | English | Turkish |
What the tlang parameter does to a caption URL
tlang is a request-time instruction, not a stored asset. The player takes a track that exists, asks YouTube to machine-translate it into the named code, and renders the result. The track itself is unchanged, and no new track is created.
Because the translation is derived at fetch time, it inherits the source track's identifiers. The lang parameter, the kind=asr marker and the video id all survive into the translated request, and only tlang says what the body will be.
Measured on 19 September 2026, the translated request for aircAruvnKk kept lang=en and added tlang=tr, and the body it returned was Turkish. A yt-dlp probe on the same day resolved the key tr to lang=en&tlang=tr&kind=asr and the key de to lang=en&tlang=de&kind=asr. The parameter changes the body while leaving every field a naive parser reads unchanged.
The measured example: 31 tracks, Arabic first, and Turkish behind an English label
aircAruvnKk is 3Blue1Brown's "But what is a neural network?", a video spoken in English. Measured on 19 September 2026:
| Fact | Measurement |
|---|---|
| Caption tracks the player listed | 31 |
| First track in the player's own list order | Arabic, because the list is alphabetical rather than spoken-language first |
| Track the player pre-selected for a viewer whose interface language was Turkish | English >> Turkish, an auto-translation |
| The URL of that translated request | keeps lang=en and adds tlang=tr |
| The body of that request | Turkish text |
| What a scraper that trusts the parameter reports | language en, returning Turkish |
The 31 figure reconciles with an independent read. On the same day, yt-dlp 2026.08.19 reported 30 keys under subtitles, the creator-uploaded set for that video, plus a single auto-generated key, en-orig. The pre-selected translation appears in neither count.
Nothing about the translated request is malformed. It returns HTTP 200, the JSON3 body parses, the segments carry text, and the language field says en. A pipeline with no tlang check stores Turkish prose in an English row.
The alphabetical order is the second half of the trap. A caller that takes the first entry, or that walks the list until it finds a language it recognises, can select Arabic on an English video without that entry being wrong.
The selection rule Prepublish uses, in order
Two implementations exist, one in the browser extension and one on the server, and both follow the same order.
The extension resolves the spoken language first, then chooses a track. spokenLanguage in prepublish-extension/src/content/youtube-main.js never consults the viewer's caption selection, because that selection is set from the interface language. pickTrack then applies this order:
- A track in the spoken language, preferring a manual track over the auto-generated one.
- If the spoken language has no track, any manual track in the list.
- Then the auto-generated track, if one exists.
- Then the first entry in the list.
- The viewer's current selection applies only as a tiebreaker between tracks already in the chosen language, such as
enagainsten-US. A selection that carriestranslationLanguageis skipped, because selecting it re-applies the translation. - The chosen entry is copied with
translationLanguagedeleted before it is handed to the player, and the result is labelled by comparing the chosen code against the spoken language rather than by reporting what was asked for.
The server applies the same idea to yt-dlp's keys, in pickOriginalTrack in prepublish-be/internal/infrastructure/youtube/channel_dlp.go:
- The creator's manual track for the video's declared language.
- The ASR key ending
-orig, which YouTube marks as the original audio and which exists even when the video declares no language. - The ASR key for the video's language, accepted only when its URL carries
kind=asrand notlang=anywhere. - If none of those match, no track. The function returns false rather than substituting a translation.
Keys are walked in sorted order, so the same video yields the same track on every run. baseLanguage reduces a tag to its base, so en-US matches en and es-orig still matches a video declared as es. That function backs the stored result at the transcript endpoint, which reports the chosen language code, the source as manual or asr, and whether the chosen track is the spoken one.
How to find the spoken language from audioTracks[defaultAudioTrackIndex]
The player describes the audio separately from the captions, and that description is the field to trust. In the player response, captions.playerCaptionsTracklistRenderer.audioTracks holds the audio tracks and defaultAudioTrackIndex names the one playing.
const renderer = playerResponse
.captions.playerCaptionsTracklistRenderer;
const audio = renderer.audioTracks[renderer.defaultAudioTrackIndex];
const spoken = audio.audioTrackId || audio.id || audio.languageCode;
An audio track also names the caption tracks recorded in its language, which gives a second read of the same fact. audio.defaultCaptionTrackIndex and audio.captionTrackIndices[0] both index into captionTracks, and that entry's languageCode is the language the player associates with the audio.
The extension falls back in a fixed order when the audio entry is absent or carries no parseable code. It tries videoDetails.defaultAudioLanguage, then the audioTrack.id of the format flagged audioIsDefault in streamingData.adaptiveFormats. After that come the language of the asr track, and then the single shared language when every listed track has one. The last two are inferences rather than declarations, and the code names them as such.
The same distinction in yt-dlp output
yt-dlp exposes the two kinds of track under separate keys, which makes the check cheap.
| Where it appears | What it is | How to spot it |
|---|---|---|
subtitles["en"] | a track the creator uploaded or approved | any key under subtitles |
automatic_captions["en"] | speech recognition of the spoken audio | URL carries kind=asr, no tlang |
automatic_captions["en-orig"] | the same recognition, explicitly marked as the original audio | key ends -orig |
automatic_captions["tr"] | a machine translation of the English track into Turkish | URL carries tlang=tr |
Measured with yt-dlp 2026.08.19 on 19 September 2026 for aircAruvnKk: 30 keys under subtitles, 162 keys under automatic_captions, exactly one key ending -orig (en-orig), and 155 keys whose URLs carry tlang=. Of the six keys left, the plain en entry resolves to a timedtext URL carrying kind=asr, and the other five resolve through a manifest URL with no lang or kind parameter on it.
The flag that pulls only the original is --sub-langs with the -orig key, alongside --write-auto-subs:
yt-dlp --skip-download --write-subs --write-auto-subs \
--sub-langs "en-orig" --sub-format vtt \
"https://www.youtube.com/watch?v=aircAruvnKk"
That command wrote a single file, aircAruvnKk.en-orig.vtt, 171,557 bytes, whose header reads Language: en, and it wrote nothing else.
The plain en key under automatic_captions resolves to the same track. Requesting it wrote aircAruvnKk.en.vtt at 171,557 bytes, and the two files were byte-identical by SHA-256 (4a78a5252564f51a38085db4d678b2edc08ba03956f2be81d86f7be218ae797e). The -orig suffix is the explicit marker, not a different rendition.
To find the key without guessing it:
yt-dlp -J --skip-download --ignore-no-formats-error "$URL" \
| jq -r '.automatic_captions | keys[] | select(endswith("-orig"))'
That returns en-orig for this video. To count how many translations a video offers, which is the number of requests that would have produced wrong-language text:
yt-dlp -J --skip-download --ignore-no-formats-error "$URL" \
| jq '[.automatic_captions | to_entries[]
| select(.value | any(.url | test("tlang=")))] | length'
That returns 155 for this video.
The filename will not warn you. Asking for tr makes yt-dlp announce Writing video subtitles to: aircAruvnKk.tr.vtt, the same <video id>.<language>.vtt shape a genuine Turkish track would produce, and nothing in the name or in the body marks it as a translation. On this test host the body fetch for that key returned HTTP 429, because yt-dlp 2026.08.19 had no impersonation target installed. The key-to-URL mapping and the destination naming came from the tool's own output.
A detection checklist for a pipeline that may be collecting translations
Four checks, run against stored rows rather than against live requests.
One. Does the row store the URL it fetched? A stored caption URL containing tlang= is a translation, whatever the language column says. A pipeline that keeps only the body and the language has discarded the only field that answers the question.
Two. Does the stored language match the video's declared spoken language? The declared value appears as videoDetails.defaultAudioLanguage in the player response and as the language field in yt-dlp's info dict. A stored en for a video declared en proves nothing, because a translated request also says en. A stored language that varies between runs for one video is unambiguous.
Three. Does the body match the language you recorded? Run a script check over stored text and look for the mismatch. A declared en row whose body is mostly non-ASCII letters is a translation. Treat the result as a signal rather than proof, because short captions, song lyrics, code-switching and proper nouns produce false positives.
Four. Does the row have anywhere to record the answer? Two fields settle the question: the track kind, either asr or manual, and a boolean for whether the chosen track is in the spoken language. A pipeline that records neither cannot distinguish a translation from an original after the fact, and the absence of both fields is the finding.
| Check | What it catches | What it misses |
|---|---|---|
Stored URL contains tlang= | every translated fetch, exactly | rows stored before URLs were kept |
| Stored language against declared language | wrong-language rows, and runs that disagree | a translation whose code matches the declaration |
| Script check on the body | wrong-language text where the label is right | short captions, mixed scripts, proper nouns |
| Track kind and spoken-language flag present | prevents the problem from recurring | existing rows |
What this does not do
The rule selects a track. It does not create one, and it cannot recover text that does not exist.
A video whose only captions are machine translations has no original-language track to return. The extension reports that case as NO_TRACK and the server as HTTP 422 with the message "no original-language captions", because returning the translation is worse than returning nothing. A video with no captions at all is the same case with a different cause.
-orig is a useful marker, not a guarantee. The video above carried one such key among 162. A video can carry none even when an original track exists under a plain language code, which is why the third rule in pickOriginalTrack also accepts an asr URL with no tlang. That rule rests on the absence of a marker, and absence is weaker evidence than a positive label.
audioTracks[defaultAudioTrackIndex] is a player field, not a documented contract. It can be missing, and the fallback chain that follows ends in inference. The last fallback concludes the spoken language from the fact that every track shares one language, which is wrong for a video whose captions are all in a language nobody speaks.
Nothing here changes what YouTube serves to whom. It changes which track a caller asks for, and what the caller stores afterwards.
What changes for script analysis: word counts, pacing and quotes
When a translated body is analysed, the translation is what gets measured. The numbers are not wrong, and they are about a text nobody spoke.
Word count is the clearest case. A translation has a different number of words than the speech it was derived from, so a count taken on it is the count of the translation. Speaking rate inherits the same shift, because it is words divided by duration. Prepublish's speaking-rate dataset reports a median of 181 wpm across 349 measured videos, a 25th percentile of 160 and a 75th of 201, with education at 179 wpm over 89 videos and finance at 190 over 40 (prepublish-fe/lib/seo/wpm-data.ts). Every one of those figures divides transcript words by video length. Substitute a translation and the arithmetic still runs, on a different text over the same audio.
Pacing shifts too. Cue timings come from the spoken performance while the words inside each cue come from the translation, so words per cue, sentence length and section length describe the translated text laid over the original timing. A pause the speaker used for emphasis lands in the middle of a clause the translator wrote.
Quote extraction is where the consequence is hardest to notice. A quote pulled from a translated transcript is a translation of what the creator said, and a grounding check that compares the quote against that transcript confirms the translation. Prepublish's channel skill pipeline verifies every quote a model returns against the supplied transcripts and removes any it cannot find, recording the removal in the provenance block (ai.ValidateChannelSkillQuotes, ai.StripChannelSkillUnverifiedQuotes). The check catches a hallucinated quote and cannot catch a faithfully translated one. The code comment in the server path says that quoting a translation back as the creator's voice would be a fabrication.
Run the word count and the pacing numbers on the original track. The script word counter counts whatever text it receives and cannot know where that text came from, and the opening 30 seconds inherits its pace and pause figures from the same source. A translated row is unmeasured rather than weakly measured.
Frequently asked questions
Why do my YouTube captions come back in the wrong language?
The player has an auto-translation switched on. YouTube generates translations by appending tlang to a real track's URL, so the request still carries lang=en while the body is Turkish. A client that reads the lang parameter, or that reads the language the player currently shows selected, records the wrong language for correct text. Read the spoken language from audioTracks[defaultAudioTrackIndex] instead, and delete tlang from every caption URL before fetching it.
What does the tlang parameter do in a YouTube caption URL?
It asks YouTube to machine-translate the requested track into the code it names. In a measured request on 19 September 2026, the URL kept lang=en and added tlang=tr, and the returned body was Turkish. Removing tlang returns the track named by lang. Passing tlang with a language YouTube cannot translate into returns a failed request rather than a translation, which is why a caller should treat a translation as a derived stream and never as a track.
How do I get the original language transcript with yt-dlp?
Ask for the key that ends in -orig and write only that one. On 19 September 2026, yt-dlp 2026.08.19 with --write-auto-subs --sub-langs "en-orig" --sub-format vtt wrote aircAruvnKk.en-orig.vtt, 171,557 bytes, headed Language: en. Without the -orig suffix a plain language key can resolve to a translation, and yt-dlp names the output file after the key either way, so the filename alone does not tell you which one you received.
What does kind=asr mean in a YouTube caption track?
It marks a track YouTube generated by recognising the video's speech rather than one a person wrote or uploaded. In the player response the field appears as captionTracks[].kind with the value asr, and in yt-dlp the same marker appears as kind=asr inside the track URL. A translation derived from an ASR track keeps kind=asr and adds tlang, so kind alone does not separate an original from a translation. The absence of tlang is the part that matters.
Does YouTube count auto-translated captions as a caption track?
No. captionTracks[] in the player response holds real tracks only, and a translation is not one of them. Measured on 19 September 2026, the player listed 31 tracks for aircAruvnKk while adding one auto-translation produced a request that carried tlang and appeared nowhere in that list. This is why counting entries in captionTracks[] gives a number of real tracks, and why a translation has to be detected from the URL rather than from the list.
Why does an English video show Arabic captions first in the list?
The list is alphabetical by language code, not sorted by the language the video is spoken in. Measured on 19 September 2026 on aircAruvnKk, a video spoken in English, the first entry in the player's own list order was Arabic. A pipeline that takes captionTracks[0] therefore picks Arabic from an English video and has no way to notice, because nothing about that entry is malformed. Sorting by spoken language is the caller's job.
Can a caption URL be trusted because it carries lang=en?
No. The lang parameter names the track the translation was derived from, not the language of the body. The translated request measured on 19 September 2026 kept lang=en and added tlang=tr, and returned Turkish. Two requests with the same lang value can return two different languages, which is why the parameter is not a reliable field to store as a language column. Store the spoken language you selected on, and refuse to store a URL that carries tlang.
What should a transcript pipeline do when the original track is missing?
Return nothing, and say so. When a video carries no captions at all, or carries only machine translations, there is no original-language text to quote, and substituting a translation changes what every later measurement means. Prepublish reports the second case as an error rather than a result: a request naming a language that exists only as a translation returns NO_TRACK in the extension and HTTP 422 with no original-language captions on the server, source prepublish-be/internal/domain/service/video_transcript_service.go.
Related Guides
Free tools to put this into practice
Hook Analyzer
Score your first 1-3 sentences
Title Analyzer
Writing rubric + 5 rewrites
Words to Minutes
Script length calculator
Word Counter
Count, reading time, duration
Want to see how this reads on real channels? Browse the channel breakdowns. Each one compares script patterns across a channel's own higher-viewed and lower-viewed uploads, quoted from the transcripts.
See where your next script leaks viewers
Paste your script, get your scores and the biggest leak for free. No login.