mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
refactor(i18n): replace hardcoded texts with i18n message keys
This commit is contained in:
parent
8b276ae91e
commit
4428a29306
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"messages",
|
||||||
|
"src/i18n"
|
||||||
|
]
|
||||||
|
}
|
130
messages/en.json
Normal file
130
messages/en.json
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"AppearanceSettings": {
|
||||||
|
"title": "Choose a theme",
|
||||||
|
"items": {
|
||||||
|
"System": "System",
|
||||||
|
"Light": "Light",
|
||||||
|
"Dark": "Dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AvatarButton": {
|
||||||
|
"Settings": "Settings",
|
||||||
|
"LogIn": "LogIn",
|
||||||
|
"LogOut": "LogOut"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"Text": "Star this project if you like it."
|
||||||
|
},
|
||||||
|
"BackButton": "Back",
|
||||||
|
"Bot": {
|
||||||
|
"title": "Ask Bot",
|
||||||
|
"description": "Powered by Vercel Ai SDK",
|
||||||
|
"placeholder": "Bot will automatically get your current code"
|
||||||
|
},
|
||||||
|
"BotVisibilityToggle": {
|
||||||
|
"open": "Open Bot",
|
||||||
|
"close": "Close Bot"
|
||||||
|
},
|
||||||
|
"DetailsPage": {
|
||||||
|
"BackButton": "All Submissions",
|
||||||
|
"Time": "Submitted on",
|
||||||
|
"Input": "Input",
|
||||||
|
"ExpectedOutput": "Expected Output",
|
||||||
|
"ActualOutput": "Acutal Output",
|
||||||
|
"Code": "Code"
|
||||||
|
},
|
||||||
|
"Difficulty": {
|
||||||
|
"EASY": "EASY",
|
||||||
|
"MEDIUM": "MEDIUM",
|
||||||
|
"HARD": "HARD"
|
||||||
|
},
|
||||||
|
"LanguageSettings": {
|
||||||
|
"en": {
|
||||||
|
"flag": "🇺🇸",
|
||||||
|
"name": "English"
|
||||||
|
},
|
||||||
|
"zh": {
|
||||||
|
"flag": "🇨🇳",
|
||||||
|
"name": "Chinese"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PlaygroundHeader": {
|
||||||
|
"RunCodeButton": {
|
||||||
|
"TooltipTrigger": {
|
||||||
|
"loading": "Running...",
|
||||||
|
"ready": "Run"
|
||||||
|
},
|
||||||
|
"TooltipContent": "Run Code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProblemPage": {
|
||||||
|
"Description": "Description",
|
||||||
|
"Solutions": "Solutions",
|
||||||
|
"Submissions": "Submissions",
|
||||||
|
"Details": "Details",
|
||||||
|
"Code": "Code",
|
||||||
|
"Testcase": "Testcase",
|
||||||
|
"Bot": "Bot"
|
||||||
|
},
|
||||||
|
"ProblemsetPage": {
|
||||||
|
"Status": "Status",
|
||||||
|
"Title": "Title",
|
||||||
|
"Difficulty": "Difficulty"
|
||||||
|
},
|
||||||
|
"SettingsDialog": {
|
||||||
|
"title": "Settings",
|
||||||
|
"description": "Customize your settings here.",
|
||||||
|
"breadcrumb": "Settings",
|
||||||
|
"nav": {
|
||||||
|
"Appearance": "Appearance",
|
||||||
|
"Language": "Language",
|
||||||
|
"CodeEditor": "CodeEditor",
|
||||||
|
"Advanced": "Advanced"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StatusMessage": {
|
||||||
|
"PD": "Pending",
|
||||||
|
"QD": "Queued",
|
||||||
|
"CP": "Compiling",
|
||||||
|
"CE": "Compilation Error",
|
||||||
|
"CS": "Compilation Success",
|
||||||
|
"RU": "Running",
|
||||||
|
"TLE": "Time Limit Exceeded",
|
||||||
|
"MLE": "Memory Limit Exceeded",
|
||||||
|
"RE": "Runtime Error",
|
||||||
|
"AC": "Accepted",
|
||||||
|
"WA": "Wrong Answer",
|
||||||
|
"SE": "System Error"
|
||||||
|
},
|
||||||
|
"SubmissionsTable": {
|
||||||
|
"Index": "Index",
|
||||||
|
"Status": "Status",
|
||||||
|
"Language": "Language",
|
||||||
|
"Time": "Time",
|
||||||
|
"Memory": "Memory"
|
||||||
|
},
|
||||||
|
"WorkspaceEditorHeader": {
|
||||||
|
"LspStatusButton": {
|
||||||
|
"TooltipContent": "Language Server"
|
||||||
|
},
|
||||||
|
"ResetButton": {
|
||||||
|
"TooltipContent": "Reset Code"
|
||||||
|
},
|
||||||
|
"UndoButton": {
|
||||||
|
"TooltipContent": "Undo"
|
||||||
|
},
|
||||||
|
"RedoButton": {
|
||||||
|
"TooltipContent": "Redo"
|
||||||
|
},
|
||||||
|
"FormatButton": {
|
||||||
|
"TooltipContent": "Format"
|
||||||
|
},
|
||||||
|
"CopyButton": {
|
||||||
|
"TooltipContent": "Copy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkspaceEditorFooter": {
|
||||||
|
"Row": "Row",
|
||||||
|
"Column": "Column"
|
||||||
|
}
|
||||||
|
}
|
130
messages/zh.json
Normal file
130
messages/zh.json
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"AppearanceSettings": {
|
||||||
|
"title": "选择一个主题",
|
||||||
|
"items": {
|
||||||
|
"System": "系统",
|
||||||
|
"Light": "浅色",
|
||||||
|
"Dark": "深色"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AvatarButton": {
|
||||||
|
"Settings": "设置",
|
||||||
|
"LogIn": "登录",
|
||||||
|
"LogOut": "登出"
|
||||||
|
},
|
||||||
|
"Banner": {
|
||||||
|
"Text": "如果喜欢该项目不妨收藏一下"
|
||||||
|
},
|
||||||
|
"BackButton": "返回",
|
||||||
|
"Bot": {
|
||||||
|
"title": "询问AI助手",
|
||||||
|
"description": "由Vercel Ai SDK驱动",
|
||||||
|
"placeholder": "AI助手将自动获取您当前的代码"
|
||||||
|
},
|
||||||
|
"BotVisibilityToggle": {
|
||||||
|
"open": "打开AI助手",
|
||||||
|
"close": "关闭AI助手"
|
||||||
|
},
|
||||||
|
"DetailsPage": {
|
||||||
|
"BackButton": "所有提交记录",
|
||||||
|
"Time": "提交于",
|
||||||
|
"Input": "输入",
|
||||||
|
"ExpectedOutput": "期望输出",
|
||||||
|
"ActualOutput": "实际输出",
|
||||||
|
"Code": "代码"
|
||||||
|
},
|
||||||
|
"Difficulty": {
|
||||||
|
"EASY": "简单",
|
||||||
|
"MEDIUM": "中等",
|
||||||
|
"HARD": "困难"
|
||||||
|
},
|
||||||
|
"LanguageSettings": {
|
||||||
|
"en": {
|
||||||
|
"flag": "🇺🇸",
|
||||||
|
"name": "英语"
|
||||||
|
},
|
||||||
|
"zh": {
|
||||||
|
"flag": "🇨🇳",
|
||||||
|
"name": "中文"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PlaygroundHeader": {
|
||||||
|
"RunCodeButton": {
|
||||||
|
"TooltipTrigger": {
|
||||||
|
"loading": "运行中...",
|
||||||
|
"ready": "运行"
|
||||||
|
},
|
||||||
|
"TooltipContent": "运行代码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProblemPage": {
|
||||||
|
"Description": "题目描述",
|
||||||
|
"Solutions": "题解",
|
||||||
|
"Submissions": "提交记录",
|
||||||
|
"Details": "详情",
|
||||||
|
"Code": "代码",
|
||||||
|
"Testcase": "测试用例",
|
||||||
|
"Bot": "AI助手"
|
||||||
|
},
|
||||||
|
"ProblemsetPage": {
|
||||||
|
"Status": "状态",
|
||||||
|
"Title": "题目",
|
||||||
|
"Difficulty": "难度"
|
||||||
|
},
|
||||||
|
"SettingsDialog": {
|
||||||
|
"title": "设置",
|
||||||
|
"description": "在此处自定义设置。",
|
||||||
|
"breadcrumb": "设置",
|
||||||
|
"nav": {
|
||||||
|
"Appearance": "外观",
|
||||||
|
"Language": "语言",
|
||||||
|
"CodeEditor": "代码编辑器",
|
||||||
|
"Advanced": "高级设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StatusMessage": {
|
||||||
|
"PD": "待处理",
|
||||||
|
"QD": "排队中",
|
||||||
|
"CP": "编译中",
|
||||||
|
"CE": "编译错误",
|
||||||
|
"CS": "编译成功",
|
||||||
|
"RU": "运行中",
|
||||||
|
"TLE": "超出时间限制",
|
||||||
|
"MLE": "超出内存限制",
|
||||||
|
"RE": "运行时错误",
|
||||||
|
"AC": "通过",
|
||||||
|
"WA": "解答错误",
|
||||||
|
"SE": "系统错误"
|
||||||
|
},
|
||||||
|
"SubmissionsTable": {
|
||||||
|
"Index": "序号",
|
||||||
|
"Status": "状态",
|
||||||
|
"Language": "语言",
|
||||||
|
"Time": "执行用时",
|
||||||
|
"Memory": "消耗内存"
|
||||||
|
},
|
||||||
|
"WorkspaceEditorHeader": {
|
||||||
|
"LspStatusButton": {
|
||||||
|
"TooltipContent": "语言服务"
|
||||||
|
},
|
||||||
|
"ResetButton": {
|
||||||
|
"TooltipContent": "重置代码"
|
||||||
|
},
|
||||||
|
"UndoButton": {
|
||||||
|
"TooltipContent": "撤销"
|
||||||
|
},
|
||||||
|
"RedoButton": {
|
||||||
|
"TooltipContent": "恢复"
|
||||||
|
},
|
||||||
|
"FormatButton": {
|
||||||
|
"TooltipContent": "格式化"
|
||||||
|
},
|
||||||
|
"CopyButton": {
|
||||||
|
"TooltipContent": "复制"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkspaceEditorFooter": {
|
||||||
|
"Row": "行",
|
||||||
|
"Column": "列"
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useChat } from "@ai-sdk/react";
|
import { useChat } from "@ai-sdk/react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
import MdxPreview from "@/components/mdx-preview";
|
import MdxPreview from "@/components/mdx-preview";
|
||||||
@ -19,6 +20,7 @@ import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
|
|||||||
import { ChatBubble, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble";
|
import { ChatBubble, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble";
|
||||||
|
|
||||||
export default function Bot() {
|
export default function Bot() {
|
||||||
|
const t = useTranslations("Bot");
|
||||||
const { problemId, problem, currentLang, currentValue } = useProblem();
|
const { problemId, problem, currentLang, currentValue } = useProblem();
|
||||||
|
|
||||||
const { messages, input, handleInputChange, setMessages, handleSubmit } = useChat({
|
const { messages, input, handleInputChange, setMessages, handleSubmit } = useChat({
|
||||||
@ -60,8 +62,8 @@ export default function Bot() {
|
|||||||
) && (
|
) && (
|
||||||
<div className="h-full flex flex-col items-center justify-center gap-2 text-muted-foreground">
|
<div className="h-full flex flex-col items-center justify-center gap-2 text-muted-foreground">
|
||||||
<BotIcon />
|
<BotIcon />
|
||||||
<span>Ask Bot</span>
|
<span>{t("title")}</span>
|
||||||
<span className="font-thin text-xs">Powered by Vercel Ai SDK</span>
|
<span className="font-thin text-xs">{t("description")}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ export default function Bot() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="h-full bg-muted border-transparent shadow-none rounded-lg"
|
className="h-full bg-muted border-transparent shadow-none rounded-lg"
|
||||||
placeholder="Bot will automatically get your current code"
|
placeholder={t("placeholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
|
8
src/app/(app)/problems/[id]/@Details/layout.tsx
Normal file
8
src/app/(app)/problems/[id]/@Details/layout.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { getUserLocale } from "@/i18n/locale";
|
||||||
|
import DetailsPage from "@/app/(app)/problems/[id]/@Details/page";
|
||||||
|
|
||||||
|
export default async function DetailsLayout() {
|
||||||
|
const locale = await getUserLocale();
|
||||||
|
|
||||||
|
return <DetailsPage locale={locale} />;
|
||||||
|
}
|
@ -7,7 +7,10 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
|
import { Locale } from "@/config/i18n";
|
||||||
|
import { getLocale } from "@/lib/i18n";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { ArrowLeftIcon } from "lucide-react";
|
import { ArrowLeftIcon } from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -20,7 +23,14 @@ import type { TestcaseResultWithTestcase } from "@/types/prisma";
|
|||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
|
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
|
||||||
|
|
||||||
export default function DetailsPage() {
|
interface DetailsPageProps {
|
||||||
|
locale: Locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DetailsPage({ locale }: DetailsPageProps) {
|
||||||
|
const localeInstance = getLocale(locale);
|
||||||
|
const t = useTranslations("DetailsPage");
|
||||||
|
const s = useTranslations("StatusMessage");
|
||||||
const { api, submission } = useDockviewStore();
|
const { api, submission } = useDockviewStore();
|
||||||
const { editorLanguageConfigs, problemId } = useProblem();
|
const { editorLanguageConfigs, problemId } = useProblem();
|
||||||
const [lastFailedTestcase, setLastFailedTestcase] =
|
const [lastFailedTestcase, setLastFailedTestcase] =
|
||||||
@ -53,7 +63,7 @@ export default function DetailsPage() {
|
|||||||
const createdAt = new Date(submission.createdAt);
|
const createdAt = new Date(submission.createdAt);
|
||||||
const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
|
const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
|
||||||
? format(createdAt, "yyyy-MM-dd")
|
? format(createdAt, "yyyy-MM-dd")
|
||||||
: formatDistanceToNow(createdAt, { addSuffix: true });
|
: formatDistanceToNow(createdAt, { addSuffix: true, locale: localeInstance });
|
||||||
|
|
||||||
const source = `\`\`\`${submission?.language}\n${submission?.code}\n\`\`\``;
|
const source = `\`\`\`${submission?.language}\n${submission?.code}\n\`\`\``;
|
||||||
|
|
||||||
@ -76,7 +86,7 @@ export default function DetailsPage() {
|
|||||||
className="h-8 w-auto p-2 hover:bg-transparent text-muted-foreground hover:text-foreground"
|
className="h-8 w-auto p-2 hover:bg-transparent text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon size={16} aria-hidden="true" />
|
<ArrowLeftIcon size={16} aria-hidden="true" />
|
||||||
<span>All Submissions</span>
|
<span>{t("BackButton")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@ -92,10 +102,10 @@ export default function DetailsPage() {
|
|||||||
getStatusColorClass(submission.status)
|
getStatusColorClass(submission.status)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{statusMap.get(submission.status)?.message}</span>
|
<span>{s(`${statusMap.get(submission.status)?.message}`)}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex max-w-full flex-1 items-center gap-1 overflow-hidden text-xs">
|
<div className="flex max-w-full flex-1 items-center gap-1 overflow-hidden text-xs">
|
||||||
<span className="whitespace-nowrap">Submitted on</span>
|
<span className="whitespace-nowrap mr-1">{t("Time")}</span>
|
||||||
<span className="max-w-full truncate">
|
<span className="max-w-full truncate">
|
||||||
{submittedDisplay}
|
{submittedDisplay}
|
||||||
</span>
|
</span>
|
||||||
@ -118,7 +128,7 @@ export default function DetailsPage() {
|
|||||||
className="bg-background has-focus-visible:border-ring has-focus-visible:ring-ring/50 relative border px-4 py-1 outline-none first:rounded-t-md last:rounded-b-md last:border-b has-focus-visible:z-10 has-focus-visible:ring-[3px]"
|
className="bg-background has-focus-visible:border-ring has-focus-visible:ring-ring/50 relative border px-4 py-1 outline-none first:rounded-t-md last:rounded-b-md last:border-b has-focus-visible:z-10 has-focus-visible:ring-[3px]"
|
||||||
>
|
>
|
||||||
<AccordionTrigger className="py-2 text-[15px] leading-6 hover:no-underline focus-visible:ring-0">
|
<AccordionTrigger className="py-2 text-[15px] leading-6 hover:no-underline focus-visible:ring-0">
|
||||||
<h4 className="text-sm font-medium">Input</h4>
|
<h4 className="text-sm font-medium">{t("Input")}</h4>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="text-muted-foreground pb-2">
|
<AccordionContent className="text-muted-foreground pb-2">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -142,7 +152,7 @@ export default function DetailsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-sm font-medium">Expected Output</h4>
|
<h4 className="text-sm font-medium">{t("ExpectedOutput")}</h4>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={lastFailedTestcase.testcase.expectedOutput}
|
value={lastFailedTestcase.testcase.expectedOutput}
|
||||||
@ -153,7 +163,7 @@ export default function DetailsPage() {
|
|||||||
|
|
||||||
{submission.status === "WA" && (
|
{submission.status === "WA" && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-sm font-medium">Your Output</h4>
|
<h4 className="text-sm font-medium">{t("ActualOutput")}</h4>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={lastFailedTestcase.output}
|
value={lastFailedTestcase.output}
|
||||||
@ -174,7 +184,7 @@ export default function DetailsPage() {
|
|||||||
|
|
||||||
<div className="flex items-center pb-2">
|
<div className="flex items-center pb-2">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
<span>Code</span>
|
<span>{t("Code")}</span>
|
||||||
<Separator
|
<Separator
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className="h-4 bg-muted-foreground"
|
className="h-4 bg-muted-foreground"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
import { getUserLocale } from "@/i18n/locale";
|
||||||
import SubmissionsTable from "@/components/submissions-table";
|
import SubmissionsTable from "@/components/submissions-table";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
@ -37,10 +38,12 @@ export default async function SubmissionsPage({ params }: SubmissionsPageProps)
|
|||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const locale = await getUserLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 flex flex-col h-full border border-t-0 border-muted rounded-b-3xl bg-background">
|
<div className="px-3 flex flex-col h-full border border-t-0 border-muted rounded-b-3xl bg-background">
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<SubmissionsTable submissions={problem.submissions} />
|
<SubmissionsTable locale={locale} submissions={problem.submissions} />
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
import { getUserLocale } from "@/i18n/locale";
|
||||||
import ProblemPage from "@/app/(app)/problems/[id]/page";
|
import ProblemPage from "@/app/(app)/problems/[id]/page";
|
||||||
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
|
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
|
||||||
import { PlaygroundHeader } from "@/components/features/playground/header";
|
import { PlaygroundHeader } from "@/components/features/playground/header";
|
||||||
@ -59,6 +60,8 @@ export default async function ProblemLayout({
|
|||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const locale = await getUserLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="flex flex-col h-screen">
|
||||||
<ProblemStoreProvider
|
<ProblemStoreProvider
|
||||||
@ -71,6 +74,7 @@ export default async function ProblemLayout({
|
|||||||
<PlaygroundHeader />
|
<PlaygroundHeader />
|
||||||
<main className="flex flex-grow overflow-y-hidden p-2.5 pt-0">
|
<main className="flex flex-grow overflow-y-hidden p-2.5 pt-0">
|
||||||
<ProblemPage
|
<ProblemPage
|
||||||
|
locale={locale}
|
||||||
Description={Description}
|
Description={Description}
|
||||||
Solutions={Solutions}
|
Solutions={Solutions}
|
||||||
Submissions={Submissions}
|
Submissions={Submissions}
|
||||||
|
@ -8,10 +8,14 @@ import {
|
|||||||
SquareCheckIcon,
|
SquareCheckIcon,
|
||||||
SquarePenIcon,
|
SquarePenIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { Locale } from "@/config/i18n";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import Dockview from "@/components/dockview";
|
import Dockview from "@/components/dockview";
|
||||||
import { useDockviewStore } from "@/stores/dockview";
|
import { useDockviewStore } from "@/stores/dockview";
|
||||||
|
|
||||||
interface ProblemPageProps {
|
interface ProblemPageProps {
|
||||||
|
locale: Locale;
|
||||||
Description: React.ReactNode;
|
Description: React.ReactNode;
|
||||||
Solutions: React.ReactNode;
|
Solutions: React.ReactNode;
|
||||||
Submissions: React.ReactNode;
|
Submissions: React.ReactNode;
|
||||||
@ -22,6 +26,7 @@ interface ProblemPageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ProblemPage({
|
export default function ProblemPage({
|
||||||
|
locale,
|
||||||
Description,
|
Description,
|
||||||
Solutions,
|
Solutions,
|
||||||
Submissions,
|
Submissions,
|
||||||
@ -30,9 +35,17 @@ export default function ProblemPage({
|
|||||||
Testcase,
|
Testcase,
|
||||||
Bot,
|
Bot,
|
||||||
}: ProblemPageProps) {
|
}: ProblemPageProps) {
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
const { setApi } = useDockviewStore();
|
const { setApi } = useDockviewStore();
|
||||||
|
const t = useTranslations("ProblemPage");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setKey((prevKey) => prevKey + 1);
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dockview
|
<Dockview
|
||||||
|
key={key}
|
||||||
storageKey="dockview:problem"
|
storageKey="dockview:problem"
|
||||||
onApiReady={setApi}
|
onApiReady={setApi}
|
||||||
options={[
|
options={[
|
||||||
@ -40,7 +53,7 @@ export default function ProblemPage({
|
|||||||
id: "Description",
|
id: "Description",
|
||||||
component: "Description",
|
component: "Description",
|
||||||
tabComponent: "Description",
|
tabComponent: "Description",
|
||||||
title: "Description",
|
title: t("Description"),
|
||||||
params: {
|
params: {
|
||||||
icon: FileTextIcon,
|
icon: FileTextIcon,
|
||||||
content: Description,
|
content: Description,
|
||||||
@ -50,7 +63,7 @@ export default function ProblemPage({
|
|||||||
id: "Solutions",
|
id: "Solutions",
|
||||||
component: "Solutions",
|
component: "Solutions",
|
||||||
tabComponent: "Solutions",
|
tabComponent: "Solutions",
|
||||||
title: "Solutions",
|
title: t("Solutions"),
|
||||||
params: {
|
params: {
|
||||||
icon: FlaskConicalIcon,
|
icon: FlaskConicalIcon,
|
||||||
content: Solutions,
|
content: Solutions,
|
||||||
@ -65,7 +78,7 @@ export default function ProblemPage({
|
|||||||
id: "Submissions",
|
id: "Submissions",
|
||||||
component: "Submissions",
|
component: "Submissions",
|
||||||
tabComponent: "Submissions",
|
tabComponent: "Submissions",
|
||||||
title: "Submissions",
|
title: t("Submissions"),
|
||||||
params: {
|
params: {
|
||||||
icon: CircleCheckBigIcon,
|
icon: CircleCheckBigIcon,
|
||||||
content: Submissions,
|
content: Submissions,
|
||||||
@ -80,7 +93,7 @@ export default function ProblemPage({
|
|||||||
id: "Details",
|
id: "Details",
|
||||||
component: "Details",
|
component: "Details",
|
||||||
tabComponent: "Details",
|
tabComponent: "Details",
|
||||||
title: "Details",
|
title: t("Details"),
|
||||||
params: {
|
params: {
|
||||||
icon: CircleCheckBigIcon,
|
icon: CircleCheckBigIcon,
|
||||||
content: Details,
|
content: Details,
|
||||||
@ -91,7 +104,7 @@ export default function ProblemPage({
|
|||||||
id: "Code",
|
id: "Code",
|
||||||
component: "Code",
|
component: "Code",
|
||||||
tabComponent: "Code",
|
tabComponent: "Code",
|
||||||
title: "Code",
|
title: t("Code"),
|
||||||
params: {
|
params: {
|
||||||
icon: SquarePenIcon,
|
icon: SquarePenIcon,
|
||||||
content: Code,
|
content: Code,
|
||||||
@ -105,7 +118,7 @@ export default function ProblemPage({
|
|||||||
id: "Testcase",
|
id: "Testcase",
|
||||||
component: "Testcase",
|
component: "Testcase",
|
||||||
tabComponent: "Testcase",
|
tabComponent: "Testcase",
|
||||||
title: "Testcase",
|
title: t("Testcase"),
|
||||||
params: {
|
params: {
|
||||||
icon: SquareCheckIcon,
|
icon: SquareCheckIcon,
|
||||||
content: Testcase,
|
content: Testcase,
|
||||||
@ -119,7 +132,7 @@ export default function ProblemPage({
|
|||||||
id: "Bot",
|
id: "Bot",
|
||||||
component: "Bot",
|
component: "Bot",
|
||||||
tabComponent: "Bot",
|
tabComponent: "Bot",
|
||||||
title: "Bot",
|
title: t("Bot"),
|
||||||
params: {
|
params: {
|
||||||
icon: BotIcon,
|
icon: BotIcon,
|
||||||
content: Bot,
|
content: Bot,
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
import { getDifficultyColorClass } from "@/lib/utils";
|
import { getDifficultyColorClass } from "@/lib/utils";
|
||||||
import { CircleCheckBigIcon, CircleDotIcon } from "lucide-react";
|
import { CircleCheckBigIcon, CircleDotIcon } from "lucide-react";
|
||||||
|
|
||||||
@ -32,13 +33,15 @@ export default async function ProblemsetPage() {
|
|||||||
const completedProblems = new Set(submissions.filter(s => s.status === "AC").map(s => s.problemId));
|
const completedProblems = new Set(submissions.filter(s => s.status === "AC").map(s => s.problemId));
|
||||||
const attemptedProblems = new Set(submissions.filter(s => s.status !== "AC").map(s => s.problemId));
|
const attemptedProblems = new Set(submissions.filter(s => s.status !== "AC").map(s => s.problemId));
|
||||||
|
|
||||||
|
const t = await getTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-transparent">
|
<TableHeader className="bg-transparent">
|
||||||
<TableRow className="hover:bg-transparent">
|
<TableRow className="hover:bg-transparent">
|
||||||
<TableHead className="w-1/3">Status</TableHead>
|
<TableHead className="w-1/3">{t("ProblemsetPage.Status")}</TableHead>
|
||||||
<TableHead className="w-1/3">Title</TableHead>
|
<TableHead className="w-1/3">{t("ProblemsetPage.Title")}</TableHead>
|
||||||
<TableHead className="w-1/3">Difficulty</TableHead>
|
<TableHead className="w-1/3">{t("ProblemsetPage.Difficulty")}</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody className="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg">
|
<TableBody className="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg">
|
||||||
@ -60,7 +63,7 @@ export default async function ProblemsetPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={`py-2.5 ${getDifficultyColorClass(problem.difficulty)}`}>
|
<TableCell className={`py-2.5 ${getDifficultyColorClass(problem.difficulty)}`}>
|
||||||
{problem.difficulty}
|
{t(`Difficulty.${problem.difficulty}`)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
@ -2,22 +2,25 @@
|
|||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { CheckIcon, MinusIcon } from "lucide-react";
|
import { CheckIcon, MinusIcon } from "lucide-react";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
|
||||||
|
export default function AppearanceSettings() {
|
||||||
|
const t = useTranslations("AppearanceSettings");
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{ value: "system", label: "System", image: "/ui-system.png" },
|
{ value: "system", label: t("items.System"), image: "/ui-system.png" },
|
||||||
{ value: "light", label: "Light", image: "/ui-light.png" },
|
{ value: "light", label: t("items.Light"), image: "/ui-light.png" },
|
||||||
{ value: "dark", label: "Dark", image: "/ui-dark.png" },
|
{ value: "dark", label: t("items.Dark"), image: "/ui-dark.png" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function AppearanceSettings() {
|
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset className="space-y-4">
|
<fieldset className="space-y-4">
|
||||||
<legend className="text-foreground text-sm leading-none font-medium">
|
<legend className="text-foreground text-sm leading-none font-medium">
|
||||||
Choose a theme
|
{t("title")}
|
||||||
</legend>
|
</legend>
|
||||||
<RadioGroup className="flex gap-3" defaultValue={theme}>
|
<RadioGroup className="flex gap-3" defaultValue={theme}>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { auth, signOut } from "@/lib/auth";
|
import { auth, signOut } from "@/lib/auth";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import LogInButton from "@/components/log-in-button";
|
import LogInButton from "@/components/log-in-button";
|
||||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
@ -28,6 +29,7 @@ async function handleLogOut() {
|
|||||||
|
|
||||||
export async function AvatarButton() {
|
export async function AvatarButton() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
const t = await getTranslations("AvatarButton");
|
||||||
const isLoggedIn = !!session?.user;
|
const isLoggedIn = !!session?.user;
|
||||||
const image = session?.user?.image ?? "https://github.com/shadcn.png";
|
const image = session?.user?.image ?? "https://github.com/shadcn.png";
|
||||||
const name = session?.user?.name ?? "unknown";
|
const name = session?.user?.name ?? "unknown";
|
||||||
@ -64,7 +66,7 @@ export async function AvatarButton() {
|
|||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
<DropdownMenuItem onClick={handleLogOut}>
|
<DropdownMenuItem onClick={handleLogOut}>
|
||||||
<LogOut />
|
<LogOut />
|
||||||
Log out
|
{t("LogOut")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</>
|
</>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { ArrowLeftIcon } from "lucide-react";
|
import { ArrowLeftIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
@ -14,6 +15,8 @@ export default function BackButton({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: BackButtonProps) {
|
}: BackButtonProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@ -29,7 +32,9 @@ export default function BackButton({
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">Back</TooltipContent>
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
|
{t("BackButton")}
|
||||||
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { siteConfig } from "@/config/site";
|
import { siteConfig } from "@/config/site";
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
import { ArrowRightIcon } from "lucide-react";
|
import { ArrowRightIcon } from "lucide-react";
|
||||||
|
|
||||||
interface BannerProps {
|
interface BannerProps {
|
||||||
@ -11,9 +12,11 @@ interface BannerProps {
|
|||||||
export function Banner({
|
export function Banner({
|
||||||
className,
|
className,
|
||||||
link = siteConfig.url.repo.github,
|
link = siteConfig.url.repo.github,
|
||||||
text = "Star this project if you like it.",
|
text,
|
||||||
...props
|
...props
|
||||||
}: BannerProps) {
|
}: BannerProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
{...props}
|
{...props}
|
||||||
@ -25,7 +28,7 @@ export function Banner({
|
|||||||
<p className="flex justify-center text-sm">
|
<p className="flex justify-center text-sm">
|
||||||
<a href={link} className="group">
|
<a href={link} className="group">
|
||||||
<span className="me-1 text-base leading-none">✨</span>
|
<span className="me-1 text-base leading-none">✨</span>
|
||||||
{text}
|
{text || t("Banner.Text")}
|
||||||
<ArrowRightIcon
|
<ArrowRightIcon
|
||||||
className="ms-2 -mt-0.5 inline-flex opacity-60 transition-transform group-hover:translate-x-0.5"
|
className="ms-2 -mt-0.5 inline-flex opacity-60 transition-transform group-hover:translate-x-0.5"
|
||||||
size={16}
|
size={16}
|
||||||
|
@ -8,11 +8,13 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { BotIcon } from "lucide-react";
|
import { BotIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { useDockviewStore } from "@/stores/dockview";
|
import { useDockviewStore } from "@/stores/dockview";
|
||||||
|
|
||||||
export default function BotVisibilityToggle() {
|
export default function BotVisibilityToggle() {
|
||||||
const { api } = useDockviewStore();
|
const { api } = useDockviewStore();
|
||||||
|
const t = useTranslations();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const [isBotVisible, setBotVisible] = useState<boolean>(false);
|
const [isBotVisible, setBotVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ export default function BotVisibilityToggle() {
|
|||||||
id: "Bot",
|
id: "Bot",
|
||||||
component: "Bot",
|
component: "Bot",
|
||||||
tabComponent: "Bot",
|
tabComponent: "Bot",
|
||||||
title: "Bot",
|
title: t("ProblemPage.Bot"),
|
||||||
position: {
|
position: {
|
||||||
direction: "right",
|
direction: "right",
|
||||||
},
|
},
|
||||||
@ -70,7 +72,7 @@ export default function BotVisibilityToggle() {
|
|||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
<p>{isBotVisible ? "Close Bot" : "Open Bot"}</p>
|
<p>{isBotVisible ? t("BotVisibilityToggle.close") : t("BotVisibilityToggle.open")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { RunCode } from "@/components/run-code";
|
import { auth } from "@/lib/auth";
|
||||||
import BackButton from "@/components/back-button";
|
import BackButton from "@/components/back-button";
|
||||||
|
import { RunCodeButton } from "@/components/run-code";
|
||||||
import { AvatarButton } from "@/components/avatar-button";
|
import { AvatarButton } from "@/components/avatar-button";
|
||||||
import BotVisibilityToggle from "@/components/bot-visibility-toggle";
|
import BotVisibilityToggle from "@/components/bot-visibility-toggle";
|
||||||
|
|
||||||
@ -8,10 +9,12 @@ interface PlaygroundHeaderProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlaygroundHeader({
|
export async function PlaygroundHeader({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: PlaygroundHeaderProps) {
|
}: PlaygroundHeaderProps) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
{...props}
|
{...props}
|
||||||
@ -33,7 +36,7 @@ export function PlaygroundHeader({
|
|||||||
<div className="z-10 absolute left-1/2 top-0 h-full -translate-x-1/2 py-2">
|
<div className="z-10 absolute left-1/2 top-0 h-full -translate-x-1/2 py-2">
|
||||||
<div className="relative flex">
|
<div className="relative flex">
|
||||||
<div className="relative flex overflow-hidden rounded">
|
<div className="relative flex overflow-hidden rounded">
|
||||||
<RunCode className="bg-muted text-muted-foreground hover:bg-muted/50" />
|
<RunCodeButton session={session} className="bg-muted text-muted-foreground hover:bg-muted/50" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,12 +9,14 @@ import {
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Check, Copy } from "lucide-react";
|
import { Check, Copy } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
export function CopyButton() {
|
export function CopyButton() {
|
||||||
const { editor } = useProblem();
|
const { editor } = useProblem();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.CopyButton");
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
try {
|
try {
|
||||||
@ -65,7 +67,7 @@ export function CopyButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Click to Copy
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
import { CircleXIcon, TriangleAlertIcon } from "lucide-react";
|
import { CircleXIcon, TriangleAlertIcon } from "lucide-react";
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ export function WorkspaceEditorFooter({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: WorkspaceEditorFooterProps) {
|
}: WorkspaceEditorFooterProps) {
|
||||||
|
const t = useTranslations("WorkspaceEditorFooter");
|
||||||
const { editor, markers } = useProblem();
|
const { editor, markers } = useProblem();
|
||||||
const [position, setPosition] = useState<{ lineNumber: number; column: number } | null>(null);
|
const [position, setPosition] = useState<{ lineNumber: number; column: number } | null>(null);
|
||||||
|
|
||||||
@ -62,8 +64,8 @@ export function WorkspaceEditorFooter({
|
|||||||
</div>
|
</div>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{position
|
{position
|
||||||
? `Row ${position.lineNumber}, Column ${position.column}`
|
? `${t("Row")} ${position.lineNumber}, ${t("Column")} ${position.column}`
|
||||||
: "Row -, Column -"}
|
: `${t("Row")} -, ${t("Column")} -`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -7,11 +7,13 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Paintbrush } from "lucide-react";
|
import { Paintbrush } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
export function FormatButton() {
|
export function FormatButton() {
|
||||||
const { editor } = useProblem();
|
const { editor } = useProblem();
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.FormatButton");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
@ -20,7 +22,7 @@ export function FormatButton() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Format Code"
|
aria-label={t("TooltipContent")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor?.trigger("format", "editor.action.formatDocument", null);
|
editor?.trigger("format", "editor.action.formatDocument", null);
|
||||||
}}
|
}}
|
||||||
@ -31,7 +33,7 @@ export function FormatButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Format Code
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ const getLspStatusColor = (webSocket: WebSocket | null) => {
|
|||||||
|
|
||||||
export function LspStatusButton() {
|
export function LspStatusButton() {
|
||||||
const { webSocket } = useProblem();
|
const { webSocket } = useProblem();
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.LspStatusButton");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
@ -48,7 +50,7 @@ export function LspStatusButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Language Server
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -7,11 +7,13 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Redo2 } from "lucide-react";
|
import { Redo2 } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
export function RedoButton() {
|
export function RedoButton() {
|
||||||
const { editor } = useProblem();
|
const { editor } = useProblem();
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.RedoButton");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
@ -20,7 +22,7 @@ export function RedoButton() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Redo Code"
|
aria-label={t("TooltipContent")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor?.trigger("redo", "redo", null);
|
editor?.trigger("redo", "redo", null);
|
||||||
}}
|
}}
|
||||||
@ -31,7 +33,7 @@ export function RedoButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Redo Code
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -7,11 +7,13 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { RotateCcw } from "lucide-react";
|
import { RotateCcw } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
export function ResetButton() {
|
export function ResetButton() {
|
||||||
const { editor, currentTemplate } = useProblem();
|
const { editor, currentTemplate } = useProblem();
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.ResetButton");
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
@ -38,7 +40,7 @@ export function ResetButton() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Reset Code"
|
aria-label={t("TooltipContent")}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
disabled={!editor}
|
disabled={!editor}
|
||||||
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
||||||
@ -47,7 +49,7 @@ export function ResetButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Reset Code
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -7,11 +7,13 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Undo2 } from "lucide-react";
|
import { Undo2 } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
|
|
||||||
export function UndoButton() {
|
export function UndoButton() {
|
||||||
const { editor } = useProblem();
|
const { editor } = useProblem();
|
||||||
|
const t = useTranslations("WorkspaceEditorHeader.UndoButton");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
@ -20,7 +22,7 @@ export function UndoButton() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Undo Code"
|
aria-label={t("TooltipContent")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor?.trigger("undo", "undo", null);
|
editor?.trigger("undo", "undo", null);
|
||||||
}}
|
}}
|
||||||
@ -31,7 +33,7 @@ export function UndoButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
Undo Code
|
{t("TooltipContent")}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
65
src/components/language-settings.tsx
Normal file
65
src/components/language-settings.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Flag from "react-world-flags";
|
||||||
|
import { Globe } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Locale, locales } from "@/config/i18n";
|
||||||
|
import { useState, useMemo, useEffect } from "react";
|
||||||
|
import { getUserLocale, setUserLocale } from "@/i18n/locale";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
|
||||||
|
export function LanguageSettings() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const [selectedOption, setSelectedOption] = useState<Locale>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLocale = async () => {
|
||||||
|
const userLocale = await getUserLocale();
|
||||||
|
if (!userLocale) return;
|
||||||
|
setSelectedOption(userLocale);
|
||||||
|
};
|
||||||
|
fetchLocale();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const localeOptions = useMemo(() => {
|
||||||
|
const options = locales.map((locale) => ({
|
||||||
|
value: locale,
|
||||||
|
label: `${t(`LanguageSettings.${locale}.name`)}`,
|
||||||
|
}));
|
||||||
|
return options.sort((a, b) => a.value.localeCompare(b.value));
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const handleValueChange = (value: Locale) => {
|
||||||
|
setSelectedOption(value);
|
||||||
|
setUserLocale(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIconForLocale = (locale: Locale) => {
|
||||||
|
switch (locale) {
|
||||||
|
case "en":
|
||||||
|
return <Flag code="US" className="h-4 w-4 mr-2" />;
|
||||||
|
case "zh":
|
||||||
|
return <Flag code="CN" className="h-4 w-4 mr-2" />;
|
||||||
|
default:
|
||||||
|
return <Globe size={16} className="mr-2" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select value={selectedOption} onValueChange={handleValueChange}>
|
||||||
|
<SelectTrigger className="w-[200px] shadow-none focus:ring-0">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="w-[200px]">
|
||||||
|
{localeOptions.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{getIconForLocale(option.value)}
|
||||||
|
<span className="truncate">{option.label}</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LogIn } from "lucide-react";
|
import { LogIn } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ export default function LogInButton() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const t = useTranslations("AvatarButton");
|
||||||
|
|
||||||
const handleLogIn = () => {
|
const handleLogIn = () => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
@ -17,8 +19,8 @@ export default function LogInButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem onClick={handleLogIn}>
|
<DropdownMenuItem onClick={handleLogIn}>
|
||||||
<LogIn className="mr-2 h-4 w-4" />
|
<LogIn />
|
||||||
Log In
|
{t("LogIn")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,24 +8,32 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Session } from "next-auth";
|
||||||
import { judge } from "@/actions/judge";
|
import { judge } from "@/actions/judge";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
import { useDockviewStore } from "@/stores/dockview";
|
import { useDockviewStore } from "@/stores/dockview";
|
||||||
import { LoaderCircleIcon, PlayIcon } from "lucide-react";
|
import { LoaderCircleIcon, PlayIcon } from "lucide-react";
|
||||||
import { showStatusToast } from "@/hooks/show-status-toast";
|
import { showStatusToast } from "@/hooks/show-status-toast";
|
||||||
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
interface RunCodeProps {
|
interface RunCodeButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
session: Session | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RunCode({
|
export function RunCodeButton({
|
||||||
className,
|
className,
|
||||||
...props
|
session,
|
||||||
}: RunCodeProps) {
|
}: RunCodeButtonProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const { api } = useDockviewStore();
|
const { api } = useDockviewStore();
|
||||||
const { currentLang, editor, problemId } = useProblem();
|
const { currentLang, editor, problemId } = useProblem();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const t = useTranslations("PlaygroundHeader.RunCodeButton");
|
||||||
|
|
||||||
const handleJudge = async () => {
|
const handleJudge = async () => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
@ -33,6 +41,11 @@ export function RunCode({
|
|||||||
const code = editor.getValue() || "";
|
const code = editor.getValue() || "";
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
params.set("redirectTo", pathname);
|
||||||
|
router.push(`/sign-in?${params.toString()}`);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
const result = await judge(currentLang, code, problemId);
|
const result = await judge(currentLang, code, problemId);
|
||||||
showStatusToast({ status: result.status });
|
showStatusToast({ status: result.status });
|
||||||
@ -46,6 +59,7 @@ export function RunCode({
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -53,7 +67,6 @@ export function RunCode({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={cn("h-8 px-3 py-1.5", className)}
|
className={cn("h-8 px-3 py-1.5", className)}
|
||||||
onClick={handleJudge}
|
onClick={handleJudge}
|
||||||
@ -72,10 +85,12 @@ export function RunCode({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoading ? "Running..." : "Run"}
|
{isLoading ? t("TooltipTrigger.loading") : t("TooltipTrigger.ready")}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="px-2 py-1 text-xs">Run Code</TooltipContent>
|
<TooltipContent className="px-2 py-1 text-xs">
|
||||||
|
{t("TooltipContent")}
|
||||||
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useSettingsStore } from "@/stores/useSettingsStore";
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
export function SettingsButton() {
|
export function SettingsButton() {
|
||||||
|
const t = useTranslations("AvatarButton");
|
||||||
const { setDialogOpen } = useSettingsStore();
|
const { setDialogOpen } = useSettingsStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem onClick={() => setDialogOpen(true)}>
|
<DropdownMenuItem onClick={() => setDialogOpen(true)}>
|
||||||
<Settings />
|
<Settings />
|
||||||
Settings
|
{t("Settings")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -25,29 +25,31 @@ import {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
import AppearanceSettings from "./appearance-settings";
|
import { useTranslations } from "next-intl";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { useSettingsStore } from "@/stores/useSettingsStore";
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
|
import AppearanceSettings from "@/components/appearance-settings";
|
||||||
|
import { LanguageSettings } from "@/components/language-settings";
|
||||||
import { CodeXml, Globe, Paintbrush, Settings } from "lucide-react";
|
import { CodeXml, Globe, Paintbrush, Settings } from "lucide-react";
|
||||||
|
|
||||||
|
export function SettingsDialog() {
|
||||||
|
const t = useTranslations("SettingsDialog");
|
||||||
const data = {
|
const data = {
|
||||||
nav: [
|
nav: [
|
||||||
{ name: "Appearance", icon: Paintbrush },
|
{ id: "Appearance", name: t("nav.Appearance"), icon: Paintbrush },
|
||||||
{ name: "Language & region", icon: Globe },
|
{ id: "Language", name: t("nav.Language"), icon: Globe },
|
||||||
{ name: "Code Editor", icon: CodeXml },
|
{ id: "CodeEditor", name: t("nav.CodeEditor"), icon: CodeXml },
|
||||||
{ name: "Advanced", icon: Settings },
|
{ id: "Advanced", name: t("nav.Advanced"), icon: Settings },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SettingsDialog() {
|
|
||||||
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } = useSettingsStore();
|
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } = useSettingsStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
|
||||||
<DialogContent className="overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]">
|
<DialogContent className="overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]">
|
||||||
<DialogTitle className="sr-only">Settings</DialogTitle>
|
<DialogTitle className="sr-only">{t("title")}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">
|
<DialogDescription className="sr-only">
|
||||||
Customize your settings here.
|
{t("description")}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
<SidebarProvider className="items-start">
|
<SidebarProvider className="items-start">
|
||||||
<Sidebar collapsible="none" className="hidden md:flex">
|
<Sidebar collapsible="none" className="hidden md:flex">
|
||||||
@ -59,10 +61,10 @@ export function SettingsDialog() {
|
|||||||
<SidebarMenuItem key={item.name}>
|
<SidebarMenuItem key={item.name}>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
asChild
|
asChild
|
||||||
isActive={item.name === activeSetting}
|
isActive={item.id === activeSetting}
|
||||||
onClick={() => setActiveSetting(item.name)}
|
onClick={() => setActiveSetting(item.id)}
|
||||||
>
|
>
|
||||||
<a href="#">
|
<a>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
</a>
|
</a>
|
||||||
@ -80,11 +82,11 @@ export function SettingsDialog() {
|
|||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem className="hidden md:block">
|
<BreadcrumbItem className="hidden md:block">
|
||||||
<BreadcrumbLink href="#">Settings</BreadcrumbLink>
|
<BreadcrumbLink>{t("breadcrumb")}</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbSeparator className="hidden md:block" />
|
<BreadcrumbSeparator className="hidden md:block" />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbPage>{activeSetting}</BreadcrumbPage>
|
<BreadcrumbPage>{t(`nav.${activeSetting}`)}</BreadcrumbPage>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
@ -92,16 +94,8 @@ export function SettingsDialog() {
|
|||||||
</header>
|
</header>
|
||||||
<ScrollArea className="flex-1 overflow-y-auto p-4 pt-0">
|
<ScrollArea className="flex-1 overflow-y-auto p-4 pt-0">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{activeSetting === "Appearance" ? (
|
{activeSetting === "Appearance" && <AppearanceSettings />}
|
||||||
<AppearanceSettings />
|
{activeSetting === "Language" && <LanguageSettings />}
|
||||||
) : (
|
|
||||||
Array.from({ length: 10 }).map((_, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="aspect-video max-w-3xl rounded-xl bg-muted/50"
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</main>
|
</main>
|
||||||
|
@ -9,6 +9,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Locale } from "@/config/i18n";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { enUS, zhCN } from "date-fns/locale";
|
||||||
import { useProblem } from "@/hooks/use-problem";
|
import { useProblem } from "@/hooks/use-problem";
|
||||||
import { Clock4Icon, CpuIcon } from "lucide-react";
|
import { Clock4Icon, CpuIcon } from "lucide-react";
|
||||||
import { useDockviewStore } from "@/stores/dockview";
|
import { useDockviewStore } from "@/stores/dockview";
|
||||||
@ -18,10 +21,23 @@ import { EditorLanguageIcons } from "@/config/editor-language-icons";
|
|||||||
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
|
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
|
||||||
|
|
||||||
interface SubmissionsTableProps {
|
interface SubmissionsTableProps {
|
||||||
|
locale: Locale;
|
||||||
submissions: SubmissionWithTestcaseResult[];
|
submissions: SubmissionWithTestcaseResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SubmissionsTable({ submissions }: SubmissionsTableProps) {
|
const getLocale = (locale: Locale) => {
|
||||||
|
switch (locale) {
|
||||||
|
case "zh":
|
||||||
|
return zhCN;
|
||||||
|
case "en":
|
||||||
|
default:
|
||||||
|
return enUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SubmissionsTable({ locale, submissions }: SubmissionsTableProps) {
|
||||||
|
const s = useTranslations("StatusMessage");
|
||||||
|
const t = useTranslations("SubmissionsTable");
|
||||||
const { editorLanguageConfigs } = useProblem();
|
const { editorLanguageConfigs } = useProblem();
|
||||||
const { api, setSubmission } = useDockviewStore();
|
const { api, setSubmission } = useDockviewStore();
|
||||||
|
|
||||||
@ -41,7 +57,7 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
|||||||
id: "Details",
|
id: "Details",
|
||||||
component: "Details",
|
component: "Details",
|
||||||
tabComponent: "Details",
|
tabComponent: "Details",
|
||||||
title: submission.status,
|
title: s(`${submission.status}`),
|
||||||
position: {
|
position: {
|
||||||
referencePanel: "Submissions",
|
referencePanel: "Submissions",
|
||||||
direction: "within",
|
direction: "within",
|
||||||
@ -54,11 +70,11 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-transparent">
|
<TableHeader className="bg-transparent">
|
||||||
<TableRow className="hover:bg-transparent">
|
<TableRow className="hover:bg-transparent">
|
||||||
<TableHead className="w-[100px]">Index</TableHead>
|
<TableHead className="w-[100px]">{t("Index")}</TableHead>
|
||||||
<TableHead className="w-[170px]">Status</TableHead>
|
<TableHead className="w-[170px]">{t("Status")}</TableHead>
|
||||||
<TableHead className="w-[100px]">Language</TableHead>
|
<TableHead className="w-[100px]">{t("Language")}</TableHead>
|
||||||
<TableHead className="w-[100px]">Time</TableHead>
|
<TableHead className="w-[100px]">{t("Time")}</TableHead>
|
||||||
<TableHead className="w-[100px]">Memory</TableHead>
|
<TableHead className="w-[100px]">{t("Memory")}</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
||||||
@ -68,11 +84,13 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
|||||||
{sortedSubmissions.map((submission, index) => {
|
{sortedSubmissions.map((submission, index) => {
|
||||||
const Icon = EditorLanguageIcons[submission.language];
|
const Icon = EditorLanguageIcons[submission.language];
|
||||||
const createdAt = new Date(submission.createdAt);
|
const createdAt = new Date(submission.createdAt);
|
||||||
|
const localeInstance = getLocale(locale);
|
||||||
const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
|
const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
|
||||||
? format(createdAt, "yyyy-MM-dd")
|
? format(createdAt, "yyyy-MM-dd")
|
||||||
: formatDistanceToNow(createdAt, { addSuffix: true });
|
: formatDistanceToNow(createdAt, { addSuffix: true, locale: localeInstance });
|
||||||
|
|
||||||
const isEven = (submissions.length - index) % 2 === 0;
|
const isEven = (submissions.length - index) % 2 === 0;
|
||||||
|
const message = statusMap.get(submission.status)?.message;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
@ -89,7 +107,7 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex flex-col truncate">
|
<div className="flex flex-col truncate">
|
||||||
<span className={getStatusColorClass(submission.status)}>
|
<span className={getStatusColorClass(submission.status)}>
|
||||||
{statusMap.get(submission.status)?.message}
|
{s(`${message}`)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs">{submittedDisplay}</span>
|
<span className="text-xs">{submittedDisplay}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import type { Status } from "@/generated/client";
|
import type { Status } from "@/generated/client";
|
||||||
import { getStatusColorClass, statusMap } from "@/lib/status";
|
import { getStatusColorClass, statusMap } from "@/lib/status";
|
||||||
@ -17,7 +18,10 @@ const StatusToast = ({
|
|||||||
Icon: LucideIcon;
|
Icon: LucideIcon;
|
||||||
message: string;
|
message: string;
|
||||||
colorClass: string;
|
colorClass: string;
|
||||||
}) => (
|
}) => {
|
||||||
|
const s = useTranslations("StatusMessage");
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="bg-background text-foreground w-full rounded-md border px-4 py-1 shadow-lg h-10 flex items-center">
|
<div className="bg-background text-foreground w-full rounded-md border px-4 py-1 shadow-lg h-10 flex items-center">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex grow gap-3">
|
<div className="flex grow gap-3">
|
||||||
@ -27,7 +31,7 @@ const StatusToast = ({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<div className="flex grow justify-between gap-12">
|
<div className="flex grow justify-between gap-12">
|
||||||
<p className="text-sm">{message}</p>
|
<p className="text-sm">{s(`${message}`)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -44,7 +48,8 @@ const StatusToast = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
|
||||||
interface ShowStatusToastProps {
|
interface ShowStatusToastProps {
|
||||||
status: Status;
|
status: Status;
|
||||||
|
12
src/lib/i18n.ts
Normal file
12
src/lib/i18n.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Locale } from "@/config/i18n";
|
||||||
|
import { enUS, zhCN } from "date-fns/locale";
|
||||||
|
|
||||||
|
export const getLocale = (locale: Locale) => {
|
||||||
|
switch (locale) {
|
||||||
|
case "zh":
|
||||||
|
return zhCN;
|
||||||
|
case "en":
|
||||||
|
default:
|
||||||
|
return enUS;
|
||||||
|
}
|
||||||
|
}
|
@ -25,16 +25,16 @@ export const getStatusColorClass = (status: Status) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const statusMap = new Map<Status, { icon: LucideIcon; message: string }>([
|
export const statusMap = new Map<Status, { icon: LucideIcon; message: string }>([
|
||||||
["PD", { icon: AlertTriangleIcon, message: "Pending" }],
|
["PD", { icon: AlertTriangleIcon, message: "PD" }],
|
||||||
["QD", { icon: AlertTriangleIcon, message: "Queued" }],
|
["QD", { icon: AlertTriangleIcon, message: "QD" }],
|
||||||
["CP", { icon: AlertTriangleIcon, message: "Compiling" }],
|
["CP", { icon: AlertTriangleIcon, message: "CP" }],
|
||||||
["CE", { icon: AlertTriangleIcon, message: "Compilation Error" }],
|
["CE", { icon: AlertTriangleIcon, message: "CE" }],
|
||||||
["CS", { icon: CircleCheckIcon, message: "Compilation Success" }],
|
["CS", { icon: CircleCheckIcon, message: "CS" }],
|
||||||
["RU", { icon: AlertTriangleIcon, message: "Running" }],
|
["RU", { icon: AlertTriangleIcon, message: "RU" }],
|
||||||
["TLE", { icon: AlertTriangleIcon, message: "Time Limit Exceeded" }],
|
["TLE", { icon: AlertTriangleIcon, message: "TLE" }],
|
||||||
["MLE", { icon: AlertTriangleIcon, message: "Memory Limit Exceeded" }],
|
["MLE", { icon: AlertTriangleIcon, message: "MLE" }],
|
||||||
["RE", { icon: AlertTriangleIcon, message: "Runtime Error" }],
|
["RE", { icon: AlertTriangleIcon, message: "RE" }],
|
||||||
["AC", { icon: CircleCheckIcon, message: "Accepted" }],
|
["AC", { icon: CircleCheckIcon, message: "AC" }],
|
||||||
["WA", { icon: AlertTriangleIcon, message: "Wrong Answer" }],
|
["WA", { icon: AlertTriangleIcon, message: "WA" }],
|
||||||
["SE", { icon: BanIcon, message: "System Error" }],
|
["SE", { icon: BanIcon, message: "SE" }],
|
||||||
]);
|
]);
|
||||||
|
Loading…
Reference in New Issue
Block a user