feat(cms): Phase A — page type selector, translation UI, SEO cleanup
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

Content page editor improvements:
- Page type selector: Content Page / Landing Page dropdown (sets template)
- Title language tabs: translate page titles per language (same pattern as sections)
- Content language tabs: translate page content per language
- Meta description language tabs: translatable SEO descriptions
- Template-driven section palette: template defines which sections are available
  (store landing pages hide Pricing, platform homepages show all)
- Hide content editor when Landing Page selected, hide sections when Content Page

Schema changes (migration cms_003):
- Add meta_description_translations column (JSON) to content_pages
- Drop meta_keywords column (obsolete, ignored by all search engines since 2009)
- Remove meta keywords tag from storefront and platform base templates

API + service updates:
- title_translations, content_translations, meta_description_translations
  added to create/update schemas, route handlers, and service methods

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 22:30:55 +02:00
parent bb3d6f0012
commit b5bb9415f6
11 changed files with 293 additions and 91 deletions

View File

@@ -24,19 +24,27 @@ class ContentPageCreate(BaseModel):
description="URL-safe identifier (about, faq, contact, etc.)",
)
title: str = Field(..., max_length=200, description="Page title")
title_translations: dict[str, str] | None = Field(
None, description="Title translations keyed by language code"
)
content: str = Field(..., description="HTML or Markdown content")
content_translations: dict[str, str] | None = Field(
None, description="Content translations keyed by language code"
)
content_format: str = Field(
default="html", description="Content format: html or markdown"
)
template: str = Field(
default="default",
max_length=50,
description="Template name (default, minimal, modern)",
description="Template name (default, minimal, modern, full)",
)
meta_description: str | None = Field(
None, max_length=300, description="SEO meta description"
)
meta_keywords: str | None = Field(None, max_length=300, description="SEO keywords")
meta_description_translations: dict[str, str] | None = Field(
None, description="Meta description translations keyed by language code"
)
is_published: bool = Field(default=False, description="Publish immediately")
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
show_in_header: bool = Field(default=False, description="Show in header navigation")
@@ -53,11 +61,13 @@ class ContentPageUpdate(BaseModel):
"""Schema for updating a content page (admin)."""
title: str | None = Field(None, max_length=200)
title_translations: dict[str, str] | None = None
content: str | None = None
content_translations: dict[str, str] | None = None
content_format: str | None = None
template: str | None = Field(None, max_length=50)
meta_description: str | None = Field(None, max_length=300)
meta_keywords: str | None = Field(None, max_length=300)
meta_description_translations: dict[str, str] | None = None
is_published: bool | None = None
show_in_footer: bool | None = None
show_in_header: bool | None = None
@@ -78,11 +88,13 @@ class ContentPageResponse(BaseModel):
store_name: str | None
slug: str
title: str
title_translations: dict[str, str] | None = None
content: str
content_translations: dict[str, str] | None = None
content_format: str
template: str | None = None
meta_description: str | None
meta_keywords: str | None
meta_description_translations: dict[str, str] | None = None
is_published: bool
published_at: str | None
display_order: int
@@ -135,7 +147,6 @@ class StoreContentPageCreate(BaseModel):
meta_description: str | None = Field(
None, max_length=300, description="SEO meta description"
)
meta_keywords: str | None = Field(None, max_length=300, description="SEO keywords")
is_published: bool = Field(default=False, description="Publish immediately")
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
show_in_header: bool = Field(default=False, description="Show in header navigation")
@@ -152,7 +163,6 @@ class StoreContentPageUpdate(BaseModel):
content: str | None = None
content_format: str | None = None
meta_description: str | None = Field(None, max_length=300)
meta_keywords: str | None = Field(None, max_length=300)
is_published: bool | None = None
show_in_footer: bool | None = None
show_in_header: bool | None = None
@@ -187,7 +197,6 @@ class PublicContentPageResponse(BaseModel):
content: str
content_format: str
meta_description: str | None
meta_keywords: str | None
published_at: str | None