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:
@@ -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
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user