- {% set title = _t(pricing.title, 'Simple, Transparent Pricing') %}
+ {% set title = _t(pricing.title, lang, default_lang, 'Simple, Transparent Pricing') %}
{{ title }}
- {% set subtitle = _t(pricing.subtitle) %}
+ {% set subtitle = _t(pricing.subtitle, lang, default_lang) %}
{% if subtitle|trim %}
{{ subtitle }}
@@ -40,13 +40,13 @@
{# Pricing toggle (monthly/annual) #}
{% if pricing.use_subscription_tiers and tiers %}
- {% set monthly_label = _t(pricing.monthly_label, 'Monthly') %}
- {% set annual_label = _t(pricing.annual_label, 'Annual') %}
- {% set save_text = _t(pricing.save_text, 'Save 2 months!') %}
- {% set popular_badge = _t(pricing.popular_badge, 'Most Popular') %}
- {% set cta_text = _t(pricing.cta_text, 'Start Free Trial') %}
- {% set per_month = _t(pricing.per_month_label, '/month') %}
- {% set per_year = _t(pricing.per_year_label, '/year') %}
+ {% set monthly_label = _t(pricing.monthly_label, lang, default_lang, 'Monthly') %}
+ {% set annual_label = _t(pricing.annual_label, lang, default_lang, 'Annual') %}
+ {% set save_text = _t(pricing.save_text, lang, default_lang, 'Save 2 months!') %}
+ {% set popular_badge = _t(pricing.popular_badge, lang, default_lang, 'Most Popular') %}
+ {% set cta_text = _t(pricing.cta_text, lang, default_lang, 'Start Free Trial') %}
+ {% set per_month = _t(pricing.per_month_label, lang, default_lang, '/month') %}
+ {% set per_year = _t(pricing.per_year_label, lang, default_lang, '/year') %}
{# Billing toggle #}
@@ -128,7 +128,7 @@
{% else %}
{# Placeholder when no tiers available #}
- {% set coming_soon = _t(pricing.coming_soon_text, 'Pricing plans coming soon') %}
+ {% set coming_soon = _t(pricing.coming_soon_text, lang, default_lang, 'Pricing plans coming soon') %}
{{ coming_soon }}
diff --git a/app/modules/tenancy/migrations/versions/tenancy_004_add_platform_description_translations.py b/app/modules/tenancy/migrations/versions/tenancy_004_add_platform_description_translations.py
new file mode 100644
index 00000000..21e45444
--- /dev/null
+++ b/app/modules/tenancy/migrations/versions/tenancy_004_add_platform_description_translations.py
@@ -0,0 +1,31 @@
+"""add description_translations to platforms
+
+Revision ID: tenancy_004
+Revises: billing_002
+Create Date: 2026-03-04
+"""
+
+import sqlalchemy as sa
+
+from alembic import op
+
+revision = "tenancy_004"
+down_revision = "billing_002"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ op.add_column(
+ "platforms",
+ sa.Column(
+ "description_translations",
+ sa.JSON(),
+ nullable=True,
+ comment="Language-keyed description dict for multi-language support",
+ ),
+ )
+
+
+def downgrade() -> None:
+ op.drop_column("platforms", "description_translations")
diff --git a/app/modules/tenancy/models/platform.py b/app/modules/tenancy/models/platform.py
index 64522eeb..1f0dbfde 100644
--- a/app/modules/tenancy/models/platform.py
+++ b/app/modules/tenancy/models/platform.py
@@ -71,6 +71,13 @@ class Platform(Base, TimestampMixin):
comment="Platform description for admin/marketing purposes",
)
+ description_translations = Column(
+ JSON,
+ nullable=True,
+ default=None,
+ comment="Language-keyed description dict for multi-language support",
+ )
+
# ========================================================================
# Domain Routing
# ========================================================================
@@ -226,6 +233,17 @@ class Platform(Base, TimestampMixin):
# Properties
# ========================================================================
+ def get_translated_description(self, lang: str, default_lang: str = "fr") -> str:
+ """Return description in the requested language with fallback."""
+ if self.description_translations:
+ return (
+ self.description_translations.get(lang)
+ or self.description_translations.get(default_lang)
+ or self.description
+ or ""
+ )
+ return self.description or ""
+
@property
def base_url(self) -> str:
"""Get the base URL for this platform (for link generation)."""
diff --git a/app/modules/tenancy/routes/api/admin_platforms.py b/app/modules/tenancy/routes/api/admin_platforms.py
index 36930463..cec9a1ab 100644
--- a/app/modules/tenancy/routes/api/admin_platforms.py
+++ b/app/modules/tenancy/routes/api/admin_platforms.py
@@ -41,6 +41,7 @@ class PlatformResponse(BaseModel):
code: str
name: str
description: str | None = None
+ description_translations: dict[str, str] | None = None
domain: str | None = None
path_prefix: str | None = None
logo: str | None = None
@@ -76,6 +77,7 @@ class PlatformUpdateRequest(BaseModel):
name: str | None = None
description: str | None = None
+ description_translations: dict[str, str] | None = None
domain: str | None = None
path_prefix: str | None = None
logo: str | None = None
@@ -115,6 +117,7 @@ def _build_platform_response(db: Session, platform) -> PlatformResponse:
code=platform.code,
name=platform.name,
description=platform.description,
+ description_translations=platform.description_translations,
domain=platform.domain,
path_prefix=platform.path_prefix,
logo=platform.logo,
diff --git a/app/modules/tenancy/static/admin/js/platform-edit.js b/app/modules/tenancy/static/admin/js/platform-edit.js
index 8bb0943a..b15936ea 100644
--- a/app/modules/tenancy/static/admin/js/platform-edit.js
+++ b/app/modules/tenancy/static/admin/js/platform-edit.js
@@ -22,10 +22,20 @@ function platformEdit() {
success: null,
platformCode: null,
+ // Language editing
+ currentLang: 'fr',
+ languageNames: {
+ fr: 'Fran\u00e7ais',
+ de: 'Deutsch',
+ en: 'English',
+ lb: 'L\u00ebtzebuergesch',
+ },
+
// Form data
formData: {
name: '',
description: '',
+ description_translations: {},
domain: '',
path_prefix: '',
logo: '',
@@ -46,7 +56,7 @@ function platformEdit() {
{ code: 'fr', name: 'French' },
{ code: 'de', name: 'German' },
{ code: 'en', name: 'English' },
- { code: 'lu', name: 'Luxembourgish' },
+ { code: 'lb', name: 'Luxembourgish' },
],
// Lifecycle
@@ -92,23 +102,34 @@ function platformEdit() {
const response = await apiClient.get(`/admin/platforms/${this.platformCode}`);
this.platform = response;
+ // Build description_translations with empty strings for all supported languages
+ const langs = response.supported_languages || ['fr', 'de', 'en'];
+ const descTranslations = {};
+ for (const lang of langs) {
+ descTranslations[lang] = (response.description_translations && response.description_translations[lang]) || '';
+ }
+
// Populate form data
this.formData = {
name: response.name || '',
description: response.description || '',
+ description_translations: descTranslations,
domain: response.domain || '',
path_prefix: response.path_prefix || '',
logo: response.logo || '',
logo_dark: response.logo_dark || '',
favicon: response.favicon || '',
default_language: response.default_language || 'fr',
- supported_languages: response.supported_languages || ['fr', 'de', 'en'],
+ supported_languages: langs,
is_active: response.is_active ?? true,
is_public: response.is_public ?? true,
theme_config: response.theme_config || {},
settings: response.settings || {},
};
+ // Set current language tab to default language
+ this.currentLang = response.default_language || 'fr';
+
platformEditLog.info(`Loaded platform: ${this.platformCode}`);
} catch (err) {
platformEditLog.error('Error loading platform:', err);
@@ -126,9 +147,14 @@ function platformEdit() {
try {
// Build update payload (only changed fields)
+ // Sync base description from default language translation
+ const defaultLang = this.formData.default_language || 'fr';
+ const baseDesc = this.formData.description_translations[defaultLang] || this.formData.description;
+
const payload = {
name: this.formData.name,
- description: this.formData.description || null,
+ description: baseDesc || null,
+ description_translations: this.formData.description_translations,
domain: this.formData.domain || null,
path_prefix: this.formData.path_prefix || null,
logo: this.formData.logo || null,
@@ -199,9 +225,17 @@ function platformEdit() {
// Don't allow removing the last language
if (this.formData.supported_languages.length > 1) {
this.formData.supported_languages.splice(index, 1);
+ // Switch tab if the removed language was active
+ if (this.currentLang === code) {
+ this.currentLang = this.formData.supported_languages[0];
+ }
}
} else {
this.formData.supported_languages.push(code);
+ // Initialize empty translation for new language
+ if (!this.formData.description_translations[code]) {
+ this.formData.description_translations[code] = '';
+ }
}
},
diff --git a/app/modules/tenancy/templates/tenancy/admin/platform-edit.html b/app/modules/tenancy/templates/tenancy/admin/platform-edit.html
index 60d95532..72692742 100644
--- a/app/modules/tenancy/templates/tenancy/admin/platform-edit.html
+++ b/app/modules/tenancy/templates/tenancy/admin/platform-edit.html
@@ -130,19 +130,41 @@
-
-