There just has to be a better way to insert a link to an alternative translation than this:

function THEME_preprocess_node(&$variables) {

  $node = &$variables['node'];

  // Current language
  $current_language = \Drupal::languageManager()->getCurrentLanguage()->getId();
  $variables['current_language'] = $current_language;
  $variables['current_language_url'] = $node->toUrl()->toString();

  // If this is a multi-lingual node, get links to alternative language.
  $languages = $node->getTranslationLanguages();
  foreach ($languages AS $langcode => $language) {
    $node_ja = ($langcode == 'ja') ? $node->getTranslation('ja') : null;
    if ($node_ja && is_object($node_ja) && $current_language == 'en') {
      $node_url = $node_ja->toUrl()->toString();
      $variables['node_url_ja'] = $node_url;
      $variables['node_url_ja_translation'] = '<a href="' . $node_url . '">Japanese</a>';

    $node_en = ($langcode == 'en') ? $node->getTranslation('en') : null;
    if ($node_en && is_object($node_en) && $current_language == 'ja') {
      $node_url = $node_en->toUrl()->toString();
      $variables['node_url_en'] = $node_url;
      $variables['node_url_en_translation'] = '<a href="' . $node_url . '">English</a>';


  // Display link to alternate translation.
  $variables['node_url_translation'] = ($current_language == 'en') ? $variables['node_url_ja_translation'] : $variables['node_url_en_translation'];


It works, but it's just not at all graceful. How should this be written? I just want to put in a link like this into a templates/node.html.twig file:

{{ node_url_translation|raw }}
  • It's best practice to not use the |raw filter. See How do I get the raw field value in a template?
    – No Sssweat
    Dec 27, 2017 at 3:27
  • Also, it's best practice to create a link using the Drupal API's link class. See How do I create a link?
    – No Sssweat
    Dec 27, 2017 at 3:35
  • I'm voting to close this question, as it is primarily opinion based. Drupal has many ways to achieve your solution. And possibly every member of Drupal Answers would have done it in a different way. So there is no chance of giving you a dedicated answer to "How should this be written?". Dec 27, 2017 at 8:37
  • 1
    I'm voting to keep it open, because it's clear to me what @MikeGifford is looking for and I think it's a good question. That said, it would help if the question was rephrased in more objective words, providing measurable criteria that answers should meet. Dec 27, 2017 at 22:18
  • It's a lot more than just adding the |raw filter. More logic is needed. I'm asking this question as it's going to be a common problem in multilingual sites. I was frustrated at how long it took me to build this logic in D8. I'm fine with re-phrasing it. I guess I'm looking for best practices for creating links to jump back/forth between multi-lingual content from within a theme. Dec 28, 2017 at 0:44

1 Answer 1


The easiest method of providing language switching links would be using the language manager's getLanguageSwitchLinks() method. However, this might - depending on your language settings - also provide fallback links, if the node is not translated to the target language. An abstract example:

use Drupal\Core\Language\LanguageInterface;

function THEME_preprocess_node(&$variables) {
  $node = $variables['node'];

  $language_manager = \Drupal::languageManager();
  $current_language = $language_manager->getCurrentLanguage()->getId();
  $links = $language_manager->getLanguageSwitchLinks(LanguageInterface::TYPE_CONTENT, $node->toUrl());

  $variables['current_language'] = $current_language;
  $variables['translations'] = [
    '#theme' => 'links',
    '#links' => $links,
    '#set_active_class' => TRUE,

For your use case - to show links to translated content only -, your approach to using getTranslationLanguages() of the TranslatableInterface is on the right track.

But be aware, that it will return entity translation instances also for non-published - or otherwise limited - content translations, to which the current user may not have view access to. Hence, he will get an "Access denied" error after clicking the links.

Additionally, the code should be generic enough to allow for further languages being added using the admin UI in a later stage of the application's life-cycle, without having to touch your preprocessing/templates anymore.

Therefore some suggested improvements for your solution:

  • Check, whether the current user has access to the translation.
  • Avoid using hard-coded language identifiers. As I suppose you did so to not add further links for the current language, you should use your $current_language variable to do the checks.
  • Avoid using hard-coded language link labels. Consider using \Drupal\Core\Language::getName() instead.
  • Use a list of language links instead of a dedicated variable for each language, and pass that list to the template. This way you can just iterate over the links.
  • Don't return markup (the links) from within your preprocess hook, but allow creating the links within the templates.

This sums up to something like this:

use Drupal\Core\Template\Attribute;

function THEME_preprocess_node(&$variables) {
  $node = $variables['node'];

  $language_manager = \Drupal::languageManager();
  $current_language = $language_manager->getCurrentLanguage()->getId();

  $links = [];
  foreach ($node->getTranslationLanguages() as $langcode => $language) {
    // Skip any translation that cannot be viewed.
    $translation = $node->getTranslation($langcode);
    if (!$translation->access('view', NULL, TRUE)->isAllowed()) {
    // Get URL of the translation.
    $url = $node->toUrl('canonical')
      ->setOption('language', $language)
    // Optionally provide an 'active' class.
    $classes = [];
    if ($langcode == $current_language) {
      $classes[] = 'active';
    // Construct information for a link.
    $link = [
      'language' => $langcode,
      'is_current' => $langcode == $current_language,
      'url' => $url,
      'title' => $language->getName(),
      'attributes' => new Attribute(['class' => $classes]),
    $links[] = $link;

  $variables['current_language'] = $current_language;
  $variables['translations'] = $links;

In your template, you can then create the links like this:

{% if translations %}
  {% for item in translations %}
    {# Optionally skip current language #}
    {% if not item.is_current %}
      <a href="{{ item.url }}"{{ item.attributes }}>{{ item.title }}</a>
    {% endif %}
  {% endfor %}
{% endif %}

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