"""Locale detection and management for i18n support. This module provides locale detection from Accept-Language headers and cookies, following the same middleware pattern as the flash message system. """ from fastapi import Request from app.infrastructure.i18n.translator import DEFAULT_LOCALE, SUPPORTED_LOCALES LOCALE_COOKIE_NAME = "locale" SUPPORTED_LOCALES_SET: frozenset[str] = SUPPORTED_LOCALES def _parse_accept_language(header: str) -> list[str]: """Parse Accept-Language header into ordered list of locale codes. Args: header: Raw Accept-Language header value. Returns: List of locale codes in preference order, with region subtags removed. """ if not header: return [] locales: list[str] = [] for part in header.split(","): part = part.strip() if not part: continue locale = part.split(";")[0].strip().split("-")[0] if locale: locales.append(locale) return locales def _get_best_locale(request: Request) -> str: """Detect the best locale for the current request. Priority order: cookie → Accept-Language header → default. Args: request: FastAPI request object. Returns: Best matching locale code, defaulting to ``en``. """ cookie_locale = request.cookies.get(LOCALE_COOKIE_NAME) if cookie_locale and cookie_locale in SUPPORTED_LOCALES_SET: return cookie_locale accept_language = request.headers.get("accept-language", "") for lang in _parse_accept_language(accept_language): if lang in SUPPORTED_LOCALES_SET: return lang return DEFAULT_LOCALE async def setup_locale_manager(request: Request) -> None: """Set the detected locale on request state. Called early in the request lifecycle so that route handlers and template rendering can access the current locale via ``request.state.locale``. Args: request: FastAPI request object. """ if not hasattr(request.state, "locale"): request.state.locale = _get_best_locale(request)