Многоязычный кеш WP Rocket: почему при сохранении черновика исчезает кеш переводов и как это исправить

Многоязычный кеш WP Rocket — это кеш страниц, который хранится отдельно в языковой папке для каждого адреса перевода TranslatePress. На сервере WordPress с Nginx + Apache по коду WP Rocket была прослежена причина, по которой кеш переводов исчезает вместе со всей языковой папкой при каждом сохранении черновика, и проблема была решена кодом, который исключает неопубликованные записи из очистки кеша переводов и заново сообщает предзагрузке об адресах, удалённых вручную.
Время до первого байта, измеренное внутри того же сервера, составило 0,015 с при наличии кеша перевода и 2,492 с без него. Когда языковая папка опустевает, посетители переводов получают такой медленный ответ, пока кто-нибудь снова не откроет этот адрес и кеш не будет создан заново.
1️⃣ Устройство многоязычного кеша WP Rocket: папки по языкам и очистка переводов
Перевод — это страница, которая показывает исходную запись на другом языке, поэтому легко решить, что её кеш связан с оригиналом, но WP Rocket кеширует адреса переводов как отдельные страницы. В папке кеша рядом с папками исходных записей появляются языковые папки вроде [en] и [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=номер записи].
[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 раз.
Судя по коду, автосохранение в редакторе проходит тот же путь. Ревизии, которые сохраняются, пока вы редактируете опубликованную запись, тоже используют адрес вида [?p=].
На тестовом сайте с Nginx + Apache опубликованная запись редактировалась в редакторе с подсчётом файлов в языковых папках. После первого автосохранения в английской и японской папках оставалось по 20 файлов, а после второго автосохранения через минуту в обеих папках стало 0 файлов.
Первое автосохранение создаёт новую ревизию, а начиная со второго изменяется и сохраняется та же ревизия, поэтому срабатывает [post_updated]. Если продолжать править текст после первого автосохранения, кеш переводов удаляется вместе с языковой папкой даже без нажатия кнопки сохранения.
Тот же симптом открыт в официальном репозитории WP Rocket как issue #6130 с 31 августа 2023 года. Pull request #7616 с исправлением был отправлен в сентябре 2025 года, но не принят, и в журнале изменений версии 3.23.5.1, вышедшей 2 октября 2026 года, исправления тоже нет.
3️⃣ Код исправления: кеш переводов удаляется только для опубликованных записей
Пока нет официального исправления, функцию интеграции отключают и заменяют функцией, которая удаляет переводы только тогда, когда адрес известен наверняка. Обходной путь из комментариев к issue идёт в том же направлении: сразу выходить из функции интеграции, если запись не опубликована.
Однако условие, проверяющее только статус записи, оставляет два случая. Главная страница даже в опубликованном статусе при сохранении удаляет всю языковую папку, а если опубликованную запись вернуть в черновик, кеш её переводов не удаляется, и снятая запись продолжает открываться по адресам переводов.
<?php
/**
* Plugin Name: Rocket TranslatePress Draft Fix
* Description: Ограничивает удаление кеша переводов интеграцией WP Rocket с TranslatePress случаями, когда адрес известен наверняка
*/
// 1. Отключить функцию интеграции WP Rocket от post_updated и сохранить её
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 → 0 | 20 → 20 | Нет |
| Сохранение опубликованной записи | Удаляются только переводы этой записи | 20 → 16 | 4 файла переводов этой записи |
| Сохранение главной страницы | 20 → 0 | 20 → 16 | 4 файла кеша главной языковой версии |
| Снятие с публикации | 20 → 0 | 20 → 16 | 4 файла переводов этой записи · адрес перевода 404 |
4️⃣ Предзагрузка не пересоздаёт кеш: статус «завершено», оставшийся в очереди
Даже если сохранение черновиков заблокировано, файлы кеша, удалённые без сигнала удаления от функции WP Rocket, предзагрузка заново не заполняет. Предзагрузка WP Rocket использует таблицу очереди, где для каждого адреса записывается статус: адреса, ожидающие запроса, получают статус «ожидание», адреса с отправленным запросом — «в процессе», адреса с созданным кешем — «завершено», а то, что не завершилось за 3 минуты, — «ошибка».
Завершённый адрес возвращается в ожидание в трёх случаях: его файл удалён по истечении времени жизни кеша, WP Rocket отправил сигнал удаления при очистке или с момента ошибки прошло больше 12 часов. Если передать false третьим аргументом [rocket_clean_files] или удалить файлы напрямую, этот сигнал не отправляется, и исчезают только файлы кеша, а в очереди они по-прежнему значатся завершёнными.
Третьим аргументом false передаёт код, который получает сигнал удаления и затем очищает другой кеш, — так он избегает повторных вызовов. При сверке кеша переводов 3 из 65 английских и 7 из 65 японских переводов значились в очереди завершёнными, хотя их файлов кеша не было.
Участник команды поддержки 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'
SQLurl status modified
https://example.com/en/sample-post-en pending 2026-10-17 09:10:48# Появились ли файлы кеша снова примерно через минуту
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 & рекомендуемые материалы














ℹ️ О партнёрских ссылках
Материалы этого сайта содержат партнёрские ссылки. Если посетитель покупает товар или услугу по такой ссылке, сайт получает комиссию от продавца. При этом сумма, которую платит покупатель (при акционных скидках сумма снижается ↓), не увеличивается. Указанные цены, скидки и наличие актуальны на момент написания и могут отличаться, поэтому перед покупкой уточняйте их у продавца. Товары выбираются и оцениваются по нашим собственным критериям, а комиссия не влияет ни на порядок, ни на содержание обзоров.

