added multi language features

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-03-31 19:45:21 +05:00
parent 4f3d73443f
commit 291375ce02
19 changed files with 728 additions and 246 deletions

View File

@@ -3,6 +3,7 @@
// ─────────────────────────────────────────────────────────────
'use client';
import React from 'react';
import { useTranslations } from 'next-intl';
import { PlagiarismCheck } from '../lib/types';
import {
getFileExtension,
@@ -166,81 +167,101 @@ interface CheckDetailViewProps {
check: PlagiarismCheck;
}
const CheckHeader: React.FC<CheckDetailViewProps> = ({ check }) => (
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 p-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-4">
<Avatar
name={check.sender.name}
avatarUrl={check.sender.avatarUrl}
size="lg"
/>
<div>
<h1 className="text-xl font-bold text-slate-900">
{check.sender.name}
</h1>
<p className="text-sm text-slate-500">{check.sender.email}</p>
<p className="text-xs text-slate-400 mt-1 font-mono">
ID: {check.id}
</p>
</div>
</div>
<StatusBadge status={check.status} />
</div>
</div>
);
const CheckHeader: React.FC<CheckDetailViewProps> = ({ check }) => {
const t = useTranslations('DetailPage');
const SubmissionInfoCard: React.FC<CheckDetailViewProps> = ({ check }) => (
<SectionCard title="Submission Details" icon={<IconFile />} accent="blue">
<InfoRow
label="Sender"
icon={<IconUser />}
value={
<span className="flex items-center gap-2 justify-end">
<Avatar name={check.sender.name} size="sm" />
{check.sender.name}
</span>
}
/>
<InfoRow
label="File Name"
return (
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 p-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-4">
<Avatar
name={check.sender.name}
avatarUrl={check.sender.avatarUrl}
size="lg"
/>
<div>
<h1 className="text-xl font-bold text-slate-900">
{check.sender.name}
</h1>
<p className="text-sm text-slate-500">{check.sender.email}</p>
<p className="text-xs text-slate-400 mt-1 font-mono">
{t('id')}: {check.id}
</p>
</div>
</div>
<StatusBadge status={check.status} />
</div>
</div>
);
};
const SubmissionInfoCard: React.FC<CheckDetailViewProps> = ({ check }) => {
const t = useTranslations('DetailPage');
return (
<SectionCard
title={t('submissionDetails')}
icon={<IconFile />}
value={
<span className="flex items-center gap-2 justify-end flex-wrap">
<FileTypeBadge extension={getFileExtension(check.fileName)} />
<span className="font-mono text-xs text-slate-700 break-all max-w-60 text-right">
{check.fileName}
accent="blue"
>
<InfoRow
label={t('sender')}
icon={<IconUser />}
value={
<span className="flex items-center gap-2 justify-end">
<Avatar name={check.sender.name} size="sm" />
{check.sender.name}
</span>
</span>
}
/>
<InfoRow
label="File Size"
value={
<span className="text-slate-600">{formatFileSize(check.fileSize)}</span>
}
/>
<InfoRow
label="Submitted"
icon={<IconCalendar />}
value={formatDate(check.submittedAt)}
/>
<InfoRow
label="Payment"
icon={<IconPayment />}
value={
<span className="text-emerald-600 font-bold">
{formatCurrency(check.paymentAmount, check.currency)}
</span>
}
/>
</SectionCard>
);
}
/>
<InfoRow
label={t('fileName')}
icon={<IconFile />}
value={
<span className="flex items-center gap-2 justify-end flex-wrap">
<FileTypeBadge extension={getFileExtension(check.fileName)} />
<span className="font-mono text-xs text-slate-700 break-all max-w-60 text-right">
{check.fileName}
</span>
</span>
}
/>
<InfoRow
label={t('fileSize')}
value={
<span className="text-slate-600">
{formatFileSize(check.fileSize)}
</span>
}
/>
<InfoRow
label={t('submitted')}
icon={<IconCalendar />}
value={formatDate(check.submittedAt)}
/>
<InfoRow
label={t('payment')}
icon={<IconPayment />}
value={
<span className="text-emerald-600 font-bold">
{formatCurrency(check.paymentAmount, check.currency)}
</span>
}
/>
</SectionCard>
);
};
const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
const t = useTranslations('DetailPage');
if (check.status === 'processing' || check.status === 'pending') {
return (
<SectionCard title="Result" icon={<IconShield />} accent="violet">
<SectionCard
title={t('resultTitle')}
icon={<IconShield />}
accent="violet"
>
<div className="flex flex-col items-center justify-center py-10 text-center space-y-3">
<div className="w-12 h-12 rounded-full bg-blue-50 flex items-center justify-center">
<svg
@@ -264,10 +285,10 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
</svg>
</div>
<p className="text-sm font-semibold text-slate-600">
Analysis in progress
{t('analysisInProgress')}
</p>
<p className="text-xs text-slate-400">
Results will appear once processing is complete.
{t('resultsReadyAfterProcessing')}
</p>
</div>
</SectionCard>
@@ -276,8 +297,12 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
if (!check.result) {
return (
<SectionCard title="Result" icon={<IconShield />} accent="violet">
<p className="text-sm text-slate-500 py-4">No result available.</p>
<SectionCard
title={t('resultTitle')}
icon={<IconShield />}
accent="violet"
>
<p className="text-sm text-slate-500 py-4">{t('noResultAvailable')}</p>
</SectionCard>
);
}
@@ -286,7 +311,7 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
return (
<SectionCard
title="Plagiarism Result"
title={t('plagiarismResult')}
icon={<IconShield />}
accent={
result.similarityLevel === 'low'
@@ -309,13 +334,13 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
<p className="text-2xl font-bold text-slate-800 tabular-nums">
{result.checkedWords.toLocaleString()}
</p>
<p className="text-xs text-slate-500 mt-1">Words Checked</p>
<p className="text-xs text-slate-500 mt-1">{t('wordsChecked')}</p>
</div>
<div className="bg-slate-50 rounded-xl p-4 text-center">
<p className="text-2xl font-bold text-slate-800 tabular-nums">
{result.matchedWords.toLocaleString()}
</p>
<p className="text-xs text-slate-500 mt-1">Words Matched</p>
<p className="text-xs text-slate-500 mt-1">{t('wordsMatched')}</p>
</div>
</div>
@@ -323,7 +348,7 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
{result.sources.length > 0 && (
<div>
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-3">
Matched Sources
{t('matchedSources')}
</h3>
<div className="space-y-2">
{result.sources.map((src, i) => (
@@ -352,7 +377,7 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
{src.matchPercentage}%
</span>
<p className="text-xs text-slate-400">
{src.matchedWords.toLocaleString()} words
{src.matchedWords.toLocaleString()} {t('words')}
</p>
</div>
</div>
@@ -362,7 +387,7 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
)}
<InfoRow
label="Processed At"
label={t('processedAt')}
icon={<IconCalendar />}
value={formatDate(result.processedAt)}
/>
@@ -372,19 +397,19 @@ const ResultCard: React.FC<CheckDetailViewProps> = ({ check }) => {
};
const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
const t = useTranslations('DetailPage');
if (!check.certificate) {
return (
<SectionCard title="Certificate" icon={<IconCert />} accent="violet">
<SectionCard title={t('certificate')} icon={<IconCert />} accent="violet">
<div className=" flex flex-col items-center justify-center py-8 text-center space-y-2">
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center">
<IconCert />
</div>
<p className="text-sm text-slate-500">
No certificate issued for this check.
</p>
<p className="text-sm text-slate-500">{t('noCertificate')}</p>
{check.result?.similarityLevel === 'high' && (
<p className="text-xs text-red-500">
Certificates are not issued for high-similarity results.
{t('noCertificateHighSimilarity')}
</p>
)}
</div>
@@ -395,7 +420,7 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
const { certificate } = check;
return (
<SectionCard title="Certificate" icon={<IconCert />} accent="green">
<SectionCard title={t('certificate')} icon={<IconCert />} accent="green">
{/* Certificate visual */}
<div className="relative rounded-xl border-2 border-dashed border-emerald-200 bg-linear-to-br from-emerald-50 to-white p-5 mb-4 overflow-hidden">
<div className="absolute top-2 right-2 opacity-10">
@@ -414,21 +439,21 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
{certificate.verificationCode}
</p>
<p className="text-xs text-slate-500 mt-1">
Certificate ID: {certificate.id}
{t('certificateId')}: {certificate.id}
</p>
</div>
<InfoRow
label="Issued"
label={t('issued')}
icon={<IconCalendar />}
value={formatDate(certificate.issuedAt)}
/>
<InfoRow
label="Expires"
label={t('expires')}
icon={<IconCalendar />}
value={formatDate(certificate.expiresAt)}
/>
<InfoRow label="Issuer" value={certificate.issuerName} />
<InfoRow label={t('issuer')} value={certificate.issuerName} />
<div className="mt-4">
<a
@@ -436,7 +461,7 @@ const CertificateCard: React.FC<CheckDetailViewProps> = ({ check }) => {
className="flex items-center justify-center gap-2 py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold rounded-xl transition-colors"
>
<IconDownload />
Download Certificate
{t('downloadCertificate')}
</a>
</div>
</SectionCard>
@@ -466,6 +491,7 @@ interface PlagiarismDetailPageProps {
export const PlagiarismDetailPage: React.FC<PlagiarismDetailPageProps> = ({
checkId,
}) => {
const t = useTranslations('DetailPage');
const { check, loadingState, error, reload } = usePlagiarismDetail(checkId);
return (
@@ -473,7 +499,7 @@ export const PlagiarismDetailPage: React.FC<PlagiarismDetailPageProps> = ({
<main className="max-w-300 mx-auto px-4 py-6">
{loadingState === 'loading' && <SkeletonLoader />}
{loadingState === 'error' && (
<ErrorState message={error ?? 'Unknown error'} onRetry={reload} />
<ErrorState message={error ?? t('unknownError')} onRetry={reload} />
)}
{loadingState === 'success' && check && (
<CheckDetailView check={check} />