Django behavior on translatable URL patterns (i18n_patterns) when prefix_default_language=False is used.

Dear django developers,

I have reported a few days ago an issue in Django 4.2 regarding translatable URL patterns: 34515

Sarah Boyce (thank you Sarah) is working on a patch to fix the 404 issue pull/16797 (sorry I can add only 2 links as a new user)

But, for now, we do not agree on the correct behavior in the case described. So, as she suggested, I write to ask the community.

In brief:

  1. Using i18n_patterns with default_prefix_language = True with 3 different languages (perfectly OK):
  • /en/about/ goes to English page
  • /es/a-proposito/ goes to Spanish page
  • /fr/a-propos/ goes to French page
  1. Using i18n_patterns with default_prefix_language = False with 3 different languages and default to english (perfectly OK):
  • /about/ goes to English page (in 4.1 based on get_language_from_path, in 4.2 because my browser language cookie or header matches the default language)
  • /es/a-proposito/ goes to Spanish page
  • /fr/a-propos/ goes to French page
  1. Using i18n_patterns with default_prefix_language = False with 3 different languages and default to french in Django 4.1 (seems OK to me):
  • /en/about/ goes to English page
  • /es/a-proposito/ goes to Spanish page
  • /a-propos/ goes to French page
  1. Using i18n_patterns with default_prefix_language = False with 3 different languages and default to french in Django 4.2 (bug report):
  • /en/about/ goes to English page
  • /es/a-proposito/ goes to Spanish page
  • /a-propos/ raises HTTP 404 (because cookie or http header is not ‘fr’)
  1. Using i18n_patterns with default_prefix_language = False with 3 different languages and default to french with Sarah’s patch (what we do not agree on):
  • /en/about/ goes to English page
  • /es/a-proposito/ goes to Spanish page
  • /a-propos/ redirects to /en/about/ (because cookie or http header is ‘en’)

IMO, behavior 3 is good (because it reproduces 1 without prefix). Sarah’s and Nessita’s opinion is that behavior 5 is correct.
More details in the issues and PR.

Related topic: #1234

So, what do you think? Thank you!

Anthony

Thanks for summarising that Anthony.

To clarify, ^ this is what I was also recommending in the ticket (which you disagreed with) :slight_smile:

I’d like to hear from other folks thought because I very rarely have to setup Django with i18n.

Thank you for spelling this out Anthony, you’ve made a confusing issue quite clear :+1:

The thing that is nice about point 5 is that if you had something like an email signature, by putting in the default language url (in this case the French one) it would direct for everyone to their translated page of preference. I think that can be quite useful.

However, in reading this, I agree there is inconsistency between the behaviour of your default language url between default_prefix_language being True and False. In my opinion default_prefix_language shouldn’t implicitly change that navigation behavior.

So I think the redirect behaviour (of 5) should be a new feature that can be opted in/out from and that we should revert the change that was implemented in 4.2.

TLDR: (+1)

FWIW I think 3 is the expected behaviour.

I (too) agree 4 seems like an issue (to be fixed).

5 would leave you without a way to view the default language page once you had a language cookie set…

  1. Visit the set_language view
  2. Visit any prefixed URL — no redirect.
  3. Visit the default language page - Should be the same, no redirect.

(I’d be inclined to wontfix a request to change that, even with a flag, unless there’s a significant latent demand.)

:thinking:

Hello everyone!

First of all, yey for having this as a topic so we can discuss the (potential change of) behavior with the rest of the community. Thanks @aboutofpluto!

I still think that option (5) is the best one. While it’s true what @sarahboyce says, in that we have these two different behaviors:

(assuming a project setup as proposed in the first post)

  1. When prefix_default_language is set to True, http://127.0.0.1:8000/a-propos/ is a 404 no matter what language we report in the request (either by cookie or header), and no matter what setting the server side has. Only http://127.0.0.1:8000/fr/a-propos/ is valid and when requested, returns the French content ignoring the lang from the request. This is OK because the URL is clearly asking for the French version.

  2. When prefix_default_language is set to False, http://127.0.0.1:8000/fr/a-propos/ is a 404 (which is expected and OK). So then http://127.0.0.1:8000/a-propos/ (which is, to the eyes of the requester, a not-necessarily lang-specific page) should behave like other “non lang specific pages” and honor the lang defined in the request. Thus I think this is correct and expected:

$ curl -I -H 'Accept-Language: en' http://127.0.0.1:8000/a-propos/
HTTP/1.1 302 Found
...
Location: /en/about/

Note that a request to http://127.0.0.1:8000/a-propos/ without any cookie or header set should return the French page, which matches the current implementation in Sarah’s branch:

$ curl -I http://127.0.0.1:8000/a-propos/
HTTP/1.1 200 OK
...
Content-Language: fr

Cheers! Natalia.

I knew I’d seen this before somewhere :sweat_smile:

From: #29425 (Auto language redirect does not work if prefix_default_language=False in root URLConf) – Django

​Explanation from PR 6264, which added the extra test and behaviour:

The redirect should only happen on / URL, with prefix_default_language set to True (default behaviour).

Each other request (like /fr/whatever/) should not look at Accept-Language header at all,
just serve the page for the language requested in URL.

When the prefix_default_language is False the / url is the same as /en/
(with settings.LANGUAGE_CODE=en) so we should just ignore the Accept-Language here.

1 Like

In my previous post I wrote (and then deleted) “most sites that provide internationalization do 5” but since I couldn’t find an example, I deleted it.

I have now found an example, Wikipedia. Suppose you ask for soccer in Spanish, with a lang header set to en (as my browser sends):

https://www.wikipedia.org/wiki/Futbol

redirects to:

https://en.wikipedia.org/wiki/Association_football

Do you think it would make sense/possible to revisit that decision? IMHO that behavior could be confusing for end users.

IDK :man_shrugging: You tell me :smile:

My first question is, OK, assuming I have a different language cookie set, how do I view the default language? (Note the difference from the Wikipedia case, where they’re redirecting to a different domain).

Then I’d review #25933 — what was the background to PR6264?

If there are really no blockers then reopening #29425 as a New Feature, with the write up would be the way to go.

(I think :partying_face:)

hahaha, touché

You are spot on with this question. The short answer, IMHO, is that you can view the page in the default language navigating to /a-propos issuing a request without any lang preference.
My main point is that it may be preferable to honor the language preference in end-user requests to provide a smoother experience for them.

Relatedly, since I learnt about i18n_patterns only yesterday :nerd_face:, I couldn’t understand why a site that chooses to use prefix_default_language=False would “loose” the ability to navigate to the default language prefixed URL. That means, I fully support not requiring the /fr/ prefix, but why are we 404-ing when using it?

Lastly, when I’m presented with a page at /lang/path that I can’t understand, I (as an end-user) would “naturally” try the same page with /my-lang/path (which does not work either, so this may be a separate conversation/ticket).

In summary, seems like ticket/PR history, Sarah, Carlton and the OP are in favor of option 3, so by no means I’ll object to that approach!

Thanks everyone!

Hi Nathalia,

Thanks for the lively discussion :grinning:

You are spot on with this question. The short answer, IMHO, is that you can view the page in the default language navigating to /a-propos issuing a request without any lang preference.

I am sorry I cannot agree with such a statement. Who (except geeks like us) knows how to remove a cookie or change the Accept-Language header of their browser? Most people just except to see a page in french when they click on a “FR” link :point_up_2:. And that’s the choice of the website editor whether some routes (“/” for instance) are to be translated automatically or not (@sarahboyce, your idea of adding an option might be helpful here).

As @carltongibson said, what you propose do not let you reach the default language.

Regarding wikipedia, all right, it redirects you when you copy/paste the link, that happens to me too. But, then, I can click on “spanish” and I can have access to the spanish page. As @carltongibson also said (thanks pal :sweat_smile:), it works because www subdomain redirects you based on your browser but once you’re on the en subdomain or es subdomain, it ignores your 'Accept-Language' header. That is why https://es.wikipedia.org/wiki/Association_football is a 404, it does not redirect to https://en.wikipedia.org/wiki/Association_football.

So the www subdomain works almost as 5, except that it redirects you to another subdomain, and that’s a difference :slight_smile:. But then, the “language-prefixed-subdomains” do not use URL redirection at all. And if you want to change, you’re free to do so.

Once again, I am sorry to insist, when you activate i18n_patterns with default_prefix_language = False, I think 3 is the correct behavior. Because if you want use a cookie or header to detect the language, you just don’t use i18n_patterns but LocaleMiddleware alone. In that case I agree that Django should do its best to redirect the visitor.

In the end, I agree with that comment.

TTYL for sure!

I’m also in favor. We should:

@claudep Can I ask for you opinion?

See PR.

Hello,

Thank you all for your patience and goodwill!

2 Likes

I have soliton.

  1. Create context_processors.py
from django.urls import translate_url
from mainapp.settings import  LANGUAGE_CODE # mainapp its you app
def globals_settings(request):
    return {
        'redirect_to': translate_url(request.path, LANGUAGE_CODE)
    }
  1. add to settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
#.....
                'mainapp.context_processors.globals_settings', # add this row. "mainapp" - your 
#.....
            ],
        },
    },
]
  1. paste into your html
{% load i18n %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
<div class="switch">
    <label>
        {% trans "language" %}:
    </label>
    <form action="{% url 'set_language' %}" method="POST">
        {% csrf_token %}
        <input name="next" type="hidden" value="{{ redirect_to }}">
        <select class="btn_v1" style="display: block" name="language" onchange="this.form.submit()">
            {% for language in languages %}
                <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                    {{ language.code.upper }}
                </option>
            {% endfor %}
        </select>
    </form>
</div>
  1. and it will work correctly with
prefix_default_language=False