feat: comprehensive SEO system with keywords and schema markup

- Add 4-locale keyword configurations (SR, EN, DE, FR)
- Create schema generators (Product, Organization, Breadcrumb)
- Add React components for JSON-LD rendering
- Implement caching for keyword performance
- Abstract all SEO logic for maintainability
This commit is contained in:
Unchained
2026-03-30 11:22:44 +02:00
parent 767afac606
commit 234b1f1739
17 changed files with 1957 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
import { Locale, LocaleKeywords } from '../types';
import { serbianKeywords } from '../locales/sr';
import { englishKeywords } from '../locales/en';
import { germanKeywords } from '../locales/de';
import { frenchKeywords } from '../locales/fr';
/**
* Cache for loaded keywords to avoid repeated imports
*/
const keywordsCache: Record<Locale, LocaleKeywords | null> = {
sr: null,
en: null,
de: null,
fr: null
};
/**
* Get all SEO keywords for a specific locale
* Uses caching for performance
*
* @param locale - The locale code ('sr', 'en', 'de', 'fr')
* @returns LocaleKeywords object with all keywords for that locale
* @example
* const keywords = getKeywords('sr');
* console.log(keywords.pages.home.primary); // ['prirodni serum za lice', ...]
*/
export function getKeywords(locale: Locale): LocaleKeywords {
// Return from cache if available
if (keywordsCache[locale]) {
return keywordsCache[locale]!;
}
// Load keywords based on locale
const keywordsMap: Record<Locale, LocaleKeywords> = {
sr: serbianKeywords,
en: englishKeywords,
de: germanKeywords,
fr: frenchKeywords
};
const keywords = keywordsMap[locale];
// Cache for future use
keywordsCache[locale] = keywords;
return keywords;
}
/**
* Get keywords for a specific page type
* Convenience function for page-level keyword access
*
* @param locale - The locale code
* @param pageType - The page type ('home', 'products', 'product', 'about', 'contact', 'checkout', 'blog')
* @returns PageKeywords for the specified page
* @example
* const homeKeywords = getPageKeywords('sr', 'home');
* console.log(homeKeywords.primary); // Primary keywords for homepage
*/
export function getPageKeywords(
locale: Locale,
pageType: keyof LocaleKeywords['pages']
) {
const keywords = getKeywords(locale);
return keywords.pages[pageType];
}
/**
* Get category-specific keywords
*
* @param locale - The locale code
* @param category - The category key ('antiAging', 'hydration', 'glow', 'sensitive', 'natural', 'organic')
* @returns Array of keywords for that category
*/
export function getCategoryKeywords(
locale: Locale,
category: keyof LocaleKeywords['categories']
): string[] {
const keywords = getKeywords(locale);
return keywords.categories[category];
}
/**
* Get content topic keywords for blog/article generation
*
* @param locale - The locale code
* @param contentType - Type of content ('educational', 'benefits', 'comparison', 'ingredients')
* @returns Array of content topic keywords
*/
export function getContentKeywords(
locale: Locale,
contentType: keyof LocaleKeywords['content']
): string[] {
const keywords = getKeywords(locale);
return keywords.content[contentType];
}
/**
* Get competitor keywords for comparison content
*
* @param locale - The locale code
* @param competitorType - Type of competitor data ('brands', 'comparisons', 'alternatives')
* @returns Array of competitor-related keywords
*/
export function getCompetitorKeywords(
locale: Locale,
competitorType: keyof LocaleKeywords['competitors']
): string[] {
const keywords = getKeywords(locale);
return keywords.competitors[competitorType];
}
/**
* Get brand information for the locale
*
* @param locale - The locale code
* @returns BrandKeywords with localized tagline, category, etc.
*/
export function getBrandKeywords(locale: Locale) {
const keywords = getKeywords(locale);
return keywords.brand;
}
/**
* Clear the keywords cache (useful for testing or hot-reloading)
*/
export function clearKeywordsCache(): void {
keywordsCache.sr = null;
keywordsCache.en = null;
keywordsCache.de = null;
keywordsCache.fr = null;
}
/**
* Get all available locales
*/
export function getAvailableLocales(): Locale[] {
return ['sr', 'en', 'de', 'fr'];
}
/**
* Validate if a locale is supported
*/
export function isValidLocale(locale: string): locale is Locale {
return ['sr', 'en', 'de', 'fr'].includes(locale);
}
export default getKeywords;