Universal language switcher for Craft CMS
Tags: craftcms, webdev, cms, internationalization
Published: 2019-07-12 18:51:49

---
title: Universal language switcher for Craft CMS
published: true
description: Language switcher is an essential part of every multilingual site. This Craft CMS template component will generate language switcher, along with country flags next to language links.
tags: craftcms,webdev,cms,internationalization 
canonical_url: http://craftsnippets.com/articles/universal-language-switcher-for-craft-cms
series: Craft Snippets
cover_image: http://craftsnippets.com/uploads/post_images/hands-600497_1920.jpg
---

This post is part of my Craft CMS template components library. For more, you can visit [craftsnippets.com](http://craftsnippets.com?s=dt)

## Overview

This article assumes you have a basic understanding of Craft CMS multisite functionality. To learn about it, [head to documentation](https://docs.craftcms.com/v3/sites.html). 

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](https://docs.craftcms.com/v3/sites.html). 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](https://docs.craftcms.com/v3/routing.html)), 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](https://github.com/lipis/flag-icon-css) library, a country flag can be appended to language link automatically.
* Language switcher has proper [ARIA](https://www.sitepoint.com/how-to-use-aria-effectively-with-html5/) 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.

```twig
{# 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">
<ul>
{% 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>
        </a>
    </li>
{% endfor %}
</ul>
</nav>
{% 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](https://docs.craftcms.com/api/v3/craft-web-urlmanager.html#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](https://github.com/lipis/flag-icon-css#usage) of element containing country flag.

Using [craft.app.i18n.getLocaleById](https://docs.craftcms.com/api/v3/craft-web-twig-variables-i18n.html#method-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](https://docs.craftcms.com/api/v3/craft-services-elements.html#method-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:

```twig
{% 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:

```twig
{% 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:

```twig
{% 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](https://github.com/lipis/flag-icon-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](https://unitedlanguagegroup.com/blog/inside-design-a-language-selector-is-no-place-for-flags/) 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:

```twig
<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](http://www.thesempost.com/google-ignores-html-lang-tag-indexing-search-results/).

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](https://plugins.craftcms.com/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:

* [Iconography for translations: best practice for communicating availability of translated content](http://www.flagsarenotlanguages.com/blog/iconography-for-translations-best-practice-for-communicating-availability-of-translated-content/)
* [Designing a language switch: examples and best practices](https://usersnap.com/blog/design-language-switch/)
* [Language selector is no place for flags](https://unitedlanguagegroup.com/blog/inside-design-a-language-selector-is-no-place-for-flags/)
* [Accessible Language Pickers: a11y meets i18n/l10n](http://terrillthompson.com/759)
* [What’s the best way to implement a language switcher ](https://medium.com/@khalidaljaaidi/whats-the-best-way-to-implement-a-language-switcher-part-1-desktop-web-a118ecd0752c)