Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

It’s required to have an hreflang attribute per language and region to help Google identify what content needs to be served for visitors in particular geolocation regions. It’s also good practice for SEO. Make sure your sitemap includes an hreflang alternate for every translation. They can be created using hook_page_attachments_alter

Code Block
languagephp
/**
 * Implements hook_page_attachments_alter().
 */
function example_page_attachments_alter(array &$attachments) {
  if (isset($attachments['#attached']['html_head_link'])) {
    $node = \Drupal::routeMatch()->getParameter('node');
    $host = \Drupal::request()->getSchemeAndHttpHost();
    if ($node instanceof NodeInterface) {
      $siblings = \Drupal::service('example.context_helpers')->getSiblings($node);
      if (!empty($siblings)) {
        $paths = \Drupal::service('example.context_helpers')->getAliases($siblings);
        if (!empty($paths)) {
          $links = [];
          $props = [];
          foreach ($paths as $i => $path) {
            $props[0]['rel'] = 'alternate';
            $props[0]['hreflang'] = $path['lang'];
            $props[0]['href'] = $host . $path['link'];
            $props[1] = TRUE;
            $links[] = $props;
          }
        }
      }
    }
    foreach ($attachments['#attached']['html_head_link'] as $key => $link) {
      if (isset($link[0]['rel']) && !empty($link[0]['rel']) && $link[0]['rel'] == 'alternate' && isset($link[0]['href'])) {
        $href = $link[0]['href'];
        $href = str_replace('https://', '', $href);
        $href = str_replace('http://', '', $href);
        $href_arr = explode('/', $href);
        if (isset($href_arr[2]) && $href_arr[2] !== 'node') {
          $attachments['#attached']['html_head_link'][$key][0]['hreflang'] = $href_arr[1] . '-' . $href_arr[2];
        }
      }
    }
    
    // Setting default one.
    $attachments['#attached']['html_head_link'][] = [['rel' => 'alternate', 'hreflang' => 'x-default', 'href' => 'https://example.com']];
  }
}

Service helper functions you should place in your service class:

Code Block
  /**
   * Helper function to get siblings of the node if sibling relationship exists.
   *
   * @param Node $node
   *    node interface.
   *
   * @return array $siblings
   *    array of node siblings ids.
   */
  public function getSiblings(Node $node) {
    $siblings = [];
    if (isset($node->field_master_node)) {
      $master = $node->field_master_node->target_id;
      if (isset($master)) {
        $siblings = $this->getChildNodes($master);
      }
    }
    return $siblings;
  }
  /**
   * Helper function to get node aliases and language / region prefix by nid.
   *
   * @param array $nids
   *    Node ids.
   *
   * @return array $aliases
   *    Array of aliases.
   */
  public function getAliases($nids) {
    $languages = \Drupal::languageManager()->getLanguages();
    if (!is_array($nids)) {
      $nids = [$nids];
    }
    $aliases = [];
    $i = 0;
    foreach ($nids as $nid) {
      foreach ($languages as $lng_code => $lang) {
        $alias = \Drupal::service('path.alias_manager')->getAliasByPath('/node/' . $nid, $lng_code);
        if (strpos($alias, '/node/') === FALSE) {
          $al_arr = explode('/', $alias);
          $alias = '/' . $lng_code . $alias;
          $aliases[$i]['link'] = $alias;
  
          $aliases[$i]['lang'] = $al_arr[1] . '-' . $lng_code;
          $i++;
        }
      }
    }
    return $aliases;
  }

Rabbit Hole

Rabbit hole is a module that allows you to prevent bots and users from accessing nodes and other entities that shouldn’t have a full page display. The example would be CTA content type nodes used inside views but not necessarily requiring full page display.

...