Для начала разберемся: что мы пытаемся построить. Ожидается, что у нас есть корневой файл sitemap.xml, который содержит список языковых xml карт.
Для двух языков это может выглядеть вот так:
Т.е. каждый язык у нас имеет собственную карту сайта.
Помимо этого, каждая описываемая ссылка в карте должна иметь перекрестную адресацию на другие языки (т.н. translation set).
В примере выше мы видим, что карта en-gb (английский, Великобритания) содержит всего две ссылки (Number of URLs).
Первая из них имеет вариант для языка по умолчанию для сайта (у ссылки на языке «по умолчанию» отстутствует префикс языка) — потому «translation set» состоит из 2х ссылок, а вторая переведена еще на два языка, а потому там три ссылки.
Приведенные выше примеры построены на базе модуля simple_sitemap версии 4. «Из коробки» нужного результата не достичь, потому мы обсудим как правильно настроить сам модуль, какой дополнительный код потребуется и даже добавим небольшой патч.
Патчим модуль
Начнем с самого простого — с патча.
Проблема модуля в том, что он отфильтровывает сущности по полю published или status, требуя чтобы значение поля было «1». Это отлично работает, но только не в случае, если у нас многоязычный сайт.
Для многоязычного сайта это будет работать так: все переводы статьи будут попадать в карту сайта только если публикация на основном языке опубликована и наоборот. Но у каждого из переводов есть свой статус «опубликовано» и нужно руководствоваться его значением.
Собирается это условие в
src/Plugin/simple_sitemap/UrlGenerator/EntityUrlGenerator.php
Просто закомментируем этот код и включим это изменение как патч в настройки composer.json
Настойки модуля simple_sitemap
4я версия модуля позволяет настраивать несколько карт и корневой список для них. Но не даёт нам возможности связать как то их с конкретным языком (это мы будем делать как дополнительный код).
В 3й версии не было и этой возможности, приходилось писать много дополнительного кода, чтобы организовать нужную структуру из карт. Прогресс на лицо!
Типы карт
Для начала настроим два типа карт (/admin/config/search/simplesitemap/types)
Корневой (sitemap index) включает только генератор «Sitemap URL», т.е. ссылки на карты. А default — включает остальные генераторы.
Карты сайта
На базе созданных типов можно создавать сами карты.
Списки публикаций для каждого языка имеют свою карту, тип — default. А корневая карта использует тип «sitemap index«.
Настройки контента
Теперь нужно задать параметры, задав какой контент включать в карты сайта. Это делается вот тут — /admin/config/search/simplesitemap/entities. Каждый тип публикации нужно сконфигурировать в какую карту он входит. Настройки для каждой языковой карты одинаковые.
Без дополнительного кода, которым мы займемся ниже, такие настройки создадут нам идентичные карты сайта.
Дополнительный код
Модуль позволяет реализовать хук — HOOK_simple_sitemap_links_alter(&$links, $sitemap).
Он вызывается для каждой карты сайта, при этом поставляется список ссылок, которые были собраны согласно настройкам контента.
Основная задача — отфильтровать неопубликованные элементы и ссылки, относящиеся к другим языкам.
Побочные задачи — это поменять языковые коды, которые в Drupal отличаются от того, что например ожидает google. А еще — удалить дубли ссылок, которые могут возникать при добавлении ссылок вручную и приходить из настроек меню.
Т.к. требуемый набор языков — зависит от вашей частной задачи, то я могу привести свой код лишь как пример того, как это сделано для одного из моих проектов, в котором используется более 15 языков. Для себя вам придется этот код адаптировать.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
/** * Implements hook_simple_sitemap_links_alter(). */ function HOOK_simple_sitemap_links_alter(&$links, SimpleSitemapInterface $sitemap) { $variant = $sitemap->id(); // Key - variant name, // value - hreflang code. static $hreflang_variants = [ 'default' => 'en', 'en' => 'en', 'uk' => 'en-gb', 'nl' => 'nl', 'be-fr' => 'fr-be', 'be-nl' => 'nl-be', 'fr' => 'fr-fr', 'fr-ca' => 'fr-ca', 'en-ca' => 'en-ca', 'da' => 'da', 'fi' => 'fi', 'no' => 'no', 'sv' => 'sv', 'ie' => 'en-ie', 'tr' => 'tr', 'es' => 'us-es' ]; // Подмена hreflang правильным значением (по google) foreach ($links as $key => $link) { if ($link['langcode'] == 'be-nl') { $links[$key]['langcode'] = 'nl-be'; } if ($link['langcode'] == 'be-fr') { $links[$key]['langcode'] = 'fr-be'; } if ($link['langcode'] == 'fr') { $links[$key]['langcode'] = 'fr-be'; } foreach ($link['alternate_urls'] as $code => $url) { if ($code == 'be-nl') { unset($links[$key]['alternate_urls']['be-nl']); $links[$key]['alternate_urls']['nl-be'] = $url; } if ($code == 'be-fr') { unset($links[$key]['alternate_urls']['be-fr']); $links[$key]['alternate_urls']['fr-be'] = $url; } if ($code == 'fr') { unset($links[$key]['alternate_urls']['fr']); $links[$key]['alternate_urls']['fr-be'] = $url; } } } // Удаление ссылок в контексте языка карты foreach ($links as $key => $link) { if (isset($link['langcode'])) { try { if ($link['langcode'] != $hreflang_variants[$variant]) { unset($links[$key]); } } catch (\Exception $e) { ; } } } // Key - variant name, value - drupal langcode. // See: /admin/config/regional/language. static $langcode_variants = [ 'default' => 'en', 'en' => 'en', 'uk' => 'en-gb', 'nl' => 'nl', 'be-fr' => 'fr', 'be-nl' => 'be-nl', 'fr' => 'fr-fr', 'fr-ca' => 'fr-ca', 'en-ca' => 'en-ca', 'da' => 'da', 'fi' => 'fi', 'no' => 'no', 'sv' => 'sv', 'ie' => 'en-ie', 'tr' => 'tr', 'es' => 'us-es', ]; // Удаление отсутствующих или не опубликованных переводов. foreach ($links as $key => $link) { if (!empty($link['meta']['entity_info']['entity_type']) && $link['meta']['entity_info']['entity_type'] == 'node') { $node = Node::load($link['meta']['entity_info']['id']); $languages = $node->getTranslationLanguages(TRUE); if (!isset($languages[$langcode_variants[$variant]])) { unset($links[$key]); continue; } foreach ($langcode_variants as $variant_name => $langcode) { if (!isset($languages[$langcode])) { unset($links[$key]['alternate_urls'][$hreflang_variants[$variant_name]]); } else { $translation = $node->getTranslation($langcode); if (!$translation->status->value) { // перевод не опубликован if ($variant_name == $variant) { unset($links[$key]); break; } else { unset($links[$key]['alternate_urls'][$hreflang_variants[$variant_name]]); } } } } } } // Удаление дублей ссылок. $paths = []; foreach ($links as $key => $link) { if (in_array($link['url'], $paths)) { unset($links[$key]); } else { $paths[] = $link['url']; } } } |
Массив $hreflang_variants связывает машинное имя карты с именем языка. У меня он задаётся дважды, что связано с двуязычными странами типа Бельгии и Канады. И для них приходится сначала править префикс, а потом уже выполнять основную задачу.
У вас эта карта будет собственная, и, возможно, вам не надо будет править префикс. Код будет вырождаться до следующего варианта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
/** * Implements hook_simple_sitemap_links_alter(). */ function HOOK_simple_sitemap_links_alter(&$links, SimpleSitemapInterface $sitemap) { $variant = $sitemap->id(); static $langcode_variants = [ 'default' => 'en', 'en' => 'en', 'uk' => 'en-gb', 'nl' => 'nl', 'fr' => 'fr-fr', 'da' => 'da', 'fi' => 'fi', 'no' => 'no', 'sv' => 'sv', 'ie' => 'en-ie', 'tr' => 'tr', 'es' => 'us-es' ]; // Удаление ссылок в контексте языка карты foreach ($links as $key => $link) { if (isset($link['langcode'])) { try { if ($link['langcode'] != $langcode_variants[$variant]) { unset($links[$key]); } } catch (\Exception $e) { ; } } } // Удаление отсутствующих или не опубликованных переводов. foreach ($links as $key => $link) { if (!empty($link['meta']['entity_info']['entity_type']) && $link['meta']['entity_info']['entity_type'] == 'node') { $node = Node::load($link['meta']['entity_info']['id']); $languages = $node->getTranslationLanguages(TRUE); if (!isset($languages[$langcode_variants[$variant]])) { unset($links[$key]); continue; } foreach ($langcode_variants as $variant_name => $langcode) { if (!isset($languages[$langcode])) { unset($links[$key]['alternate_urls'][$langcode_variants[$variant_name]]); } else { $translation = $node->getTranslation($langcode); if (!$translation->status->value) { // перевод не опубликован if ($variant_name == $variant) { unset($links[$key]); break; } else { unset($links[$key]['alternate_urls'][$langcode_variants[$variant_name]]); } } } } } } // Удаление дублей ссылок. $paths = []; foreach ($links as $key => $link) { if (in_array($link['url'], $paths)) { unset($links[$key]); } else { $paths[] = $link['url']; } } } |