settings->all() ) { printf( '

%s

', esc_html__( 'First add languages on the "Languages" page.', 'lingocanvas' ) ); return; } $rows = array( array( 'key' => -1, 'title' => __( 'Language', 'lingocanvas' ), 'url' => self::MARKER_DROPDOWN, 'label' => __( 'Dropdown list', 'lingocanvas' ), 'description' => __( 'A single item; the languages open on hover or click, like a regular theme submenu.', 'lingocanvas' ), ), array( 'key' => -2, 'title' => __( 'Language switcher', 'lingocanvas' ), 'url' => self::MARKER_ROW, 'label' => __( 'In a row', 'lingocanvas' ), 'description' => __( 'The languages are visible right away as separate items in a row, with no dropdown.', 'lingocanvas' ), ), ); ?>

$items Пункты меню, уже готовые к выводу. * @return array */ public function injectLanguages( $items ): array { if ( ! is_array( $items ) ) { return $items; } $languages = array_values( $this->settings->published() ); if ( count( $languages ) < 2 ) { // Переключаться не на что — маркеры без языков были бы пустой ссылкой. return array_values( array_filter( $items, array( $this, 'isNotMarker' ) ) ); } $current = $this->resolver->current(); $result = array(); foreach ( $items as $item ) { $marker = $this->markerType( $item ); if ( null === $marker ) { $result[] = $item; continue; } if ( 'dropdown' === $marker ) { $result[] = $this->prepareDropdownTrigger( $item, $current ); foreach ( $languages as $index => $language ) { $result[] = $this->buildItem( $item, $language, $current, (int) $item->db_id, $index ); } continue; } // «В ряд»: сам маркер становится первым языком, остальные — соседи. $this->applyLanguageToItem( $item, $languages[0], $current ); $result[] = $item; for ( $index = 1, $count = count( $languages ); $index < $count; $index++ ) { $result[] = $this->buildItem( $item, $languages[ $index ], $current, 0, $index ); } } return $result; } /** * Добавляет класс `mlp-language-item` прямо на `` языкового пункта. * * Ядро WordPress выводит `$item->classes` только на `
  • `. Этого обычно * достаточно, но InternalLinks::apply() (второй, независимый проход по * готовому HTML, который добавляет языковой префикс к ссылкам темы) * должен уметь узнать ссылку переключателя, даже если тема подменяет * Walker и не выводит классы `
  • ` как ожидается. Класс прямо на `` — * это гарантия, не зависящая от чужого кода вывода меню. * * @param array $atts Атрибуты ссылки. * @param object $item Пункт меню. * @return array */ public function filterLinkAttributes( $atts, $item ): array { $atts = is_array( $atts ) ? $atts : array(); if ( ! in_array( 'mlp-language-item', (array) ( $item->classes ?? array() ), true ) ) { return $atts; } $existing = isset( $atts['class'] ) ? trim( (string) $atts['class'] ) : ''; $atts['class'] = '' !== $existing ? $existing . ' mlp-language-item' : 'mlp-language-item'; return $atts; } /** * Не является ли пункт нашим маркером. * * @param object $item Пункт меню. */ private function isNotMarker( object $item ): bool { return null === $this->markerType( $item ); } /** * Тип маркера, если пункт им является. * * @param object $item Пункт меню. */ private function markerType( object $item ): ?string { $url = isset( $item->url ) ? (string) $item->url : ''; if ( self::MARKER_DROPDOWN === $url ) { return 'dropdown'; } return self::MARKER_ROW === $url ? 'row' : null; } /** * Готовит пункт-триггер выпадающего списка: сам он никуда не ведёт, * а помечается как имеющий дочерние пункты — так тема покажет подменю * своими же стилями, ничего кроме класса ей для этого не нужно. * * @param object $item Пункт-маркер. * @param Language $current Язык текущей страницы. */ private function prepareDropdownTrigger( object $item, Language $current ): object { $item->url = '#'; /** * Подпись пункта, открывающего список языков. * * По умолчанию — текущий язык в том же виде, что и остальные пункты * (флаг, код, название — как выбрано в настройках). Статичная * надпись вроде «Язык» на этом месте сообщает посетителю ровно * ничего: он и так видит, что это переключатель языков, а вот на * каком языке страница сейчас — не видит, пока не откроет список. * * Название пункта, заданное в «Внешний вид → Меню», при этом * теряется. Кому нужно именно оно — возвращает его этим фильтром. * * @since 0.3.13 Имя фильтра — `lingocanvas_nav_menu_trigger_title`. * * @param string $title Подпись по умолчанию (может быть разметкой). * @param Language $current Язык текущей страницы. * @param object $item Сам пункт-маркер, с исходным `title`. */ $title = Flags::switcherMarkup( $current, $this->settings->switcherDisplay() ); $item->title = (string) apply_filters( 'lingocanvas_nav_menu_trigger_title', $title, $current, $item ); /** * Классы триггера выпадающего списка. * * `menu-item-has-children` понимает подавляющее большинство тем — * это тот же класс, что WordPress сам добавляет обычным пунктам * с дочерними. Мега-меню и кастомные JS-переключатели темы иногда * ждут свой класс или data-атрибут — фильтр позволяет его добавить, * не трогая код плагина. * * @since 0.3.13 Имя фильтра — `lingocanvas_nav_menu_dropdown_classes`. * * @param list $classes Классы пункта-триггера. * @param object $item Сам пункт меню. */ $classes = array_merge( is_array( $item->classes ) ? $item->classes : array(), array( 'mlp-language-switcher-trigger', 'menu-item-has-children' ) ); $item->classes = array_values( array_unique( apply_filters( 'lingocanvas_nav_menu_dropdown_classes', $classes, $item ) ) ); return $item; } /** * Синтезирует пункт меню для одного языка на основе маркера. * * Клонирование маркера, а не сборка объекта с нуля: маркер — уже * настоящий объект пункта меню WordPress со всеми полями, которые * ожидает Walker и тема. Переопределяются только те, что должны * отличаться для конкретного языка. * * @param object $marker Пункт-маркер, с которого клонируем. * @param Language $language Язык, для которого собирается пункт. * @param Language $current Язык текущего запроса. * @param int $parentDbId 0 для «в ряд», db_id маркера для выпадающего списка. * @param int $index Порядковый номер языка — часть уникального ID. */ private function buildItem( object $marker, Language $language, Language $current, int $parentDbId, int $index ): object { $item = clone $marker; $id = self::SYNTHETIC_ID_BASE + ( (int) $marker->db_id ) * 1000 + $index; $item->ID = $id; $item->db_id = $id; $item->object = 'custom'; $item->object_id = 0; $item->type = 'custom'; $item->type_label = __( 'Custom link', 'lingocanvas' ); $item->menu_item_parent = $parentDbId; $item->menu_order = (int) $marker->menu_order + $index + 1; $item->description = ''; $item->attr_title = ''; $item->target = ''; $item->xfn = ''; $this->applyLanguageToItem( $item, $language, $current ); return $item; } /** * Подставляет адрес, подпись и классы конкретного языка в пункт меню. * * @param object $item Пункт меню (маркер или его клон). * @param Language $language Язык. * @param Language $current Язык текущего запроса. */ private function applyLanguageToItem( object $item, Language $language, Language $current ): void { // Страница скрыта на этом языке — ведём на его главную (см. LanguageSwitcher::urlFor()). $item->url = null !== $this->pageLanguages && $this->pageLanguages->isHiddenOn( $language ) ? $this->urls->homeFor( $language ) : $this->urls->switchUrlFor( $language ); /* * Подпись собирается там же, где и для шорткода-переключателя, * иначе меню и переключатель показывали бы языки по-разному. * WordPress выводит название пункта меню без экранирования, так * что картинка флага сюда доходит как разметка. */ $item->title = Flags::switcherMarkup( $language, $this->settings->switcherDisplay() ); $classes = array( 'mlp-language-item', 'mlp-language-item-' . $language->locale ); if ( $language->locale === $current->locale ) { $classes[] = 'current-lang'; $classes[] = 'current-menu-item'; } $item->classes = $classes; } }