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.
This commit is contained in:
parent
efa21cf9c0
commit
555666d6fe
2 changed files with 81 additions and 0 deletions
75
evibes/i18n.py
Normal file
75
evibes/i18n.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -12,6 +12,7 @@ from engine.core.views import (
|
||||||
favicon_view,
|
favicon_view,
|
||||||
index,
|
index,
|
||||||
)
|
)
|
||||||
|
from evibes.i18n import set_language
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
### COMMON URLS ###
|
### COMMON URLS ###
|
||||||
|
|
@ -35,6 +36,11 @@ urlpatterns = [
|
||||||
"summernote/",
|
"summernote/",
|
||||||
include("django_summernote.urls"),
|
include("django_summernote.urls"),
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
r"i18n/setlang/",
|
||||||
|
set_language,
|
||||||
|
name="set_language",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
r"i18n/",
|
r"i18n/",
|
||||||
include("django.conf.urls.i18n"),
|
include("django.conf.urls.i18n"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue