25

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?

9 Answers 9

29

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!

4
  • This is great...how does it work if there's no matching entry ID in one of the other languages? Jun 12, 2014 at 10:39
  • 1
    Supercool. 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
    Jun 12, 2014 at 10:48
  • 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
    Jun 12, 2014 at 11:08
  • 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! Aug 30, 2016 at 12:27
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 %}
0
10

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.

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.

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.

2
  • 1
    Yes, something like this works great as long as you don't translate entry slugs.
    – carlcs
    Mar 6, 2015 at 0:35
  • Fair point @carlcs! We're not translating those on our site. :)
    – Lindsey D
    Mar 6, 2015 at 0:36
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 %}
3

If you happen to be using the SEOmatic plugin for Craft 2.x, 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.

For SEOmatic for Craft CMS 3,there's a similarly the seomatic.helper.getLocalizedUrls(URI, SITE_ID) helper function:

Return a list of localized URLs for a given URI that are in the SITE_ID site's group. Both URI and SITE_ID are optional, and will use the current request's URI and the current site's SITE_ID if omitted.

This makes building a language switcher trivial.

2

Here is my language switcher for Craft CMS 3:

http://craftsnippets.com/articles/universal-language-switcher-for-craft-cms?s=s

It works for Entries, categories and other elements with their own URLs and has an option for an automatic appending country flag to language links.

1

Here's my take with the latest CraftCMS 3.2 (using sites groups)

{% set swapLinkDone = false %}

{# ——— Loop through all of the site locales, except the current one #}
{% if entry is defined %}
    {% set otherLocales = craft.app.i18n.siteLocales|filter(locale => locale.id != craft.app.language) %}
    {% for locale in otherLocales %}
        {% set otherSite = craft.app.sites.allSites|filter(s => s.language == locale.id and s.groupId == currentSite.groupId)|first %}
        {% if otherSite is defined %}
            {% set localeEntry = craft.entries.id(entry.id).site(otherSite.handle).one %}
            {% if localeEntry is defined and localeEntry.slug != "home" %}
                <a href="{{ siteUrl(localeEntry.uri, [], null, otherSite.id) }}">{{ locale.id|t }}</a>
                {% set swapLinkDone = true %}
            {% endif %}
        {% endif %}
    {% endfor %}
{% endif %}

{% if swapLinkDone == false %}
    {% set otherLang = craft.app.language == "en" ? "fr" : "en" %}
    <a href="{{ otherLang == "en" ? "/en" : "/fr" }}">{{ otherLang|t }}</a>
{% endif %}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.