detail page text translation added

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-03 14:19:03 +05:00
parent b6dfde7337
commit 9ca13fceda
6 changed files with 131 additions and 34 deletions

View File

@@ -34,6 +34,8 @@
"PlagiarismCheck": {
"badge": "Originality Check",
"title": "Submit Your Document",
"submissionSuccess": "Submission successful! ID",
"secureNote": "Your document is processed securely and not stored beyond the analysis period.",
"description": "Upload a document to verify its originality. Results are typically ready within a few minutes.",
"documentTopic": "Document Topic",
"topicPlaceholder": "e.g. The Impact of Artificial Intelligence on Education",
@@ -80,6 +82,26 @@
"fileName": "File Name",
"fileSize": "File Size",
"submitted": "Submitted",
"failedToLoadDocument": "Failed to load document",
"unexpectedError": "An unexpected error occurred.",
"documentNumber": "Document #{{id}}",
"downloadPdf": "Download PDF",
"created": "Created",
"updated": "Updated",
"hash": "Hash",
"analysisScores": "Analysis Scores",
"documentText": "Document Text",
"plagiarismHighlights": "Plagiarism Highlights",
"plainText": "Plain Text",
"highlightedHint": "Highlighted fragments indicate potential plagiarism matches",
"textStatistics": "Text Statistics",
"detectionFlags": "Detection Flags",
"topWords": "Top Words",
"aiContentDetected": "AI Content Detected",
"possibleAiContent": "Possible AI Content",
"likelyOriginal": "Likely Original",
"aiProbabilityText": "AI probability score: {{score}}% — content may have been generated or assisted by an AI model.",
"resultFooter": "Result ID #{{id}} · Analyzed {{date}}",
"payment": "Payment",
"resultTitle": "Result",
"analysisInProgress": "Analysis in progress",
@@ -211,5 +233,5 @@
},
"unknownUser": "Username not found",
"file": "File",
"upload":"Download certificate"
"upload": "Download certificate"
}

View File

@@ -34,6 +34,8 @@
"PlagiarismCheck": {
"badge": "Проверка оригинальности",
"title": "Отправьте ваш документ",
"submissionSuccess": "Отправка прошла успешно! ID",
"secureNote": "Ваш документ обрабатывается безопасно и не хранится после периода анализа.",
"description": "Загрузите документ для проверки его оригинальности. Результаты обычно готовы в течение нескольких минут.",
"documentTopic": "Тема документа",
"topicPlaceholder": "например: Влияние искусственного интеллекта на образование",
@@ -80,6 +82,26 @@
"fileName": "Имя файла",
"fileSize": "Размер файла",
"submitted": "Отправлено",
"failedToLoadDocument": "Не удалось загрузить документ",
"unexpectedError": "Произошла непредвиденная ошибка.",
"documentNumber": "Документ №{{id}}",
"downloadPdf": "Скачать PDF",
"created": "Создан",
"updated": "Обновлен",
"hash": "Hash",
"analysisScores": "Оценки анализа",
"documentText": "Текст документа",
"plagiarismHighlights": "Выделения плагиата",
"plainText": "Обычный текст",
"highlightedHint": "Выделенные фрагменты указывают на возможный плагиат",
"textStatistics": "Статистика текста",
"detectionFlags": "Флаги обнаружения",
"topWords": "Топ слова",
"aiContentDetected": "Обнаружен AI-контент",
"possibleAiContent": "Возможный AI-контент",
"likelyOriginal": "Вероятно оригинал",
"aiProbabilityText": "Вероятность AI: {{score}}% — контент может быть создан или сгенерирован AI.",
"resultFooter": "Результат ID #{{id}} · Анализирован {{date}}",
"payment": "Оплата",
"resultTitle": "Результат",
"analysisInProgress": "Анализ выполняется",
@@ -210,6 +232,6 @@
"payButton": "Оплатить через Payme"
},
"unknownUser": "Имя пользователя не найдено",
"file":"Файл",
"upload":"Скачать сертификат"
"file": "Файл",
"upload": "Скачать сертификат"
}

View File

@@ -37,6 +37,8 @@ declare const messages: {
PlagiarismCheck: {
badge: 'Orijinallik tekshiruvi';
title: 'Hujjatni yuboring';
submissionSuccess: 'Yuborish muvaffaqiyatli yakunlandi! ID';
secureNote: 'Hujjatingiz xavfsiz qayta ishlanadi va tahlil muddati tugagach saqlanmaydi.';
description: "Hujjatning orijinalligini tekshirish uchun yuklang. Natijalar odatda bir necha daqiqada tayyor bo'ladi.";
documentTopic: 'Hujjat mavzusi';
topicPlaceholder: "masalan: Sun'iy intellektning ta'limga ta'siri";
@@ -87,6 +89,26 @@ declare const messages: {
resultTitle: 'Natija';
analysisInProgress: 'Tahlil davom etmoqda';
resultsReadyAfterProcessing: "Natijalar qayta ishlash tugagach paydo bo'ladi.";
failedToLoadDocument: 'Hujjatni yuklash muvaffaqiyatsiz tugadi';
unexpectedError: 'Kutilmagan xatolik yuz berdi.';
documentNumber: 'Hujjat №{{id}}';
downloadPdf: 'PDFni yuklab olish';
created: 'Yaratilgan';
updated: 'Yangilangan';
hash: 'Hash';
analysisScores: 'Tahlil ballari';
documentText: 'Hujjat matni';
plagiarismHighlights: 'Plagiat belgilari';
plainText: 'Oddiy matn';
highlightedHint: 'Belgilangan bolimlar plagiat ehtimolini korsatadi';
textStatistics: 'Matn statistikalari';
detectionFlags: 'Aniqlash bayroqlari';
topWords: 'Top sozlar';
aiContentDetected: 'AI kontent aniqlandi';
possibleAiContent: 'Ehtimoliy AI kontent';
likelyOriginal: 'Ehtimol original';
aiProbabilityText: 'AI ehtimollik balli: {{score}}% — kontent AI model tomonidan yaratilgan yoki yordam berilgan bolishi mumkin.';
resultFooter: 'Natija ID #{{id}} · Tahlil qilingan {{date}}';
noResultAvailable: 'Natija mavjud emas.';
plagiarismResult: 'Plagiat natijasi';
wordsChecked: "Tekshirilgan so'zlar";

View File

@@ -34,6 +34,8 @@
"PlagiarismCheck": {
"badge": "Orijinallik tekshiruvi",
"title": "Hujjatni yuboring",
"submissionSuccess": "Yuborish muvaffaqiyatli yakunlandi! ID",
"secureNote": "Hujjatingiz xavfsiz qayta ishlanadi va tahlil muddati tugagach saqlanmaydi.",
"description": "Hujjatning orijinalligini tekshirish uchun yuklang. Natijalar odatda bir necha daqiqada tayyor bo'ladi.",
"documentTopic": "Hujjat mavzusi",
"topicPlaceholder": "masalan: Sun'iy intellektning ta'limga ta'siri",
@@ -84,6 +86,26 @@
"resultTitle": "Natija",
"analysisInProgress": "Tahlil davom etmoqda",
"resultsReadyAfterProcessing": "Natijalar qayta ishlash tugagach paydo bo'ladi.",
"failedToLoadDocument": "Hujjatni yuklash muvaffaqiyatsiz tugadi",
"unexpectedError": "Kutilmagan xatolik yuz berdi.",
"documentNumber": "Hujjat №{{id}}",
"downloadPdf": "PDFni yuklab olish",
"created": "Yaratilgan",
"updated": "Yangilangan",
"hash": "Hash",
"analysisScores": "Tahlil ballari",
"documentText": "Hujjat matni",
"plagiarismHighlights": "Plagiat belgilari",
"plainText": "Oddiy matn",
"highlightedHint": "Belgilangan bolimlar plagiat ehtimolini korsatadi",
"textStatistics": "Matn statistikalari",
"detectionFlags": "Aniqlash bayroqlari",
"topWords": "Top sozlar",
"aiContentDetected": "AI kontent aniqlandi",
"possibleAiContent": "Ehtimoliy AI kontent",
"likelyOriginal": "Ehtimol original",
"aiProbabilityText": "AI ehtimollik balli: {{score}}% — kontent AI model tomonidan yaratilgan yoki yordam berilgan bolishi mumkin.",
"resultFooter": "Natija ID #{{id}} · Tahlil qilingan {{date}}",
"noResultAvailable": "Natija mavjud emas.",
"plagiarismResult": "Plagiat natijasi",
"wordsChecked": "Tekshirilgan so'zlar",
@@ -209,7 +231,7 @@
"connecting": "Paymega ulanmoqda…",
"payButton": "Payme orqali to'lash"
},
"unknownUser":"Foydalanuvchi topilmadi",
"file":"Fayl",
"upload":"Sertifikatni yuklab olish"
"unknownUser": "Foydalanuvchi topilmadi",
"file": "Fayl",
"upload": "Sertifikatni yuklab olish"
}

View File

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

View File

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