Commit 839a955d authored by Krzysztof Urbaniak's avatar Krzysztof Urbaniak Committed by Tim Graham
Browse files

Fixed #25933 -- Allowed an unprefixed default language in i18n_patterns().

parent 4b129ac8
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
from django.conf import settings
from django.conf.urls import url
from django.urls import LocaleRegexURLResolver
from django.urls import LocaleRegexURLResolver, get_resolver
from django.utils import lru_cache
from django.views.i18n import set_language


def i18n_patterns(*urls):
def i18n_patterns(*urls, **kwargs):
    """
    Adds the language code prefix to every URL pattern within this
    function. This may only be used in the root URLconf, not in an included
@@ -12,7 +13,23 @@ def i18n_patterns(*urls):
    """
    if not settings.USE_I18N:
        return urls
    return [LocaleRegexURLResolver(list(urls))]
    prefix_default_language = kwargs.pop('prefix_default_language', True)
    assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs
    return [LocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]


@lru_cache.lru_cache(maxsize=None)
def is_language_prefix_patterns_used(urlconf):
    """
    Return a tuple of two booleans: (
        `True` if LocaleRegexURLResolver` is used in the `urlconf`,
        `True` if the default language should be prefixed
    )
    """
    for url_pattern in get_resolver(urlconf).url_patterns:
        if isinstance(url_pattern, LocaleRegexURLResolver):
            return True, url_pattern.prefix_default_language
    return False, False


urlpatterns = [
+14 −22
Original line number Diff line number Diff line
"This is the locale selecting middleware that will look at accept headers"

from django.conf import settings
from django.conf.urls.i18n import is_language_prefix_patterns_used
from django.http import HttpResponseRedirect
from django.urls import (
    LocaleRegexURLResolver, get_resolver, get_script_prefix, is_valid_path,
)
from django.utils import lru_cache, translation
from django.urls import get_script_prefix, is_valid_path
from django.utils import translation
from django.utils.cache import patch_vary_headers


@@ -21,9 +20,10 @@ class LocaleMiddleware(object):

    def process_request(self, request):
        urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
        language = translation.get_language_from_request(
            request, check_path=self.is_language_prefix_patterns_used(urlconf)
        )
        i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
        language = translation.get_language_from_request(request, check_path=i18n_patterns_used)
        if not language and i18n_patterns_used and not prefixed_default_language:
            language = settings.LANGUAGE_CODE
        translation.activate(language)
        request.LANGUAGE_CODE = translation.get_language()

@@ -31,8 +31,12 @@ class LocaleMiddleware(object):
        language = translation.get_language()
        language_from_path = translation.get_language_from_path(request.path_info)
        urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
        if (response.status_code == 404 and not language_from_path
                and self.is_language_prefix_patterns_used(urlconf)):
        i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)

        if not language_from_path and i18n_patterns_used and not prefixed_default_language:
            language_from_path = settings.LANGUAGE_CODE

        if response.status_code == 404 and not language_from_path and i18n_patterns_used:
            language_path = '/%s%s' % (language, request.path_info)
            path_valid = is_valid_path(language_path, urlconf)
            path_needs_slash = (
@@ -53,20 +57,8 @@ class LocaleMiddleware(object):
                )
                return self.response_redirect_class(language_url)

        if not (self.is_language_prefix_patterns_used(urlconf)
                and language_from_path):
        if not (i18n_patterns_used and language_from_path):
            patch_vary_headers(response, ('Accept-Language',))
        if 'Content-Language' not in response:
            response['Content-Language'] = language
        return response

    @lru_cache.lru_cache(maxsize=None)
    def is_language_prefix_patterns_used(self, urlconf):
        """
        Returns `True` if the `LocaleRegexURLResolver` is used
        at root level of the urlpatterns, else it returns `False`.
        """
        for url_pattern in get_resolver(urlconf).url_patterns:
            if isinstance(url_pattern, LocaleRegexURLResolver):
                return True
        return False
+10 −3
Original line number Diff line number Diff line
@@ -382,15 +382,22 @@ class LocaleRegexURLResolver(RegexURLResolver):
    Rather than taking a regex argument, we just override the ``regex``
    function to always return the active language-code as regex.
    """
    def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
    def __init__(
        self, urlconf_name, default_kwargs=None, app_name=None, namespace=None,
        prefix_default_language=True,
    ):
        super(LocaleRegexURLResolver, self).__init__(
            None, urlconf_name, default_kwargs, app_name, namespace,
        )
        self.prefix_default_language = prefix_default_language

    @property
    def regex(self):
        language_code = get_language() or settings.LANGUAGE_CODE
        if language_code not in self._regex_dict:
            regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
            self._regex_dict[language_code] = regex_compiled
            if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
                regex_string = ''
            else:
                regex_string = '^%s/' % language_code
            self._regex_dict[language_code] = re.compile(regex_string, re.UNICODE)
        return self._regex_dict[language_code]
+4 −0
Original line number Diff line number Diff line
@@ -248,6 +248,10 @@ Internationalization
  used in a root URLConf specified using :attr:`request.urlconf
  <django.http.HttpRequest.urlconf>`.

* By setting the new ``prefix_default_language`` parameter for
  :func:`~django.conf.urls.i18n.i18n_patterns` to ``False``, you can allow
  accessing the default language without a URL prefix.

Management Commands
~~~~~~~~~~~~~~~~~~~

+23 −2
Original line number Diff line number Diff line
@@ -1326,11 +1326,17 @@ Django provides two mechanisms to internationalize URL patterns:
Language prefix in URL patterns
-------------------------------

.. function:: i18n_patterns(*pattern_list)
.. function:: 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
:func:`~django.conf.urls.i18n.i18n_patterns`. Example URL patterns::
:func:`~django.conf.urls.i18n.i18n_patterns`.

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

Example URL patterns::

    from django.conf.urls import include, url
    from django.conf.urls.i18n import i18n_patterns
@@ -1371,6 +1377,21 @@ function. Example::
    >>> reverse('news:detail', kwargs={'slug': 'news-slug'})
    '/nl/news/news-slug/'

With ``prefix_default_language=False`` and  ``LANGUAGE_CODE='en'``, the URLs
will be::

    >>> activate('en')
    >>> reverse('news:index')
    '/news/'

    >>> activate('nl')
    >>> reverse('news:index')
    '/nl/news/'

.. versionadded:: 1.10

    The ``prefix_default_language`` parameter was added.

.. warning::

    :func:`~django.conf.urls.i18n.i18n_patterns` is only allowed in a root
Loading