From 8440fe38238ea2a0d8051c370bb6908ee65b7401 Mon Sep 17 00:00:00 2001 From: Mandel Olaiya Date: Mon, 11 May 2026 20:38:50 +0200 Subject: [PATCH] Add first-visit language redirect middleware --- mandelstudio/middleware_language_redirect.py | 80 ++++++++++++++++++++ mandelstudio/settings/base.py | 16 ++++ 2 files changed, 96 insertions(+) create mode 100644 mandelstudio/middleware_language_redirect.py diff --git a/mandelstudio/middleware_language_redirect.py b/mandelstudio/middleware_language_redirect.py new file mode 100644 index 0000000..cb3ad94 --- /dev/null +++ b/mandelstudio/middleware_language_redirect.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from django.conf import settings +from django.http import HttpRequest, HttpResponseRedirect +from django.utils.translation import get_language_from_request + + +class FirstVisitLanguageRedirectMiddleware: + """Redirect first-visit requests to a supported language path.""" + + SKIP_PREFIXES = ( + "/admin", + "/cms", + "/dashboard", + "/manage", + "/api", + "/static", + "/media", + "/i18n/setlang", + ) + + def __init__(self, get_response): + self.get_response = get_response + self.language_cookie_name = getattr( + settings, "LANGUAGE_COOKIE_NAME", "django_language" + ) + self.default_language = settings.LANGUAGE_CODE.split("-")[0].lower() + self.supported_languages = { + code.split("-")[0].lower() for code, _label in settings.LANGUAGES + } + + def __call__(self, request: HttpRequest): + redirect_url = self._build_redirect_url(request) + if redirect_url: + return HttpResponseRedirect(redirect_url) + return self.get_response(request) + + def _build_redirect_url(self, request: HttpRequest) -> str | None: + if request.method != "GET": + return None + if self.language_cookie_name in request.COOKIES: + return None + + path = request.path_info or "/" + if self._should_skip_path(path): + return None + if self._is_localized_path(path): + return None + + target_lang = self._preferred_language(request) + if not target_lang or target_lang == self.default_language: + return None + + if path == "/": + localized_path = f"/{target_lang}/" + else: + localized_path = f"/{target_lang}{path}" + + query = request.META.get("QUERY_STRING") + if query: + return f"{localized_path}?{query}" + return localized_path + + def _should_skip_path(self, path: str) -> bool: + for prefix in self.SKIP_PREFIXES: + if path == prefix or path.startswith(f"{prefix}/"): + return True + return False + + def _is_localized_path(self, path: str) -> bool: + parts = [part for part in path.split("/") if part] + if not parts: + return False + return parts[0].lower() in self.supported_languages + + def _preferred_language(self, request: HttpRequest) -> str | None: + matched = get_language_from_request(request, check_path=False) + if not matched: + return None + return matched.split("-")[0].lower() diff --git a/mandelstudio/settings/base.py b/mandelstudio/settings/base.py index 29503b3..b17177a 100644 --- a/mandelstudio/settings/base.py +++ b/mandelstudio/settings/base.py @@ -119,6 +119,22 @@ if "django.middleware.locale.LocaleMiddleware" not in MIDDLEWARE: else: MIDDLEWARE.insert(0, "django.middleware.locale.LocaleMiddleware") +# First-visit language redirect (does not override manual language cookie choice). +if ( + "mandelstudio.middleware_language_redirect.FirstVisitLanguageRedirectMiddleware" + not in MIDDLEWARE +): + if "django.middleware.locale.LocaleMiddleware" in MIDDLEWARE: + idx = MIDDLEWARE.index("django.middleware.locale.LocaleMiddleware") + 1 + MIDDLEWARE.insert( + idx, + "mandelstudio.middleware_language_redirect.FirstVisitLanguageRedirectMiddleware", + ) + else: + MIDDLEWARE.append( + "mandelstudio.middleware_language_redirect.FirstVisitLanguageRedirectMiddleware" + ) + # Redirect production apex to `www` for a single canonical domain. if "mandelstudio.middleware.RedirectApexToWwwMiddleware" not in MIDDLEWARE: MIDDLEWARE.insert(0, "mandelstudio.middleware.RedirectApexToWwwMiddleware")