7

I am looking to modify the default output of Drupal 8 of links in a certain situation, the language switcher. Ideally I want to be able to do that in a twig template and not have to produce a massive php file that overwwrites that whole link generation system and add a BIG if for the cases I need to overwrite.

I think the output layout should be controllable in the twig template similar to this answer (this answer outputs the same url for every languages)

I am using bootstrap as a base theme so i'd like to use the bootstrap markup for my links (btn btn-primary, dropdown)

Here is my code I am trying to make links--language-block.html.twig

{%- if links|length == 2 -%}
    {# show only alternate language button #}
    {%- for key, item in links -%}
        {%- if not item.attributes['#options']['set_active_class'] -%} {# <--- this is always true!? #}
            <a href="{{ item.link['#url'] }}" class="btn btn-primary">{{ item.link['#title'] }}</a> {# #url is always the same what ever the language it is pointing to! #}
        {%- endif -%}
    {%- endfor -%}
{%- elseif links|length > 2  -%}
    {# show selected language in button and other languages in drop down #}
    <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{{ selectedLanguage }}<span class="caret"></span></button>
    <ul class="dropdown-menu">
        {% for key, item in links %}
            {%- if not item.attributes['#options']['set_active_class'] -%} {# <--- this is always true!? #}
            <li><a href="{{ item.link['#url'] }}">{{ item.link['#title'] }}</a></li>
            {% endif %}
        {% endfor %}
    </ul>
{%- endif -%}

Anyone has an idea on how to do this?

13

Ok I found 2 ways to do that.

1. In a custom theme

You can alter variables in your my_theme.theme file. You need to figure out the name of the function you need. ex: my_theme_preprocess_twig_file(), in my case I needed my_theme_preprocess_links__language_block() You need to take the twig file name and replace all - for _.

my_theme.theme:

function my_theme_preprocess_links__language_block(&$variables) {
  $currentLanguageCode = \Drupal::languageManager()
    ->getCurrentLanguage()
    ->getId();
  // replace key of active language with 'activeLink'
  foreach ($variables['links'] as $i => $link) {
    /** @var \Drupal\language\Entity\ConfigurableLanguage $linkLanguage */
    $linkLanguage = $link['link']['#options']['language'];
    if ($currentLanguageCode == $linkLanguage->get('id')) {
      $variables['links']['activeLink'] = $link;
      unset($variables['links'][$i]);
    }
  }
  // if there is only 2 languages remove active one
  if (sizeof($variables['links']) == 2) {
    unset($variables['links']['activeLink']);
    // give class 'btn btn-primary' to alternate language
    /** @var \Drupal\Core\Url $alternate */
    $alternate = current($variables['links']);
    $alternate['link']['#options']['attributes']['class'][] = 'btn';
    $alternate['link']['#options']['attributes']['class'][] = 'btn-primary';
    $variables['links'] = [$alternate];
  }
}

2. In a custom module

You can also create a module that will change those same variables. There is a big difference in the values of the variables because this preprocess happened earlier in the flow. The name of the function is also quite different, ex: my_module_api_to_modify_alter(). In my case I needed to modify the language_switch_links from the language.api.php. You can find all alter function by searching for *.api.php files in drupal 8. Those are there as a reference for exactly that.

my_module.module:

function my_module_language_switch_links_alter(&$variables) {
  $currentLanguageCode = \Drupal::languageManager()
    ->getCurrentLanguage()
    ->getId();
  // replace key of active language with 'activeLink'
  foreach ($variables as $i => $link) {
    /** @var \Drupal\language\Entity\ConfigurableLanguage $linkLanguage */
    $linkLanguage = $link['language'];
    if ($currentLanguageCode == $linkLanguage->get('id')) {
      $variables['activeLink'] = $link;
      unset($variables[$i]);
    }
  }
  // if there is only 2 languages remove active one
  if (sizeof($variables) == 2) {
    unset($variables['activeLink']);
    // give class 'btn btn-primary' to alternate language
    /** @var \Drupal\Core\Url $alternate */
    $alternate = current($variables);
    $alternate['attributes']['class'][] = 'btn';
    $alternate['attributes']['class'][] = 'btn-primary';
    $variables = [$alternate];
  }
}

And my Twig template for both cases links--language-block.html.twig:

{% if links -%}
    {%- if links|length == 1 -%}
        {# show only alternate language button #}
        {{ (links|first).link }}
    {%- else -%}
        {# show selected language in button and other languages in drop down #}
        <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{{ links['activeLink'].text }} <span class="caret"></span></button>
        <ul class="dropdown-menu">
            {% for key, item in links %}
                {% if key is not same as("activeLink") %}
                <li>{{ item.link }}</li>
                {% endif %}
            {% endfor %}
        </ul>
    {%- endif -%}
{%- endif %}
1

I made this adaption the language switcher so that when it finds a node with type article, and this is not a translation in a different langauge that it would change the link to be the homepage.

This needed to be made in a module rather than in the Theme.

I needed this as I had a massive difference in the number of translated articles per language.

function HOOK_language_switch_links_alter(array &$links, $type, \Drupal\Core\Url $url){
$currentLanguageCode = \Drupal::languageManager()
->getCurrentLanguage()
->getId();
// look at all links.
foreach ($links as $link) {
  // Only work on links that are not in the current page language.
  $lang_id = $link['language']->get('id');
  if ($currentLanguageCode != $lang_id) {
    // Trying to get the node.
    $node = \Drupal::request()->attributes->get('node');
    // Making sure its an object and has the method getType and is Article.
    if (!empty((object) $node) 
      && method_exists($node, 'getType')
      && $node->getType() == 'article') {
      //  We know its an article, checking for languages.
      $flipped = array_flip(array_keys($node->getTranslationLanguages()));
      // Using flipped array rather than in_array
      if (!isset($flipped[$lang_id])) {
        // Translation not available.
        $links[$lang_id]['url'] = Url::fromRoute('<front>');
      }
    }
  }
 }
}

Don't forget to add the use statement for Drupal\Core\Url and remove it from the function arguments, its also used for the new route generation.

Also note that you have to use a new generated Url object to change the url, there is no set method for the existing url ('<current>') to change it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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