Add SEO data generation and testing for bilingual pages

- Implemented SEO data structures for programmatic tool and collection pages.
- Created functions to build FAQs and content sections for SEO pages.
- Added tests to ensure at least 50 bilingual SEO pages are generated, no duplicate English slugs, and matching Arabic localized paths.
- Verified that both tool and collection SEO inventories are populated adequately.
This commit is contained in:
Your Name
2026-03-21 10:55:43 +02:00
parent a8a7ec55a2
commit c800f707e3
12 changed files with 1920 additions and 22 deletions

187
frontend/src/seo/seoData.ts Normal file
View File

@@ -0,0 +1,187 @@
import seoSeedConfig from '@/seo/seoData.json';
import type {
LocalizedText,
LocalizedTextList,
ProgrammaticToolPage,
SeoCollectionPage,
SeoFaqTemplate,
SeoLocale,
} from '@/config/seoPages';
interface ToolPageSeed {
slug: string;
toolSlug: string;
category: 'PDF' | 'Image' | 'AI' | 'Convert' | 'Utility';
focusKeyword: LocalizedText;
supportingKeywords: LocalizedTextList;
benefit: LocalizedText;
useCase: LocalizedText;
relatedCollectionSlugs: string[];
}
interface CollectionPageSeed {
slug: string;
focusKeyword: LocalizedText;
supportingKeywords: LocalizedTextList;
introAngle: LocalizedText;
targetToolSlugs: string[];
relatedCollectionSlugs: string[];
}
interface SeoSeedConfig {
toolPageSeeds: ToolPageSeed[];
collectionPageSeeds: CollectionPageSeed[];
}
const seedConfig = seoSeedConfig as SeoSeedConfig;
function buildToolFaqs(seed: ToolPageSeed): SeoFaqTemplate[] {
return [
{
question: {
en: `When should I use ${seed.focusKeyword.en}?`,
ar: `متى أستخدم ${seed.focusKeyword.ar}؟`,
},
answer: {
en: `${seed.useCase.en} ${seed.benefit.en}`,
ar: `${seed.useCase.ar} ${seed.benefit.ar}`,
},
},
{
question: {
en: `What makes ${seed.focusKeyword.en} useful online?`,
ar: `ما الذي يجعل ${seed.focusKeyword.ar} مفيداً أونلاين؟`,
},
answer: {
en: `Dociva helps you handle this workflow in the browser with secure processing, fast output, and no installation. ${seed.benefit.en}`,
ar: `يساعدك Dociva على تنفيذ هذا المسار من المتصفح مع معالجة آمنة ونتيجة سريعة وبدون تثبيت. ${seed.benefit.ar}`,
},
},
];
}
function buildCollectionFaqs(seed: CollectionPageSeed): SeoFaqTemplate[] {
return [
{
question: {
en: `What is included in ${seed.focusKeyword.en}?`,
ar: `ماذا تتضمن ${seed.focusKeyword.ar}؟`,
},
answer: {
en: `${seed.introAngle.en} This page brings together the main workflows users usually need before downloading, sharing, or archiving files.`,
ar: `${seed.introAngle.ar} تجمع هذه الصفحة أهم المسارات التي يحتاجها المستخدمون عادة قبل التنزيل أو المشاركة أو الأرشفة.`,
},
},
{
question: {
en: `How do I choose the right workflow from ${seed.focusKeyword.en}?`,
ar: `كيف أختار المسار المناسب من ${seed.focusKeyword.ar}؟`,
},
answer: {
en: `Start with the outcome you need, then open the matching tool. This collection is designed to reduce guesswork and move directly into execution.`,
ar: `ابدأ بالنتيجة التي تحتاجها ثم افتح الأداة المطابقة. صُممت هذه المجموعة لتقليل التخمين والانتقال مباشرة إلى التنفيذ.`,
},
},
];
}
function buildToolSections(seed: ToolPageSeed): Array<{ heading: LocalizedText; body: LocalizedText }> {
return [
{
heading: {
en: `Why people search for ${seed.focusKeyword.en}`,
ar: `لماذا يبحث المستخدمون عن ${seed.focusKeyword.ar}`,
},
body: {
en: seed.benefit.en,
ar: seed.benefit.ar,
},
},
{
heading: {
en: 'Common use cases',
ar: 'حالات الاستخدام الشائعة',
},
body: {
en: seed.useCase.en,
ar: seed.useCase.ar,
},
},
];
}
function buildCollectionSections(seed: CollectionPageSeed): Array<{ heading: LocalizedText; body: LocalizedText }> {
return [
{
heading: {
en: `What this ${seed.focusKeyword.en} page covers`,
ar: `ما الذي تغطيه صفحة ${seed.focusKeyword.ar}`,
},
body: {
en: seed.introAngle.en,
ar: seed.introAngle.ar,
},
},
{
heading: {
en: 'How to use this collection',
ar: 'كيفية استخدام هذه المجموعة',
},
body: {
en: 'Choose the workflow that matches the output you need first, then chain cleanup, security, or AI tools only when the file requires them.',
ar: 'اختر أولاً مسار العمل الذي يطابق النتيجة التي تحتاجها، ثم أضف أدوات التنظيف أو الأمان أو الذكاء الاصطناعي فقط عندما يتطلب الملف ذلك.',
},
},
];
}
export const PROGRAMMATIC_TOOL_PAGES: ProgrammaticToolPage[] = seedConfig.toolPageSeeds.map((seed) => ({
slug: seed.slug,
toolSlug: seed.toolSlug,
category: seed.category,
focusKeyword: seed.focusKeyword,
supportingKeywords: seed.supportingKeywords,
titleTemplate: {
en: `{{focusKeyword}} online | {{brand}}`,
ar: `{{focusKeyword}} أونلاين | {{brand}}`,
},
descriptionTemplate: {
en: `${seed.benefit.en} ${seed.useCase.en} Use {{brand}} to complete this workflow online with no signup required.`,
ar: `${seed.benefit.ar} ${seed.useCase.ar} استخدم {{brand}} لإتمام هذا المسار أونلاين بدون تسجيل.`,
},
faqTemplates: buildToolFaqs(seed),
relatedCollectionSlugs: seed.relatedCollectionSlugs,
contentSections: buildToolSections(seed),
}));
export const SEO_COLLECTION_PAGES: SeoCollectionPage[] = seedConfig.collectionPageSeeds.map((seed) => ({
slug: seed.slug,
focusKeyword: seed.focusKeyword,
supportingKeywords: seed.supportingKeywords,
titleTemplate: {
en: `{{focusKeyword}} | {{brand}}`,
ar: `{{focusKeyword}} | {{brand}}`,
},
descriptionTemplate: {
en: `${seed.introAngle.en} Browse the most relevant workflows in one focused landing page.`,
ar: `${seed.introAngle.ar} تصفح أكثر المسارات صلة في صفحة هبوط واحدة مركزة.`,
},
introTemplate: {
en: seed.introAngle.en,
ar: seed.introAngle.ar,
},
targetToolSlugs: seed.targetToolSlugs,
faqTemplates: buildCollectionFaqs(seed),
relatedCollectionSlugs: seed.relatedCollectionSlugs,
contentSections: buildCollectionSections(seed),
}));
export const SEO_TOTAL_PAGE_COUNT = PROGRAMMATIC_TOOL_PAGES.length + SEO_COLLECTION_PAGES.length;
export function getLocalizedSeoLandingPaths(locale: SeoLocale): string[] {
const prefix = locale === 'ar' ? '/ar' : '';
return [
...PROGRAMMATIC_TOOL_PAGES.map((page) => `${prefix}/${page.slug}`),
...SEO_COLLECTION_PAGES.map((page) => `${prefix}/${page.slug}`),
];
}