Django: префикс language slug в i18n_urls
У меня есть django-cms сайт, который использует i18n_patterns
в urls.py
. Это работает, урлы строятся как /lang/here-starts-the-normal/etc/
.
Теперь я хотел бы иметь такие урлы: /prefix-lang/here-starts...
. Поскольку будет несколько доменов, специфичных для каждой страны, это будет выглядеть как /ch-de/here-...
для Швейцарии/домена.ch, /us-en/here-starts....
для штатов, и еще несколько. Таким образом, когда url будет /ch-de/...
, LANGUAGE
все равно будет de
. Надеюсь, это понятно?
Поскольку контент заполнен существующими LANGUAGES=(('de', 'DE'), ('en', 'EN'), ...)
, я не могу изменить LANGUAGES
для каждого домена - контент не будет найден в cms, modeltranslation, только для упоминания этих двух.
Как сделать префикс для языкового словаря в i18n_patterns
? Возможно ли это вообще?
Я думаю, что способом, не взламывая Django слишком сильно, было бы использовать возможности перезаписи URL, предоставляемые веб-сервером, на котором вы работаете, например, для mod_wsgi
вы можете использовать mod_rewrite, подобная возможность существует также для uWSGI
.
Возможно, вам также потребуется пост-обработка вывода Django, чтобы убедиться, что все ссылки также правильно переписаны в соответствии с новой схемой. Не самый чистый подход, но кажется выполнимым.
Рабочий пример, хотя порядок стран/языков изменен (en-ch
вместо ch-en
), чтобы он был таким, каким его ожидает django при попытке найти язык (т.е., установив язык на "en-ch", он найдет "en", если он доступен).
Это решение включает в себя модифицированные LocaleMiddleware
, i18n_patterns
, LocaleRegexResolver
. Он поддерживает отсутствие страны или код страны из 2 символов, настраиваемый с помощью settings.SITE_COUNTRY
. Это работает, изменяя урлы в режим lang-country
, но найденный код языка в промежуточном ПО будет по-прежнему только языком, 2 символа, и отлично работает с существующими LANGUAGES
, которые содержат 2 символа кода языка.
custom_i18n_patterns.py - здесь просто используется наш новый резольвер, см. ниже
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 - изменено всего несколько строк, но пришлось заменить полностью 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