feat: add language switching to admin and merchant frontends
Some checks failed
Some checks failed
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
})
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{# app/templates/admin/login.html #}
|
||||
<!DOCTYPE html>
|
||||
<html :class="{ 'dark': dark }" x-data="adminLogin()" lang="en">
|
||||
<html :class="{ 'dark': dark }" x-data="adminLogin()" lang="{{ current_language|default('en') }}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@@ -9,6 +9,12 @@
|
||||
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
<!-- Flag Icons CSS (for language selector) with local fallback -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"
|
||||
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/store/flag-icons.min.css') }}';"
|
||||
/>
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
@@ -97,6 +103,23 @@
|
||||
← Back to Platform
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- Language selector -->
|
||||
<div class="flex items-center justify-center gap-3 mt-6" x-data="{
|
||||
async setLang(lang) {
|
||||
await fetch('/api/v1/platform/language/set', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({language: lang})
|
||||
});
|
||||
window.location.reload();
|
||||
}
|
||||
}">
|
||||
<button @click="setLang('en')" class="fi fi-gb text-lg opacity-60 hover:opacity-100 transition-opacity" title="English"></button>
|
||||
<button @click="setLang('fr')" class="fi fi-fr text-lg opacity-60 hover:opacity-100 transition-opacity" title="Français"></button>
|
||||
<button @click="setLang('de')" class="fi fi-de text-lg opacity-60 hover:opacity-100 transition-opacity" title="Deutsch"></button>
|
||||
<button @click="setLang('lb')" class="fi fi-lu text-lg opacity-60 hover:opacity-100 transition-opacity" title="Lëtzebuergesch"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{# app/modules/tenancy/templates/tenancy/merchant/login.html #}
|
||||
{# Standalone login page - does NOT extend merchant/base.html #}
|
||||
<!DOCTYPE html>
|
||||
<html :class="{ 'dark': dark }" x-data="merchantLogin()" lang="en">
|
||||
<html :class="{ 'dark': dark }" x-data="merchantLogin()" lang="{{ current_language|default('fr') }}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@@ -10,6 +10,12 @@
|
||||
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='merchant/css/tailwind.output.css') }}" />
|
||||
<!-- Flag Icons CSS (for language selector) with local fallback -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"
|
||||
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/store/flag-icons.min.css') }}';"
|
||||
/>
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
@@ -128,6 +134,23 @@
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Language selector -->
|
||||
<div class="flex items-center justify-center gap-3 mt-6" x-data="{
|
||||
async setLang(lang) {
|
||||
await fetch('/api/v1/platform/language/set', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({language: lang})
|
||||
});
|
||||
window.location.reload();
|
||||
}
|
||||
}">
|
||||
<button @click="setLang('en')" class="fi fi-gb text-lg opacity-60 hover:opacity-100 transition-opacity" title="English"></button>
|
||||
<button @click="setLang('fr')" class="fi fi-fr text-lg opacity-60 hover:opacity-100 transition-opacity" title="Français"></button>
|
||||
<button @click="setLang('de')" class="fi fi-de text-lg opacity-60 hover:opacity-100 transition-opacity" title="Deutsch"></button>
|
||||
<button @click="setLang('lb')" class="fi fi-lu text-lg opacity-60 hover:opacity-100 transition-opacity" title="Lëtzebuergesch"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user