Merge pull request #12 from DavronNabijonv/dev
detail page text translation added
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "Скачать сертификат"
|
||||
}
|
||||
|
||||
@@ -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 bo‘limlar plagiat ehtimolini ko‘rsatadi';
|
||||
textStatistics: 'Matn statistikalari';
|
||||
detectionFlags: 'Aniqlash bayroqlari';
|
||||
topWords: 'Top so‘zlar';
|
||||
aiContentDetected: 'AI kontent aniqlandi';
|
||||
possibleAiContent: 'Ehtimoliy AI kontent';
|
||||
likelyOriginal: 'Ehtimol original';
|
||||
aiProbabilityText: 'AI ehtimollik balli: {{score}}% — kontent AI model tomonidan yaratilgan yoki yordam berilgan bo‘lishi mumkin.';
|
||||
resultFooter: 'Natija ID #{{id}} · Tahlil qilingan {{date}}';
|
||||
noResultAvailable: 'Natija mavjud emas.';
|
||||
plagiarismResult: 'Plagiat natijasi';
|
||||
wordsChecked: "Tekshirilgan so'zlar";
|
||||
|
||||
@@ -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 bo‘limlar plagiat ehtimolini ko‘rsatadi",
|
||||
"textStatistics": "Matn statistikalari",
|
||||
"detectionFlags": "Aniqlash bayroqlari",
|
||||
"topWords": "Top so‘zlar",
|
||||
"aiContentDetected": "AI kontent aniqlandi",
|
||||
"possibleAiContent": "Ehtimoliy AI kontent",
|
||||
"likelyOriginal": "Ehtimol original",
|
||||
"aiProbabilityText": "AI ehtimollik balli: {{score}}% — kontent AI model tomonidan yaratilgan yoki yordam berilgan bo‘lishi 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"
|
||||
}
|
||||
|
||||
@@ -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