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" && (