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:
cfngc4594 2025-05-13 15:36:07 +08:00
parent ad2aca2f67
commit eea16a8224
4 changed files with 116 additions and 68 deletions

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };