schon/evibes/i18n.py
Egor fureunoir Gorbunov 555666d6fe Features: 1) Add language switching functionality with URL-based language detection and session/cookie persistence;
Fixes: 1) Ensure safe next URL handling with host validation;

Extra: 1) Introduce new i18n module with language normalization and translation support; 2) Add route for language setting endpoint.
2025-11-16 01:23:09 +03:00

75 lines
2.4 KiB
Python

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