diff --git a/evibes/i18n.py b/evibes/i18n.py new file mode 100644 index 00000000..ee803531 --- /dev/null +++ b/evibes/i18n.py @@ -0,0 +1,75 @@ +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 diff --git a/evibes/urls.py b/evibes/urls.py index 233877d4..4edd993a 100644 --- a/evibes/urls.py +++ b/evibes/urls.py @@ -12,6 +12,7 @@ from engine.core.views import ( favicon_view, index, ) +from evibes.i18n import set_language urlpatterns = [ ### COMMON URLS ### @@ -35,6 +36,11 @@ urlpatterns = [ "summernote/", include("django_summernote.urls"), ), + path( + r"i18n/setlang/", + set_language, + name="set_language", + ), path( r"i18n/", include("django.conf.urls.i18n"),