# /opt/docker/dev/service_finder/backend/app/core/i18n_middleware.py """ FastAPI middleware for automatic locale/language detection and context management. Implements the priority order: 1. ?lang= query parameter 2. Accept-Language HTTP header 3. User profile language (if authenticated) 4. Default "hu" (Hungarian) """ import logging from typing import Optional from fastapi import Request, HTTPException from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response from app.core.context import set_current_locale, get_current_locale from app.core.config import settings logger = logging.getLogger(__name__) class I18nMiddleware(BaseHTTPMiddleware): """ Middleware for automatic locale detection and context management. Sets the locale in contextvars for the duration of the request. """ async def dispatch(self, request: Request, call_next): # Determine locale based on priority locale = self._determine_locale(request) # Set locale in context set_current_locale(locale) # Add locale to request state for debugging/logging request.state.locale = locale # Process request response = await call_next(request) # Optionally add locale header to response response.headers["X-Content-Language"] = locale return response def _determine_locale(self, request: Request) -> str: """ Determine the locale based on the priority order. Returns a valid locale code (e.g., "hu", "en", "de"). """ # 1. Query parameter: ?lang= query_lang = request.query_params.get("lang") if query_lang and self._is_valid_locale(query_lang): logger.debug(f"Locale from query param: {query_lang}") return query_lang # 2. Accept-Language HTTP header accept_language = request.headers.get("accept-language") if accept_language: header_lang = self._parse_accept_language(accept_language) if header_lang and self._is_valid_locale(header_lang): logger.debug(f"Locale from Accept-Language header: {header_lang}") return header_lang # 3. User profile language (if authenticated) # Note: This requires the user to be authenticated, which may not be available # in middleware before authentication. We'll handle this in endpoint dependencies. # For now, we'll skip this step in middleware and let endpoints handle it. # 4. Default locale default_locale = getattr(settings, "DEFAULT_LOCALE", "hu") logger.debug(f"Using default locale: {default_locale}") return default_locale def _parse_accept_language(self, accept_language: str) -> Optional[str]: """ Parse Accept-Language header and return the highest priority locale. Example: "en-US,en;q=0.9,hu;q=0.8" -> "en" """ try: # Split by comma and process each language range languages = accept_language.split(',') for lang_range in languages: # Remove quality factor if present lang = lang_range.split(';')[0].strip() # Extract primary language subtag (e.g., "en" from "en-US") if '-' in lang: lang = lang.split('-')[0] if self._is_valid_locale(lang): return lang except Exception as e: logger.debug(f"Error parsing Accept-Language header: {e}") return None def _is_valid_locale(self, locale: str) -> bool: """ Check if a locale code is valid/supported. For now, we accept any 2-5 character locale code. In production, you might want to check against a list of supported locales. """ if not locale or len(locale) < 2 or len(locale) > 5: return False # Basic validation: only letters and hyphens return locale.replace('-', '').isalpha() # Create middleware instance (commented out to avoid instantiation error during import) # i18n_middleware = I18nMiddleware()