Большинство рекомендаций WordPress hreflang - это & quot;установите многоязычный плагин, и он будет обрабатывать его. & quot; Это нормально, когда плагин владеет вашими переводами. Это неправильный ответ в случае, когда я нажимаю чаще всего: сайт с двумя или тремя языковыми версиями, построенными как отдельные подкаталоги, отдельные установки или набор вручную переведенных страниц, где никто не хочет полную структуру перевода и схему его базы данных для четырех страниц.
Я поддерживаю свойства WordPress в такой форме, включая сайты документации, где переводы написаны от руки и под которыми они живут /de/ а /es/. Рефланг есть сорок строк в файле темы, он читает из одного массива, и он никогда не дрейфовал Это руководство является тем кодом, плюс части, которые идут не так, когда плагин излучает рефланг рядом с вашей темой.
Он сидит под полный путеводитель по грефлангу, рядом с Версия Next.js, йо-
TL;DR: WordPress не излучает рефланг самостоятельно. Крючок
wp_head, перейдите через карту локалей и распечатайте полный обратный набор, включаяx-defaultна каждой странице в группе. Build URL сhome_url()или происхождение для каждого локаля, а не жесткое кодирование, сохраните каждую страницу и каноническую самореферентность #39;s и проверьте, что ни один плагин SEO уже не излучает конкурирующий блок. Для многосайтовых настроек,get_sites()дает карту; для одного сайта с переведенными страницами достаточно аналогов для сопоставления метаполей публикации.
Добавляет ли WordPress рефланг сам по себе?
№ Core выводит каноническую ссылку, короткую ссылку и различные каналы связи и REST wp_head, но в языке ничего не меняется. get_locale() знает, на каком языке находится сайт, и ни одна часть ядра не превращает его в аннотацию.
Плагины заполняют пробел. Polylang и WPML излучают hreflang для переводов, которыми они управляют, и большинство плагинов SEO будут излучать его, если вы настраиваете отношения перевода, о которых они могут видеть. если вы уже используете один из них, и он охватывает весь ваш сайт, используйте его и пропустите эту статью. Причина ручной прокрутки заключается в том, что ваши переводы существуют вне того, о чем знает плагин, а плагин, который излучает частичный набор, хуже того, который не излучает ни одного.
Минимальная версия: один массив, один крючок
Для сайта, переводы которого являются известным, небольшой набор префиксов URL:
<?php
/**
* Hreflang annotations for a hand-translated multilingual site.
* Drop in the theme's functions.php or a small mu-plugin.
*/
function mysite_hreflang_map(): array {
return [
'en-US' => 'https://example.com',
'de-DE' => 'https://example.com/de',
'es-ES' => 'https://example.com/es',
'x-default' => 'https://example.com',
];
}
function mysite_print_hreflang(): void {
$path = mysite_current_path(); // e.g. '/pricing/'
foreach ( mysite_hreflang_map() as $hreflang => $origin ) {
printf(
'<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
esc_attr( $hreflang ),
esc_url( untrailingslashit( $origin ) . $path )
);
}
}
add_action( 'wp_head', 'mysite_print_hreflang', 1 );
Три вещи, которые можно сделать правильно, обычно это не делают рукописные блоки.
Он печатает полный комплект на каждой странице, включая страницу's собственный языкВзаимность удовлетворяется структурно: нет ветки, которая могла бы опустить запись, потому что ветки нет вообще.
x-default является строкой в одном массиве, поэтому он излучается одним и тем же циклом и не может быть забыт на одном шаблоне.
URL-адреса экранированы. esc_url на href в wp_head не является необязательным, и значения hreflang проходят esc_attr по той же причине.
Выработка текущего пути
Путь - это та часть, которая нуждается в уходе, потому что он должен быть путем в пределах язык, а не полный URI запроса.
function mysite_current_path(): string {
$home = wp_parse_url( home_url(), PHP_URL_PATH ) ?: '';
$req = wp_parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) ?: '/';
// Strip the language prefix this install is served under.
if ( $home && str_starts_with( $req, $home ) ) {
$req = substr( $req, strlen( $home ) );
}
return user_trailingslashit( '/' . ltrim( $req, '/' ) );
}
Если ваша немецкая установка живет по адресу example.com/de и посетитель включен example.com/de/preise/, это возвращается /preise/, который затем получает префикс с каждой локалью's origin.The trailing slash readmanding имеет большее значение, чем выглядит: /preise а /preise/ это разные URL-адреса, и указание аннотации на неканоническую форму - это несоответствие задней косой черты это приводит к ошибкам обратной метки в наборе, который в противном случае выглядит идеально. user_trailingslashit уважает настройку permalink сайта и #39;s, поэтому она соответствует всему, что обслуживает WordPress.
Здесь предполагается, что переведенные URL-адреса структурно параллельны: /pricing/ карты на /de/pricing/. Если ваш немецкий slugs тоже переведен, /de/preise/, этой функции недостаточно, и вам нужно явное отображение, которое является следующим разделом.
Когда slugs отличаются: постмета-сопоставление
Переведенные slugs лучше для пользователей и для ранжирования, и они означают, что путь не может быть выведен. вместо этого храните аналоги:
/**
* Meta key holds a JSON map: {"de-DE": 123, "es-ES": 456} of post IDs,
* or absolute URLs for external installs.
*/
function mysite_alternates_for( int $post_id ): array {
$raw = get_post_meta( $post_id, '_hreflang_alternates', true );
$map = $raw ? json_decode( $raw, true ) : [];
if ( ! is_array( $map ) ) {
return [];
}
$out = [];
foreach ( $map as $hreflang => $target ) {
$url = is_numeric( $target ) ? get_permalink( (int) $target ) : $target;
if ( $url ) {
$out[ $hreflang ] = $url;
}
}
return $out;
}
Затем wp_head обратный вызов объединяет текущую запись и #39;s собственную запись на этой карте перед печатью, поэтому набор является полным и самореферентным:
function mysite_print_hreflang_mapped(): void {
if ( ! is_singular() ) {
return;
}
$post_id = get_queried_object_id();
$alts = mysite_alternates_for( $post_id );
if ( count( $alts ) < 1 ) {
return; // Nothing to declare: a one-entry cluster is not a cluster.
}
$alts[ mysite_hreflang_for_locale( get_locale() ) ] = get_permalink( $post_id );
$alts['x-default'] = $alts['en-US'] ?? get_permalink( $post_id );
foreach ( $alts as $hreflang => $url ) {
printf(
'<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
esc_attr( $hreflang ),
esc_url( $url )
);
}
}
Стоимость обслуживания заключается в том, что мета должен быть написан с обеих сторон. пользовательский интерфейс администратора с выборщиком аналогов - это честное решение; команда WP-CLI, которая заполняет карту из CSV, является быстрой. Чего вы не должны делать, так это заполнять ее только с одной стороны, потому что одностороннее объявление - это именно то отсутствующий тег возврата провал.
Мультисайт: получите карту из сети
Если каждый язык является сайтом в многосайтовой сети, карта уже существует:
function mysite_network_hreflang_map(): array {
$map = [];
foreach ( get_sites( [ 'number' => 50 ] ) as $site ) {
switch_to_blog( (int) $site->blog_id );
$map[ mysite_hreflang_for_locale( get_locale() ) ] = home_url();
restore_current_blog();
}
return $map;
}
Кэшировать результат в переходный период; switch_to_blog в а wp_head крюк на каждый запрос - это реальная стоимость на сети любого размера. И будьте осторожны с get_locale() под switch_to_blog: он отражает коммутируемый сайт только в том случае, если сайт's WPLANG установлен параметр, что является обычным случаем, но не гарантируется в сетях, настроенных через константу.
Какой подход подходит вашему сайту?
| снабжать | подход | Усилия |
|---|---|---|
| Параллельно slugs, несколько локалей | Статический массив плюс производный путь | Самый низкий; одна функция |
| Переведено slugs | Карта-аналог постмета | Умеренный; нужен рабочий процесс редактирования |
| Мультисайт, по одному сайту на язык | Получить из get_sites(), кэшировано |
Низкий код, нужна дисциплина кэша |
| Polylang или WPML, управляющие переводами | Используйте вывод плагина's | Проверьте, что он излучает x-по умолчанию |
| Плагин SEO с полями ручного перевода | Используйте его, затем проверьте визуализированную головку | Следите за дублирующими блоками |
Вместо этого поместите на карту сайта рефланг
Если заголовки ваших страниц уже переполнены, или у вас достаточно локалей, что блок длиннее остальной части головы, альтернативой является карта сайта. WordPress 5.5 и более поздняя поставка основные карты сайта, и они поддаются фильтрации, хотя фильтры не были разработаны с учетом альтернатив и объявления пространства имен <urlset> это не то, что ядро позволяет вам чисто прикасаться.
Прагматичный маршрут - это отдельная карта сайта, подаваемая на основе правила переписывания, а не боя wp_sitemaps,
add_action( 'init', function () {
add_rewrite_rule( '^sitemap-i18n\.xml$', 'index.php?mysite_i18n_sitemap=1', 'top' );
} );
add_filter( 'query_vars', fn( $vars ) => array_merge( $vars, [ 'mysite_i18n_sitemap' ] ) );
add_action( 'template_redirect', function () {
if ( ! get_query_var( 'mysite_i18n_sitemap' ) ) {
return;
}
header( 'Content-Type: application/xml; charset=UTF-8' );
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ';
echo 'xmlns:xhtml="http://www.w3.org/1999/xhtml">' . "\n";
foreach ( mysite_translated_paths() as $path ) {
echo " <url>\n <loc>" . esc_url( home_url( $path ) ) . "</loc>\n";
foreach ( mysite_hreflang_map() as $hreflang => $origin ) {
printf(
' <xhtml:link rel="alternate" hreflang="%s" href="%s"/>' . "\n",
esc_attr( $hreflang ),
esc_url( untrailingslashit( $origin ) . $path )
);
}
echo " </url>\n";
}
echo '</urlset>';
exit;
} );
Две вещи, которые нужно сделать правильно. The xmlns:xhtml декларация о <urlset> требуется; без него альтернативные записи игнорируются, и файл по-прежнему проверяется как карта сайта, которая снова является шаблоном бесшумного отказа. И правила перезаписи Flush один раз после добавления правила, при активации, а не при каждом запросе.
Сделав это, удалите головной вывод. Исключить оба - это методы реализации смешивания, что дает вам два дрейфующих источника.
Проблема дублирующего блока
Это WordPress-специфичный сбой и тот, который стоит проверить, прежде чем писать какой-либо код. несколько плагинов излучают hreflang, и более одного может быть активным одновременно. плагин SEO, настроенный с отношениями перевода, многоязычный плагин, и ваша новая функция темы все будет счастливо печатать, чтобы wp_head, и в результате получается два или три блока с разным содержанием и, возможно, два x-default записи.
Обработка противоречивых аннотаций в Google's на одной странице призвана им не доверять. Таким образом, сайт, у которого не было рефланга и теперь есть три блока, может оказаться в худшем положении, чем раньше.
Проверьте отображаемый вывод, а не настройки плагина:
curl -s https://example.com/de/preise/ | grep 'rel="alternate"' | grep hreflang
Подсчитайте строки и посчитайте отдельные коды. Одна запись на локаль плюс одна x-default, и ничего не повторяется. Если плагин излучает набор, который вы не можете настроить, обычный аварийный люк - удалить его действие wp_head вместо того, чтобы добавлять свой сверху:
// Example shape: find the real hook and callback with `has_action`.
add_action( 'init', function () {
remove_action( 'wp_head', 'some_plugin_hreflang_output', 10 );
} );
Канонические теги, а не ломающие их
WordPress излучает rel="canonical" для отдельных видов через rel_canonical(), и он указывает на post's собственный permalink, что вы хотите Большинство SEO плагинов заменяют его на свой собственный, также самореферентный по умолчанию.
Правило из грефланг против канонического применяется без изменений: каждая страница канонизируется к себе, и hreflang описывает отношения между этими самоканоническими страницами. способ взлома со вкусом WordPress - это настройка плагина, которая канонизирует переведенные сообщения в & quot;primary& quot; языковая версия, которая читается как разумный вариант дедупликации и незаметно удаляет ваши переводы из индекса. Если настройка предлагает канонизировать разные языки, оставьте это в стороне.
Проверка результата
Получите две родственные страницы и сравните их блоки аннотаций Они должны быть идентичными, с только каноническим различающимся Затем проверьте, что каждый URL-адрес, который вы назвали, возвращает 200 без перенаправления, потому что удаленные локали и измененные permalinks - это то, как правильный набор гниет в сломанный.
Вставьте набор в бесплатный генератор грефланга для проверки кодов: каждое значение проверяется на соответствие форме ISO, флагируются нераспознанные подтеги, например en-UK, ловит дубликаты, отклоняет относительные URL-адреса и предупреждает, когда набор не имеет запасных частей. Он полностью работает в браузере, что имеет значение, когда URL-адреса описывают клиентский сайт, который еще не является общедоступным. Затем используйте Search Console's International Targeting отчет для текущей проверки, и руководство инструмента Hreflang для рабочего процесса вокруг него.
Часто задаваемые вопросы
Добавляет ли WordPress теги hreflang автоматически?
Нет. Core излучает каноническую ссылку и различные ссылки на заголовки но ничего о языке чередуется.Hreflang приходит от плагина или от вашего собственного wp_head выход.
Как добавить hreflang в WordPress без плагина?
Крюк wp_head и напечатать а <link rel="alternate" hreflang> строка для каждого местоположения на карте, которую вы контролируете, включая x-default запись, на каждой странице в группе. Escape the values с esc_attr а esc_url, йо-
Куда идут теги hreflang в теме WordPress?
В функции, к которой подключено wp_headв идеале в небольшом мю-плагине, а не в теме, поэтому переключение тем не приводит к молчанию ваших аннотаций.
Что, если мои переведенные страницы будут разными slugs?
Ничего не выводить. храните идентификаторы или URL-адреса сообщений-партнеров в мета-посте и читайте карту во время рендеринга. Заполните его с обеих сторон или создайте отсутствующий тег возврата.
Обращаются ли Polylang и WPML с hreflang?
Да, для переводов, которыми они управляют. проверьте вывод включает в себя x-default и что ни один второй плагин не излучает конкурирующий блок.
Почему у меня на одной странице два блока-рефланга?
Две вещи печатаются wp_head: обычно плагин SEO плюс многоязычный плагин, или плагин плюс ваша собственная функция Противоречивые аннотации на одной странице не доверяют, поэтому удалите один, а не многоуровневый.
Должна ли каждая языковая версия WordPress иметь свой канонический характер?
Да, самореферентный Не используйте настройку плагина, которая канонизирует переведенные сообщения на основной язык, потому что это удаляет их из индекса и отменяет набор hreflang.
Как добавить hreflang в многосайтовую сеть WordPress?
Петля get_sites()переключитесь на каждый блог, чтобы прочитать его местоположение и home_url(), и кэшировать полученную карту в переходном периоде. Бег switch_to_blog по каждому запросу в wp_head дорого в большой сети.



