WP Rocket 多語系快取:儲存草稿時翻譯版快取消失的原因與解法

聯盟行銷揭露 Ver 3.1
聯盟行銷揭露 Ver 3.2(僅限行動版)
測試環境 RHEL 系列AlmaLinux 9.8Nginx 1.28 + Apache 2.4WP Rocket 3.23.5.1TranslatePress 3.3.82026-10-17 確認

WP Rocket 多語系快取是依 TranslatePress 每個翻譯版網址,分別存放在語言資料夾底下的頁面快取。在 Nginx + Apache 架構的 WordPress 伺服器上,從 WP Rocket 的程式碼追查每次儲存草稿時,翻譯版快取連同整個語言資料夾一起消失的原因後,改用一段程式碼解決:讓尚未發佈的文章不參與翻譯版快取刪除,並把自行刪除的網址重新告知預先載入。

在同一台伺服器內量到的首位元組,有翻譯版快取時是 0.015 秒,沒有時是 2.492 秒。語言資料夾一旦清空,翻譯版的訪客在有人重新開啟該網址、快取重新建立之前,都會收到這樣緩慢的回應。

1️⃣ WP Rocket 多語系快取結構:各語言資料夾與翻譯版刪除

翻譯版是把原文文章翻譯後顯示的頁面,所以很容易以為它的快取和原文綁在一起,但 WP Rocket 會把翻譯版網址當成另一個頁面來快取。快取資料夾底下,[en]、[ja] 等語言資料夾會和原文文章資料夾並列,翻譯版文章則以翻譯後的代稱存放在其中。

wp-content/cache/wp-rocket/example.com/
sample-post/原文文章
index-https.html
index-mobile-https.html
en/英文語言資料夾
index-https.html英文語言首頁
sample-post-en/英文翻譯版文章(翻譯後的代稱)
index-https.html
index-mobile-https.html
ja/日文語言資料夾
index-https.html
sample-post-ja/

修改文章時,WP Rocket 的 TranslatePress 整合也會一併刪除該文章的翻譯版快取。官方文件也說明,一般修改會清除所有翻譯版,而在 TranslatePress 翻譯編輯器中修改時,只會清除有變更的語言。

// wp-rocket/inc/ThirdParty/Plugins/I18n/TranslatePress.php (3.23.5.1)
'post_updated' => 'clear_post_languages',            // 每次儲存文章時執行

public function clear_post_languages( $post_id ) {
    // …
    $default_permalink = get_permalink( $post_id );   // 不檢查文章狀態

    foreach ( $trp_settings['translation-languages'] as $language ) {
        if ( $language === $trp_settings['default-language'] ) {
            continue;
        }
        $clear_urls[] = $converter->get_url_for_language( $language, $default_permalink, '' );
    }
    // …
    rocket_clean_files( $clear_urls );
}

整合函式會把文章網址轉換成各語言的翻譯版網址,再交給快取檔案清理函式 [rocket_clean_files]。這個過程沒有任何一行檢查文章狀態,所以不論文章已發佈還是草稿,都會以同樣的順序產生翻譯版網址並刪除。

2️⃣ 儲存草稿:整個語言資料夾遭到刪除

儲存草稿和翻譯版快取並非無關。發佈之前,文章的永久連結不是代稱,而是 [?p=文章編號] 的形式,而 TranslatePress 會把這個網址轉換成加上語言路徑的網址,例如 [/en/?p=文章編號]。

儲存草稿在編輯器儲存 · 在伺服器執行 wp_update_post · 只修改標題
post_updated
clear_post_languages( 123 )WP Rocket 的 TranslatePress 整合
get_permalink()→ https://example.com/?p=123 · 尚未發佈,所以沒有代稱
get_url_for_language()→ https://example.com/en/?p=123(ja·es·de 也一樣)
rocket_clean_files()
拿掉查詢 ?p=123→ 路徑 /en/
快取位置→ wp-content/cache/wp-rocket/example.com/en/
因為是資料夾,所以遞迴刪除→ 整個英文翻譯版快取遭到刪除

[rocket_clean_files] 會從網址拿掉查詢,只依路徑找出快取位置。剩下的路徑如果是 [/en/],就代表該語言的整個快取資料夾;目標如果是資料夾,連子資料夾也會遞迴刪除。

// wp-rocket/inc/functions/files.php (3.23.5.1) — rocket_clean_files() 內
$parsed_url = get_rocket_parse_url( $url );               // 分開路徑與查詢
// …
$entry = $dir . $parsed_url['path'];                      // …/example.com/en/
// …
if ( $filesystem->is_dir( $entry ) ) {
    rocket_rrmdir( $entry, [], $filesystem );             // 連同資料夾遞迴刪除
} else {
    $filesystem->delete( $entry );
}

首頁也會經過同樣的路徑。首頁網址 [/] 會轉換成 [/en/],所以就算儲存已發佈的首頁,也會刪除整個語言資料夾。只修改草稿標題再儲存一次,四個語言的快取資料夾就全部清空;從儲存時間與快取刪除記錄也確認,在草稿儲存集中的一個晚上,同樣的刪除重複了 30 次以上。

🧭 儲存文章與刪除翻譯版快取
WP Rocket說明圖解
📝 儲存的文章
📝 永久連結
[get_permalink]
/sample-post/已發佈文章 · 代稱網址
/?p=123尚未發佈 · 自動儲存的修訂版本也一樣
/首頁
post_updated→
🌐 翻譯版網址
TranslatePress 語言路徑
/en/sample-post-en/
/en/?p=123
/en/
ja·es·de 也一樣
✅ 路徑 /en/sample-post-en/ · 該文章資料夾
❌ 拿掉 ?p=123 · 只剩路徑 /en/
❌ 路徑 /en/ · 語言資料夾
rocket_clean_files→
🗑️ 被刪除的快取
① 只有該文章的翻譯版資料夾en/sample-post-en/語言首頁和其他文章的翻譯版保持不變
② 整個語言資料夾en/語言首頁 · 連其他文章的翻譯版也刪除 · 20 個 → 0 個
已發佈文章的永久連結是代稱,所以翻譯版網址也指向該文章,只會刪除該文章的翻譯版快取資料夾。
草稿的永久連結是 [?p=123] 的形式,所以翻譯版網址會變成 [/en/?p=123]。[rocket_clean_files] 拿掉查詢後只剩 [/en/],整個英文語言資料夾就會被刪除;編輯器自動儲存的修訂版本也使用同樣的網址。
首頁網址 [/] 會轉換成 [/en/],所以就算儲存已發佈的首頁,也會刪除整個語言資料夾。

從程式碼來看,編輯器自動儲存也走同樣的路徑。你編輯已發佈文章時儲存的修訂版本,同樣使用 [?p=] 形式的網址。

在 Nginx + Apache 架構的測試網站上,用編輯器修改已發佈文章並檢查語言資料夾的檔案數。第一次自動儲存後,英文與日文資料夾仍各有 20 個檔案;1 分鐘後第二次自動儲存時,兩個資料夾都變成 0 個。

第一次自動儲存會建立新的修訂版本,從第二次起則是修改同一個修訂版本後儲存,因此會執行 [post_updated]。第一次自動儲存後如果繼續修改內文,就算不按儲存按鈕,翻譯版快取也會連同語言資料夾一起被刪除。

同樣的症狀自 2023 年 8 月 31 日起,就以 issue #6130 開在 WP Rocket 官方儲存庫中。修正它的 pull request #7616 在 2025 年 9 月提出,但尚未合併,2026 年 10 月 2 日推出的 3.23.5.1 變更記錄中也沒有修正內容。

3️⃣ 修正程式碼:只刪除已發佈文章的翻譯版快取

在官方修正推出之前,做法是拆下整合函式,改用只在網址確定時才刪除翻譯版的函式。issue 留言中分享的繞過方法也是同一個方向,也就是文章尚未發佈時就直接離開整合函式。

不過,只檢查文章狀態的條件還會留下兩種情況。首頁即使是已發佈狀態,儲存時仍會刪除整個語言資料夾;而把已發佈文章改回草稿時,翻譯版快取不會被刪除,下架的文章仍能在翻譯版網址開啟。

<?php
/**
 * Plugin Name: Rocket TranslatePress Draft Fix
 * Description: 把 WP Rocket TranslatePress 整合的翻譯版快取刪除限制在網址確定的情況
 */

// 1. 從 post_updated 拆下 WP Rocket 整合函式並保留
add_action( 'init', function () {
    global $wp_filter;
    if ( empty( $wp_filter['post_updated'] ) ) {
        return;
    }
    foreach ( $wp_filter['post_updated']->callbacks as $priority => $callbacks ) {
        foreach ( $callbacks as $cb ) {
            $fn = $cb['function'];
            if ( is_array( $fn ) && is_object( $fn[0] )
                && 0 === strpos( get_class( $fn[0] ), 'WP_Rocket\\' )
                && false !== stripos( get_class( $fn[0] ), 'TranslatePress' ) ) {
                remove_action( 'post_updated', $fn, $priority );
                $GLOBALS['rtdf_original'] = $fn;
            }
        }
    }
}, 9999 );

// 2. 替代函式
add_action( 'post_updated', function ( $post_id, $after, $before ) {
    if ( empty( $GLOBALS['rtdf_original'] ) || ! class_exists( 'TRP_Translate_Press' ) ) {
        return;
    }
    $trp       = TRP_Translate_Press::get_trp_instance();
    $converter = $trp->get_component( 'url_converter' );
    $settings  = $trp->get_component( 'settings' )->get_settings();
    $languages = array_diff( $settings['translation-languages'], [ $settings['default-language'] ] );

    // 首頁 — 只刪除語言首頁的快取檔案,不刪除語言資料夾
    if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === (int) $post_id ) {
        $urls = [];
        add_filter( 'trp_add_language_to_home_url_check_for_admin', '__return_false' );
        foreach ( $languages as $lang ) {
            $home = $converter->get_url_for_language( $lang, home_url( '/' ), '' );
            $dir  = WP_ROCKET_CACHE_PATH . wp_parse_url( $home, PHP_URL_HOST ) . wp_parse_url( $home, PHP_URL_PATH );
            array_map( 'unlink', glob( $dir . 'index*.html*' ) ?: [] );
            $urls[] = $home;
        }
        remove_filter( 'trp_add_language_to_home_url_check_for_admin', '__return_false' );
        do_action( 'rocket_after_clean_terms', $urls );   // 告知預先載入(5️⃣)
        return;
    }

    // 儲存已發佈文章 — 照樣執行 WP Rocket 原本的函式
    if ( 'publish' === $after->post_status ) {
        call_user_func( $GLOBALS['rtdf_original'], $post_id );
        return;
    }

    // 已發佈 → 草稿·私密·回收桶 — 只處理下架前網址的翻譯版
    if ( 'publish' === $before->post_status ) {
        $old  = get_permalink( $before );
        $urls = [];
        add_filter( 'trp_add_language_to_home_url_check_for_admin', '__return_false' );
        foreach ( $languages as $lang ) {
            $urls[] = $converter->get_url_for_language( $lang, $old, '' );
        }
        remove_filter( 'trp_add_language_to_home_url_check_for_admin', '__return_false' );
        rocket_clean_files( $urls );
    }

    // 其他情況(草稿·修訂版本·自動儲存)沒有翻譯版網址,因此不刪除
}, 10, 3 );

第一部分會找出 WP Rocket 登記的整合函式,從 [post_updated] 拆下,並保留原本的函式。替代函式遇到首頁時只刪除語言首頁的快取檔案,遇到已發佈文章時照樣執行保留下來的原本函式,而已發佈文章下架時,會用下架前的網址找出翻譯版並刪除。

存成 PHP 檔案放在 [wp-content/mu-plugins/] 資料夾,不必啟用就會立即生效;刪除檔案後,WP Rocket 就會回到原本的運作方式。用運作方式相同的程式碼測試的結果,再次儲存草稿時語言資料夾的檔案數維持不變,再次儲存已發佈文章時只刪除該文章的原文與翻譯版資料夾。

在加入上方程式碼前後,於同一環境比較了四種情況下語言資料夾的檔案數。英文與日文資料夾各有 20 個語言首頁與四篇文章的電腦版·行動版快取檔案。

加入程式碼之前,儲存草稿、儲存首頁、取消發佈三種情況都讓兩個資料夾變成 0 個,只有儲存已發佈文章時才只刪除該文章的翻譯版快取。

加入程式碼之後,儲存草稿與編輯器自動儲存時 20 個檔案都還在。儲存首頁只刪除 4 個語言首頁快取檔案,儲存已發佈文章與取消發佈只刪除該文章的 4 個翻譯版檔案,剩下 16 個;下架文章的英文翻譯版網址回應 404。

情況修正前加入程式碼後加入後刪除的快取
儲存草稿20 → 020 → 20無
儲存已發佈文章只刪除該文章的翻譯版20 → 16該文章的翻譯版 4 個
儲存首頁20 → 020 → 16語言首頁快取 4 個
取消發佈20 → 020 → 16該文章的翻譯版 4 個 · 翻譯版網址 404

4️⃣ 預先載入沒有重建:殘留在佇列中的完成狀態

即使擋下了草稿儲存,在沒有 WP Rocket 函式刪除訊號的情況下刪掉的快取檔案,預先載入也不會重新補上。 WP Rocket 預先載入使用一張依網址記錄狀態的佇列表:待請求的網址是等待,已送出請求的是進行中,建立好快取的會變成完成,3 分鐘內沒完成則變成失敗。

已完成的網址會在三種情況下回到等待:快取壽命到期而刪除的檔案、WP Rocket 刪除時發出刪除訊號的網址,以及失敗後已超過 12 小時的網址。如果把 [rocket_clean_files] 的第三個參數傳成 false,或直接刪除檔案,就不會發出這個訊號,佇列中仍顯示完成,只有快取檔案消失。

接收刪除訊號後接著清除其他快取的程式碼,為了避免重複呼叫,會把第三個參數傳成 false。比對翻譯版快取時,英文翻譯版 65 個中有 3 個、日文翻譯版 65 個中有 7 個,在佇列中仍是完成,快取檔案卻不存在。

WP Rocket 支援團隊的貢獻者也在 issue #6130 的留言中承認,清除快取後表中的網址仍維持完成,導致狀態對不上。定期讓表格與快取檔案一致的提案(#2939)在 2020 年未實作就被關閉。

5️⃣ 重新登記預先載入:把自行刪除的網址告知佇列

自行刪除的網址,只要在刪除後立即用一行程式碼告知預先載入就能解決。預先載入收到 [rocket_after_clean_terms] 訊號後,會把收到的網址改回等待,表中沒有的網址則新增進去。

// 沒有刪除訊號就刪除之後 — 把刪除的網址陣列重新登記到預先載入佇列
rocket_clean_files( $urls, null, false );          // 第三個參數 false = 沒有刪除訊號
do_action( 'rocket_after_clean_terms', $urls );     // 預先載入把這些網址改回等待(pending)

在 WP Rocket 內部,接收這個訊號的只有預先載入佇列函式與 SpinUpWP 主機整合,而預先載入函式只修改佇列資料列,不會刪除 WP Rocket 快取檔案,也不會再次發出刪除訊號,所以不會發生重複呼叫。只用 WP Rocket 函式清除快取、並讓第三個參數維持預設值的網站,已經會發出訊號,不需要這一行。

加入這一行後,清除一篇有四種語言翻譯版的文章快取,2 秒內四列佇列資料就變成等待。在每分鐘執行 cron 的伺服器上測試兩次,翻譯版的電腦版·行動版快取檔案 8 個在 101 秒與 153 秒後全部重新建立。可以從佇列表的狀態與快取資料夾的檔案確認。

# 該網址在佇列表中的狀態(表名前的 wp_ 是網站前綴)
wp db query <<'SQL'
SELECT url, status, modified FROM wp_wpr_rocket_cache WHERE url LIKE '%/en/sample-post-en'
SQL
url	status	modified
https://example.com/en/sample-post-en	pending	2026-10-17 09:10:48
# 約 1 分鐘後快取檔案是否重新產生
ls -l wp-content/cache/wp-rocket/example.com/en/sample-post-en/
total 240
-rw-r--r-- 1 example nobody 95084 Oct 17 18:12 index-https.html
-rw-r--r-- 1 example nobody 22352 Oct 17 18:12 index-https.html_gzip
-rw-r--r-- 1 example nobody 95083 Oct 17 18:12 index-mobile-https.html
-rw-r--r-- 1 example nobody 22348 Oct 17 18:12 index-mobile-https.html_gzip

刪除後狀態會立刻變成 [pending],預先載入送出請求、產生快取檔案後就會變成 [completed]。如果狀態是 [completed] 卻沒有快取檔案,表示那個網址是在沒有訊號的情況下被刪除的。

儲存首頁後,上方程式碼刪除語言首頁快取檔案並發出訊號,英文與日文首頁網址就重新進入佇列。語言首頁的電腦版·行動版快取最晚在第二次 cron 執行時重新建立。

測試兩次,從儲存到最後一個快取檔案分別花了 47 秒與 91 秒。

🔢 FAQ & 推薦內容

本文以 WP Rocket 的 TranslatePress 整合為準,沒有確認 WPML·Polylang 整合。其他快取外掛與 Polylang 搭配時,只剩查詢的網址被轉換成快取最上層資料夾而整個被刪除的案例,2026 年 7 月曾發表在 WordPress.org 支援論壇,那個案例也是用拆下並包住該 callback 的程式碼解決。

這是 2026 年 10 月 16 日在 WP Rocket 儲存庫開出的 issue #9028。預先載入記錄快取時,用的是原本的代稱網址而不是翻譯後的代稱網址,因此翻譯版網址的資料列拿不到完成狀態與最後存取時間,會被刪除一個月內沒有存取資料列的每週清理作業一併刪掉。這段期間可以用 [rocket_preload_delay_delete_non_accessed] 篩選器延長,官方文件的範例是 1 年,而修正草案 #9029 截至 2026 年 10 月 17 日尚未合併。

當 issue #6130 的修正出現在變更記錄中,且整合函式開始檢查文章狀態或查詢網址時,就可以拿掉。如果函式名稱或登記的 hook 改變,拆下的程式碼會找不到目標,原本的運作會照樣保留,所以更新後請儲存一篇草稿,確認語言資料夾是否完好。

🧭 WP Rocket 相關文章 文章 16 · 工具 3
📋 WP Rocket 設定教學 📄 WP Rocket MCP Server 📄 WP Rocket 韓文網址 📄 nginx njs 📄 WP Rocket 多語系快取 📄 gclid 快取
📄 WP Rocket 速度測試 📝 快取 · 優化外掛 📝 速度改善案例
📋 快取:定義與所有領域 📄 網站快取 & 伺服器內部快取
📋 BEST 主機 TOP 7 📄 快速主機 2026
🛠️ WordPress 偵測 🛠️ WHOIS 網域查詢 🛠️ 我的 IP 查詢

ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Prove your humanity: 3   +   10   =