大多數 WordPress hreflang 建議是 "安裝一個多語言插件並處理它。&引用;當我最常點擊的情況下,這就沒問題了:一個網站有兩個或三種語言版本,建置為單獨的子目錄、單獨的安裝或一組手動翻譯的頁面,沒有人想要完整的翻譯框架及其四頁資料庫模式。
我以這種形式維護 WordPress 屬性,包括手寫翻譯並駐留在其中的文件網站 /de/ 和 /es/. hreflang 主題檔案中有四十行,它從一個陣列讀取,並且從未漂移過。本指南就是該程式碼,以及插件在主題旁邊發出 hreflang 時出錯的部分。
它坐落在下面 完整的 hreflang 指南,旁邊 下一個。js 版本.
TL;DR: WordPress 不會自行發出 hreflang。鉤子
wp_head,循環區域設定映射,並列印完整的倒數集,包括x-default在群組中的每一頁上。建立 URLhome_url()或按區域設定來源而不是硬編碼,保留每個頁面'規範的自引用,並檢查是否沒有 SEO 外掛程式已經發出競爭區塊。對於多站點設置,get_sites()給你地圖;對於具有翻譯頁面的單一網站,發布元欄位對應項就足夠了。
WordPress 是否會單獨新增 hreflang?
No。 Core 輸出規範連結、短連結以及各種提要和 REST 連結 wp_head,但語言沒有任何變化。 get_locale() 知道網站使用什麼語言,核心的任何部分都不會將其變成註釋。
插件填補了空白。 Polylang 和 WPML 會為他們管理的翻譯發出 hreflang,如果您配置了他們可以看到的翻譯關係,大多數 SEO 插件都會發出 hreflang。如果您已經在使用其中一個並且它覆蓋了您的整個網站,請使用它並跳過本文。手動滾動的原因是您的翻譯存在於插件所知道的任何內容之外,並且發出部分集合的插件比不發出任何集合的插件更糟糕。
最小版本:一個陣列,一個掛鉤
對於翻譯是已知的一小組 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 );
這三件事是正確的,而手寫塊通常不正確。
它在每一頁上列印全套,包括頁面'自己的語言。互惠在結構上得到滿足:沒有一個分支可以省略條目,因為根本沒有分支。
x-default 是同一數組中的一行,因此它是由同一個循環發出的,不能在一個模板上忘記。
URL 被轉義。 esc_url 稍後 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/,然後以每個區域設定和#39;為前綴;其起源。尾隨斜線處理比看起來更重要: /preise 和 /preise/ 是不同的 URL,並將註釋指向非規範形式是 尾隨斜線不匹配 這會在看起來完美的集合上產生返回標籤錯誤。 user_trailingslashit 尊重網站'永久連結設置,因此它與 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 回調合併了目前貼文'在列印之前自己進入該地圖,因此該集合是完整的且自引用的:
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 )
);
}
}
維護成本是元必須寫在兩側。具有對應選擇器的管理 UI 是誠實的解決方案;從 CSV 填充地圖的 WP-CLI 命令是快速命令。您不能只在一側填充它,因為單向聲明正是 缺少返回標籤 失敗。
多站點:從網路匯出地圖
如果每種語言都是多站點網路中的一個站點,則地圖已經存在:
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 在a wp_head 在任何規模的網路上,連接每個請求都是真正的成本。並小心 get_locale() 下 switch_to_blog:僅當網站's 時,它才會反映交換的網站 WPLANG 設定選項,這是常見情況,但在透過常數配置的網路上無法保證。
哪種方法適合您的網站?
| 設定 | 方法 | 努力 |
|---|---|---|
| 並行slugs,幾個區域設定 | 靜態數組加上派生路徑 | 最低;一個功能 |
| 翻譯slugs | 後元對應圖 | 中等;需要編輯工作流程 |
| 多站點,每種語言一個站點 | 源自 get_sites(),快取 |
程式碼低,需要快取紀律 |
| Polylang 或 WPML 管理翻譯 | 使用插件's輸出 | 驗證它發出 x 預設值 |
| 帶有手動翻譯欄位的 SEO 外掛程式 | 使用它,然後檢查渲染的頭 | 注意重複的區塊 |
將 hreflang 放入網站地圖中
如果您的頁面頭已經很擁擠,或者您有足夠的區域設定使該區塊比頁頭的其餘部分更長,則可以選擇網站地圖。 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;
} );
要做對的兩件事。這 xmlns:xhtml 聲明開啟 <urlset> 需要;如果沒有它,備用條目將被忽略,並且該文件仍然作為網站地圖進行驗證,這又是靜默故障模式。新增規則後,在啟動時而不是在每個請求時刷新重寫一次規則。
完成此操作後,刪除頭部輸出。同時發射兩者 混合實施方法,這給了你兩個漂移的來源。
重複塊問題
這是 WordPress 特定的故障,在編寫任何程式碼之前值得檢查。多個插件發出 hreflang,並且多個插件可以同時啟動。配置翻譯關係的 SEO 插件、多語言插件以及您的新主題功能都將愉快地列印到 wp_head,結果是兩個或三個區塊具有不同的內容,並且可能是兩個 x-default 條目。
Google'在一頁內處理相互矛盾的註釋就是不信任它們。因此,一個沒有 hreflang 且現在有三個區塊的網站可能會比以前更糟。
檢查渲染的輸出而不是插件設定:
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(),它指向帖子'擁有自己的永久鏈接,這就是您想要的。大多數 SEO 外掛程式都會用自己的外掛程式替換它,預設也是自引用的。
規則來自 hreflang 與規範 不變地適用:每個頁面都規範化到自身,hreflang 描述了這些自規範頁面之間的關係。 WordPress 風格的破解方式是一種插件設置,它將翻譯後的帖子規範化為 "primary"語言版本,讀起來就像一個合理的重複資料刪除選項,並悄悄地從索引中刪除您的翻譯。如果設定提供跨語言的規範化,請將其保留。
驗證結果
取得兩個兄弟頁面並比較它們的註釋區塊。它們應該是相同的,只是規範不同。然後檢查您命名的每個 URL 是否傳回 200 而不重定向,因為退役的局域名和更改的永久連結是正確集合如何腐爛為損壞集合的方式。
將套裝貼入 免費 hreflang 產生器 為了驗證程式碼:它根據 ISO 形狀檢查每個值,標記無法識別的子標籤,例如 en-UK,捕獲重複項,拒絕相關 URL,並在集合沒有後備時發出警告。它完全在瀏覽器中運行,當 URL 描述尚未公開的客戶端網站時,這一點很重要。然後使用搜尋控制台's 國際目標報告進行持續檢查,以及 hreflang 工具指南 對於它周圍的工作流程。
常見問題
WordPress 會自動新增 hreflang 標籤嗎?
No。 Core 發出規範連結和各種頭部鏈接,但沒有關於語言交替的內容。 Hreflang 來自插件或您自己的插件 wp_head 輸出。
如何在沒有外掛程式的情況下在 WordPress 中新增 hreflang?
鉤 wp_head 並列印a <link rel="alternate" hreflang> 適用於您控制的地圖中每個區域的行,包括 x-default 條目,在群組中的每一頁。用 逃逸值 esc_attr 和 esc_url.
wordpress 主題中的 hreflang 標籤位於何處?
在一個掛鉤的函數中 wp_head理想情況下,在小型 mu 插件中而不是主題中,因此切換主題不會默默地刪除註釋。
如果我的翻譯頁面有不同的slugs怎麼辦?
導出任何內容。將對應的貼文 ID 或 URL 儲存在貼文元中,並在渲染時讀取地圖。在兩側填充它,或建立缺少的返回標籤。
Polylang 和 WPML 處理 hreflang 嗎?
是的,對於他們管理的翻譯。驗證輸出包括 x-default 並且沒有第二個插件發出競爭區塊。
為什麼我的一頁上有兩個 hreflang 區塊?
有兩件事是列印到 wp_head:通常是 SEO 外掛程式加上多語言插件,或插件加上您自己的功能。一頁上的矛盾註釋是不值得信任的,因此刪除一頁而不是分層。
每個 WordPress 語言版本是否應該有自己的規範?
是的,自我指涉。請勿使用將翻譯後的貼文規範化為主要語言的插件設置,因為這會將它們從索引中刪除並取消 hreflang 集。
如何在 WordPress 多站點網路中新增 hreflang?
循環 get_sites()() 切換到每個部落格以閱讀其區域設定 home_url(),並將產生的映射快取為瞬態。跑步 switch_to_blog 根據每一個請求 wp_head 在大型網路上價格昂貴。



