refactor: centralize locale resolution through PlatformSettingsService

Remove hardcoded locale fallbacks from templates and ensure all routes
use the proper resolution chain (AdminSetting → Config → Default).

Changes:
- Add get_vendor_context() helper to vendor_pages.py mirroring shop pattern
- Update all vendor routes to use get_vendor_context() with db session
- Update shop routes to pass db session for locale resolution
- Remove hardcoded fallbacks from shop/base.html and vendor/base.html
- Templates now receive pre-resolved locale values from routes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-03 11:12:06 +01:00
parent 214e3997b0
commit 3715493e47
4 changed files with 162 additions and 130 deletions

View File

@@ -240,7 +240,9 @@ async def shop_products_page(request: Request, db: Session = Depends(get_db)):
"/products/{product_id}", response_class=HTMLResponse, include_in_schema=False
)
async def shop_product_detail_page(
request: Request, product_id: int = Path(..., description="Product ID")
request: Request,
product_id: int = Path(..., description="Product ID"),
db: Session = Depends(get_db),
):
"""
Render product detail page.
@@ -256,7 +258,7 @@ async def shop_product_detail_page(
)
return templates.TemplateResponse(
"shop/product.html", get_shop_context(request, product_id=product_id)
"shop/product.html", get_shop_context(request, db=db, product_id=product_id)
)
@@ -264,7 +266,9 @@ async def shop_product_detail_page(
"/categories/{category_slug}", response_class=HTMLResponse, include_in_schema=False
)
async def shop_category_page(
request: Request, category_slug: str = Path(..., description="Category slug")
request: Request,
category_slug: str = Path(..., description="Category slug"),
db: Session = Depends(get_db),
):
"""
Render category products page.
@@ -280,12 +284,12 @@ async def shop_category_page(
)
return templates.TemplateResponse(
"shop/category.html", get_shop_context(request, category_slug=category_slug)
"shop/category.html", get_shop_context(request, db=db, category_slug=category_slug)
)
@router.get("/cart", response_class=HTMLResponse, include_in_schema=False)
async def shop_cart_page(request: Request):
async def shop_cart_page(request: Request, db: Session = Depends(get_db)):
"""
Render shopping cart page.
Shows cart items and allows quantity updates.
@@ -299,11 +303,11 @@ async def shop_cart_page(request: Request):
},
)
return templates.TemplateResponse("shop/cart.html", get_shop_context(request))
return templates.TemplateResponse("shop/cart.html", get_shop_context(request, db=db))
@router.get("/checkout", response_class=HTMLResponse, include_in_schema=False)
async def shop_checkout_page(request: Request):
async def shop_checkout_page(request: Request, db: Session = Depends(get_db)):
"""
Render checkout page.
Handles shipping, payment, and order confirmation.
@@ -317,11 +321,11 @@ async def shop_checkout_page(request: Request):
},
)
return templates.TemplateResponse("shop/checkout.html", get_shop_context(request))
return templates.TemplateResponse("shop/checkout.html", get_shop_context(request, db=db))
@router.get("/search", response_class=HTMLResponse, include_in_schema=False)
async def shop_search_page(request: Request):
async def shop_search_page(request: Request, db: Session = Depends(get_db)):
"""
Render search results page.
Shows products matching search query.
@@ -335,7 +339,7 @@ async def shop_search_page(request: Request):
},
)
return templates.TemplateResponse("shop/search.html", get_shop_context(request))
return templates.TemplateResponse("shop/search.html", get_shop_context(request, db=db))
# ============================================================================
@@ -344,7 +348,7 @@ async def shop_search_page(request: Request):
@router.get("/account/register", response_class=HTMLResponse, include_in_schema=False)
async def shop_register_page(request: Request):
async def shop_register_page(request: Request, db: Session = Depends(get_db)):
"""
Render customer registration page.
No authentication required.
@@ -359,12 +363,12 @@ async def shop_register_page(request: Request):
)
return templates.TemplateResponse(
"shop/account/register.html", get_shop_context(request)
"shop/account/register.html", get_shop_context(request, db=db)
)
@router.get("/account/login", response_class=HTMLResponse, include_in_schema=False)
async def shop_login_page(request: Request):
async def shop_login_page(request: Request, db: Session = Depends(get_db)):
"""
Render customer login page.
No authentication required.
@@ -379,14 +383,14 @@ async def shop_login_page(request: Request):
)
return templates.TemplateResponse(
"shop/account/login.html", get_shop_context(request)
"shop/account/login.html", get_shop_context(request, db=db)
)
@router.get(
"/account/forgot-password", response_class=HTMLResponse, include_in_schema=False
)
async def shop_forgot_password_page(request: Request):
async def shop_forgot_password_page(request: Request, db: Session = Depends(get_db)):
"""
Render forgot password page.
Allows customers to reset their password.
@@ -401,7 +405,7 @@ async def shop_forgot_password_page(request: Request):
)
return templates.TemplateResponse(
"shop/account/forgot-password.html", get_shop_context(request)
"shop/account/forgot-password.html", get_shop_context(request, db=db)
)
@@ -752,7 +756,7 @@ async def generic_content_page(
)
return templates.TemplateResponse(
"shop/content-page.html", get_shop_context(request, page=page)
"shop/content-page.html", get_shop_context(request, db=db, page=page)
)

View File

@@ -36,7 +36,9 @@ from app.api.deps import (
)
from app.services.content_page_service import content_page_service
from app.services.onboarding_service import OnboardingService
from app.services.platform_settings_service import platform_settings_service
from models.database.user import User
from models.database.vendor import Vendor
logger = logging.getLogger(__name__)
@@ -44,6 +46,79 @@ router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
# ============================================================================
# HELPER: Build Vendor Dashboard Context
# ============================================================================
def get_vendor_context(
request: Request,
db: Session,
current_user: User,
vendor_code: str,
**extra_context,
) -> dict:
"""
Build template context for vendor dashboard pages.
Resolves locale/currency using the platform settings service with
vendor override support:
1. Vendor's storefront_locale (if set)
2. Platform's default from PlatformSettingsService
3. Environment variable
4. Hardcoded fallback
Args:
request: FastAPI request object
db: Database session
current_user: Authenticated vendor user
vendor_code: Vendor subdomain/code
**extra_context: Additional variables for template
Returns:
Dictionary with request, user, vendor, resolved locale/currency, and extra context
"""
# Load vendor from database
vendor = db.query(Vendor).filter(Vendor.subdomain == vendor_code).first()
# Get platform defaults
platform_config = platform_settings_service.get_storefront_config(db)
# Resolve with vendor override
storefront_locale = platform_config["locale"]
storefront_currency = platform_config["currency"]
if vendor and vendor.storefront_locale:
storefront_locale = vendor.storefront_locale
context = {
"request": request,
"user": current_user,
"vendor": vendor,
"vendor_code": vendor_code,
"storefront_locale": storefront_locale,
"storefront_currency": storefront_currency,
"dashboard_language": vendor.dashboard_language if vendor else "en",
}
# Add any extra context
if extra_context:
context.update(extra_context)
logger.debug(
"[VENDOR_CONTEXT] Context built",
extra={
"vendor_id": vendor.id if vendor else None,
"vendor_code": vendor_code,
"storefront_locale": storefront_locale,
"storefront_currency": storefront_currency,
"extra_keys": list(extra_context.keys()) if extra_context else [],
},
)
return context
# ============================================================================
# PUBLIC ROUTES (No Authentication Required)
# ============================================================================
@@ -143,11 +218,7 @@ async def vendor_onboarding_page(
return templates.TemplateResponse(
"vendor/onboarding.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -181,11 +252,7 @@ async def vendor_dashboard_page(
return templates.TemplateResponse(
"vendor/dashboard.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -201,6 +268,7 @@ async def vendor_products_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render products management page.
@@ -208,11 +276,7 @@ async def vendor_products_page(
"""
return templates.TemplateResponse(
"vendor/products.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -228,6 +292,7 @@ async def vendor_orders_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render orders management page.
@@ -235,11 +300,7 @@ async def vendor_orders_page(
"""
return templates.TemplateResponse(
"vendor/orders.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -253,6 +314,7 @@ async def vendor_order_detail_page(
vendor_code: str = Path(..., description="Vendor code"),
order_id: int = Path(..., description="Order ID"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render order detail page.
@@ -268,12 +330,7 @@ async def vendor_order_detail_page(
"""
return templates.TemplateResponse(
"vendor/order-detail.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
"order_id": order_id,
},
get_vendor_context(request, db, current_user, vendor_code, order_id=order_id),
)
@@ -289,6 +346,7 @@ async def vendor_customers_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render customers management page.
@@ -296,11 +354,7 @@ async def vendor_customers_page(
"""
return templates.TemplateResponse(
"vendor/customers.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -316,6 +370,7 @@ async def vendor_messages_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render messages page.
@@ -323,11 +378,7 @@ async def vendor_messages_page(
"""
return templates.TemplateResponse(
"vendor/messages.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -341,6 +392,7 @@ async def vendor_message_detail_page(
vendor_code: str = Path(..., description="Vendor code"),
conversation_id: int = Path(..., description="Conversation ID"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render message detail page.
@@ -348,12 +400,9 @@ async def vendor_message_detail_page(
"""
return templates.TemplateResponse(
"vendor/messages.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
"conversation_id": conversation_id,
},
get_vendor_context(
request, db, current_user, vendor_code, conversation_id=conversation_id
),
)
@@ -369,6 +418,7 @@ async def vendor_inventory_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render inventory management page.
@@ -376,11 +426,7 @@ async def vendor_inventory_page(
"""
return templates.TemplateResponse(
"vendor/inventory.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -396,6 +442,7 @@ async def vendor_marketplace_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render marketplace import page.
@@ -403,11 +450,7 @@ async def vendor_marketplace_page(
"""
return templates.TemplateResponse(
"vendor/marketplace.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -423,6 +466,7 @@ async def vendor_letzshop_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render Letzshop integration page.
@@ -430,11 +474,7 @@ async def vendor_letzshop_page(
"""
return templates.TemplateResponse(
"vendor/letzshop.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -450,6 +490,7 @@ async def vendor_invoices_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render invoices management page.
@@ -457,11 +498,7 @@ async def vendor_invoices_page(
"""
return templates.TemplateResponse(
"vendor/invoices.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -475,6 +512,7 @@ async def vendor_team_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render team management page.
@@ -482,11 +520,7 @@ async def vendor_team_page(
"""
return templates.TemplateResponse(
"vendor/team.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -502,6 +536,7 @@ async def vendor_profile_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render vendor profile page.
@@ -509,11 +544,7 @@ async def vendor_profile_page(
"""
return templates.TemplateResponse(
"vendor/profile.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -524,6 +555,7 @@ async def vendor_settings_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render vendor settings page.
@@ -531,11 +563,7 @@ async def vendor_settings_page(
"""
return templates.TemplateResponse(
"vendor/settings.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -546,6 +574,7 @@ async def vendor_billing_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render billing and subscription management page.
@@ -553,11 +582,7 @@ async def vendor_billing_page(
"""
return templates.TemplateResponse(
"vendor/billing.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -573,6 +598,7 @@ async def vendor_notifications_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render notifications center page.
@@ -580,11 +606,7 @@ async def vendor_notifications_page(
"""
return templates.TemplateResponse(
"vendor/notifications.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -600,6 +622,7 @@ async def vendor_analytics_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render analytics and reports page.
@@ -607,11 +630,7 @@ async def vendor_analytics_page(
"""
return templates.TemplateResponse(
"vendor/analytics.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -627,6 +646,7 @@ async def vendor_content_pages_list(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content pages management page.
@@ -634,11 +654,7 @@ async def vendor_content_pages_list(
"""
return templates.TemplateResponse(
"vendor/content-pages.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
},
get_vendor_context(request, db, current_user, vendor_code),
)
@@ -651,18 +667,14 @@ async def vendor_content_page_create(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content page creation form.
"""
return templates.TemplateResponse(
"vendor/content-page-edit.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
"page_id": None,
},
get_vendor_context(request, db, current_user, vendor_code, page_id=None),
)
@@ -676,18 +688,14 @@ async def vendor_content_page_edit(
vendor_code: str = Path(..., description="Vendor code"),
page_id: int = Path(..., description="Content page ID"),
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content page edit form.
"""
return templates.TemplateResponse(
"vendor/content-page-edit.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
"page_id": page_id,
},
get_vendor_context(request, db, current_user, vendor_code, page_id=page_id),
)
@@ -759,11 +767,22 @@ async def vendor_content_page(
},
)
# Resolve locale for shop template (uses same resolution chain as shop routes)
platform_config = platform_settings_service.get_storefront_config(db)
storefront_locale = platform_config["locale"]
storefront_currency = platform_config["currency"]
if vendor and vendor.storefront_locale:
storefront_locale = vendor.storefront_locale
return templates.TemplateResponse(
"shop/content-page.html",
{
"request": request,
"page": page,
"vendor": vendor,
"vendor_code": vendor_code,
"storefront_locale": storefront_locale,
"storefront_currency": storefront_currency,
},
)

View File

@@ -308,12 +308,12 @@
{# 1. Log Configuration (must load first) #}
<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
{# 2. Global Shop Configuration (currency/locale settings) #}
{# 2. Global Shop Configuration (resolved via PlatformSettingsService) #}
<script>
window.SHOP_CONFIG = {
locale: '{{ storefront_locale | default("fr-LU") }}',
currency: '{{ storefront_currency | default("EUR") }}',
language: '{{ request.state.language|default("fr") }}'
locale: '{{ storefront_locale }}',
currency: '{{ storefront_currency }}',
language: '{{ request.state.language }}'
};
</script>

View File

@@ -50,6 +50,15 @@
<!-- 1. FIRST: Log Configuration -->
<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
<!-- 1.5: Vendor Configuration (resolved via PlatformSettingsService) -->
<script>
window.VENDOR_CONFIG = {
locale: '{{ storefront_locale }}',
currency: '{{ storefront_currency }}',
dashboardLanguage: '{{ dashboard_language }}'
};
</script>
<!-- 2. SECOND: Icons (before Alpine.js) -->
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>