Een taalschakelaar voor meertalige Wagtail-sites

Het is relatief eenvoudig om een taalschakelaar te maken die terugkeert naar de startpagina in de gewenste taal. Om terug te keren naar dezelfde pagina in de gewenste taal, is wat meer werk nodig.

12 juli 2020 09:53
Thema's: Meerdere talen

Django heeft veel taalgerelateerde hulpprogramma's. In deze tutorial zullen we een aantal van hen gebruiken om een taalschakelaar te maken die we kunnen activeren via een link in ons navigatiemenu en die de pagina weergeeft waar we ons bevonden in de gekozen taal. Het definiëren van een weergave in onze views.py die de startpagina retourneert in de gekozen taal is relatief rechttoe-rechtaan:

from django.http import HttpResponseRedirect
from django.utils import translation

def set_language_from_url(request, language_code):

    if not language_code in [lang[0] for lang in settings.LANGUAGES]:
        return HttpResponseRedirect('/')

    next_url = '/' + language_code +'/'
    translation.activate(language_code)
    response = HttpResponseRedirect(next_url)
    response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language_code)
    return response

Dit veronderstelt dat we taalprefixen gebruiken voor onze urls. We controleren eerst of de gewenste language_code in de LANGUAGES van onze instellingen staat. Vervolgens stellen we in om naar de startpagina te gaan, activeren we de taal en stellen we een taalcookie in.

Als we willen terugkeren naar de pagina waarop we ons bevonden, maar in een andere taal, is het eerste dat we moeten doen de pagina ophalen waarop we ons bevonden:

try:
    # get the full path of the referring page; go back if requested language equals current language
    previous = request.META['HTTP_REFERER']
    if language_code == translation.get_language():
        return HttpResponseRedirect(previous)

    CODE FOLLOWS

except KeyError:
    # if for some reason the previous page cannot be found, go to the home page
    next_url = '/' + language_code +'/'

Als we de vorige pagina niet kunnen vinden, gaan we standaard naar de startpagina. Als er een vorige pagina is en de huidige taal is al de gevraagde taal, dan keren we onmiddellijk terug. We halen de huidige taal op met Django's functie get_language.

Nu zijn er twee opties: previous hoort bij een pagina die deel uitmaakt van de Wagtail-boom, of niet. Om previous in de Wagtail-boom te vinden, is een beetje url-engineering nodig. Splits eerst het pad van de url af met de functie urlparse van Python:

from urllib.parse import urlparse

prev_path = urlparse(previous).path

Nu kunnen we dit opzoeken in de Wagtail-boom, met behulp van het veld url_path. Er is echter een kleine complicatie: op meertalige sites heeft wagtailtrans het translatable root-pad vóór de url_path geplakt. We kunnen de huidige site vinden met behulp van de methode find_for_request van Wagtail's Site-model. Dus onze lookup wordt (beide paden hebben een begin en een eind /, we verwijderen er een met de slice [1:]):

from wagtail.core.models import Site
prev_url_path = Site.find_for_request(request).root_page.url_path + prev_path[1:]
prev_page = TranslatablePage.objects.get(url_path=prev_url_path)

Nu kunnen we de vertaalde versie van deze pagina ophalen met de velden canonical_page en language van het model TranslatablePage, net zoals we deden in ons navigatiemenu.

can_page = prev_page.canonical_page if prev_page.canonical_page else prev_page
language = Language.objects.get(code=language_code)
next_url = can_page.url if language_code == settings.LANGUAGE_CODE else TranslatablePage.objects.get(language=language, canonical_page=can_page).url

Dat is alles voor het geval af waar de vorige pagina deel uitmaakt van de Wagtail-boom. Meertalige pagina's in ons project buiten de boom zouden geconstrueerd moeten zijn met i18n-patronen. Django biedt een functie om hun urls te vertalen:

from django import urls

next_url = urls.translate_url(previous, language_code)

Deze functie wordt ook gebruikt in Django's set_language implementatie. Als translate_url geen vertaling kan vinden, retourneert het de originele url. We hebben het geval waarin language_code gelijk is aan de huidige taal al afgehandeld, dus als dit gebeurt, is er blijkbaar geen vertaling. In dat geval verwijzen we terug naar de homepage:

if next_url == previous:
    next_url = '/' + language_code + '/'

Door deze twee gevallen te combineren en excepties netjes af te handelen, krijgen we de volledige code die we in het views.py-bestand van onze app plaatsen:

from django import urls
from django.conf import settings
from django.http import HttpResponseRedirect
from django.utils import translation
from urllib.parse import urlparse
from wagtail.core.models import Site
from wagtailtrans.models import TranslatablePage, Language


def set_language_from_url(request, language_code):

    if not language_code in [lang[0] for lang in settings.LANGUAGES]:
        return HttpResponseRedirect('/')

    try:
        # get the full path of the referring page; go back if requested language equals current language
        previous = request.META['HTTP_REFERER']
        if language_code == translation.get_language():
            return HttpResponseRedirect(previous)

        try:
            # split off the path of the previous page
            prev_path = urlparse(previous).path
            # wagtailtrans prefixes the translatable root's url_path, so we need to do that as well
            prev_url_path = Site.find_for_request(request).root_page.url_path + prev_path[1:]
            prev_page = TranslatablePage.objects.get(url_path=prev_url_path)

            # if the current page is not canonical, get the canonical page
            can_page = prev_page if prev_page.is_canonical else prev_page.canonical_page

            # if the requested language is the canonical (default) language, use the canonical page, else find the translated page
            language = Language.objects.get(code=language_code)
            next_url = can_page.url if language_code == settings.LANGUAGE_CODE else TranslatablePage.objects.get(language=language, canonical_page=can_page).url
        except (TranslatablePage.DoesNotExist, Language.DoesNotExist):
            # previous page is not a TranslatablePage, try if previous path can be translated by changing the language code
            next_url = urls.translate_url(previous, language_code)

            # if no translation is found, translate_url will return the original url
            # in that case, go to the home page in the requested language
            if next_url == previous:
                next_url = '/' + language_code + '/'

    except KeyError:
        # if for some reason the previous page cannot be found, go to the home page
        next_url = '/' + language_code +'/'

    translation.activate(language_code)
    response = HttpResponseRedirect(next_url)
    response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language_code)
    return response

In de urls.py van onze app creëren we een pad naar deze functie:

from django.urls import path
from .views import set_language_from_url

urlpatterns = [
    path('<str:language_code>/', set_language_from_url, name='set_language_from_url'),
]

We kunnen deze url-patronen opnemen in onze project- urls.py door toe te voegen:

urlpatterns += i18n_patterns(
   path('language/', include('cms.urls')),
)

Als we meer vermeldingen in onze urls.py van onze applicatie zouden hebben, zouden we dit waarschijnlijk anders doen, misschien een aparte app maken, maar voor nu is dit oké. We kunnen de functie nu activeren door links te maken naar /language/en/, /language/fr/ etc. waar we maar willen. In een eerdere tutorial hebben we een menu-bouwer gemaakt; we kunnen een menu language maken, de taalkoppelingen voor elk menu-item definiëren, er pictogrammen met vlaggen van de verschillende talen aan toevoegen en dit menu toevoegen aan ons hoofdmenu. In de toekomst een nieuwe taal toevoegen is dan een kwestie van toevoegen aan het menu. Bekijk de video om dit in actie te zien.

Lees verder als u gebruikers wilt toestaan opmerkingen toe te voegen aan artikelen op uw site.

Reageer op dit artikel (log eerst in of bevestig hieronder met naam en email)