# 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 - Store 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.modules.enums import FrontendType from app.utils.i18n import ( DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, parse_accept_language, resolve_store_dashboard_language, resolve_storefront_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: Cookie → User preference → English - Merchant: Cookie → User preference → Platform default - Store dashboard: Cookie → User preference → Store dashboard_language → default - Storefront: Customer preference → Cookie → Store storefront_language → browser → default - Platform: Cookie → Browser → Platform default """ async def dispatch(self, request: Request, call_next) -> Response: """Process the request and set language.""" # Get frontend type from FrontendTypeMiddleware frontend_type = getattr(request.state, "frontend_type", None) # Get store from previous middleware (if available) store = getattr(request.state, "store", 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 frontend type if frontend_type == FrontendType.ADMIN: # Admin dashboard: cookie → user preference → English default user_preferred = self._get_user_language_from_token(request) language = cookie_language or user_preferred or "en" elif frontend_type == FrontendType.STORE: # Store dashboard user_preferred = self._get_user_language_from_token(request) store_dashboard = store.dashboard_language if store else None language = resolve_store_dashboard_language( user_preferred=user_preferred, store_dashboard=store_dashboard, cookie_language=cookie_language, ) elif frontend_type == FrontendType.STOREFRONT: # Storefront customer_preferred = self._get_customer_language_from_token(request) store_storefront = store.storefront_language if store else None enabled_languages = store.storefront_languages if store else None language = resolve_storefront_language( customer_preferred=customer_preferred, session_language=cookie_language, store_storefront=store_storefront, browser_language=browser_language, enabled_languages=enabled_languages, ) elif frontend_type == FrontendType.MERCHANT: # Merchant portal: cookie → user preference → platform default user_preferred = self._get_user_language_from_token(request) language = cookie_language or user_preferred or DEFAULT_LANGUAGE elif frontend_type == FrontendType.PLATFORM: # Platform marketing pages: Use cookie, browser, or default language = cookie_language or browser_language or DEFAULT_LANGUAGE else: # Fallback (API or unknown): Use Accept-Language or cookie 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, "frontend_type": frontend_type.value if frontend_type else None, } # Log language detection for debugging frontend_value = frontend_type.value if frontend_type else "unknown" logger.debug( f"Language detected: {language} " f"(frontend={frontend_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", path="/", ) 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, path="/") return response