4

In my Drupal 8 project I have a menu containing menu items in english. Some of the items have a german translation. How do I filter the menu to only show the german items when viewing the site in german?

I'm currently using MenuLinkTree to fetch my menu and I have created my own MenuLinkTreeManipulator transform where I try to filter out MenuLinkContent items that does not have a translation in the current language. But the method for getting an entity from a MenuLinkContent object is protected, so I cannot access the language property of the underlying entity!

Is there some other way of finding out the language of a MenuLinkContent object? Or another way of filtering a menu by current language?

Edit:

Here is the method I'm currently using (as a transform) when getting the menu via the MenuLinkTree service:

public function currentLanguage($tree) {
  $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();

  return array_filter($tree, function($item) use($lang) {
    return $item->link->getEntity()->language()->getId() === $lang;
  });
}

$item->link above is an instance of \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent. The above code does not work since $item->link->getEntity() is a protected method. If make it public the code above works. I see no way of accessing the underlying entity here without hacking core. Is it possible in any way? Is it possible to "swap" out this class for my own implementation in a custom module?

2
  • Menu items create via config should support translations. Items created via UI should also support translations so could it be that you do not have translated items but rather separate items each with different language?
    – user21641
    Feb 12, 2016 at 11:20
  • I'm quite sure that I have menu items that have translated versions. I have created them via the Menu UI and I can switch between the translated versions of a menu item there. The problem is that I see no way of getting the translated versions of a Menu item while using the MenuTree service for fetching a menu.
    – reekris
    Feb 12, 2016 at 12:55

5 Answers 5

8

I managed to hide non translated menu items with the following code:

use Drupal\Core\Menu\MenuLinkInterface;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;

/**
* Implements hook_preprocess_menu().
*/
function MY_MODULE_preprocess_menu(&$variables) {
  if ($variables['menu_name'] == 'main') {
   $language = Drupal::languageManager()->getCurrentLanguage()->getId();

    foreach ($variables['items'] as $key => &$item) {
      $menuLinkEntity = MY_MODULE_load_link_entity_by_link($item['original_link']);

      if ($menuLinkEntity != NULL) {
        $languages = $menuLinkEntity->getTranslationLanguages();
        // Remove links which is not translated to current language.
        if (!array_key_exists($language, $languages)) {
          unset($variables['items'][$key]);
        }
      }
    }
  }
}

function MY_MODULE_load_link_entity_by_link(MenuLinkInterface $menuLinkContentPlugin) {
  $entity = NULL;

  if ($menuLinkContentPlugin instanceof MenuLinkContent) {
    list($entity_type, $uuid) = explode(':', $menuLinkContentPlugin->getPluginId(), 2);
    $entity = \Drupal::entityManager()->loadEntityByUuid($entity_type, $uuid);
  }
  return $entity;
}

There is probably a better way but this worked for me. Got the inspiration from here: http://www.agoradesign.at/blog/add-custom-menu-item-attributes-drupal-8

3
  • Awesome, this saved me at least one hour coding! Thanks a lot :)
    – Sebastian
    Nov 21, 2016 at 12:58
  • Great! Same to me! Thx Sockiplast
    – Bruno
    Apr 17, 2018 at 14:36
  • This is a more generic solution. The node_access solution only works if the link is a node. But menu links can be many things other than node. Jun 2, 2022 at 11:15
1

Short answer is that you can't.

The longer answer is that you can, but it depends.

Background information

The tricky thing with menu items is that some are created via .yml files, some are created via Views and some are created as content entities.

This means that the way menu items are translated are very different, as it uses

  • interface translation
  • configuration translation
  • content translation

I looked at this issue around beta 6-8 and talked with Gabór about it (the translation initiative lead). There is an open ticket for it (https://www.drupal.org/node/2466553) but no work has been done. I'm not sure it's actually possible to fix this.

Possible solution / workaround

If you make sure your menu only exist of content menu items (menu items created in your interface) you can make a solution. Basically what you need to do, is to use views to display the menu items. In views interface you can make a filter on the language, to filter out untranslated menu items. I'm not sure how well it works, if you need to menu to expand. But for some use cases this can be an easy solutions.

If you need your menu items to expand, you can probably also do this with views, but my guess is that it will require more work and maybe some custom code to fetch the menu items under the active menu item.

6
  • Thanks for clearing things out and for linking to that issue. For my use case I only got content menu items connected to nodes, so they do have language information. The only problem seems to be that the getEntity method of the menu item is protected and there is no other method on the menu item class to retrieve the language of the menu item (see here goo.gl/aXQa9u). If there was, my issue would be solved. Maybe I should file a core issue about adding a public method to get the language of a MenuLinkContent instance, or perhaps there is a reason that no such method exist?
    – reekris
    Feb 28, 2016 at 16:34
  • @Filip You can use ::language to get the language of a menu item and ::getLanguages to get all languages. You should see ContentEntityBase.
    – googletorp
    Feb 29, 2016 at 9:26
  • That's what I thought at first but there seems to be two different MenuLinkContent classes. When filtering the menu tree using a transform what you get for each item is an instance of Drupal\menu_link_content\Plugin\Menu\MenuLinkContent which is different from Drupal\menu_link_content\Entity\MenuLinkContent. The plugin one is not a subclass of ContentEntityBase and does not have any language property. What it does have however is a getEntity method to fetch the underlying MenuLinkContent entity. The problem is, it's marked as protected so I cannot call in my transform method.
    – reekris
    Feb 29, 2016 at 17:32
  • @Filip When working with entity based menu links (menu links created in drupal) you don't need to deal with the plugin class.
    – googletorp
    Mar 1, 2016 at 9:13
  • But I do need to deal with the Plugin class because I'm trying to filter out menu items using a MenuLinkTree transform (as shown here goo.gl/hJhqXa and in my original question). When doing this and looping through the tree, each item in the tree is an instance of the plugin class even if the underlying menu item is an entity based menu item. But if you know any other way of filtering a menu tree besides using a transform, that would allow me to deal with the menu entity class instead and thereby get the menu item language, please enlighten me!
    – reekris
    Mar 1, 2016 at 21:31
1

Updated code snippet from @Sockiplast to remove depricated function entityManager(). Improvement has also been made to remove menu items from the entire tree and not only first level menu depth.

use Drupal\Core\Menu\MenuLinkInterface;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;

/**
* Implements hook_preprocess_menu().
*/
function MY_MODULE_preprocess_menu(&$variables) {

  if ($variables['menu_name'] == 'main') {
    // Get context language.
    $language = Drupal::languageManager()->getCurrentLanguage()->getId();

    if (isset($variables['items'])) {
      MY_MODULE_menu_item_cleanup($variables['items'], $language);
    }
  }
}

/**
 * Recursive function to remove menu items with different language
 * than context language.
 *
 * @param $items
 *   Menu items for current menu depth.
 * @param $language
 *   The context language.
 */
function MY_MODULE_menu_item_cleanup(&$items, $language) {

  // Check language for current menu depth.
  foreach ($items as $key => &$item) {
    $menuLinkEntity = MY_MODULE_load_link_entity_by_link($item['original_link']);

    // Ignore if we don´t have a menu object.
    if ($menuLinkEntity != NULL) {
      $languages = $menuLinkEntity->getTranslationLanguages();

      // Remove links with different language than context.
      if (!array_key_exists($language, $languages)) {
        unset($items[$key]);
      }
      // Recursive call until no more below items exists.
      else if (isset($items[$key]['below'])) {
        MY_MODULE_menu_item_cleanup($items[$key]['below'], $language);
      }
    }
  }
}

function MY_MODULE_load_link_entity_by_link(MenuLinkInterface $menuLinkContentPlugin) {
  $entity = NULL;

  if ($menuLinkContentPlugin instanceof MenuLinkContent) {
    list($entity_type, $uuid) = explode(':', $menuLinkContentPlugin->getPluginId(), 2);
    $entity = \Drupal::service('entity.repository')->loadEntityByUuid($entity_type, $uuid);
  }
  return $entity;
}
0

I found another way of solving this which is much simpler and allows my to bypass the whole MenuLinkTree manipulators thing. Instead I simply add a hook_node_access and restrict access to the node if it isn't translated in the currently viewed language with $node->language(). This hides the menu items since the MenuLinkTree service checks the node access when getting each menu item.

Note: this only works for content menu items connected to a node. In my case that is just what I needed.

I got the idea from this answer by @Berdir: How do I filter entity reference fields based on language?

Here is a gist of my solution, which also includes filtering paragraphs and my custom inline_entity by language. https://gist.github.com/reekris/e6f64de799df75d724c2

0

Another solution which I use at the moment is the module Menu Multilingual. You get multilingual options on each menu block:

enter image description here

And with the selected option the menus only consist items with translations in the current language or with neutral language settings.

Your Answer

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

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