feat(storefront): translatable Store description + nav home key + dynamic html lang

Three small storefront i18n improvements found during the FR
pre-launch walkthrough on FASHIONHUB:

- Store description (e.g. "Trendy clothing and accessories") was a
  single English string rendering in the footer regardless of locale.
  Added a description_translations JSON column on Store with the same
  shape used elsewhere (CMS, Platform, Subscription), exposed via
  get_translated_description(lang), and updated the footer + meta tag
  to use it. Seeded FR/DE/LB/EN for FASHIONHUB and FASHIONOUTLET so
  Fashion Group renders correctly out of the box. Other stores still
  show the single description field as fallback.

- "Home" was a hardcoded English literal in both desktop and mobile
  nav, even though the FR translation already existed at nav.home in
  static/locales/fr.json. Now uses _('nav.home').

- <html lang="en"> was hardcoded, which made <input type="date"> show
  in mm/dd/yyyy on the FR storefront. Now driven by current_language
  so the browser's locale-aware date picker matches the page locale.

Migration tenancy_005 adds the description_translations column;
nullable, no backfill needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 19:46:17 +02:00
parent caf1361291
commit 06a44e55e7
4 changed files with 64 additions and 5 deletions

View File

@@ -0,0 +1,31 @@
"""add description_translations to stores
Revision ID: tenancy_005
Revises: tenancy_004
Create Date: 2026-05-16
"""
import sqlalchemy as sa
from alembic import op
revision = "tenancy_005"
down_revision = "tenancy_004"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"stores",
sa.Column(
"description_translations",
sa.JSON(),
nullable=True,
comment="Language-keyed description dict for multi-language support",
),
)
def downgrade() -> None:
op.drop_column("stores", "description_translations")

View File

@@ -56,6 +56,11 @@ class Store(Base, TimestampMixin, SoftDeleteMixin):
String, nullable=False
) # Non-nullable name column for the store (brand name)
description = Column(Text) # Optional text description column for the store
description_translations = Column(
JSON,
nullable=True,
comment="Language-keyed description dict for multi-language support",
)
# Letzshop URLs - multi-language support (brand-specific marketplace feeds)
letzshop_csv_url_fr = Column(String) # URL for French CSV in Letzshop
@@ -320,6 +325,16 @@ class Store(Base, TimestampMixin, SoftDeleteMixin):
"logo"
) # Return None or the logo URL if found
def get_translated_description(self, lang: str, default_lang: str = "fr") -> str | None:
"""Get description in the given language, falling back to default_lang then self.description."""
if self.description_translations:
return (
self.description_translations.get(lang)
or self.description_translations.get(default_lang)
or self.description
)
return self.description
# ========================================================================
# Domain Helper Methods
# ========================================================================