feat(i18n): complete post-launch i18n phases 5-8
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

- 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>
This commit is contained in:
2026-03-03 05:50:06 +01:00
parent 05c53e1865
commit b8aa484653
16 changed files with 965 additions and 235 deletions

View File

@@ -64,9 +64,20 @@ from app.modules.tenancy.models import Platform
# ============================================================================
def t(fr: str, en: str, de: str) -> dict:
def t(fr: str, en: str, de: str, lb: str | None = None) -> dict:
"""Build a TranslatableText-compatible dict."""
return {"translations": {"fr": fr, "en": en, "de": de}}
d = {"translations": {"fr": fr, "en": en, "de": de}}
if lb is not None:
d["translations"]["lb"] = lb
return d
def tt(en: str, fr: str, de: str, lb: str | None = None) -> dict:
"""Build a simple language-keyed dict for title_translations / content_translations."""
d = {"en": en, "fr": fr, "de": de}
if lb is not None:
d["lb"] = lb
return d
# ============================================================================
@@ -128,6 +139,7 @@ def _wizard_homepage_sections() -> dict:
"Synchronisieren Sie Ihre Letzshop-Bestellungen, verwalten Sie Lagerbestände und erstellen Sie MwSt-konforme Rechnungen.",
),
"url": "/platforms/oms/",
"link_text": t("En savoir plus", "Learn More", "Mehr erfahren"),
},
{
"icon": "heart",
@@ -142,6 +154,7 @@ def _wizard_homepage_sections() -> dict:
"Erstellen Sie Treueprogramme mit Punkten, Prämien und Stufen, um Ihre Kunden zu binden.",
),
"url": "/platforms/loyalty/",
"link_text": t("En savoir plus", "Learn More", "Mehr erfahren"),
},
{
"icon": "globe-alt",
@@ -320,6 +333,18 @@ def _oms_homepage_sections() -> dict:
"Wählen Sie den Plan, der zu Ihrem Unternehmen passt. Alle Pläne beinhalten eine kostenlose Testversion.",
),
"use_subscription_tiers": True,
"monthly_label": t("Mensuel", "Monthly", "Monatlich"),
"annual_label": t("Annuel", "Annual", "Jährlich"),
"save_text": t("Économisez 2 mois !", "Save 2 months!", "Sparen Sie 2 Monate!"),
"popular_badge": t("LE PLUS POPULAIRE", "MOST POPULAR", "AM BELIEBTESTEN"),
"cta_text": t("Essai gratuit", "Start Free Trial", "Kostenlos testen"),
"per_month_label": t("/mois", "/month", "/Monat"),
"per_year_label": t("/an", "/year", "/Jahr"),
"coming_soon_text": t(
"Plans tarifaires bientôt disponibles",
"Pricing plans coming soon",
"Preispläne demnächst verfügbar",
),
},
"cta": {
"enabled": True,
@@ -472,6 +497,18 @@ def _loyalty_homepage_sections() -> dict:
"Wählen Sie den Plan, der zu Ihrem Treueprogramm passt.",
),
"use_subscription_tiers": True,
"monthly_label": t("Mensuel", "Monthly", "Monatlich"),
"annual_label": t("Annuel", "Annual", "Jährlich"),
"save_text": t("Économisez 2 mois !", "Save 2 months!", "Sparen Sie 2 Monate!"),
"popular_badge": t("LE PLUS POPULAIRE", "MOST POPULAR", "AM BELIEBTESTEN"),
"cta_text": t("Essai gratuit", "Start Free Trial", "Kostenlos testen"),
"per_month_label": t("/mois", "/month", "/Monat"),
"per_year_label": t("/an", "/year", "/Jahr"),
"coming_soon_text": t(
"Plans tarifaires bientôt disponibles",
"Pricing plans coming soon",
"Preispläne demnächst verfügbar",
),
},
"cta": {
"enabled": True,
@@ -521,6 +558,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "about",
"title": "About Wizard",
"title_translations": tt("About Wizard", "À propos de Wizard", "Über Wizard", "Iwwer Wizard"),
"content": """<div class="prose-content">
<h2>About Wizard</h2>
<p>Wizard is the digital toolkit for Luxembourg businesses. We provide integrated solutions for order management, customer loyalty, and online presence.</p>
@@ -543,6 +581,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "contact",
"title": "Contact Us",
"title_translations": tt("Contact Us", "Contactez-nous", "Kontakt", "Kontakt"),
"content": """<div class="prose-content">
<h2>Contact Wizard</h2>
<p>We'd love to hear from you. Get in touch with our team.</p>
@@ -571,6 +610,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "faq",
"title": "FAQ",
"title_translations": tt("FAQ", "FAQ", "FAQ", "FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h3>General</h3>
@@ -601,6 +641,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "about",
"title": "About OMS",
"title_translations": tt("About OMS", "À propos d'OMS", "Über OMS", "Iwwer OMS"),
"content": """<div class="prose-content">
<h2>About OMS by Wizard</h2>
<p>OMS (omsflow.lu) is a lightweight order management system built specifically for Letzshop sellers in Luxembourg.</p>
@@ -623,6 +664,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "contact",
"title": "Contact OMS Support",
"title_translations": tt("Contact OMS Support", "Contacter le support OMS", "OMS Support kontaktieren", "OMS Support kontaktéieren"),
"content": """<div class="prose-content">
<h2>Contact OMS Support</h2>
<p>Need help with your order management? We're here for you.</p>
@@ -644,6 +686,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "faq",
"title": "OMS FAQ",
"title_translations": tt("OMS FAQ", "FAQ OMS", "OMS FAQ", "OMS FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>Do I need a Letzshop account?</h4>
@@ -669,6 +712,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "about",
"title": "About Loyalty",
"title_translations": tt("About Loyalty", "À propos de Loyalty", "Über Loyalty", "Iwwer Loyalty"),
"content": """<div class="prose-content">
<h2>About Loyalty by Wizard</h2>
<p>Loyalty (rewardflow.lu) helps Luxembourg businesses create engaging customer loyalty programs.</p>
@@ -690,6 +734,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "contact",
"title": "Contact Loyalty Support",
"title_translations": tt("Contact Loyalty Support", "Contacter le support Loyalty", "Loyalty Support kontaktieren", "Loyalty Support kontaktéieren"),
"content": """<div class="prose-content">
<h2>Contact Loyalty Support</h2>
<p>Need help with your loyalty program? We're here for you.</p>
@@ -711,6 +756,7 @@ def _get_platform_pages(platform_code: str) -> list[dict]:
{
"slug": "faq",
"title": "Loyalty FAQ",
"title_translations": tt("Loyalty FAQ", "FAQ Loyalty", "Loyalty FAQ", "Loyalty FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>How do loyalty points work?</h4>
@@ -737,6 +783,7 @@ SHARED_PLATFORM_PAGES = [
{
"slug": "privacy",
"title": "Privacy Policy",
"title_translations": tt("Privacy Policy", "Politique de confidentialité", "Datenschutzrichtlinie", "Dateschutzrichtlinn"),
"content": """<div class="prose-content">
<h2>Privacy Policy</h2>
<p><em>Last Updated: February 2026</em></p>
@@ -763,6 +810,7 @@ SHARED_PLATFORM_PAGES = [
{
"slug": "terms",
"title": "Terms of Service",
"title_translations": tt("Terms of Service", "Conditions d'utilisation", "Nutzungsbedingungen", "Notzungsbedingungen"),
"content": """<div class="prose-content">
<h2>Terms of Service</h2>
<p><em>Last Updated: February 2026</em></p>
@@ -800,6 +848,7 @@ STORE_DEFAULTS_COMMON = [
{
"slug": "about",
"title": "About Us",
"title_translations": tt("About Us", "À propos", "Über uns", "Iwwer eis"),
"content": """<div class="prose-content">
<h2>About {{store_name}}</h2>
<p>Welcome to {{store_name}}. We are committed to providing you with quality products and excellent service.</p>
@@ -820,6 +869,7 @@ STORE_DEFAULTS_COMMON = [
{
"slug": "contact",
"title": "Contact Us",
"title_translations": tt("Contact Us", "Contactez-nous", "Kontakt", "Kontakt"),
"content": """<div class="prose-content">
<h2>Contact {{store_name}}</h2>
<p>We're here to help! Reach out to us.</p>
@@ -838,6 +888,7 @@ STORE_DEFAULTS_COMMON = [
{
"slug": "faq",
"title": "FAQ",
"title_translations": tt("FAQ", "FAQ", "FAQ", "FAQ"),
"content": """<div class="prose-content">
<h2>Frequently Asked Questions</h2>
<h4>How can I contact you?</h4>
@@ -855,6 +906,7 @@ STORE_DEFAULTS_COMMON = [
{
"slug": "privacy",
"title": "Privacy Policy",
"title_translations": tt("Privacy Policy", "Politique de confidentialité", "Datenschutzrichtlinie", "Dateschutzrichtlinn"),
"content": """<div class="prose-content">
<h2>Privacy Policy</h2>
<p>{{store_name}} is committed to protecting your personal information.</p>
@@ -872,6 +924,7 @@ STORE_DEFAULTS_COMMON = [
{
"slug": "terms",
"title": "Terms of Service",
"title_translations": tt("Terms of Service", "Conditions d'utilisation", "Nutzungsbedingungen", "Notzungsbedingungen"),
"content": """<div class="prose-content">
<h2>Terms of Service</h2>
<p>By using the services provided by {{store_name}}, you agree to these terms.</p>
@@ -893,6 +946,7 @@ STORE_DEFAULTS_OMS_EXTRA = [
{
"slug": "shipping",
"title": "Shipping Policy",
"title_translations": tt("Shipping Policy", "Politique de livraison", "Versandrichtlinie", "Versandrichtlinn"),
"content": """<div class="prose-content">
<h2>Shipping Policy</h2>
<h3>Shipping Methods</h3>
@@ -916,6 +970,7 @@ STORE_DEFAULTS_OMS_EXTRA = [
{
"slug": "returns",
"title": "Return & Refund Policy",
"title_translations": tt("Return & Refund Policy", "Politique de retour et remboursement", "Rückgabe- und Erstattungsrichtlinie", "Retour- a Rembourséierungsrichtlinn"),
"content": """<div class="prose-content">
<h2>Return & Refund Policy</h2>
<h3>Returns</h3>
@@ -978,7 +1033,9 @@ def _create_page(
store_id=None,
slug=slug,
title=title,
title_translations=page_data.get("title_translations"),
content=page_data.get("content", ""),
content_translations=page_data.get("content_translations"),
content_format="html",
template=page_data.get("template", "default"),
sections=sections,