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

@@ -473,7 +473,7 @@ class ContentPageService:
content_format: str = "html",
template: str = "default",
meta_description: str | None = None,
meta_keywords: str | None = None,
meta_description_translations: str | None = None,
is_published: bool = False,
show_in_footer: bool = True,
show_in_header: bool = False,
@@ -495,7 +495,7 @@ class ContentPageService:
content_format: "html" or "markdown"
template: Template name for landing pages
meta_description: SEO description
meta_keywords: SEO keywords
meta_description_translations: Meta description translations dict
is_published: Publish immediately
show_in_footer: Show in footer navigation
show_in_header: Show in header navigation
@@ -516,7 +516,7 @@ class ContentPageService:
content_format=content_format,
template=template,
meta_description=meta_description,
meta_keywords=meta_keywords,
meta_description_translations=meta_description_translations,
is_published=is_published,
published_at=datetime.now(UTC) if is_published else None,
show_in_footer=show_in_footer,
@@ -542,11 +542,13 @@ class ContentPageService:
db: Session,
page_id: int,
title: str | None = None,
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 = None,
meta_description: str | None = None,
meta_keywords: str | None = None,
meta_description_translations: str | None = None,
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
@@ -574,16 +576,20 @@ class ContentPageService:
# Update fields if provided
if title is not None:
page.title = title
if title_translations is not None:
page.title_translations = title_translations
if content is not None:
page.content = content
if content_translations is not None:
page.content_translations = content_translations
if content_format is not None:
page.content_format = content_format
if template is not None:
page.template = template
if meta_description is not None:
page.meta_description = meta_description
if meta_keywords is not None:
page.meta_keywords = meta_keywords
if meta_description_translations is not None:
page.meta_description_translations = meta_description_translations
if is_published is not None:
page.is_published = is_published
if is_published and not page.published_at:
@@ -699,7 +705,7 @@ class ContentPageService:
content: str | None = None,
content_format: str | None = None,
meta_description: str | None = None,
meta_keywords: str | None = None,
meta_description_translations: str | None = None,
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
@@ -726,7 +732,7 @@ class ContentPageService:
content=content,
content_format=content_format,
meta_description=meta_description,
meta_keywords=meta_keywords,
meta_description_translations=meta_description_translations,
is_published=is_published,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
@@ -761,7 +767,7 @@ class ContentPageService:
content: str,
content_format: str = "html",
meta_description: str | None = None,
meta_keywords: str | None = None,
meta_description_translations: str | None = None,
is_published: bool = False,
show_in_footer: bool = True,
show_in_header: bool = False,
@@ -792,7 +798,7 @@ class ContentPageService:
is_platform_page=False,
content_format=content_format,
meta_description=meta_description,
meta_keywords=meta_keywords,
meta_description_translations=meta_description_translations,
is_published=is_published,
show_in_footer=show_in_footer,
show_in_header=show_in_header,
@@ -914,11 +920,13 @@ class ContentPageService:
db: Session,
page_id: int,
title: str | None = None,
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 = None,
meta_description: str | None = None,
meta_keywords: str | None = None,
meta_description_translations: str | None = None,
is_published: bool | None = None,
show_in_footer: bool | None = None,
show_in_header: bool | None = None,
@@ -936,11 +944,13 @@ class ContentPageService:
db,
page_id=page_id,
title=title,
title_translations=title_translations,
content=content,
content_translations=content_translations,
content_format=content_format,
template=template,
meta_description=meta_description,
meta_keywords=meta_keywords,
meta_description_translations=meta_description_translations,
is_published=is_published,
show_in_footer=show_in_footer,
show_in_header=show_in_header,