How to customize the language switcher in Drupal 8

 

Drupal 8 dramatically improved the capabilities for creating multilingual websites. Content translation was moved into core and an improved architecture made internationalization of complex sites much easier. The language switcher however, could do with some improvements. Here’s how we customized it.

Published on February 20, 2020

As a creative digital agency based in Amsterdam, we’ve gotten used to having two languages of conduct: Dutch and English. We switch back and forth without even noticing and have learned to read, write and conduct business in both languages effortlessly. Our clients are mostly Dutch, but many cater to an international audience or operate beyond borders, so by now there quite a few multilingual websites in our portfolio.

Despite the potential complexities multilingual websites may pose, Drupal has always been notoriously adamant about supporting all languages - including those with non-latin scripts and those read from right to left. Whereas multilingual sites in Drupal 7 often required a plethora of internationalization and translation modules, combined with some custom code, Drupal 8 was going to solve all our multilingual headaches once and for all. Or did it?

Everything is perfect. Almost.

Admittedly, Drupal 8 has made it easier than ever to support multiple languages. Its architectural overhaul simplified the structure, thereby making internationalization of content much more logical and efficient. A lot of internationalization functionality was moved to core in order to improve maintenance and support. And of course, to enable Drupal site builders to create multilingual sites out of the box.

So Drupal 8 solves every pet-peeve you could’ve had with multiple languages in a single site perfectly and for good. Right? Not quite. Things are never exactly how we (or our clients) want them and that’s fine. That’s why there are Drupal Developers.

Sleek and minimal will do.

Let’s talk about the language switcher in Drupal 8. It can be enabled, placed as a block in the region of your choice and it pretty much works. It shows all added languages written out fully, like so:

  • English
  • Nederlands

However, as we like our site sleek and minimal and consider our visitors tech-savvy, we would like to customize the links and determine exactly where and how the language switcher links get rendered.

Customize the language block

In order to control the output of the language switcher block, we want to be able to render the necessary links only. We don’t need a block title or any of those pesky elements, we just want an HTML list with links, for example:

<ul class="language-switcher-language-url links">
  <li hreflang="en" class="en">
    <a href="/" class="language-link" hreflang="en">en</a>
  </li>
  <li hreflang="nl" class="nl is-active">
    <a href="/nl" class="language-link is-active" hreflang="nl">nl</a>
  </li>
</ul>

Luckily, Drupal 8’s Twig templating system makes it pretty easy to render exactly what we want. Just place this in the page.html.twig of your custom theme where you want your language switcher links:

{ # Language switcher # }
{{ drupal_block('language_block:language_interface', wrapper=false) }}

Changing the link labels

Although it’s probably best practise to fully write out the available languages - English, Nederlands, Francais - we could do with some more sleek minimalism. Besides, we consider our visitors as tech-savvy, so we can probably suffice with showing only the language codes as our links: EN and NL.

Let’s tackle this one with a preprocess function. Just paste this code in your CUSTOMTHEMENAME.theme and the links in your switcher should magically transform into language codes:

/**
 * Use language code for the language switcher
 */
function CUSTOMTHEMENAME_preprocess_links__language_block(&$variables) {
  foreach ($variables['links'] as $i => $link) {
    // @var \Drupal\language\Entity\ConfigurableLanguage $linkLanguage
    $linkLanguage = $link['link']['#options']['language'];
    $variables['links'][$i]['link']['#title'] = $linkLanguage->get('id');
  }
}

Don’t forget to change CUSTOMTHEMENAME to the name of your custom theme.

Hide links for untranslated content

Once the language switcher is enabled and placed, it’s always there. Even when there is no translated version of the page you are viewing, there is a link to the translation of that page. Which doesn’t exist and will just lead you back to the same node, possibly on an unaliased path. That’s bad for SEO and worse for usability.

Let’s fix this by only rendering translation links when translations really do exist. We’re going to create a custom module. Let’s call it “untranslated”.

Our untranslated.info.yml file looks like this:

name: Untranslated
type: module
description: "Disables language switcher links for untranslated content."
package: Custom
core: 8.x

dependencies:
  - language

And untranslated.module looks like this:

<?php

/**
 * @file
 * Hide language switcher links for untranslated languages on an entity.
 */
use Drupal\Core\Entity\ContentEntityInterface;

/**
 * Implements hook_language_switch_links_alter().
 */
function untranslated_language_switch_links_alter(array &$links, $type, $path) {
  if ($entity = untranslated_get_page_entity()) {
    $new_links = array();
    foreach ($links as $lang_code => $link) {
      try {
        if ($entity->getTranslation($lang_code)->access('view')) {
          $new_links[$lang_code] = $link;
        }
      }
      catch (\InvalidArgumentException $e) {
        // This language is untranslated so do not add it to the links.
      }
    }
    $links = $new_links;

    // If we're left with less than 2 links, then there's nothing to switch.
    // Hide the language switcher.
    if (count($links) < 2) {
      $links = array();
    }
  }
}

/**
 * Retrieve the current page entity.
 *
 * @return Drupal\Core\Entity\ContentEntityInterface
 *   The retrieved entity, or FALSE if none found.
 */
function untranslated_get_page_entity() {
  $params = \Drupal::routeMatch()->getParameters()->all();
  $entity = reset($params);
  if ($entity instanceof ContentEntityInterface) {
    return $entity;
  }
  return FALSE;
}

Now enable this module and your untranslated links should magically vanish until you publish a translation of your page.

There’s been some debate about whether you should remove the links to untranslated content as we demonstrate here or go for a less radical approach and add css classes in order to display links as grayed out or apply strikethrough. This decision will depend on your particular case or client.

The rest of the customizations we applied were just styling, so we’ll leave those up to you and your designers.

Have fun out there!

___

A big thank you to these very helpful references:

 

planet drupal
drupal
drupal 8
development