Files
SaaS-PDF/frontend/src/config/seoPages.ts
Your Name c800f707e3 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.
2026-03-21 10:55:43 +02:00

93 lines
2.6 KiB
TypeScript

import {
PROGRAMMATIC_TOOL_PAGES,
SEO_COLLECTION_PAGES,
getLocalizedSeoLandingPaths,
} from '@/seo/seoData';
export type SeoLocale = 'en' | 'ar';
export interface LocalizedText {
en: string;
ar: string;
}
export interface LocalizedTextList {
en: string[];
ar: string[];
}
export interface SeoFaqTemplate {
question: LocalizedText;
answer: LocalizedText;
}
export interface SeoContentSection {
heading: LocalizedText;
body: LocalizedText;
}
export interface ProgrammaticToolPage {
slug: string;
toolSlug: string;
category: 'PDF' | 'Image' | 'AI' | 'Convert' | 'Utility';
focusKeyword: LocalizedText;
supportingKeywords: LocalizedTextList;
titleTemplate: LocalizedText;
descriptionTemplate: LocalizedText;
faqTemplates: SeoFaqTemplate[];
relatedCollectionSlugs: string[];
contentSections?: SeoContentSection[];
}
export interface SeoCollectionPage {
slug: string;
focusKeyword: LocalizedText;
supportingKeywords: LocalizedTextList;
titleTemplate: LocalizedText;
descriptionTemplate: LocalizedText;
introTemplate: LocalizedText;
targetToolSlugs: string[];
faqTemplates: SeoFaqTemplate[];
relatedCollectionSlugs: string[];
contentSections?: SeoContentSection[];
}
export function normalizeSeoLocale(language: string): SeoLocale {
return language.toLowerCase().startsWith('ar') ? 'ar' : 'en';
}
export function getLocalizedText(value: LocalizedText, locale: SeoLocale): string {
return value[locale] || value.en;
}
export function getLocalizedTextList(value: LocalizedTextList, locale: SeoLocale): string[] {
return value[locale] || value.en;
}
export function interpolateTemplate(template: string, tokens: Record<string, string>): string {
return template.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_, key: string) => tokens[key] ?? '');
}
export function getProgrammaticToolPage(slug: string): ProgrammaticToolPage | undefined {
return PROGRAMMATIC_TOOL_PAGES.find((page) => page.slug === slug);
}
export function getSeoCollectionPage(slug: string): SeoCollectionPage | undefined {
return SEO_COLLECTION_PAGES.find((page) => page.slug === slug);
}
export function getAllProgrammaticSeoPaths(): string[] {
return PROGRAMMATIC_TOOL_PAGES.map((page) => `/${page.slug}`);
}
export function getAllCollectionSeoPaths(): string[] {
return SEO_COLLECTION_PAGES.map((page) => `/${page.slug}`);
}
export function getAllSeoLandingPaths(): string[] {
return [...getAllProgrammaticSeoPaths(), ...getAllCollectionSeoPaths()];
}
export function getAllLocalizedSeoLandingPaths(): string[] {
return [...getLocalizedSeoLandingPaths('en'), ...getLocalizedSeoLandingPaths('ar')];
}