Opened 4 years ago

Last modified 4 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

  1. python manage.py migrate
  2. python manage.py createsuperuser
  3. python manage.py runserver
  4. Browse to /admin/
  5. Login as superuser
  6. Edit your own user to set its language to "fr". Verify that you now have a django_language=fr cookie
  7. 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)

test_project.zip (20.7 KB ) - added by Seb G 4 years ago.

Download all attachments as: .zip

Change History (7)

by Seb G, 4 years ago

Attachment: test_project.zip added

comment:1 by Claude Paroz, 4 years ago

Triage Stage: UnreviewedAccepted

Thanks for the well-prepared ticket.

comment:2 by Charlie Overton, 4 years ago

Owner: changed from nobody to Charlie Overton
Status: newassigned

I'll start working on this issue.

comment:3 by sergioisidoro, 3 years ago

I've drafted something that I think will solve this issue:
https://github.com/django/django/pull/16142

comment:4 by Foucauld Degeorges, 4 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 Foucauld Degeorges, 4 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, with prefix_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_urls command 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, LocaleMiddleware should infer the language from the route. If type A, it shouldn't.

That solution works for us because we only have two languages. It probably doesn't scale very well to many languages.

Last edited 4 months ago by Foucauld Degeorges (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top