Files
UmbraAudio/specs/28-ui-language-selection.md
B.D. Murphy 6b85e48b55 Multilingual samples, multi-pass translation design, UI language plan, renderer
Operator direction: English is the source and stays editable; translations
are confirm-only.

- samples/translations/: Deal Hunter sample chapter 1 in all 10 Qwen3-TTS
  languages, named deal_hunter_sample_ch1_<LANG>.json. Single-pass LLM
  drafts, labelled as such in every file - they are the failure mode the
  multi-pass design exists to fix, and they double as a bad-case corpus for
  the Pass 4 critic.
- scripts/build_sample_translations.py, with a validator that refuses to
  write on stray Latin residue, duplicated utterances or a wrong count. It
  caught three defects in my own draft before it would ship.
- specs/27-translation-pipeline-multipass.md - six passes, the point being
  that Pass 4 must be a separate critic call, not the same call that drafted.
- specs/28-ui-language-selection.md - confirm-only translations, English
  editable, selector scoped to the chapter page, language named on the page
  because the cookie is per browser.
- scripts/render_lang_test.py - renders the translations with each speaker's
  own voice into <project>/Lang_test/, plus a stitched file per language.
  Dry run resolves all 12 utterances x 10 languages; 11 of 12 use a cached
  ICL prompt, Trader Romano falls back to ref_audio.

The renderer resolves display names through the alias map rather than by
munging strings: the translations say 'Kainda' and the speaker record says
'Kainda Walters Foster Murphy'. String-munging silently produced no voice for
Kainda in the first draft - the same failure mode as the speaker-review
audit.

NOT DONE: no audio rendered. Qwen3-TTS generation on this machine is stalled
- /v1/tts accepts the connection and never completes (zero POSTs logged across
17 minutes after a clean restart), while /v1/voice-prompt and /load return 422
immediately, so requests do reach the handler and the block is inside
generation. Swap is at 16GB of 17.4GB. Left as a known blocker rather than
worked around.

Suite: 721 passed, 8 pre-existing.
2026-10-12 08:07:50 -07:00

5.0 KiB

Language selection in the review UI - design

Status: design, not implemented. Written 2026-10-12.

What the reviewer needs to do

Select a project. Work in English by default, because that is the source language and everything else derives from it. Then, for each of the other languages, confirm the translated utterances are right — check meaning, check the speaker is still right, check nothing was dropped.

The language is a property of the review session, not of the project. English is the source; every other language is a translation of English, so English can never be edited while viewing a translation without an explicit warning.

The decision that shapes everything else

Is the reviewer looking at English source text, or at a translation?

Both are needed, and they are not the same view:

  • In English, the reviewer edits. This is the master.
  • In another language, the reviewer confirms. Editing a translation would fork the source of truth, and nothing would tell the two apart later.

So in a non-English language the text fields are read-only, and the affordances are accept / flag, not edit. This is the single most important rule in the design and it is what stops the 15 languages drifting apart.

Proposed placement

A language selector belongs on the chapter audio page, not only in a global nav. Reasons:

  • The reviewer works chapter by chapter. A selector at the top of the page they are already on removes the "go somewhere, change a setting, come back" step that made the last round of UI fixes necessary.
  • The existing speaker and quality pages are project-scoped, and the project is already a cookie. Language is the same kind of scope and should behave the same way.
  • A global nav selector invites the reviewer to believe they changed something project-wide when they only changed what they can see.

Concretely, on /chapters/{id}:

Chapter 14 — Salvage          [ English ▾ ]   ● English (source)  ○ Review mode

Selecting a language swaps the utterance table to that language's rows. English is marked source; the rest are marked translation and show progress: 812 / 1874 confirmed.

State and persistence

Language is a cookie alongside active_project, so it behaves like the project selector already does and survives a reload. Two consequences worth stating:

  • It is per browser, not per user. Two browsers will show two different languages, the same failure already hit with active_project (which is why the speakers page now names the book it is listing). The chapter page must therefore also name the language it is showing, in the same way.
  • The selected language must never be inferred from content. There is no auto-detect, and no "guess the language from the text". An explicit selector that can be wrong is far better than a correct guess the reviewer cannot see or override.

Confirming a translation

Per utterance, in a non-English language:

Action Meaning
Accept translation is correct; speaker and meaning both fine
Flag something is wrong - opens the English source beside it

Flagging must show the English next to the translation. A translator reporting "this is wrong" is nearly useless; the reviewer needs to judge it. Two-column view, source left, translation right, diff only where flagged.

Bulk actions per chapter, because a reviewer will not click 1,874 times: Accept all unflagged in this chapter; Flag all unflagged for a given speaker.

What must be shown, and why

  • Progress per language. Without it there is no way to know whether Spanish is 4% or 94% reviewed. This is the same mistake as the speaker page showing a count with no denominator.
  • Flag reason. Wrong meaning, wrong speaker, mistranslated term, missing, extra, or "does not sound like this character". The last one is a voice question, not a language question, and it belongs to Pass 3.
  • Source utterance id. Every translated row must carry the English utterance index. A translation that cannot be traced back cannot be checked, and cannot be re-translated when the source changes.

Dependencies

This cannot be built until the following exist, because the view has nothing to show without them:

  1. Translated utterance rows keyed to source utterance index (the export format already produced by build_sample_translations.py is a candidate shape).
  2. A per-language confirm/flag record with an author and a timestamp.
  3. A decision on whether flagging is per-utterance or per-(utterance, reviewer).

Open questions

  1. Does the reviewer confirm, or does a second person? A single reviewer checking a machine translation is a weak control, and worth knowing which one is intended before the data model settles.
  2. Should the English view show which utterances have already been flagged in other languages? Probably yes, but it risks pushing the reviewer toward English-first thinking.
  3. Is a language ever "retired" mid-project, leaving a partially confirmed translation that no longer matches the source?