1. Support translation
Sept. 14, 2017, 2:12 a.m.
The first problem was in Levels, that did not solve enough fastly, is support of some languages of site, and it's need that part of urls built normally for different languages, i. e. with language prefix before url, but other part of urls did not contain this prefixes. For illustrative purposes, I show some urls:
levels.pro/about/ - "About company" in English levels.pro/ru/about/ - "About company" in Russian levels.pro/de/about/ - "About company" in German levels.pro/messages/ - user messages levels.pro/friends/ - friend list of user
levels.pro/about/
, levels.pro/ru/about/
, levels.pro/de/about/
urls, as you see, contains information about company in English, in Russian and German language respectively. Language prefix before /about/
tell language in which information will be presented.
But it is needn't language prefix for levels.pro/messages/
and levels.pro/friends/
urls, because this urls of specific authorizes site user. It's better to make similar links identical (without prefixing) regardless of chosen language - this is convenient and beautiful way to represent url.
But for promote the site in search engine, information links such as "About company" should be made as separate pages for each language. As a result, personal user sections (Messages, Friends and so on) are closed from search engines, but information "About company", "Advertising" and so on is open and will be indexed by search engines.
Pay should also pay attention to "Main url", i. e. to levels.pro/
. For unauthorized users you should show greeting page with an offer to log or register. But if user logs in, then he should look, say, his personal page or news feed.
Having understood logic of url building, let's move on to implement.
Project structure
For illustrative purposes, I show part of project structure, that there is at the time of writing the article:
levels ├─ profile │ ├─ templates │ │ └─ friends.html │ ├─ __init__.py │ ├─ urls.py │ └─ views.py ├─ __init__.py ├─ settings.py └─ urls.py
First, I added profile
application and put user's link handle there.
profile/urls.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.conf.urls import url from profile.views import show_my_page, show_feed, show_messages, show_friends, show_home_or_feed urlpatterns = [ url(r'^feed/$', show_feed, name='feed'), # User feed url(r'^messages/$', show_messages, name='messages'), # User messages url(r'^friends/$', show_friends, name='friends'), # User friends ... url(r'^$', show_home_or_feed), # Main page or user feed url(r'^(?P<id_or_slug>[\w-]+)/$', show_my_page, name='my_page'), # User page ]
Note the line url(r'^$', show_home_or_feed),
. show_home_or_feed
view define to show user feed, if he is registered, or wellcome page, created in the usual way in Django CMS.
profile/views.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from cms.views import details from django.conf import settings from django.contrib.auth import get_user_model from django.shortcuts import render, redirect User = get_user_model() # get_user() method tries to find user in DB by id or slug. If user does not exists, then None is returned def get_user(id_or_slug): try: if id_or_slug.startswith('user-'): user_id = id_or_slug.split('-')[1] try: return User.objects.get(id=user_id) except ValueError: return None else: return User.objects.get(slug=id_or_slug) except User.DoesNotExist: return None def show_my_page(request, id_or_slug): user = get_user(id_or_slug) # trying to define user by id_or_slug if not user: # If there is no such user, we try handle url with django-cms through its details(request, slug) view. # For me, links of main page of different language such as levels.pro/ru/ returned 404 code error, because id_or_slug equals /ru/, # and request.path equals levels.pro/ru/, so Django cannot find page. # For fixing it enough just to clear id_or_slug. if id_or_slug in dict(settings.LANGUAGES).keys(): id_or_slug = '' return details(request, id_or_slug) return render(request, 'profile/my_page.html', {'shown_user': user}) def show_feed(request): return render(request, 'profile/feed.html') def show_home_or_feed(request): # This is special a way, that allows to edit the main cms-page. # As it was said above, when user is authorized, he redirects to feed. # But how can admin add content to main page? For it I use next a way - # if url contains edit_main GET-parameter (for example, levels.pro/?edit_main), # then link will be handled by view of django-cms. if 'edit_main' in request.GET or not request.user.is_authenticated: return details(request, '') return redirect(show_feed) def show_messages(request): return render(request, 'profile/messages.html') def show_friends(request): return render(request, 'profile/friends.html')
urls.py:
from django.conf import settings from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns from django.conf.urls.static import static from django.contrib import admin admin.autodiscover() ... i18n_urls = ( url(r'^admin/', include(admin.site.urls)), ... ) profile_urls = [ url(r'^', include('profile.urls')), ] cms_urls = [ url(r'^', include('cms.urls')), ] urlpatterns = [] urlpatterns.extend(i18n_patterns(*i18n_urls, prefix_default_language=False)) urlpatterns.extend(profile_urls) urlpatterns.extend(i18n_patterns(*cms_urls, prefix_default_language=False)) urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
Idea is that urls profile
application should not be built with language prefix, this is a first. And secondly, url(r'^', include('profile.urls')),
line shouldn't be above, than url(r'^', include('cms.urls')),
. It's need to be able to view of profile
app to handle, for example, such url as levels.pro/vivazzi
, namely, to determine whether it is slug of user or usual cms page.
Here I get difficulty. My trying to use profile_urls
above, then all other urls:
# ----------------------------- # Does NOT work correctly!!! # ----------------------------- from django.conf import settings from django.conf.urls import include, url from django.conf.urls.i18n import i18n_patterns from django.conf.urls.static import static from django.contrib import admin admin.autodiscover() ... profile_urls = [ url(r'^', include('profile.urls')), ] i18n_urls = ( url(r'^admin/', include(admin.site.urls)), ... url(r'^', include('cms.urls')), ) urlpatterns = [] urlpatterns.extend(profile_urls) urlpatterns.extend(i18n_patterns(*i18n_urls, prefix_default_language=False)) urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
... fails, since admin urls are no longer loaded, so the order of urls is important.
After, I added support of multiple languages for the site. This is well described in the article Мультиязычность и перевод в Django. In principle, the settings are same as in article, except the one thing: it's need custom Middleware to determine the language by url. Default 'django.middleware.locale.LocaleMiddleware'
is not suitable, because when trying to go to eg levels.pro/friends/
django will think, if there is no language prefix, then use English as current language (English is default: LANGUAGE_CODE = 'en'
), but we need that language is not defined by such method for these urls. To solve this problem, I took as basis Django's LocaleMiddleware
and inherited from it, overridden process_request
method:
from cms.models import Title from django.conf import settings from django.conf.urls.i18n import is_language_prefix_patterns_used from django.middleware.locale import LocaleMiddleware from django.utils import translation class CustomLocaleMiddleware(LocaleMiddleware): def process_request(self, request): urlconf = getattr(request, 'urlconf', settings.ROOT_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) language_from_path = translation.get_language_from_path(request.path_info) if 'lang' in request.GET: language = request.GET['lang'] elif not language_from_path and i18n_patterns_used and not prefixed_default_language: titles = Title.objects.filter(page__depth=1).distinct().values_list('slug', flat=True) titles = list('/{}/'.format(t) for t in titles) titles += ('/admin/', ) if request.path.startswith(tuple(titles)): language = settings.LANGUAGE_CODE translation.activate(language) request.LANGUAGE_CODE = translation.get_language()
Since we doesn't use language prefix in user links such as levels.pro/friends/
, then we cannot define language from link. Here I found one way: to change language to the default (in this case, English) only if url, i. e. request.path
, matches the slug of cms pages. For example, /about/
is in ['/about/', '/terms/', ...]
list of titles, so this url with the missing prefix tell that it's need to show English version of "About company" page (accordingly for /ru/about/
Russian version of page will be shown). But, for example, /vivazzi/
is not in list of titles. It follows that this user page and the language does not need to be switched.
Also I had to add /admin/
link in titles
to work the redirect to admin panel. While working like this.
One more thing: if there is lang
in request.GET
, then we switch language. It's need when we are, for example, on page levels.pro/friends/
and we need to switch from English to Russian. We don't have levels.pro/ru/friends/
link for the reasons described above, so it's need somehow to send information about language change. For this I added lang
GET-parameter to language switch template:
{% load i18n menu_tags %} {% spaceless %} {% for language in languages %} <a href="{% page_language_url language.0 %}?lang={{ language.0 }}"{% if current_language == language.0 %} class="active"{% endif %} title="{% trans 'Change language to' %} {{ language.1 }}">{{ language.0|upper }}</a> {% endfor %} {% endspaceless %}
And after I check for lang
in GET-parameters of request:
if 'lang' in request.GET: language = request.GET['lang']
My settings.py
for functionality of this miltulanguage site:
# -*- coding: utf-8 -*- from __future__ import unicode_literals ... TIME_ZONE = 'Asia/Irkutsk' LANGUAGE_CODE = 'en' LANGUAGES = (('en', 'English'), ('ru', 'Russian')) LOCALE_PATHS = ('locale', ) PARLER_LANGUAGES = { 1: ( {'code': 'en'}, {'code': 'ru'}, ), 'default': { 'fallback': 'en', 'hide_untranslated': False, } } USE_I18N = True USE_L10N = True USE_TZ = True MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.common.BrokenLinkEmailsMiddleware', 'spec.middleware.CustomLocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'cms.middleware.user.CurrentUserMiddleware', 'cms.middleware.page.CurrentPageMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', 'cms.middleware.language.LanguageCookieMiddleware', 'cms.middleware.utils.ApphookReloadMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', ) ...
That's all that I wanted to tell you about the adding multilingualism in Levels project. There are a lot of subtleties. I hope, that I explain clearly the nuances of adding more complex translation logic.
I feel that there are many moments in code, that are needed in improvement. For example, thisTitle.objects.filter(page__depth=1).distinct().values_list('slug', flat=True)
request. It should be cached so that the database does not jeck every time.
Over time, of course, this code will be supplemented. I think it's good beginning.
I'm glad that you have read this voluminous article! I wait for your comment to discuss the code.
Comments: 0