From 1717f335f06482b38eb9adc01799904f3e434e17 Mon Sep 17 00:00:00 2001 From: corpi Date: Tue, 10 Mar 2026 13:32:37 +0900 Subject: [PATCH] =?UTF-8?q?refactor(i18n):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EB=AC=B8=EA=B5=AC=20=EC=B0=B8=EC=A1=B0=EB=A5=BC=20=EC=A4=91?= =?UTF-8?q?=EC=95=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(auth)/login/page.tsx | 17 +- src/app/admin/page.tsx | 200 ++++++++---------- src/app/layout.tsx | 5 +- src/entities/media/api/mediaManifestApi.ts | 3 +- src/entities/plan/model/mockPlan.ts | 19 +- src/entities/scene/model/scenes.ts | 121 +++++------ src/entities/session/model/mockSession.ts | 88 +------- src/features/admin/api/adminApi.ts | 7 +- .../auth/components/SocialLoginGroup.tsx | 3 +- src/features/auth/hooks/useSocialLogin.ts | 11 +- src/features/exit-hold/ui/ExitHoldButton.tsx | 7 +- .../model/useFocusSessionEngine.ts | 15 +- src/features/inbox/ui/InboxList.tsx | 7 +- .../ui/ManagePlanSheetContent.tsx | 12 +- .../paywall-sheet/ui/PaywallSheetContent.tsx | 16 +- src/features/plan-pill/ui/PlanPill.tsx | 3 +- .../preferences/api/preferencesApi.ts | 3 +- .../model/useUserFocusPreferences.ts | 9 +- src/features/restart-30s/model/copy.ts | 10 +- .../scene-select/ui/SceneSelectCarousel.tsx | 3 +- .../session-goal/ui/SessionGoalField.tsx | 8 +- .../sound-preset/model/useSoundPlayback.ts | 5 +- .../sound-preset/ui/SoundPresetControls.tsx | 22 +- src/features/stats/model/useFocusStats.ts | 3 +- src/shared/config/settingsOptions.ts | 10 +- src/shared/lib/apiClient.ts | 7 +- src/shared/ui/Modal.tsx | 5 +- .../ui/ControlCenterSheetWidget.tsx | 22 +- .../settings-panel/ui/SettingsPanelWidget.tsx | 26 +-- .../space-focus-hud/ui/GoalCompleteSheet.tsx | 30 ++- .../ui/SpaceFocusHudWidget.tsx | 9 +- .../ui/SpaceSetupDrawerWidget.tsx | 30 +-- .../space-sheet-shell/ui/SpaceSideSheet.tsx | 5 +- .../ui/SpaceTimerHudWidget.tsx | 19 +- .../space-tools-dock/ui/FocusRightRail.tsx | 9 +- .../ui/SpaceToolsDockWidget.tsx | 48 +++-- src/widgets/space-tools-dock/ui/constants.tsx | 9 +- .../ui/panels/InboxToolPanel.tsx | 13 +- .../ui/panels/SettingsToolPanel.tsx | 16 +- .../ui/panels/StatsToolPanel.tsx | 5 +- .../ui/popovers/QuickNotesPopover.tsx | 10 +- .../ui/popovers/QuickSoundPopover.tsx | 9 +- .../ui/SpaceWorkspaceWidget.tsx | 19 +- .../stats-overview/ui/StatsOverviewWidget.tsx | 50 ++--- 44 files changed, 433 insertions(+), 515 deletions(-) diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 81f200e..3156a87 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,23 +1,26 @@ import Link from "next/link"; import { SocialLoginGroup } from "@/features/auth/components/SocialLoginGroup"; +import { copy } from '@/shared/i18n'; export default function LoginPage() { + const { login } = copy; + return (
{/* 상단 로고 (홈으로 돌아가기) */} - 🪴 VibeRoom + 🪴 {copy.appName} {/* 로그인 카드 컨테이너 */}
-

다시 오셨군요!

+

{login.title}

- 비밀번호를 외울 필요 없이,
- 사용 중인 계정으로 3초 만에 시작하세요. + {login.descriptionFirstLine}
+ {login.descriptionSecondLine}

@@ -28,8 +31,8 @@ export default function LoginPage() {
- 로그인함으로써 VibeRoom의
- 이용약관개인정보처리방침에 동의하게 됩니다. + {login.agreementPrefix}
+ {login.terms} {login.agreementAnd} {login.privacy}{login.agreementSuffix}
@@ -37,4 +40,4 @@ export default function LoginPage() {
); -} \ No newline at end of file +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 1bd1f82..deef730 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -3,6 +3,7 @@ import { FormEvent, useEffect, useState } from 'react'; import { adminApi, type SceneMediaAssetUploadResponse, type SoundMediaAssetUploadResponse } from '@/features/admin/api/adminApi'; import type { AuthResponse } from '@/features/auth/types'; +import { copy } from '@/shared/i18n'; import { Button } from '@/shared/ui'; const ADMIN_STORAGE_KEY = 'vr_admin_session'; @@ -20,20 +21,7 @@ type NavItem = { section: string; }; -const navItems: NavItem[] = [ - { - id: 'scene', - title: '이미지 등록', - subtitle: 'Scene background assets', - section: 'MEDIA', - }, - { - id: 'sound', - title: '오디오 등록', - subtitle: 'Loop and preview assets', - section: 'MEDIA', - }, -]; +const navItems: NavItem[] = [...copy.admin.navItems]; const readStoredSession = (): AuthResponse | null => { if (typeof window === 'undefined') { @@ -76,48 +64,26 @@ const textareaClassName = 'min-h-32 w-full rounded-xl border border-slate-200 bg-white px-3 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-sky-400 focus:ring-2 focus:ring-sky-100'; const getViewMeta = (view: AdminView) => { - if (view === 'scene') { - return { - eyebrow: 'Image Registration', - title: 'Scene 이미지 등록', - description: - 'Space에 들어온 사용자가 선택하는 배경 이미지 세트를 등록합니다. cardFile과 stageFile은 최초 생성 시 필수입니다.', - accent: 'sky', - statTitle: 'Image Pipeline', - statValue: 'Scene Assets', - statHint: 'R2 / Manifest Sync', - }; - } - - return { - eyebrow: 'Audio Registration', - title: 'Sound 오디오 등록', - description: - 'Loop, preview, fallback 오디오를 등록합니다. loopFile은 최초 생성 시 필수이며 기본 볼륨과 duration 메타데이터를 함께 저장합니다.', - accent: 'emerald', - statTitle: 'Audio Pipeline', - statValue: 'Sound Assets', - statHint: 'Loop / Preview / Fallback', - }; + return copy.admin.views[view]; }; const formatResultSummary = (uploadResult: UploadResult | null) => { if (!uploadResult) { - return '아직 업로드 작업이 없습니다.'; + return copy.admin.inspector.noUploadSummary; } if (uploadResult.type === 'scene') { - return `${uploadResult.payload.sceneId} 이미지 세트가 최신 버전 ${uploadResult.payload.assetVersion}로 반영되었습니다.`; + return copy.admin.messages.sceneSummary(uploadResult.payload.sceneId, uploadResult.payload.assetVersion); } - return `${uploadResult.payload.presetId} 오디오 세트가 최신 버전 ${uploadResult.payload.assetVersion}로 반영되었습니다.`; + return copy.admin.messages.soundSummary(uploadResult.payload.presetId, uploadResult.payload.assetVersion); }; export default function AdminPage() { const [session, setSession] = useState(null); const [activeView, setActiveView] = useState('scene'); - const [loginId, setLoginId] = useState('qwer1234'); - const [password, setPassword] = useState('qwer1234!'); + const [loginId, setLoginId] = useState(copy.admin.defaultLoginId); + const [password, setPassword] = useState(copy.admin.defaultPassword); const [loginError, setLoginError] = useState(null); const [loginPending, setLoginPending] = useState(false); const [scenePending, setScenePending] = useState(false); @@ -142,13 +108,13 @@ export default function AdminPage() { }); if (response.user?.grade !== 'ADMIN') { - throw new Error('ADMIN 권한이 없는 계정입니다.'); + throw new Error(copy.admin.messages.nonAdmin); } setSession(response); storeSession(response); } catch (error) { - setLoginError(error instanceof Error ? error.message : '로그인에 실패했습니다.'); + setLoginError(error instanceof Error ? error.message : copy.admin.messages.loginFailed); } finally { setLoginPending(false); } @@ -166,7 +132,7 @@ export default function AdminPage() { const handleSceneUpload = async (event: FormEvent) => { event.preventDefault(); if (!session?.accessToken) { - setSceneMessage('먼저 관리자 로그인을 해주세요.'); + setSceneMessage(copy.admin.messages.loginRequired); return; } @@ -179,7 +145,7 @@ export default function AdminPage() { const sceneId = String(source.get('sceneId') ?? '').trim(); if (!sceneId) { - throw new Error('sceneId를 입력해주세요.'); + throw new Error(copy.admin.messages.sceneIdRequired); } const formData = new FormData(); @@ -199,10 +165,10 @@ export default function AdminPage() { const response = await adminApi.uploadScene(sceneId, formData, session.accessToken); setUploadResult({ type: 'scene', payload: response }); - setSceneMessage(`scene "${sceneId}" 업로드가 완료되었습니다.`); + setSceneMessage(copy.admin.messages.sceneUploadDone(sceneId)); form.reset(); } catch (error) { - setSceneMessage(error instanceof Error ? error.message : 'scene 업로드에 실패했습니다.'); + setSceneMessage(error instanceof Error ? error.message : copy.admin.messages.sceneUploadFailed); } finally { setScenePending(false); } @@ -211,7 +177,7 @@ export default function AdminPage() { const handleSoundUpload = async (event: FormEvent) => { event.preventDefault(); if (!session?.accessToken) { - setSoundMessage('먼저 관리자 로그인을 해주세요.'); + setSoundMessage(copy.admin.messages.loginRequired); return; } @@ -224,7 +190,7 @@ export default function AdminPage() { const presetId = String(source.get('presetId') ?? '').trim(); if (!presetId) { - throw new Error('presetId를 입력해주세요.'); + throw new Error(copy.admin.messages.presetIdRequired); } const formData = new FormData(); @@ -244,10 +210,10 @@ export default function AdminPage() { const response = await adminApi.uploadSound(presetId, formData, session.accessToken); setUploadResult({ type: 'sound', payload: response }); - setSoundMessage(`sound "${presetId}" 업로드가 완료되었습니다.`); + setSoundMessage(copy.admin.messages.soundUploadDone(presetId)); form.reset(); } catch (error) { - setSoundMessage(error instanceof Error ? error.message : 'sound 업로드에 실패했습니다.'); + setSoundMessage(error instanceof Error ? error.message : copy.admin.messages.soundUploadFailed); } finally { setSoundPending(false); } @@ -267,15 +233,15 @@ export default function AdminPage() { V
-

VibeRoom

-

Admin Console

+

{copy.appName}

+

{copy.admin.consoleLabel}

- Media Operations + {copy.admin.mediaOperations}

{navItems.map((item, index) => ( @@ -296,36 +262,36 @@ export default function AdminPage() {
-

Access

-

Admin Only

+

{copy.admin.access}

+

{copy.admin.accessTitle}

- 로그인 후 업로드 토큰으로 scene 이미지와 sound 오디오를 바로 R2에 반영합니다. + {copy.admin.accessDescription}

- Local admin credentials + {copy.admin.localCredentialsHint}

-

Sign In

+

{copy.admin.signInEyebrow}

- 관리자 로그인 + {copy.admin.loginTitle}

- 관리자 권한이 확인되면 좌측 사이드바 기반 대시보드가 열립니다. + {copy.admin.loginDescription}

setLoginId(event.target.value)} autoComplete="username" - placeholder="qwer1234" + placeholder={copy.admin.defaultLoginId} />
setPassword(event.target.value)} autoComplete="current-password" - placeholder="qwer1234!" + placeholder={copy.admin.defaultPassword} />
{loginError ? ( @@ -356,7 +322,7 @@ export default function AdminPage() {
) : null} @@ -375,15 +341,15 @@ export default function AdminPage() { V
-

VibeRoom

-

Media Admin

+

{copy.appName}

+

{copy.admin.mediaAdminLabel}