Apply i18n to all components, add collapsible sidebar, playback speed control, inline title editing, and ConfirmDialog for deletions

Phase 4: i18n all 15 components from PR #19. Sidebar gains collapse/expand
toggle. Player gets playback speed selector (0.25x-2.0x) fixing bug B1
(hardcoded Chinese '正常'). SongList adds inline title editing and model
version badge. Delete actions now use ConfirmDialog instead of
window.confirm. Volume persisted to localStorage. Training nav item
deferred to Phase 7.
This commit is contained in:
fspecii
2026-02-08 18:35:09 +02:00
parent 1b13f01b3d
commit 325046eaf2
17 changed files with 760 additions and 431 deletions
+12 -10
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useRef } from 'react';
import { Song } from '../types';
import { useI18n } from '../context/I18nContext';
import {
Video,
Edit3,
@@ -76,6 +77,7 @@ export const SongDropdownMenu: React.FC<SongDropdownMenuProps> = ({
onUseAsReference,
onCoverSong
}) => {
const { t } = useI18n();
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@@ -174,32 +176,32 @@ export const SongDropdownMenu: React.FC<SongDropdownMenuProps> = ({
{/* Creative Actions */}
<MenuItem
icon={<Video size={14} />}
label="Create Video"
label={t('createVideo')}
onClick={() => handleAction(onCreateVideo)}
/>
{isOwner && (
<MenuItem
icon={<Edit3 size={14} />}
label="Edit Audio"
label={t('editAudio')}
onClick={onEditAudio ? () => handleAction(onEditAudio) : handleEditAudio}
/>
)}
<MenuItem
icon={<Layers size={14} />}
label="Extract Stems"
label={t('extractStems')}
onClick={onExtractStems ? () => handleAction(onExtractStems) : handleExtractStems}
/>
{onReusePrompt && (
<MenuItem
icon={<Repeat size={14} />}
label="Reuse Prompt"
label={t('reusePrompt')}
onClick={() => handleAction(onReusePrompt)}
/>
)}
{onUseAsReference && (
<MenuItem
icon={<Layers size={14} />}
label="Use as Reference"
label={t('useAsReference')}
onClick={() => handleAction(onUseAsReference)}
disabled={!song.audioUrl}
/>
@@ -207,7 +209,7 @@ export const SongDropdownMenu: React.FC<SongDropdownMenuProps> = ({
{onCoverSong && (
<MenuItem
icon={<Layers size={14} />}
label="Cover Song"
label={t('coverSong')}
onClick={() => handleAction(onCoverSong)}
disabled={!song.audioUrl}
/>
@@ -218,17 +220,17 @@ export const SongDropdownMenu: React.FC<SongDropdownMenuProps> = ({
{/* Library Actions */}
<MenuItem
icon={<ListPlus size={14} />}
label="Add to Playlist"
label={t('addToPlaylist')}
onClick={() => handleAction(onAddToPlaylist)}
/>
<MenuItem
icon={<Download size={14} />}
label="Download"
label={t('download')}
onClick={onDownload ? () => handleAction(onDownload) : handleDownload}
/>
<MenuItem
icon={<Share2 size={14} />}
label="Share"
label={t('share')}
onClick={() => handleAction(onShare)}
/>
@@ -238,7 +240,7 @@ export const SongDropdownMenu: React.FC<SongDropdownMenuProps> = ({
<MenuDivider />
<MenuItem
icon={<Trash2 size={14} />}
label="Delete Song"
label={t('deleteSong')}
onClick={() => handleAction(onDelete)}
danger
/>