feat: add SEO configuration and pages for programmatic tools and collections
- Introduced seoPages.ts to manage SEO-related configurations and types for programmatic tools and collection pages. - Created SeoCollectionPage and SeoProgrammaticPage components to render SEO content dynamically based on the new configuration. - Enhanced API service to ensure CSRF token handling for secure requests. - Added generateHowTo utility function for structured data generation. - Updated sitemap generation script to include SEO tool and collection pages. - Configured TypeScript to resolve JSON modules for easier integration of SEO data. ستراتيجية التنفيذ لم أغير أي core logic في أدوات التحويل أو الضغط أو التحرير استخدمت architecture إضافية فوق النظام الحالي بدل استبداله جعلت الـ SEO pages تعتمد على source of truth واحد حتى يسهل التوسع ربطت التوليد مع build حتى لا تبقى sitemap وrobots ثابتة أو منسية دعمت العربية والإنجليزية داخل نفس config الجديد عززت internal linking من: صفحات SEO إلى tool pages صفحات SEO إلى collection pages footer إلى collection pages Suggested tools داخل صفحات الأدوات التحقق
This commit is contained in:
88
frontend/src/config/seoPages.ts
Normal file
88
frontend/src/config/seoPages.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import seoToolsConfig from '@/config/seo-tools.json';
|
||||
|
||||
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 ProgrammaticToolPage {
|
||||
slug: string;
|
||||
toolSlug: string;
|
||||
category: 'PDF' | 'Image' | 'AI' | 'Convert' | 'Utility';
|
||||
focusKeyword: LocalizedText;
|
||||
supportingKeywords: LocalizedTextList;
|
||||
titleTemplate: LocalizedText;
|
||||
descriptionTemplate: LocalizedText;
|
||||
faqTemplates: SeoFaqTemplate[];
|
||||
relatedCollectionSlugs: string[];
|
||||
}
|
||||
|
||||
export interface SeoCollectionPage {
|
||||
slug: string;
|
||||
focusKeyword: LocalizedText;
|
||||
supportingKeywords: LocalizedTextList;
|
||||
titleTemplate: LocalizedText;
|
||||
descriptionTemplate: LocalizedText;
|
||||
introTemplate: LocalizedText;
|
||||
targetToolSlugs: string[];
|
||||
faqTemplates: SeoFaqTemplate[];
|
||||
relatedCollectionSlugs: string[];
|
||||
}
|
||||
|
||||
interface SeoToolsConfig {
|
||||
toolPages: ProgrammaticToolPage[];
|
||||
collectionPages: SeoCollectionPage[];
|
||||
}
|
||||
|
||||
const config = seoToolsConfig as SeoToolsConfig;
|
||||
|
||||
export const PROGRAMMATIC_TOOL_PAGES = config.toolPages;
|
||||
export const SEO_COLLECTION_PAGES = config.collectionPages;
|
||||
|
||||
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()];
|
||||
}
|
||||
Reference in New Issue
Block a user