mirror of
https://github.com/massbug/judge4c.git
synced 2026-10-15 15:17:31 +00:00
refactor(i18n): replace hardcoded texts with i18n message keys
This commit is contained in:
@@ -2,22 +2,25 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { CheckIcon, MinusIcon } from "lucide-react";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
const items = [
|
||||
{ value: "system", label: "System", image: "/ui-system.png" },
|
||||
{ value: "light", label: "Light", image: "/ui-light.png" },
|
||||
{ value: "dark", label: "Dark", image: "/ui-dark.png" },
|
||||
];
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
const t = useTranslations("AppearanceSettings");
|
||||
|
||||
const items = [
|
||||
{ value: "system", label: t("items.System"), image: "/ui-system.png" },
|
||||
{ value: "light", label: t("items.Light"), image: "/ui-light.png" },
|
||||
{ value: "dark", label: t("items.Dark"), image: "/ui-dark.png" },
|
||||
];
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<fieldset className="space-y-4">
|
||||
<legend className="text-foreground text-sm leading-none font-medium">
|
||||
Choose a theme
|
||||
{t("title")}
|
||||
</legend>
|
||||
<RadioGroup className="flex gap-3" defaultValue={theme}>
|
||||
{items.map((item) => (
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { auth, signOut } from "@/lib/auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import LogInButton from "@/components/log-in-button";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
@@ -28,6 +29,7 @@ async function handleLogOut() {
|
||||
|
||||
export async function AvatarButton() {
|
||||
const session = await auth();
|
||||
const t = await getTranslations("AvatarButton");
|
||||
const isLoggedIn = !!session?.user;
|
||||
const image = session?.user?.image ?? "https://github.com/shadcn.png";
|
||||
const name = session?.user?.name ?? "unknown";
|
||||
@@ -64,7 +66,7 @@ export async function AvatarButton() {
|
||||
<SettingsButton />
|
||||
<DropdownMenuItem onClick={handleLogOut}>
|
||||
<LogOut />
|
||||
Log out
|
||||
{t("LogOut")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
@@ -14,6 +15,8 @@ export default function BackButton({
|
||||
className,
|
||||
...props
|
||||
}: BackButtonProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
@@ -29,7 +32,9 @@ export default function BackButton({
|
||||
</Link>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">Back</TooltipContent>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("BackButton")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { ArrowRightIcon } from "lucide-react";
|
||||
|
||||
interface BannerProps {
|
||||
@@ -11,9 +12,11 @@ interface BannerProps {
|
||||
export function Banner({
|
||||
className,
|
||||
link = siteConfig.url.repo.github,
|
||||
text = "Star this project if you like it.",
|
||||
text,
|
||||
...props
|
||||
}: BannerProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<header
|
||||
{...props}
|
||||
@@ -25,7 +28,7 @@ export function Banner({
|
||||
<p className="flex justify-center text-sm">
|
||||
<a href={link} className="group">
|
||||
<span className="me-1 text-base leading-none">✨</span>
|
||||
{text}
|
||||
{text || t("Banner.Text")}
|
||||
<ArrowRightIcon
|
||||
className="ms-2 -mt-0.5 inline-flex opacity-60 transition-transform group-hover:translate-x-0.5"
|
||||
size={16}
|
||||
|
||||
@@ -8,11 +8,13 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { BotIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { useDockviewStore } from "@/stores/dockview";
|
||||
|
||||
export default function BotVisibilityToggle() {
|
||||
const { api } = useDockviewStore();
|
||||
const t = useTranslations();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isBotVisible, setBotVisible] = useState<boolean>(false);
|
||||
|
||||
@@ -37,7 +39,7 @@ export default function BotVisibilityToggle() {
|
||||
id: "Bot",
|
||||
component: "Bot",
|
||||
tabComponent: "Bot",
|
||||
title: "Bot",
|
||||
title: t("ProblemPage.Bot"),
|
||||
position: {
|
||||
direction: "right",
|
||||
},
|
||||
@@ -70,7 +72,7 @@ export default function BotVisibilityToggle() {
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<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>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { RunCode } from "@/components/run-code";
|
||||
import { auth } from "@/lib/auth";
|
||||
import BackButton from "@/components/back-button";
|
||||
import { RunCodeButton } from "@/components/run-code";
|
||||
import { AvatarButton } from "@/components/avatar-button";
|
||||
import BotVisibilityToggle from "@/components/bot-visibility-toggle";
|
||||
|
||||
@@ -8,10 +9,12 @@ interface PlaygroundHeaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PlaygroundHeader({
|
||||
export async function PlaygroundHeader({
|
||||
className,
|
||||
...props
|
||||
}: PlaygroundHeaderProps) {
|
||||
const session = await auth();
|
||||
|
||||
return (
|
||||
<header
|
||||
{...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="relative flex">
|
||||
<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>
|
||||
|
||||
@@ -9,12 +9,14 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function CopyButton() {
|
||||
const { editor } = useProblem();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const t = useTranslations("WorkspaceEditorHeader.CopyButton");
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
@@ -65,7 +67,7 @@ export function CopyButton() {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Click to Copy
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
import { CircleXIcon, TriangleAlertIcon } from "lucide-react";
|
||||
|
||||
@@ -20,6 +21,7 @@ export function WorkspaceEditorFooter({
|
||||
className,
|
||||
...props
|
||||
}: WorkspaceEditorFooterProps) {
|
||||
const t = useTranslations("WorkspaceEditorFooter");
|
||||
const { editor, markers } = useProblem();
|
||||
const [position, setPosition] = useState<{ lineNumber: number; column: number } | null>(null);
|
||||
|
||||
@@ -62,8 +64,8 @@ export function WorkspaceEditorFooter({
|
||||
</div>
|
||||
<span className="truncate">
|
||||
{position
|
||||
? `Row ${position.lineNumber}, Column ${position.column}`
|
||||
: "Row -, Column -"}
|
||||
? `${t("Row")} ${position.lineNumber}, ${t("Column")} ${position.column}`
|
||||
: `${t("Row")} -, ${t("Column")} -`}
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Paintbrush } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function FormatButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.FormatButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
@@ -20,7 +22,7 @@ export function FormatButton() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Format Code"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("format", "editor.action.formatDocument", null);
|
||||
}}
|
||||
@@ -31,7 +33,7 @@ export function FormatButton() {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Format Code
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
+3
-1
@@ -6,6 +6,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
@@ -28,6 +29,7 @@ const getLspStatusColor = (webSocket: WebSocket | null) => {
|
||||
|
||||
export function LspStatusButton() {
|
||||
const { webSocket } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.LspStatusButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
@@ -48,7 +50,7 @@ export function LspStatusButton() {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Language Server
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Redo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function RedoButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.RedoButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
@@ -20,7 +22,7 @@ export function RedoButton() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Redo Code"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("redo", "redo", null);
|
||||
}}
|
||||
@@ -31,7 +33,7 @@ export function RedoButton() {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Redo Code
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function ResetButton() {
|
||||
const { editor, currentTemplate } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.ResetButton");
|
||||
|
||||
const handleReset = () => {
|
||||
if (editor) {
|
||||
@@ -38,7 +40,7 @@ export function ResetButton() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Reset Code"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={handleReset}
|
||||
disabled={!editor}
|
||||
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>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Reset Code
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Undo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function UndoButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.UndoButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
@@ -20,7 +22,7 @@ export function UndoButton() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Undo Code"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("undo", "undo", null);
|
||||
}}
|
||||
@@ -31,7 +33,7 @@ export function UndoButton() {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
Undo Code
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -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";
|
||||
|
||||
import { LogIn } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
@@ -8,6 +9,7 @@ export default function LogInButton() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("AvatarButton");
|
||||
|
||||
const handleLogIn = () => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
@@ -17,8 +19,8 @@ export default function LogInButton() {
|
||||
|
||||
return (
|
||||
<DropdownMenuItem onClick={handleLogIn}>
|
||||
<LogIn className="mr-2 h-4 w-4" />
|
||||
Log In
|
||||
<LogIn />
|
||||
{t("LogIn")}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
+33
-18
@@ -8,24 +8,32 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Session } from "next-auth";
|
||||
import { judge } from "@/actions/judge";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
import { useDockviewStore } from "@/stores/dockview";
|
||||
import { LoaderCircleIcon, PlayIcon } from "lucide-react";
|
||||
import { showStatusToast } from "@/hooks/show-status-toast";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
interface RunCodeProps {
|
||||
interface RunCodeButtonProps {
|
||||
className?: string;
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
export function RunCode({
|
||||
export function RunCodeButton({
|
||||
className,
|
||||
...props
|
||||
}: RunCodeProps) {
|
||||
session,
|
||||
}: RunCodeButtonProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const { api } = useDockviewStore();
|
||||
const { currentLang, editor, problemId } = useProblem();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const t = useTranslations("PlaygroundHeader.RunCodeButton");
|
||||
|
||||
const handleJudge = async () => {
|
||||
if (!editor) return;
|
||||
@@ -33,18 +41,24 @@ export function RunCode({
|
||||
const code = editor.getValue() || "";
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const result = await judge(currentLang, code, problemId);
|
||||
showStatusToast({ status: result.status });
|
||||
const panel = api?.getPanel("Submissions");
|
||||
if (panel && !panel.api.isActive) {
|
||||
panel.api.setActive();
|
||||
if (!session) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set("redirectTo", pathname);
|
||||
router.push(`/sign-in?${params.toString()}`);
|
||||
} else {
|
||||
try {
|
||||
const result = await judge(currentLang, code, problemId);
|
||||
showStatusToast({ status: result.status });
|
||||
const panel = api?.getPanel("Submissions");
|
||||
if (panel && !panel.api.isActive) {
|
||||
panel.api.setActive();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error occurred while judging the code:");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error occurred while judging the code:");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,7 +67,6 @@ export function RunCode({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
{...props}
|
||||
variant="secondary"
|
||||
className={cn("h-8 px-3 py-1.5", className)}
|
||||
onClick={handleJudge}
|
||||
@@ -72,10 +85,12 @@ export function RunCode({
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{isLoading ? "Running..." : "Run"}
|
||||
{isLoading ? t("TooltipTrigger.loading") : t("TooltipTrigger.ready")}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">Run Code</TooltipContent>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function SettingsButton() {
|
||||
const t = useTranslations("AvatarButton");
|
||||
const { setDialogOpen } = useSettingsStore();
|
||||
|
||||
return (
|
||||
<DropdownMenuItem onClick={() => setDialogOpen(true)}>
|
||||
<Settings />
|
||||
Settings
|
||||
{t("Settings")}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,29 +25,31 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import AppearanceSettings from "./appearance-settings";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
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";
|
||||
|
||||
const data = {
|
||||
nav: [
|
||||
{ name: "Appearance", icon: Paintbrush },
|
||||
{ name: "Language & region", icon: Globe },
|
||||
{ name: "Code Editor", icon: CodeXml },
|
||||
{ name: "Advanced", icon: Settings },
|
||||
],
|
||||
};
|
||||
|
||||
export function SettingsDialog() {
|
||||
const t = useTranslations("SettingsDialog");
|
||||
const data = {
|
||||
nav: [
|
||||
{ id: "Appearance", name: t("nav.Appearance"), icon: Paintbrush },
|
||||
{ id: "Language", name: t("nav.Language"), icon: Globe },
|
||||
{ id: "CodeEditor", name: t("nav.CodeEditor"), icon: CodeXml },
|
||||
{ id: "Advanced", name: t("nav.Advanced"), icon: Settings },
|
||||
],
|
||||
};
|
||||
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } = useSettingsStore();
|
||||
|
||||
return (
|
||||
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
|
||||
<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">
|
||||
Customize your settings here.
|
||||
{t("description")}
|
||||
</DialogDescription>
|
||||
<SidebarProvider className="items-start">
|
||||
<Sidebar collapsible="none" className="hidden md:flex">
|
||||
@@ -59,10 +61,10 @@ export function SettingsDialog() {
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={item.name === activeSetting}
|
||||
onClick={() => setActiveSetting(item.name)}
|
||||
isActive={item.id === activeSetting}
|
||||
onClick={() => setActiveSetting(item.id)}
|
||||
>
|
||||
<a href="#">
|
||||
<a>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
@@ -80,11 +82,11 @@ export function SettingsDialog() {
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">Settings</BreadcrumbLink>
|
||||
<BreadcrumbLink>{t("breadcrumb")}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{activeSetting}</BreadcrumbPage>
|
||||
<BreadcrumbPage>{t(`nav.${activeSetting}`)}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@@ -92,16 +94,8 @@ export function SettingsDialog() {
|
||||
</header>
|
||||
<ScrollArea className="flex-1 overflow-y-auto p-4 pt-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
{activeSetting === "Appearance" ? (
|
||||
<AppearanceSettings />
|
||||
) : (
|
||||
Array.from({ length: 10 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-video max-w-3xl rounded-xl bg-muted/50"
|
||||
/>
|
||||
))
|
||||
)}
|
||||
{activeSetting === "Appearance" && <AppearanceSettings />}
|
||||
{activeSetting === "Language" && <LanguageSettings />}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</main>
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
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 { Clock4Icon, CpuIcon } from "lucide-react";
|
||||
import { useDockviewStore } from "@/stores/dockview";
|
||||
@@ -18,10 +21,23 @@ import { EditorLanguageIcons } from "@/config/editor-language-icons";
|
||||
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
|
||||
|
||||
interface SubmissionsTableProps {
|
||||
locale: Locale;
|
||||
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 { api, setSubmission } = useDockviewStore();
|
||||
|
||||
@@ -41,7 +57,7 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
||||
id: "Details",
|
||||
component: "Details",
|
||||
tabComponent: "Details",
|
||||
title: submission.status,
|
||||
title: s(`${submission.status}`),
|
||||
position: {
|
||||
referencePanel: "Submissions",
|
||||
direction: "within",
|
||||
@@ -54,11 +70,11 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
||||
<Table>
|
||||
<TableHeader className="bg-transparent">
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableHead className="w-[100px]">Index</TableHead>
|
||||
<TableHead className="w-[170px]">Status</TableHead>
|
||||
<TableHead className="w-[100px]">Language</TableHead>
|
||||
<TableHead className="w-[100px]">Time</TableHead>
|
||||
<TableHead className="w-[100px]">Memory</TableHead>
|
||||
<TableHead className="w-[100px]">{t("Index")}</TableHead>
|
||||
<TableHead className="w-[170px]">{t("Status")}</TableHead>
|
||||
<TableHead className="w-[100px]">{t("Language")}</TableHead>
|
||||
<TableHead className="w-[100px]">{t("Time")}</TableHead>
|
||||
<TableHead className="w-[100px]">{t("Memory")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -68,11 +84,13 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
||||
{sortedSubmissions.map((submission, index) => {
|
||||
const Icon = EditorLanguageIcons[submission.language];
|
||||
const createdAt = new Date(submission.createdAt);
|
||||
const localeInstance = getLocale(locale);
|
||||
const submittedDisplay = isBefore(createdAt, subDays(new Date(), 1))
|
||||
? format(createdAt, "yyyy-MM-dd")
|
||||
: formatDistanceToNow(createdAt, { addSuffix: true });
|
||||
: formatDistanceToNow(createdAt, { addSuffix: true, locale: localeInstance });
|
||||
|
||||
const isEven = (submissions.length - index) % 2 === 0;
|
||||
const message = statusMap.get(submission.status)?.message;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -89,7 +107,7 @@ export default function SubmissionsTable({ submissions }: SubmissionsTableProps)
|
||||
<TableCell>
|
||||
<div className="flex flex-col truncate">
|
||||
<span className={getStatusColorClass(submission.status)}>
|
||||
{statusMap.get(submission.status)?.message}
|
||||
{s(`${message}`)}
|
||||
</span>
|
||||
<span className="text-xs">{submittedDisplay}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user