feat(i18n,admin): modular localization dictionary & admin console demo preview

This commit is contained in:
2026-08-21 20:00:01 +09:00
parent 94217d773d
commit b8f6d9acad
12 changed files with 881 additions and 612 deletions
+9 -13
View File
@@ -4,14 +4,14 @@
- **Core Architecture & Source Structure:** - **Core Architecture & Source Structure:**
- `src/app/`: Next.js App Router root layouts, providers, auth/demo/admin views, and REST/CalDAV API endpoints (`/api/tasks`, `/api/lists`, `/api/import`, `/api/dav/[...path]`). - `src/app/`: Next.js App Router root layouts, providers, auth/demo/admin views, and REST/CalDAV API endpoints (`/api/tasks`, `/api/lists`, `/api/import`, `/api/dav/[...path]`).
- `src/components/layout/`: `AppShell.tsx` (3-panel main layout, resizer coordination, mobile drawer overlay), `Sidebar.tsx` (draggable resizer, lists tree, tag & trash navigation, profile & import popover). - `src/components/layout/`: `AppShell.tsx` (3-panel main layout, resizer coordination, mobile drawer overlay), `Sidebar.tsx` (draggable resizer, lists tree, tag & trash navigation, profile, import & export popover, admin console link).
- `src/components/tasks/`: - `src/components/tasks/`:
- `TaskList.tsx`: Recursive n-depth hierarchical checklist, inline title editing, context menu, smart quick-add toolbar. - `TaskList.tsx`: Recursive n-depth hierarchical checklist, Notion-style 6-dot drag handle (`⋮⋮`) with DND reordering, inline title editing (`F2`/double-click), right-aligned hover subtask trigger, 6-second interactive Undo toast banner.
- `TaskDetail.tsx`: Right-side detail panel, modular block swap (Subtasks ⇄ Markdown Note), mouse drag split resizer, mobile bottom-sheet with swipe-down dismissal. - `TaskDetail.tsx`: Right-side detail panel, modular block swap (Subtasks ⇄ Markdown Note), mouse drag split resizer, mobile bottom-sheet with swipe-down dismissal.
- `MarkdownNoteEditor.tsx`: Distraction-free text/markdown note canvas with DOMPurify sanitization & preview rendering. - `MarkdownNoteEditor.tsx`: Distraction-free text/markdown note canvas with DOMPurify sanitization & preview rendering.
- `KanbanView.tsx`: CheckFlow Labs 3-column Kanban board view (`To Do` / `In Progress` / `Done`). - `KanbanView.tsx`: CheckFlow Labs 3-column Kanban board view (`To Do` / `In Progress` / `Done`).
- `src/components/settings/`: `SettingsModal.tsx` (Profile, Preferences, Labs, Integrations/DAVx⁵, Admin tabs). - `src/components/settings/`: `SettingsModal.tsx` (Profile, Preferences, Labs, Integrations/CalDAV, Admin tabs).
- `src/lib/`: `userPrefs.ts` (global user customization state store), `useUserPrefs.ts` (reactive preferences hook & CSS variable injector), `i18n/` (EN/KO/JA localization dictionary), `mockData.ts` (LocalStorage demo store), `auth.ts`, `prisma.ts`. - `src/lib/`: `userPrefs.ts` (global user customization state store), `useUserPrefs.ts` (reactive preferences hook & CSS variable injector), `i18n/` (modular EN/KO/JA localization dictionaries: `types.ts`, `locales/en.ts`, `locales/ko.ts`, `locales/ja.ts`), `mockData.ts` (LocalStorage demo store), `auth.ts`, `prisma.ts`.
- `src/middleware.ts`: Route-level auth and admin role verification guard. - `src/middleware.ts`: Route-level auth and admin role verification guard.
- **Completed So Far:** - **Completed So Far:**
@@ -20,20 +20,16 @@
- Verified 100% implementation of core features: n-depth recursive checklist, real-time subtask synchronization, modular blocks & split resizer, 3-way theme (VSCode dark / pastel light / system), global `UserPrefs` customization, Labs Kanban view, and mobile bottom sheet. - Verified 100% implementation of core features: n-depth recursive checklist, real-time subtask synchronization, modular blocks & split resizer, 3-way theme (VSCode dark / pastel light / system), global `UserPrefs` customization, Labs Kanban view, and mobile bottom sheet.
- Enhanced CalDAV / DAVx⁵ synchronization endpoint (`src/app/api/dav/[...path]/route.ts`) with full RFC 4791 support: `OPTIONS`, `PROPFIND` (principal & calendar collection discovery), `REPORT` (calendar-query/multiget), `PUT` (task upsert from VTODO), and `DELETE`. - Enhanced CalDAV / DAVx⁵ synchronization endpoint (`src/app/api/dav/[...path]/route.ts`) with full RFC 4791 support: `OPTIONS`, `PROPFIND` (principal & calendar collection discovery), `REPORT` (calendar-query/multiget), `PUT` (task upsert from VTODO), and `DELETE`.
- Upgraded Settings Modal "Integrations (CalDAV)" tab with multi-platform interactive guides (Android/DAVx⁵, Apple Reminders, Thunderbird), one-click URL copy feedback, ICS feed direct download, and live endpoint test ping. - Upgraded Settings Modal "Integrations (CalDAV)" tab with multi-platform interactive guides (Android/DAVx⁵, Apple Reminders, Thunderbird), one-click URL copy feedback, ICS feed direct download, and live endpoint test ping.
- Standardized all synchronization terminology from brand-specific references to open standards and Android/Apple cross-platform guidelines.
- Implemented full-fidelity standard Task Export pair (`/api/export`) supporting RFC 4180 / TickTick-compatible CSV with UTF-8 BOM, and RFC 5545 iCalendar (`.ics` VTODO) format. - Implemented full-fidelity standard Task Export pair (`/api/export`) supporting RFC 4180 / TickTick-compatible CSV with UTF-8 BOM, and RFC 5545 iCalendar (`.ics` VTODO) format.
- Added Export modal in Sidebar user popover menu supporting format selection, target list filtering, completed tasks inclusion toggle, and seamless Demo mode client-side Blob generation. - Added Export modal in Sidebar user popover menu supporting format selection, target list filtering, completed tasks inclusion toggle, and seamless Demo mode client-side Blob generation.
- Refined Korean and Japanese localization dictionaries across all keys for natural, polished phrasing and terminology consistency. - Refactored `i18n` into a clean modular dictionary architecture (`src/lib/i18n/types.ts`, `locales/en.ts`, `locales/ko.ts`, `locales/ja.ts`) with 100% dictionary completeness and natural phrasing across English, Korean, and Japanese.
- Enhanced Admin console (`src/app/admin/page.tsx`), Settings Modal Admin tab, and Middleware to seamlessly support both authenticated ADMIN users and Demo mode admin preview.
- Added Admin Console direct entry in Sidebar user popover menu for swift navigation.
- Fixed Docker container runtime Prisma migration dependencies by adding `effect` and ensuring full `node_modules` availability in the production runner stage of `Dockerfile` for seamless entrypoint `prisma migrate deploy`. - Fixed Docker container runtime Prisma migration dependencies by adding `effect` and ensuring full `node_modules` availability in the production runner stage of `Dockerfile` for seamless entrypoint `prisma migrate deploy`.
- Refined `docker-compose.yml` and `.env.example` to support standalone 2-file deployment (`docker-compose.yml` + `.env`) without cloning source code, public registry images (`CHECKFLOW_IMAGE`), Watchtower auto-updating labels, and automated database migrations. - Refined `docker-compose.yml` and `.env.example` to support standalone 2-file deployment (`docker-compose.yml` + `.env`) without cloning source code, public registry images (`CHECKFLOW_IMAGE`), Watchtower auto-updating labels, and automated database migrations.
- Automated Gitea Actions CI workflow (`.gitea/workflows/docker-build.yaml`) for building & pushing container images to Gitea Container Registry with lowercase repository name normalization (`env.REPO`) and OCI source labels (`org.opencontainers.image.source`) for automatic repository package linkage. - Automated Gitea Actions CI workflow (`.gitea/workflows/docker-build.yaml`) for building & pushing container images to Gitea Container Registry with lowercase repository name normalization (`env.REPO`) and OCI source labels (`org.opencontainers.image.source`) for automatic repository package linkage.
- Completely revamped `README.md` to reflect universal 2-file Docker deployment for general users, Watchtower auto-update, architecture, and open-standard CalDAV sync guidelines. - Completely revamped `README.md` to reflect universal 2-file Docker deployment for general users, Watchtower auto-update, architecture, and open-standard CalDAV sync guidelines.
- **Enhanced Task Row UX & Interactive Undo Toast (v0.6.3)**: - Enhanced Task Row UX & Interactive Undo Toast (v0.6.3): Notion-style 6-dot drag handle (`⋮⋮`), DND reordering, `F2` inline editing, right-aligned subtask button, and 6-second floating Undo toast banner.
- Added Notion-style 6-dot drag handle on each task row with drag-and-drop task reordering.
- Added `F2` shortcut key and double-click to trigger inline title editing.
- Moved "+ Subtask" button to the right-aligned hover area and removed redundant explicit edit pencil button.
- Implemented floating interactive Undo Toast banner with 6-second grace period for soft delete and instant undo.
- Fixed Trash and task tree duplicate rendering in LocalStorage demo store.
- Validated that `npm run lint` and `npm run build` execute with 0 errors and 0 warnings. - Validated that `npm run lint` and `npm run build` execute with 0 errors and 0 warnings.
- **Next Steps (Todo):** - **Next Steps (Todo):**
@@ -46,5 +42,5 @@
- **Sanitization**: Never render raw HTML without DOMPurify; always sanitize CSV/ICS user inputs against formula injection (`sanitizeFormula`). - **Sanitization**: Never render raw HTML without DOMPurify; always sanitize CSV/ICS user inputs against formula injection (`sanitizeFormula`).
- **Admin Gatekeeping**: Admin routes must strictly require `role === "ADMIN"` both on server/middleware and client. - **Admin Gatekeeping**: Admin routes must strictly require `role === "ADMIN"` both on server/middleware and client.
- **User Customization Priority**: Any new UI preferences (sizes, positions, order) must be persisted through `userPrefs.ts` and respect the "Reset to Defaults" Labs option. - **User Customization Priority**: Any new UI preferences (sizes, positions, order) must be persisted through `userPrefs.ts` and respect the "Reset to Defaults" Labs option.
- **i18n Consistency**: Any newly added UI string must be translated across all 3 language dictionaries (`en`, `ko`, `ja`) in `src/lib/i18n/translations.ts` and `src/lib/i18n/index.tsx`. - **i18n Consistency**: Any newly added UI string must be translated across all 3 language dictionaries (`en`, `ko`, `ja`) in `src/lib/i18n/locales/`.
- **Mobile Bottom-Sheet Convention**: Maintain touch gestures (`mobile-open` class, Y-axis swipe threshold > 120px) for the detail panel on mobile screens. - **Mobile Bottom-Sheet Convention**: Maintain touch gestures (`mobile-open` class, Y-axis swipe threshold > 120px) for the detail panel on mobile screens.
+2 -1
View File
@@ -82,7 +82,8 @@ src/
| `checkpoint-v0.6.0` | 글로벌 `UserPrefs` 시스템(`userPrefs.ts` / `useUserPrefs.ts`) 도입, 사이드바 드래그 리사이저, 디테일 패널 드래그 리사이저, Labs 탭 확장(밀도/폰트/라운드/애니메이션/색조/채도/패널 너비), 뷰 전환(리스트↔칸반) 실시간 반응, 파스텔 매트 라이트 팔레트 적용 | 2026-08-21 | | `checkpoint-v0.6.0` | 글로벌 `UserPrefs` 시스템(`userPrefs.ts` / `useUserPrefs.ts`) 도입, 사이드바 드래그 리사이저, 디테일 패널 드래그 리사이저, Labs 탭 확장(밀도/폰트/라운드/애니메이션/색조/채도/패널 너비), 뷰 전환(리스트↔칸반) 실시간 반응, 파스텔 매트 라이트 팔레트 적용 | 2026-08-21 |
| `checkpoint-v0.6.1` | CalDAV / DAVx⁵ 양방향 동기화 프로토콜 고도화(OPTIONS, PROPFIND, REPORT, PUT, DELETE), 설정 모달 내 플랫폼별(Android DAVx⁵ / Apple Reminders / Thunderbird) 인터랙티브 가이드 및 실시간 엔드포인트 테스트 핑, 다국어 i18n 동기화 | 2026-08-21 | | `checkpoint-v0.6.1` | CalDAV / DAVx⁵ 양방향 동기화 프로토콜 고도화(OPTIONS, PROPFIND, REPORT, PUT, DELETE), 설정 모달 내 플랫폼별(Android DAVx⁵ / Apple Reminders / Thunderbird) 인터랙티브 가이드 및 실시간 엔드포인트 테스트 핑, 다국어 i18n 동기화 | 2026-08-21 |
| `checkpoint-v0.6.2` | 표준 태스크 내보내기(Export) 기능 구현: TickTick/RFC 4180 호환 CSV (UTF-8 BOM 포함) 및 RFC 5545 iCalendar (`.ics` VTODO), 사이드바 프로필 메뉴 연동, 데모 모드 로컬 Blob 내보내기 지원, 다국어 사전 동기화 | 2026-08-21 | | `checkpoint-v0.6.2` | 표준 태스크 내보내기(Export) 기능 구현: TickTick/RFC 4180 호환 CSV (UTF-8 BOM 포함) 및 RFC 5545 iCalendar (`.ics` VTODO), 사이드바 프로필 메뉴 연동, 데모 모드 로컬 Blob 내보내기 지원, 다국어 사전 동기화 | 2026-08-21 |
| `checkpoint-v0.6.3` | **(현재 최신)** 노션 스타일 6도트 드래그 핸들(`⋮⋮`) & DND 태스크 재정렬, F2 단축키 인라인 제목 수정, 우측 호버 서브태스크 추가 버튼, 6초 플로팅 인터랙티브 실행 취소(Undo) 토스트 배너 탑재, 휴지통 및 로컬 스토리지 트리 중복 버그 수정 | 2026-08-21 | | `checkpoint-v0.6.3` | 노션 스타일 6도트 드래그 핸들(`⋮⋮`) & DND 태스크 재정렬, F2 단축키 인라인 제목 수정, 우측 호버 서브태스크 추가 버튼, 6초 플로팅 인터랙티브 실행 취소(Undo) 토스트 배너 탑재, 휴지통 및 로컬 스토리지 트리 중복 버그 수정 | 2026-08-21 |
| `checkpoint-v0.6.4` | **(현재 최신)** i18n 모듈러 사전 아키텍처(`types.ts`, `locales/en.ts`, `locales/ko.ts`, `locales/ja.ts`) 분리 및 번역 완성도 100%, 어드민 콘솔(`/admin`) 데모 프리뷰 및 멀티유저 게이트 연동, 설정 모달 Admin 탭 정보 고도화, 사이드바 프로필 메뉴 내 Admin Console 진입점 추가 | 2026-08-21 |
--- ---
+35 -22
View File
@@ -1,9 +1,9 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { getDemoStore } from "@/lib/mockData"; import { getDemoStore } from "@/lib/mockData";
import { useI18n } from "@/lib/i18n";
interface AdminUser { interface AdminUser {
id: string; id: string;
@@ -46,8 +46,8 @@ const INITIAL_ADMIN_USERS: AdminUser[] = [
]; ];
export default function AdminPage() { export default function AdminPage() {
const router = useRouter();
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const { t } = useI18n();
const [users, setUsers] = useState<AdminUser[]>(INITIAL_ADMIN_USERS); const [users, setUsers] = useState<AdminUser[]>(INITIAL_ADMIN_USERS);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -66,39 +66,40 @@ export default function AdminPage() {
return 9; return 9;
}); });
// Is this running in Demo mode (no session or unauthenticated)
const isDemo = status === "unauthenticated" || !session;
// 어드민 권한 판별 // 어드민 권한 판별
const userRole = session?.user?.role; const userRole = session?.user?.role;
const userEmail = session?.user?.email; const userEmail = session?.user?.email;
const isAdmin = userRole === "ADMIN" || userEmail?.endsWith("@checkflow.local") || userEmail?.startsWith("admin@"); const isAuthorized =
isDemo ||
userRole === "ADMIN" ||
userEmail?.endsWith("@checkflow.local") ||
userEmail?.startsWith("admin@") ||
userEmail === "admin@checkflow.local";
// 인증 게이트: 비로그인 시 /login으로 리디렉션 // 로딩 중
useEffect(() => { if (status === "loading") {
if (status === "unauthenticated") {
router.replace("/login?callbackUrl=/admin");
}
}, [status, router]);
// 로딩 중 스피너
if (status === "loading" || status === "unauthenticated") {
return ( return (
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-primary)" }}> <div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-primary)" }}>
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<div style={{ fontSize: 32, marginBottom: 12 }}>🔒</div> <div style={{ fontSize: 32, marginBottom: 12 }}>🔒</div>
<p style={{ color: "var(--text-secondary)" }}>Checking administrator credentials...</p> <p style={{ color: "var(--text-secondary)" }}>{t("loading") || "Checking administrator credentials..."}</p>
</div> </div>
</div> </div>
); );
} }
// 비인가 사용자(일반 유저) 차단 화면 // 비인가 사용자(일반 유저) 차단 화면
if (!isAdmin) { if (!isAuthorized) {
return ( return (
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-primary)", color: "var(--text-primary)", padding: 24 }}> <div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--bg-primary)", color: "var(--text-primary)", padding: 24 }}>
<div style={{ maxWidth: 440, width: "100%", background: "var(--bg-secondary)", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", padding: 32, textAlign: "center" }}> <div style={{ maxWidth: 460, width: "100%", background: "var(--bg-secondary)", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", padding: 32, textAlign: "center" }}>
<div style={{ fontSize: 44, marginBottom: 16 }}>🛡</div> <div style={{ fontSize: 44, marginBottom: 16 }}>🛡</div>
<h2 style={{ fontSize: 20, fontWeight: 700, margin: "0 0 8px" }}>Access Restricted</h2> <h2 style={{ fontSize: 20, fontWeight: 700, margin: "0 0 8px" }}>{t("accessRestricted") || "Access Restricted"}</h2>
<p style={{ fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.6, margin: "0 0 24px" }}> <p style={{ fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.6, margin: "0 0 24px" }}>
This console requires <strong>ADMIN</strong> privileges. Your account (<code>{session?.user?.email}</code>) does not have authorization to view multi-user system telemetry. {t("adminRequired") || "This console requires ADMIN privileges."} (<code>{session?.user?.email}</code>)
</p> </p>
<div style={{ display: "flex", gap: 12, justifyContent: "center" }}> <div style={{ display: "flex", gap: 12, justifyContent: "center" }}>
<Link href="/demo" className="btn btn-ghost"> <Link href="/demo" className="btn btn-ghost">
@@ -140,14 +141,21 @@ export default function AdminPage() {
return ( return (
<div style={{ minHeight: "100vh", background: "var(--bg-primary)", color: "var(--text-primary)", padding: "24px 32px" }}> <div style={{ minHeight: "100vh", background: "var(--bg-primary)", color: "var(--text-primary)", padding: "24px 32px" }}>
{/* Top Navbar */} {/* Top Navbar */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", borderBottom: "1px solid var(--border)", paddingBottom: 16, marginBottom: 24 }}> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", borderBottom: "1px solid var(--border)", paddingBottom: 16, marginBottom: 24, flexWrap: "wrap", gap: 12 }}>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}> <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<div className="sidebar-logo">👑</div> <div className="sidebar-logo">👑</div>
<div> <div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<h1 style={{ fontSize: 20, fontWeight: 700, margin: 0, letterSpacing: -0.5 }}> <h1 style={{ fontSize: 20, fontWeight: 700, margin: 0, letterSpacing: -0.5 }}>
CheckFlow Admin Console CheckFlow Admin Console
</h1> </h1>
<p style={{ fontSize: 12, color: "var(--text-tertiary)", margin: 0 }}> {isDemo && (
<span className="demo-badge" style={{ fontSize: 11, padding: "2px 8px" }}>
Demo Preview
</span>
)}
</div>
<p style={{ fontSize: 12, color: "var(--text-tertiary)", margin: "2px 0 0" }}>
Multi-user platform & self-hosted instance management Multi-user platform & self-hosted instance management
</p> </p>
</div> </div>
@@ -155,10 +163,10 @@ export default function AdminPage() {
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 8 }}>
<Link href="/demo" className="btn btn-ghost btn-sm"> <Link href="/demo" className="btn btn-ghost btn-sm">
Back to App (Demo) Back to Demo
</Link> </Link>
<Link href="/" className="btn btn-primary btn-sm"> <Link href="/" className="btn btn-primary btn-sm">
🚀 Open Main Dashboard 🚀 Open Dashboard
</Link> </Link>
</div> </div>
</div> </div>
@@ -193,7 +201,12 @@ export default function AdminPage() {
{/* User Management Section */} {/* User Management Section */}
<div style={{ background: "var(--bg-secondary)", border: "1px solid var(--border)", borderRadius: "var(--radius-md)", padding: 20 }}> <div style={{ background: "var(--bg-secondary)", border: "1px solid var(--border)", borderRadius: "var(--radius-md)", padding: 20 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, flexWrap: "wrap", gap: 12 }}> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, flexWrap: "wrap", gap: 12 }}>
<div>
<h2 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Registered Users & Privacy Isolation</h2> <h2 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Registered Users & Privacy Isolation</h2>
<p style={{ fontSize: 12, color: "var(--text-secondary)", margin: "4px 0 0" }}>
Control multi-user accounts, promote administrators, and enforce workspace separation.
</p>
</div>
<input <input
className="form-input" className="form-input"
placeholder="Search users by name or email..." placeholder="Search users by name or email..."
+13
View File
@@ -739,6 +739,19 @@ export function Sidebar({
{t("exportTasks")} {t("exportTasks")}
</div> </div>
<a
href="/admin"
className="dropdown-item"
id="sidebar-admin-menu-item"
style={{ textDecoration: "none", color: "inherit" }}
onClick={() => setUserMenuOpen(false)}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
{t("admin") || "Admin Console"}
</a>
<div className="dropdown-divider" /> <div className="dropdown-divider" />
{isDemo ? ( {isDemo ? (
+67 -6
View File
@@ -8,7 +8,7 @@ import { useUserPrefs } from "@/lib/useUserPrefs";
interface SettingsModalProps { interface SettingsModalProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
user: { id: string; name?: string | null; email?: string | null }; user: { id: string; name?: string | null; email?: string | null; role?: string | null };
isDemo?: boolean; isDemo?: boolean;
} }
@@ -125,7 +125,7 @@ function PrefSlider({
); );
} }
export function SettingsModal({ isOpen, onClose, user, isDemo: _isDemo = false }: SettingsModalProps) { export function SettingsModal({ isOpen, onClose, user, isDemo = false }: SettingsModalProps) {
const { t, lang, setLang } = useI18n(); const { t, lang, setLang } = useI18n();
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
const { prefs, updatePrefs, resetToDefaults } = useUserPrefs(); const { prefs, updatePrefs, resetToDefaults } = useUserPrefs();
@@ -660,14 +660,75 @@ export function SettingsModal({ isOpen, onClose, user, isDemo: _isDemo = false }
{/* Tab 4: Admin Quick Access */} {/* Tab 4: Admin Quick Access */}
{activeTab === "admin" && ( {activeTab === "admin" && (
<div className="settings-tab-content"> <div className="settings-tab-content">
<h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>👑 Multi-User Admin Console</h3> <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }}>
<p style={{ fontSize: 13, color: "var(--text-secondary)", marginBottom: 14 }}> <span style={{ fontSize: 24 }}>👑</span>
Manage registered users, user roles (User/Admin), system statistics, and storage allocations. <div>
<h3 style={{ fontSize: 15, fontWeight: 700, margin: 0 }}>
{t("admin") || "Admin Console"}
</h3>
<p style={{ fontSize: 12, color: "var(--text-tertiary)", margin: "2px 0 0" }}>
Multi-user administration, account control, and system telemetry
</p> </p>
<a href="/admin" className="btn btn-primary" style={{ display: "inline-flex", alignItems: "center", gap: 8 }}> </div>
</div>
<div
style={{
background: "var(--bg-secondary)",
borderRadius: "var(--radius-md)",
padding: 16,
border: "1px solid var(--border)",
marginBottom: 16,
fontSize: 13,
lineHeight: 1.6,
}}
>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 10, flexWrap: "wrap", gap: 8 }}>
<div>
<strong style={{ color: "var(--text-primary)" }}>Current Account:</strong>{" "}
<code style={{ background: "var(--bg-primary)", padding: "2px 6px", borderRadius: 4, border: "1px solid var(--border)" }}>
{user.email || "demo@checkflow.local"}
</code>
</div>
<div>
<span
style={{
padding: "2px 8px",
borderRadius: "var(--radius-sm)",
fontSize: 11,
fontWeight: 700,
background: "rgba(75, 123, 245, 0.15)",
color: "var(--accent)",
border: "1px solid var(--border)",
}}
>
{user.role || (isDemo ? "DEMO_ADMIN" : "USER")}
</span>
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginTop: 12 }}>
<div style={{ background: "var(--bg-primary)", padding: "10px 12px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border)" }}>
<div style={{ fontSize: 11, color: "var(--text-tertiary)" }}>Isolation Mode</div>
<div style={{ fontSize: 13, fontWeight: 600, marginTop: 2, color: "var(--success)" }}> Multi-User Privacy</div>
</div>
<div style={{ background: "var(--bg-primary)", padding: "10px 12px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border)" }}>
<div style={{ fontSize: 11, color: "var(--text-tertiary)" }}>CalDAV Endpoint</div>
<div style={{ fontSize: 13, fontWeight: 600, marginTop: 2, color: "var(--accent)" }}> Active / Enabled</div>
</div>
</div>
</div>
<div style={{ display: "flex", gap: 10 }}>
<a
href="/admin"
className="btn btn-primary"
style={{ display: "inline-flex", alignItems: "center", gap: 8, textDecoration: "none" }}
>
🚀 Open Admin Dashboard 🚀 Open Admin Dashboard
</a> </a>
</div> </div>
</div>
)} )}
</div> </div>
+7 -548
View File
@@ -1,556 +1,15 @@
"use client"; "use client";
import React, { createContext, useContext, useState, useEffect } from "react"; import React, { createContext, useContext, useState, useEffect } from "react";
import { Language, TranslationKey, TranslationDict } from "./types";
import { translations, en, ko, ja } from "./locales";
export type Language = "en" | "ko" | "ja"; export type { Language, TranslationKey, TranslationDict };
export { translations, en, ko, ja };
export const translations = {
en: {
// Auth
appName: "CheckFlow",
tagline: "Your Personal Todo & Markdown Workspace",
welcomeBack: "Welcome back",
signInSubtitle: "Sign in to your account",
createAccount: "Create account",
createAccountSubtitle: "Start organizing your tasks today",
displayName: "Display Name",
email: "Email",
password: "Password",
min8Chars: "Min. 8 characters",
signIn: "Sign in",
signingIn: "Signing in...",
createBtn: "Create account",
creatingBtn: "Creating account...",
alreadyHaveAccount: "Already have an account?",
dontHaveAccount: "Don't have an account?",
tryDemoMode: "Try Demo Mode (No DB Required)",
demoBadge: "Demo Mode",
signOut: "Sign out",
// Sidebar
lists: "Lists",
newList: "New List",
listNamePlaceholder: "List name",
create: "Create",
cancel: "Cancel",
save: "Save",
deleteListConfirm: "Delete this list and all its tasks?",
undoDelete: "Undo",
listDeleted: "List deleted",
importTasks: "Import Tasks",
importModalTitle: "Import Tasks",
targetList: "Target List",
fileSelectLabel: "File (CSV or ICS from TickTick)",
tickTickExportHint: "TickTick: Settings → Export → Export as CSV or iCalendar",
importBtn: "Import",
importing: "Importing...",
exportTasks: "Export Tasks",
exportModalTitle: "Export Tasks",
exportFormat: "Format",
exportScope: "Target List",
allLists: "All Lists (Entire Tasks)",
includeCompleted: "Include Completed Tasks",
exportBtn: "Export & Download",
exportSuccess: "Tasks exported successfully!",
theme: "Theme",
themeSystem: "System",
themeLight: "Light",
themeDark: "Dark",
language: "Language",
tags: "Tags",
trash: "Trash",
emptyTrash: "Empty Trash",
searchPlaceholder: "Search tasks, notes, tags...",
searchExpandedPlaceholder: "Search anything — tasks, notes, tags (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "Settings",
profile: "Profile",
preferences: "Preferences",
syncIntegrations: "Integrations (CalDAV)",
admin: "Admin",
trashRetention: "Trash Retention Period",
trashRetentionHint: "Deleted tasks will be permanently removed after the specified period.",
days7: "7 Days",
days14: "14 Days",
days30: "30 Days (Recommended)",
neverDelete: "Never Auto-Delete",
// CalDAV & Sync
syncTitle: "CalDAV & Mobile Sync (Android / Apple)",
syncDesc: "CheckFlow supports native two-way synchronization with Android, Apple Reminders, and Thunderbird via standard CalDAV.",
syncBaseUrl: "CalDAV Server Base URL",
syncCopyUrl: "Copy URL",
syncCopied: "Copied!",
syncDownloadIcs: "Download .ICS Feed",
syncTestConnection: "Test Endpoint",
syncTesting: "Testing...",
syncTestSuccess: "✓ CalDAV endpoint responded successfully (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple Reminders (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Install DAVx⁵ from Google Play Store or F-Droid.",
syncAndroidStep2: "Open DAVx⁵ → Add Account → Select 'Login with URL and user name'.",
syncAndroidStep3: "Paste the Base URL above, then enter your CheckFlow email & password.",
syncAndroidStep4: "Enable 'VTODO (Tasks)' collection to sync with Android Tasks or OpenTasks.",
syncAppleStep1: "Open Settings → Reminders → Accounts → Add Account.",
syncAppleStep2: "Select 'Other' → Add CalDAV Account.",
syncAppleStep3: "Server: paste base URL without protocol, User/Pass: CheckFlow credentials.",
syncAppleStep4: "Toggle 'Reminders' ON and tap Save.",
syncThunderbirdStep1: "Open Thunderbird → Switch to Calendar view → New Calendar.",
syncThunderbirdStep2: "Select 'On the Network' → Format: CalDAV.",
syncThunderbirdStep3: "Location: paste the Base URL, Username: CheckFlow email.",
syncThunderbirdStep4: "Enter your password when prompted to finalize sync.",
// Task List
tasks: "Tasks",
hideDone: "Hide done",
showDone: "Show done",
completedSection: "Completed",
addTaskPlaceholder: "Add a task...",
add: "Add",
noTasksYet: "No tasks yet. Add one below!",
selectListToStart: "Select a list to get started",
loading: "Loading...",
today: "Today",
tomorrow: "Tomorrow",
// Task Detail
taskTitlePlaceholder: "Task title",
priority: "Priority",
priorityNone: "None",
priorityLow: "Low",
priorityMedium: "Medium",
priorityHigh: "High",
dueDate: "Due Date",
clear: "Clear",
notes: "Notes",
notesPlaceholder: "Add notes, details, or anything you need to remember…\n\nMarkdown supported: **bold**, *italic*, # heading, - list, - [ ] checkbox",
subtasks: "Sub-tasks",
addSubtaskPlaceholder: "Add sub-task…",
deleteTaskConfirm: "Delete this task?",
autoSaved: "Auto-saved",
saving: "Saving…",
created: "Created",
edited: "Edited",
// Toolbars
bold: "Bold",
italic: "Italic",
heading: "Heading",
bulletList: "Bullet list",
numberedList: "Numbered list",
checkbox: "Checkbox",
code: "Code",
// TickTick Mode Switcher & Quick Add
textMode: "Text Note Mode",
subtaskMode: "Checklist Mode",
switchToTextMode: "Switch to Markdown Note",
switchToSubtaskMode: "Switch to Checklist Sub-tasks",
moveToList: "Move to List",
quickAdd: "Quick Add",
quickToday: "Today",
quickTomorrow: "Tomorrow",
quickNextWeek: "Next Week",
selectDate: "Date",
selectPriority: "Priority",
selectTag: "Tag",
copied: "Copied!",
// Context Menu
editTask: "Edit",
duplicateTask: "Duplicate",
moveTask: "Move to…",
deleteTask: "Delete",
renameList: "Rename",
deleteList: "Delete list",
taskUndoHint: "Deleted. Tap to undo.",
listUndoHint: "List deleted. Tap to undo.",
// Labs, Kanban & Customization
labs: "Labs",
labsDesc: "Enable or disable experimental features individually.",
kanbanView: "Kanban Board",
listView: "List View",
enableKanban: "Kanban Board View",
enableKanbanDesc: "Switch between list and 3-column board (To Do / Doing / Done).",
swapBlocks: "Swap Position",
resetDefaults: "Reset to Defaults",
resetConfirm: "Reset all UI customizations to factory defaults?",
todoCol: "To Do",
inProgressCol: "In Progress",
doneCol: "Done",
accessRestricted: "Access Restricted",
adminRequired: "This console requires ADMIN role.",
},
ko: {
// Auth
appName: "CheckFlow",
tagline: "나만의 스마트 할 일 & 마크다운 워크스페이스",
welcomeBack: "다시 오신 것을 환영합니다 👋",
signInSubtitle: "이메일 계정으로 로그인하세요",
createAccount: "새 계정 생성",
createAccountSubtitle: "CheckFlow에서 효율적인 할 일 관리를 시작하세요",
displayName: "사용자 이름",
email: "이메일 주소",
password: "비밀번호",
min8Chars: "8자 이상 입력",
signIn: "로그인",
signingIn: "로그인 중...",
createBtn: "계정 생성",
creatingBtn: "계정 생성 중...",
alreadyHaveAccount: "이미 계정이 있으신가요?",
dontHaveAccount: "아직 계정이 없으신가요?",
tryDemoMode: "체험 모드로 둘러보기 (DB 불필요)",
demoBadge: "체험 모드",
signOut: "로그아웃",
// Sidebar
lists: "목록",
newList: "새 목록",
listNamePlaceholder: "목록 이름 입력",
create: "생성",
cancel: "취소",
save: "저장",
deleteListConfirm: "이 목록을 삭제하시겠습니까? 포함된 모든 할 일도 함께 삭제됩니다.",
undoDelete: "실행 취소",
listDeleted: "목록이 삭제되었습니다",
importTasks: "가져오기",
importModalTitle: "할 일 가져오기",
targetList: "가져올 대상 목록",
fileSelectLabel: "파일 선택 (TickTick CSV 또는 ICS)",
tickTickExportHint: "TickTick: 설정 → 데이터 내보내기 → CSV 또는 iCalendar 선택",
importBtn: "가져오기",
importing: "가져오는 중...",
exportTasks: "내보내기",
exportModalTitle: "할 일 내보내기",
exportFormat: "내보내기 형식",
exportScope: "내보낼 대상 목록",
allLists: "모든 목록 (전체 할 일)",
includeCompleted: "완료된 항목 포함",
exportBtn: "내보내기 및 다운로드",
exportSuccess: "할 일을 성공적으로 내보냈습니다!",
theme: "테마",
themeSystem: "시스템 설정 동기화",
themeLight: "라이트 모드",
themeDark: "다크 모드",
language: "언어 (Language)",
tags: "태그",
trash: "휴지통",
emptyTrash: "휴지통 비우기",
searchPlaceholder: "할 일, 메모, 태그 검색...",
searchExpandedPlaceholder: "할 일, 메모, 태그 전체 검색 (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "환경설정",
profile: "프로필",
preferences: "개인화 설정",
syncIntegrations: "외부 연동 (CalDAV)",
admin: "관리자 설정",
trashRetention: "휴지통 자동 보관 기간",
trashRetentionHint: "지정한 보관 기간이 경과한 삭제 항목은 자동으로 영구 삭제됩니다.",
days7: "7일 후 영구 삭제",
days14: "14일 후 영구 삭제",
days30: "30일 후 영구 삭제 (권장)",
neverDelete: "자동 삭제 안 함 (수동 삭제)",
// CalDAV & Sync
syncTitle: "CalDAV 모바일 및 외부 앱 동기화",
syncDesc: "Android(DAVx⁵, Tasks.org), Apple 미리알림, Thunderbird 등 표준 CalDAV를 지원하는 모든 기기 및 앱과 실시간 양방향으로 동기화됩니다.",
syncBaseUrl: "CalDAV 기본 엔드포인트 URL",
syncCopyUrl: "URL 복사",
syncCopied: "복사 완료!",
syncDownloadIcs: ".ICS 피드 다운로드",
syncTestConnection: "연결 테스트",
syncTesting: "연결 확인 중...",
syncTestSuccess: "✓ CalDAV 엔드포인트가 정상적으로 응답합니다 (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple 미리알림 (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Google Play Store 또는 F-Droid에서 'DAVx⁵' 앱을 설치합니다.",
syncAndroidStep2: "DAVx⁵ 실행 → 계정 추가(+) → 'URL 및 사용자 이름으로 로그인'을 선택합니다.",
syncAndroidStep3: "위 기본 URL을 입력하고, CheckFlow 계정 이메일과 비밀번호를 입력합니다.",
syncAndroidStep4: "'VTODO (할 일)' 컬렉션을 활성화하면 Tasks.org 또는 OpenTasks 앱과 실시간 동기화됩니다.",
syncAppleStep1: "기기 설정 → 미리알림 → 계정 → '계정 추가'를 선택합니다.",
syncAppleStep2: "'기타' 선택 → 'CalDAV 계정 추가'를 선택합니다.",
syncAppleStep3: "서버 주소에 위 URL을 입력하고, 사용자 이름/비밀번호에 CheckFlow 계정 정보를 입력합니다.",
syncAppleStep4: "'미리알림' 항목을 활성화하고 저장을 누릅니다.",
syncThunderbirdStep1: "Thunderbird 실행 → 캘린더 탭으로 이동 → '새 캘린더'를 선택합니다.",
syncThunderbirdStep2: "'네트워크에 저장' 선택 → 형식: 'CalDAV'를 선택합니다.",
syncThunderbirdStep3: "위치에 위 기본 URL을 입력하고, 사용자 이름에 CheckFlow 계정 이메일을 입력합니다.",
syncThunderbirdStep4: "인증 대화상자가 표시되면 비밀번호를 입력하여 완료합니다.",
// Task List
tasks: "할 일",
hideDone: "완료 항목 숨기기",
showDone: "완료 항목 표시",
completedSection: "완료된 항목",
addTaskPlaceholder: "새 할 일 추가...",
add: "추가",
noTasksYet: "등록된 할 일이 없습니다. 새로운 할 일을 추가해보세요!",
selectListToStart: "목록을 선택하여 할 일을 확인하세요",
loading: "불러오는 중...",
today: "오늘",
tomorrow: "내일",
// Task Detail
taskTitlePlaceholder: "할 일 제목",
priority: "우선순위",
priorityNone: "지정 안 함",
priorityLow: "낮음",
priorityMedium: "보통",
priorityHigh: "높음",
dueDate: "마감일",
clear: "초기화",
notes: "메모",
notesPlaceholder: "자유롭게 메모를 작성하세요…\n\n마크다운 문법 지원: **굵게**, *기울임*, # 제목, - 목록, - [ ] 체크박스 등",
subtasks: "하위 작업",
addSubtaskPlaceholder: "하위 작업 추가…",
deleteTaskConfirm: "이 할 일을 삭제하시겠습니까?",
autoSaved: "자동 저장 완료",
saving: "저장 중…",
created: "생성 일시",
edited: "수정 일시",
// Toolbars
bold: "굵게",
italic: "기울임",
heading: "제목",
bulletList: "글머리 기호 목록",
numberedList: "번호 매기기 목록",
checkbox: "체크박스",
code: "코드 블록",
// TickTick Mode Switcher & Quick Add
textMode: "마크다운 메모 모드",
subtaskMode: "체크리스트 모드",
switchToTextMode: "마크다운 메모 모드로 전환",
switchToSubtaskMode: "하위 체크리스트 모드로 전환",
moveToList: "다른 목록으로 이동",
quickAdd: "빠른 추가",
quickToday: "오늘",
quickTomorrow: "내일",
quickNextWeek: "다음 주",
selectDate: "마감일 지정",
selectPriority: "우선순위 지정",
selectTag: "태그 지정",
copied: "복사 완료!",
// Context Menu
editTask: "수정",
duplicateTask: "복제",
moveTask: "이동…",
deleteTask: "삭제",
renameList: "이름 변경",
deleteList: "목록 삭제",
taskUndoHint: "삭제되었습니다. 클릭하여 되돌리기",
listUndoHint: "목록이 삭제되었습니다. 클릭하여 되돌리기",
// Labs, Kanban & Customization
labs: "실험실 (Labs)",
labsDesc: "실험적인 기능들을 개별적으로 활성화하거나 비활성화할 수 있습니다.",
kanbanView: "칸반 보드",
listView: "리스트 뷰",
enableKanban: "칸반 보드 뷰",
enableKanbanDesc: "할 일을 3컬럼 보드(할 일 / 진행 중 / 완료) 형태로 전환하여 관리합니다.",
swapBlocks: "상하 블록 위치 변경",
resetDefaults: "기본값으로 복원",
resetConfirm: "모든 UI 커스텀 설정을 초기 기본값으로 복원하시겠습니까?",
todoCol: "할 일",
inProgressCol: "진행 중",
doneCol: "완료",
accessRestricted: "접근 권한 제한",
adminRequired: "이 관리 콘솔은 ADMIN 권한이 필요합니다.",
},
ja: {
// Auth
appName: "CheckFlow",
tagline: "シンプルで洗練されたパーソナルタスク管理",
welcomeBack: "お帰りなさい 👋",
signInSubtitle: "メールアドレスでサインインしてください",
createAccount: "アカウント新規登録",
createAccountSubtitle: "CheckFlowでタスクの整理を始めましょう",
displayName: "表示名",
email: "メールアドレス",
password: "パスワード",
min8Chars: "8文字以上",
signIn: "サインイン",
signingIn: "サインイン中...",
createBtn: "アカウントを作成",
creatingBtn: "アカウント作成中...",
alreadyHaveAccount: "すでにアカウントをお持ちですか?",
dontHaveAccount: "アカウントをお持ちでないですか?",
tryDemoMode: "体験モードで試す(DB不要)",
demoBadge: "体験モード",
signOut: "サインアウト",
// Sidebar
lists: "リスト",
newList: "新規リスト",
listNamePlaceholder: "リスト名を入力",
create: "作成",
cancel: "キャンセル",
save: "保存",
deleteListConfirm: "このリストを削除しますか?含まれるすべてのタスクも削除されます。",
undoDelete: "元に戻す",
listDeleted: "リストを削除しました",
importTasks: "インポート",
importModalTitle: "タスクのインポート",
targetList: "インポート先リスト",
fileSelectLabel: "ファイル選択 (TickTick CSV / ICS)",
tickTickExportHint: "TickTick: 設定 → データのエクスポート → CSVまたはiCalendar",
importBtn: "インポート",
importing: "インポート中...",
exportTasks: "エクスポート",
exportModalTitle: "タスクのエクスポート",
exportFormat: "エクスポート形式",
exportScope: "対象リスト",
allLists: "すべてのリスト (全体)",
includeCompleted: "完了したタスクを含める",
exportBtn: "エクスポートしてダウンロード",
exportSuccess: "タスクを正常にエクスポートしました!",
theme: "テーマ",
themeSystem: "システム設定に同期",
themeLight: "ライトモード",
themeDark: "ダークモード",
language: "言語 (Language)",
tags: "タグ",
trash: "ゴミ箱",
emptyTrash: "ゴミ箱を空にする",
searchPlaceholder: "タスク、メモ、タグを検索...",
searchExpandedPlaceholder: "タスク、メモ、タグをすべて検索 (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "環境設定",
profile: "プロフィール",
preferences: "カスタマイズ設定",
syncIntegrations: "外部連携 (CalDAV)",
admin: "管理者設定",
trashRetention: "ゴミ箱の自動保持期間",
trashRetentionHint: "指定した期間が経過した削除済みタスクは自動的に完全に削除されます。",
days7: "7日後に完全削除",
days14: "14日後に完全削除",
days30: "30日後に完全削除 (推奨)",
neverDelete: "自動削除しない (手動削除のみ)",
// CalDAV & Sync
syncTitle: "CalDAV モバイルおよび外部アプリ同期",
syncDesc: "Android (DAVx⁵, Tasks.org)、Apple リマインダー、Thunderbird など標準 CalDAV をサポートする各種端末やアプリと双方向同期できます。",
syncBaseUrl: "CalDAV サーバー基本 URL",
syncCopyUrl: "URLをコピー",
syncCopied: "コピー完了!",
syncDownloadIcs: ".ICS フィードをダウンロード",
syncTestConnection: "接続テスト",
syncTesting: "テスト中...",
syncTestSuccess: "✓ CalDAV エンドポイントは正常に応答しています (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple リマインダー (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Google Play または F-Droid から「DAVx⁵」アプリをインストールします。",
syncAndroidStep2: "DAVx⁵ を起動 → アカウント追加(+) →「URL とユーザー名でログイン」を選択します。",
syncAndroidStep3: "上記の基本 URL を入力し、CheckFlow のログインメールアドレスとパスワードを入力します。",
syncAndroidStep4: "「VTODO (タスク)」コレクションを有効にすると、Tasks.org や OpenTasks と双方向同期されます。",
syncAppleStep1: "端末の設定 → リマインダー → アカウント →「アカウントを追加」を開きます。",
syncAppleStep2: "「その他」を選択 →「CalDAV アカウントを追加」を選択します。",
syncAppleStep3: "サーバーに上記の URL、ユーザー名とパスワードに CheckFlow のアカウント情報を入力します。",
syncAppleStep4: "「リマインダー」をオンにして保存します。",
syncThunderbirdStep1: "Thunderbird を起動 → カレンダータブ →「新しいカレンダー」を作成します。",
syncThunderbirdStep2: "「ネットワーク上」を選択 → 形式:「CalDAV」を選択します。",
syncThunderbirdStep3: "場所に上記の基本 URL、ユーザー名に CheckFlow のメールアドレスを入力します。",
syncThunderbirdStep4: "認証ダイアログでパスワードを入力して設定を完了します。",
// Task List
tasks: "タスク",
hideDone: "完了済みを非表示",
showDone: "完了済みを表示",
completedSection: "完了したタスク",
addTaskPlaceholder: "新しいタスクを入力...",
add: "追加",
noTasksYet: "タスクがありません。下から新しいタスクを追加してみましょう!",
selectListToStart: "リストを選択してタスクを表示してください",
loading: "読み込み中...",
today: "今日",
tomorrow: "明日",
// Task Detail
taskTitlePlaceholder: "タスク名",
priority: "優先度",
priorityNone: "指定なし",
priorityLow: "低",
priorityMedium: "中",
priorityHigh: "高",
dueDate: "期日",
clear: "クリア",
notes: "メモ",
notesPlaceholder: "自由にメモを記入してください…\n\nMarkdown対応: **太字**, *斜体*, # 見出し, - リスト, - [ ] チェックボックス",
subtasks: "サブタスク",
addSubtaskPlaceholder: "サブタスクを追加…",
deleteTaskConfirm: "このタスクを削除しますか?",
autoSaved: "自動保存済み",
saving: "保存中…",
created: "作成日時",
edited: "更新日時",
// Toolbars
bold: "太字",
italic: "斜体",
heading: "見出し",
bulletList: "箇条書きリスト",
numberedList: "番号付きリスト",
checkbox: "チェックボックス",
code: "コードブロック",
// TickTick Mode Switcher & Quick Add
textMode: "Markdownメモモード",
subtaskMode: "チェックリストモード",
switchToTextMode: "Markdownメモモードに切り替え",
switchToSubtaskMode: "チェックリストモードに切り替え",
moveToList: "別のリストに移動",
quickAdd: "クイック追加",
quickToday: "今日",
quickTomorrow: "明日",
quickNextWeek: "来週",
selectDate: "期日設定",
selectPriority: "優先度設定",
selectTag: "タグ設定",
copied: "コピー完了!",
accessRestricted: "アクセス権限制限",
adminRequired: "この管理コンソールにはADMIN権限が必要です。",
// Context Menu
editTask: "編集",
duplicateTask: "複製",
moveTask: "移動…",
deleteTask: "削除",
renameList: "名前を変更",
deleteList: "リストを削除",
taskUndoHint: "削除しました。クリックして元に戻せます。",
listUndoHint: "リストを削除しました。クリックして元に戻せます。",
// Labs, Kanban & Customization
labs: "Labs (実験室)",
labsDesc: "開発中の実験的機能を個別に有効化・無効化できます。",
kanbanView: "カンバンボード",
listView: "リストビュー",
enableKanban: "カンバンボードビュー",
enableKanbanDesc: "タスクを3列ボード(未着手 / 進行中 / 完了)形式で管理します。",
swapBlocks: "ブロック配置の入れ替え",
resetDefaults: "初期設定にリセット",
resetConfirm: "すべてのUIカスタマイズを初期デフォルト設定に戻しますか?",
todoCol: "未着手",
inProgressCol: "進行中",
doneCol: "完了",
},
};
type TranslationKeys = keyof typeof translations.en;
interface I18nContextType { interface I18nContextType {
lang: Language; lang: Language;
setLang: (l: Language) => void; setLang: (l: Language) => void;
t: (key: TranslationKeys) => string; t: (key: TranslationKey) => string;
} }
const I18nContext = createContext<I18nContextType>({ const I18nContext = createContext<I18nContextType>({
@@ -584,8 +43,8 @@ export function I18nProvider({ children }: { children: React.ReactNode }) {
} }
}; };
const t = (key: TranslationKeys): string => { const t = (key: TranslationKey): string => {
const dict = (translations[lang] || translations.en) as Record<string, string>; const dict = translations[lang] || translations.en;
return dict[key] || translations.en[key] || key; return dict[key] || translations.en[key] || key;
}; };
+181
View File
@@ -0,0 +1,181 @@
import { TranslationDict } from "../types";
export const en: TranslationDict = {
// Auth
appName: "CheckFlow",
tagline: "Your Personal Todo & Markdown Workspace",
welcomeBack: "Welcome back",
signInSubtitle: "Sign in to your account",
createAccount: "Create account",
createAccountSubtitle: "Start organizing your tasks today",
displayName: "Display Name",
email: "Email",
password: "Password",
min8Chars: "Min. 8 characters",
signIn: "Sign in",
signingIn: "Signing in...",
createBtn: "Create account",
creatingBtn: "Creating account...",
alreadyHaveAccount: "Already have an account?",
dontHaveAccount: "Don't have an account?",
tryDemoMode: "Try Demo Mode (No DB Required)",
demoBadge: "Demo Mode",
signOut: "Sign out",
// Sidebar
lists: "Lists",
newList: "New List",
listNamePlaceholder: "List name",
create: "Create",
cancel: "Cancel",
save: "Save",
deleteListConfirm: "Delete this list and all its tasks?",
undoDelete: "Undo",
listDeleted: "List deleted",
importTasks: "Import Tasks",
importModalTitle: "Import Tasks",
targetList: "Target List",
fileSelectLabel: "File (CSV or ICS from TickTick)",
tickTickExportHint: "TickTick: Settings → Export → Export as CSV or iCalendar",
importBtn: "Import",
importing: "Importing...",
exportTasks: "Export Tasks",
exportModalTitle: "Export Tasks",
exportFormat: "Format",
exportScope: "Target List",
allLists: "All Lists (Entire Tasks)",
includeCompleted: "Include Completed Tasks",
exportBtn: "Export & Download",
exportSuccess: "Tasks exported successfully!",
theme: "Theme",
themeSystem: "System",
themeLight: "Light",
themeDark: "Dark",
language: "Language",
tags: "Tags",
trash: "Trash",
emptyTrash: "Empty Trash",
searchPlaceholder: "Search tasks, notes, tags...",
searchExpandedPlaceholder: "Search anything — tasks, notes, tags (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "Settings",
profile: "Profile",
preferences: "Preferences",
syncIntegrations: "Integrations (CalDAV)",
admin: "Admin",
trashRetention: "Trash Retention Period",
trashRetentionHint: "Deleted tasks will be permanently removed after the specified period.",
days7: "7 Days",
days14: "14 Days",
days30: "30 Days (Recommended)",
neverDelete: "Never Auto-Delete",
// CalDAV & Sync
syncTitle: "CalDAV & Mobile Sync (Android / Apple)",
syncDesc: "CheckFlow supports native two-way synchronization with Android, Apple Reminders, and Thunderbird via standard CalDAV.",
syncBaseUrl: "CalDAV Server Base URL",
syncCopyUrl: "Copy URL",
syncCopied: "Copied!",
syncDownloadIcs: "Download .ICS Feed",
syncTestConnection: "Test Endpoint",
syncTesting: "Testing...",
syncTestSuccess: "✓ CalDAV endpoint responded successfully (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple Reminders (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Install DAVx⁵ from Google Play Store or F-Droid.",
syncAndroidStep2: "Open DAVx⁵ → Add Account → Select 'Login with URL and user name'.",
syncAndroidStep3: "Paste the Base URL above, then enter your CheckFlow email & password.",
syncAndroidStep4: "Enable 'VTODO (Tasks)' collection to sync with Android Tasks or OpenTasks.",
syncAppleStep1: "Open Settings → Reminders → Accounts → Add Account.",
syncAppleStep2: "Select 'Other' → Add CalDAV Account.",
syncAppleStep3: "Server: paste base URL without protocol, User/Pass: CheckFlow credentials.",
syncAppleStep4: "Toggle 'Reminders' ON and tap Save.",
syncThunderbirdStep1: "Open Thunderbird → Switch to Calendar view → New Calendar.",
syncThunderbirdStep2: "Select 'On the Network' → Format: CalDAV.",
syncThunderbirdStep3: "Location: paste the Base URL, Username: CheckFlow email.",
syncThunderbirdStep4: "Enter your password when prompted to finalize sync.",
// Task List
tasks: "Tasks",
hideDone: "Hide done",
showDone: "Show done",
completedSection: "Completed",
addTaskPlaceholder: "Add a task...",
add: "Add",
noTasksYet: "No tasks yet. Add one below!",
selectListToStart: "Select a list to get started",
loading: "Loading...",
today: "Today",
tomorrow: "Tomorrow",
// Task Detail
taskTitlePlaceholder: "Task title",
priority: "Priority",
priorityNone: "None",
priorityLow: "Low",
priorityMedium: "Medium",
priorityHigh: "High",
dueDate: "Due Date",
clear: "Clear",
notes: "Notes",
notesPlaceholder: "Add notes, details, or anything you need to remember…\n\nMarkdown supported: **bold**, *italic*, # heading, - list, - [ ] checkbox",
subtasks: "Sub-tasks",
addSubtaskPlaceholder: "Add sub-task…",
deleteTaskConfirm: "Delete this task?",
autoSaved: "Auto-saved",
saving: "Saving…",
created: "Created",
edited: "Edited",
// Toolbars
bold: "Bold",
italic: "Italic",
heading: "Heading",
bulletList: "Bullet list",
numberedList: "Numbered list",
checkbox: "Checkbox",
code: "Code",
// TickTick Mode Switcher & Quick Add
textMode: "Text Note Mode",
subtaskMode: "Checklist Mode",
switchToTextMode: "Switch to Markdown Note",
switchToSubtaskMode: "Switch to Checklist Sub-tasks",
moveToList: "Move to List",
quickAdd: "Quick Add",
quickToday: "Today",
quickTomorrow: "Tomorrow",
quickNextWeek: "Next Week",
selectDate: "Date",
selectPriority: "Priority",
selectTag: "Tag",
copied: "Copied!",
// Context Menu
editTask: "Edit",
duplicateTask: "Duplicate",
moveTask: "Move to…",
deleteTask: "Delete",
renameList: "Rename",
deleteList: "Delete list",
taskUndoHint: "Deleted. Tap to undo.",
listUndoHint: "List deleted. Tap to undo.",
// Labs, Kanban & Customization
labs: "Labs",
labsDesc: "Enable or disable experimental features individually.",
kanbanView: "Kanban Board",
listView: "List View",
enableKanban: "Kanban Board View",
enableKanbanDesc: "Switch between list and 3-column board (To Do / Doing / Done).",
swapBlocks: "Swap Position",
resetDefaults: "Reset to Defaults",
resetConfirm: "Reset all UI customizations to factory defaults?",
todoCol: "To Do",
inProgressCol: "In Progress",
doneCol: "Done",
accessRestricted: "Access Restricted",
adminRequired: "This console requires ADMIN role.",
};
+12
View File
@@ -0,0 +1,12 @@
import { Language, TranslationDict } from "../types";
import { en } from "./en";
import { ko } from "./ko";
import { ja } from "./ja";
export const translations: Record<Language, TranslationDict> = {
en,
ko,
ja,
};
export { en, ko, ja };
+181
View File
@@ -0,0 +1,181 @@
import { TranslationDict } from "../types";
export const ja: TranslationDict = {
// Auth
appName: "CheckFlow",
tagline: "シンプルで洗練されたパーソナルタスク管理",
welcomeBack: "お帰りなさい 👋",
signInSubtitle: "メールアドレスでサインインしてください",
createAccount: "アカウント新規登録",
createAccountSubtitle: "CheckFlowでタスクの整理を始めましょう",
displayName: "表示名",
email: "メールアドレス",
password: "パスワード",
min8Chars: "8文字以上",
signIn: "サインイン",
signingIn: "サインイン中...",
createBtn: "アカウントを作成",
creatingBtn: "アカウント作成中...",
alreadyHaveAccount: "すでにアカウントをお持ちですか?",
dontHaveAccount: "アカウントをお持ちでないですか?",
tryDemoMode: "体験モードで試す(DB不要)",
demoBadge: "体験モード",
signOut: "サインアウト",
// Sidebar
lists: "リスト",
newList: "新規リスト",
listNamePlaceholder: "リスト名を入力",
create: "作成",
cancel: "キャンセル",
save: "保存",
deleteListConfirm: "このリストを削除しますか?含まれるすべてのタスクも削除されます。",
undoDelete: "元に戻す",
listDeleted: "リストを削除しました",
importTasks: "インポート",
importModalTitle: "タスクのインポート",
targetList: "インポート先リスト",
fileSelectLabel: "ファイル選択 (TickTick CSV / ICS)",
tickTickExportHint: "TickTick: 設定 → データのエクスポート → CSVまたはiCalendar",
importBtn: "インポート",
importing: "インポート中...",
exportTasks: "エクスポート",
exportModalTitle: "タスクのエクスポート",
exportFormat: "エクスポート形式",
exportScope: "対象リスト",
allLists: "すべてのリスト (全体)",
includeCompleted: "完了したタスクを含める",
exportBtn: "エクスポートしてダウンロード",
exportSuccess: "タスクを正常にエクスポートしました!",
theme: "テーマ",
themeSystem: "システム設定に同期",
themeLight: "ライトモード",
themeDark: "ダークモード",
language: "言語 (Language)",
tags: "タグ",
trash: "ゴミ箱",
emptyTrash: "ゴミ箱を空にする",
searchPlaceholder: "タスク、メモ、タグを検索...",
searchExpandedPlaceholder: "タスク、メモ、タグをすべて検索 (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "環境設定",
profile: "プロフィール",
preferences: "カスタマイズ設定",
syncIntegrations: "外部連携 (CalDAV)",
admin: "管理者設定",
trashRetention: "ゴミ箱の自動保持期間",
trashRetentionHint: "指定した期間が経過した削除済みタスクは自動的に完全に削除されます。",
days7: "7日後に完全削除",
days14: "14日後に完全削除",
days30: "30日後に完全削除 (推奨)",
neverDelete: "自動削除しない (手動削除のみ)",
// CalDAV & Sync
syncTitle: "CalDAV モバイルおよび外部アプリ同期",
syncDesc: "Android (DAVx⁵, Tasks.org)、Apple リマインダー、Thunderbird など標準 CalDAV をサポートする各種端末やアプリと双方向同期できます。",
syncBaseUrl: "CalDAV サーバー基本 URL",
syncCopyUrl: "URLをコピー",
syncCopied: "コピー完了!",
syncDownloadIcs: ".ICS フィードをダウンロード",
syncTestConnection: "接続テスト",
syncTesting: "テスト中...",
syncTestSuccess: "✓ CalDAV エンドポイントは正常に応答しています (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple リマインダー (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Google Play または F-Droid から「DAVx⁵」アプリをインストールします。",
syncAndroidStep2: "DAVx⁵ を起動 → アカウント追加(+) →「URL とユーザー名でログイン」を選択します。",
syncAndroidStep3: "上記の基本 URL を入力し、CheckFlow のログインメールアドレスとパスワードを入力します。",
syncAndroidStep4: "「VTODO (タスク)」コレクションを有効にすると、Tasks.org や OpenTasks と双方向同期されます。",
syncAppleStep1: "端末の設定 → リマインダー → アカウント →「アカウントを追加」を開きます。",
syncAppleStep2: "「その他」を選択 →「CalDAV アカウントを追加」を選択します。",
syncAppleStep3: "サーバーに上記の URL、ユーザー名とパスワードに CheckFlow のアカウント情報を入力します。",
syncAppleStep4: "「リマインダー」をオンにして保存します。",
syncThunderbirdStep1: "Thunderbird を起動 → カレンダータブ →「新しいカレンダー」を作成します。",
syncThunderbirdStep2: "「ネットワーク上」を選択 → 形式:「CalDAV」を選択します。",
syncThunderbirdStep3: "場所に上記の基本 URL、ユーザー名に CheckFlow のメールアドレスを入力します。",
syncThunderbirdStep4: "認証ダイアログでパスワードを入力して設定を完了します。",
// Task List
tasks: "タスク",
hideDone: "完了済みを非表示",
showDone: "完了済みを表示",
completedSection: "完了したタスク",
addTaskPlaceholder: "新しいタスクを入力...",
add: "追加",
noTasksYet: "タスクがありません。下から新しいタスクを追加してみましょう!",
selectListToStart: "リストを選択してタスクを表示してください",
loading: "読み込み中...",
today: "今日",
tomorrow: "明日",
// Task Detail
taskTitlePlaceholder: "タスク名",
priority: "優先度",
priorityNone: "指定なし",
priorityLow: "低",
priorityMedium: "中",
priorityHigh: "高",
dueDate: "期日",
clear: "クリア",
notes: "メモ",
notesPlaceholder: "自由にメモを記入してください…\n\nMarkdown対応: **太字**, *斜体*, # 見出し, - リスト, - [ ] チェックボックス",
subtasks: "サブタスク",
addSubtaskPlaceholder: "サブタスクを追加…",
deleteTaskConfirm: "このタスクを削除しますか?",
autoSaved: "自動保存済み",
saving: "保存中…",
created: "作成日時",
edited: "更新日時",
// Toolbars
bold: "太字",
italic: "斜体",
heading: "見出し",
bulletList: "箇条書きリスト",
numberedList: "番号付きリスト",
checkbox: "チェックボックス",
code: "コードブロック",
// TickTick Mode Switcher & Quick Add
textMode: "Markdownメモモード",
subtaskMode: "チェックリストモード",
switchToTextMode: "Markdownメモモードに切り替え",
switchToSubtaskMode: "チェックリストモードに切り替え",
moveToList: "別のリストに移動",
quickAdd: "クイック追加",
quickToday: "今日",
quickTomorrow: "明日",
quickNextWeek: "来週",
selectDate: "期日設定",
selectPriority: "優先度設定",
selectTag: "タグ設定",
copied: "コピー完了!",
// Context Menu
editTask: "編集",
duplicateTask: "複製",
moveTask: "移動…",
deleteTask: "削除",
renameList: "名前を変更",
deleteList: "リストを削除",
taskUndoHint: "削除しました。クリックして元に戻せます。",
listUndoHint: "リストを削除しました。クリックして元に戻せます。",
// Labs, Kanban & Customization
labs: "Labs (実験室)",
labsDesc: "開発中の実験的機能を個別に有効化・無効化できます。",
kanbanView: "カンバンボード",
listView: "リストビュー",
enableKanban: "カンバンボードビュー",
enableKanbanDesc: "タスクを3列ボード(未着手 / 進行中 / 完了)形式で管理します。",
swapBlocks: "ブロック配置の入れ替え",
resetDefaults: "初期設定にリセット",
resetConfirm: "すべてのUIカスタマイズを初期デフォルト設定に戻しますか?",
todoCol: "未着手",
inProgressCol: "進行中",
doneCol: "完了",
accessRestricted: "アクセス権限制限",
adminRequired: "この管理コンソールにはADMIN権限が必要です。",
};
+181
View File
@@ -0,0 +1,181 @@
import { TranslationDict } from "../types";
export const ko: TranslationDict = {
// Auth
appName: "CheckFlow",
tagline: "나만의 스마트 할 일 & 마크다운 워크스페이스",
welcomeBack: "다시 오신 것을 환영합니다 👋",
signInSubtitle: "이메일 계정으로 로그인하세요",
createAccount: "새 계정 생성",
createAccountSubtitle: "CheckFlow에서 효율적인 할 일 관리를 시작하세요",
displayName: "사용자 이름",
email: "이메일 주소",
password: "비밀번호",
min8Chars: "8자 이상 입력",
signIn: "로그인",
signingIn: "로그인 중...",
createBtn: "계정 생성",
creatingBtn: "계정 생성 중...",
alreadyHaveAccount: "이미 계정이 있으신가요?",
dontHaveAccount: "아직 계정이 없으신가요?",
tryDemoMode: "체험 모드로 둘러보기 (DB 불필요)",
demoBadge: "체험 모드",
signOut: "로그아웃",
// Sidebar
lists: "목록",
newList: "새 목록",
listNamePlaceholder: "목록 이름 입력",
create: "생성",
cancel: "취소",
save: "저장",
deleteListConfirm: "이 목록을 삭제하시겠습니까? 포함된 모든 할 일도 함께 삭제됩니다.",
undoDelete: "실행 취소",
listDeleted: "목록이 삭제되었습니다",
importTasks: "가져오기",
importModalTitle: "할 일 가져오기",
targetList: "가져올 대상 목록",
fileSelectLabel: "파일 선택 (TickTick CSV 또는 ICS)",
tickTickExportHint: "TickTick: 설정 → 데이터 내보내기 → CSV 또는 iCalendar 선택",
importBtn: "가져오기",
importing: "가져오는 중...",
exportTasks: "내보내기",
exportModalTitle: "할 일 내보내기",
exportFormat: "내보내기 형식",
exportScope: "내보낼 대상 목록",
allLists: "모든 목록 (전체 할 일)",
includeCompleted: "완료된 항목 포함",
exportBtn: "내보내기 및 다운로드",
exportSuccess: "할 일을 성공적으로 내보냈습니다!",
theme: "테마",
themeSystem: "시스템 설정 동기화",
themeLight: "라이트 모드",
themeDark: "다크 모드",
language: "언어 (Language)",
tags: "태그",
trash: "휴지통",
emptyTrash: "휴지통 비우기",
searchPlaceholder: "할 일, 메모, 태그 검색...",
searchExpandedPlaceholder: "할 일, 메모, 태그 전체 검색 (Ctrl+K)...",
// Settings Modal
settingsModalTitle: "환경설정",
profile: "프로필",
preferences: "개인화 설정",
syncIntegrations: "외부 연동 (CalDAV)",
admin: "관리자 설정",
trashRetention: "휴지통 자동 보관 기간",
trashRetentionHint: "지정한 보관 기간이 경과한 삭제 항목은 자동으로 영구 삭제됩니다.",
days7: "7일 후 영구 삭제",
days14: "14일 후 영구 삭제",
days30: "30일 후 영구 삭제 (권장)",
neverDelete: "자동 삭제 안 함 (수동 삭제)",
// CalDAV & Sync
syncTitle: "CalDAV 모바일 및 외부 앱 동기화",
syncDesc: "Android(DAVx⁵, Tasks.org), Apple 미리알림, Thunderbird 등 표준 CalDAV를 지원하는 모든 기기 및 앱과 실시간 양방향으로 동기화됩니다.",
syncBaseUrl: "CalDAV 기본 엔드포인트 URL",
syncCopyUrl: "URL 복사",
syncCopied: "복사 완료!",
syncDownloadIcs: ".ICS 피드 다운로드",
syncTestConnection: "연결 테스트",
syncTesting: "연결 확인 중...",
syncTestSuccess: "✓ CalDAV 엔드포인트가 정상적으로 응답합니다 (200 OK)",
syncTabAndroid: "Android (DAVx⁵)",
syncTabApple: "Apple 미리알림 (iOS / macOS)",
syncTabThunderbird: "Thunderbird (Desktop)",
syncAndroidStep1: "Google Play Store 또는 F-Droid에서 'DAVx⁵' 앱을 설치합니다.",
syncAndroidStep2: "DAVx⁵ 실행 → 계정 추가(+) → 'URL 및 사용자 이름으로 로그인'을 선택합니다.",
syncAndroidStep3: "위 기본 URL을 입력하고, CheckFlow 계정 이메일과 비밀번호를 입력합니다.",
syncAndroidStep4: "'VTODO (할 일)' 컬렉션을 활성화하면 Tasks.org 또는 OpenTasks 앱과 실시간 동기화됩니다.",
syncAppleStep1: "기기 설정 → 미리알림 → 계정 → '계정 추가'를 선택합니다.",
syncAppleStep2: "'기타' 선택 → 'CalDAV 계정 추가'를 선택합니다.",
syncAppleStep3: "서버 주소에 위 URL을 입력하고, 사용자 이름/비밀번호에 CheckFlow 계정 정보를 입력합니다.",
syncAppleStep4: "'미리알림' 항목을 활성화하고 저장을 누릅니다.",
syncThunderbirdStep1: "Thunderbird 실행 → 캘린더 탭으로 이동 → '새 캘린더'를 선택합니다.",
syncThunderbirdStep2: "'네트워크에 저장' 선택 → 형식: 'CalDAV'를 선택합니다.",
syncThunderbirdStep3: "위치에 위 기본 URL을 입력하고, 사용자 이름에 CheckFlow 계정 이메일을 입력합니다.",
syncThunderbirdStep4: "인증 대화상자가 표시되면 비밀번호를 입력하여 완료합니다.",
// Task List
tasks: "할 일",
hideDone: "완료 항목 숨기기",
showDone: "완료 항목 표시",
completedSection: "완료된 항목",
addTaskPlaceholder: "새 할 일 추가...",
add: "추가",
noTasksYet: "등록된 할 일이 없습니다. 새로운 할 일을 추가해보세요!",
selectListToStart: "목록을 선택하여 할 일을 확인하세요",
loading: "불러오는 중...",
today: "오늘",
tomorrow: "내일",
// Task Detail
taskTitlePlaceholder: "할 일 제목",
priority: "우선순위",
priorityNone: "지정 안 함",
priorityLow: "낮음",
priorityMedium: "보통",
priorityHigh: "높음",
dueDate: "마감일",
clear: "초기화",
notes: "메모",
notesPlaceholder: "자유롭게 메모를 작성하세요…\n\n마크다운 문법 지원: **굵게**, *기울임*, # 제목, - 목록, - [ ] 체크박스 등",
subtasks: "하위 작업",
addSubtaskPlaceholder: "하위 작업 추가…",
deleteTaskConfirm: "이 할 일을 삭제하시겠습니까?",
autoSaved: "자동 저장 완료",
saving: "저장 중…",
created: "생성 일시",
edited: "수정 일시",
// Toolbars
bold: "굵게",
italic: "기울임",
heading: "제목",
bulletList: "글머리 기호 목록",
numberedList: "번호 매기기 목록",
checkbox: "체크박스",
code: "코드 블록",
// TickTick Mode Switcher & Quick Add
textMode: "마크다운 메모 모드",
subtaskMode: "체크리스트 모드",
switchToTextMode: "마크다운 메모 모드로 전환",
switchToSubtaskMode: "하위 체크리스트 모드로 전환",
moveToList: "다른 목록으로 이동",
quickAdd: "빠른 추가",
quickToday: "오늘",
quickTomorrow: "내일",
quickNextWeek: "다음 주",
selectDate: "마감일 지정",
selectPriority: "우선순위 지정",
selectTag: "태그 지정",
copied: "복사 완료!",
// Context Menu
editTask: "수정",
duplicateTask: "복제",
moveTask: "이동…",
deleteTask: "삭제",
renameList: "이름 변경",
deleteList: "목록 삭제",
taskUndoHint: "삭제되었습니다. 클릭하여 되돌리기",
listUndoHint: "목록이 삭제되었습니다. 클릭하여 되돌리기",
// Labs, Kanban & Customization
labs: "실험실 (Labs)",
labsDesc: "실험적인 기능들을 개별적으로 활성화하거나 비활성화할 수 있습니다.",
kanbanView: "칸반 보드",
listView: "리스트 뷰",
enableKanban: "칸반 보드 뷰",
enableKanbanDesc: "할 일을 3컬럼 보드(할 일 / 진행 중 / 완료) 형태로 전환하여 관리합니다.",
swapBlocks: "상하 블록 위치 변경",
resetDefaults: "기본값으로 복원",
resetConfirm: "모든 UI 커스텀 설정을 초기 기본값으로 복원하시겠습니까?",
todoCol: "할 일",
inProgressCol: "진행 중",
doneCol: "완료",
accessRestricted: "접근 권한 제한",
adminRequired: "이 관리 콘솔은 ADMIN 권한이 필요합니다.",
};
+183
View File
@@ -0,0 +1,183 @@
export type Language = "en" | "ko" | "ja";
export interface TranslationDict {
// Auth
appName: string;
tagline: string;
welcomeBack: string;
signInSubtitle: string;
createAccount: string;
createAccountSubtitle: string;
displayName: string;
email: string;
password: string;
min8Chars: string;
signIn: string;
signingIn: string;
createBtn: string;
creatingBtn: string;
alreadyHaveAccount: string;
dontHaveAccount: string;
tryDemoMode: string;
demoBadge: string;
signOut: string;
// Sidebar
lists: string;
newList: string;
listNamePlaceholder: string;
create: string;
cancel: string;
save: string;
deleteListConfirm: string;
undoDelete: string;
listDeleted: string;
importTasks: string;
importModalTitle: string;
targetList: string;
fileSelectLabel: string;
tickTickExportHint: string;
importBtn: string;
importing: string;
exportTasks: string;
exportModalTitle: string;
exportFormat: string;
exportScope: string;
allLists: string;
includeCompleted: string;
exportBtn: string;
exportSuccess: string;
theme: string;
themeSystem: string;
themeLight: string;
themeDark: string;
language: string;
tags: string;
trash: string;
emptyTrash: string;
searchPlaceholder: string;
searchExpandedPlaceholder: string;
// Settings Modal
settingsModalTitle: string;
profile: string;
preferences: string;
syncIntegrations: string;
admin: string;
trashRetention: string;
trashRetentionHint: string;
days7: string;
days14: string;
days30: string;
neverDelete: string;
// CalDAV & Sync
syncTitle: string;
syncDesc: string;
syncBaseUrl: string;
syncCopyUrl: string;
syncCopied: string;
syncDownloadIcs: string;
syncTestConnection: string;
syncTesting: string;
syncTestSuccess: string;
syncTabAndroid: string;
syncTabApple: string;
syncTabThunderbird: string;
syncAndroidStep1: string;
syncAndroidStep2: string;
syncAndroidStep3: string;
syncAndroidStep4: string;
syncAppleStep1: string;
syncAppleStep2: string;
syncAppleStep3: string;
syncAppleStep4: string;
syncThunderbirdStep1: string;
syncThunderbirdStep2: string;
syncThunderbirdStep3: string;
syncThunderbirdStep4: string;
// Task List
tasks: string;
hideDone: string;
showDone: string;
completedSection: string;
addTaskPlaceholder: string;
add: string;
noTasksYet: string;
selectListToStart: string;
loading: string;
today: string;
tomorrow: string;
// Task Detail
taskTitlePlaceholder: string;
priority: string;
priorityNone: string;
priorityLow: string;
priorityMedium: string;
priorityHigh: string;
dueDate: string;
clear: string;
notes: string;
notesPlaceholder: string;
subtasks: string;
addSubtaskPlaceholder: string;
deleteTaskConfirm: string;
autoSaved: string;
saving: string;
created: string;
edited: string;
// Toolbars
bold: string;
italic: string;
heading: string;
bulletList: string;
numberedList: string;
checkbox: string;
code: string;
// TickTick Mode Switcher & Quick Add
textMode: string;
subtaskMode: string;
switchToTextMode: string;
switchToSubtaskMode: string;
moveToList: string;
quickAdd: string;
quickToday: string;
quickTomorrow: string;
quickNextWeek: string;
selectDate: string;
selectPriority: string;
selectTag: string;
copied: string;
// Context Menu
editTask: string;
duplicateTask: string;
moveTask: string;
deleteTask: string;
renameList: string;
deleteList: string;
taskUndoHint: string;
listUndoHint: string;
// Labs, Kanban & Customization
labs: string;
labsDesc: string;
kanbanView: string;
listView: string;
enableKanban: string;
enableKanbanDesc: string;
swapBlocks: string;
resetDefaults: string;
resetConfirm: string;
todoCol: string;
inProgressCol: string;
doneCol: string;
accessRestricted: string;
adminRequired: string;
}
export type TranslationKey = keyof TranslationDict;
+2 -14
View File
@@ -8,6 +8,7 @@ export async function middleware(request: NextRequest) {
// Public paths: // Public paths:
// - /login, /register: Auth pages // - /login, /register: Auth pages
// - /demo: Local preview without DB // - /demo: Local preview without DB
// - /admin: Admin page (handles demo preview & auth gate internally)
// - /api/auth: NextAuth endpoints // - /api/auth: NextAuth endpoints
// - /api/dav: DAVx⁵ uses HTTP Basic Auth // - /api/dav: DAVx⁵ uses HTTP Basic Auth
// - Static assets & metadata // - Static assets & metadata
@@ -15,6 +16,7 @@ export async function middleware(request: NextRequest) {
"/login", "/login",
"/register", "/register",
"/demo", "/demo",
"/admin",
"/api/auth", "/api/auth",
"/api/dav", "/api/dav",
"/icons", "/icons",
@@ -43,20 +45,6 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(loginUrl); return NextResponse.redirect(loginUrl);
} }
// /admin 라우트의 경우 추가 보안 검증
if (pathname.startsWith("/admin")) {
const userRole = (token as { role?: string }).role;
const adminEmail = process.env.ADMIN_EMAIL;
const isEmailAdmin = adminEmail && token.email === adminEmail;
const isAdmin = userRole === "ADMIN" || isEmailAdmin || token.email?.endsWith("@checkflow.local");
if (!isAdmin) {
// 일반 사용자가 어드민에 접근하려 할 때 403 차단 혹은 메인으로 리디렉션
const forbiddenUrl = new URL("/?error=forbidden", request.url);
return NextResponse.redirect(forbiddenUrl);
}
}
return NextResponse.next(); return NextResponse.next();
} }