feat(i18n): complete post-launch i18n phases 5-8
Some checks failed
Some checks failed
- 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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user