Django i18n default language without path prefixes
Asked Answered
C

4

15

I have an existing multi-lingual Django app that I'm porting to Django 1.4. I18n support is currently based on some ugly hacks, I'd like to make it use Django's built-in i18n modules.

One constraint is that I don't want to change the urls that are already in place. This site has been active for a while and there are external links to it that I dont' want to break or redirect. The urls scheme works like this: English content is at the root of the site, while other languages use prefixes in the domain name:

English urls:

/
/articles/
/suggestions/

Spanish urls:

/es/
/es/articulos/
/es/sugerencias/

I've got the translated pages working with Django 1.4's i18n modules, but it really, really, really wants to put all the English urls under /en/. I've tried a few different hacks, including defining non-internationalized urls for the English version:

def build(callback):
  return callback('',
    url(_(r'^$'), home.index, name="home"),
    url(_(r'^articles/$'), content.article_list, name='article_list'),
    url(_(r'^suggestions/$'), suggestions.suggestions, name='suggestions'),
  )

urlpatterns = build(patterns)
urlpatterns += build(i18n_patterns)

This makes the urls resolve properly, but the {% url %} tag to do reverse resolution doesn't work.

What's the best way to accomplish non-prefixed English-language urls?

Came answered 29/11, 2012 at 23:21 Comment(4)
just a question: is your _ the non-lazy version fo gettext? i.e. from django.utils.translation import ugettext as _Din
No, it's the lazy version: django.utils.translation.ugettext_lazyCame
can you try it with the non-lazy version and see if it works?Din
Non-lazy didn't work either. I ended up writing some functions to automatically generate the prefixed, translated urls from the non-prefixed, English patterns. Then I translate the urls for each language in my .po files, and use something like [% trans "/articles/" %} instead of the {% url %} tag. Not pretty, but it works.Came
F
11

UPDATE: Read answer bellow, Django 1.10 supports it natively

I faced this problem and solved this way:

  • Created an alternative i18n_patterns that do not prefix the site main language (defined in settings.LANGUAGE_CODE).

  • Created an alternative middleware that only uses the URL prefixes language to activate the current language.

I didn't see any side-effect using this technique.

The code:

# coding: utf-8
"""
Cauê Thenório - cauelt(at)gmail.com

This snippet makes Django do not create URL languages prefix (i.e. /en/)
for the default language (settings.LANGUAGE_CODE).

It also provides a middleware that activates the language based only on the URL.
This middleware ignores user session data, cookie and 'Accept-Language' HTTP header.

Your urls will be like:

In your default language (english in example):

    /contact
    /news
    /articles

In another languages (portuguese in example):

    /pt/contato
    /pt/noticias
    /pt/artigos

To use it, use the 'simple_i18n_patterns' instead the 'i18n_patterns'
in your urls.py:

    from this_sinppet import simple_i18n_patterns as i18n_patterns

And use the 'SimpleLocaleMiddleware' instead the Django's 'LocaleMiddleware'
in your settings.py:

    MIDDLEWARE_CLASSES = (
    ...
        'this_snippet.SimpleLocaleMiddleware'
    ...
    )

Works on Django >=1.4
"""

import re

from django.conf import settings
from django.conf.urls import patterns
from django.core.urlresolvers import LocaleRegexURLResolver
from django.middleware.locale import LocaleMiddleware
from django.utils.translation import get_language, get_language_from_path
from django.utils import translation


class SimpleLocaleMiddleware(LocaleMiddleware):

    def process_request(self, request):

        if self.is_language_prefix_patterns_used():
            lang_code = (get_language_from_path(request.path_info) or
                         settings.LANGUAGE_CODE)

            translation.activate(lang_code)
            request.LANGUAGE_CODE = translation.get_language()


class NoPrefixLocaleRegexURLResolver(LocaleRegexURLResolver):

    @property
    def regex(self):
        language_code = get_language()

        if language_code not in self._regex_dict:
            regex_compiled = (re.compile('', re.UNICODE)
                              if language_code == settings.LANGUAGE_CODE
                              else re.compile('^%s/' % language_code, re.UNICODE))

            self._regex_dict[language_code] = regex_compiled
        return self._regex_dict[language_code]


def simple_i18n_patterns(prefix, *args):
    """
    Adds the language code prefix to every URL pattern within this
    function, when the language not is the main language.
    This may only be used in the root URLconf, not in an included URLconf.

    """
    pattern_list = patterns(prefix, *args)
    if not settings.USE_I18N:
        return pattern_list
    return [NoPrefixLocaleRegexURLResolver(pattern_list)]

The code above is available on: https://gist.github.com/cauethenorio/4948177

Fineman answered 13/2, 2013 at 23:17 Comment(7)
from django.conf.urls import patterns doesn't existLidialidice
@Lidialidice for newer django versions you should use the native solution: docs.djangoproject.com/en/1.10/topics/i18n/translation/…Hamate
Thanks, completely overlooked the kwarg of i18n_patterns(*urls, prefix_default_language=True)Lidialidice
@CauêThenório you sir are a genius, thanks for coming back and and giving the hint with prefix_default_languageSiouan
This code does not work in recent versions of Django. Has anyone adapted it to Django 3.x or 4.x?Phyllisphylloclade
@PabloCastellano you should use Django native i18n_patterns function with prefix_default_language=False as specified in the documentation: docs.djangoproject.com/en/4.0/topics/i18n/translation/…Hamate
The problem with setting prefix_default_language=False is that yo lose LocaleMiddleware's feature of automatically redirecting users to their preferred language. I have just posted a workaround for that in django 3Phyllisphylloclade
T
16

I used solid-i18n-url to solve similar problem: https://github.com/st4lk/django-solid-i18n-urls

Nice description how it works located here: http://www.lexev.org/en/2013/multilanguage-site-django-without-redirects/

Thermometer answered 17/6, 2013 at 13:7 Comment(0)
F
13

Django 1.10 supports it natively. As they say in the doc:

i18n_patterns(*urls, prefix_default_language=True)

This function can be used in a root URLconf and Django will automatically prepend the current active language code to all url patterns defined within i18n_patterns().

Setting prefix_default_language to False removes the prefix from the default language (LANGUAGE_CODE). This can be useful when adding translations to existing site so that the current URLs won’t change.

Source: https://docs.djangoproject.com/en/1.10/topics/i18n/translation/#language-prefix-in-url-patterns

Fineman answered 24/6, 2016 at 17:29 Comment(3)
The problem is that prefix_default_language prefixes all languages, including the default one which is English in his case, so he would end up having urls like /en/, /en/articles/ and /en/suggestions/ which is not what he is asking for.Phyllisphylloclade
@PabloCastellano specifying prefix_default_language=False will remove the prefix for the default languageHamate
By setting prefix_default_language=False you lose the ability to redirect to the prefixed page. I am going to share the solution I found for my case.Phyllisphylloclade
F
11

UPDATE: Read answer bellow, Django 1.10 supports it natively

I faced this problem and solved this way:

  • Created an alternative i18n_patterns that do not prefix the site main language (defined in settings.LANGUAGE_CODE).

  • Created an alternative middleware that only uses the URL prefixes language to activate the current language.

I didn't see any side-effect using this technique.

The code:

# coding: utf-8
"""
Cauê Thenório - cauelt(at)gmail.com

This snippet makes Django do not create URL languages prefix (i.e. /en/)
for the default language (settings.LANGUAGE_CODE).

It also provides a middleware that activates the language based only on the URL.
This middleware ignores user session data, cookie and 'Accept-Language' HTTP header.

Your urls will be like:

In your default language (english in example):

    /contact
    /news
    /articles

In another languages (portuguese in example):

    /pt/contato
    /pt/noticias
    /pt/artigos

To use it, use the 'simple_i18n_patterns' instead the 'i18n_patterns'
in your urls.py:

    from this_sinppet import simple_i18n_patterns as i18n_patterns

And use the 'SimpleLocaleMiddleware' instead the Django's 'LocaleMiddleware'
in your settings.py:

    MIDDLEWARE_CLASSES = (
    ...
        'this_snippet.SimpleLocaleMiddleware'
    ...
    )

Works on Django >=1.4
"""

import re

from django.conf import settings
from django.conf.urls import patterns
from django.core.urlresolvers import LocaleRegexURLResolver
from django.middleware.locale import LocaleMiddleware
from django.utils.translation import get_language, get_language_from_path
from django.utils import translation


class SimpleLocaleMiddleware(LocaleMiddleware):

    def process_request(self, request):

        if self.is_language_prefix_patterns_used():
            lang_code = (get_language_from_path(request.path_info) or
                         settings.LANGUAGE_CODE)

            translation.activate(lang_code)
            request.LANGUAGE_CODE = translation.get_language()


class NoPrefixLocaleRegexURLResolver(LocaleRegexURLResolver):

    @property
    def regex(self):
        language_code = get_language()

        if language_code not in self._regex_dict:
            regex_compiled = (re.compile('', re.UNICODE)
                              if language_code == settings.LANGUAGE_CODE
                              else re.compile('^%s/' % language_code, re.UNICODE))

            self._regex_dict[language_code] = regex_compiled
        return self._regex_dict[language_code]


def simple_i18n_patterns(prefix, *args):
    """
    Adds the language code prefix to every URL pattern within this
    function, when the language not is the main language.
    This may only be used in the root URLconf, not in an included URLconf.

    """
    pattern_list = patterns(prefix, *args)
    if not settings.USE_I18N:
        return pattern_list
    return [NoPrefixLocaleRegexURLResolver(pattern_list)]

The code above is available on: https://gist.github.com/cauethenorio/4948177

Fineman answered 13/2, 2013 at 23:17 Comment(7)
from django.conf.urls import patterns doesn't existLidialidice
@Lidialidice for newer django versions you should use the native solution: docs.djangoproject.com/en/1.10/topics/i18n/translation/…Hamate
Thanks, completely overlooked the kwarg of i18n_patterns(*urls, prefix_default_language=True)Lidialidice
@CauêThenório you sir are a genius, thanks for coming back and and giving the hint with prefix_default_languageSiouan
This code does not work in recent versions of Django. Has anyone adapted it to Django 3.x or 4.x?Phyllisphylloclade
@PabloCastellano you should use Django native i18n_patterns function with prefix_default_language=False as specified in the documentation: docs.djangoproject.com/en/4.0/topics/i18n/translation/…Hamate
The problem with setting prefix_default_language=False is that yo lose LocaleMiddleware's feature of automatically redirecting users to their preferred language. I have just posted a workaround for that in django 3Phyllisphylloclade
P
0

Since Django 1.10 there is a prefix_default_language parameter you can specify in i18n_patterns().

OP says that he doesn't want to use a prefix for the default language, so he sets prefix_default_language=False. However by doing that you are losing one nice feature provided by the LocaleMiddleware which is automatically redirecting the user to the version of the website in his preferred language. You only have this behavior when you set prefix_default_language=True.

In my case I wanted to preserve this functionality. Here I'm sharing a workaround I found to this problem, highly inspired by the mentioned django-solid-i18n-urls module and updated to newer Django. Tested and works on Django 3.2.

# urlresolvers.py

from django.conf import settings
from django.urls import LocalePrefixPattern, URLResolver
from django.utils.translation import get_language


def solid_i18n_patterns(*urls, prefix_default_language=True):
    """
    Same as i18n_patterns but uses SolidLocalePrefixPattern instead of LocalePrefixPattern
    """
    if not settings.USE_I18N:
        return list(urls)
    return [
        URLResolver(
            SolidLocalePrefixPattern(prefix_default_language=prefix_default_language),
            list(urls),
        )
    ]


class SolidLocalePrefixPattern(LocalePrefixPattern):
    """
    Based on django-solid-i18n-urls.
    """

    @property
    def language_prefix(self):
        language_code = get_language() or settings.LANGUAGE_CODE
        if language_code == settings.LANGUAGE_CODE:
            return ""
        else:
            return "%s/" % language_code

        # Original:
        """
        if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
            return ""
        else:
            return "%s/" % language_code
        """
# urls.py

from .urlresolvers import solid_i18n_patterns

# And replace i18n_patterns by solid_i18n_patterns
Phyllisphylloclade answered 8/7, 2022 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.