# middleware/language.py """ Language detection middleware for multi-language support. This middleware detects the appropriate language for each request based on: - User/Customer preferences (from JWT token) - Session/cookie language - Vendor settings - Browser Accept-Language header - System default The resolved language is stored in request.state.language for use in templates. """ import logging from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response from app.utils.i18n import ( DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, parse_accept_language, resolve_storefront_language, resolve_vendor_dashboard_language, ) logger = logging.getLogger(__name__) # Cookie name for language preference LANGUAGE_COOKIE_NAME = "lang" class LanguageMiddleware(BaseHTTPMiddleware): """ Middleware to detect and set the request language. Sets request.state.language based on context: - Admin: Always English (for now) - Vendor dashboard: User preference → Vendor dashboard_language → default - Storefront: Customer preference → Cookie → Vendor storefront_language → browser → default - API: Accept-Language header → default """ async def dispatch(self, request: Request, call_next) -> Response: """Process the request and set language.""" # Get context type from previous middleware context_type = getattr(request.state, "context_type", None) context_value = context_type.value if context_type else None # Get vendor from previous middleware (if available) vendor = getattr(request.state, "vendor", None) # Get language from cookie cookie_language = request.cookies.get(LANGUAGE_COOKIE_NAME) # Get browser language from Accept-Language header accept_language = request.headers.get("accept-language") browser_language = parse_accept_language(accept_language) # Resolve language based on context if context_value == "admin": # Admin dashboard: English only (for now) # TODO: Implement admin language support later language = "en" elif context_value == "vendor_dashboard": # Vendor dashboard user_preferred = self._get_user_language_from_token(request) vendor_dashboard = vendor.dashboard_language if vendor else None language = resolve_vendor_dashboard_language( user_preferred=user_preferred, vendor_dashboard=vendor_dashboard, ) elif context_value == "shop": # Storefront customer_preferred = self._get_customer_language_from_token(request) vendor_storefront = vendor.storefront_language if vendor else None enabled_languages = vendor.storefront_languages if vendor else None language = resolve_storefront_language( customer_preferred=customer_preferred, session_language=cookie_language, vendor_storefront=vendor_storefront, browser_language=browser_language, enabled_languages=enabled_languages, ) elif context_value == "api": # API requests: Use Accept-Language or cookie language = cookie_language or browser_language or DEFAULT_LANGUAGE else: # Fallback: Use cookie, browser, or default language = cookie_language or browser_language or DEFAULT_LANGUAGE # Validate language is supported if language not in SUPPORTED_LANGUAGES: language = DEFAULT_LANGUAGE # Store language in request state request.state.language = language # Also store related info for templates request.state.language_info = { "code": language, "cookie": cookie_language, "browser": browser_language, "context": context_value, } # Log language detection for debugging logger.debug( f"Language detected: {language} " f"(context={context_value}, cookie={cookie_language}, browser={browser_language})" ) # Process request response = await call_next(request) return response def _get_user_language_from_token(self, request: Request) -> str | None: """ Extract user's preferred_language from JWT token. This requires the auth middleware to have run first and stored user info in request.state. """ # Check if user info is in request state (set by auth dependency) current_user = getattr(request.state, "current_user", None) if current_user and hasattr(current_user, "preferred_language"): return current_user.preferred_language return None def _get_customer_language_from_token(self, request: Request) -> str | None: """ Extract customer's preferred_language from JWT token. This requires the shop auth middleware to have run first. """ # Check if customer info is in request state current_customer = getattr(request.state, "current_customer", None) if current_customer and hasattr(current_customer, "preferred_language"): return current_customer.preferred_language return None def set_language_cookie(response: Response, language: str) -> Response: """ Helper function to set the language cookie on a response. Args: response: Response object to modify language: Language code to set Returns: Modified response with language cookie """ if language in SUPPORTED_LANGUAGES: response.set_cookie( key=LANGUAGE_COOKIE_NAME, value=language, max_age=60 * 60 * 24 * 365, # 1 year httponly=False, # Accessible to JavaScript samesite="lax", ) return response def delete_language_cookie(response: Response) -> Response: """ Helper function to delete the language cookie. Args: response: Response object to modify Returns: Modified response with cookie deleted """ response.delete_cookie(key=LANGUAGE_COOKIE_NAME) return response