La maggior parte dei consigli hreflang di WordPress è & quot; installa un plugin multilingue e lo gestisce.& quot; Va bene quando il plugin possiede le tue traduzioni È la risposta sbagliata nel caso in cui colpisca più spesso: un sito con due o tre versioni linguistiche create come sottodirectory separate, installazioni separate o un insieme di pagine tradotte manualmente, dove nessuno vuole un framework di traduzione completo e il suo schema di database per quattro pagine.
mantengo le proprietà di WordPress in quella forma, inclusi i siti di documentazione in cui le traduzioni sono scritte a mano e vivono sotto /de/ e /es/. L'hreflang ci sono quaranta righe in un file di tema, si legge da un array, e non è mai stato spostato Questa guida è quel codice, più le parti che vanno male quando un plugin sta emettendo hreflang accanto al tuo tema.
Si trova sotto il guida completa di hreflang, accanto al Prossima versione.js.
tl; dr: WordPress non emette hreflang da solo Hook
wp_head, eseguire il loop su una mappa locale e stampare l'intero set reciproco inclusox-defaultin ogni pagina del gruppo Costruisci URL conhome_url()oppure un'origine per locale anziché la codifica fissa, mantieni ogni pagina' è autoreferenziale canonico e controlla che nessun plugin SEO emetta già un blocco concorrente Per configurazioni multisito,get_sites()ti dà la mappa; per un singolo sito con pagine tradotte, bastano le controparti di mappatura dei meta campi post.
WordPress aggiunge hreflang da solo?
No. Core emette un collegamento canonico, un collegamento breve e vari collegamenti feed e REST da wp_head, ma nulla del linguaggio si alterna. get_locale() sa in che lingua si trova il sito e nessuna parte del nucleo lo trasforma in un'annotazione.
I plugin colmano il vuoto Polylang e WPML emettono hreflang per le traduzioni che gestiscono, e la maggior parte dei plugin SEO lo emetteranno se configuri una relazione di traduzione che possono vedere Se stai già usando uno di quelli e copre tutto il tuo sito, usalo e salta questo articolo Il motivo per eseguire il rollio manuale è che le tue traduzioni esistono al di fuori di qualunque cosa il plugin sappia, e un plugin che emette un set parziale è peggiore di uno che non ne emette nessuno.
La versione minimale: un array, un gancio
Per un sito le cui traduzioni sono un insieme noto e ridotto di prefissi 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 );
Tre cose che questo fa bene che i blocchi scritti a mano di solito non fanno.
Stampa il set completo su ogni pagina, incluso il linguaggio proprio page' La reciprocità è soddisfatta strutturalmente: non esiste un ramo che possa omettere una voce, perché non esiste alcun ramo.
x-default è una riga nello stesso array, quindi viene emesso dallo stesso ciclo e non può essere dimenticato su un modello.
Gli URL sono sfuggiti. esc_url su un href in wp_head non è facoltativo e i valori hreflang vengono eliminati esc_attr per lo stesso motivo.
Elaborare il percorso attuale
Il percorso è la parte che ha bisogno di cure, perché deve essere il percorso entro la lingua, non l'URI di richiesta 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 il tuo tedesco installa vite a example.com/de e il visitatore è acceso example.com/de/preise/, questo ritorna /preise/, che poi ottiene il prefisso con ogni origine locale's. La gestione delle barre finali conta più di quanto sembri: /preise e /preise/ sono URL diversi e puntare un'annotazione nella forma non canonica è il mancata corrispondenza della barra finale ciò produce errori di tag di ritorno su un set che altrimenti sembra perfetto. user_trailingslashit rispetta l'impostazione permalink di site's, quindi corrisponde a qualunque cosa serva WordPress.
Il presupposto qui è che gli URL tradotti siano strutturalmente paralleli: /pricing/ mappe a /de/pricing/. Se anche il tuo tedesco slugs viene tradotto, /de/preise/, questa funzione non è sufficiente e hai bisogno di una mappatura esplicita, che è la sezione successiva.
Quando slugs differiscono: una mappatura post-meta
Tradotto slugs sono migliori per gli utenti e per il ranking, e significano che il percorso non può essere derivato Conservare invece le controparti:
/**
* 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;
}
Poi il wp_head callback unisce l'attuale post's propria voce in quella mappa prima della stampa, quindi il set è completo e autoreferenziale:
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 )
);
}
}
Il costo di manutenzione è che la meta deve essere scritta su entrambi i lati Un'interfaccia utente di amministrazione con un selettore di contropartita è la soluzione onesta; un comando WP-CLI che popola la mappa da un CSV è quello veloce Quello che non devi fare è popolarla solo su un lato, perché una dichiarazione unidirezionale è esattamente il tag di ritorno mancante fallimento.
Multisito: ricavare la mappa dalla rete
Se ogni lingua è un sito in una rete multisito, la mappa esiste già
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;
}
Memorizzare nella cache il risultato in un transitorio; switch_to_blog in un wp_head hook ad ogni richiesta è un costo reale su una rete di qualsiasi dimensione E attenzione con get_locale() sotto switch_to_blog: riflette il sito commutato solo se il sito's WPLANG opzione è impostata, che è il caso usuale ma non garantito su reti configurate attraverso una costante.
Quale approccio si adatta al tuo sito?
| impostazione | avvicinare | Sforzo |
|---|---|---|
| Parallelo slugs, poche località | Array statico più percorso derivato | Più basso; una funzione |
| Tradotto slugs | Mappa della controparte post-meta | Moderato; necessita di un flusso di lavoro di modifica |
| Multisito, un sito per lingua | Derivare da get_sites(), memorizzato nella cache |
Codice basso, ha bisogno di disciplina della cache |
| Polylang o WPML che gestiscono le traduzioni | Utilizza l'uscita plugin's | Verifica che emetta x-default |
| Plugin SEO con campi di traduzione manuale | Usalo, quindi controlla la testina renderizzata | Guarda per i blocchi duplicati |
Mettere hreflang nella sitemap invece
Se le tue teste di pagina sono già affollate, o hai abbastanza locali che il blocco è più lungo del resto della testa, la mappa del sito è l'alternativa WordPress 5.5 e versioni successive spediscono mappe del sito principali, e sono filtrabili, sebbene i filtri non siano stati progettati pensando alle alternative e alla dichiarazione dello spazio dei nomi accesa <urlset> non è qualcosa che il nucleo ti consente di toccare in modo pulito.
Il percorso pragmatico è una mappa del sito autonoma servita da una regola di riscrittura piuttosto che da una lotta con 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;
} );
Due cose per ottenere giusto Il xmlns:xhtml dichiarazione sulla <urlset> è richiesto; senza di esso le voci alternative vengono ignorate e il file viene ancora convalidato come mappa del sito, che è di nuovo il modello di guasto silenzioso. E riscrivi le regole una volta dopo aver aggiunto la regola, all'attivazione anziché su ogni richiesta.
Fatto ciò, rimuovere l'uscita della testina Emettendo entrambi è metodi di implementazione della miscelazione, che ti dà due fonti che vanno alla deriva.
Il problema del blocco duplicato
Questo è il fallimento specifico di WordPress e quello che vale la pena controllare prima di scrivere qualsiasi codice Diversi plugin emettono hreflang, e più di uno può essere attivo in una volta Un plugin SEO configurato con relazioni di traduzione, un plugin multilingue, e la tua nuova funzione tema sarà tutto felicemente stampato a wp_head, e il risultato è di due o tre blocchi con contenuti diversi ed eventualmente due x-default voci.
Google's gestione di annotazioni contraddittorie all'interno di una pagina è quello di diffidare di loro Quindi un sito che non aveva hreflang e ora ha tre blocchi può essere peggio di prima.
Controllare l'output renderizzato anziché le impostazioni del plugin:
curl -s https://example.com/de/preise/ | grep 'rel="alternate"' | grep hreflang
Conta le righe e conta i codici distinti Una voce per locale più una x-default, e nulla ripetuto Se un plugin sta emettendo un set non è possibile configurare via, il solito escape hatch è quello di rimuovere la sua azione da wp_head piuttosto che aggiungere il tuo in 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 );
} );
Tag canonici, e non romperli
WordPress emette rel="canonical" per viste singolari attraverso rel_canonical(), e punta al post's proprio permalink, che è quello che vuoi La maggior parte dei plugin SEO lo sostituiscono con il proprio, anch'esso autoreferenziale di default.
La regola dal hreflang vs canonico si applica invariato: ogni pagina si canonizza su se stessa e hreflang descrive le relazioni tra quelle pagine autocanoniche Il modo al gusto di WordPress per romperla è un'impostazione del plugin che canonizza i post tradotti in una versione & quot;primary" language, che si legge come un'opzione di deduplicazione sensata e rimuove silenziosamente le tue traduzioni dall'indice Se un'impostazione offre di canonicalizzare in tutte le lingue, lascia perdere.
Verifica del risultato
Recupera due pagine fratelli e confronta i loro blocchi di annotazione Dovrebbero essere identici, con solo la differenza canonica Quindi controlla che ogni URL che hai nominato restituisca 200 senza reindirizzare, perché le località ritirate e i permalink modificati sono il modo in cui un set corretto si trasforma in uno rotto.
Incolla il set nel generatore hreflang gratuito per convalidare i codici: controlla ogni valore rispetto alla forma ISO, contrassegna i sottotag non riconosciuti come en-UK, cattura i duplicati, rifiuta gli URL relativi e avvisa quando il set non ha fallback Funziona interamente nel browser, il che conta quando gli URL descrivono un sito client che non è ancora pubblico Quindi utilizzare Search Console' s International Targeting report per il controllo in corso, e il guida agli strumenti di Hreflang per il flusso di lavoro attorno ad esso.
Domande frequenti
WordPress aggiunge tag hreflang automaticamente?
No. Core emette un link canonico e vari collegamenti alla testa ma nulla sulla lingua si alterna Hreflang proviene da un plugin o dal proprio wp_head uscita.
Come faccio ad aggiungere hreflang in WordPress senza un plugin?
Gancio wp_head e stampa a <link rel="alternate" hreflang> linea per ogni locale in una mappa che controlli, incluso un x-default voce, in ogni pagina del gruppo Sfuggire ai valori con esc_attr e esc_url.
Dove vanno i tag hreflang in un tema WordPress?
In una funzione agganciata a wp_head, idealmente in un piccolo plugin mu anziché nel tema, quindi il cambio di tema non lascia cadere silenziosamente le annotazioni.
E se le mie pagine tradotte avessero slugs diverso?
Deriva nulla Memorizza gli ID o gli URL dei post di controparte nel meta dei post e leggi la mappa al momento del rendering Popolala su entrambi i lati, oppure crei un tag di ritorno mancante.
Polylang e WPML gestiscono hreflang?
Sì, per le traduzioni che gestiscono Verificare l'output include x-default e che nessun secondo plugin sta emettendo un blocco concorrente.
Perché ho due blocchi hreflang in una pagina?
Due cose stanno stampando per wp_head: di solito un plugin SEO più un plugin multilingue, o un plugin più la propria funzione Le annotazioni contraddittorie su una pagina sono diffidate, quindi rimuoverne una anziché layering.
Ogni versione in lingua WordPress dovrebbe avere il proprio canonico?
Sì, autoreferenziale Non utilizzare un'impostazione plugin che canonizza i post tradotti in una lingua primaria, perché che li rimuove dall'indice e annulla l'insieme hreflang.
Come faccio ad aggiungere hreflang attraverso una rete multisito WordPress?
Loop get_sites()passa a ciascun blog per leggerne il locale e home_url(), e memorizzare nella cache la mappa risultante in un transitorio Esecuzione switch_to_blog su ogni richiesta in wp_head è costoso su una rete di grandi dimensioni.



