fix(dub): Export-step language tabs switch the transcript segments too (#1148)
Owner request with screenshot: the Original/Bengali/German/… pills above a finished dub only swapped the preview VIDEO; the segment list kept showing the last generated/edited language — German audio playing over Bengali text. The pills now also route through switchDubLangCode (the P1.2 user-driven language switch: outgoing text snapshotted into translations[prev], incoming swapped in, non-destructive when no saved entry exists) plus setDubLang — exactly what the language dropdown and the multi-language generate loop already do, so fingerprint/staleness semantics are identical. The Original pill deliberately leaves the editing language untouched: there is no 'original' editing language, and every row already renders the original line under its translation. Tests: clicking the German pill swaps segment text to the stored German translation, snapshots the outgoing Bengali, and sets dubLangCode; the Original pill leaves dubLangCode alone. Fail-before verified (wiring stashed → text swap test fails). Full frontend suite: 1251 passed. Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
mergetest
Claude Opus 4.8
parent
9ecb810946
commit
22a513a404
@@ -16,6 +16,8 @@ The bundled TTS model package (`pyproject.toml`) is versioned independently.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The Export step's language tabs now switch the transcript too.** Clicking Bengali/German/Hindi… above the finished dub swapped the *video* but left the segment list showing whichever language you generated last — German audio over Bengali text. The tabs now also swap every segment's text to that language (through the same per-language store the language picker uses, so nothing is lost when you switch back); the Original tab keeps your editing language as-is, since each row already shows the original line beneath its translation. (#1148)
|
||||
|
||||
- **A "backend crashed" notice can no longer outlive the update that fixed the crash — and the desktop shell's self-repair paths are now pinned by tests that CI actually runs.** Crash notices now record which app version wrote them, and a notice left behind by an older version is ignored and cleaned up after you upgrade instead of resurfacing as if the new build had crashed. The Windows blank-window repair (the one-click WebView cache fix after a BSOD) also gets regression tests pinning its safety contract — one attempt per request, never touches anything unasked, never blocks startup on a locked cache — and CI now runs the desktop shell's entire Rust unit-test suite on macOS, Windows, and Linux, which it previously never executed at all. (#1145)
|
||||
|
||||
- **The MLX-Audio phonemizer's language model now ships with the app environment instead of being fetched mid-generation.** Follow-up to the pip fix: with the installer present, the first English MLX-Audio generation would auto-download a small model straight from GitHub — an outbound request that bypasses the app's mirror system (a problem on restricted networks) and fails offline. The model is now a pinned dependency of the managed environment: it arrives at install/update time through the normal dependency flow, and first generation works fully offline. (#1146)
|
||||
|
||||
@@ -223,7 +223,18 @@ export default function DubLeftColumn({
|
||||
role="radio"
|
||||
aria-checked={previewMode === code}
|
||||
className={`dub-lang-pill ${previewMode === code ? 'is-active' : ''}`}
|
||||
onClick={() => setPreviewMode(code)}
|
||||
onClick={() => {
|
||||
setPreviewMode(code);
|
||||
// The transcript/segment list follows the previewed track:
|
||||
// swap segment texts to this language's saved translations
|
||||
// (the P1.2 per-language store — non-destructive, exactly
|
||||
// what the language dropdown and multi-language generate
|
||||
// already do). Without this, previewing German played
|
||||
// German audio over, say, Bengali segment text.
|
||||
const st = useAppStore.getState();
|
||||
st.setDubLang(label);
|
||||
st.switchDubLangCode(code);
|
||||
}}
|
||||
title={trackTooltip(code)}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { createRef } from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import i18n from '../i18n';
|
||||
|
||||
// Preview-tab ↔ transcript sync: clicking a dubbed-language pill on the
|
||||
// Export step must ALSO switch the segment texts to that language (the P1.2
|
||||
// per-language translations store) — previously the tabs only swapped the
|
||||
// video, so previewing German played German audio over Bengali segment text.
|
||||
|
||||
vi.mock('../components/WaveformTimeline', () => ({ default: () => <div data-testid="wf" /> }));
|
||||
vi.mock('../components/MultiLangPicker', () => ({ default: () => <div data-testid="mlp" /> }));
|
||||
vi.mock('react-hot-toast', () => ({
|
||||
default: { error: vi.fn(), success: vi.fn(), loading: vi.fn() },
|
||||
}));
|
||||
const dubListTracks = vi.hoisted(() => vi.fn());
|
||||
vi.mock('../api/dub', () => ({ dubListTracks: (...a) => dubListTracks(...a) }));
|
||||
|
||||
import DubLeftColumn from '../components/dub/DubLeftColumn';
|
||||
import { useAppStore } from '../store';
|
||||
|
||||
const t = i18n.t.bind(i18n);
|
||||
|
||||
function makeProps(over = {}) {
|
||||
return {
|
||||
hasDubbedTrack: true,
|
||||
t,
|
||||
i18n,
|
||||
previewMode: 'bn',
|
||||
setPreviewMode: vi.fn(),
|
||||
dubTracks: ['bn', 'de'],
|
||||
videoSrc: '',
|
||||
waveformRef: createRef(),
|
||||
dubJobId: 'job1',
|
||||
dubSegments: [{ id: '1', text: 'hi' }],
|
||||
timelineOnsets: [],
|
||||
timelineSelSegId: null,
|
||||
setTimelineSelSegId: vi.fn(),
|
||||
incrementalPlan: null,
|
||||
segmentMoveResize: vi.fn(),
|
||||
segmentDelete: vi.fn(),
|
||||
onTimelinePreviewSegment: vi.fn(),
|
||||
dubStep: 'done',
|
||||
dubProgress: { current: 0, total: 0, text: '' },
|
||||
fmtDur: (s) => `${s}s`,
|
||||
genElapsed: 0,
|
||||
genRemaining: null,
|
||||
speakerClones: {},
|
||||
setDubSegments: vi.fn(),
|
||||
profiles: [],
|
||||
settingsOpen: false,
|
||||
setSettingsOpen: vi.fn(),
|
||||
dubLang: 'Bengali',
|
||||
dubLangCode: 'bn',
|
||||
translateQuality: 'fast',
|
||||
activeEngineUnavailable: false,
|
||||
translateProvider: 'google',
|
||||
dubInstruct: '',
|
||||
setDubInstruct: vi.fn(),
|
||||
handleTranslateAll: vi.fn(),
|
||||
isTranslating: false,
|
||||
editSegments: vi.fn(),
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
dubListTracks.mockResolvedValue({ tracks: {} });
|
||||
useAppStore.setState({
|
||||
dubLangCode: 'bn',
|
||||
dubLang: 'Bengali',
|
||||
dubSegments: [
|
||||
{
|
||||
id: '1',
|
||||
text: 'বাংলা লাইন',
|
||||
text_original: 'the original line',
|
||||
translations: { bn: 'বাংলা লাইন', de: 'die deutsche Zeile' },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('preview tab → transcript language sync', () => {
|
||||
it('clicking a language pill swaps segment text to that language', () => {
|
||||
render(<DubLeftColumn {...makeProps()} />);
|
||||
fireEvent.click(screen.getByRole('radio', { name: /german|deutsch/i }));
|
||||
const st = useAppStore.getState();
|
||||
expect(st.dubLangCode).toBe('de');
|
||||
expect(st.dubSegments[0].text).toBe('die deutsche Zeile');
|
||||
// Outgoing language snapshotted, not lost.
|
||||
expect(st.dubSegments[0].translations.bn).toBe('বাংলা লাইন');
|
||||
});
|
||||
|
||||
it('the Original pill leaves the editing language untouched', () => {
|
||||
render(<DubLeftColumn {...makeProps()} />);
|
||||
fireEvent.click(screen.getByRole('radio', { name: /original/i }));
|
||||
expect(useAppStore.getState().dubLangCode).toBe('bn');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user