diff --git a/README.md b/README.md index f2714af..0abb3e1 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,11 @@ Practice Japanese Karaoke lyrics reading and typing at the same time with KaraDa * [ ] 音域分析(自动推荐升降调幅度) * [ ] 电视模式 * [ ] 和手机配对、用手机点歌 + * [ ] 从网易云搜索歌曲 ## Technical Tasks -* [ ] i18n +* [x] i18n * [ ] 404 page * [ ] Update an existing playlist * [ ] Allow users to correct lyric pronunciations through correction feedback diff --git a/src/lib/i18n/en.ts b/src/lib/i18n/en.ts index 048ca18..d8176dd 100644 --- a/src/lib/i18n/en.ts +++ b/src/lib/i18n/en.ts @@ -86,7 +86,8 @@ export default { song: { mode: { typing: 'Typing Mode', - music: 'Music Mode' + music: 'Music Mode', + karaoke: 'Singing Mode' }, karaoke: { noVocals: 'No vocal separation track detected, cannot adjust vocal volume. Please process in song details page first.' diff --git a/src/lib/i18n/ja.ts b/src/lib/i18n/ja.ts index f14d9c9..0cdff11 100644 --- a/src/lib/i18n/ja.ts +++ b/src/lib/i18n/ja.ts @@ -86,7 +86,8 @@ export default { song: { mode: { typing: 'タイピングモード', - music: '音楽モード' + music: '音楽モード', + karaoke: '歌うモード' }, karaoke: { noVocals: 'ボーカル分離トラックが検出されないため、ボーカル音量を調整できません。まず曲の詳細ページで処理してください。' diff --git a/src/lib/i18n/zh.ts b/src/lib/i18n/zh.ts index d648260..305c876 100644 --- a/src/lib/i18n/zh.ts +++ b/src/lib/i18n/zh.ts @@ -86,7 +86,8 @@ export default { song: { mode: { typing: '打字模式', - music: '音乐模式' + music: '音乐模式', + karaoke: '唱歌模式' }, karaoke: { noVocals: '未检测到人声分离音轨,无法调节人声音量。请先在歌曲详情页进行处理。' diff --git a/src/lib/server/songs.ts b/src/lib/server/songs.ts index d41124c..fbc00de 100644 --- a/src/lib/server/songs.ts +++ b/src/lib/server/songs.ts @@ -140,7 +140,7 @@ export const getSongUrl = async (id: number | string) => { // ///////////////////////////////////////////////////////////////////////////// // API for Song Preparation -export interface ProgressItem { task: string, progress: number } +export interface ProgressItem { id: string, task: string, progress: number } export interface SongProcessState { items: ProgressItem[], status: 'running' | 'done' | 'error' } const songProcessingStatus = new Map() @@ -152,20 +152,20 @@ export const prepareSong = async (songId: number) => { const state: SongProcessState = { items: [], status: 'running' } songProcessingStatus.set(songId, state) - const addTask = (task: string) => ({ task, progress: 0 }).also(it => state.items.push(it)) + const addTask = (id: string, task: string) => ({ id, task, progress: 0 }).also(it => state.items.push(it)) try { // 1. Get Lyrics - const taskLyrics = addTask('从网易云获取歌词') + const taskLyrics = addTask('lyrics', '从网易云获取歌词') const raw = await getLyricsRaw(songId) taskLyrics.progress = 1 if (raw.lang !== 'jpn') { - addTask('错误: 不是日语歌曲').progress = -1 + addTask('error', '错误: 不是日语歌曲').progress = -1 return state.status = 'error' } // 2. AI Process - const taskAI = addTask('AI 标注歌词读音') + const taskAI = addTask('ai', 'AI 标注歌词读音') // Check cache if (await checkLyricsProcessed(songId)) taskAI.progress = 1 @@ -176,12 +176,12 @@ export const prepareSong = async (songId: number) => { } // 3. Audio - const taskAudio = addTask('从网易云获取音乐') + const taskAudio = addTask('music', '从网易云获取音乐') await getSongUrl(songId) taskAudio.progress = 1 // 4. Source Separation - const taskSeparation = addTask('AI 人声分离') + const taskSeparation = addTask('separation', 'AI 人声分离') const inputPath = path.join(CACHE_DIR, `${songId}/exhigh.mp3`) const outputDir = path.join(CACHE_DIR, `${songId}`) @@ -189,14 +189,14 @@ export const prepareSong = async (songId: number) => { await separateSong(inputPath, outputDir) taskSeparation.progress = 1 } catch (e: any) { - addTask(`错误: ${e.message}`).progress = -1 + addTask('error', `错误: ${e.message}`).progress = -1 // Don't fail the whole process, just this step } state.status = 'done' } catch (e) { - addTask(`错误: ${eToString(e)}`).progress = -1 + addTask('error', `错误: ${eToString(e)}`).progress = -1 state.status = 'error' } } diff --git a/src/routes/song/[id]/+page.svelte b/src/routes/song/[id]/+page.svelte index f870126..be9b7ed 100644 --- a/src/routes/song/[id]/+page.svelte +++ b/src/routes/song/[id]/+page.svelte @@ -11,6 +11,19 @@ const t = getI18n().song.mode let { data } = $props() + let taskStatus = $state({ + lyrics: false, + ai: false, + music: false, + separation: false + }) + + let modes = $derived([ + { icon: "i-material-symbols:keyboard-rounded", label: t.typing, url: `/song/${data.song.id}/play`, disabled: !taskStatus.lyrics || !taskStatus.ai }, + { icon: "i-material-symbols:music-note-rounded", label: t.music, url: `/song/${data.song.id}/play?music=true`, disabled: !taskStatus.music }, + { icon: "i-material-symbols:mic-rounded", label: t.karaoke, url: `/song/${data.song.id}/karaoke`, disabled: !taskStatus.separation }, + ]) + let loadStatus = $state<"idle" | "loading" | "done">("idle") let progressItems = $state([]) let progressPercentage = $state(0) @@ -27,6 +40,15 @@ const state = res.status if (state && state.items) { + // Update task status + for (const item of state.items) { + if (item.progress !== 1) continue + if (item.id === 'lyrics') taskStatus.lyrics = true + if (item.id === 'ai') taskStatus.ai = true + if (item.id === 'music') taskStatus.music = true + if (item.id === 'separation') taskStatus.separation = true + } + progressItems = state.items.map((item: any) => ({ title: item.task + (item.progress > 0 && item.progress < 1 ? ` (${Math.round(item.progress * 100)}%)` : ''), icon: item.progress === 1 ? 'i-material-symbols:check text-green-500' : @@ -53,9 +75,8 @@ -{#if loadStatus === "done"} -
- - -
-{/if} +
+ {#each modes as mode} + + {/each} +
diff --git a/src/routes/song/[id]/karaoke/+page.svelte b/src/routes/song/[id]/karaoke/+page.svelte index bb7e02d..dbfad3e 100644 --- a/src/routes/song/[id]/karaoke/+page.svelte +++ b/src/routes/song/[id]/karaoke/+page.svelte @@ -1,6 +1,5 @@