Language middleware: user and Accept-Language _only_

(as a matter of taste: I prefer to keep this as simple as possible)

See #161
This commit is contained in:
Klaas van Schelven
2025-08-28 12:02:18 +02:00
parent 2b5fb1bf67
commit a7abde68a2
2 changed files with 30 additions and 21 deletions

View File

@@ -5,6 +5,10 @@ from django.contrib.auth.decorators import login_required
from django.db import connection
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.translation import get_supported_language_variant
from django.utils.translation.trans_real import parse_accept_lang_header
from django.utils import translation
performance_logger = logging.getLogger("bugsink.performance.views")
@@ -130,30 +134,33 @@ class SetRemoteAddrMiddleware:
return self.get_response(request)
def language_from_accept_language(request):
"""
Pick a language using ONLY the Accept-Language header. Ignores URL prefixes, session, and cookies. I prefer to have
as little "magic" in the language selection as possible, and I _know_ we don't do anything with paths, so I'd rather
not have such code invoked at all (at the cost of reimplementing some of Django's logic here).
"""
header = request.META.get("HTTP_ACCEPT_LANGUAGE", "")
for lang_code, _q in parse_accept_lang_header(header):
try:
# strict=False lets country variants match (e.g. 'es-CO' for 'es')
return get_supported_language_variant(lang_code, strict=False)
except LookupError:
continue
return settings.LANGUAGE_CODE
class UserLanguageMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated and request.user.language != "auto":
normalized_explicit_lang = get_supported_language_variant(request.user.language, strict=False)
translation.activate(normalized_explicit_lang)
else:
auto_lang = language_from_accept_language(request)
translation.activate(auto_lang)
response = self.get_response(request)
if (request.user.is_authenticated and
hasattr(request.user, 'language')):
user_language = request.user.language
current_cookie = request.COOKIES.get('django_language')
if user_language == 'auto':
if current_cookie is not None:
response.delete_cookie('django_language')
else:
if current_cookie != user_language:
response.set_cookie(
'django_language',
user_language,
max_age=365 * 24 * 60 * 60,
httponly=False,
samesite='Lax'
)
return response

View File

@@ -97,12 +97,14 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'verbose_csrf_middleware.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'bugsink.middleware.LoginRequiredMiddleware',
# note on ordering: we need request.user, so after AuthenticationMiddleware; and we're not tied to "before
# CommonMiddleware" as django.middleware.locale.LocaleMiddleware is, because we don't do path-related stuff.
'bugsink.middleware.UserLanguageMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',