◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-07-11Advanced

Stop Your Captions From Dancing: Splitting Live Translate Into Committed and Volatile Text

Pasting Gemini 3.5 Live Translate interim results straight to the screen makes captions flicker until they are unreadable. Here is a stabilizer that separates a committed prefix from a volatile tail and never rewinds, with working code and measured numbers.

Gemini Live API6Live Translatecaptionsrealtimedesign

✦ Premium Article

For a while I obsessed over the latency of voice translation and left the more basic question — is it actually readable? — for later.

I run a few calm, healing-style apps as a solo developer, and I once tried wiring up audio-to-audio conversation translation with the Live API. The audio came back more smoothly than I expected. But the moment I overlaid that same translation as an on-screen caption, the text started rewriting itself in tiny jerks and I could no longer follow it. Before the speaker had even finished a sentence, the trailing word would flip from "light" to "lights," reordering itself. The reader's eyes get yanked all the way back to a sentence opening that should have been settled long ago.

This article is an operations note focused not on the audio, but on how to render the text. Instead of pasting every Live Translate interim result as the latest full string, we split it into a part that has settled and a part that is still moving. That single change made the captions far easier to read.

Why pasting the latest string is unreadable

Live Translate streams the translation back. The tricky part is that what you receive is not a growing, confirmed sentence but a hypothesis that gets rebuilt each time. Every time new audio arrives, the model rewrites the tail — and sometimes re-translates a word or two just before it.

The naive implementation pipes the whole translation into the caption component on every message.

session.on("message", (msg) => {
  const text = msg.translation?.text ?? "";
  setCaption(text); // repaint the entire string every time
});

With this, changing a single trailing character re-lays-out the entire caption. On my setup, interim results arrived roughly 15–25 times per second. That means the caption was being fully recomposed a dozen-plus times a second. The opening words are not going to change anymore, yet every reflow nudges their position slightly, and the eye never settles. That is the "dancing caption" problem.

Separating the committed prefix from the volatile tail

Let's flip the mental model. Instead of treating the translation as one string, we hold two: a committed prefix that never moves backward, and a volatile tail that may still be rewritten.

RegionNatureHow to render
committedOnce settled, never allowed to regressFixed, in the normal style
volatileMay be rewritten as audio arrivesDimmed or underlined to signal "provisional"

The committed region only ever gets appended to. Only the volatile part is rebuilt each frame. Now reflow is confined to a narrow tail, and the reader's eyes rest on a stable opening. Even if the tail corrects "light" into "lights," that fix happens inside the volatile region, so the committed text never shows a wrong word.

The open question is where to draw the line. Commit early and reflow drops, but a later correction forces you to rewind what you committed. Commit late and it is safe, but the caption feels sluggish to settle. There is a trade-off here.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A working CaptionStabilizer that separates committed prefix from volatile tail, with zero backward rewrites verified on a replay
✦A trailing-window approach for translating into spaceless languages like Japanese, where word boundaries do not exist
✦A tuning guide for balancing flicker against latency, derived from measured update rates and hold times
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-10-16
The Day /models Returns 200 but generateContent Returns 403 — Decide Your Triage Order Before It Happens
A triage order for the case where listing models succeeds with 200 but generation fails with 403 permission_denied. Includes a small diagnostic script that checks key, project, and billing in turn, why you should not retry a 403, and how to assemble what support needs.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.
◈ API / SDK2026-10-08
When a Deprecation Has No Shutdown Date: Re-Picking an Image Model on Three Conditions
gemini-nano-banana-2.1 went GA on October 6, and gemini-3.1-flash-image was deprecated with no shutdown date. Here is a table and a short script for choosing a replacement by resolution, reference images, and search grounding.