mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
feat(problems): add localization support for problem descriptions and solutions
- Replace cached problem data with direct Prisma queries for localized content - Implement locale-based content selection for both descriptions and solutions - Refactor skeleton loading components structure - Change all exports from named to default exports
This commit is contained in:
parent
ad2aca2f67
commit
eea16a8224
@ -1,56 +1,83 @@
|
|||||||
import { getCachedProblem } from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
|
const getLocalizedDescription = (
|
||||||
|
localizations: ProblemLocalization[],
|
||||||
|
locale: Locale
|
||||||
|
) => {
|
||||||
|
if (!localizations || localizations.length === 0) {
|
||||||
|
return "Unknown Description";
|
||||||
|
}
|
||||||
|
|
||||||
|
const localization = localizations.find(
|
||||||
|
(localization) => localization.locale === locale
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
localization?.content ?? localizations[0].content ?? "Unknown Description"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface DescriptionContentProps {
|
interface DescriptionContentProps {
|
||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DescriptionContent = async ({ problemId }: DescriptionContentProps) => {
|
export const DescriptionContent = async ({
|
||||||
const problem = await getCachedProblem(problemId);
|
problemId,
|
||||||
|
}: DescriptionContentProps) => {
|
||||||
|
const locale = await getLocale();
|
||||||
|
|
||||||
|
const descriptions = await prisma.problemLocalization.findMany({
|
||||||
|
where: {
|
||||||
|
problemId,
|
||||||
|
type: "DESCRIPTION",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const description = getLocalizedDescription(descriptions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<MdxRenderer
|
<MdxRenderer source={description} className="p-4 md:p-6" />
|
||||||
source={problem?.description ?? "description not found"}
|
|
||||||
className="p-4 md:p-6"
|
|
||||||
/>
|
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DescriptionContentSkeleton = () => {
|
export const DescriptionContentSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col p-4 md:p-6">
|
<div className="relative h-full w-full">
|
||||||
{/* Title skeleton */}
|
<div className="absolute h-full w-full p-4 md:p-6">
|
||||||
<Skeleton className="h-8 w-3/4 mb-6" />
|
{/* Title skeleton */}
|
||||||
|
<Skeleton className="mb-6 h-8 w-3/4" />
|
||||||
|
|
||||||
{/* Content skeletons */}
|
{/* Content skeletons */}
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-5/6 mb-4" />
|
<Skeleton className="mb-4 h-4 w-5/6" />
|
||||||
<Skeleton className="h-4 w-2/3 mb-4" />
|
<Skeleton className="mb-4 h-4 w-2/3" />
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-4/5 mb-4" />
|
<Skeleton className="mb-4 h-4 w-4/5" />
|
||||||
|
|
||||||
{/* Example section heading */}
|
{/* Example section heading */}
|
||||||
<Skeleton className="h-6 w-1/4 mb-4 mt-8" />
|
<Skeleton className="mb-4 mt-8 h-6 w-1/4" />
|
||||||
|
|
||||||
{/* Example content */}
|
{/* Example content */}
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-5/6 mb-4" />
|
<Skeleton className="mb-4 h-4 w-5/6" />
|
||||||
|
|
||||||
{/* Code block skeleton */}
|
{/* Code block skeleton */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="h-40 w-full rounded-md" />
|
<Skeleton className="h-40 w-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* More content */}
|
||||||
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
|
<Skeleton className="mb-4 h-4 w-3/4" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* More content */}
|
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
|
||||||
<Skeleton className="h-4 w-3/4 mb-4" />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { DescriptionContent, DescriptionContentSkeleton };
|
|
||||||
|
@ -8,7 +8,7 @@ interface DescriptionPanelProps {
|
|||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DescriptionPanel = ({ problemId }: DescriptionPanelProps) => {
|
export const DescriptionPanel = ({ problemId }: DescriptionPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@ -21,5 +21,3 @@ const DescriptionPanel = ({ problemId }: DescriptionPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { DescriptionPanel };
|
|
||||||
|
@ -1,56 +1,81 @@
|
|||||||
import { getCachedProblem } from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
|
const getLocalizedSolution = (
|
||||||
|
localizations: ProblemLocalization[],
|
||||||
|
locale: Locale
|
||||||
|
) => {
|
||||||
|
if (!localizations || localizations.length === 0) {
|
||||||
|
return "Unknown Solution";
|
||||||
|
}
|
||||||
|
|
||||||
|
const localization = localizations.find(
|
||||||
|
(localization) => localization.locale === locale
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
localization?.content ?? localizations[0].content ?? "Unknown Solution"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface SolutionContentProps {
|
interface SolutionContentProps {
|
||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SolutionContent = async ({ problemId }: SolutionContentProps) => {
|
export const SolutionContent = async ({ problemId }: SolutionContentProps) => {
|
||||||
const problem = await getCachedProblem(problemId);
|
const locale = await getLocale();
|
||||||
|
|
||||||
|
const solutions = await prisma.problemLocalization.findMany({
|
||||||
|
where: {
|
||||||
|
problemId,
|
||||||
|
type: "SOLUTION",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const solution = getLocalizedSolution(solutions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<MdxRenderer
|
<MdxRenderer source={solution} className="p-4 md:p-6" />
|
||||||
source={problem?.solution ?? "solution not found"}
|
|
||||||
className="p-4 md:p-6"
|
|
||||||
/>
|
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SolutionContentSkeleton = () => {
|
export const SolutionContentSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col p-4 md:p-6">
|
<div className="relative h-full w-full">
|
||||||
{/* Title skeleton */}
|
<div className="absolute h-full w-full p-4 md:p-6">
|
||||||
<Skeleton className="h-8 w-3/4 mb-6" />
|
{/* Title skeleton */}
|
||||||
|
<Skeleton className="mb-6 h-8 w-3/4" />
|
||||||
|
|
||||||
{/* Content skeletons */}
|
{/* Content skeletons */}
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-5/6 mb-4" />
|
<Skeleton className="mb-4 h-4 w-5/6" />
|
||||||
<Skeleton className="h-4 w-2/3 mb-4" />
|
<Skeleton className="mb-4 h-4 w-2/3" />
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-4/5 mb-4" />
|
<Skeleton className="mb-4 h-4 w-4/5" />
|
||||||
|
|
||||||
{/* Example section heading */}
|
{/* Example section heading */}
|
||||||
<Skeleton className="h-6 w-1/4 mb-4 mt-8" />
|
<Skeleton className="mb-4 mt-8 h-6 w-1/4" />
|
||||||
|
|
||||||
{/* Example content */}
|
{/* Example content */}
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
<Skeleton className="h-4 w-5/6 mb-4" />
|
<Skeleton className="mb-4 h-4 w-5/6" />
|
||||||
|
|
||||||
{/* Code block skeleton */}
|
{/* Code block skeleton */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="h-40 w-full rounded-md" />
|
<Skeleton className="h-40 w-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* More content */}
|
||||||
|
<Skeleton className="mb-4 h-4 w-full" />
|
||||||
|
<Skeleton className="mb-4 h-4 w-3/4" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* More content */}
|
|
||||||
<Skeleton className="h-4 w-full mb-4" />
|
|
||||||
<Skeleton className="h-4 w-3/4 mb-4" />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { SolutionContent, SolutionContentSkeleton };
|
|
||||||
|
@ -8,7 +8,7 @@ interface SolutionPanelProps {
|
|||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SolutionPanel = ({ problemId }: SolutionPanelProps) => {
|
export const SolutionPanel = ({ problemId }: SolutionPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@ -21,5 +21,3 @@ const SolutionPanel = ({ problemId }: SolutionPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { SolutionPanel };
|
|
||||||
|
Loading…
Reference in New Issue
Block a user