#34515 closed Bug (fixed)
Translatable URL patterns raise 404 for non-English default language when prefix_default_language=False is used.
Reported by: | ab | Owned by: | Sarah Boyce |
---|---|---|---|
Component: | Internationalization | Version: | 4.2 |
Severity: | Release blocker | Keywords: | internationalization, i18n, prefix_default_language, i18n_patterns |
Cc: | Sarah Boyce, Claude Paroz, sergioisidoro | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
A simple django project with instruction to replicate the bug can be found here:
github repo
In brief: prefix_default_language = False
raises HTTP 404 for the default unprefixed pages if LANGUAGE_CODE
is not "en".
I think the problem is that the function get_language_from_path
in django/utils/translation/trans_real.py
returns None
in case of failure instead of LANGUAGE_CODE
: diff in 4.2
Consequently, other mechanisms are used to get the language (cookies or headers) that do not work neither.
Related issue with my last comment adding some extra context: https://code.djangoproject.com/ticket/34455
It is the first time I contribute to django, I hope the bug report is OK. I am also willing to write the patch and test if required.
Change History (19)
follow-up: 4 comment:1 by , 2 years ago
comment:2 by , 2 years ago
Cc: | added; removed |
---|---|
Component: | Core (Other) → Internationalization |
Severity: | Normal → Release blocker |
Summary: | prefix_default_language=False raises HTTP 404 for default language if LANGUAGE_CODE != 'en' → Translatable URL patterns raise 404 for non-English default language when prefix_default_language=False is used. |
Triage Stage: | Unreviewed → Accepted |
Thanks for the report. The use of URL patterns marked as translatable is crucial for this bug.
Regression in 94e7f471c4edef845a4fe5e3160132997b4cca81.
Reproduced at c24cd6575f948661fa0ed8b27b79098610dc3ccc.
comment:3 by , 2 years ago
Cc: | removed |
---|
follow-up: 5 comment:4 by , 2 years ago
Replying to ab:
Expected behavior: django 4.2 documentation
LocaleMiddleware tries to determine the user’s language preference by following this algorithm:
- First, it looks for the language prefix in the requested URL. This is only performed when you are using the i18n_patterns function in your root URLconf. See Internationalization: in URL patterns for more information about the language prefix and how to internationalize URL patterns.
- Failing that, it looks for a cookie. The name of the cookie used is set by the LANGUAGE_COOKIE_NAME setting. (The default name is django_language.)
- Failing that, it looks at the Accept-Language HTTP header. This header is sent by your browser and tells the server which language(s) you prefer, in order by priority. Django tries each language in the header until it finds one with available translations.
- Failing that, it uses the global LANGUAGE_CODE setting.
IMO it still works that way. However, in Django 4.2 get_language_from_request()
returns the language from a request (en
for me) which is activated and the default path about/
is no longer translated to the a-propos/
. This is definitely a change from the previous behavior.
comment:5 by , 2 years ago
Replying to Mariusz Felisiak:
IMO it still works that way. However, in Django 4.2
get_language_from_request()
returns the language from a request (en
for me) which is activated and the default pathabout/
is no longer translated to thea-propos/
. This is definitely a change from the previous behavior.
Thank you Mariusz for the quick reaction.
I agree it still globally works that way, nevertheless, in the case I describe, when django looks for the language prefix in the requested URL and there is not language prefix, I would expect django to return "fr", not to go to the next steps of the algorithm. Because I want prefix_default_language = False
to take precedence on cookies or headers. Does it make sense?
I need to add that I use translate_url
to build the links in my templates.
Consequently, my URLs are translated in the template only (hence the 404).
So you're right about the default path not being translated anymore.
comment:6 by , 2 years ago
Cc: | added |
---|
comment:7 by , 2 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:8 by , 2 years ago
Owner: | changed from | to
---|
I have a PR with what I think the issue is, but not confident https://github.com/django/django/pull/16797
@ab I think what you're saying makes sense
comment:9 by , 2 years ago
Has patch: | set |
---|
comment:10 by , 2 years ago
I agree it still globally works that way, nevertheless, in the case I describe, when django looks for the language prefix in the requested URL and there is not language prefix, I would expect django to return "fr", not to go to the next steps of the algorithm. Because I want prefix_default_language = False to take precedence on cookies or headers. Does it make sense?
My 2¢: Is ignoring the process for determining the language the least surprising choice here though? It all depends on whether no-prefix URL should refer to a user setting or the site's default language. I mean imho navigating to a URL I might expect it to show the language I chose 🤷♂️
comment:11 by , 2 years ago
Keywords: | i18n_patterns added |
---|
@Sarah: yes, it is the same problem. After investigating the code, the change in behavior is linked to the fact that get_language_from_path
returns None
when the url is not prefixed. So, the cookie is used or the Accept-Language
header sent by the browser. In my case, I think it is the HTTP header.
@David: thanks for your contribution, but I do not fully agree. If prefix_default_url
is True
, the language is correctly detected by django based on the URL. If I set prefix_default_url
to False
I expect the same behavior for the default language without prefix. When I decide do use i18n_patterns
at least (I have just added this tag to the ticket).
When i18n_patterns
is not used, I agree with you.
So the problem might come from i18n_patterns not calling/handling correctly the calls to the new get_language_*
functions.
comment:12 by , 2 years ago
@sarah: I'll test your patch because your edits might solve the problem with HTTP headers too. Thanks!
comment:13 by , 2 years ago
Just to keep track of the current work on this issue, there is a discussion about how django should behave here: https://github.com/django/django/pull/16797#issuecomment-1524958085
As suggested by Sarah, I'll post to django-developers for a wider range of opinions.
comment:19 by , 7 weeks ago
Quite logically, this fix recreated the previous bug https://code.djangoproject.com/ticket/32886.
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, the PR that fixed that other bug, simply inverted the behaviour and created this bug here. Reverting that PR recreated the old bug.
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.
edit: I'll post this again on the other bug report, that's the one that's still present.
Expected behavior: django 4.2 documentation
LocaleMiddleware tries to determine the user’s language preference by following this algorithm: