feat: fit upload controls into first fold
This commit is contained in:
parent
5c750ae34f
commit
fd86f7e10b
@ -34,11 +34,18 @@ describe('App bilingual UI', () => {
|
|||||||
fireEvent.click(screen.getByLabelText('switch-ui-language-en'));
|
fireEvent.click(screen.getByLabelText('switch-ui-language-en'));
|
||||||
|
|
||||||
expect(screen.getByRole('heading', { name: 'Upload & prepare' })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: 'Upload & prepare' })).toBeInTheDocument();
|
||||||
expect(
|
expect(screen.getByText('Everything you need to start translation is visible right away.')).toBeInTheDocument();
|
||||||
screen.getByText(
|
|
||||||
'Upload a source video, tune subtitle defaults, and choose dubbing settings before generation.',
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Video Translate')).toBeInTheDocument();
|
expect(screen.getByText('Video Translate')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the upload shell with compact first-fold copy', () => {
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText('switch-ui-language-en'));
|
||||||
|
|
||||||
|
expect(screen.getByRole('heading', { name: 'Upload & prepare' })).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText('Everything you need to start translation is visible right away.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
20
src/App.tsx
20
src/App.tsx
@ -70,24 +70,24 @@ function AppContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(14,165,233,0.10),_transparent_24%),radial-gradient(circle_at_top_right,_rgba(16,185,129,0.10),_transparent_22%),linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_52%,_#f8fafc_100%)] text-slate-900 font-sans">
|
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(14,165,233,0.10),_transparent_24%),radial-gradient(circle_at_top_right,_rgba(16,185,129,0.10),_transparent_22%),linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_52%,_#f8fafc_100%)] text-slate-900 font-sans">
|
||||||
<div className="mx-auto max-w-7xl px-4 py-5 sm:px-6 sm:py-8 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 py-4 sm:px-6 sm:py-5 lg:px-8">
|
||||||
{currentView === 'upload' ? (
|
{currentView === 'upload' ? (
|
||||||
<header
|
<header
|
||||||
role="banner"
|
role="banner"
|
||||||
className="mb-8 flex flex-col gap-5 rounded-[28px] border border-white/70 bg-white/75 px-5 py-5 shadow-[0_18px_60px_-32px_rgba(15,23,42,0.35)] backdrop-blur sm:px-6 lg:flex-row lg:items-start lg:justify-between"
|
className="mb-4 flex flex-col gap-3 rounded-[24px] border border-white/70 bg-white/75 px-4 py-4 shadow-[0_18px_60px_-32px_rgba(15,23,42,0.35)] backdrop-blur sm:px-5 lg:flex-row lg:items-center lg:justify-between"
|
||||||
>
|
>
|
||||||
<div className="min-w-0 space-y-3">
|
<div className="min-w-0 space-y-2">
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm">
|
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm">
|
||||||
<span className="rounded-full border border-sky-200 bg-sky-50 px-3 py-1 font-medium text-sky-700">
|
<span className="rounded-full border border-sky-200 bg-sky-50 px-2.5 py-1 font-medium text-sky-700">
|
||||||
{m.upload.workbenchEyebrow}
|
{m.upload.workbenchEyebrow}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500">{m.app.productName}</span>
|
<span className="text-slate-500">{m.app.productName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-1">
|
||||||
<h1 className="text-3xl font-semibold tracking-tight text-slate-950 sm:text-4xl">
|
<h1 className="text-2xl font-semibold tracking-tight text-slate-950 sm:text-3xl">
|
||||||
{m.upload.workbenchTitle}
|
{m.upload.workbenchTitle}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-2xl text-sm leading-6 text-slate-600 sm:text-base">
|
<p className="max-w-2xl text-sm leading-5 text-slate-600">
|
||||||
{m.upload.workbenchDescription}
|
{m.upload.workbenchDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -96,11 +96,11 @@ function AppContent() {
|
|||||||
<div className="flex justify-start lg:justify-end">{languageSwitcher}</div>
|
<div className="flex justify-start lg:justify-end">{languageSwitcher}</div>
|
||||||
</header>
|
</header>
|
||||||
) : (
|
) : (
|
||||||
<div className="mb-4 flex justify-end">{languageSwitcher}</div>
|
<div className="mb-3 flex justify-end">{languageSwitcher}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto max-w-7xl px-4 pb-8 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-4 pb-5 sm:px-6 lg:px-8">
|
||||||
{currentView === 'upload' ? (
|
{currentView === 'upload' ? (
|
||||||
<UploadScreen onUpload={handleVideoUpload} />
|
<UploadScreen onUpload={handleVideoUpload} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -67,6 +67,23 @@ describe('UploadScreen', () => {
|
|||||||
expect(screen.getByRole('heading', { name: 'Language & Dubbing' })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: 'Language & Dubbing' })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows all supported tts languages in a compact always-visible grid', () => {
|
||||||
|
renderUploadScreen();
|
||||||
|
|
||||||
|
expect(screen.getByTestId('tts-language-grid')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Chinese' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Cantonese' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'English' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'French' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Indonesian' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'German' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Filipino' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByText('ABC')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('GHI')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('DEF')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('shows a fixed English subtitle language and the supported TTS languages', () => {
|
it('shows a fixed English subtitle language and the supported TTS languages', () => {
|
||||||
renderUploadScreen();
|
renderUploadScreen();
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export default function UploadScreen({
|
|||||||
const activeModeLabel = mode === 'editing' ? m.upload.editingMode : m.upload.simpleMode;
|
const activeModeLabel = mode === 'editing' ? m.upload.editingMode : m.upload.simpleMode;
|
||||||
const activeModeDescription = mode === 'editing' ? m.upload.editingModeDesc : m.upload.simpleModeDesc;
|
const activeModeDescription = mode === 'editing' ? m.upload.editingModeDesc : m.upload.simpleModeDesc;
|
||||||
const settingsCardClass =
|
const settingsCardClass =
|
||||||
'app-surface rounded-[28px] border border-white/70 px-5 py-5 shadow-[0_18px_60px_-34px_rgba(15,23,42,0.3)] sm:px-6';
|
'app-surface rounded-[24px] border border-white/70 px-4 py-4 shadow-[0_18px_60px_-34px_rgba(15,23,42,0.3)] sm:px-5';
|
||||||
|
|
||||||
const clearPendingUpload = () => {
|
const clearPendingUpload = () => {
|
||||||
setTempFile(null);
|
setTempFile(null);
|
||||||
@ -61,23 +61,23 @@ export default function UploadScreen({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid="upload-workbench"
|
data-testid="upload-workbench"
|
||||||
className="grid items-start gap-6 lg:gap-8 xl:grid-cols-[minmax(0,1.65fr)_minmax(320px,420px)]"
|
className="grid items-start gap-4 lg:gap-5 xl:grid-cols-[minmax(0,1.45fr)_minmax(300px,380px)]"
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
data-testid="upload-dropzone-card"
|
data-testid="upload-dropzone-card"
|
||||||
className="app-surface min-w-0 rounded-[32px] border border-white/70 px-5 py-5 shadow-[0_24px_80px_-40px_rgba(15,23,42,0.35)] sm:px-6 sm:py-6 lg:px-8 lg:py-8"
|
className="app-surface min-w-0 rounded-[28px] border border-white/70 px-4 py-4 shadow-[0_24px_80px_-40px_rgba(15,23,42,0.35)] sm:px-5 sm:py-5 lg:px-6 lg:py-5"
|
||||||
>
|
>
|
||||||
<div className="mb-6 flex flex-col gap-4 sm:mb-8 lg:flex-row lg:items-start lg:justify-between">
|
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||||
<div className="min-w-0 space-y-2">
|
<div className="min-w-0 space-y-2">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight text-slate-950 sm:text-3xl">
|
<h2 className="text-xl font-semibold tracking-tight text-slate-950 sm:text-2xl">
|
||||||
{m.upload.uploadPanelTitle}
|
{m.upload.uploadPanelTitle}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-2xl text-sm leading-6 text-slate-600 sm:text-base">
|
<p className="max-w-2xl text-sm leading-5 text-slate-600">
|
||||||
{m.upload.uploadPanelDescription}
|
{m.upload.uploadPanelDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl border border-sky-100 bg-sky-50/80 px-4 py-3 text-left shadow-sm shadow-sky-100/50">
|
<div className="rounded-2xl border border-sky-100 bg-sky-50/80 px-3.5 py-2.5 text-left shadow-sm shadow-sky-100/50">
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-sky-600">
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-sky-600">
|
||||||
{m.upload.modeWorkflowTitle}
|
{m.upload.modeWorkflowTitle}
|
||||||
</p>
|
</p>
|
||||||
@ -86,9 +86,9 @@ export default function UploadScreen({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative overflow-hidden rounded-[28px] border border-slate-200/80 bg-[linear-gradient(180deg,rgba(248,250,252,0.95)_0%,rgba(226,232,240,0.88)_100%)] p-3 sm:p-4">
|
<div className="relative overflow-hidden rounded-[24px] border border-slate-200/80 bg-[linear-gradient(180deg,rgba(248,250,252,0.95)_0%,rgba(226,232,240,0.88)_100%)] p-2.5 sm:p-3">
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.12),_transparent_38%)]" />
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.12),_transparent_38%)]" />
|
||||||
<div className="relative flex min-h-[360px] flex-col items-center justify-center rounded-[24px] border-2 border-dashed border-slate-300 bg-white/70 px-6 py-12 text-center sm:min-h-[420px] lg:min-h-[500px]">
|
<div className="relative flex min-h-[250px] flex-col items-center justify-center rounded-[20px] border-2 border-dashed border-slate-300 bg-white/70 px-5 py-8 text-center sm:min-h-[280px] lg:min-h-[320px]">
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@ -99,14 +99,14 @@ export default function UploadScreen({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pointer-events-none relative z-0 flex max-w-xl flex-col items-center">
|
<div className="pointer-events-none relative z-0 flex max-w-xl flex-col items-center">
|
||||||
<div className="mb-6 rounded-full bg-slate-900 p-5 text-white shadow-lg shadow-slate-300/70">
|
<div className="mb-4 rounded-full bg-slate-900 p-4 text-white shadow-lg shadow-slate-300/70">
|
||||||
<Upload className="h-10 w-10 sm:h-12 sm:w-12" />
|
<Upload className="h-8 w-8 sm:h-10 sm:w-10" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold text-slate-900 sm:text-2xl">{m.upload.uploadVideo}</h3>
|
<h3 className="text-lg font-semibold text-slate-900 sm:text-xl">{m.upload.uploadVideo}</h3>
|
||||||
<p className="mt-3 max-w-md text-sm leading-6 text-slate-600 sm:text-base">
|
<p className="mt-2 max-w-md text-sm leading-5 text-slate-600">
|
||||||
{m.upload.clickToUpload}
|
{m.upload.clickToUpload}
|
||||||
</p>
|
</p>
|
||||||
<button className="mt-8 flex w-full max-w-sm items-center justify-center gap-2 rounded-2xl bg-[#16a34a] px-6 py-3.5 text-sm font-semibold text-white shadow-lg shadow-emerald-200 transition-colors pointer-events-none sm:text-base">
|
<button className="mt-5 flex w-full max-w-xs items-center justify-center gap-2 rounded-2xl bg-[#16a34a] px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-emerald-200 transition-colors pointer-events-none">
|
||||||
<Upload className="h-5 w-5" />
|
<Upload className="h-5 w-5" />
|
||||||
{m.upload.uploadVideo}
|
{m.upload.uploadVideo}
|
||||||
</button>
|
</button>
|
||||||
@ -114,7 +114,7 @@ export default function UploadScreen({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 flex flex-col gap-3 text-sm text-slate-500 md:flex-row md:items-center md:justify-between">
|
<div className="mt-4 flex flex-col gap-2 text-sm text-slate-500 md:flex-row md:items-center md:justify-between">
|
||||||
<p>{m.upload.supportedFormats}</p>
|
<p>{m.upload.supportedFormats}</p>
|
||||||
<div className="inline-flex items-center rounded-full border border-slate-200 bg-white/80 px-3 py-1.5 font-medium text-slate-700">
|
<div className="inline-flex items-center rounded-full border border-slate-200 bg-white/80 px-3 py-1.5 font-medium text-slate-700">
|
||||||
{activeModeLabel}
|
{activeModeLabel}
|
||||||
@ -124,19 +124,19 @@ export default function UploadScreen({
|
|||||||
|
|
||||||
<aside
|
<aside
|
||||||
data-testid="upload-settings-column"
|
data-testid="upload-settings-column"
|
||||||
className="min-w-0 space-y-5 xl:sticky xl:top-6"
|
className="min-w-0 space-y-4"
|
||||||
>
|
>
|
||||||
<section className={settingsCardClass}>
|
<section className={settingsCardClass}>
|
||||||
<div className="mb-4">
|
<div className="mb-3">
|
||||||
<h2 className="text-lg font-semibold text-slate-950">{m.upload.modeWorkflowTitle}</h2>
|
<h2 className="text-base font-semibold text-slate-950">{m.upload.modeWorkflowTitle}</h2>
|
||||||
<p className="mt-1 text-sm leading-6 text-slate-600">{m.upload.modeWorkflowDescription}</p>
|
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.modeWorkflowDescription}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={mode === 'editing'}
|
aria-pressed={mode === 'editing'}
|
||||||
className={`rounded-2xl border px-4 py-4 text-left transition-all ${
|
className={`rounded-2xl border px-3.5 py-3 text-left transition-all ${
|
||||||
mode === 'editing'
|
mode === 'editing'
|
||||||
? 'border-sky-400 bg-sky-50 shadow-sm shadow-sky-100'
|
? 'border-sky-400 bg-sky-50 shadow-sm shadow-sky-100'
|
||||||
: 'border-slate-200 bg-white/80 hover:border-sky-200 hover:bg-slate-50'
|
: 'border-slate-200 bg-white/80 hover:border-sky-200 hover:bg-slate-50'
|
||||||
@ -151,13 +151,13 @@ export default function UploadScreen({
|
|||||||
)}
|
)}
|
||||||
<span className="font-semibold text-slate-900">{m.upload.editingMode}</span>
|
<span className="font-semibold text-slate-900">{m.upload.editingMode}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-sm leading-6 text-slate-600">{m.upload.editingModeDesc}</p>
|
<p className="mt-2 text-sm leading-5 text-slate-600">{m.upload.editingModeDesc}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={mode === 'simple'}
|
aria-pressed={mode === 'simple'}
|
||||||
className={`rounded-2xl border px-4 py-4 text-left transition-all ${
|
className={`rounded-2xl border px-3.5 py-3 text-left transition-all ${
|
||||||
mode === 'simple'
|
mode === 'simple'
|
||||||
? 'border-sky-400 bg-sky-50 shadow-sm shadow-sky-100'
|
? 'border-sky-400 bg-sky-50 shadow-sm shadow-sky-100'
|
||||||
: 'border-slate-200 bg-white/80 hover:border-sky-200 hover:bg-slate-50'
|
: 'border-slate-200 bg-white/80 hover:border-sky-200 hover:bg-slate-50'
|
||||||
@ -172,16 +172,16 @@ export default function UploadScreen({
|
|||||||
)}
|
)}
|
||||||
<span className="font-semibold text-slate-900">{m.upload.simpleMode}</span>
|
<span className="font-semibold text-slate-900">{m.upload.simpleMode}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-sm leading-6 text-slate-600">{m.upload.simpleModeDesc}</p>
|
<p className="mt-2 text-sm leading-5 text-slate-600">{m.upload.simpleModeDesc}</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={settingsCardClass}>
|
<section className={settingsCardClass}>
|
||||||
<div className="mb-4 flex items-start justify-between gap-4">
|
<div className="mb-3 flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-slate-950">{m.upload.subtitleDefaults}</h2>
|
<h2 className="text-base font-semibold text-slate-950">{m.upload.subtitleDefaults}</h2>
|
||||||
<p className="mt-1 text-sm leading-6 text-slate-600">{m.upload.subtitleDefaultsDesc}</p>
|
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.subtitleDefaultsDesc}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -192,8 +192,8 @@ export default function UploadScreen({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-5 rounded-[24px] border border-slate-200 bg-slate-950/95 p-3">
|
<div className="mb-4 rounded-[22px] border border-slate-200 bg-slate-950/95 p-2.5">
|
||||||
<div className="relative h-40 overflow-hidden rounded-[18px] bg-[radial-gradient(circle_at_top,_rgba(148,163,184,0.35),_transparent_40%),linear-gradient(180deg,_#1e293b_0%,_#0f172a_100%)] sm:h-44">
|
<div className="relative h-28 overflow-hidden rounded-[16px] bg-[radial-gradient(circle_at_top,_rgba(148,163,184,0.35),_transparent_40%),linear-gradient(180deg,_#1e293b_0%,_#0f172a_100%)] sm:h-30">
|
||||||
<div className="absolute inset-0 bg-[linear-gradient(90deg,transparent_0%,rgba(255,255,255,0.04)_50%,transparent_100%)]" />
|
<div className="absolute inset-0 bg-[linear-gradient(90deg,transparent_0%,rgba(255,255,255,0.04)_50%,transparent_100%)]" />
|
||||||
<p
|
<p
|
||||||
data-testid="upload-subtitle-preview"
|
data-testid="upload-subtitle-preview"
|
||||||
@ -267,12 +267,12 @@ export default function UploadScreen({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={`${settingsCardClass} flex flex-col`}>
|
<section className={`${settingsCardClass} flex flex-col`}>
|
||||||
<div className="mb-5">
|
<div className="mb-3">
|
||||||
<h2 className="text-lg font-semibold text-slate-950">{m.upload.languageDubbingTitle}</h2>
|
<h2 className="text-base font-semibold text-slate-950">{m.upload.languageDubbingTitle}</h2>
|
||||||
<p className="mt-1 text-sm leading-6 text-slate-600">{m.upload.languageDubbingDescription}</p>
|
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.languageDubbingDescription}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-5 rounded-2xl border border-slate-200 bg-white/80 p-4">
|
<div className="mb-3 rounded-2xl border border-slate-200 bg-white/80 p-3">
|
||||||
<h3 className="font-semibold text-slate-900">{m.upload.subtitleLanguage}</h3>
|
<h3 className="font-semibold text-slate-900">{m.upload.subtitleLanguage}</h3>
|
||||||
<p className="mt-1 text-xs text-slate-500">{m.upload.subtitleLanguageHint}</p>
|
<p className="mt-1 text-xs text-slate-500">{m.upload.subtitleLanguageHint}</p>
|
||||||
<div className="mt-3 inline-flex items-center rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-medium text-slate-700">
|
<div className="mt-3 inline-flex items-center rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-medium text-slate-700">
|
||||||
@ -280,41 +280,25 @@ export default function UploadScreen({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-3">
|
||||||
<h3 className="font-semibold text-slate-900">{m.upload.ttsLanguage}</h3>
|
<h3 className="font-semibold text-slate-900">{m.upload.ttsLanguage}</h3>
|
||||||
<p className="mt-1 text-xs text-slate-500">{m.upload.ttsLanguageHint}</p>
|
<p className="mt-1 text-xs text-slate-500">{m.upload.ttsLanguageHint}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4 flex gap-4 overflow-x-auto border-b border-slate-200 pb-2 text-sm text-slate-500">
|
<div
|
||||||
<button type="button" className="border-b-2 border-sky-600 pb-2 font-medium text-sky-600 -mb-[9px]">
|
data-testid="tts-language-grid"
|
||||||
{m.upload.popular}
|
className="grid grid-cols-2 gap-2"
|
||||||
</button>
|
>
|
||||||
<button type="button" className="hover:text-slate-800">
|
{LANGUAGES.map((lang) => (
|
||||||
{m.upload.groupABC}
|
|
||||||
</button>
|
|
||||||
<button type="button" className="hover:text-slate-800">
|
|
||||||
{m.upload.groupGHI}
|
|
||||||
</button>
|
|
||||||
<button type="button" className="hover:text-slate-800">
|
|
||||||
{m.upload.groupDEF}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="custom-scrollbar max-h-[280px] flex-1 overflow-y-auto pr-2">
|
|
||||||
{['C', 'E', 'F', 'G', 'I'].map((letter) => (
|
|
||||||
<div key={letter} className="flex border-b border-slate-100 py-3 last:border-0">
|
|
||||||
<div className="w-8 font-medium text-emerald-600">{letter}</div>
|
|
||||||
<div className="flex flex-1 flex-wrap gap-x-3 gap-y-2 sm:gap-x-4">
|
|
||||||
{LANGUAGES.filter((l) => l.group === letter).map((lang) => (
|
|
||||||
<button
|
<button
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded-full px-2.5 py-1 text-sm transition-colors ${
|
className={`rounded-2xl border px-3 py-2 text-left text-sm font-medium transition-colors ${
|
||||||
selectedTtsLanguage === lang.code
|
selectedTtsLanguage === lang.code
|
||||||
? 'bg-emerald-600 text-white'
|
? 'border-emerald-500 bg-emerald-50 text-emerald-700'
|
||||||
: lang.code === 'zh' || lang.code === 'yue'
|
: lang.code === 'zh' || lang.code === 'yue'
|
||||||
? 'text-orange-500 hover:bg-orange-50'
|
? 'border-orange-100 bg-orange-50 text-orange-600 hover:border-orange-200'
|
||||||
: 'text-slate-700 hover:bg-slate-100 hover:text-sky-700'
|
: 'border-slate-200 bg-white/80 text-slate-700 hover:border-sky-200 hover:text-sky-700'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setSelectedTtsLanguage(lang.code)}
|
onClick={() => setSelectedTtsLanguage(lang.code)}
|
||||||
>
|
>
|
||||||
@ -322,12 +306,9 @@ export default function UploadScreen({
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`mt-5 w-full rounded-2xl px-4 py-3.5 text-sm font-semibold transition-colors sm:text-base ${
|
className={`mt-4 w-full rounded-2xl px-4 py-3 text-sm font-semibold transition-colors ${
|
||||||
tempFile
|
tempFile
|
||||||
? 'bg-[#16a34a] text-white shadow-lg shadow-emerald-200 hover:bg-[#15803d]'
|
? 'bg-[#16a34a] text-white shadow-lg shadow-emerald-200 hover:bg-[#15803d]'
|
||||||
: 'cursor-not-allowed bg-slate-200 text-slate-400'
|
: 'cursor-not-allowed bg-slate-200 text-slate-400'
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const messages = {
|
|||||||
upload: {
|
upload: {
|
||||||
workbenchEyebrow: 'Video Translate',
|
workbenchEyebrow: 'Video Translate',
|
||||||
workbenchTitle: '\u4e0a\u4f20\u5e76\u51c6\u5907',
|
workbenchTitle: '\u4e0a\u4f20\u5e76\u51c6\u5907',
|
||||||
workbenchDescription: '\u4e0a\u4f20\u6e90\u89c6\u9891\uff0c\u8c03\u6574\u5b57\u5e55\u9ed8\u8ba4\u6837\u5f0f\uff0c\u5e76\u5728\u751f\u6210\u524d\u9009\u62e9\u914d\u97f3\u8bbe\u7f6e\u3002',
|
workbenchDescription: '\u9996\u5c4f\u5373\u53ef\u770b\u5230\u5f00\u59cb\u7ffb\u8bd1\u6240\u9700\u7684\u5168\u90e8\u5173\u952e\u9009\u9879\u3002',
|
||||||
uploadPanelTitle: '\u4e0a\u4f20\u6e90\u89c6\u9891',
|
uploadPanelTitle: '\u4e0a\u4f20\u6e90\u89c6\u9891',
|
||||||
uploadPanelDescription: '\u62d6\u62fd\u6216\u70b9\u51fb\u9009\u62e9\u89c6\u9891\u6587\u4ef6\uff0c\u6211\u4eec\u4f1a\u5728\u4e0b\u4e00\u6b65\u8fdb\u5165\u88c1\u526a\u548c\u7ffb\u8bd1\u6d41\u7a0b\u3002',
|
uploadPanelDescription: '\u62d6\u62fd\u6216\u70b9\u51fb\u9009\u62e9\u89c6\u9891\u6587\u4ef6\uff0c\u6211\u4eec\u4f1a\u5728\u4e0b\u4e00\u6b65\u8fdb\u5165\u88c1\u526a\u548c\u7ffb\u8bd1\u6d41\u7a0b\u3002',
|
||||||
modeWorkflowTitle: '\u6a21\u5f0f\u4e0e\u6d41\u7a0b',
|
modeWorkflowTitle: '\u6a21\u5f0f\u4e0e\u6d41\u7a0b',
|
||||||
@ -145,8 +145,7 @@ const messages = {
|
|||||||
upload: {
|
upload: {
|
||||||
workbenchEyebrow: 'Video Translate',
|
workbenchEyebrow: 'Video Translate',
|
||||||
workbenchTitle: 'Upload & prepare',
|
workbenchTitle: 'Upload & prepare',
|
||||||
workbenchDescription:
|
workbenchDescription: 'Everything you need to start translation is visible right away.',
|
||||||
'Upload a source video, tune subtitle defaults, and choose dubbing settings before generation.',
|
|
||||||
uploadPanelTitle: 'Upload source video',
|
uploadPanelTitle: 'Upload source video',
|
||||||
uploadPanelDescription:
|
uploadPanelDescription:
|
||||||
'Drop a video here or browse from your device. We will move into trim and translation setup next.',
|
'Drop a video here or browse from your device. We will move into trim and translation setup next.',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user