0

I have a django-cms site, that uses i18n_patterns in urls.py. This works, urls are built like /lang/here-starts-the-normal/etc/.

Now, I would like to have urls like this: /prefix-lang/here-starts.... As there will be a couple of country specific domains, this wille be like /ch-de/here-... for Switzerland/.ch domain, /us-en/here-starts.... for the states, and some more. So, when the url would be /ch-de/..., the LANGUAGE would still be de. Hope this is clear?

As the content is filled with existing LANGUAGES=(('de', 'DE'), ('en', 'EN'), ...), I cannot change LANGUAGES for every domain - no content would be found in the cms, modeltranslation, only to mention those two.

How can I prefix the language slug in i18n_patterns? Is it possible at all?

benzkji
  • 1,718
  • 20
  • 41

2 Answers2

2

I think a way without hacking Django too much would be to use URL rewrite facility provided by the webserver you run, for example, for mod_wsgi you can use mod_rewrite, similar facility exists also for uWSGI.

You may need to also post-process the output from Django to make sure that any links are also correctly re-written to follow the new schema. Not the cleanest approach but seems doable.

petr
  • 2,554
  • 3
  • 20
  • 29
  • yep. looks easy. the most obvious we forget about. though, there would be some fiddling with `SCRIPT_NAME`, `FORCE_SCRIPT_NAME`, etc... see https://stackoverflow.com/questions/47941075/host-django-on-subfolder/47945170#47945170 will report if it works! – benzkji Aug 24 '21 at 10:27
  • have a working demo with a quite DRY i18n_patterns hack...`FORCE_SCRIPT_NAME` doesnt work for django-cms pages for now, only got it workign for apphooks...ARF. – benzkji Aug 25 '21 at 23:06
  • ended with a customized `LocaleMiddleware` and `i18n_patterns` - see my own answer, if of interest... – benzkji Sep 01 '21 at 13:04
0

Working example, though the country/language order is reversed (en-ch instead of ch-en), to have it as django expects it when trying to find a language (ie, setting language to "en-ch", it will find "en", if available).

This solution involves a modified LocaleMiddleware, i18n_patterns, LocaleRegexResolver. It supports no country, or a 2 char country code, setup with settings.SITE_COUNTRY. It works by changing urls to a lang-country mode, but the found language code in middleware will still be language only, 2 chars, and work perfectly with existing LANGUAGES, that contain 2 chars language codes.

custom_i18n_patterns.py - this just uses our new resolver, see below

from django.conf import settings

from ceco.resolvers import CountryLocaleRegexURLResolver


def country_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
    URLconf.
    """
    if not settings.USE_I18N:
        return list(urls)
    prefix_default_language = kwargs.pop('prefix_default_language', True)
    assert not kwargs, 'Unexpected kwargs for i18n_patterns(): %s' % kwargs
    return [CountryLocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]

resolvers.py

import re

from django.conf import settings
from django.urls import LocaleRegexURLResolver
from modeltranslation.utils import get_language


class CountryLocaleRegexURLResolver(LocaleRegexURLResolver):
    """
    A URL resolver that always matches the active language code as URL prefix.
    extended, to support custom country postfixes as well.
    """
    @property
    def regex(self):
        language_code = get_language() or settings.LANGUAGE_CODE
        if language_code not in self._regex_dict:
            if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
                regex_string = ''
            else:
                # start country changes
                country_postfix = ''
                if getattr(settings, 'SITE_COUNTRY', None):
                    country_postfix = '-{}'.format(settings.SITE_COUNTRY)
                regex_string = '^%s%s/' % (language_code, country_postfix)
                # end country changes
            self._regex_dict[language_code] = re.compile(regex_string, re.UNICODE)
        return self._regex_dict[language_code]

middleware.py - only very few lines changed, but had to replace the complete process_response.


from django.middleware.locale import LocaleMiddleware
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 get_script_prefix, is_valid_path
from django.utils import translation
from django.utils.cache import patch_vary_headers

class CountryLocaleMiddleware(LocaleMiddleware):
    """
    This is a very simple middleware that parses a request
    and decides what translation object to install in the current
    thread context. This allows pages to be dynamically
    translated to the language the user desires (if the language
    is available, of course).
    """
    response_redirect_class = HttpResponseRedirect

    def process_response(self, request, response):
        language = translation.get_language()
        language_from_path = translation.get_language_from_path(request.path_info)
        urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
        i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)

        if (response.status_code == 404 and not language_from_path and
                i18n_patterns_used and prefixed_default_language):
            # Maybe the language code is missing in the URL? Try adding the
            # language prefix and redirecting to that URL.

            # start country changes
            language_country = language
            if getattr(settings, 'SITE_COUNTRY', None):
                language_country = '{}-{}'.format(language, settings.SITE_COUNTRY)
            language_path = '/%s%s' % (language_country, request.path_info)
            # end country changes!

            path_valid = is_valid_path(language_path, urlconf)
            path_needs_slash = (
                not path_valid and (
                    settings.APPEND_SLASH and not language_path.endswith('/') and
                    is_valid_path('%s/' % language_path, urlconf)
                )
            )

            if path_valid or path_needs_slash:
                script_prefix = get_script_prefix()
                # Insert language after the script prefix and before the
                # rest of the URL
                language_url = request.get_full_path(force_append_slash=path_needs_slash).replace(
                    script_prefix,
                    '%s%s/' % (script_prefix, language_country),
                    1
                )
                return self.response_redirect_class(language_url)

        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

benzkji
  • 1,718
  • 20
  • 41