from __future__ import annotations from contextlib import suppress from urllib.parse import urlparse from django.conf import settings from django.http import HttpRequest, HttpResponseRedirect from django.urls import translate_url from django.utils.http import url_has_allowed_host_and_scheme from django.utils.translation import activate from django.views.decorators.csrf import csrf_exempt def _normalize_language_code(lang: str | None) -> str: if not lang: return settings.LANGUAGE_CODE lang = lang.replace("_", "-").lower() overrides = getattr(settings, "LANGUAGE_URL_OVERRIDES", {}) or {} if lang in overrides: lang = overrides[lang].lower() supported = {code.lower() for code, _ in getattr(settings, "LANGUAGES", [])} if lang not in supported: primary = lang.split("-")[0] if primary in overrides and overrides[primary].lower() in supported: return overrides[primary].lower() return settings.LANGUAGE_CODE return lang def _safe_next_url(request: HttpRequest) -> str: next_url = request.POST.get("next") or request.GET.get("next") or request.META.get("HTTP_REFERER") or "/" if not url_has_allowed_host_and_scheme( url=next_url, allowed_hosts={request.get_host()}, require_https=request.is_secure(), ): return "/" return next_url LANGUAGE_SESSION_KEY = "_language" @csrf_exempt def set_language(request: HttpRequest): language = request.POST.get("language") or request.GET.get("language") normalized = _normalize_language_code(language) response = HttpResponseRedirect("/") if hasattr(request, "session"): request.session[LANGUAGE_SESSION_KEY] = normalized response.set_cookie(settings.LANGUAGE_COOKIE_NAME, normalized) activate(normalized) next_url = _safe_next_url(request) if translate_url is not None: with suppress(Exception): next_url = translate_url(next_url, normalized) else: parsed = urlparse(next_url) parts = [p for p in parsed.path.split("/") if p] supported = {code.lower() for code, _ in getattr(settings, "LANGUAGES", [])} if parts and parts[0].lower() in supported: parts[0] = normalized path = "/" + "/".join(parts) + "/" if parsed.path.endswith("/") else "/" + "/".join(parts) next_url = parsed._replace(path=path).geturl() response["Location"] = next_url return response