From f71207aeb5e3f8b23ca8741396290801d738beb7 Mon Sep 17 00:00:00 2001 From: Jaybe Date: Wed, 10 Dec 2025 15:56:51 +0900 Subject: [PATCH 1/3] =?UTF-8?q?i18n=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/dashboard/page.tsx | 72 +- src/app/editor/EditorCanvas.tsx | 22 +- .../forms/FormCheckboxPropertiesPanel.tsx | 162 +-- src/app/editor/forms/FormControllerPanel.tsx | 96 +- .../editor/forms/FormInputPropertiesPanel.tsx | 131 +- .../editor/forms/FormRadioPropertiesPanel.tsx | 125 +- .../forms/FormSelectPropertiesPanel.tsx | 126 +- src/app/editor/page.tsx | 151 +- src/app/editor/panels/BlocksSidebar.tsx | 80 +- .../editor/panels/ButtonPropertiesPanel.tsx | 152 +- .../editor/panels/DividerPropertiesPanel.tsx | 61 +- .../editor/panels/ImagePropertiesPanel.tsx | 76 +- src/app/editor/panels/ListPropertiesPanel.tsx | 82 +- .../editor/panels/ProjectPropertiesPanel.tsx | 79 +- src/app/editor/panels/PropertiesSidebar.tsx | 615 +------- .../editor/panels/SectionPropertiesPanel.tsx | 122 +- src/app/editor/panels/TextPropertiesPanel.tsx | 98 +- .../editor/panels/VideoPropertiesPanel.tsx | 102 +- src/app/layout.tsx | 17 +- src/app/login/page.tsx | 30 +- src/app/preview/page.tsx | 14 +- src/app/projects/page.tsx | 92 +- src/app/projects/submissions/page.tsx | 54 +- src/app/signup/page.tsx | 59 +- .../editor/components/ColorPickerField.tsx | 13 +- .../editor/components/PublicPageRenderer.tsx | 20 +- src/features/i18n/LocaleProvider.tsx | 39 + src/features/i18n/LocaleSwitcher.tsx | 30 + src/features/i18n/locale.ts | 22 + src/features/i18n/messages/auth.ts | 120 ++ src/features/i18n/messages/dashboard.ts | 103 ++ src/features/i18n/messages/editor.ts | 153 ++ .../i18n/messages/editorButtonPanel.ts | 286 ++++ src/features/i18n/messages/editorCanvas.ts | 40 + .../i18n/messages/editorColorPickerField.ts | 70 + .../messages/editorDividerPropertiesPanel.ts | 122 ++ .../i18n/messages/editorFormCheckboxPanel.ts | 298 ++++ .../messages/editorFormControllerPanel.ts | 205 +++ .../i18n/messages/editorFormInputPanel.ts | 266 ++++ .../i18n/messages/editorFormRadioPanel.ts | 152 ++ .../i18n/messages/editorFormSelectPanel.ts | 226 +++ .../i18n/messages/editorImagePanel.ts | 148 ++ src/features/i18n/messages/editorListPanel.ts | 160 +++ .../messages/editorProjectPropertiesPanel.ts | 170 +++ .../i18n/messages/editorPropertiesSidebar.ts | 1251 +++++++++++++++++ .../i18n/messages/editorSectionPanel.ts | 301 ++++ .../i18n/messages/editorSidebar.broken.ts.bak | Bin 0 -> 4835 bytes src/features/i18n/messages/editorSidebar.ts | 128 ++ src/features/i18n/messages/editorTemplates.ts | 291 ++++ src/features/i18n/messages/editorTextPanel.ts | 160 +++ .../i18n/messages/editorVideoPanel.ts | 215 +++ src/features/i18n/messages/preview.ts | 32 + src/features/i18n/messages/projects.ts | 122 ++ .../i18n/messages/publicPageRenderer.ts | 40 + src/features/i18n/messages/submissions.ts | 70 + tests/api/export.spec.ts | 24 +- tests/e2e/auth.spec.ts | 16 +- tests/e2e/dashboard.spec.ts | 16 +- tests/e2e/editor.spec.ts | 64 +- tests/e2e/preview.spec.ts | 40 +- tests/e2e/projects.spec.ts | 18 +- tests/unit/AllProjectSubmissionsPage.spec.tsx | 22 +- tests/unit/BlocksSidebar.spec.tsx | 81 +- tests/unit/ButtonPropertiesPanel.spec.tsx | 54 +- tests/unit/ColorPickerField.spec.tsx | 9 +- tests/unit/DashboardPage.spec.tsx | 55 +- tests/unit/DividerPropertiesPanel.spec.tsx | 18 +- tests/unit/EditorCanvasBackground.spec.tsx | 62 +- tests/unit/EditorExportPreview.spec.tsx | 6 +- tests/unit/EditorJsonExportImport.spec.tsx | 16 +- tests/unit/EditorLayoutScroll.spec.tsx | 2 +- tests/unit/EditorPageBackground.spec.tsx | 14 +- tests/unit/EditorProjectSaveLoad.spec.tsx | 54 +- tests/unit/FormControllerPanel.spec.tsx | 40 +- tests/unit/FormFieldPropertiesPanels.spec.tsx | 152 +- tests/unit/ImagePropertiesPanel.spec.tsx | 22 +- tests/unit/ListPropertiesPanel.spec.tsx | 32 +- tests/unit/LocaleProvider.spec.tsx | 56 + tests/unit/LocaleSwitcher.spec.tsx | 54 + tests/unit/LoginPage.spec.tsx | 18 +- tests/unit/PreviewExport.spec.tsx | 6 +- tests/unit/ProjectPropertiesPanel.spec.tsx | 38 +- tests/unit/ProjectsPage.spec.tsx | 82 +- tests/unit/PropertiesSidebar.spec.tsx | 14 +- ...licPageRendererFormSubmitMessages.spec.tsx | 50 +- .../unit/PublicPageRendererTemplates.spec.tsx | 18 +- tests/unit/SectionLayoutPresets.spec.tsx | 2 +- tests/unit/SectionPropertiesPanel.spec.tsx | 52 +- tests/unit/SignupPage.spec.tsx | 67 +- .../TextListFormBackgroundControls.spec.tsx | 4 +- tests/unit/TextPropertiesPanel.spec.tsx | 8 +- tests/unit/VideoPropertiesPanel.spec.tsx | 32 +- tests/unit/dashboardMessages.spec.ts | 40 + tests/unit/editorCanvasMessages.spec.ts | 31 + tests/unit/editorMessages.spec.ts | 39 + tests/unit/localeHelpers.spec.ts | 29 + tests/unit/projectsMessages.spec.ts | 35 + tests/unit/submissionsMessages.spec.ts | 33 + uploads/0d82dd3a-1874-41d9-b959-0f11e45eaf1d | 1 + uploads/6b0d957a-9e6d-414d-809e-0087ddb80438 | 1 + uploads/73719364-214f-48db-85a7-82ae85c7aa04 | 1 + uploads/aa796672-4c41-4acc-831d-1e51b3055466 | 1 + uploads/ac619ab0-6e38-4d81-8dc1-64c37c1969ad | 1 + uploads/b9e53c73-8533-454b-8256-00d37582fd99 | 1 + uploads/d9c0c842-9ae1-4ff0-ab46-f55633e18aea | 1 + uploads/dc7f0267-4caa-4559-b12e-b6aca44c1faa | 1 + uploads/e90e54f3-4d76-4075-8d51-af8a2cb26ace | 1 + uploads/test-image-1765335114803 | 1 + uploads/test-image-1765335614192 | 1 + uploads/test-image-1765335859857 | 1 + uploads/test-image-1765335931937 | 1 + uploads/test-section-bg-1765335114818 | 1 + uploads/test-section-bg-1765335614314 | 1 + uploads/test-section-bg-1765335859869 | 1 + uploads/test-section-bg-1765335931955 | 1 + uploads/test-section-bg-video-1765335114930 | 1 + uploads/test-section-bg-video-1765335614345 | 1 + uploads/test-section-bg-video-1765335860069 | 1 + uploads/test-section-bg-video-1765335931973 | 1 + uploads/test-video-1765335114884 | 1 + uploads/test-video-1765335614339 | 1 + uploads/test-video-1765335860061 | 1 + uploads/test-video-1765335931969 | 1 + uploads/test-video-poster-1765335114809 | 1 + uploads/test-video-poster-1765335614201 | 1 + uploads/test-video-poster-1765335859862 | 1 + uploads/test-video-poster-1765335931948 | 1 + 127 files changed, 7346 insertions(+), 2079 deletions(-) create mode 100644 src/features/i18n/LocaleProvider.tsx create mode 100644 src/features/i18n/LocaleSwitcher.tsx create mode 100644 src/features/i18n/locale.ts create mode 100644 src/features/i18n/messages/auth.ts create mode 100644 src/features/i18n/messages/dashboard.ts create mode 100644 src/features/i18n/messages/editor.ts create mode 100644 src/features/i18n/messages/editorButtonPanel.ts create mode 100644 src/features/i18n/messages/editorCanvas.ts create mode 100644 src/features/i18n/messages/editorColorPickerField.ts create mode 100644 src/features/i18n/messages/editorDividerPropertiesPanel.ts create mode 100644 src/features/i18n/messages/editorFormCheckboxPanel.ts create mode 100644 src/features/i18n/messages/editorFormControllerPanel.ts create mode 100644 src/features/i18n/messages/editorFormInputPanel.ts create mode 100644 src/features/i18n/messages/editorFormRadioPanel.ts create mode 100644 src/features/i18n/messages/editorFormSelectPanel.ts create mode 100644 src/features/i18n/messages/editorImagePanel.ts create mode 100644 src/features/i18n/messages/editorListPanel.ts create mode 100644 src/features/i18n/messages/editorProjectPropertiesPanel.ts create mode 100644 src/features/i18n/messages/editorPropertiesSidebar.ts create mode 100644 src/features/i18n/messages/editorSectionPanel.ts create mode 100644 src/features/i18n/messages/editorSidebar.broken.ts.bak create mode 100644 src/features/i18n/messages/editorSidebar.ts create mode 100644 src/features/i18n/messages/editorTemplates.ts create mode 100644 src/features/i18n/messages/editorTextPanel.ts create mode 100644 src/features/i18n/messages/editorVideoPanel.ts create mode 100644 src/features/i18n/messages/preview.ts create mode 100644 src/features/i18n/messages/projects.ts create mode 100644 src/features/i18n/messages/publicPageRenderer.ts create mode 100644 src/features/i18n/messages/submissions.ts create mode 100644 tests/unit/LocaleProvider.spec.tsx create mode 100644 tests/unit/LocaleSwitcher.spec.tsx create mode 100644 tests/unit/dashboardMessages.spec.ts create mode 100644 tests/unit/editorCanvasMessages.spec.ts create mode 100644 tests/unit/editorMessages.spec.ts create mode 100644 tests/unit/localeHelpers.spec.ts create mode 100644 tests/unit/projectsMessages.spec.ts create mode 100644 tests/unit/submissionsMessages.spec.ts create mode 100644 uploads/0d82dd3a-1874-41d9-b959-0f11e45eaf1d create mode 100644 uploads/6b0d957a-9e6d-414d-809e-0087ddb80438 create mode 100644 uploads/73719364-214f-48db-85a7-82ae85c7aa04 create mode 100644 uploads/aa796672-4c41-4acc-831d-1e51b3055466 create mode 100644 uploads/ac619ab0-6e38-4d81-8dc1-64c37c1969ad create mode 100644 uploads/b9e53c73-8533-454b-8256-00d37582fd99 create mode 100644 uploads/d9c0c842-9ae1-4ff0-ab46-f55633e18aea create mode 100644 uploads/dc7f0267-4caa-4559-b12e-b6aca44c1faa create mode 100644 uploads/e90e54f3-4d76-4075-8d51-af8a2cb26ace create mode 100644 uploads/test-image-1765335114803 create mode 100644 uploads/test-image-1765335614192 create mode 100644 uploads/test-image-1765335859857 create mode 100644 uploads/test-image-1765335931937 create mode 100644 uploads/test-section-bg-1765335114818 create mode 100644 uploads/test-section-bg-1765335614314 create mode 100644 uploads/test-section-bg-1765335859869 create mode 100644 uploads/test-section-bg-1765335931955 create mode 100644 uploads/test-section-bg-video-1765335114930 create mode 100644 uploads/test-section-bg-video-1765335614345 create mode 100644 uploads/test-section-bg-video-1765335860069 create mode 100644 uploads/test-section-bg-video-1765335931973 create mode 100644 uploads/test-video-1765335114884 create mode 100644 uploads/test-video-1765335614339 create mode 100644 uploads/test-video-1765335860061 create mode 100644 uploads/test-video-1765335931969 create mode 100644 uploads/test-video-poster-1765335114809 create mode 100644 uploads/test-video-poster-1765335614201 create mode 100644 uploads/test-video-poster-1765335859862 create mode 100644 uploads/test-video-poster-1765335931948 diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 482f5c0..8f44c06 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react"; +import { useAppLocale } from "@/features/i18n/LocaleProvider"; +import { getDashboardMessages } from "@/features/i18n/messages/dashboard"; // 대시보드 페이지 (/dashboard) // - /api/dashboard/overview 에서 요약/프로젝트별 통계를 가져와 카드 형태로 렌더링한다. @@ -35,6 +37,8 @@ type PageStatus = "idle" | "loading" | "error"; export default function DashboardPage() { const router = useRouter(); + const locale = useAppLocale(); + const t = getDashboardMessages(locale); const [summary, setSummary] = useState(null); const [projects, setProjects] = useState([]); @@ -59,13 +63,13 @@ export default function DashboardPage() { if (res.status === 401) { setStatus("error"); - setErrorMessage("대시보드를 보려면 로그인이 필요합니다. 다시 로그인해 주세요."); + setErrorMessage(t.errorUnauthorized); router.push("/login"); return; } setStatus("error"); - setErrorMessage("대시보드 데이터를 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."); + setErrorMessage(t.errorGeneric); return; } @@ -87,9 +91,7 @@ export default function DashboardPage() { } catch { if (!cancelled) { setStatus("error"); - setErrorMessage( - "대시보드 데이터를 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.", - ); + setErrorMessage(t.errorGeneric); } } }; @@ -141,8 +143,8 @@ export default function DashboardPage() {
-

대시보드

-

프로젝트와 폼 제출 현황을 한눈에 확인할 수 있는 요약 화면입니다.

+

{t.title}

+

{t.description}

{isMenuOpen && (
@@ -197,7 +199,7 @@ export default function DashboardPage() { void handleLogout(); }} > - 로그아웃 + {t.logoutLabel}
)} @@ -211,7 +213,7 @@ export default function DashboardPage() { )} {status === "loading" && ( -

대시보드 데이터를 불러오는 중입니다...

+

{t.loadingText}

)} {/* 상단 요약 위젯 영역 */} @@ -220,7 +222,9 @@ export default function DashboardPage() { data-testid="dashboard-summary-total-projects" className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70" > - 프로젝트 수 + + {t.summaryTotalProjectsLabel} + {safeSummary.totalProjects}
@@ -228,7 +232,9 @@ export default function DashboardPage() { data-testid="dashboard-summary-total-submissions" className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70" > - 전체 폼 제출 수 + + {t.summaryTotalSubmissionsLabel} + {safeSummary.totalSubmissions}
@@ -236,7 +242,9 @@ export default function DashboardPage() { data-testid="dashboard-summary-today-submissions" className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70" > - 오늘 제출 수 + + {t.summaryTodaySubmissionsLabel} + {safeSummary.todaySubmissions} @@ -244,7 +252,9 @@ export default function DashboardPage() { data-testid="dashboard-summary-last7days-submissions" className="rounded-xl border border-slate-200 bg-white/80 px-4 py-3 flex flex-col gap-1 shadow-sm dark:border-slate-800 dark:bg-slate-900/70" > - 최근 7일 제출 수 + + {t.summaryLast7DaysSubmissionsLabel} + {safeSummary.last7DaysSubmissions} @@ -255,12 +265,12 @@ export default function DashboardPage() { className="mt-2 rounded-xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm dark:border-slate-800 dark:bg-slate-900/70" >
-

최근 제출 추이 (일별)

- 최근 활동이 있는 날짜만 표시됩니다. +

{t.chartTitle}

+ {t.chartSubtitle}
{dailySubmissions.length === 0 ? ( -

아직 일별 제출 내역이 없습니다.

+

{t.chartEmptyLabel}

) : (
{dailySubmissions.map((item) => ( @@ -283,10 +293,12 @@ export default function DashboardPage() { {/* 프로젝트 카드 리스트 영역 */}
-

프로젝트별 제출 현황

+

+ {t.sectionProjectsTitle} +

{projects.length === 0 && status === "idle" && !errorMessage && ( -

아직 생성된 프로젝트가 없거나 제출 내역이 없습니다.

+

{t.sectionProjectsEmpty}

)} {projects.length > 0 && ( @@ -294,7 +306,7 @@ export default function DashboardPage() { {projects.map((project) => { const lastSubmitted = project.lastSubmissionAt ? new Date(project.lastSubmissionAt).toLocaleString() - : "제출 내역 없음"; + : t.projectLatestSubmissionEmpty; return (
- 총 제출 {project.totalSubmissions}건 + {t.projectTotalSubmissionsPrefix} + {project.totalSubmissions} + {t.projectTotalSubmissionsSuffix} + + + {t.projectLatestSubmissionPrefix} {lastSubmitted} - 최근 제출: {lastSubmitted}
@@ -324,13 +340,13 @@ export default function DashboardPage() { href={`/projects/${encodeURIComponent(project.slug)}/submissions`} className="text-emerald-700 hover:text-emerald-900 underline-offset-2 hover:underline dark:text-emerald-300 dark:hover:text-emerald-100" > - 폼 제출 내역 보기 + {t.projectViewSubmissionsLink} - 퍼블릭 페이지 열기 + {t.projectOpenPublicPageLink}
diff --git a/src/app/editor/EditorCanvas.tsx b/src/app/editor/EditorCanvas.tsx index 01e106a..6523d59 100644 --- a/src/app/editor/EditorCanvas.tsx +++ b/src/app/editor/EditorCanvas.tsx @@ -11,6 +11,8 @@ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable" import { rectIntersection } from "@dnd-kit/core"; import type { Block, ButtonBlockProps, ImageBlockProps, ProjectConfig, TextBlockProps } from "@/features/editor/state/editorStore"; +import { useAppLocale } from "@/features/i18n/LocaleProvider"; +import { getEditorCanvasMessages } from "@/features/i18n/messages/editorCanvas"; interface EditorCanvasProps { blocks: Block[]; @@ -42,6 +44,9 @@ export function EditorCanvas(props: EditorCanvasProps) { projectConfig, } = props; + const locale = useAppLocale(); + const m = getEditorCanvasMessages(locale); + const canvasOuterStyle: CSSProperties = {}; const canvasInnerStyle: CSSProperties = {}; const preset = projectConfig.canvasPreset ?? "full"; @@ -86,7 +91,7 @@ export function EditorCanvas(props: EditorCanvasProps) { > {blocks.length === 0 ? (
- 왼쪽에서 "텍스트" 버튼을 눌러 블록을 추가해 보세요. + {m.emptyStateHint}
) : (
@@ -138,16 +146,16 @@ export function EditorCanvasDragPreview({ block }: DragPreviewProps) {
{block.type === "text" - ? (block.props as TextBlockProps).text || "텍스트 블록" + ? (block.props as TextBlockProps).text || m.previewFallbackTextBlock : block.type === "button" - ? (block.props as ButtonBlockProps).label || "버튼 블록" + ? (block.props as ButtonBlockProps).label || m.previewFallbackButtonBlock : block.type === "image" - ? (block.props as ImageBlockProps).alt || "이미지 블록" + ? (block.props as ImageBlockProps).alt || m.previewFallbackImageBlock : block.type === "list" - ? "리스트 블록" + ? m.previewFallbackListBlock : block.type === "divider" - ? "구분선 블록" - : "섹션 블록"} + ? m.previewFallbackDividerBlock + : m.previewFallbackSectionBlock}
); diff --git a/src/app/editor/forms/FormCheckboxPropertiesPanel.tsx b/src/app/editor/forms/FormCheckboxPropertiesPanel.tsx index 4196a7e..05f163d 100644 --- a/src/app/editor/forms/FormCheckboxPropertiesPanel.tsx +++ b/src/app/editor/forms/FormCheckboxPropertiesPanel.tsx @@ -3,6 +3,8 @@ import type { Block, FormCheckboxBlockProps } from "@/features/editor/state/editorStore"; import { ColorPickerField, TEXT_COLOR_PALETTE } from "@/features/editor/components/ColorPickerField"; import { NumericPropertyControl } from "@/features/editor/components/NumericPropertyControl"; +import { useAppLocale } from "@/features/i18n/LocaleProvider"; +import { getEditorFormCheckboxPanelMessages } from "@/features/i18n/messages/editorFormCheckboxPanel"; interface FormCheckboxPropertiesPanelProps { block: Block; @@ -11,6 +13,8 @@ interface FormCheckboxPropertiesPanelProps { } export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBlock }: FormCheckboxPropertiesPanelProps) { + const locale = useAppLocale(); + const m = getEditorFormCheckboxPanelMessages(locale); if (!selectedBlockId || block.id !== selectedBlockId) return null; const checkboxProps = block.props as FormCheckboxBlockProps; @@ -18,10 +22,10 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc return (
-

체크박스 필드

+

{m.sectionTitle}

{groupLabelDisplay === "visible" && ( )} {groupLabelDisplay === "visible" && (checkboxProps.labelLayout ?? "stacked") === "inline" && ( - 그룹 타이틀 + {m.groupTitleLabel} {source === "url" && (