diff --git a/CHANGELOG.md b/CHANGELOG.md index eea696893..c3f3070e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace. - 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함. +### Changed + +- Localize Workspace export, stem-player, loop, solo, mute, and transcription controls in English and Korean, including single-pass literal placeholder interpolation that never reinterprets placeholder-looking replacement text and an accessible localized role fallback. + ## [0.1.3] - 2026-04-29 ### Fixed diff --git a/apps/desktop/src/features/workspace/Workspace.test.tsx b/apps/desktop/src/features/workspace/Workspace.test.tsx index a3da5ffe6..3cbdbca31 100644 --- a/apps/desktop/src/features/workspace/Workspace.test.tsx +++ b/apps/desktop/src/features/workspace/Workspace.test.tsx @@ -86,6 +86,7 @@ describe("Workspace", () => { }); it("enables bass transcription from selected role metadata rather than role id text", () => { + setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); song.sections[0]!.roles[0] = { ...song.sections[0]!.roles[0]!, @@ -96,11 +97,30 @@ describe("Workspace", () => { render(); fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); - const transcribeButton = screen.getByRole("button", { name: "Transcribe Bass" }) as HTMLButtonElement; + const transcribeButton = screen.getByRole("button", { name: /Transcribe Bass/ }) as HTMLButtonElement; expect(transcribeButton.disabled).toBe(false); expect(transcribeButton.title).toBe("Transcribe part"); }); + it("names unavailable transcription for the selected non-bass role", () => { + setNavigatorLanguage("en-US"); + const song = createDemoRehearsalSong(); + song.sections[0]!.roles[0] = { + ...song.sections[0]!.roles[0]!, + id: "guitar-role", + name: "Guitar" + }; + + render(); + fireEvent.click(screen.getByRole("tab", { name: "Guitar" })); + + const label = "Guitar transcription is coming soon. Bass is ready first."; + const transcribeButton = screen.getByRole("button", { name: label }); + expect(transcribeButton).toHaveTextContent("Transcribe part"); + expect(transcribeButton).toHaveAttribute("aria-disabled", "true"); + expect(transcribeButton).toHaveAttribute("title", label); + }); + it("renders bass transcription in the dark rehearsal cockpit system", () => { const song = createDemoRehearsalSong(); song.sections[0]!.roles[0] = { @@ -269,5 +289,8 @@ describe("Workspace", () => { expect(screen.getByText("스템")).toBeTruthy(); expect(screen.getByText("합주 우선순위")).toBeTruthy(); expect(screen.getByText("역할과 화성")).toBeTruthy(); + expect(screen.getByRole("button", { name: "큐 시트 내보내기 (CSV)" })).toBeTruthy(); + expect(screen.getByRole("button", { name: "차트 요약 내보내기 (JSON)" })).toBeTruthy(); + expect(screen.getByRole("button", { name: "핸드오프 데이터 내보내기 (JSON)" })).toBeTruthy(); }); }); diff --git a/apps/desktop/src/features/workspace/Workspace.tsx b/apps/desktop/src/features/workspace/Workspace.tsx index 71546b524..dda6628a4 100644 --- a/apps/desktop/src/features/workspace/Workspace.tsx +++ b/apps/desktop/src/features/workspace/Workspace.tsx @@ -150,6 +150,9 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp return roleMap.get(activeRole); }, [activeRole, roleMap]); const canTranscribeBass = activeRoleDetails?.name.toLowerCase().includes("bass") ?? false; + const transcriptionUnavailableLabel = t("transcriptionComingSoon", { + roleName: activeRoleDetails?.name ?? t("thisRole") + }); /** Handle the practice progress change internally by immutably updating the song state. */ const handlePracticeProgressChange = (newProgress: number) => { @@ -263,7 +266,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp className="min-h-10 border-cyan-300/30 bg-cyan-300/10 font-semibold text-cyan-50 shadow-[0_10px_30px_rgba(34,211,238,0.16)] hover:bg-cyan-300/20 hover:text-white" >