Cover image for Universal language switcher for Craft CMS

Universal language switcher for Craft CMS

piotrpog profile image Piotr Pogorzelski Originally published at craftsnippets.com ・6 min read

This post is part of my Craft CMS template components library. For more, you can visit craftsnippets.com


This article assumes you have a basic understanding of Craft CMS multisite functionality. To learn about it, head to documentation.

Now, let's sum up how language switcher works.

  • Language switcher will display links to alternative versions of the content on the website - links to other sites. Only links to sites belonging to same site group as the current site will be displayed.
  • If a site has "This site has its own base URL" option disabled, it will not appear in language switcher.
  • If you are on the page associated with element - entry, category, commerce product - language switcher will display links to other versions of this element (belonging to other sites). It will work even for elements added by plugins, assuming they have their own URL.
  • While looping through sites, language switcher performs few checks before outputting proper link. If the current element does not have an alternative version (it does not propagate across sites or is not enabled for specific site) or current page is not associated to any element (because it is accessed by custom route or template routing), language switcher link will direct to base URL of another site.
  • If current site is the only site in its site group, language switcher will not be displayed. No need for language switcher if there is nothing to switch to.
  • Each language switcher link consists of language name in its native form (for english "english", for german "deutsche" etc.) and country flag. Thanks to flag icon CSS library, a country flag can be appended to language link automatically.
  • Language switcher has proper ARIA tags that make it accessible - aria-role and aria-label. Aria label text can be provided by static translations.
  • Each language switcher link has hreflang parameter that lets google crawler recognize the multilingual structure of your website.
  • Language switcher link representing currently viewed site has is-active CSS class.

Language switcher - twig code

This language switcher works out of the box. Just include it in the proper place.

{# v2 #}
{# http://craftsnippets.com/articles/universal-language-switcher-for-craft-cms #}

{# logic #}
{% set currentElement = craft.app.urlManager.matchedElement %}
{% set sites = craft.app.getSites().getGroupById(currentSite.groupId).getSites() %}
{% set switcherLinks = [] %}

{% for site in sites if site.baseUrl is not empty %}

    {% set title = craft.app.i18n.getLocaleById(site.language).nativeName %}
    {% set url = site.getBaseUrl() %}
    {% if currentElement %}
        {% set otherLocaleElement = craft.app.getElements().getElementById(currentElement.id, currentElement.className(), site.id) %}
        {% if otherLocaleElement and otherLocaleElement.enabledForSite %}
            {% set url = otherLocaleElement.url %}
        {% endif %}
    {% endif %}

    {% set switcherLinks = switcherLinks|merge([{
        url: url, 
        title: title, 
        countryCode: site.language|split('-')|last,
        current: site.id == currentSite.id ? true : false,
        language: site.language,
    }]) %}

{% endfor %}

{# outputting html #}
{% if switcherLinks|length > 1 %}
<nav aria-label="{{'Switch language'|t}}" aria-role="navigation">
{% for switcherLink in switcherLinks %}
    <li class="{{switcherLink.current ? 'is-active'}}">
        <a href="{{switcherLink.url}}" hreflang="{{switcherLink.language}}" lang="{{switcherLink.language}}">
        <span>{{ switcherLink.title }}</span>
        <span class="flag-icon flag-icon-{{switcherLink.countryCode}}"></span>
{% endfor %}
{% endif %}

How does it work?

Let's dive into Twig code and analyze how language switcher works. Understanding its structure will allow you to easily modify it to your needs.

First, we get element associated with currently viewed page using craft.app.urlManager.matchedElement. If not such element exists, matchedElement returns false.

Then, we loop through sites that are in the same group as a currently viewed site. if site.baseUrl is not empty ensures that sites with no public URLs are omitted.

Each site has its locale code - represented by site.language variable. For example, a site set to American locale has en-US. The first part represents language, second part represent a country. We need to extract country code from locale code to use it in CSS class of element containing country flag.

Using craft.app.i18n.getLocaleById we get locale data of specific site. We need it to display locale name in human readable form, in its native spelling.

Now, its time to get URL of link. First, we first set url variable to base URL of site within current for loop. If page is associated to element, we then overwrite url value to URL of alternative version of this element, using craft.app.getElements().getElementById.

Finally, we append all data related to the link to switcherLinks array. After looping through sites, we can loop through links and output them as HTML.

Customizing language switcher

Here are a few ideas for modifying default language switcher.

If language descriptions like "English (United states)" are too explict to you, you can display only "language" part of locale name by extracting language code from locale code:

{% set languageCode = site.language|split('-')|first %}
{% set title = craft.app.i18n.getLocaleById(languageCode).nativeName %}

You can also display country code next to its name in native language, for example: "English (EN)". It's good alternative to displaying flags:

{% set languageCode = site.language|split('-')|first %}
{% set nativeName = craft.app.i18n.getLocaleById(languageCode).nativeName %}
{% set title = nativeName ~ '(' ~ languageCode ~ ')' %}

If you don't wish to display link to currently browsed site in language switcher, you can exclude it:

{% set sites = craft.app.getSites().getGroupById(currentSite.groupId).getSites()|without(currentSite) %}

Country flags

In order to display country flags in language switcher links, you need to:

  • Include Flag icons CSS library in your project.
  • Make sure that each site locale has locale code consisting of country code and language code. So - instead of just "en", use "en-US". Country code is needed for setting proper CSS class of element with flag.

But should you really use flags for your language switcher? Well, it depends.

Some countries have multiple official languages. For example, Canada official languages are both english and french. If you display just Canadian flag, it will tell nothing about the actual language used by this version of the site.

There are also multiple countries that use the same language - like USA, UK and Australia using english, or multiple counties that use portugalese. Is your content just translated to a specific language, or tailored to a specific country?

Each multilingual site requires thinking it all through. I recommend reading THIS article on United Language Group website to expand your knowledge on this subject.

Lang attribute

Besides having proper language switcher, it's important to properly set lang attributes on your website. This attribute specifies the language of content and should be always specified in <html> element. You can do this in Craft like this:

<html lang="{{ currentSite.language }}">

Setting proper lang attribute will tell screen readers how they should pronounce the text. It can also affect text rendering in case of some fonts. It however does not tell Google about the language of your website.

If you have some content in a different language from rest of website, you can set lang attribute to element containing this content. That's why each language switcher link that contains language name in its native spelling has proper lang attribute.

Site switcher plugin

Site switcher plugin can take care of outputting URLs of alternative versions of the current element. The same thing however can be achieved purely in Twig - like in language switcher described in this article.

Therefore, I don't see the reason to use this plugin.

Further reading

Here are some useful links that will help you design proper language switcher:


markdown guide