main
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2b294d7fa4 |
Per-utterance language flag + a working language selector (spec 31)
The review UI's language dropdown did nothing: every selection kept rendering the English text. Two independent causes, both fixed. 1. The translations were never being read. The pipeline's `utterance_translations` table holds one row in Deal Hunter, and that row stores the *English* text under a `zh-Hans` code. The real translations live in the on-disk sample files under `samples/translations/`. `umbraaudio/translations.py` now resolves both sources and merges them by (chapter_id, utterance_index): the table wins where it holds a real translation, the sample files fill the gaps. A candidate whose text is identical to the source is a placeholder, not a translation, so it is ignored - otherwise the page would show English under a Chinese heading and claim it was translated. 2. The query that read them was broken. `language_routes.py` selected `u.index` and `u.speaker`; the columns are `utterance_index` and `speaker_name`, so every read raised `sqlite3.OperationalError: near "index": syntax error` and `/language-mockup` returned 500. It also opened `<project>/project.db` - a 0-byte decoy - instead of `<project>/.umbra/project.db`, the file that actually holds the data. Also in this commit: * The chapter review page honours `?lang=`, remembers the choice in a cookie (set by a middleware, so it survives on every page), and shows the selected language's *name* in the "has not been translated yet" message. * The page now shows the translation, not the source, and the edit box is bound to the source text so saving cannot overwrite the English with the Chinese. * The stray language selector that sat after `</html>` in base.html is gone; the chapters-list page's dropdown had zero options because the route never passed the language list. * The language tag is wired into generation: `utterance.language_code` (NULL = project source language) reaches `engine.generate()` and the staleness marker, so a retagged line is re-rendered instead of keeping English phonology behind an unchanged text. The marker records the language; a marker written before the tag existed means English, so the ~4,300 existing clips are not invalidated. * `project.source_language` (spec 31 section 13.1) records the book's own language, with a migration for existing project DBs. * A scene-break marker deliberately records no language: it is silence, identical in every language, and tagging it would claim a phonology the audio does not have. Tests: 883 passed / 8 failed - the same 8 pre-existing failures (2 test_pronunciation, 6 voice_profile_manager accent). 26 new tests cover the resolver and the generation wiring. ruff introduces no new findings. |
||
|
|
b63bc5bd0a |
Fix language selector being invisible; correct sample utterance types
The dropdown was in the HTML all along and still could not be seen. Three separate causes, found by measuring the rendered element rather than trusting that the markup existed: 1. Clipped off-screen. The h1 is a flex row with no wrap. Measured at an 800px viewport: the language span started at x=620 and ended at x=810, past the viewport edge, so only its first few pixels were painted. The project badge alone took 309px. Adding flex-shrink and truncating the badge did not fix it - there is genuinely no room in that row. The selector now gets its own bar directly under the header, and the project name ellipsises. Verified visible: x=181, right=341 in an 800px viewport, 34px tall. 2. Empty on every other page. base.html rendered the select from a `languages` variable that almost no page supplies, so the login page and the rest of the app showed a 38px-wide dropdown with zero options - effectively invisible, and a dead control. Pages now opt in by overriding a header_bar block. Verified /, /speakers, /chapters-audio and /login all still render. 3. Browser cache made my own screenshot lie. It showed a stale Japanese string containing the word "decisiveness" that is in no file on disk. A cache-busted fetch confirmed the server is clean. Worth remembering: a screenshot taken through a warm cache is not evidence. The screenshot also exposed a data bug. All 12 sample utterances carried type "narration" - including Kainda, Duchess Victoria, Deal Hunter, Nanny and Trader Romano, who are all speaking. Every row rendered a narration badge and the file told UmbraTrans that six characters were not speaking. SHAPE in build_sample_translations.py now marks 6 dialogue / 6 narration, and the generated JSONs are regenerated to match. Also removed a misleading issue. It reported "12 rendered clips have no staleness marker" as a translation problem; the markers are absent because render_lang_test.py does not write them, so it was sending the reader after a defect that does not exist. Replaced with the honest one: only 12 of 4,319 translated and none confirmed - sample only, not publishable. Suite: 721 passed, 8 pre-existing. ruff clean on changed regions. |
||
|
|
2f46be9520 |
Sample translations: formal -> casual register, all 10 languages
The Korean render sounded formal because the Korean translation was in
합쇼체 (-입니다/-합니다/-습니다) - the written formal register, like a
news anchor. Every line used it.
Korean politeness is grammatical, not stylistic: it lives in the verb
endings. 합쇼체 -습니다, 해요체 -이에요/-예요, 해체 -아/-어 are different
morphemes, so no TTS instruction can convert them. The fix has to be in
the text. The same applies to Japanese です/ます and to the T/V languages,
where Sie->du changes the words themselves.
This was not a Korean-only problem. All 9 translated languages were in
formal register while the English source is neutral and friendly:
DE 12 markers FR 13 PT 5 ES 4 IT 3 RU 3 JA polite KO 합쇼체
ZH 3 honorific 您 EN 0 (source)
Applied:
KO 합쇼체 -> 해요체. 해체/반말 deliberately NOT used: speech level is
social, and Nanny speaking 반말 to Kainda would be wrong.
DE/FR/ES/IT/PT Sie/vous/usted/Lei/você -> du/tu/tú/tu/tu
RU kept вы; ты is too intimate for narration. Softened instead.
JA polite-but-warm, fewer ください
ZH 您 -> 你
Re-verified against the formal-pronoun probe (not the earlier loose one,
which flagged possessives: su/suo/seu are 'his', and Japanese です is the
copula while います is existential - both register-neutral).
This is Pass 3 from specs/27 (register and voice consistency), which was
specified and then never applied.
Also fixed a drift trap: build_sample_translations.py wrote to
scripts/translations, a directory nothing reads and nothing tracks, while
the tracked record is samples/translations and the renderer reads
<project>/samples/translations. One regeneration silently left both stale.
It now writes the repo copy and, with --project, the renderer's copy in
the same run.
Re-rendered 120 clips, 0 failed. All 120 bitwise-different from the
formal render, confirming the new text was synthesized rather than a
replay. Korean total 117.8s -> 110.1s, consistent with the shorter 해요체
endings. Previous render kept as Lang_test_formalRegister.
Suite: 721 passed, 8 pre-existing.
|
||
|
|
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. |