diff --git a/django/middleware/locale.py b/django/middleware/locale.py index 71db230da2..bc1d862574 100644 --- a/django/middleware/locale.py +++ b/django/middleware/locale.py @@ -16,28 +16,37 @@ class LocaleMiddleware(MiddlewareMixin): response_redirect_class = HttpResponseRedirect + def get_fallback_language(self, request): + """ + Return the fallback language for the current request based on the + settings. If LANGUAGE_CODE is a variant not included in the supported + languages, get_fallback_language() will try to fallback to a supported + generic variant. + + Can be overridden to have a fallback language depending on the request, + e.g. based on top level domain. + """ + try: + return translation.get_supported_language_variant(settings.LANGUAGE_CODE) + except LookupError: + return settings.LANGUAGE_CODE + def process_request(self, request): urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) - ( - i18n_patterns_used, - prefixed_default_language, - ) = is_language_prefix_patterns_used(urlconf) + i18n_patterns_used, _ = is_language_prefix_patterns_used(urlconf) language = translation.get_language_from_request( request, check_path=i18n_patterns_used ) - language_from_path = translation.get_language_from_path(request.path_info) - if ( - not language_from_path - and i18n_patterns_used - and not prefixed_default_language - ): - language = settings.LANGUAGE_CODE + if not language: + language = self.get_fallback_language(request) + translation.activate(language) request.LANGUAGE_CODE = translation.get_language() def process_response(self, request, response): language = translation.get_language() language_from_path = translation.get_language_from_path(request.path_info) + language_from_request = translation.get_language_from_request(request) urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) ( i18n_patterns_used, @@ -48,7 +57,7 @@ class LocaleMiddleware(MiddlewareMixin): response.status_code == 404 and not language_from_path and i18n_patterns_used - and prefixed_default_language + and (prefixed_default_language or language_from_request) ): # Maybe the language code is missing in the URL? Try adding the # language prefix and redirecting to that URL. diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index c8bfb1256e..595a705e2b 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -53,7 +53,7 @@ def check_for_language(x): def get_language_from_request(request, check_path=False): - return settings.LANGUAGE_CODE + return None def get_language_from_path(request): diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 423f30eaba..595a9ec2e4 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -578,11 +578,7 @@ def get_language_from_request(request, check_path=False): return get_supported_language_variant(accept_lang) except LookupError: continue - - try: - return get_supported_language_variant(settings.LANGUAGE_CODE) - except LookupError: - return settings.LANGUAGE_CODE + return None @functools.lru_cache(maxsize=1000) diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index c3ddb7c7af..6a87a5bbe9 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -186,6 +186,10 @@ Internationalization * Added support and translations for the Central Kurdish (Sorani) language. +* The :class:`~django.middleware.locale.LocaleMiddleware` now respects a + language from the request when :func:`~django.conf.urls.i18n.i18n_patterns` + is used with the ``prefix_default_language`` argument set to ``False``. + Logging ~~~~~~~ diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 53585a1871..1fec6009a5 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -2137,8 +2137,22 @@ class UnprefixedDefaultLanguageTests(SimpleTestCase): response = self.client.get("/fr/simple/") self.assertEqual(response.content, b"Oui") - def test_unprefixed_language_other_than_accept_language(self): + def test_unprefixed_language_with_accept_language(self): + """'Accept-Language' is respected.""" response = self.client.get("/simple/", HTTP_ACCEPT_LANGUAGE="fr") + self.assertRedirects(response, "/fr/simple/") + + def test_unprefixed_language_with_cookie_language(self): + """A language set in the cookies is respected.""" + self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"}) + response = self.client.get("/simple/") + self.assertRedirects(response, "/fr/simple/") + + def test_unprefixed_language_with_non_valid_language(self): + response = self.client.get("/simple/", HTTP_ACCEPT_LANGUAGE="fi") + self.assertEqual(response.content, b"Yes") + self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fi"}) + response = self.client.get("/simple/") self.assertEqual(response.content, b"Yes") def test_page_with_dash(self): @@ -2214,10 +2228,7 @@ class CountrySpecificLanguageTests(SimpleTestCase): def test_get_language_from_request_null(self): lang = trans_null.get_language_from_request(None) - self.assertEqual(lang, "en") - with override_settings(LANGUAGE_CODE="de"): - lang = trans_null.get_language_from_request(None) - self.assertEqual(lang, "de") + self.assertEqual(lang, None) def test_specific_language_codes(self): # issue 11915