Skip to content

Commit

Permalink
[4.2.x] Fixed #34515 -- Made LocaleMiddleware prefer language from pa…
Browse files Browse the repository at this point in the history
…ths when i18n patterns are used.

Regression in 94e7f47.

This reverts commit 94e7f47
(refs #34069) and
partly reverts commit 3b47283.

Thanks Anthony Baillard for the report.

Co-Authored-By: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>

Backport of 0e444e8 from main
  • Loading branch information
felixxm committed May 2, 2023
1 parent 4f343a1 commit f200d83
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 48 deletions.
33 changes: 12 additions & 21 deletions django/middleware/locale.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,28 @@ 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, _ = is_language_prefix_patterns_used(urlconf)
(
i18n_patterns_used,
prefixed_default_language,
) = is_language_prefix_patterns_used(urlconf)
language = translation.get_language_from_request(
request, check_path=i18n_patterns_used
)
if not language:
language = self.get_fallback_language(request)

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
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,
Expand All @@ -57,7 +48,7 @@ def process_response(self, request, response):
response.status_code == 404
and not language_from_path
and i18n_patterns_used
and (prefixed_default_language or language_from_request)
and prefixed_default_language
):
# Maybe the language code is missing in the URL? Try adding the
# language prefix and redirecting to that URL.
Expand Down
5 changes: 2 additions & 3 deletions django/urls/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from django.utils.functional import cached_property
from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
from django.utils.regex_helper import _lazy_re_compile, normalize
from django.utils.translation import get_language, get_supported_language_variant
from django.utils.translation import get_language

from .converters import get_converter
from .exceptions import NoReverseMatch, Resolver404
Expand Down Expand Up @@ -351,8 +351,7 @@ def regex(self):
@property
def language_prefix(self):
language_code = get_language() or settings.LANGUAGE_CODE
default_language = get_supported_language_variant(settings.LANGUAGE_CODE)
if language_code == default_language and not self.prefix_default_language:
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
return ""
else:
return "%s/" % language_code
Expand Down
1 change: 0 additions & 1 deletion django/utils/translation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"get_language_from_request",
"get_language_info",
"get_language_bidi",
"get_supported_language_variant",
"check_for_language",
"to_language",
"to_locale",
Expand Down
2 changes: 1 addition & 1 deletion django/utils/translation/trans_null.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def check_for_language(x):


def get_language_from_request(request, check_path=False):
return None
return settings.LANGUAGE_CODE


def get_language_from_path(request):
Expand Down
6 changes: 5 additions & 1 deletion django/utils/translation/trans_real.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,11 @@ def get_language_from_request(request, check_path=False):
return get_supported_language_variant(accept_lang)
except LookupError:
continue
return None

try:
return get_supported_language_variant(settings.LANGUAGE_CODE)
except LookupError:
return settings.LANGUAGE_CODE


@functools.lru_cache(maxsize=1000)
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/4.2.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ Bugfixes
``prefix_default_language`` argument when a fallback language of the default
language was used (:ticket:`34455`).

* Fixed a regression in Django 4.2 where translated URLs of the default
language from ``i18n_patterns()`` with ``prefix_default_language`` set to
``False`` raised 404 errors for a request with a different language
(:ticket:`34515`).

* Fixed a regression in Django 4.2 where creating copies and deep copies of
``HttpRequest``, ``HttpResponse``, and their subclasses didn't always work
correctly (:ticket:`34482`, :ticket:`34484`).
Expand Down
4 changes: 0 additions & 4 deletions docs/releases/4.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,6 @@ 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
~~~~~~~

Expand Down
21 changes: 21 additions & 0 deletions tests/i18n/patterns/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,27 @@ def test_nl_path(self):
self.assertEqual(response.context["LANGUAGE_CODE"], "nl")


@override_settings(ROOT_URLCONF="i18n.urls_default_unprefixed", LANGUAGE_CODE="nl")
class URLPrefixedFalseTranslatedTests(URLTestCaseBase):
def test_translated_path_unprefixed_language_other_than_accepted_header(self):
response = self.client.get("/gebruikers/", headers={"accept-language": "en"})
self.assertEqual(response.status_code, 200)

def test_translated_path_unprefixed_language_other_than_cookie_language(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "en"})
response = self.client.get("/gebruikers/")
self.assertEqual(response.status_code, 200)

def test_translated_path_prefixed_language_other_than_accepted_header(self):
response = self.client.get("/en/users/", headers={"accept-language": "nl"})
self.assertEqual(response.status_code, 200)

def test_translated_path_prefixed_language_other_than_cookie_language(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "nl"})
response = self.client.get("/en/users/")
self.assertEqual(response.status_code, 200)


class URLRedirectWithScriptAliasTests(URLTestCaseBase):
"""
#21579 - LocaleMiddleware should respect the script prefix.
Expand Down
23 changes: 6 additions & 17 deletions tests/i18n/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2142,22 +2142,8 @@ def test_other_lang_with_prefix(self):
response = self.client.get("/fr/simple/")
self.assertEqual(response.content, b"Oui")

def test_unprefixed_language_with_accept_language(self):
"""'Accept-Language' is respected."""
response = self.client.get("/simple/", headers={"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/", headers={"accept-language": "fi"})
self.assertEqual(response.content, b"Yes")
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fi"})
response = self.client.get("/simple/")
def test_unprefixed_language_other_than_accept_language(self):
response = self.client.get("/simple/", HTTP_ACCEPT_LANGUAGE="fr")
self.assertEqual(response.content, b"Yes")

def test_page_with_dash(self):
Expand Down Expand Up @@ -2233,7 +2219,10 @@ def test_get_language_from_request(self):

def test_get_language_from_request_null(self):
lang = trans_null.get_language_from_request(None)
self.assertEqual(lang, None)
self.assertEqual(lang, "en")
with override_settings(LANGUAGE_CODE="de"):
lang = trans_null.get_language_from_request(None)
self.assertEqual(lang, "de")

def test_specific_language_codes(self):
# issue 11915
Expand Down
1 change: 1 addition & 0 deletions tests/i18n/urls_default_unprefixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
re_path(r"^(?P<arg>[\w-]+)-page", lambda request, **arg: HttpResponse(_("Yes"))),
path("simple/", lambda r: HttpResponse(_("Yes"))),
re_path(r"^(.+)/(.+)/$", lambda *args: HttpResponse()),
re_path(_(r"^users/$"), lambda *args: HttpResponse(), name="users"),
prefix_default_language=False,
)

0 comments on commit f200d83

Please sign in to comment.