Multilingual sites¶
This guide shows you how to implement several languages in a single site.
Note
If you also want to serve multiple sites in a single Django installation you should start here and continue with Multisite setup.
Making the page language selectable¶
Pages may come in varying languages. LanguageMixin
helps with that.
It adds a language_code
field to the model which allows selecting
the language based on settings.LANGUAGES
. The first language is set
as default:
from django.utils.translation import gettext_lazy as _
from feincms3.mixins import LanguageMixin
from feincms3.pages import AbstractPage
class Page(AbstractPage, LanguageMixin):
class Meta(AbstractPage.Meta):
unique_together = [("language_code", "translation_of")]
Activating the language¶
The activate_language
method is the preferred way to activate the
page’s language for the current request. It runs
django.utils.translation.activate
and sets request.LANGUAGE_CODE
to the value of django.utils.translation.get_language
, the same
things Django’s LocaleMiddleware
does.
Note that activate
may fail and get_language
might return a
different language, however that’s not specific to feincms3.
page = ... # MAGIC! (or maybe get_object_or_404...)
page.activate_language(request)
...
Note
page.activate_language
does not persist the language across
requests as Django’s django.views.i18n.set_language
does.
(set_language
modifies the session and sets cookies.) That is
mostly what you want though since the page’s language is tied to its
URL.
Page tree tips¶
I most often add a root page per language, which means that the main
navigation’s tree_depth
would be 1
, not 0
. The menu template
tags described in Navigation generation recipes would also require an additional
.filter(language_code=django.utils.translation.get_language())
statement to only return pages in the current language.
A page tree might look as follows then:
Home (EN)
- About us
- News
Startseite (DE)
- Über uns
- Neuigkeiten
Page d'acceuil (FR)
- A propos de nous
- Actualité
By manually setting the slug of all root pages to their respective
language code (e.g. Home (EN) has a URL of /en/
, Startseite (DE) a
URL of /de/
) you can generate a navigation pointing to all sites in
their respective language (assuming that the built-in template context
processor django.template.context_processors.i18n
is active):
<nav class="languages">
{% for code, name in LANGUAGES %}
<a href="/{{ code }}/">{{ name }}</a>
{% endfor %}
</nav>
Deep links to pages in other languages¶
The navigation snippet above does not link translations directly but instead always shows the home page to visitors. It may be preferrable to define pages (and other CMS objects) as translations of each other so that it is possible to generate a navigation menu directly linking the same content in different languages directly.
feincms3 offers a built-in way to achieve this. Instead of inheriting
the default feincms3.mixins.LanguageMixin
inherit the
feincms3.mixins.LanguageAndTranslationOfMixin
. The latter
provides an additional translation_of
foreign key which allows
linking pages in other languages to the page in the first language in
the LANGUAGES
setting’s list. In the example above, you could
specify that “Über uns” is the german translation of “About us”, and “A
propos de nous” the french translation of “About us”. The
feincms3.mixins.LanguageAndTranslationOfMixin.translations()
method returns a list of all known translations. Together with the
translations()
template filter you
can generate a navigation menu as follows (assuming that object
is
the current page):
{% load feincms %}
<nav class="languages">
{% for lang in page.translations.active|translations %}
<a href="{% if lang.object %}{{ lang.object.get_absolute_url }}{% else %}/{{ lang.code }}/{% endif %}">
{{ lang.name }}
</a>
{% endfor %}
</nav>
LanguageAndTranslationOfMixin within feincms3.applications
The same should work for any CMS object inheriting
feincms3.mixins.LanguageAndTranslationOfMixin
, and should
also work when used within a feincms3 app.
(Apps will be introduced later.)
In this case it may be extra-important to wrap the object’s call to
reverse_app()
in a block which overrides the
active language so that the article is preferrably shown in a website
with the matching language:
from django.utils.translation import override
from feincms3.applications import reverse_app
class Article(LanguageAndTranslationOfMixin, ...):
class Meta:
unique_together = [("language_code", "translation_of")]
def get_absolute_url(self):
with override(self.language_code):
return reverse_app("articles", "detail", ...)
Generating the navigation menu for changing the language should preferrably link to the translated article and only fall back to the translated page’s URL if no such article exists:
def article_detail(request, ...):
page = page_for_app_request(request)
page.activate_language(request)
article = get_object_or_404(Article, ...)
translations = {obj.language_code: obj for obj in page.translations().active()}
translations.update(
{obj.language_code: obj for obj in article.translations().active()}
)
# Use {% for lang in available_translations|translations %} ... {% endfor %}
context = {"available_translations": translations.values()}