Opened 4 years ago
Last modified 5 months ago
#32886 assigned Bug
Translation: clash between language cookie and i18n_patterns URLs
| Reported by: | Seb G | Owned by: | Charlie Overton |
|---|---|---|---|
| Component: | Internationalization | Version: | 3.2 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Summary
When using i18n_patterns, language cookie is disregarded, even in non-i18n_patterns views.
Description
Given the following project urls.py file:
from django.conf.urls.i18n import i18n_patterns from django.contrib import admin from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), ] urlpatterns += i18n_patterns( path("app/", include("app.urls", namespace="app")), prefix_default_language=False, )
Browsing to /admin/ with a valid django_language cookie whose value is fr still returns an english-translated page.
This behavior seems to emerge from the LocaleMiddleware.process_request method:
def process_request(self, request): urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF) # This returns (True, False) because i18n_patterns are indeed used (albeit not on the requested route) i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf) # This returns "fr" as expected from the django_language cookie language = translation.get_language_from_request(request, check_path=i18n_patterns_used) # This returns None since, indeed, this route does not use i18n_patterns language_from_path = translation.get_language_from_path(request.path_info) # This test passes (somewhat unfortunately) if not language_from_path and i18n_patterns_used and not prefixed_default_language: # This gets executed and reverts language to default language (en) language = settings.LANGUAGE_CODE # "en" gets activated instead of "fr" translation.activate(language) request.LANGUAGE_CODE = translation.get_language()
Steps to reproduce
Using the provided test project
python manage.py migratepython manage.py createsuperuserpython manage.py runserver- Browse to
/admin/ - Login as superuser
- Edit your own user to set its language to "fr". Verify that you now have a
django_language=frcookie - Browse to any admin page => not translated to "fr"
Expected behavior
Cookie language should be respected on routes that do not use i18n_patterns. It is ok though that path language overrides cookie language on routes that do use i18n_patterns.
Attachments (1)
Change History (7)
by , 4 years ago
| Attachment: | test_project.zip added |
|---|
comment:1 by , 4 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:2 by , 4 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
I'll start working on this issue.
comment:3 by , 3 years ago
I've drafted something that I think will solve this issue:
https://github.com/django/django/pull/16142
comment:4 by , 5 months ago
Hi,
The bug still seems present in django 5.2. The code in version 5.2 is still identical to the code posted in the issue.
Was the PR reverted?
Thanks.
comment:6 by , 5 months ago
Quite logically, that revert recreated this bug.
A summary of my understanding of the issue:
The root cause seems to be that in an app where the following routes coexist:
- type A: a language-independant route. The language cannot be inferred from calling such routes.
- type B: a language-dependant route, with a prefix from
i18n_patterns, withprefix_default_language=False. The language can be inferred from calling such routes, and will supersede other sources of language.
LocaleMiddleware cannot tell those apart. With the code currently on master, LocaleMiddleware detects that at least one URL of type B exists in the app, and will assume that all routes of type A or B are of type B. It will always choose to infer the language.
If I understand correctly, this PR simply inverted the behaviour and caused #34515. Reverting that PR recreated this one.
The actual fix would be for LocaleMiddleware to be able to know whether the specific route is of type A or B, and based on that, choose to infer the language of the request or not.
As a workaround, we have implemented our own LocaleMiddleware which:
- uses logic adapted from
django-extensions'show_urlscommand to list all routes in all enabled languages - groups them by name to see whether some routes have more than one distinct pattern
- this tells us whether a route is translated (of type B), or not translated (of type A)
- if type B,
LocaleMiddlewareshould infer the language from the route. If type A, it shouldn't.
Thanks for the well-prepared ticket.