From a0a1424e90b82e98f6408e2a86152fcc0d71abf8 Mon Sep 17 00:00:00 2001 From: DEV-bot Date: Mon, 2 Mar 2026 00:31:42 +0000 Subject: [PATCH 1/2] fix(issue-225): honor Accept-Language fallback chain in locale resolver --- lobby/i18n.py | 12 ++++++++---- lobby/tests.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lobby/i18n.py b/lobby/i18n.py index 0a8ac89..7d8ad0f 100644 --- a/lobby/i18n.py +++ b/lobby/i18n.py @@ -37,10 +37,14 @@ def lobby_i18n_error_messages() -> dict: def resolve_locale(request: HttpRequest) -> str: default_locale, supported_locales = i18n_locale_config() - raw_accept_language = (request.META.get("HTTP_ACCEPT_LANGUAGE") or "").split(",", 1)[0] - raw_requested = raw_accept_language.split(";", 1)[0].strip().replace("_", "-").split("-", 1)[0].lower() - if raw_requested in supported_locales: - return raw_requested + accept_language = request.META.get("HTTP_ACCEPT_LANGUAGE") or "" + for candidate in accept_language.split(","): + tag, _sep, quality = candidate.partition(";") + if "q=0" in quality.replace(" ", ""): + continue + normalized = tag.strip().replace("_", "-").split("-", 1)[0].lower() + if normalized in supported_locales: + return normalized requested = (get_language_from_request(request) or "").replace("_", "-").split("-", 1)[0].lower() if requested in supported_locales: diff --git a/lobby/tests.py b/lobby/tests.py index b8ec79d..d89b4ec 100644 --- a/lobby/tests.py +++ b/lobby/tests.py @@ -1274,6 +1274,28 @@ class I18nResolverTests(TestCase): self.assertEqual(response.status_code, 400) self.assertEqual(resolve_locale(response.wsgi_request), "da") + def test_resolve_locale_uses_next_supported_language_when_primary_is_unsupported(self): + response = self.client.post( + reverse("lobby:join_session"), + data={"code": "", "nickname": "Luna"}, + content_type="application/json", + HTTP_ACCEPT_LANGUAGE="fr-FR, da;q=0.9, en;q=0.8", + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(resolve_locale(response.wsgi_request), "da") + + def test_resolve_locale_skips_q0_languages_and_falls_back_to_next_supported(self): + response = self.client.post( + reverse("lobby:join_session"), + data={"code": "", "nickname": "Luna"}, + content_type="application/json", + HTTP_ACCEPT_LANGUAGE="da;q=0, en;q=0.8", + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(resolve_locale(response.wsgi_request), "en") + def test_resolve_locale_defaults_to_en_when_header_missing(self): response = self.client.post( reverse("lobby:join_session"), From c34a52e83e8e695713732fd86db8c0d6841ae502 Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Mon, 2 Mar 2026 00:38:34 +0000 Subject: [PATCH 2/2] Fix Accept-Language q parsing in locale resolver --- lobby/i18n.py | 17 +++++++++++++++-- lobby/tests.py | 11 +++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lobby/i18n.py b/lobby/i18n.py index 7d8ad0f..82fdc0d 100644 --- a/lobby/i18n.py +++ b/lobby/i18n.py @@ -34,14 +34,27 @@ def lobby_i18n_error_messages() -> dict: return lobby_i18n_catalog().get("backend", {}).get("errors", {}) +def _quality_value(language_candidate: str) -> float | None: + for parameter in language_candidate.split(";")[1:]: + key, separator, value = parameter.partition("=") + if separator and key.strip().lower() == "q": + try: + return float(value.strip()) + except ValueError: + return None + return None + + def resolve_locale(request: HttpRequest) -> str: default_locale, supported_locales = i18n_locale_config() accept_language = request.META.get("HTTP_ACCEPT_LANGUAGE") or "" for candidate in accept_language.split(","): - tag, _sep, quality = candidate.partition(";") - if "q=0" in quality.replace(" ", ""): + quality = _quality_value(candidate) + if quality is not None and quality <= 0: continue + + tag = candidate.split(";", 1)[0] normalized = tag.strip().replace("_", "-").split("-", 1)[0].lower() if normalized in supported_locales: return normalized diff --git a/lobby/tests.py b/lobby/tests.py index d89b4ec..42e32d1 100644 --- a/lobby/tests.py +++ b/lobby/tests.py @@ -1296,6 +1296,17 @@ class I18nResolverTests(TestCase): self.assertEqual(response.status_code, 400) self.assertEqual(resolve_locale(response.wsgi_request), "en") + def test_resolve_locale_keeps_q08_languages_eligible_in_accept_language_loop(self): + response = self.client.post( + reverse("lobby:join_session"), + data={"code": "", "nickname": "Luna"}, + content_type="application/json", + HTTP_ACCEPT_LANGUAGE="da;q=0.8, en;q=0.9", + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(resolve_locale(response.wsgi_request), "da") + def test_resolve_locale_defaults_to_en_when_header_missing(self): response = self.client.post( reverse("lobby:join_session"),