feat: add configurable currency locale and fix vendor JS init

Currency Locale Configuration:
- Add platform-level storefront settings (locale, currency)
- Create PlatformSettingsService with resolution chain:
  vendor → AdminSetting → environment → hardcoded fallback
- Add storefront_locale nullable field to Vendor model
- Update shop routes to resolve and pass locale to templates
- Add window.SHOP_CONFIG for frontend JavaScript access
- Centralize formatPrice() in shop-layout.js using SHOP_CONFIG
- Remove local formatPrice functions from shop templates

Vendor JS Bug Fix:
- Fix vendorCode being null on all vendor pages
- Root cause: page components overriding init() without calling parent
- Add parent init call to 14 vendor JS files
- Add JS-013 architecture rule to prevent future regressions
- Validator now checks vendor JS files for parent init pattern

Files changed:
- New: app/services/platform_settings_service.py
- New: alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py
- Modified: 14 vendor JS files, shop templates, validation scripts

🤖 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-02 21:26:12 +01:00
parent d9d34ab102
commit c87bdfa129
30 changed files with 522 additions and 48 deletions

View File

@@ -39,6 +39,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_customer_from_cookie_or_header, get_db
from app.services.content_page_service import content_page_service
from app.services.platform_settings_service import platform_settings_service
from models.database.customer import Customer
router = APIRouter()
@@ -47,6 +48,40 @@ templates = Jinja2Templates(directory="app/templates")
logger = logging.getLogger(__name__)
# ============================================================================
# HELPER: Resolve Storefront Locale
# ============================================================================
def get_resolved_storefront_config(db: Session, vendor) -> dict:
"""
Resolve storefront locale and currency with priority:
1. Vendor's storefront_locale (if set)
2. Platform's default_storefront_locale (from AdminSetting)
3. Environment variable (from config)
4. Hardcoded fallback: 'fr-LU'
Args:
db: Database session
vendor: Vendor model instance
Returns:
dict with 'locale' and 'currency' keys
"""
# Get platform defaults from service (handles resolution chain 2-4)
platform_config = platform_settings_service.get_storefront_config(db)
# Check for vendor override (step 1)
locale = platform_config["locale"]
if vendor and vendor.storefront_locale:
locale = vendor.storefront_locale
return {
"locale": locale,
"currency": platform_config["currency"],
}
# ============================================================================
# HELPER: Build Shop Template Context
# ============================================================================
@@ -133,6 +168,11 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
)
# Resolve storefront locale and currency
storefront_config = {"locale": "fr-LU", "currency": "EUR"} # defaults
if db and vendor:
storefront_config = get_resolved_storefront_config(db, vendor)
context = {
"request": request,
"vendor": vendor,
@@ -142,6 +182,8 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
"base_url": base_url,
"footer_pages": footer_pages,
"header_pages": header_pages,
"storefront_locale": storefront_config["locale"],
"storefront_currency": storefront_config["currency"],
}
# Add any extra context (user, product_id, category_slug, etc.)
@@ -157,6 +199,8 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
"has_theme": theme is not None,
"access_method": access_method,
"base_url": base_url,
"storefront_locale": storefront_config["locale"],
"storefront_currency": storefront_config["currency"],
"footer_pages_count": len(footer_pages),
"header_pages_count": len(header_pages),
"extra_keys": list(extra_context.keys()) if extra_context else [],