Wondering how to build a language switcher in a Wagtail multi-language project? In this blog post you will learn the following:

  1. How to setup the Page models to allow users to switch to the translation page of the page viewed
  2. How to setup the language switcher in your templates

Watch out: In order for this to work your project should have a duplicate page tree. Read our blog post on how to do that. This blog post builds up on the knowledge mentioned there.

Link Wagtail page models for multi-language support

So as we have defined the prerequisites, let's answer the next question in order to get a better idea on how to build a language switcher: How to setup the page models to allow users to switch to the translation of the currently viewed page.

When building a language switcher it is good UX to allow the user to switch between the provided languages. Language switchers are often implemented as such that the user is redirected to the language specific home page of the website when choosing another language. This is a viable solution, but it is better to allow the user to switch to the translation of the actual page the user is currently viewing.

In order to achieve this, the current page needs to know about its own translation. This can be achieved by setting up the following:

  • Different language version of Wagtail pages needs to be linked together
  • Editors should only need to link one of the pages to the other versions

In order to implement these requirements consistently for all our Wagtail pages we put this behavior into a mixin, which you can find in our GitHub repository.

Note that you also need to add this custom setting to your Django settings:

OUR_I18N_METADATA = {
    # iso15897 uses "_DE" because Facebook does not recognize _AT. And we have to use
    # opengraph metatags
    "de": {"display_name": "Deutsch", "flag_code": "at", "iso15897": "de_DE"},
    "en": {"display_name": "English", "flag_code": "gb", "iso15897": "en_US"},
}

This TranslatablePageMixin allows the editors to set the english_link for a page. This link would hold the translated version of a specific page in our page tree. The editor is required to only set this once - it is used in the get_english_page method to get the translated version of the page.

The german translation for a given page is fetched implicitly in get_german_page by filtering for pages that have set their english_link to the current page.

By using this mixin, we solved the two requirements mentioned above. Editors can link a page to the translated version of the page, but the link needs to be only set once, not on both sides of the relationship - which is great!

Usage in a model

class ServiceOverviewPage(TranslatablePageMixin, Page):
    """Serves as the Service Overview Page"""

    hero_title = models.CharField(
        _("Hero Title"), max_length=250, blank=True, null=True
    )
    hero_intro = models.TextField(_("Hero Intro"), blank=True, null=True)

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("hero_title", classname="full"),
                FieldPanel("hero_intro", classname="full"),
            ],
            heading="Header",
        ),
        MultiFieldPanel(TranslatablePageMixin.panels, heading="Translation"),
    ]

    edit_handler = TabbedInterface(
        [ObjectList(content_panels, heading=_("Content"))]
        + CustomMetadataPageMixin.default_content_panels
    )

    parent_page_types = ["cms.HomePage"]

Frontend Implementation

From a backend perspective we are already done. We only need to inform the frontend about whether the currently served page has a translation page assigned and how that translated version of the page can be viewed. This is implemented by using the TranslatablePageMixin.

You might have noticed the i18n_pages property. That's what we'll use to provide the frontend template with all the translations of the served page and further information that is needed to build a language switcher. We can iterate over all available languages and output something like this:

<nav class="languageswitcher" aria-label="switch page language">
{% for code, language in i18n_pages.items %}
    {% with 'img/flags/'|add:language.flag_code|add:'.svg' as flag_path %}
        <a
            class="languageswitcher__link {% if language.is_active %}languageswitcher__link--active{% endif %}"
            href="{{ language.url }}"
            lang="{{ code }}"
            hreflang="{{ code }}"
        >
            <img class="languageswitcher__flag" src="{% static flag_path %}" width="24" height="16" alt="" />
            <span>{{ language.display_name }}</span>
        </a>
    {% endwith %}
{% endfor %}
</nav>

It's important to set the hreflang attribute on the links as a signal to search engines to avoid the translation being classified as duplicate content. The lang attribute sets the language for the link content itself and helps screenreaders to pronounce the language name correctly.

Other Posts

  • Syncing Toggl Time Tracking with Slack

    When you're working in a distributed team, it's often hard to keep track of everything that's going on. That's why we've built a tool to help us sync our internal time tracking to slack.