Files
orion/app/modules/cms/schemas/homepage_sections.py
Samir Boulahtit b8aa484653
Some checks failed
CI / dependency-scanning (push) Successful in 28s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
CI / ruff (push) Successful in 12s
CI / pytest (push) Failing after 47m21s
CI / validate (push) Successful in 25s
feat(i18n): complete post-launch i18n phases 5-8
- Phase 5: Translate homepage-modern.html (~90 new locale keys, all
  hardcoded strings replaced with _() calls for dashboard mock,
  features, pricing tiers, testimonial sections)
- Phase 6: Translate homepage-minimal.html (17 new locale keys for
  fallback content, features, and CTA sections)
- Phase 7: Add multi-language page.title/content support with
  title_translations and content_translations JSON columns, Alembic
  migration cms_002, translated title/content resolution in templates,
  and seed script updates with tt() helper
- Phase 8: Complete lb.json audit — fill 6 missing keys (messages,
  confirmations), also backfill same keys in fr.json and de.json

All 4 locale files now have 340 keys with full parity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 05:50:06 +01:00

209 lines
6.8 KiB
Python

"""
Homepage Section Schemas with Dynamic Multi-Language Support.
Language codes are NOT hardcoded - they come from platform.supported_languages.
The TranslatableText class stores translations as a dict where keys are language codes.
Example JSON structure:
{
"hero": {
"enabled": true,
"title": {"translations": {"fr": "Bienvenue", "en": "Welcome"}},
"subtitle": {"translations": {...}},
"buttons": [...]
},
"features": {...},
"pricing": {...},
"cta": {...}
}
"""
from pydantic import BaseModel, Field
class TranslatableText(BaseModel):
"""
Text field with translations stored as language-keyed dict.
Languages come from platform.supported_languages (not hardcoded).
Use .get(lang, default_lang) to retrieve translation with fallback.
"""
translations: dict[str, str] = Field(
default_factory=dict, description="Language code -> translated text mapping"
)
def get(self, lang: str, default_lang: str = "fr") -> str:
"""Get translation with fallback to default language."""
return self.translations.get(lang) or self.translations.get(default_lang) or ""
def set(self, lang: str, text: str) -> None:
"""Set translation for a language."""
self.translations[lang] = text
def has_translation(self, lang: str) -> bool:
"""Check if translation exists for language."""
return bool(self.translations.get(lang))
class HeroButton(BaseModel):
"""Button in hero or CTA section."""
text: TranslatableText = Field(default_factory=TranslatableText)
url: str = ""
style: str = Field(default="primary", description="primary, secondary, outline")
class HeroSection(BaseModel):
"""Hero section configuration."""
enabled: bool = True
badge_text: TranslatableText | None = None
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText = Field(default_factory=TranslatableText)
background_type: str = Field(
default="gradient", description="gradient, image, solid"
)
background_image: str | None = None
buttons: list[HeroButton] = Field(default_factory=list)
class FeatureCard(BaseModel):
"""Single feature in features section."""
icon: str = ""
title: TranslatableText = Field(default_factory=TranslatableText)
description: TranslatableText = Field(default_factory=TranslatableText)
class ProductCard(BaseModel):
"""Single product/offering card in products section."""
icon: str = ""
title: TranslatableText = Field(default_factory=TranslatableText)
description: TranslatableText = Field(default_factory=TranslatableText)
url: str = ""
badge: TranslatableText | None = None
link_text: TranslatableText | None = None
class ProductsSection(BaseModel):
"""Product/offering showcase section (e.g. wizard.lu multi-product landing)."""
enabled: bool = True
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText | None = None
products: list[ProductCard] = Field(default_factory=list)
class FeaturesSection(BaseModel):
"""Features section configuration."""
enabled: bool = True
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText | None = None
features: list[FeatureCard] = Field(default_factory=list)
layout: str = Field(default="grid", description="grid, list, cards")
class PricingSection(BaseModel):
"""Pricing section configuration."""
enabled: bool = True
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText | None = None
use_subscription_tiers: bool = Field(
default=True, description="Pull pricing from subscription_tiers table dynamically"
)
# UI label overrides (CMS-driven, with hardcoded English fallback in template)
monthly_label: TranslatableText | None = None
annual_label: TranslatableText | None = None
save_text: TranslatableText | None = None
popular_badge: TranslatableText | None = None
cta_text: TranslatableText | None = None
per_month_label: TranslatableText | None = None
per_year_label: TranslatableText | None = None
coming_soon_text: TranslatableText | None = None
class CTASection(BaseModel):
"""Call-to-action section configuration."""
enabled: bool = True
title: TranslatableText = Field(default_factory=TranslatableText)
subtitle: TranslatableText | None = None
buttons: list[HeroButton] = Field(default_factory=list)
background_type: str = Field(
default="gradient", description="gradient, image, solid"
)
class HomepageSections(BaseModel):
"""Complete homepage sections structure."""
hero: HeroSection | None = None
products: ProductsSection | None = None
features: FeaturesSection | None = None
pricing: PricingSection | None = None
cta: CTASection | None = None
@classmethod
def get_empty_structure(cls, languages: list[str]) -> "HomepageSections":
"""
Create empty section structure with language placeholders.
Args:
languages: List of language codes from platform.supported_languages
Returns:
HomepageSections with empty translations for all languages
"""
def make_translatable(langs: list[str]) -> TranslatableText:
return TranslatableText(translations=dict.fromkeys(langs, ""))
return cls(
hero=HeroSection(
title=make_translatable(languages),
subtitle=make_translatable(languages),
buttons=[],
),
products=ProductsSection(
title=make_translatable(languages),
products=[],
),
features=FeaturesSection(
title=make_translatable(languages),
features=[],
),
pricing=PricingSection(
title=make_translatable(languages),
use_subscription_tiers=True,
),
cta=CTASection(
title=make_translatable(languages),
buttons=[],
),
)
# =============================================================================
# API Request/Response Schemas
# =============================================================================
class SectionUpdateRequest(BaseModel):
"""Request to update a single section."""
section_name: str = Field(..., description="hero, products, features, pricing, or cta")
section_data: dict = Field(..., description="Section configuration")
class HomepageSectionsResponse(BaseModel):
"""Response containing all homepage sections with platform language info."""
sections: HomepageSections | None = None
supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en"])
default_language: str = "fr"