How can I build a language switcher?

25

12

I'm currently building a site with two languages, and I need a language switcher so wherever you enter the website you can switch the language easily.

I've come this far:

{% for locale in craft.i18n.getSiteLocales() %}
    <li {{ locale == craft.i18n.getCurrentLocale() ? 'class="active"' }}>
        <a href="/{{ locale }}/{{ entry.slug }}">
           {% if loop.first %}DE{% else %}FR{% endif %}
        </a>
    </li>
{% endfor %}

The problem here is that {{ entry.slug }} only returns the slug for the current language and doesn't loop trough the languages.

When I'm on french it returns /bonjour twice and on german it returns /hallo twice. Why is that?

KSPR

Posted 2014-06-12T09:58:12.153

Reputation: 1 829

Answers

27

I am building my first multi-language site with Craft as well. This is from a thread over at Google+, so I didn't come up with this myself but it works very well:

{% block locale__switch %}

    {# Loop through all of the site locales, except the current one #}
    {% set otherLocales = craft.i18n.getSiteLocaleIds()|without(craft.locale) %}
    {% for locale in otherLocales %}

        {# Find the current entry in the other locale #}
        {% set localeEntry = craft.entries.id(entry.id).locale(locale).first %}

        {# Make sure that it's actually localized to this locale #}
        {% if localeEntry.locale == locale %}

            {# Output a link to it #}
            <a href="{{ localeEntry.getUrl() }}">{{ locale }}</a>
        {% else %}

            {# Output a link to the hompage #}
            <a href="{{ craft.config.siteUrl[locale] }}">{{ locale }}</a>
        {% endif %}

    {% endfor %}
{% endblock %}

I set this up in a block to implement the switch in various templates. It loops through all the languages minus the current one and outputs the links. It also matches the entries by ID which is why you can have different slugs. Hope it helps.

Yay for the craftcms.stackexchange site!

portnull

Posted 2014-06-12T09:58:12.153

Reputation: 440

I get the following error: Impossible to access an attribute ("locale") on a null variable

What do I do wrong? I copy/pasted the code you showed.

thanx in advance! – Martijn La Feber – 2016-08-30T12:27:13.700

This is great...how does it work if there's no matching entry ID in one of the other languages? – Patrick Nesbitt – 2014-06-12T10:39:43.097

1Supercool. Thanks. It would be cool to have some sort of explanation of each line becaue I find it quite hard to learn twig. The documentation is scattered across the various craft/twig sites. I added a conditional to check for the homepage because there I don't need the locale.Entry.uri – KSPR – 2014-06-12T10:48:09.200

1@Patrick If there is no matching entry in another locale you could link to the homepage of that locale. Just committed an edit to the original answer. – carlcs – 2014-06-12T11:08:29.377

14

I have changed the above code a little bit because it causes an error when I have for example a website with three active locales and an entry with only one or two active locales.

The error is: Impossible to access an attribute ("locale") on a NULL variable ("")

The expanded if-statement looks now like this:

{% if localeEntry.locale is defined and localeEntry.locale == locale %}

And the complete code:

{% set otherLocales = craft.i18n.getSiteLocaleIds()|without(entry.locale) %}
{% for locale in otherLocales %}
    {% set localeEntry = craft.entries.id(entry.id).locale(locale).first %}
    {% if localeEntry.locale is defined and localeEntry.locale == locale %}
        <a href="{{ localeEntry.getUrl() }}">{{ locale }}</a>
    {% else %}
        <a href="{{ craft.config.siteUrl[locale] }}">{{ locale }}</a>
    {% endif %}
{% endfor %}

Christian Steger

Posted 2014-06-12T09:58:12.153

Reputation: 390

9

Thought I would share my language switcher, as it shows languages in their native name.

<nav class="nav nav--locale">
    <ul>
        {% set locales = craft.i18n.getSiteLocales() %}

        {% for locale in locales %}
            {% set entryURL = craft.config.siteUrl[locale.id] %}

            {% if entry is defined %}
                {% set entryLocal = craft.entries.id(entry.id).locale(locale.id).first %}

                {% if entryLocal.getUrl() is defined %}
                    {% set entryURL = entryLocal.getUrl() %}
                {% endif %}

                <li class="nav__item {% if locale.id == craft.locale %}nav__item--active{% endif %}">
                    <a href="{{ entryURL }}">{{ locale.nativeName }}</a>
                </li>
            {% else %}
                <li class="nav__item {% if locale.id == craft.locale %}nav__item--active{% endif %}">
                    <a href="{{ entryURL }}">{{ locale.nativeName }}</a>
                </li>
            {% endif %}
        {% endfor %}
    </ul>
</nav>

EDIT: I have fixed an issue when the entry doesn't have the content in all the different locales. This happens when you turn the default locale off in the section settings. In this case the link will take you back to the home page for that language.

Martin

Posted 2014-06-12T09:58:12.153

Reputation: 266

9

I've created a plugin which makes it incredibly easy to add language-switching links to your website...

Language Link plugin for Craft CMS

Standard Usage

Add this to your _layout, or create a separate template to include in each page:

{% if entry is not defined %}
    {% set entry = null %}
{% endif %}

<ul>
    <li><a href="{{ craft.languageLink.url('en', entry) }}">English</a></li>
    <li><a href="{{ craft.languageLink.url('es', entry) }}">Español</a></li>
    <li><a href="{{ craft.languageLink.url('fr', entry) }}">Français</a></li>
    <li><a href="{{ craft.languageLink.url('de', entry) }}">Deutsch</a></li>
</ul>

This plugin is based on the code I originally shared here, taking into account the feedback received from carlcs on that thread. If an entry exists, the plugin will use the proper slug from each locale of an entry (or category, or any other element type). Otherwise, it will simply share the page URI between locales.

Lindsey D

Posted 2014-06-12T09:58:12.153

Reputation: 18 570

5

This simple solution has been working brilliantly for me...

{% set pageUri = craft.request.url|replace(siteUrl, '') %}

<ul>
    <li><a href="{{ craft.config.siteUrl['en'] ~ pageUri }}">English</a></li>
    <li><a href="{{ craft.config.siteUrl['fr'] ~ pageUri }}">Français</a></li>
</ul>

The pageUri is basically the current URL, minus the localized siteUrl.

Append the clean pageUri to each localized siteUrl, and voila... whatever page you are currently on will be linked to it's other-language counterpart.

Lindsey D

Posted 2014-06-12T09:58:12.153

Reputation: 18 570

1Yes, something like this works great as long as you don't translate entry slugs. – carlcs – 2015-03-06T00:35:50.817

Fair point @carlcs! We're not translating those on our site. :) – Lindsey D – 2015-03-06T00:36:30.977

3

I tweaked the version in the answer above by Christian Steger (https://craftcms.stackexchange.com/a/1794/3835). This version, hides the current language from the list of language links, and displays the language links in the target language.

It also allows you to omit any desired languages from the list of language links. This is useful for enabling a locale for use in the control panel without having it appear in the list of language links throughout the site.

{% set currentLocale = craft.i18n.getCurrentLocale() %}
{# Decide which languages should be omitted from language links #}
{% set localeExceptions = ["en", currentLocale] %}
{% set otherLocales = craft.i18n.getSiteLocaleIds()|without(localeExceptions) %}

{% for locale in otherLocales %}
  {% if entry is defined %}
     {% set localeEntry = craft.entries.id(entry.id).locale(locale).first %}
  {% endif %}

  {% if localeEntry.locale is defined and localeEntry.locale == locale %}
      <a href="{{ localeEntry.getUrl() }}">{{ craft.i18n.getLocaleById(locale).nativeName }}</a>
  {% else %}
      <a href="{{ craft.config.siteUrl[locale] }}">{{ craft.i18n.getLocaleById(locale).nativeName }}</a>
  {% endif %}
{% endfor %}

Justin K

Posted 2014-06-12T09:58:12.153

Reputation: 149

3

If you happen to be using the SEOmatic plugin, it comes with a getLocalizedUrls() function that:

Returns an array of localized URLs for the current page request. This handles elements with localized slugs, etc. This function returns the unique URLs for each language for the current page request, not just the localized site URLs.

This makes building a language switcher trivial.

andrew.welch

Posted 2014-06-12T09:58:12.153

Reputation: 5 136

1

I tried using the codes found here but couldn't get my 404 page to work without getting a twig_error page so I had to look for another language switcher. I found this code and just added in my css .nav-link.current {display: none;} ( Finally a language switcher that won't give me errors. Even if the entry doesn't exist.

{% set locales = ['en', 'fr'] %}
{% for locale in locales %}

<!-- Check if locale equals the requested page locale -->
{% if locale == craft.locale %}
    {% set current = true %}
{% else %}
    {% set current = false %}
{% endif %}


<!-- Is this an entry page? -->
{% if entry is defined %}


    <!-- Find the current entry in the other locale -->
    {% set localeEntry = craft.entries.id(entry.id).locale(locale).first %}

    <!-- Make sure that the localised entry exists -->
    {% if localeEntry %}

        {# Make sure that it's actually localized to this locale#}
        {% if localeEntry.locale == locale %}

            {# Output a link to it #}
            <a href="{{ localeEntry.getUrl() }}" class="nav-link{{ current ? ' current'}}">{{ craft.i18n.getLocaleById(locale).id[:2] }}</a>

        {% else %}

            {# Output a link to the hompage #}
            <a href="{{ craft.config.siteUrl[locale] }}" class="nav-link{{ current ? ' current'}}">{{ craft.i18n.getLocaleById(locale).id[:2] }}</a>

        {% endif %}
    {% else %}

        {# Output a link to the hompage #}
        <a href="{{ craft.config.siteUrl[locale] }}" class="nav-link{{ current ? ' current'}}">{{ craft.i18n.getLocaleById(locale).id[:2] }}</a>
    {% endif %}
{# Not an entry page #}
{% else %}

    {# Output the same path with the locale's base URL (`siteUrl`) #}
    <a href="{{ craft.config.siteUrl[locale] ~ craft.request.getPath() }}" class="nav-link{{ current ? ' current'}}">{{ craft.i18n.getLocaleById(locale).id[:2] }}</a>

{% endif %}
{% endfor %}

mateostabio

Posted 2014-06-12T09:58:12.153

Reputation: 56