diff --git a/apps/desktop/src/App.test.tsx b/apps/desktop/src/App.test.tsx index c039dfba..48f98e21 100644 --- a/apps/desktop/src/App.test.tsx +++ b/apps/desktop/src/App.test.tsx @@ -212,6 +212,27 @@ describe("App", () => { expect(screen.getByText(/YouTube only leaves the app when you choose import/i)).toBeTruthy(); }); + it("renders localized Korean shell copy for buyer-demo surfaces", () => { + const languageSpy = vi.spyOn(window.navigator, "language", "get").mockReturnValue("ko-KR"); + + try { + render(); + + expect(screen.getByRole("navigation", { name: /주요 합주 보기/i })).toBeTruthy(); + expect(screen.getByRole("heading", { name: /작업 공간 홈/i })).toBeTruthy(); + expect(screen.getByText(/동기화됨 • 로컬/i)).toBeTruthy(); + expect(screen.getByRole("button", { name: /^작업 공간$/i })).toBeTruthy(); + expect(screen.getByRole("button", { name: /프로젝트 열기/i })).toBeTruthy(); + expect(screen.getByRole("button", { name: /유튜브 가져오기/i })).toBeTruthy(); + expect(screen.getByText(/로컬 우선/i)).toBeTruthy(); + expect(screen.getByText(/합주 지도는 이 기기에 머뭅니다/i)).toBeTruthy(); + expect(screen.getByText(/^템포$/i)).toBeTruthy(); + expect(screen.queryByRole("heading", { name: /Workspace Home/i })).toBeNull(); + } finally { + languageSpy.mockRestore(); + } + }); + it("keeps source controls before the analysis summary", () => { render(); diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 24f5fb09..da621b78 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -40,7 +40,7 @@ import { selectLocalAudioSource, startAnalysisJob } from "./lib/analysis"; -import { createTranslator, detectPreferredLocale } from "./i18n"; +import { createTranslator, detectPreferredLocale, type TranslationKey } from "./i18n"; import { Workspace } from "./features/workspace/Workspace"; import { EmptyState, ErrorState, LoadingState } from "./features/workspace/WorkspaceStates"; import { Button } from "@/components/ui/button"; @@ -54,15 +54,15 @@ const URL_PATTERN = /\bhttps?:\/\/[^\s"'<>]+/gi; const SECRET_ASSIGNMENT_PATTERN = /\b(token|secret|password|api[_-]?key|access[_-]?token)\s*[:=]\s*[^\s,;]+/gi; const NAV_ITEMS = [ - { label: "Workspace", icon: Home, active: true }, - { label: "Import", icon: Upload, active: false }, - { label: "Export", icon: Save, active: false }, - { label: "Sections", icon: ListMusic, active: false }, - { label: "Roles", icon: Users, active: false }, - { label: "Stem Lab", icon: AudioWaveform, active: false }, - { label: "Cues", icon: Sparkles, active: false }, - { label: "Transpose", icon: SlidersHorizontal, active: false }, - { label: "Notes", icon: FileMusic, active: false } + { labelKey: "navWorkspace", icon: Home, active: true }, + { labelKey: "navImport", icon: Upload, active: false }, + { labelKey: "navExport", icon: Save, active: false }, + { labelKey: "navSections", icon: ListMusic, active: false }, + { labelKey: "navRoles", icon: Users, active: false }, + { labelKey: "navStemLab", icon: AudioWaveform, active: false }, + { labelKey: "navCues", icon: Sparkles, active: false }, + { labelKey: "navTranspose", icon: SlidersHorizontal, active: false }, + { labelKey: "navNotes", icon: FileMusic, active: false } ] as const; const BRAND_BAR_HEIGHTS = ["h-3", "h-5", "h-7", "h-4", "h-6"] as const; @@ -127,11 +127,11 @@ function safeErrorDetail(error: unknown, fallback: string): string { } /** Documented. */ -function BandScopeMark() { +function BandScopeMark({ ariaLabel }: { ariaLabel: string }) { return (