A friend of mine doesn't speak English. I do most of my podcast listening in English. Every so often I hit an episode worth passing on — an interview, some strange little history segment — and I want to send it to her, and I can't. A summary isn't the episode. A translated transcript isn't something you listen to on a walk.
It turned out I'd been sitting on the pieces to fix this for months. This is the architecture I landed on, and the one design rule that shaped everything else. No code — just the shape of the thing and the decisions that mattered.
I'm building a personal audio library as a side project. One thing already shipped was AI-narrated audiobooks: take a manuscript, translate it if needed, run it through a neural text-to-speech voice, stitch the result into a real audio file. Two managed services doing the heavy lifting — machine translation on one side, neural TTS on the other — with my own orchestration in between.
A podcast episode is, structurally, a much shorter audiobook. That's the whole thing. I didn't need to build "podcast translation." I needed to point a pipeline I'd already written at a different kind of input.
That reframing is the most useful architectural move I know: before building a feature, check whether it's a new shape or just a new input to a shape you already have. This was the second.
Podcast RSS feed
│
▼
Find <podcast:transcript> ──► no transcript? ──► feature not offered
│ (hard stop — see below)
▼
Fetch the podcaster's own transcript text
│
▼
Machine translation (per-character, managed service)
│
▼
Neural TTS (per-character, managed voice)
│
▼
Stitch → single playable audio file
│
▼
Store in the requester's own library (household-scoped)
Everything interesting is in two places: the hard stop near the top, and the scope at the bottom.
The obvious architecture is: transcribe the audio with speech-to-text, translate that, narrate the result. I deliberately did not do that, and it's the single decision I actually agonized over.
Speech-to-text makes mistakes, and translation compounds them. A mis-heard word becomes a mistranslated sentence becomes a confidently narrated line that is simply wrong — with nothing in the output pointing back at where it broke. For a toy, that's an annoyance. For something a person might rely on to understand what someone actually said, it's a trust problem, and it gets worse the more the feature is used.
So the rule is: this only works on episodes that publish a transcript. Podcasting 2.0 added a <podcast:transcript>
tag to the RSS spec a few years ago, and a meaningful and growing minority of shows already attach one. If a transcript exists, I translate the podcaster's own published words. If it doesn't, the feature isn't offered for that episode. No guessing, ever.
Architecturally this is a quality gate placed at the input, not the output. Instead of building elaborate confidence-scoring and error-handling downstream to cope with bad STT, I refuse the bad input at the door. The system can only be asked to do the thing it can do well. That single constraint deleted an entire category of downstream complexity — and it's why I trust the output enough to ship it.
Managed translation and TTS both bill per character, so the unit economics are legible: cost is translation-per-character plus narration-per-character, marked up enough to be sustainable, and capped so it never exceeds the up-front quote.
A real measurement, not a round marketing number: one episode of a (faintly ridiculous) British royal-gossip podcast that happened to be in my test feed — 8,570 characters of transcript, English to German — landed at 90 cents. A typical 5–15 minute episode comes out around a dollar.
The architectural point: because the price is derived from the transcript length before any paid API call runs, I can show the user the exact cost and get consent before spending anything. No surprise bills, no "estimated." The quote is the cap.
I timed it from the database row's own timestamps rather than a stopwatch, because I wanted a number I could stand behind: a 9-minute episode, translated, narrated, and assembled into a playable file, in 93 seconds.
That number changes the UX category. At 93 seconds, "translate this episode" stops being a batch job you plan around and becomes something you kick off while still looking at the episode list.
Two scope decisions are deliberately baked into the architecture:
That personal scope isn't a limitation I'm apologizing for; it's the line that makes the feature defensible to build at all.
Four things generalize well beyond this:
The result is what I've started calling an 80% solution, on purpose: one voice, transcript-only, personal-scope. If the alternative is not understanding the episode at all, 80% is a genuinely good outcome — and it's the difference between hearing something today and never hearing it, because nobody commissions a real translation for one podcast episode.
I write about building own.audio, a personal library for audiobooks, music, and podcasts you actually own. It's a pre-launch side project; the fuller write-up of this feature, with screenshots, is on the blog. The product-design side of this same decision — why shipping something deliberately imperfect was the right call — is a separate piece.