feat: add comparison page functionality and related routes
- Added a new route for comparison pages in routes.ts. - Introduced a TOOL_WORKFLOWS object in seoData.ts to define tool usage sequences. - Updated internal link generation to include workflow slugs. - Added Arabic, English, and French translations for comparison features and FAQs in respective i18n files. - Implemented the ComparisonPage component to display feature comparisons, advantages, verdicts, and related tools. - Enhanced sitemap generation script to include comparison pages.
This commit is contained in:
@@ -31,6 +31,7 @@ const DevelopersPage = lazy(() => import('@/pages/DevelopersPage'));
|
||||
const AllToolsPage = lazy(() => import('@/pages/AllToolsPage'));
|
||||
const InternalAdminPage = lazy(() => import('@/pages/InternalAdminPage'));
|
||||
const SeoRoutePage = lazy(() => import('@/pages/SeoRoutePage'));
|
||||
const ComparisonPage = lazy(() => import('@/pages/ComparisonPage'));
|
||||
const CookieConsent = lazy(() => import('@/components/layout/CookieConsent'));
|
||||
const SiteAssistant = lazy(() => import('@/components/layout/SiteAssistant'));
|
||||
|
||||
@@ -122,6 +123,7 @@ export default function App() {
|
||||
<Route path="/developers" element={<DevelopersPage />} />
|
||||
<Route path="/tools" element={<AllToolsPage />} />
|
||||
<Route path="/internal/admin" element={<InternalAdminPage />} />
|
||||
<Route path="/compare/:slug" element={<ComparisonPage />} />
|
||||
<Route path="/ar/:slug" element={<SeoRoutePage />} />
|
||||
<Route path="/:slug" element={<SeoRoutePage />} />
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ const FOOTER_TOOLS = {
|
||||
{ slug: 'free-pdf-tools-online', label: 'Free PDF Tools Online', isLanding: true },
|
||||
{ slug: 'convert-files-online', label: 'Convert Files Online', isLanding: true },
|
||||
],
|
||||
Comparisons: [
|
||||
{ slug: 'compress-pdf-vs-ilovepdf', label: 'Dociva vs iLovePDF', isComparison: true },
|
||||
{ slug: 'merge-pdf-vs-smallpdf', label: 'Dociva vs Smallpdf', isComparison: true },
|
||||
{ slug: 'pdf-to-word-vs-adobe-acrobat', label: 'Dociva vs Adobe Acrobat', isComparison: true },
|
||||
{ slug: 'compress-image-vs-tinypng', label: 'Dociva vs TinyPNG', isComparison: true },
|
||||
{ slug: 'ocr-vs-adobe-scan', label: 'Dociva vs Adobe Scan', isComparison: true },
|
||||
],
|
||||
};
|
||||
|
||||
export default function Footer() {
|
||||
@@ -55,7 +62,7 @@ export default function Footer() {
|
||||
{tools.map((tool) => (
|
||||
<li key={tool.slug}>
|
||||
<Link
|
||||
to={(tool as { slug: string; isLanding?: boolean }).isLanding ? `/${tool.slug}` : `/tools/${tool.slug}`}
|
||||
to={(tool as { slug: string; isLanding?: boolean; isComparison?: boolean }).isComparison ? `/compare/${tool.slug}` : (tool as { slug: string; isLanding?: boolean }).isLanding ? `/${tool.slug}` : `/tools/${tool.slug}`}
|
||||
className="text-sm text-slate-500 transition-colors hover:text-primary-600 dark:text-slate-400 dark:hover:text-primary-400"
|
||||
>
|
||||
{tool.label}
|
||||
|
||||
145
frontend/src/config/comparisonData.ts
Normal file
145
frontend/src/config/comparisonData.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Comparison page data — defines competitor comparisons for SEO landing pages.
|
||||
*
|
||||
* Adding a new comparison:
|
||||
* 1. Append an entry to COMPARISON_PAGES below.
|
||||
* 2. Add matching i18n keys under pages.comparison.<i18nKey>.* in en/ar/fr.json.
|
||||
* 3. Add the slug to both sitemap generators (generate_sitemap.py + generate-seo-assets.mjs).
|
||||
*/
|
||||
|
||||
export interface ComparisonFeature {
|
||||
/** i18n key suffix under pages.comparison.features.* */
|
||||
key: string;
|
||||
/** true = has the feature, false = missing, 'partial' = limited */
|
||||
us: boolean | 'partial';
|
||||
competitor: boolean | 'partial';
|
||||
}
|
||||
|
||||
export interface ComparisonPage {
|
||||
/** URL slug: /compare/<slug> */
|
||||
slug: string;
|
||||
/** i18n key prefix: pages.comparison.<i18nKey>.* */
|
||||
i18nKey: string;
|
||||
/** Our tool slug (links to /tools/<ourToolSlug>) */
|
||||
ourToolSlug: string;
|
||||
/** Competitor display name */
|
||||
competitorName: string;
|
||||
/** SEO category */
|
||||
category: 'PDF' | 'Image' | 'AI' | 'Convert' | 'Utility';
|
||||
/** Feature comparison rows */
|
||||
features: ComparisonFeature[];
|
||||
/** Related comparison page slugs */
|
||||
relatedComparisonSlugs: string[];
|
||||
/** Tool slugs to show as related */
|
||||
relatedToolSlugs: string[];
|
||||
}
|
||||
|
||||
export const COMPARISON_PAGES: ComparisonPage[] = [
|
||||
{
|
||||
slug: 'compress-pdf-vs-ilovepdf',
|
||||
i18nKey: 'compressPdfVsIlovepdf',
|
||||
ourToolSlug: 'compress-pdf',
|
||||
competitorName: 'iLovePDF',
|
||||
category: 'PDF',
|
||||
features: [
|
||||
{ key: 'freeUnlimited', us: true, competitor: false },
|
||||
{ key: 'noSignup', us: true, competitor: false },
|
||||
{ key: 'batchProcessing', us: true, competitor: 'partial' },
|
||||
{ key: 'compressionLevels', us: true, competitor: true },
|
||||
{ key: 'autoDelete', us: true, competitor: true },
|
||||
{ key: 'noAds', us: true, competitor: false },
|
||||
{ key: 'apiAccess', us: true, competitor: true },
|
||||
{ key: 'offlineMode', us: false, competitor: false },
|
||||
],
|
||||
relatedComparisonSlugs: ['merge-pdf-vs-smallpdf', 'pdf-to-word-vs-adobe-acrobat'],
|
||||
relatedToolSlugs: ['compress-pdf', 'merge-pdf', 'split-pdf', 'compress-image'],
|
||||
},
|
||||
{
|
||||
slug: 'merge-pdf-vs-smallpdf',
|
||||
i18nKey: 'mergePdfVsSmallpdf',
|
||||
ourToolSlug: 'merge-pdf',
|
||||
competitorName: 'Smallpdf',
|
||||
category: 'PDF',
|
||||
features: [
|
||||
{ key: 'freeUnlimited', us: true, competitor: false },
|
||||
{ key: 'noSignup', us: true, competitor: false },
|
||||
{ key: 'batchProcessing', us: true, competitor: true },
|
||||
{ key: 'dragReorder', us: true, competitor: true },
|
||||
{ key: 'autoDelete', us: true, competitor: true },
|
||||
{ key: 'noAds', us: true, competitor: false },
|
||||
{ key: 'apiAccess', us: true, competitor: 'partial' },
|
||||
{ key: 'offlineMode', us: false, competitor: false },
|
||||
],
|
||||
relatedComparisonSlugs: ['compress-pdf-vs-ilovepdf', 'pdf-to-word-vs-adobe-acrobat'],
|
||||
relatedToolSlugs: ['merge-pdf', 'split-pdf', 'compress-pdf', 'reorder-pdf'],
|
||||
},
|
||||
{
|
||||
slug: 'pdf-to-word-vs-adobe-acrobat',
|
||||
i18nKey: 'pdfToWordVsAdobeAcrobat',
|
||||
ourToolSlug: 'pdf-to-word',
|
||||
competitorName: 'Adobe Acrobat',
|
||||
category: 'PDF',
|
||||
features: [
|
||||
{ key: 'freeUnlimited', us: true, competitor: false },
|
||||
{ key: 'noSignup', us: true, competitor: false },
|
||||
{ key: 'preserveFormatting', us: true, competitor: true },
|
||||
{ key: 'batchProcessing', us: true, competitor: true },
|
||||
{ key: 'autoDelete', us: true, competitor: true },
|
||||
{ key: 'noAds', us: true, competitor: true },
|
||||
{ key: 'noInstall', us: true, competitor: false },
|
||||
{ key: 'offlineMode', us: false, competitor: true },
|
||||
],
|
||||
relatedComparisonSlugs: ['compress-pdf-vs-ilovepdf', 'merge-pdf-vs-smallpdf'],
|
||||
relatedToolSlugs: ['pdf-to-word', 'word-to-pdf', 'pdf-to-excel', 'compress-pdf'],
|
||||
},
|
||||
{
|
||||
slug: 'compress-image-vs-tinypng',
|
||||
i18nKey: 'compressImageVsTinypng',
|
||||
ourToolSlug: 'compress-image',
|
||||
competitorName: 'TinyPNG',
|
||||
category: 'Image',
|
||||
features: [
|
||||
{ key: 'freeUnlimited', us: true, competitor: false },
|
||||
{ key: 'noSignup', us: true, competitor: true },
|
||||
{ key: 'multiFormat', us: true, competitor: 'partial' },
|
||||
{ key: 'batchProcessing', us: true, competitor: 'partial' },
|
||||
{ key: 'qualityControl', us: true, competitor: false },
|
||||
{ key: 'autoDelete', us: true, competitor: true },
|
||||
{ key: 'apiAccess', us: true, competitor: true },
|
||||
{ key: 'offlineMode', us: false, competitor: false },
|
||||
],
|
||||
relatedComparisonSlugs: ['compress-pdf-vs-ilovepdf', 'ocr-vs-adobe-scan'],
|
||||
relatedToolSlugs: ['compress-image', 'image-converter', 'image-resize', 'remove-background'],
|
||||
},
|
||||
{
|
||||
slug: 'ocr-vs-adobe-scan',
|
||||
i18nKey: 'ocrVsAdobeScan',
|
||||
ourToolSlug: 'ocr',
|
||||
competitorName: 'Adobe Scan',
|
||||
category: 'AI',
|
||||
features: [
|
||||
{ key: 'freeUnlimited', us: true, competitor: false },
|
||||
{ key: 'noSignup', us: true, competitor: false },
|
||||
{ key: 'noInstall', us: true, competitor: false },
|
||||
{ key: 'multiLanguageOcr', us: true, competitor: true },
|
||||
{ key: 'batchProcessing', us: true, competitor: false },
|
||||
{ key: 'autoDelete', us: true, competitor: true },
|
||||
{ key: 'apiAccess', us: true, competitor: false },
|
||||
{ key: 'offlineMode', us: false, competitor: true },
|
||||
],
|
||||
relatedComparisonSlugs: ['pdf-to-word-vs-adobe-acrobat', 'compress-image-vs-tinypng'],
|
||||
relatedToolSlugs: ['ocr', 'chat-pdf', 'summarize-pdf', 'pdf-to-word'],
|
||||
},
|
||||
];
|
||||
|
||||
export function getComparisonPage(slug: string): ComparisonPage | undefined {
|
||||
return COMPARISON_PAGES.find((p) => p.slug === slug);
|
||||
}
|
||||
|
||||
export function getAllComparisonSlugs(): string[] {
|
||||
return COMPARISON_PAGES.map((p) => p.slug);
|
||||
}
|
||||
|
||||
export function getComparisonPagesByTool(toolSlug: string): ComparisonPage[] {
|
||||
return COMPARISON_PAGES.filter((p) => p.ourToolSlug === toolSlug);
|
||||
}
|
||||
@@ -27,6 +27,7 @@ const STATIC_PAGE_ROUTES = [
|
||||
'/tools',
|
||||
'/internal/admin',
|
||||
'/pricing-transparency',
|
||||
'/compare/:slug',
|
||||
] as const;
|
||||
|
||||
const SEO_PAGE_ROUTES = getAllSeoLandingPaths();
|
||||
|
||||
@@ -917,6 +917,31 @@ const POPULAR_TOOL_SLUGS = [
|
||||
'video-to-gif',
|
||||
] as const;
|
||||
|
||||
/** Workflow chains: tools users are likely to use in sequence */
|
||||
const TOOL_WORKFLOWS: Record<string, string[]> = {
|
||||
'compress-pdf': ['merge-pdf', 'split-pdf', 'pdf-to-word'],
|
||||
'merge-pdf': ['compress-pdf', 'split-pdf', 'rotate-pdf'],
|
||||
'split-pdf': ['merge-pdf', 'compress-pdf', 'pdf-to-word'],
|
||||
'pdf-to-word': ['word-to-pdf', 'compress-pdf', 'ocr'],
|
||||
'word-to-pdf': ['compress-pdf', 'merge-pdf', 'pdf-editor'],
|
||||
'pdf-to-excel': ['pdf-to-word', 'ocr', 'compress-pdf'],
|
||||
'pdf-to-pptx': ['pdf-to-word', 'compress-pdf', 'merge-pdf'],
|
||||
'rotate-pdf': ['merge-pdf', 'split-pdf', 'compress-pdf'],
|
||||
'pdf-editor': ['compress-pdf', 'merge-pdf', 'watermark-pdf'],
|
||||
'watermark-pdf': ['compress-pdf', 'pdf-editor', 'merge-pdf'],
|
||||
'protect-pdf': ['watermark-pdf', 'compress-pdf', 'merge-pdf'],
|
||||
'unlock-pdf': ['compress-pdf', 'pdf-to-word', 'merge-pdf'],
|
||||
'ocr': ['pdf-to-word', 'compress-pdf', 'image-converter'],
|
||||
'compress-image': ['image-resize', 'image-converter', 'image-to-pdf'],
|
||||
'image-resize': ['compress-image', 'image-converter', 'image-crop'],
|
||||
'image-converter': ['compress-image', 'image-resize', 'image-to-pdf'],
|
||||
'image-to-pdf': ['compress-pdf', 'merge-pdf', 'compress-image'],
|
||||
'image-crop': ['image-resize', 'compress-image', 'image-converter'],
|
||||
'html-to-pdf': ['compress-pdf', 'merge-pdf', 'pdf-editor'],
|
||||
'qr-code': ['barcode-generator', 'html-to-pdf'],
|
||||
'barcode-generator': ['qr-code', 'html-to-pdf'],
|
||||
};
|
||||
|
||||
function dedupeExistingToolSlugs(slugs: string[], excludeSlugs: string[] = []): string[] {
|
||||
const excluded = new Set(excludeSlugs);
|
||||
const seen = new Set<string>();
|
||||
@@ -952,12 +977,14 @@ export function getInternalLinkToolSlugs(currentSlug: string, limit = 8): string
|
||||
return [];
|
||||
}
|
||||
|
||||
const workflowSlugs = TOOL_WORKFLOWS[currentSlug] || [];
|
||||
|
||||
const sameCategorySlugs = TOOLS_SEO
|
||||
.filter((tool) => tool.category === currentTool.category && tool.slug !== currentSlug)
|
||||
.map((tool) => tool.slug);
|
||||
|
||||
const internalLinks = dedupeExistingToolSlugs(
|
||||
[...currentTool.relatedSlugs, ...sameCategorySlugs, ...POPULAR_TOOL_SLUGS],
|
||||
[...currentTool.relatedSlugs, ...workflowSlugs, ...sameCategorySlugs, ...POPULAR_TOOL_SLUGS],
|
||||
[currentSlug]
|
||||
);
|
||||
|
||||
|
||||
@@ -411,6 +411,134 @@
|
||||
"pricingCta": "قارن بين Free وPro",
|
||||
"toolsCta": "استكشف الأدوات"
|
||||
},
|
||||
"comparison": {
|
||||
"badge": "مقارنة",
|
||||
"featureComparison": "مقارنة المميزات",
|
||||
"feature": "الميزة",
|
||||
"verdictTitle": "الحكم النهائي",
|
||||
"faqTitle": "الأسئلة الشائعة",
|
||||
"relatedComparisons": "مقارنات أخرى",
|
||||
"relatedTools": "أدوات ذات صلة",
|
||||
"ctaSubtext": "مجاني، بدون تسجيل — جرّبه الآن.",
|
||||
"notFound": "المقارنة غير موجودة",
|
||||
"browseTools": "تصفح جميع الأدوات",
|
||||
"features": {
|
||||
"freeUnlimited": "مجاني وبدون حدود",
|
||||
"noSignup": "بدون تسجيل حساب",
|
||||
"batchProcessing": "معالجة دفعات",
|
||||
"compressionLevels": "مستويات ضغط متعددة",
|
||||
"autoDelete": "حذف تلقائي للملفات",
|
||||
"noAds": "بدون إعلانات أو نوافذ منبثقة",
|
||||
"apiAccess": "وصول API",
|
||||
"offlineMode": "وضع سطح المكتب بدون اتصال",
|
||||
"dragReorder": "إعادة ترتيب بالسحب والإفلات",
|
||||
"preserveFormatting": "الحفاظ على التنسيق",
|
||||
"noInstall": "بدون حاجة لتثبيت برامج",
|
||||
"multiFormat": "دعم صيغ متعددة",
|
||||
"qualityControl": "شريط تحكم بالجودة",
|
||||
"multiLanguageOcr": "OCR متعدد اللغات"
|
||||
},
|
||||
"compressPdfVsIlovepdf": {
|
||||
"title": "Dociva مقابل iLovePDF — مقارنة ضغط PDF المجاني",
|
||||
"metaDescription": "قارن بين Dociva وiLovePDF لضغط ملفات PDF. شاهد المميزات والحدود والأسعار جنبًا إلى جنب.",
|
||||
"heading": "Dociva مقابل iLovePDF",
|
||||
"subtitle": "كلتا الأداتين تضغطان ملفات PDF، لكن Dociva تقدم استخدامًا مجانيًا غير محدود بدون تسجيل وبدون إعلانات.",
|
||||
"advantagesTitle": "لماذا تختار Dociva بدلاً من iLovePDF",
|
||||
"advantages": [
|
||||
"بلا حدود حقيقية — لا قيود يومية على الملفات",
|
||||
"بدون حاجة لإنشاء حساب — ابدأ الضغط فورًا",
|
||||
"صفر إعلانات، صفر نوافذ منبثقة، واجهة نظيفة",
|
||||
"الملفات تُحذف تلقائيًا بعد 30 دقيقة",
|
||||
"وصول API متاح في خطة Pro"
|
||||
],
|
||||
"verdict": "للمستخدمين الذين يحتاجون ضاغط PDF مجاني وموثوق بدون حدود يومية أو جدران تسجيل، Dociva هي الخيار الأوضح.",
|
||||
"faqs": [
|
||||
{ "q": "هل Dociva مجاني فعلًا لضغط PDF؟", "a": "نعم. ضاغط PDF في Dociva مجاني بدون حدود يومية وبدون حاجة لحساب." },
|
||||
{ "q": "هل يحدّ iLovePDF المستخدمين المجانيين؟", "a": "نعم. الطبقة المجانية في iLovePDF بها حدود يومية للمهام وتعرض إعلانات." },
|
||||
{ "q": "أي أداة تضغط أكثر؟", "a": "كلتا الأداتين تقدمان نسب ضغط متقاربة. Dociva توفر ثلاث مستويات ضغط لتوازن الجودة والحجم." }
|
||||
]
|
||||
},
|
||||
"mergePdfVsSmallpdf": {
|
||||
"title": "Dociva مقابل Smallpdf — مقارنة دمج PDF المجاني",
|
||||
"metaDescription": "قارن بين Dociva وSmallpdf لدمج ملفات PDF. شاهد المميزات والحدود والأسعار جنبًا إلى جنب.",
|
||||
"heading": "Dociva مقابل Smallpdf",
|
||||
"subtitle": "كلتا المنصتين تدمجان ملفات PDF أونلاين، لكن Dociva تقدم دمجًا غير محدود بدون تسجيل.",
|
||||
"advantagesTitle": "لماذا تختار Dociva بدلاً من Smallpdf",
|
||||
"advantages": [
|
||||
"دمج PDF غير محدود — بدون حدود يومية",
|
||||
"بدون حاجة لحساب لدمج الملفات",
|
||||
"واجهة نظيفة بدون إعلانات",
|
||||
"إعادة ترتيب الصفحات بالسحب والإفلات",
|
||||
"الملفات تُحذف تلقائيًا بعد المعالجة"
|
||||
],
|
||||
"verdict": "Smallpdf أنيقة لكن تقفل معظم المميزات خلف حاجز دفع بعد مهمتين مجانيتين يوميًا. Dociva تقدم نفس وظيفة الدمج الأساسية بلا حدود وبلا تسجيل.",
|
||||
"faqs": [
|
||||
{ "q": "هل يمكنني دمج PDF مجانًا مع Dociva؟", "a": "نعم. أداة دمج Dociva مجانية تمامًا بدون حدود يومية." },
|
||||
{ "q": "هل يحدّ Smallpdf عمليات الدمج المجانية؟", "a": "نعم. Smallpdf تسمح فقط بمهمتين مجانيتين يوميًا." },
|
||||
{ "q": "هل يمكنني إعادة ترتيب الصفحات قبل الدمج؟", "a": "نعم. كلتا الأداتين تدعمان إعادة الترتيب بالسحب والإفلات قبل الدمج النهائي." }
|
||||
]
|
||||
},
|
||||
"pdfToWordVsAdobeAcrobat": {
|
||||
"title": "Dociva مقابل Adobe Acrobat — مقارنة تحويل PDF إلى Word",
|
||||
"metaDescription": "قارن بين Dociva وAdobe Acrobat لتحويل PDF إلى Word. أداة أونلاين مجانية مقابل برنامج سطح مكتب مدفوع.",
|
||||
"heading": "Dociva مقابل Adobe Acrobat",
|
||||
"subtitle": "Adobe Acrobat هو المعيار الصناعي، لكن Dociva تقدم نفس تحويل PDF إلى Word مجانًا وأونلاين بدون تثبيت.",
|
||||
"advantagesTitle": "لماذا تختار Dociva بدلاً من Adobe Acrobat",
|
||||
"advantages": [
|
||||
"مجاني 100% — بدون رسوم اشتراك",
|
||||
"بدون تحميل أو تثبيت برامج",
|
||||
"يعمل على أي جهاز بمتصفح",
|
||||
"بدون حاجة لمعرف Adobe",
|
||||
"تحويل سريع مع الحفاظ على التنسيق"
|
||||
],
|
||||
"verdict": "Adobe Acrobat يقدم أقوى مجموعة أدوات PDF، لكن بسعر مرتفع. لتحويل PDF إلى Word المباشر، Dociva تطابق الجودة مجانًا وأونلاين.",
|
||||
"faqs": [
|
||||
{ "q": "هل جودة تحويل Dociva بمستوى Adobe؟", "a": "للمستندات العادية، تحافظ Dociva على التنسيق والخطوط والتخطيط بشكل مقارب لمحول Adobe Acrobat." },
|
||||
{ "q": "هل يتطلب Adobe Acrobat اشتراكًا؟", "a": "نعم. مميزات التحويل الكاملة تتطلب اشتراك Acrobat Pro." },
|
||||
{ "q": "هل يمكنني تحويل ملفات PDF كبيرة؟", "a": "Dociva تدعم ملفات حتى 20 ميجابايت. Adobe Acrobat يدعم ملفات أكبر عبر تطبيق سطح المكتب." }
|
||||
]
|
||||
},
|
||||
"compressImageVsTinypng": {
|
||||
"title": "Dociva مقابل TinyPNG — مقارنة ضغط الصور المجاني",
|
||||
"metaDescription": "قارن بين Dociva وTinyPNG لضغط الصور. شاهد دعم الصيغ والحدود والجودة.",
|
||||
"heading": "Dociva مقابل TinyPNG",
|
||||
"subtitle": "TinyPNG ضاغط صور شهير، لكن Dociva تدعم صيغ أكثر وبدون حدود على عدد الملفات.",
|
||||
"advantagesTitle": "لماذا تختار Dociva بدلاً من TinyPNG",
|
||||
"advantages": [
|
||||
"ضغط غير محدود — بدون حدود يومية",
|
||||
"تدعم JPEG وPNG وWebP وGIF والمزيد",
|
||||
"شريط تحكم بالجودة لنتائج دقيقة",
|
||||
"رفع ومعالجة صور متعددة دفعة واحدة",
|
||||
"بدون قيود على عدد الملفات في الطبقة المجانية"
|
||||
],
|
||||
"verdict": "TinyPNG تتفوق في تحسين PNG/JPEG بواجهة بسيطة، لكنها تحدّ المستخدمين المجانيين بـ 20 صورة لكل دفعة. Dociva تزيل هذه الحدود وتضيف مرونة في الصيغ.",
|
||||
"faqs": [
|
||||
{ "q": "هل لدى TinyPNG حدود؟", "a": "نعم. أداة TinyPNG المجانية تحدّك بـ 20 صورة لكل دفعة و5 ميجابايت لكل ملف." },
|
||||
{ "q": "ما الصيغ التي تدعمها Dociva؟", "a": "Dociva تضغط JPEG وPNG وWebP وGIF وSVG والمزيد." },
|
||||
{ "q": "هل يمكنني التحكم بجودة الضغط؟", "a": "نعم. Dociva توفر شريط تحكم بالجودة لتوازن حجم الملف والوضوح البصري." }
|
||||
]
|
||||
},
|
||||
"ocrVsAdobeScan": {
|
||||
"title": "Dociva OCR مقابل Adobe Scan — مقارنة التعرف على النص",
|
||||
"metaDescription": "قارن بين Dociva OCR وAdobe Scan للتعرف على النص. أداة متصفح مقابل تطبيق جوال.",
|
||||
"heading": "Dociva OCR مقابل Adobe Scan",
|
||||
"subtitle": "Adobe Scan تطبيق مسح ضوئي للجوال، بينما Dociva OCR تعمل مباشرة في متصفحك بدون تثبيت.",
|
||||
"advantagesTitle": "لماذا تختار Dociva OCR بدلاً من Adobe Scan",
|
||||
"advantages": [
|
||||
"تعمل في أي متصفح — بدون تثبيت تطبيق",
|
||||
"معالجة OCR غير محدودة",
|
||||
"بدون حاجة لحساب Adobe",
|
||||
"تدعم معالجة دفعات لملفات متعددة",
|
||||
"تعرف نصي متعدد اللغات (إنجليزي، عربي، فرنسي)"
|
||||
],
|
||||
"verdict": "Adobe Scan تطبيق مسح ضوئي جوال ممتاز مع دعم العمل بدون اتصال. لكن لـ OCR عبر الويب مع معالجة دفعات وبدون تسجيل، Dociva أسرع وأكثر مرونة.",
|
||||
"faqs": [
|
||||
{ "q": "هل يعمل Dociva OCR بدون اتصال؟", "a": "لا. Dociva OCR يتطلب اتصال إنترنت لمعالجة الملفات على خوادم آمنة." },
|
||||
{ "q": "هل Adobe Scan مجاني؟", "a": "المميزات الأساسية مجانية، لكن OCR المتقدم والتصدير يتطلبان اشتراك Acrobat." },
|
||||
{ "q": "ما اللغات التي يدعمها Dociva OCR؟", "a": "Dociva OCR يدعم التعرف على النص بالإنجليزية والعربية والفرنسية." }
|
||||
]
|
||||
}
|
||||
},
|
||||
"developers": {
|
||||
"metaDescription": "استكشف بوابة مطوري Dociva، وتدفق API غير المتزامن، والنقاط الجاهزة لأتمتة المستندات.",
|
||||
"badge": "بوابة المطورين",
|
||||
|
||||
@@ -411,6 +411,134 @@
|
||||
"pricingCta": "Compare Free and Pro",
|
||||
"toolsCta": "Explore tools"
|
||||
},
|
||||
"comparison": {
|
||||
"badge": "Comparison",
|
||||
"featureComparison": "Feature Comparison",
|
||||
"feature": "Feature",
|
||||
"verdictTitle": "The Verdict",
|
||||
"faqTitle": "Frequently Asked Questions",
|
||||
"relatedComparisons": "More Comparisons",
|
||||
"relatedTools": "Related Tools",
|
||||
"ctaSubtext": "Free, no signup required — try it now.",
|
||||
"notFound": "Comparison not found",
|
||||
"browseTools": "Browse all tools",
|
||||
"features": {
|
||||
"freeUnlimited": "Free & unlimited use",
|
||||
"noSignup": "No signup required",
|
||||
"batchProcessing": "Batch processing",
|
||||
"compressionLevels": "Multiple compression levels",
|
||||
"autoDelete": "Auto-delete files",
|
||||
"noAds": "No ads or popups",
|
||||
"apiAccess": "API access",
|
||||
"offlineMode": "Offline desktop mode",
|
||||
"dragReorder": "Drag-and-drop reorder",
|
||||
"preserveFormatting": "Preserve formatting",
|
||||
"noInstall": "No software install needed",
|
||||
"multiFormat": "Multi-format support",
|
||||
"qualityControl": "Quality control slider",
|
||||
"multiLanguageOcr": "Multi-language OCR"
|
||||
},
|
||||
"compressPdfVsIlovepdf": {
|
||||
"title": "Dociva vs iLovePDF — Free PDF Compression Compared",
|
||||
"metaDescription": "Compare Dociva and iLovePDF for PDF compression. See features, limits, and pricing side by side.",
|
||||
"heading": "Dociva vs iLovePDF",
|
||||
"subtitle": "Both tools compress PDFs, but Dociva offers unlimited free use with no signup and no ads. See how they compare feature by feature.",
|
||||
"advantagesTitle": "Why choose Dociva over iLovePDF",
|
||||
"advantages": [
|
||||
"Truly unlimited — no daily file caps or task limits",
|
||||
"No account required — start compressing immediately",
|
||||
"Zero ads, zero popups, clean interface",
|
||||
"Files automatically deleted after 30 minutes",
|
||||
"API access included on Pro plan"
|
||||
],
|
||||
"verdict": "For users who need a reliable, free PDF compressor without daily limits or sign-up walls, Dociva is the clear choice. iLovePDF offers a broader desktop suite, but its free tier is restrictive.",
|
||||
"faqs": [
|
||||
{ "q": "Is Dociva really free for PDF compression?", "a": "Yes. Dociva's PDF compressor is free with no daily limits, no account required, and no hidden paywalls." },
|
||||
{ "q": "Does iLovePDF limit free users?", "a": "Yes. iLovePDF's free tier has daily task limits and shows ads. Premium features require a paid subscription." },
|
||||
{ "q": "Which tool compresses PDFs more?", "a": "Both tools offer comparable compression ratios. Dociva provides three compression levels to balance quality and file size." }
|
||||
]
|
||||
},
|
||||
"mergePdfVsSmallpdf": {
|
||||
"title": "Dociva vs Smallpdf — Free PDF Merge Compared",
|
||||
"metaDescription": "Compare Dociva and Smallpdf for merging PDFs. See features, limits, and pricing side by side.",
|
||||
"heading": "Dociva vs Smallpdf",
|
||||
"subtitle": "Both platforms merge PDFs online, but Dociva offers unlimited merges with no signup. See the differences.",
|
||||
"advantagesTitle": "Why choose Dociva over Smallpdf",
|
||||
"advantages": [
|
||||
"Unlimited PDF merges — no daily caps",
|
||||
"No account required to merge files",
|
||||
"Clean, ad-free interface",
|
||||
"Drag-and-drop page reordering",
|
||||
"Files auto-deleted after processing"
|
||||
],
|
||||
"verdict": "Smallpdf is polished but locks most features behind a paywall after two free tasks per day. Dociva provides the same core merge functionality with no limits and no signup.",
|
||||
"faqs": [
|
||||
{ "q": "Can I merge PDFs for free with Dociva?", "a": "Yes. Dociva's merge tool is completely free with no daily limits." },
|
||||
{ "q": "Does Smallpdf limit free merges?", "a": "Yes. Smallpdf allows only two free tasks per day on its free tier." },
|
||||
{ "q": "Can I reorder pages before merging?", "a": "Yes. Both tools support drag-and-drop reordering before the final merge." }
|
||||
]
|
||||
},
|
||||
"pdfToWordVsAdobeAcrobat": {
|
||||
"title": "Dociva vs Adobe Acrobat — Free PDF to Word Compared",
|
||||
"metaDescription": "Compare Dociva and Adobe Acrobat for PDF to Word conversion. Free online tool vs premium desktop software.",
|
||||
"heading": "Dociva vs Adobe Acrobat",
|
||||
"subtitle": "Adobe Acrobat is the industry standard, but Dociva delivers the same PDF-to-Word conversion for free, online, with no install.",
|
||||
"advantagesTitle": "Why choose Dociva over Adobe Acrobat",
|
||||
"advantages": [
|
||||
"100% free — no subscription fees",
|
||||
"No software download or installation",
|
||||
"Works on any device with a browser",
|
||||
"No Adobe ID required",
|
||||
"Fast conversion with formatting preserved"
|
||||
],
|
||||
"verdict": "Adobe Acrobat offers the most powerful PDF toolkit, but at a high price. For straightforward PDF-to-Word conversion, Dociva matches the quality for free, online, with zero friction.",
|
||||
"faqs": [
|
||||
{ "q": "Is Dociva's conversion quality as good as Adobe?", "a": "For standard documents, Dociva preserves formatting, fonts, and layout comparable to Adobe Acrobat's online converter." },
|
||||
{ "q": "Does Adobe Acrobat require a subscription?", "a": "Yes. Adobe Acrobat's full conversion features require an Acrobat Pro subscription starting at $19.99/month." },
|
||||
{ "q": "Can I convert large PDFs?", "a": "Dociva supports files up to 20MB. Adobe Acrobat supports larger files with its desktop app." }
|
||||
]
|
||||
},
|
||||
"compressImageVsTinypng": {
|
||||
"title": "Dociva vs TinyPNG — Free Image Compression Compared",
|
||||
"metaDescription": "Compare Dociva and TinyPNG for image compression. See format support, limits, and quality differences.",
|
||||
"heading": "Dociva vs TinyPNG",
|
||||
"subtitle": "TinyPNG is a popular image compressor, but Dociva supports more formats and has no file count limits. Compare them here.",
|
||||
"advantagesTitle": "Why choose Dociva over TinyPNG",
|
||||
"advantages": [
|
||||
"Unlimited compressions — no daily file caps",
|
||||
"Supports JPEG, PNG, WebP, GIF, and more",
|
||||
"Quality control slider for precise results",
|
||||
"Batch upload and process multiple images",
|
||||
"No file count restrictions on free tier"
|
||||
],
|
||||
"verdict": "TinyPNG excels at PNG/JPEG optimization with a simple interface, but limits free users to 20 images per batch. Dociva removes those limits and adds format flexibility.",
|
||||
"faqs": [
|
||||
{ "q": "Does TinyPNG have limits?", "a": "Yes. TinyPNG's free web tool limits you to 20 images per batch and 5MB per file." },
|
||||
{ "q": "Which formats does Dociva support?", "a": "Dociva compresses JPEG, PNG, WebP, GIF, SVG, and more image formats." },
|
||||
{ "q": "Can I control compression quality?", "a": "Yes. Dociva provides a quality slider so you can balance file size and visual quality." }
|
||||
]
|
||||
},
|
||||
"ocrVsAdobeScan": {
|
||||
"title": "Dociva OCR vs Adobe Scan — Free Text Recognition Compared",
|
||||
"metaDescription": "Compare Dociva OCR and Adobe Scan for text recognition. Browser-based vs mobile app — see features side by side.",
|
||||
"heading": "Dociva OCR vs Adobe Scan",
|
||||
"subtitle": "Adobe Scan is a mobile scanning app, while Dociva OCR works directly in your browser with no install. See how they compare.",
|
||||
"advantagesTitle": "Why choose Dociva OCR over Adobe Scan",
|
||||
"advantages": [
|
||||
"Works in any browser — no app install",
|
||||
"Unlimited OCR processing",
|
||||
"No Adobe account required",
|
||||
"Supports batch processing of multiple files",
|
||||
"Multi-language text recognition (English, Arabic, French)"
|
||||
],
|
||||
"verdict": "Adobe Scan is a great mobile scanning app with offline support. But for web-based OCR with batch processing and no login, Dociva is faster and more flexible.",
|
||||
"faqs": [
|
||||
{ "q": "Does Dociva OCR work offline?", "a": "No. Dociva OCR requires an internet connection as it processes files on secure servers." },
|
||||
{ "q": "Is Adobe Scan free?", "a": "Adobe Scan's basic features are free, but advanced OCR and export features require an Acrobat subscription." },
|
||||
{ "q": "Which languages does Dociva OCR support?", "a": "Dociva OCR supports English, Arabic, and French text recognition out of the box." }
|
||||
]
|
||||
}
|
||||
},
|
||||
"developers": {
|
||||
"metaDescription": "Explore the Dociva developer portal, async API flow, and production-ready endpoints for document automation.",
|
||||
"badge": "Developer Portal",
|
||||
|
||||
@@ -411,6 +411,134 @@
|
||||
"pricingCta": "Comparer Gratuit et Pro",
|
||||
"toolsCta": "Explorer les outils"
|
||||
},
|
||||
"comparison": {
|
||||
"badge": "Comparaison",
|
||||
"featureComparison": "Comparaison des fonctionnalités",
|
||||
"feature": "Fonctionnalité",
|
||||
"verdictTitle": "Le verdict",
|
||||
"faqTitle": "Questions fréquentes",
|
||||
"relatedComparisons": "Autres comparaisons",
|
||||
"relatedTools": "Outils associés",
|
||||
"ctaSubtext": "Gratuit, sans inscription — essayez maintenant.",
|
||||
"notFound": "Comparaison introuvable",
|
||||
"browseTools": "Parcourir tous les outils",
|
||||
"features": {
|
||||
"freeUnlimited": "Gratuit et illimité",
|
||||
"noSignup": "Aucune inscription requise",
|
||||
"batchProcessing": "Traitement par lots",
|
||||
"compressionLevels": "Plusieurs niveaux de compression",
|
||||
"autoDelete": "Suppression automatique des fichiers",
|
||||
"noAds": "Pas de publicités ni de popups",
|
||||
"apiAccess": "Accès API",
|
||||
"offlineMode": "Mode bureau hors ligne",
|
||||
"dragReorder": "Réorganisation par glisser-déposer",
|
||||
"preserveFormatting": "Préservation du formatage",
|
||||
"noInstall": "Aucune installation requise",
|
||||
"multiFormat": "Support multi-format",
|
||||
"qualityControl": "Curseur de contrôle qualité",
|
||||
"multiLanguageOcr": "OCR multilingue"
|
||||
},
|
||||
"compressPdfVsIlovepdf": {
|
||||
"title": "Dociva vs iLovePDF — Compression PDF gratuite comparée",
|
||||
"metaDescription": "Comparez Dociva et iLovePDF pour la compression PDF. Fonctionnalités, limites et prix côte à côte.",
|
||||
"heading": "Dociva vs iLovePDF",
|
||||
"subtitle": "Les deux outils compressent les PDF, mais Dociva offre une utilisation gratuite illimitée sans inscription ni publicité.",
|
||||
"advantagesTitle": "Pourquoi choisir Dociva plutôt qu'iLovePDF",
|
||||
"advantages": [
|
||||
"Vraiment illimité — aucun quota quotidien",
|
||||
"Aucun compte requis — commencez immédiatement",
|
||||
"Zéro publicité, zéro popup, interface épurée",
|
||||
"Fichiers supprimés automatiquement après 30 minutes",
|
||||
"Accès API inclus dans le plan Pro"
|
||||
],
|
||||
"verdict": "Pour les utilisateurs qui ont besoin d'un compresseur PDF fiable et gratuit sans limites quotidiennes ni mur d'inscription, Dociva est le choix évident.",
|
||||
"faqs": [
|
||||
{ "q": "Dociva est-il vraiment gratuit pour la compression PDF ?", "a": "Oui. Le compresseur PDF de Dociva est gratuit sans limites quotidiennes ni compte requis." },
|
||||
{ "q": "iLovePDF limite-t-il les utilisateurs gratuits ?", "a": "Oui. Le niveau gratuit d'iLovePDF a des limites quotidiennes et affiche des publicités." },
|
||||
{ "q": "Quel outil compresse le plus ?", "a": "Les deux offrent des taux de compression comparables. Dociva propose trois niveaux pour équilibrer qualité et taille." }
|
||||
]
|
||||
},
|
||||
"mergePdfVsSmallpdf": {
|
||||
"title": "Dociva vs Smallpdf — Fusion PDF gratuite comparée",
|
||||
"metaDescription": "Comparez Dociva et Smallpdf pour la fusion de PDF. Fonctionnalités, limites et prix côte à côte.",
|
||||
"heading": "Dociva vs Smallpdf",
|
||||
"subtitle": "Les deux plateformes fusionnent les PDF en ligne, mais Dociva offre des fusions illimitées sans inscription.",
|
||||
"advantagesTitle": "Pourquoi choisir Dociva plutôt que Smallpdf",
|
||||
"advantages": [
|
||||
"Fusions PDF illimitées — aucun quota quotidien",
|
||||
"Aucun compte requis pour fusionner",
|
||||
"Interface propre sans publicité",
|
||||
"Réorganisation par glisser-déposer",
|
||||
"Fichiers supprimés après traitement"
|
||||
],
|
||||
"verdict": "Smallpdf est élégant mais verrouille la plupart des fonctionnalités derrière un paywall après deux tâches gratuites par jour. Dociva offre la même fonctionnalité sans limites.",
|
||||
"faqs": [
|
||||
{ "q": "Puis-je fusionner des PDF gratuitement avec Dociva ?", "a": "Oui. L'outil de fusion de Dociva est entièrement gratuit sans limites quotidiennes." },
|
||||
{ "q": "Smallpdf limite-t-il les fusions gratuites ?", "a": "Oui. Smallpdf ne permet que deux tâches gratuites par jour." },
|
||||
{ "q": "Puis-je réorganiser les pages avant la fusion ?", "a": "Oui. Les deux outils supportent la réorganisation par glisser-déposer." }
|
||||
]
|
||||
},
|
||||
"pdfToWordVsAdobeAcrobat": {
|
||||
"title": "Dociva vs Adobe Acrobat — Conversion PDF vers Word comparée",
|
||||
"metaDescription": "Comparez Dociva et Adobe Acrobat pour la conversion PDF vers Word. Outil gratuit en ligne vs logiciel premium.",
|
||||
"heading": "Dociva vs Adobe Acrobat",
|
||||
"subtitle": "Adobe Acrobat est la référence du secteur, mais Dociva offre la même conversion PDF vers Word gratuitement, en ligne.",
|
||||
"advantagesTitle": "Pourquoi choisir Dociva plutôt qu'Adobe Acrobat",
|
||||
"advantages": [
|
||||
"100% gratuit — aucun abonnement",
|
||||
"Aucun téléchargement ni installation",
|
||||
"Fonctionne sur tout appareil avec un navigateur",
|
||||
"Aucun identifiant Adobe requis",
|
||||
"Conversion rapide avec formatage préservé"
|
||||
],
|
||||
"verdict": "Adobe Acrobat offre la suite PDF la plus puissante, mais à un prix élevé. Pour une conversion PDF vers Word directe, Dociva égale la qualité gratuitement.",
|
||||
"faqs": [
|
||||
{ "q": "La qualité de conversion de Dociva est-elle aussi bonne qu'Adobe ?", "a": "Pour les documents standard, Dociva préserve le formatage de manière comparable au convertisseur en ligne d'Adobe." },
|
||||
{ "q": "Adobe Acrobat nécessite-t-il un abonnement ?", "a": "Oui. Les fonctionnalités complètes nécessitent un abonnement Acrobat Pro." },
|
||||
{ "q": "Puis-je convertir de gros PDF ?", "a": "Dociva supporte les fichiers jusqu'à 20 Mo. Adobe Acrobat supporte des fichiers plus volumineux via son application." }
|
||||
]
|
||||
},
|
||||
"compressImageVsTinypng": {
|
||||
"title": "Dociva vs TinyPNG — Compression d'images gratuite comparée",
|
||||
"metaDescription": "Comparez Dociva et TinyPNG pour la compression d'images. Formats, limites et qualité.",
|
||||
"heading": "Dociva vs TinyPNG",
|
||||
"subtitle": "TinyPNG est un compresseur d'images populaire, mais Dociva supporte plus de formats sans limites de fichiers.",
|
||||
"advantagesTitle": "Pourquoi choisir Dociva plutôt que TinyPNG",
|
||||
"advantages": [
|
||||
"Compressions illimitées — aucun quota quotidien",
|
||||
"Supporte JPEG, PNG, WebP, GIF et plus",
|
||||
"Curseur de qualité pour des résultats précis",
|
||||
"Upload et traitement par lots",
|
||||
"Aucune restriction sur le nombre de fichiers"
|
||||
],
|
||||
"verdict": "TinyPNG excelle dans l'optimisation PNG/JPEG avec une interface simple, mais limite les utilisateurs gratuits à 20 images par lot. Dociva supprime ces limites.",
|
||||
"faqs": [
|
||||
{ "q": "TinyPNG a-t-il des limites ?", "a": "Oui. L'outil gratuit de TinyPNG limite à 20 images par lot et 5 Mo par fichier." },
|
||||
{ "q": "Quels formats Dociva supporte-t-il ?", "a": "Dociva compresse JPEG, PNG, WebP, GIF, SVG et d'autres formats." },
|
||||
{ "q": "Puis-je contrôler la qualité de compression ?", "a": "Oui. Dociva propose un curseur de qualité pour équilibrer taille et qualité visuelle." }
|
||||
]
|
||||
},
|
||||
"ocrVsAdobeScan": {
|
||||
"title": "Dociva OCR vs Adobe Scan — Reconnaissance de texte comparée",
|
||||
"metaDescription": "Comparez Dociva OCR et Adobe Scan pour la reconnaissance de texte. Navigateur vs application mobile.",
|
||||
"heading": "Dociva OCR vs Adobe Scan",
|
||||
"subtitle": "Adobe Scan est une application mobile de numérisation, tandis que Dociva OCR fonctionne directement dans votre navigateur.",
|
||||
"advantagesTitle": "Pourquoi choisir Dociva OCR plutôt qu'Adobe Scan",
|
||||
"advantages": [
|
||||
"Fonctionne dans tout navigateur — aucune installation",
|
||||
"Traitement OCR illimité",
|
||||
"Aucun compte Adobe requis",
|
||||
"Traitement par lots de plusieurs fichiers",
|
||||
"Reconnaissance multilingue (anglais, arabe, français)"
|
||||
],
|
||||
"verdict": "Adobe Scan est une excellente application de numérisation mobile avec support hors ligne. Mais pour l'OCR web avec traitement par lots et sans connexion, Dociva est plus rapide et flexible.",
|
||||
"faqs": [
|
||||
{ "q": "Dociva OCR fonctionne-t-il hors ligne ?", "a": "Non. Dociva OCR nécessite une connexion Internet pour traiter les fichiers sur des serveurs sécurisés." },
|
||||
{ "q": "Adobe Scan est-il gratuit ?", "a": "Les fonctionnalités de base sont gratuites, mais l'OCR avancé nécessite un abonnement Acrobat." },
|
||||
{ "q": "Quelles langues Dociva OCR supporte-t-il ?", "a": "Dociva OCR supporte la reconnaissance en anglais, arabe et français." }
|
||||
]
|
||||
}
|
||||
},
|
||||
"developers": {
|
||||
"metaDescription": "Explorez le portail développeur Dociva, le flux API asynchrone et les endpoints prêts pour l'automatisation documentaire.",
|
||||
"badge": "Portail développeur",
|
||||
|
||||
278
frontend/src/pages/ComparisonPage.tsx
Normal file
278
frontend/src/pages/ComparisonPage.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { CheckCircle, XCircle, MinusCircle, ArrowRight, Swords, Trophy, ExternalLink } from 'lucide-react';
|
||||
import { getComparisonPage, getComparisonPagesByTool, type ComparisonFeature } from '@/config/comparisonData';
|
||||
import { getToolSEO } from '@/config/seoData';
|
||||
import { getSiteOrigin, buildSocialImageUrl, getOgLocale, generateWebPage, generateFAQ } from '@/utils/seo';
|
||||
|
||||
function FeatureIcon({ value }: { value: boolean | 'partial' }) {
|
||||
if (value === true) return <CheckCircle className="h-5 w-5 text-green-500" />;
|
||||
if (value === 'partial') return <MinusCircle className="h-5 w-5 text-amber-500" />;
|
||||
return <XCircle className="h-5 w-5 text-red-400" />;
|
||||
}
|
||||
|
||||
function FeatureLabel({ value }: { value: boolean | 'partial' }) {
|
||||
if (value === true) return <span className="sr-only">Yes</span>;
|
||||
if (value === 'partial') return <span className="sr-only">Partial</span>;
|
||||
return <span className="sr-only">No</span>;
|
||||
}
|
||||
|
||||
export default function ComparisonPage() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const comparison = slug ? getComparisonPage(slug) : undefined;
|
||||
|
||||
if (!comparison) {
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl py-16 text-center">
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.notFound')}
|
||||
</h1>
|
||||
<Link to="/tools" className="mt-4 inline-block text-primary-600 hover:underline">
|
||||
{t('pages.comparison.browseTools')}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ourTool = getToolSEO(comparison.ourToolSlug);
|
||||
const origin = getSiteOrigin(typeof window !== 'undefined' ? window.location.origin : '');
|
||||
const canonicalUrl = `${origin}/compare/${comparison.slug}`;
|
||||
const socialImageUrl = buildSocialImageUrl(origin);
|
||||
const currentOgLocale = getOgLocale(i18n.language);
|
||||
const prefix = `pages.comparison.${comparison.i18nKey}`;
|
||||
|
||||
const title = t(`${prefix}.title`);
|
||||
const metaDescription = t(`${prefix}.metaDescription`);
|
||||
|
||||
const faqItems = t(`${prefix}.faqs`, { returnObjects: true }) as Array<{ q: string; a: string }>;
|
||||
const faqs = Array.isArray(faqItems) ? faqItems : [];
|
||||
|
||||
const webPageSchema = generateWebPage({
|
||||
name: title,
|
||||
description: metaDescription,
|
||||
url: canonicalUrl,
|
||||
});
|
||||
|
||||
const faqSchema = faqs.length > 0
|
||||
? generateFAQ(faqs.map((f) => ({ question: f.q, answer: f.a })))
|
||||
: null;
|
||||
|
||||
// Related comparisons
|
||||
const relatedComparisons = comparison.relatedComparisonSlugs
|
||||
.map((s) => getComparisonPage(s))
|
||||
.filter(Boolean);
|
||||
|
||||
// Related tools
|
||||
const relatedTools = comparison.relatedToolSlugs
|
||||
.map((s) => getToolSEO(s))
|
||||
.filter(Boolean);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{title} | {t('common.appName')}</title>
|
||||
<meta name="description" content={metaDescription} />
|
||||
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1" />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={metaDescription} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content={socialImageUrl} />
|
||||
<meta property="og:locale" content={currentOgLocale} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={metaDescription} />
|
||||
<script type="application/ld+json">{JSON.stringify(webPageSchema)}</script>
|
||||
{faqSchema && (
|
||||
<script type="application/ld+json">{JSON.stringify(faqSchema)}</script>
|
||||
)}
|
||||
</Helmet>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4">
|
||||
{/* Hero */}
|
||||
<section className="mb-12 text-center">
|
||||
<span className="mb-4 inline-flex items-center gap-2 rounded-full bg-primary-50 px-4 py-1.5 text-sm font-semibold text-primary-700 dark:bg-primary-900/30 dark:text-primary-300">
|
||||
<Swords className="h-4 w-4" />
|
||||
{t('pages.comparison.badge')}
|
||||
</span>
|
||||
<h1 className="mt-4 text-3xl font-extrabold text-slate-900 dark:text-white sm:text-4xl">
|
||||
{t(`${prefix}.heading`)}
|
||||
</h1>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-lg text-slate-600 dark:text-slate-400">
|
||||
{t(`${prefix}.subtitle`)}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Feature Comparison Table */}
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.featureComparison')}
|
||||
</h2>
|
||||
<div className="overflow-hidden rounded-2xl border border-slate-200 dark:border-slate-700">
|
||||
{/* Table header */}
|
||||
<div className="grid grid-cols-3 bg-slate-100 px-4 py-3 dark:bg-slate-800">
|
||||
<div className="text-sm font-semibold text-slate-600 dark:text-slate-300">
|
||||
{t('pages.comparison.feature')}
|
||||
</div>
|
||||
<div className="text-center text-sm font-semibold text-primary-700 dark:text-primary-400">
|
||||
{t('common.appName')}
|
||||
</div>
|
||||
<div className="text-center text-sm font-semibold text-slate-600 dark:text-slate-300">
|
||||
{comparison.competitorName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature rows */}
|
||||
{comparison.features.map((feature: ComparisonFeature, idx: number) => (
|
||||
<div
|
||||
key={feature.key}
|
||||
className={`grid grid-cols-3 items-center px-4 py-3 ${
|
||||
idx % 2 === 0
|
||||
? 'bg-white dark:bg-slate-900'
|
||||
: 'bg-slate-50 dark:bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm text-slate-700 dark:text-slate-300">
|
||||
{t(`pages.comparison.features.${feature.key}`)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<FeatureIcon value={feature.us} />
|
||||
<FeatureLabel value={feature.us} />
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<FeatureIcon value={feature.competitor} />
|
||||
<FeatureLabel value={feature.competitor} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Our Advantages */}
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">
|
||||
<Trophy className="mr-2 inline-block h-5 w-5 text-amber-500" />
|
||||
{t(`${prefix}.advantagesTitle`)}
|
||||
</h2>
|
||||
<div className="rounded-2xl border border-green-200 bg-green-50 p-6 dark:border-green-800/40 dark:bg-green-900/10">
|
||||
{(() => {
|
||||
const advantages = t(`${prefix}.advantages`, { returnObjects: true }) as string[];
|
||||
return Array.isArray(advantages) ? (
|
||||
<ul className="space-y-3">
|
||||
{advantages.map((adv, idx) => (
|
||||
<li key={idx} className="flex items-start gap-3">
|
||||
<CheckCircle className="mt-0.5 h-5 w-5 shrink-0 text-green-600 dark:text-green-400" />
|
||||
<span className="text-slate-700 dark:text-slate-300">{adv}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Verdict */}
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.verdictTitle')}
|
||||
</h2>
|
||||
<div className="rounded-2xl border border-primary-200 bg-primary-50 p-6 dark:border-primary-800/40 dark:bg-primary-900/10">
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
{t(`${prefix}.verdict`)}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="mb-12 text-center">
|
||||
<Link
|
||||
to={`/tools/${comparison.ourToolSlug}`}
|
||||
className="inline-flex items-center gap-2 rounded-xl bg-primary-600 px-8 py-3 text-lg font-semibold text-white shadow-md transition-all hover:bg-primary-700 hover:shadow-lg dark:bg-primary-500 dark:hover:bg-primary-600"
|
||||
>
|
||||
{ourTool ? t(`tools.${ourTool.i18nKey}.title`) : comparison.ourToolSlug}
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</Link>
|
||||
<p className="mt-3 text-sm text-slate-500 dark:text-slate-400">
|
||||
{t('pages.comparison.ctaSubtext')}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* FAQ */}
|
||||
{faqs.length > 0 && (
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-6 text-xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.faqTitle')}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, idx) => (
|
||||
<details
|
||||
key={idx}
|
||||
className="group rounded-xl border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-800"
|
||||
>
|
||||
<summary className="cursor-pointer text-sm font-semibold text-slate-800 dark:text-slate-200">
|
||||
{faq.q}
|
||||
</summary>
|
||||
<p className="mt-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
{faq.a}
|
||||
</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Related Comparisons */}
|
||||
{relatedComparisons.length > 0 && (
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.relatedComparisons')}
|
||||
</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{relatedComparisons.map((comp) => (
|
||||
<Link
|
||||
key={comp!.slug}
|
||||
to={`/compare/${comp!.slug}`}
|
||||
className="group flex items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 transition-all hover:border-primary-300 hover:shadow-md dark:border-slate-700 dark:bg-slate-800 dark:hover:border-primary-600"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 shrink-0 text-slate-400 group-hover:text-primary-500" />
|
||||
<span className="text-sm font-medium text-slate-700 group-hover:text-primary-600 dark:text-slate-300 dark:group-hover:text-primary-400">
|
||||
{t(`pages.comparison.${comp!.i18nKey}.heading`)}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Related Tools */}
|
||||
{relatedTools.length > 0 && (
|
||||
<section className="mb-12">
|
||||
<h2 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">
|
||||
{t('pages.comparison.relatedTools')}
|
||||
</h2>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{relatedTools.map((tool) => (
|
||||
<Link
|
||||
key={tool!.slug}
|
||||
to={`/tools/${tool!.slug}`}
|
||||
className="group rounded-xl border border-slate-200 bg-white p-4 transition-all hover:border-primary-300 hover:shadow-md dark:border-slate-700 dark:bg-slate-800 dark:hover:border-primary-600"
|
||||
>
|
||||
<h3 className="font-semibold text-slate-800 group-hover:text-primary-600 dark:text-slate-200 dark:group-hover:text-primary-400">
|
||||
{t(`tools.${tool!.i18nKey}.title`)}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400 line-clamp-2">
|
||||
{t(`tools.${tool!.i18nKey}.shortDesc`)}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user