A maioria dos conselhos do WordPress hreflang são & quot; instale um plugin multilíngue e ele o manipula." Tudo bem quando o plugin possui suas traduções É a resposta errada no caso em que EU bati com mais frequência: um site com duas ou três versões de idiomas construídas como subdiretórios separados, instalações separadas ou um conjunto de páginas traduzidas manualmente, onde ninguém quer uma estrutura de tradução completa e seu esquema de banco de dados para quatro páginas.
mantenho propriedades WordPress nesse formato, incluindo sites de documentação onde as traduções são escritas à mão e sob os quais moram /de/ e /es/. O hreflang há quarenta linhas em um arquivo de tema, ele lê de uma matriz, e nunca derivou Este guia é esse código, além das partes que dão errado quando um plugin está emitindo hreflang ao lado do seu tema.
Ele fica sob o guia completo de hreflang, ao lado do Próxima versão.js.
tl;dr: WordPress não emite hreflang por conta própria Gancho
wp_head, faça um loop sobre um mapa de localidade e imprima o conjunto recíproco completo, incluindox-defaultem todas as páginas do grupo. Construa URLs comhome_url()ou uma origem por local em vez de hardcoding, mantenha cada página & #39; s auto-referencial canônico, e verifique se nenhum plugin de SEO já está emitindo um bloco concorrente Para configurações de vários sites,get_sites()fornece o mapa; para um único site com páginas traduzidas, basta um post meta field mapping contrapartes.
O WordPress adiciona hreflang sozinho?
No. Core gera um link canônico, um shortlink e vários links feed e REST wp_head, mas nada sobre alternância de linguagem. get_locale() sabe em que idioma o site está e nenhuma parte do núcleo transforma isso em uma anotação.
Os plugins preenchem a lacuna Polylang e WPML emitem hreflang para as traduções que gerenciam, e a maioria dos plugins de SEO o emitirá se você configurar um relacionamento de tradução que eles possam ver Se você já estiver usando um desses e ele cobrir todo o seu site, use-o e pule este artigo A razão para o hand-roll é que suas traduções existem fora do que o plugin sabe sobre, e um plugin que emite um conjunto parcial é pior do que um que não emite nenhum.
A versão mínima: uma matriz, um gancho
Para um site cujas traduções são um pequeno conjunto conhecido de prefixos de 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 );
Três coisas que isso acerta que os blocos escritos à mão geralmente não fazem.
Ele imprime o conjunto completo em cada página, incluindo a página & #39; s linguagem própria A reciprocidade é satisfeita estruturalmente: não há ramificação que poderia omitir uma entrada, porque não há ramificação alguma.
x-default é uma linha na mesma matriz, portanto, é emitido pelo mesmo loop e não pode ser esquecido em um modelo.
Os URLs são escapados. esc_url em um href em wp_head não é opcional e os valores do hreflang são aprovados esc_attr pela mesma razão.
Trabalhando o caminho atual
O caminho é a parte que precisa de cuidados, porque tem que ser o caminho dentro o idioma, não o URI de solicitação completo.
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, '/' ) );
}
Se a sua instalação alemã viver em example.com/de e o visitante está ligado example.com/de/preise/, isso retorna /preise/0, que depois fica prefixado com cada locale' s origem O tratamento da barra à direita importa mais do que parece: /preise e /preise/ são URLs diferentes e apontar uma anotação na forma não canônica é o incompatibilidade trailing-slash isso produz erros de marcação de retorno em um conjunto que parece perfeito. user_trailingslashit respeita a configuração do link permanente do site e #39;, portanto, corresponde a tudo o que o WordPress serve.
A suposição aqui é que as URLs traduzidas são estruturalmente paralelas: /pricing/ mapas para /de/pricing/. Se o seu alemão slugs também for traduzido, /de/preise/, esta função não é suficiente e você precisa de um mapeamento explícito, que é a próxima seção.
Quando slugs diferem: um mapeamento pós-meta
Traduzido slugs são melhores para usuários e para classificação, e eles significam que o caminho não pode ser derivado Armazene as contrapartes em vez disso:
/**
* 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;
}
Então o wp_head o retorno de chamada mescla a entrada atual do post & #39; nesse mapa antes da impressão, para que o conjunto seja completo e autorreferencial:
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 )
);
}
}
O custo de manutenção é que o meta tem que ser escrito em ambos os lados Uma UI de administrador com um selecionador de contrapartida é a solução honesta; um comando WP-CLI que preenche o mapa de um CSV é o rápido O que você não deve fazer é preenchê-lo apenas de um lado, porque uma declaração unidirecional é exatamente o tag de retorno ausente falha.
Multisite: derive o mapa da rede
Se cada idioma é um site em uma rede multisite, o mapa já existe:
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;
}
Cache o resultado em um transitório; switch_to_blog em a wp_head gancho em cada pedido é um custo real em uma rede de qualquer tamanho. E tenha cuidado com get_locale() sob switch_to_blog: reflete o site comutado somente se o site's WPLANG a opção é definida, o que é o caso usual, mas não garantido em redes configuradas através de uma constante.
Qual abordagem se adapta ao seu site?
| instalação | aproximar-se | Esforço |
|---|---|---|
| Paralelo slugs, poucos locais | Matriz estática mais caminho derivado | Mais baixo; uma função |
| Traduzido slugs | Mapa de contrapartida pós-meta | Moderado; precisa de um fluxo de trabalho de edição |
| Multisite, um site por idioma | Derivar de get_sites(), armazenado em cache |
Código baixo, precisa de disciplina de cache |
| Polylang ou WPML gerenciando traduções | Use a saída do plugin's | Verifique se emite x-default |
| plugin SEO com campos de tradução manual | Use-o e verifique o cabeçote renderizado | Assista a blocos duplicados |
Colocando hreflang no mapa do site
Se seus cabeçalhos de página já estão lotados, ou você tem locais suficientes para que o bloco seja mais longo que o resto da cabeça, o mapa do site é a alternativa WordPress 5.5 e posterior envio sitemaps principais, e eles são filtráveis, embora os filtros não tenham sido projetados com alternativas em mente e a declaração do namespace ativada <urlset> não é algo que o núcleo permite tocar de forma limpa.
A rota pragmática é um mapa do site independente servido a partir de uma regra de reescrita, em vez de uma briga 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;
} );
Duas coisas para acertar. A xmlns:xhtml declaração sobre <urlset> é necessário; sem ele as entradas alternativas são ignoradas e o arquivo ainda valida como um mapa do site, que é o padrão de falha silenciosa novamente. E descarregue as regras de reescrita uma vez após adicionar a regra, na ativação e não em todas as solicitações.
Feito isso, remova a saída da cabeça Emitir ambos é misturando métodos de implementação, o que lhe dá duas fontes que derivam.
O problema do bloco duplicado
Esta é a falha específica do WordPress e a que vale a pena verificar antes de escrever qualquer código Vários plugins emitem hreflang, e mais de um pode estar ativo de uma só vez Um plugin de SEO configurado com relacionamentos de tradução, um plugin multilíngue e sua nova função de tema serão todos felizmente impressos para wp_head, e o resultado são dois ou três blocos com conteúdos diferentes e possivelmente dois x-default entradas.
O tratamento do Google's de anotações contraditórias dentro de uma página é desconfiar delas. Portanto, um site que não tinha hreflang e agora tem três blocos pode estar em pior situação do que antes.
Verifique a saída renderizada em vez das configurações do plugin:
curl -s https://example.com/de/preise/ | grep 'rel="alternate"' | grep hreflang
Conte as linhas e conte os códigos distintos Uma entrada por localidade mais uma x-default, e nada repetido Se um plugin estiver emitindo um conjunto que você não pode configurar, a escotilha de escape usual é remover sua ação wp_head em vez de adicionar o seu por cima:
// Example shape: find the real hook and callback with `has_action`.
add_action( 'init', function () {
remove_action( 'wp_head', 'some_plugin_hreflang_output', 10 );
} );
Tags canônicas, e não quebrá-las
WordPress emite rel="canonical" para visualizações singulares rel_canonical(), e aponta para o post & #39; s próprio permalink, que é o que você quer A maioria dos plugins de SEO substituí-lo com o seu próprio, também auto-referencial por padrão.
A regra de hreflang vs canônico aplica - se inalterada: cada página canoniza - se a si própria, e o hreflang descreve as relações entre essas páginas auto - canónicas A forma com sabor a WordPress para a quebrar é uma configuração de 'plugin' que canoniza as publicações traduzidas para um & quot; primary" versão da língua, que se lê como uma opção de desduplicação sensata e remove silenciosamente as suas traduções do índice Se uma configuração se oferece para canonizar entre línguas, deixe - a desligada.
Verificando o resultado
Busque duas páginas irmãs e compare seus blocos de anotação Eles devem ser idênticos, com apenas o canônico diferindo Em seguida, verifique se cada URL que você nomeou retorna 200 sem redirecionar, porque locais aposentados e links permanentes alterados são como um conjunto correto apodrece em um quebrado.
Cole o conjunto no gerador livre hreflang para validar os códigos: ele verifica cada valor em relação à forma ISO, sinaliza subtags não reconhecidas como en-UK, pega duplicatas, rejeita URLs relativos e avisa quando o conjunto não tem fallback Ele é executado inteiramente no navegador, o que importa quando os URLs descrevem um site cliente que ainda não é público Em seguida, use o Search Console' s International Targeting report para a verificação em andamento, e o guia de ferramentas Hreflang para o fluxo de trabalho em torno dele.
Perguntas frequentes
O WordPress adiciona tags hreflang automaticamente?
O No. Core emite um link canônico e vários links principais, mas nada sobre alternados de idiomas O Hreflang vem de um plugin ou do seu próprio wp_head saída.
Como adiciono hreflang no WordPress sem um plugin?
Gancho wp_head e imprima a <link rel="alternate" hreflang> linha para cada localidade em um mapa que você controla, incluindo um x-default entrada, em todas as páginas do grupo Escape dos valores com esc_attr e esc_url.
Para onde vão as tags hreflang em um tema WordPress?
Em uma função ligada a wp_head, idealmente em um pequeno plugin mu em vez do tema, portanto, alternar temas não deixa cair silenciosamente suas anotações.
E se minhas páginas traduzidas tiverem slugs diferentes?
Não derive nada Armazene os IDs ou URLs da postagem correspondente no post meta e leia o mapa no momento da renderização Preencha-o em ambos os lados ou você cria uma tag de retorno ausente.
Polylang e WPML lidam com hreflang?
Sim, para as traduções que eles gerenciam Verifique a saída inclui x-default e que nenhum segundo plugin está emitindo um bloco concorrente.
Por que tenho dois blocos hreflang em uma página?
Duas coisas estão imprimindo wp_head: geralmente um plugin de SEO mais um plugin multilíngue, ou um plugin mais sua própria função Anotações contraditórias em uma página são desconfiadas, então remova uma em vez de camadas.
Cada versão da linguagem WordPress deve ter seu próprio canônico?
Sim, auto-referencial Não use uma configuração de plugin que canoniza postagens traduzidas para um idioma primário, porque isso as remove do índice e cancela o conjunto hreflang.
Como adiciono hreflang em uma rede multisite WordPress?
Loop get_sites(), mude para cada blog para ler sua localidade e home_url()& e armazenar em cache o mapa resultante em um transiente. Em execução switch_to_blog em cada solicitação wp_head é caro em uma grande rede.



