From 6c78827c7f3552df8e9cb12d832fc528b25f0382 Mon Sep 17 00:00:00 2001
From: Samir Boulahtit
Date: Tue, 24 Feb 2026 10:26:57 +0100
Subject: [PATCH] feat: add language switching to admin and merchant frontends
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add cookie to ADMIN resolution chain (cookie → user_pref → "en")
- Add explicit MERCHANT resolution (cookie → user_pref → "fr")
- Add language selector dropdown to admin and merchant headers
- Add languageSelector() function to merchant init-alpine.js
- Add flag-icons CSS and i18n.js setup to merchant base template
- Add compact flag-based language selector to both login pages
- Make lang attribute dynamic on all base and login templates
- Pass current_language to login route template context
- Update architecture doc with ADMIN/MERCHANT resolution priorities
Co-Authored-By: Claude Opus 4.6
---
app/modules/core/routes/pages/admin.py | 5 +-
app/modules/core/routes/pages/merchant.py | 5 +-
.../core/static/merchant/js/init-alpine.js | 46 +++++++++++++++++++
.../templates/tenancy/admin/login.html | 25 +++++++++-
.../templates/tenancy/merchant/login.html | 25 +++++++++-
app/templates/admin/base.html | 2 +-
app/templates/admin/partials/header.html | 38 +++++++++++++++
app/templates/merchant/base.html | 18 +++++++-
app/templates/merchant/partials/header.html | 38 +++++++++++++++
docs/architecture/language-i18n.md | 18 +++++---
middleware/language.py | 16 +++++--
11 files changed, 219 insertions(+), 17 deletions(-)
diff --git a/app/modules/core/routes/pages/admin.py b/app/modules/core/routes/pages/admin.py
index c750fe81..dd8aff34 100644
--- a/app/modules/core/routes/pages/admin.py
+++ b/app/modules/core/routes/pages/admin.py
@@ -57,7 +57,10 @@ async def admin_login_page(
if current_user:
return RedirectResponse(url="/admin/dashboard", status_code=302)
- return templates.TemplateResponse("tenancy/admin/login.html", {"request": request})
+ return templates.TemplateResponse("tenancy/admin/login.html", {
+ "request": request,
+ "current_language": getattr(request.state, "language", "en"),
+ })
@router.get("/select-platform", response_class=HTMLResponse, include_in_schema=False)
diff --git a/app/modules/core/routes/pages/merchant.py b/app/modules/core/routes/pages/merchant.py
index 930f4b85..5059f752 100644
--- a/app/modules/core/routes/pages/merchant.py
+++ b/app/modules/core/routes/pages/merchant.py
@@ -67,7 +67,10 @@ async def merchant_login_page(
if current_user:
return RedirectResponse(url="/merchants/dashboard", status_code=302)
- return templates.TemplateResponse("tenancy/merchant/login.html", {"request": request})
+ return templates.TemplateResponse("tenancy/merchant/login.html", {
+ "request": request,
+ "current_language": getattr(request.state, "language", "fr"),
+ })
# ============================================================================
diff --git a/app/modules/core/static/merchant/js/init-alpine.js b/app/modules/core/static/merchant/js/init-alpine.js
index aa7f4c8a..41a43537 100644
--- a/app/modules/core/static/merchant/js/init-alpine.js
+++ b/app/modules/core/static/merchant/js/init-alpine.js
@@ -201,3 +201,49 @@ function data() {
}
};
}
+
+/**
+ * Language Selector Component
+ * Alpine.js component for language switching in merchant portal
+ */
+function languageSelector(currentLang, enabledLanguages) {
+ return {
+ isLangOpen: false,
+ currentLang: currentLang || 'fr',
+ languages: enabledLanguages || ['en', 'fr', 'de', 'lb'],
+ languageNames: {
+ 'en': 'English',
+ 'fr': 'Français',
+ 'de': 'Deutsch',
+ 'lb': 'Lëtzebuergesch'
+ },
+ languageFlags: {
+ 'en': 'gb',
+ 'fr': 'fr',
+ 'de': 'de',
+ 'lb': 'lu'
+ },
+ async setLanguage(lang) {
+ if (lang === this.currentLang) {
+ this.isLangOpen = false;
+ return;
+ }
+ try {
+ const response = await fetch('/api/v1/platform/language/set', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ language: lang })
+ });
+ if (response.ok) {
+ this.currentLang = lang;
+ window.location.reload();
+ }
+ } catch (error) {
+ console.error('Failed to set language:', error);
+ }
+ this.isLangOpen = false;
+ }
+ };
+}
+
+window.languageSelector = languageSelector;
diff --git a/app/modules/tenancy/templates/tenancy/admin/login.html b/app/modules/tenancy/templates/tenancy/admin/login.html
index 64ff69a6..317ea94e 100644
--- a/app/modules/tenancy/templates/tenancy/admin/login.html
+++ b/app/modules/tenancy/templates/tenancy/admin/login.html
@@ -1,6 +1,6 @@
{# app/templates/admin/login.html #}
-
+
@@ -9,6 +9,12 @@
+
+
@@ -97,6 +103,23 @@
← Back to Platform
+
+
+
+
+
+
+
+
diff --git a/app/modules/tenancy/templates/tenancy/merchant/login.html b/app/modules/tenancy/templates/tenancy/merchant/login.html
index 374ddaa0..1a9cfc91 100644
--- a/app/modules/tenancy/templates/tenancy/merchant/login.html
+++ b/app/modules/tenancy/templates/tenancy/merchant/login.html
@@ -1,7 +1,7 @@
{# app/modules/tenancy/templates/tenancy/merchant/login.html #}
{# Standalone login page - does NOT extend merchant/base.html #}
-
+
@@ -10,6 +10,12 @@
+
+
@@ -128,6 +134,23 @@
+
+
+
+
+
+
+
+
diff --git a/app/templates/admin/base.html b/app/templates/admin/base.html
index 3cfefbab..e8077347 100644
--- a/app/templates/admin/base.html
+++ b/app/templates/admin/base.html
@@ -1,6 +1,6 @@
{# app/templates/admin/base.html #}
-
+
diff --git a/app/templates/admin/partials/header.html b/app/templates/admin/partials/header.html
index f253d819..d9648495 100644
--- a/app/templates/admin/partials/header.html
+++ b/app/templates/admin/partials/header.html
@@ -50,6 +50,44 @@
+
+
+
+
+
+
+
+
+
+
-
+
@@ -13,6 +13,13 @@
+
+
+