WP 博客多语言化实操
(5) 博客菜单翻译实操
最近,我将 WordPress 英文站从原来的 /en/ 路径迁移到了独立子域名:
中文站:www.shuijingwanwq.com
英文站:en.shuijingwanwq.com
后台:admin.shuijingwanwq.com
三个域名共用同一套 WordPress 程序、数据库和 Redis,但分别承担不同职责:
www:中文前台,通过 EdgeOne;en:英文前台,通过 Cloudflare;admin:WordPress 后台,直接连接源站;- 多语言插件:Polylang;
- 页面缓存:W3 Total Cache Disk Enhanced;
- 对象缓存:W3 Total Cache Redis Object Cache;
- 自定义代码:WPCode。
这套架构在访问、后台升级和 CDN 流量控制方面都达到了预期效果,但很快暴露出一个比较隐蔽的问题:
在
admin后台发布文章、修改 WPCode 片段或清理缓存后,www和en并不一定能够同步获得最新内容。
经过多轮源码分析、缓存对照和受控测试,最终确认问题并不是单一的“页面缓存没有清理”,而是 W3 Total Cache 在多域名环境下同时存在:
- Page Cache 清理 URL 不完整;
- Redis Object Cache 按 Host 隔离;
- 后台保存发生在
adminHost,前台缓存却存放在www和enHost; - W3TC 原生缓存组版本与单个对象缓存键的 Host 规则并不完全一致。
本文记录完整的排查过程与最终修复方案。
一、问题表现
1. 发布英文文章后,英文首页没有出现新文章
通过 admin.shuijingwanwq.com 发布英文文章后:
- 英文文章详情页可以访问;
- 文章数据库状态正常;
- 英文首页仍然保留旧文章列表。
W3TC 会清理文章详情页,也会清理一个首页,但它计算首页 URL 时受到当前后台 Host 和 Polylang 语言上下文影响。
结果是:
保存英文文章
→ 英文文章详情缓存被删除
→ 中文首页可能被删除
→ 英文首页缓存没有被删除
2. 中文首页重新生成后,仍然缺少最新两篇文章
后来又发现,即使中文首页 Page Cache 文件已经被删除,重新访问首页后,页面仍然缺少最新两篇文章。
当时最新的中文文章是:
19440
19429
但中文首页仍然从 19419 开始显示。
这说明问题已经不仅是 Page Cache。
3. 修改 WPCode 片段后,英文页面仍执行旧代码
我在 WPCode 中修复了一个未初始化变量:
$current_lang = '';
$current_series_slugs = array();
后台和中文站已经读取到新代码,但英文站仍然执行旧版本。
三个 Host 的 WPCode 缓存对比结果为:
admin:包含新初始化代码
www: 包含新初始化代码
en: 不包含新初始化代码
英文测试文章中的 CTA 数量仍然为:
0
这证明英文 Host 的 Redis Object Cache 没有同步失效。
二、先区分 Page Cache、Object Cache 与 CDN
整个排查过程中,首先要避免把所有“页面没更新”都归因于 CDN。
我分别抓取了三份中文首页:
1. 直连源站动态页面
2. EdgeOne 正常首页
3. EdgeOne 带随机参数的页面
结果显示三份页面都缺少相同的两篇文章。
同时,EdgeOne 响应状态为:
eo-cache-status: MISS
这意味着 EdgeOne 当时并没有返回旧的边缘缓存,而是正常回源。
因此问题位于 WordPress 源站内部。
三、对比正常查询与绕过缓存查询
为了确认是否为 Redis 查询缓存问题,我分别执行了:
cached
uncached
两种 WordPress 首页查询。
www Host 下的缓存查询结果:
found_posts:1312
首页前两篇:
19419
19406
强制禁用查询缓存后的结果:
found_posts:1314
首页前两篇:
19440
19429
这一步直接证明:
数据库中的文章正常,Polylang 查询正常,真正陈旧的是
wwwHost 下的 WordPress 查询对象缓存。
更明显的是,三个 Host 的 posts:last_changed 完全不同:
admin:0.97323000 1784020979
www: 0.94302500 1783993649
en: 0.97440600 1784019451
也就是说,同一个 WordPress 单站已经形成了三套互相独立的文章查询缓存。
四、W3 Total Cache 为什么会生成三套 Redis 缓存
继续查看 W3 Total Cache 源码后发现,Object Cache 的普通缓存键最初由以下内容组成:
return $blog_id . $w3tc_group . $id;
但在创建 Redis 实例时,W3TC 还会传入:
$engine_config['blog_id'] = $blog_id;
$engine_config['module'] = 'object';
$engine_config['host'] = Util_Environment::host();
$engine_config['instance_id'] = Util_Environment::instance_id();
最终 Redis 存储键的公式为:
return sprintf(
'w3tc_%d_%s_%d_%s_%s',
$this->_instance_id,
$this->_host,
$this->_blog_id,
$this->_module,
$w3tc_name
);
也就是:
w3tc_{instance_id}_{host}_{blog_id}_{module}_{name}
在当前单站环境中,blog_id 实际为 0,真正导致缓存分裂的是 Host:
w3tc_0_admin.shuijingwanwq.com_0_object_...
w3tc_0_www.shuijingwanwq.com_0_object_...
w3tc_0_en.shuijingwanwq.com_0_object_...
因此:
后台保存文章
→ admin Host 的对象缓存发生变化
→ www Host 仍然保留旧对象
→ en Host 仍然保留旧对象
这不是 Polylang 主动生成的,也不是我已有的 MU Plugin 设置了不同 Salt,而是 W3 Total Cache Object Cache 的 Host 隔离机制。
五、为什么不能直接让三个域名共享 Object Cache
看到 Host 隔离后,一个很自然的想法是:
是否可以删除 Host,让三个域名共享同一套 Redis Object Cache?
我最终没有采用这个方案。
原因是 www 与 en 并不是内容完全相同的镜像域名,而是 Polylang 的不同语言站点:
www:中文语言上下文
en: 英文语言上下文
两者可能存在不同的:
lang;- canonical;
- hreflang;
- 菜单;
- 分类;
- 首页文章列表;
- 全局样式;
- Polylang 配置对象。
之前英文站出现中文 <html lang>、canonical 指向中文站、分类跳转错误和旧全局样式,都与跨域对象缓存边界有关。
因此,更安全的方向不是取消 Host 隔离,而是:
保留三个 Host 的独立缓存对象,在必要时同步提升缓存组版本,使旧缓存整体失效。
六、W3TC 缓存组版本键恰好不包含 Host
W3TC 的普通对象缓存键包含 Host,但缓存组版本键格式为:
return sprintf(
'w3tc_%d_%d_%s_%s_key_version',
$this->_instance_id,
$this->_blog_id,
$this->_module,
$w3tc_group
);
格式为:
w3tc_{instance_id}_{blog_id}_{module}_{group}_key_version
这里没有 Host。
W3TC 的 Redis flush() 实现并不是遍历删除所有键,而是递增缓存组版本:
public function flush( $w3tc_group = '' ) {
$this->_get_key_version( $w3tc_group );
if ( isset( $this->_key_version[ $w3tc_group ] ) ) {
++$this->_key_version[ $w3tc_group ];
$this->_set_key_version(
$this->_key_version[ $w3tc_group ],
$w3tc_group
);
}
return true;
}
因此执行:
wp_cache_flush_group( 'posts' );
可以一次性让三个 Host 下旧的 posts 对象失效,而不需要清空整个 Redis 数据库。
七、受控测试:同步失效 posts 缓存组
我先记录三个 Host 的 posts:last_changed,然后从 admin 上下文执行:
wp_cache_flush_group( 'posts' );
执行结果为:
支持 flush_group:是
执行结果:true
随后重新查询三个 Host。
中文站结果从:
found_posts:1312
恢复为:
found_posts:1314
首页重新出现:
19440
19429
英文站的语言上下文和文章列表仍然正常。
这证明:
使用缓存组版本失效,可以在保留 Host 隔离的同时,解决后台保存后前台查询缓存不更新的问题。
八、Page Cache 的另一个缺口:语言首页没有加入清理队列
W3TC 保存文章时会调用:
save_post
→ w3tc_flush_post()
→ PgCache_Flush::flush_post()
→ 生成需要清理的 URL
→ 延迟执行 Page Cache 删除
文章详情页 URL 可以根据文章语言正确生成,但首页 URL 的计算依赖当前请求 Host。
当后台请求来自:
admin.shuijingwanwq.com
W3TC 并不会始终把对应语言首页加入清理队列。
因此需要在 W3TC 原生 w3tc_flush_post 回调之后,额外加入当前文章语言的首页:
$language = pll_get_post_language(
$post_id,
'slug'
);
$language_home = pll_home_url(
$language
);
w3tc_flush_url(
trailingslashit( $language_home )
);
W3TC 原生处理优先级为 1100,补充回调放在 1200,这样可以在延迟清理执行前追加 URL。
九、WPCode 缓存为什么也会在 en Host 中过期
WPCode 会把已经加载的活动片段统一存入:
wpcode_snippets
其缓存类通过:
get_option( 'wpcode_snippets' )
读取数据。
但 get_option() 会经过 WordPress Object Cache 的 options 缓存组。
因此在多 Host 环境下,实际形成:
admin Host 的 options 缓存
www Host 的 options 缓存
en Host 的 options 缓存
我修改 WPCode 片段后,数据库与 admin、www 已经是最新版,但 en 仍然保留旧对象。
当时三个 Host 的序列化缓存结果为:
admin:
SHA256:b531ca1288a6cd31179bb61ad6ca48593315e44bfc26048607d8ed7d1a243560
包含初始化语句:是
www:
SHA256:b531ca1288a6cd31179bb61ad6ca48593315e44bfc26048607d8ed7d1a243560
包含初始化语句:是
en:
SHA256:c765320e9ccc3fe0bbf9daafb16554c4c5023b19916d5fd3bcc257ca2277d14c
包含初始化语句:否
英文测试文章中的 CTA 数量为:
0
十、不监听 WPCode 保存,继续保留手动清缓存习惯
最初也考虑过:
保存 WPCode
→ 自动清全部 Page Cache
但这没有必要。
以前只有 www 一个域名时,我的习惯就是:
保存 WPCode
→ 手动清除缓存
多域名以后,理想方案应该保持相同使用习惯:
保存 WPCode
→ 性能
→ 清除模块
→ 对象缓存
→ 清除模块
→ 页面缓存
不需要让每次 WPCode 保存都自动清理整个站点,也不需要每次都清除操作码缓存。
十一、将 options 组同步挂到对象缓存清理之后
W3TC 清除对象缓存的流程为:
do_action( 'w3tc_flush_objectcache' );
$objectcache->flush();
do_action( 'w3tc_flush_after_objectcache' );
因此最合适的 Hook 是:
w3tc_flush_after_objectcache
它同时覆盖:
性能 → 清除模块 → 对象缓存
性能 → 清除所有缓存
在该 Hook 中执行:
wp_cache_flush_group( 'options' );
可以让三个 Host 的 options 缓存同时失效。
这个处理:
- 不监听 WPCode 保存;
- 不主动清 Page Cache;
- 不清空整个 Redis;
- 不影响其他对象缓存组;
- 不清除操作码缓存。
十二、最终 MU Plugin 完整代码
最终文件:
wp-content/mu-plugins/swq-w3tc-polylang-purge.php
版本为 1.3.0:
<?php
/**
* Plugin Name: SWQ W3TC Polylang Purge
* Description: Completes W3 Total Cache Page Cache and Object Cache invalidation for a Polylang multi-domain site.
* Version: 1.3.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Add the post language homepage to W3TC's current Page Cache purge queue.
*
* W3TC registers its native w3tc_flush_post callback at priority 1100.
* This callback runs afterwards and appends the missing language homepage
* before W3TC executes its delayed Page Cache cleanup.
*
* @param int $post_id Post ID being flushed.
* @param bool $force Whether W3TC forced the purge.
* @param mixed|null $extras Additional W3TC purge information.
*/
function swq_w3tc_polylang_queue_language_home(
$post_id,
$force = false,
$extras = null
) {
$post_id = (int) $post_id;
if (
$post_id < 1
|| ! function_exists( 'w3tc_flush_url' )
|| ! function_exists( 'pll_get_post_language' )
|| ! function_exists( 'pll_home_url' )
) {
return;
}
$post = get_post( $post_id );
if ( ! $post || 'post' !== $post->post_type ) {
return;
}
$language = pll_get_post_language(
$post_id,
'slug'
);
if ( ! is_string( $language ) || '' === $language ) {
return;
}
$language_home = pll_home_url( $language );
if (
! is_string( $language_home )
|| '' === $language_home
) {
return;
}
w3tc_flush_url(
trailingslashit( $language_home ),
array(
'swq_source' => 'polylang_language_home',
'post_id' => $post_id,
'language' => $language,
)
);
}
add_action(
'w3tc_flush_post',
'swq_w3tc_polylang_queue_language_home',
1200,
3
);
/**
* Invalidate the shared W3TC Redis version for the WordPress posts group.
*
* W3TC places the request Host in individual Redis object keys. As a result,
* saving a post through admin.shuijingwanwq.com normally invalidates only the
* admin Host's posts:last_changed value, leaving www and en query caches stale.
*
* W3TC's Redis group-version key does not contain the Host. Flushing the posts
* group therefore invalidates stale post queries across all three Hosts,
* without flushing the entire Redis database or unrelated cache groups.
*
* @param int $post_id Post ID being flushed.
* @param bool $force Whether W3TC forced the purge.
* @param mixed|null $extras Additional W3TC purge information.
*/
function swq_w3tc_sync_posts_object_cache_group(
$post_id,
$force = false,
$extras = null
) {
static $flushed = false;
if ( $flushed ) {
return;
}
$post_id = (int) $post_id;
if (
$post_id < 1
|| ! function_exists( 'wp_cache_flush_group' )
|| ! function_exists( 'wp_cache_supports' )
|| ! wp_cache_supports( 'flush_group' )
) {
return;
}
$post = get_post( $post_id );
if ( ! $post || 'post' !== $post->post_type ) {
return;
}
/*
* Prevent repeated group-version increments when W3TC triggers more than
* one post purge during the same WordPress request.
*/
$flushed = true;
wp_cache_flush_group( 'posts' );
}
add_action(
'w3tc_flush_post',
'swq_w3tc_sync_posts_object_cache_group',
1250,
3
);
/**
* Invalidate Host-separated options caches after W3TC Object Cache is flushed.
*
* W3TC includes the current Host in individual Object Cache keys. Its normal
* Object Cache purge therefore invalidates only the current Host namespace.
*
* The Redis group-version key does not include the Host. Flushing the options
* group makes admin, www and en reload options such as WPCode's
* wpcode_snippets data from the shared database.
*
* This callback runs after either:
*
* - Purge Module: Object Cache
* - Purge All Caches
*
* It does not initiate a Page Cache or opcode-cache purge.
*/
function swq_w3tc_sync_options_group_after_objectcache_flush() {
if (
! function_exists( 'wp_cache_flush_group' )
|| ! function_exists( 'wp_cache_supports' )
|| ! wp_cache_supports( 'flush_group' )
) {
return;
}
wp_cache_flush_group( 'options' );
}
add_action(
'w3tc_flush_after_objectcache',
'swq_w3tc_sync_options_group_after_objectcache_flush',
10,
0
);
十三、最终验证结果
1. 保存文章后的 Page Cache
执行中文文章缓存清理后:
中文首页 HTML:不存在
中文首页 GZIP:不存在
中文文章 HTML:不存在
中文文章 GZIP:不存在
英文首页 HTML:仍然存在
英文首页 GZIP:仍然存在
说明中文文章更新时:
- 中文文章详情缓存被删除;
- 中文首页缓存被删除;
- 英文首页未被误删。
2. posts 查询缓存
清理后 www 首页恢复为:
found_posts:1314
首页前两篇:
19440
19429
3. WPCode 对象缓存
执行:
性能 → 清除模块 → 对象缓存
之后三个 Host 的 WPCode 缓存完全一致:
大小:71475
SHA256:
b531ca1288a6cd31179bb61ad6ca48593315e44bfc26048607d8ed7d1a243560
包含初始化语句:是
英文测试文章中的 CTA 数量也从:
0
恢复为:
1
4. Page Cache 模块清理
执行:
性能 → 清除模块 → 页面缓存
后检查:
中文首页:有效 HTML/GZIP 不存在
英文首页:有效 HTML/GZIP 不存在
中文文章:有效缓存不存在
英文文章:有效缓存不存在
部分缓存文件会变成:
_index_slash_ssl.html_old
_index_slash_ssl.html_gzip_old
这是 W3TC 的正常清理过程。
清理后看到少量新的有效缓存文件,也不代表清理失败,因为网站仍然会被用户、搜索引擎和爬虫访问,页面会立即重新生成。
十四、页面缓存别名与对象缓存全局组能否替代插件
在 W3TC 设置中,还可以看到两个看起来与多域名有关的功能:
页面缓存 → 缓存别名主机名
对象缓存 → 全局组
但它们不能直接替代当前方案。
缓存别名主机名
这个功能更适合同一份内容通过多个镜像域名访问,例如:
example.com/article/
mirror.example.com/article/
而当前:
www = 中文
en = 英文
不是镜像关系。
中英文站拥有不同的:
- 内容;
- URL;
- 语言上下文;
- 首页;
- canonical;
- 分类与菜单。
因此不应该把 www 和 en 简单设置为页面缓存别名。
对象缓存全局组
“全局组”主要解决 WordPress Multisite 中不同站点之间的缓存组共享。
当前是普通 WordPress 单站,而且 W3TC 最终 Redis 键中仍然包含 Host。
即使把 posts 或 options 加入全局组,也不能自动移除:
admin.shuijingwanwq.com
www.shuijingwanwq.com
en.shuijingwanwq.com
这一层 Host 隔离。
因此,目前仍然需要 MU Plugin 对特定缓存组进行同步失效。
十五、现在的日常操作方式
发布或更新普通文章
正常情况下不再需要手动清缓存:
后台保存文章
→ W3TC 清理文章详情页
→ MU Plugin 补充清理对应语言首页
→ MU Plugin 同步失效 posts 对象缓存组
修改 WPCode 片段
继续保留以前的手动操作习惯:
性能
→ 清除模块
→ 对象缓存
性能
→ 清除模块
→ 页面缓存
不需要清理:
操作码缓存
也不一定需要每次都执行:
清除所有缓存
十六、当前方案的边界
本次已经完整验证:
- WordPress 源站 Page Cache;
- W3TC Redis Object Cache;
- Polylang 中英文语言首页;
- admin、www、en 三 Host;
- WPCode 活动代码缓存;
- 页面缓存模块清理;
- 对象缓存模块清理。
EdgeOne 当前返回 MISS 时,公网首页和源站内容已经一致。
不过,本文没有单独完成下面这个 CDN 场景的闭环测试:
EdgeOne 已经 HIT
→ 后台更新文章
→ 是否主动刷新边缘 HTML
这属于 CDN 层面的缓存刷新问题,与本文处理的 W3TC、Redis 和 Polylang 源站缓存问题并不相同。
如果后续再次发现:
源站已经更新
EdgeOne 仍然返回旧页面
再单独处理 EdgeOne API 或 WordPress 自动刷新机制会更合适。
十七、总结
这次问题最容易产生误判的地方是:
Page Cache 文件已经删除,并不代表页面一定会生成最新内容。
在当前多域名架构中,完整链路是:
数据库
→ WordPress 查询缓存
→ WPCode / options 对象缓存
→ W3TC Page Cache
→ CDN
只清除其中一层,页面仍然可能继续显示旧内容。
最终解决方案没有取消 W3TC 的 Host 隔离,也没有粗暴清空整个 Redis,而是利用 W3TC 本身的缓存组版本机制:
wp_cache_flush_group( 'posts' );
wp_cache_flush_group( 'options' );
这样既保留了 Polylang 多域名之间必要的隔离,又能够在关键操作发生时,让 admin、www 和 en 同步放弃旧缓存。
对于当前这套 WordPress、Polylang、W3 Total Cache、Redis 与多 CDN 架构来说,这比完全共享 Object Cache 或频繁执行 Redis Flush 更安全,也更容易维护。
WordPress 网站维护、性能优化与博客运营咨询
本站已持续运营超过 10 年,累计发布 1000+ 篇原创技术文章,长期实践 WordPress 网站建设、CDN / Cloudflare 配置、缓存优化、Google SEO、广告变现和多语言网站运营。
如果你的 WordPress 网站遇到访问慢、缓存异常、插件冲突、广告不显示、SEO 基础结构混乱、CDN 配置不确定等问题,可以联系我做一次远程技术排查。
适合以下用户:
✅ 个人博客站长
✅ WordPress 网站运营者
✅ 独立开发者与内容创作者
✅ SaaS 产品官网运营团队
✅ 希望优化网站速度与稳定性的站点
服务内容:
✅ WordPress 速度优化
✅ Cloudflare / CDN / 缓存配置排查
✅ 插件冲突与页面异常排查
✅ AdSense 广告显示问题排查
✅ SEO 基础结构检查
✅ 博客运营与商业化咨询
如需了解方案或交流相关问题,请直接联系我,并注明:WordPress 维护咨询。
联系方式:
Telegram:@shuijingwan
微信:13980074657
邮箱:shuijingwanwq@gmail.com

发表回复