detail page text translation added
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { links } from '@/shared/request/links';
|
||||
import { apiRequest } from '@/shared/request/apiRequest';
|
||||
import Sertifikat from './sertifikat';
|
||||
@@ -69,8 +71,8 @@ function parseHighlightedText(text_res: string): React.ReactNode[] {
|
||||
});
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleString('uz-UZ', {
|
||||
function formatDate(iso: string, locale: string): string {
|
||||
return new Date(iso).toLocaleString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
@@ -243,8 +245,9 @@ function LoadingSkeleton() {
|
||||
}
|
||||
|
||||
// ── Error State ───────────────────────────────────────────────────────────────
|
||||
type TFunction = ReturnType<typeof useTranslations>;
|
||||
|
||||
function ErrorState({ message }: { message?: string }) {
|
||||
function ErrorState({ message, t }: { message?: string; t: TFunction }) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-6 py-20 flex flex-col items-center gap-4 text-center">
|
||||
<div className="w-14 h-14 rounded-full bg-red-50 flex items-center justify-center">
|
||||
@@ -262,9 +265,11 @@ function ErrorState({ message }: { message?: string }) {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-slate-700 font-semibold">Failed to load document</p>
|
||||
<p className="text-slate-700 font-semibold">
|
||||
{t('failedToLoadDocument')}
|
||||
</p>
|
||||
<p className="text-slate-400 text-sm">
|
||||
{message ?? 'An unexpected error occurred.'}
|
||||
{message ?? t('unexpectedError')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -273,6 +278,9 @@ function ErrorState({ message }: { message?: string }) {
|
||||
// ── Main Page ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
const t = useTranslations('DetailPage');
|
||||
const { locale } = useParams() as { locale: string };
|
||||
|
||||
const {
|
||||
data: doc,
|
||||
isLoading,
|
||||
@@ -305,7 +313,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<header className="bg-white border-b border-slate-200 h-16.25" />
|
||||
<ErrorState message={(error as Error)?.message} />
|
||||
<ErrorState message={(error as Error)?.message} t={t} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -329,9 +337,9 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
low: 'text-emerald-600 bg-emerald-50 border-emerald-200',
|
||||
};
|
||||
const aiLabel: Record<string, string> = {
|
||||
high: 'AI Content Detected',
|
||||
medium: 'Possible AI Content',
|
||||
low: 'Likely Original',
|
||||
high: t('aiContentDetected'),
|
||||
medium: t('possibleAiContent'),
|
||||
low: t('likelyOriginal'),
|
||||
};
|
||||
|
||||
// Partition analyze_text dynamically:
|
||||
@@ -377,7 +385,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
</button>
|
||||
<div>
|
||||
<p className="text-[11px] uppercase tracking-widest text-slate-400 font-semibold">
|
||||
Document #{doc.id}
|
||||
{t('documentNumber', { id: doc.id })}
|
||||
</p>
|
||||
<h1 className="text-base font-bold text-slate-800 leading-tight">
|
||||
{doc.title}
|
||||
@@ -407,7 +415,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
Download PDF
|
||||
{t('downloadPdf')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -431,7 +439,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Created: {formatDate(doc.created_at)}
|
||||
{t('created')}: {formatDate(doc.created_at, locale)}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<svg
|
||||
@@ -447,7 +455,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
Updated: {formatDate(doc.updated_at)}
|
||||
{t('updated')}: {formatDate(doc.updated_at, locale)}
|
||||
</span>
|
||||
{res?.hash && (
|
||||
<span className="flex items-center gap-1.5 font-mono">
|
||||
@@ -464,14 +472,14 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"
|
||||
/>
|
||||
</svg>
|
||||
Hash: {res.hash}
|
||||
{t('hash')}: {res.hash}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Score Overview ── */}
|
||||
{res && (
|
||||
<Section title="Analysis Scores" accent="bg-violet-500">
|
||||
<Section title={t('analysisScores')} accent="bg-violet-500">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div className="flex flex-wrap justify-around gap-8">
|
||||
<ScoreRing
|
||||
@@ -511,8 +519,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
<div>
|
||||
<p className="font-bold text-sm">{aiLabel[aiRisk]}</p>
|
||||
<p className="text-xs opacity-75 mt-0.5">
|
||||
AI probability score: <strong>{res.ai}%</strong> — content
|
||||
may have been generated or assisted by an AI model.
|
||||
{t('aiProbabilityText', { score: res.ai })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -522,7 +529,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
|
||||
{/* ── Document Text ── */}
|
||||
{(res?.text || textRes) && (
|
||||
<Section title="Document Text" accent="bg-blue-500">
|
||||
<Section title={t('documentText')} accent="bg-blue-500">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
|
||||
<div className="flex border-b border-slate-100">
|
||||
{(['highlighted', 'plain'] as const).map((tab) => (
|
||||
@@ -536,8 +543,8 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
}`}
|
||||
>
|
||||
{tab === 'highlighted'
|
||||
? 'Plagiarism Highlights'
|
||||
: 'Plain Text'}
|
||||
? t('plagiarismHighlights')
|
||||
: t('plainText')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -553,7 +560,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
{activeTab === 'highlighted' && (
|
||||
<div className="px-6 pb-5 flex items-center gap-2 text-xs text-slate-500">
|
||||
<span className="inline-block w-3 h-3 rounded-sm bg-amber-200 border border-amber-300" />
|
||||
Highlighted fragments indicate potential plagiarism matches
|
||||
{t('highlightedHint')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -562,7 +569,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
|
||||
{/* ── Text Statistics (all numeric / non-boolean keys) ── */}
|
||||
{statKeys.length > 0 && (
|
||||
<Section title="Text Statistics" accent="bg-teal-500">
|
||||
<Section title={t('textStatistics')} accent="bg-teal-500">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{statKeys.map((key) => (
|
||||
<StatCard key={key} label={key} value={analyze[key]} />
|
||||
@@ -573,7 +580,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
|
||||
{/* ── Detection Flags (all boolean Yes/No keys) ── */}
|
||||
{flagKeys.length > 0 && (
|
||||
<Section title="Detection Flags" accent="bg-rose-500">
|
||||
<Section title={t('detectionFlags')} accent="bg-rose-500">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
{flagKeys.map((key) => {
|
||||
const val = analyze[key];
|
||||
@@ -607,7 +614,7 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
|
||||
{/* ── Top Words (key detected by "топ"/"top" substring) ── */}
|
||||
{topWordsValue && (
|
||||
<Section title="Top Words" accent="bg-orange-500">
|
||||
<Section title={t('topWords')} accent="bg-orange-500">
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{topWordsValue
|
||||
@@ -639,7 +646,10 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
{/* ── Footer ── */}
|
||||
{result && (
|
||||
<footer className="text-center text-xs text-slate-400 pb-10 mt-4">
|
||||
Result ID #{result.id} · Analyzed {formatDate(result.created_at)}
|
||||
{t('resultFooter', {
|
||||
id: result.id,
|
||||
date: formatDate(result.created_at, locale),
|
||||
})}
|
||||
</footer>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -86,7 +86,7 @@ export function PlagiarismCheckForm() {
|
||||
{submission.status === 'success' && (
|
||||
<StatusBanner
|
||||
status="success"
|
||||
message={`Submission successful! ID`}
|
||||
message={t('submissionSuccess')}
|
||||
onDismiss={resetSubmission}
|
||||
dismissText={t('dismiss')}
|
||||
/>
|
||||
@@ -177,8 +177,7 @@ export function PlagiarismCheckForm() {
|
||||
|
||||
{/* Footer note */}
|
||||
<p className="text-center text-xs text-stone-400 mt-5">
|
||||
Your document is processed securely and not stored beyond the
|
||||
analysis period.
|
||||
{t('secureNote')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user