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.
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:
- Translated utterance rows keyed to source utterance index (the export format
already produced by
build_sample_translations.pyis a candidate shape). - A per-language confirm/flag record with an author and a timestamp.
- A decision on whether flagging is per-utterance or per-(utterance, reviewer).
Open questions
- 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.
- 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.
- Is a language ever "retired" mid-project, leaving a partially confirmed translation that no longer matches the source?