Wielojęzyczny cache WP Rocket: dlaczego cache wersji językowych znika przy zapisie szkicu i jak to naprawić

Wielojęzyczny cache WP Rocket to cache stron zapisywany osobno w folderze języka dla każdego przetłumaczonego adresu TranslatePress. Na serwerze WordPress z Nginx + Apache prześledziliśmy w kodzie WP Rocket, dlaczego cache przetłumaczonych stron znikał razem z całym folderem języka przy każdym zapisie szkicu, i rozwiązaliśmy to kodem, który wyłącza nieopublikowane wpisy z czyszczenia cache przetłumaczonych stron i ponownie informuje preload o adresach usuwanych przez nas samych.
Czas do pierwszego bajtu zmierzony na tym samym serwerze wynosił 0,015 s z cache przetłumaczonej strony i 2,492 s bez niego. Gdy folder języka jest pusty, odwiedzający przetłumaczone strony dostają tę wolną odpowiedź, dopóki ktoś znów nie otworzy adresu i cache się nie odbuduje.
1️⃣ Budowa wielojęzycznego cache WP Rocket: foldery języków i czyszczenie przetłumaczonych stron
Przetłumaczona strona pokazuje oryginalny wpis w tłumaczeniu, więc łatwo założyć, że jej cache jest razem z oryginałem, ale WP Rocket buforuje przetłumaczone adresy jako osobne strony. W folderze cache obok folderów oryginalnych wpisów pojawiają się foldery języków, np. [en] i [ja], a przetłumaczone wpisy są w nich zapisane pod przetłumaczonymi slugami.
Gdy edytujesz wpis, integracja WP Rocket z TranslatePress usuwa też cache przetłumaczonych stron tego wpisu. Oficjalna dokumentacja również podaje, że zwykła edycja czyści wszystkie wersje językowe, a edycja w edytorze tłumaczeń TranslatePress – tylko zmieniony język.
// wp-rocket/inc/ThirdParty/Plugins/I18n/TranslatePress.php (3.23.5.1)
'post_updated' => 'clear_post_languages', // uruchamia się przy każdym zapisie wpisu
public function clear_post_languages( $post_id ) {
// …
$default_permalink = get_permalink( $post_id ); // status wpisu nie jest sprawdzany
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 );
}Funkcja integracji zamienia adres wpisu na przetłumaczony adres dla każdego języka, a potem przekazuje je funkcji czyszczącej cache [rocket_clean_files]. Nic w tym procesie nie sprawdza statusu wpisu, więc przetłumaczone adresy są tworzone i usuwane tak samo, niezależnie od tego, czy wpis jest opublikowany, czy jest szkicem.
2️⃣ Zapis szkicu: usuwany jest cały folder języka
Zapis szkicu nie jest obojętny dla cache przetłumaczonych stron. Przed publikacją odnośnik bezpośredni wpisu nie jest slugiem, tylko ma postać [?p=ID wpisu], a TranslatePress zamienia ten adres na adres ze ścieżką języka, np. [/en/?p=ID wpisu].
[rocket_clean_files] usuwa zapytanie z adresu i szuka miejsca cache tylko po ścieżce. Jeśli zostaje ścieżka [/en/], oznacza to cały folder cache tego języka, a jeśli celem jest folder, jest on usuwany rekurencyjnie razem z podfolderami.
// wp-rocket/inc/functions/files.php (3.23.5.1) — wewnątrz rocket_clean_files()
$parsed_url = get_rocket_parse_url( $url ); // rozdziela ścieżkę i zapytanie
// …
$entry = $dir . $parsed_url['path']; // …/example.com/en/
// …
if ( $filesystem->is_dir( $entry ) ) {
rocket_rrmdir( $entry, [], $filesystem ); // usuwa cały folder rekurencyjnie
} else {
$filesystem->delete( $entry );
}Strona główna przechodzi tę samą drogę. Jej adres [/] zamienia się na [/en/], więc nawet zapis opublikowanej strony głównej usuwa cały folder języka. Sama zmiana tytułu szkicu i ponowny zapis opróżniły foldery cache wszystkich czterech języków, a z czasów zapisu i logów usuwania cache potwierdziliśmy, że w jedną noc z wieloma zapisami szkiców to samo usunięcie powtórzyło się ponad 30 razy.
W kodzie autozapis edytora idzie tą samą drogą. Wersje zapisywane podczas edycji opublikowanego wpisu też używają adresu w postaci [?p=].
Na witrynie testowej z Nginx + Apache edytowaliśmy opublikowany wpis w edytorze, licząc pliki w folderach języków. Po pierwszym autozapisie foldery angielski i japoński miały nadal po 20 plików, a po drugim autozapisie minutę później oba miały 0.
Pierwszy autozapis tworzy nową wersję, a od drugiego ta sama wersja jest aktualizowana i zapisywana, więc uruchamia się [post_updated]. Jeśli edytujesz dalej po pierwszym autozapisie, cache przetłumaczonych stron jest usuwany razem z folderem języka nawet bez kliknięcia przycisku zapisu.
Ten sam objaw jest otwarty w oficjalnym repozytorium WP Rocket jako zgłoszenie #6130 od 31 sierpnia 2023 r. Pull request #7616 z poprawką wysłano we wrześniu 2025 r., ale go nie scalono, a dziennik zmian wersji 3.23.5.1 z 2 października 2026 r. też nie zawiera poprawki.
3️⃣ Rozwiązanie: czyszczenie cache przetłumaczonych stron tylko dla opublikowanych wpisów
Do czasu oficjalnej poprawki odłączasz funkcję integracji i zastępujesz ją funkcją, która czyści przetłumaczone strony tylko wtedy, gdy adres jest pewny. Obejście z komentarzy pod zgłoszeniem idzie w tym samym kierunku i od razu wychodzi z funkcji integracji, gdy wpis nie jest opublikowany.
Warunek sprawdzający tylko status wpisu zostawia jednak dwa przypadki. Strona główna przy zapisie czyści cały folder języka, choć jest opublikowana, a gdy cofniesz opublikowany wpis do szkicu, cache przetłumaczonych stron nie zostaje usunięty, więc wycofany wpis dalej otwiera się pod przetłumaczonymi adresami.
<?php
/**
* Plugin Name: Rocket TranslatePress Draft Fix
* Description: Ogranicza usuwanie cache przetłumaczonych stron przez integrację WP Rocket z TranslatePress do przypadków, gdy adres jest pewny
*/
// 1. Odłącz funkcję integracji WP Rocket od post_updated i zachowaj ją
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. Funkcja zastępcza
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'] ] );
// Strona główna — tylko pliki cache strony głównej języka, nie folder języka
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 ); // powiadom preload (5️⃣)
return;
}
// Zapis opublikowanego wpisu — oryginalna funkcja WP Rocket bez zmian
if ( 'publish' === $after->post_status ) {
call_user_func( $GLOBALS['rtdf_original'], $post_id );
return;
}
// Opublikowany → szkic·prywatny·kosz — tylko przetłumaczone strony adresu sprzed wycofania
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 );
}
// Pozostałe (szkice·wersje·autozapis) nie mają przetłumaczonego adresu, więc nic nie jest usuwane
}, 10, 3 );Pierwsza część znajduje funkcję integracji zarejestrowaną przez WP Rocket, odłącza ją od [post_updated] i zachowuje oryginał. Funkcja zastępcza dla strony głównej czyści tylko pliki cache strony głównej języka, dla opublikowanych wpisów uruchamia zachowaną oryginalną funkcję bez zmian, a gdy opublikowany wpis zostanie wycofany, znajduje i usuwa przetłumaczone strony po adresie sprzed wycofania.
Zapisany jako plik PHP w folderze [wp-content/mu-plugins/] działa od razu bez aktywacji, a po usunięciu pliku WP Rocket wraca do oryginalnego zachowania. W teście z kodem działającym tak samo ponowny zapis szkicu nie zmienił liczby plików w folderach języków, a ponowny zapis opublikowanego wpisu wyczyścił tylko foldery oryginału i przetłumaczonych stron tego wpisu.
Przed dodaniem powyższego kodu i po nim porównaliśmy liczbę plików w folderach języków dla czterech przypadków w tym samym środowisku. Foldery angielski i japoński miały po 20 plików cache na PC i urządzenia mobilne dla strony głównej języka i czterech wpisów.
Przed dodaniem kodu zapis szkicu, zapis strony głównej i cofnięcie publikacji sprowadzały oba foldery do 0, a tylko zapis opublikowanego wpisu usuwał wyłącznie cache przetłumaczonych stron tego wpisu.
Po dodaniu kodu przy zapisie szkicu i autozapisie edytora zostało wszystkie 20 plików. Zapis strony głównej usunął 4 pliki cache strony głównej języka, a zapis lub wycofanie opublikowanego wpisu usunęły tylko 4 przetłumaczone strony tego wpisu, zostawiając 16; angielski adres wycofanego wpisu zwracał 404.
| Przypadek | Przed poprawką | Po dodaniu kodu | Cache usunięty po poprawce |
|---|---|---|---|
| Zapis szkicu | 20 → 0 | 20 → 20 | Brak |
| Zapis opublikowanego wpisu | Usunięte tylko przetłumaczone strony tego wpisu | 20 → 16 | 4 przetłumaczone strony tego wpisu |
| Zapis strony głównej | 20 → 0 | 20 → 16 | 4 pliki cache strony głównej języka |
| Cofnięcie publikacji | 20 → 0 | 20 → 16 | 4 przetłumaczone strony tego wpisu · przetłumaczony adres zwraca 404 |
4️⃣ Pominięte odbudowy preload: status ukończony w kolejce
Nawet przy zablokowanym zapisie szkiców preload nie odtwarza plików cache usuniętych bez sygnału usunięcia z funkcji WP Rocket. Preload WP Rocket korzysta z tabeli kolejki, która zapisuje status każdego adresu: adresy do zażądania są oczekujące, zażądane – w toku, zbuforowane stają się ukończone, a niezakończone w ciągu 3 minut – nieudane.
Ukończony adres wraca do stanu oczekującego w trzech przypadkach: gdy jego plik usunięto po upływie czasu życia cache, gdy WP Rocket przy czyszczeniu wysłał sygnał usunięcia albo gdy nie powiódł się ponad 12 godzin temu. Jeśli jako trzeci argument [rocket_clean_files] podasz false albo usuniesz pliki bezpośrednio, sygnał nie zostanie wysłany, więc znikną tylko pliki cache, a kolejka dalej pokaże je jako ukończone.
Kod, który odbiera sygnał usunięcia i dalej czyści inne cache, podaje false jako trzeci argument, by uniknąć powtórnych wywołań. Gdy porównaliśmy cache przetłumaczonych stron, 3 z 65 angielskich i 7 z 65 japońskich stron były w kolejce jako ukończone, choć nie miały plików cache.
Współpracownik wsparcia WP Rocket przyznał też w komentarzach do zgłoszenia #6130, że po wyczyszczeniu cache adresy w tabeli zostają ukończone i stan się rozjeżdża. Propozycję okresowego uzgadniania tabeli z plikami cache (#2939) zamknięto w 2020 r. bez wdrożenia.
5️⃣ Ponowna rejestracja w preload: zgłoszenie kolejce adresów usuniętych samodzielnie
W przypadku adresów usuwanych samodzielnie wystarczy jeden wiersz, który zaraz po usunięciu powiadamia preload. Gdy preload dostaje sygnał [rocket_after_clean_terms], przywraca przekazane adresy do stanu oczekującego i dodaje te, których brakuje w tabeli.
// Po usunięciu bez sygnału — zarejestruj ponownie tablicę usuniętych adresów w kolejce preload
rocket_clean_files( $urls, null, false ); // trzeci argument false = brak sygnału usunięcia
do_action( 'rocket_after_clean_terms', $urls ); // preload przywraca te adresy do oczekiwania (pending)W WP Rocket ten sygnał odbierają tylko funkcja kolejki preload i integracja hostingu SpinUpWP, a funkcja preload jedynie aktualizuje wiersze kolejki, nie usuwając plików cache WP Rocket ani nie wysyłając ponownie sygnału, więc nie dochodzi do powtórnych wywołań. Witryny, które czyszczą cache tylko funkcjami WP Rocket i zostawiają trzeci argument domyślny, już wysyłają sygnał i nie potrzebują tego wiersza.
Po dodaniu tego wiersza wyczyściliśmy cache jednego wpisu z przetłumaczonymi stronami w czterech językach i w ciągu 2 sekund cztery wiersze kolejki przeszły w stan oczekujący. W dwóch testach na serwerze z cronem co minutę wszystkie 8 plików cache PC i mobilnych przetłumaczonych stron odbudowało się po 101 i 153 sekundach. Sprawdzisz to po statusie w tabeli kolejki i plikach w folderze cache.
# Status tego adresu w tabeli kolejki (wp_ przed nazwą tabeli to prefiks witryny)
wp db query <<'SQL'
SELECT url, status, modified FROM wp_wpr_rocket_cache WHERE url LIKE '%/en/sample-post-en'
SQLurl status modified
https://example.com/en/sample-post-en pending 2026-10-17 09:10:48# Czy pliki cache wróciły po około minucie
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_gzipZaraz po usunięciu status zmienia się na [pending], a gdy preload wyśle żądanie i powstanie plik cache, staje się [completed]. Jeśli status to [completed], a pliku cache nie ma, adres usunięto bez sygnału.
Gdy zapisaliśmy stronę główną, a powyższy kod usunął pliki cache stron głównych języków i wysłał sygnał, angielski i japoński adres strony głównej wróciły do kolejki. Cache PC i mobilny stron głównych języków odbudował się najpóźniej przy drugim uruchomieniu crona.
W dwóch testach od zapisu do ostatniego pliku cache minęło 47 i 91 sekund.
🔢 FAQ & polecane treści














ℹ️ Informacja o linkach partnerskich
Treści tej witryny zawierają linki partnerskie. Jeśli odwiedzający kupi produkt lub usługę przez taki link, witryna otrzymuje od sprzedawcy prowizję. Kwota płacona przy tym przez kupującego (podczas promocji spada↓) nie wzrasta. Podane ceny, rabaty i dostępność odnoszą się do chwili pisania i mogą różnić się od stanu faktycznego, dlatego przed zakupem sprawdź je u sprzedawcy. Produkty wybieramy i oceniamy według własnych kryteriów, a prowizja nie wpływa ani na kolejność prezentacji, ani na treść ocen.

