diff --git a/.gitignore b/.gitignore index 3f63869..3c6b3ba 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ venv_test/ venv/ env/ ENV/ +checkpoints/ # ---- CDMF runtime-heavy / user data (DO NOT COMMIT) ---- models/* diff --git a/ui/App.tsx b/ui/App.tsx index e3cd9a3..b8e83c5 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -23,9 +23,11 @@ import { TrainingPanel } from './components/TrainingPanel'; import { StemSplittingPanel } from './components/StemSplittingPanel'; import { VoiceCloningPanel } from './components/VoiceCloningPanel'; import { MidiPanel } from './components/MidiPanel'; +import { useTranslation } from 'react-i18next'; export default function App() { + const { t } = useTranslation(); // Responsive const { isMobile, isDesktop } = useResponsive(); @@ -436,9 +438,9 @@ export default function App() { if (audio.error && audio.error.code !== 1) { console.error("Audio playback error:", audio.error); if (audio.error.code === 4) { - showToast('This song is no longer available.', 'error'); + showToast(t('app.song_unavailable'), 'error'); } else { - showToast('Unable to play this song.', 'error'); + showToast(t('app.unable_to_play'), 'error'); } } setIsPlaying(false); @@ -476,7 +478,7 @@ export default function App() { if (err instanceof Error && err.name !== 'AbortError') { console.error("Playback failed:", err); if (err.name === 'NotSupportedError') { - showToast('This song is no longer available.', 'error'); + showToast(t('app.song_unavailable'), 'error'); } setIsPlaying(false); } @@ -725,7 +727,7 @@ export default function App() { if (activeJobsRef.current.has(job.jobId)) { console.warn(`Job ${job.jobId} timed out`); cleanupJob(job.jobId, tempId); - showToast('Generation timed out', 'error'); + showToast(t('app.generation_timed_out'), 'error'); } }, 600000); @@ -736,7 +738,7 @@ export default function App() { if (activeJobsRef.current.size === 0) { setIsGenerating(false); } - const msg = e instanceof Error ? e.message : 'Generation failed. Please try again.'; + const msg = e instanceof Error ? e.message : t('app.generation_failed', { error: 'Unknown' }); showToast(msg, 'error'); } }; @@ -839,7 +841,7 @@ export default function App() { const handleDeleteSong = async (song: Song) => { // Show confirmation dialog const confirmed = window.confirm( - `Are you sure you want to delete "${song.title}"? This action cannot be undone.` + t('app.confirm_delete_song', { title: song.title }) ); if (!confirmed) return; @@ -876,10 +878,10 @@ export default function App() { // Remove from play queue if present setPlayQueue(prev => prev.filter(s => s.id !== song.id)); - showToast('Song deleted successfully'); + showToast(t('app.song_deleted_success')); } catch (error) { console.error('Failed to delete song:', error); - showToast('Failed to delete song', 'error'); + showToast(t('app.delete_song_failed'), 'error'); } }; @@ -893,10 +895,10 @@ export default function App() { setSongToAddToPlaylist(null); playlistsApi.getMyPlaylists(token ?? undefined).then(r => setPlaylists(r.playlists)); } - showToast('Playlist created successfully!'); + showToast(t('app.playlist_created_success')); } catch (error) { console.error('Create playlist error:', error); - showToast('Failed to create playlist', 'error'); + showToast(t('app.playlist_create_failed'), 'error'); } }; @@ -910,11 +912,11 @@ export default function App() { try { await playlistsApi.addSong(playlistId, songToAddToPlaylist.id, token ?? ''); setSongToAddToPlaylist(null); - showToast('Song added to playlist'); + showToast(t('app.song_added_to_playlist')); playlistsApi.getMyPlaylists(token ?? undefined).then(r => setPlaylists(r.playlists)); } catch (error) { console.error('Add song error:', error); - showToast('Failed to add song to playlist', 'error'); + showToast(t('app.add_song_failed'), 'error'); } }; @@ -1083,7 +1085,7 @@ export default function App() { onClick={() => setMobileShowList(!mobileShowList)} className="bg-zinc-800 text-white px-4 py-2 rounded-full shadow-lg border border-white/10 flex items-center gap-2 text-sm font-bold" > - {mobileShowList ? 'Tools' : 'View List'} + {mobileShowList ? t('common.tools') : t('common.view_list')} @@ -1160,7 +1162,7 @@ export default function App() { onClick={() => setMobileShowList(!mobileShowList)} className="bg-zinc-800 text-white px-4 py-2 rounded-full shadow-lg border border-white/10 flex items-center gap-2 text-sm font-bold" > - {mobileShowList ? 'Create Song' : 'View List'} + {mobileShowList ? t('app.create_song') : t('common.view_list')} diff --git a/ui/components/CreatePanel.tsx b/ui/components/CreatePanel.tsx index 1f324cd..a4ecf9c 100644 --- a/ui/components/CreatePanel.tsx +++ b/ui/components/CreatePanel.tsx @@ -3,6 +3,7 @@ import { Sparkles, ChevronDown, Settings2, Trash2, Music2, Sliders, Dices, Hash, import { GenerationParams, Song } from '../types'; import { useAuth } from '../context/AuthContext'; import { generateApi, preferencesApi, aceStepModelsApi, type LoraAdapter } from '../services/api'; +import { useTranslation } from 'react-i18next'; /** Tasks that require ACE-Step Base model only (see docs/ACE-Step-Tutorial.md). */ const TASKS_REQUIRING_BASE = ['lego', 'extract', 'complete'] as const; @@ -156,6 +157,7 @@ const VOCAL_LANGUAGES = [ type CreateMode = 'simple' | 'custom' | 'cover' | 'lego'; export const CreatePanel: React.FC = ({ onGenerate, isGenerating, initialData, onOpenSettings }) => { + const { t } = useTranslation(); const { isAuthenticated, token } = useAuth(); // Mode: simple | custom | cover | lego @@ -1018,19 +1020,19 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati onClick={() => { setCreateMode('simple'); setLegoValidationError(''); setCoverValidationError(''); }} className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-all ${createMode === 'simple' ? 'bg-white dark:bg-zinc-800 text-black dark:text-white shadow-sm' : 'text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-300'}`} > - Simple + {t('create.modes.simple')} @@ -1052,13 +1054,13 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati {/* Title (same as Custom mode) */}
- Title + {t('create.inputs.title')}
setTitle(e.target.value)} - placeholder="Name your song" + placeholder={t('create.inputs.title_placeholder')} className="w-full bg-transparent p-3 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 dark:placeholder-zinc-600 focus:outline-none" />
@@ -1066,12 +1068,12 @@ export const CreatePanel: React.FC = ({ onGenerate, isGenerati {/* Genre preset + Song Description */}
- Describe Your Song - + {t('create.inputs.describe_song')} +
- +