TranslatePress Slug 翻译误开启后的清理与跳转方案
现象
前段时间在排查一个 WordPress 多语言站点时,发现了一个比较典型的问题:TranslatePress 的 Slug 翻译功能被误开启了。
起因是用户在使用 TranslatePress 的过程中,不小心在设置里开启了 Slug Translation 功能。这个功能会自动将所有文章(Posts)、页面(Pages)、分类法(Taxonomy)的 URL slug 翻译成目标语言。结果当用户注意到这个问题时,已经有 135 个 slug 被自动翻译 成了西班牙语。
这个站点是英语 → 西班牙语的翻译配置。TranslatePress 默认的数据库表前缀是 trp_,但这个站点因为使用了自定义表前缀,实际表名为 test_trp_。
虽然用户后来关闭了这个功能,但那 135 个翻译后的 slug 仍然残留在数据库中。这时候就面临一个问题:如果把翻译后的 slug 直接删掉,访问这些西语 URL 的用户会看到 404 吗?
TranslatePress 的数据存储
TranslatePress 使用以下数据库表来管理 slug 翻译:
主要表结构
| 表名 | 用途 | 记录数 |
|---|---|---|
| `{prefix}_trp_slug_originals` | 存储原始 slug(所有需要翻译的 slug) | 136 |
| `{prefix}_trp_slug_translations` | 存储翻译后的 slug | 135 |
| `{prefix}_trp_dictionary_{lang_from}_{lang_to}` | 存储内容翻译(页面文字等) | 14,787 |
| `{prefix}_trp_original_strings` | 存储原始字符串 | 14,782 |
| `{prefix}_trp_gettext_{lang}` | 存储 gettext 翻译 | 274 |
Slug 翻译表结构
test_trp_slug_originals 表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 自增 ID |
| original | varchar | 原始 slug |
| type | varchar | 类型:post / taxonomy / other |
test_trp_slug_translations 表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int | 自增 ID |
| original_id | int | 关联 originals 表的 ID |
| translated | varchar | 翻译后的 slug |
| language | varchar | 目标语言代码(如 es_ES) |
| status | int | 状态(1=已翻译) |
查询已翻译的 Slug
global $wpdb;
// 查询所有原始 slug 及其翻译
$results = $wpdb->get_results("
SELECT o.id, o.original, o.type, t.translated, t.language
FROM {$wpdb->prefix}trp_slug_originals o
LEFT JOIN {$wpdb->prefix}trp_slug_translations t
ON o.id = t.original_id
ORDER BY o.id
");
// 查看 TranslatePress 设置
$settings = get_option('trp_settings');
测试:删除翻译 slug 后会不会自动跳转
在决定怎么处理之前,先做一个关键测试:只删除一条翻译记录,看看 TranslatePress 有没有内置的自动跳转机制。
测试步骤
第 1 步:删除一条翻译记录
global $wpdb;
$deleted = $wpdb->delete(
"test_trp_slug_translations",
['id' => 1] // ventanas-abatibles-de-aluminio → aluminum-casement-windows
);
第 2 步:用 curl 测试西语 URL
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}" \
"https://example.com/ventanas-abatibles-de-aluminio/"
第 3 步:观察结果
返回结果:404 — 没有跳转,直接 404。
这说明 TranslatePress 没有内置的 301 自动回退机制。删除翻译 slug 后,对应的西语 URL 直接 404,必须手动创建跳转。
对比测试(跳转配置完成后)
等所有跳转规则配置好之后(详见下文),重新测试同样的 URL:
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}" \
"https://example.com/ventanas-abatibles-de-aluminio/"
此时返回:301 https://example.com/aluminum-casement-windows/
再测试另一个还在翻译表中的西语 slug:
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}" \
"https://example.com/ventanas-correderas-de-aluminio/"
同样返回 301,说明走的是手动配置的跳转规则,与翻译记录无关。
结论
TranslatePress 没有内置 slug 跳转机制。 删除翻译记录后必须自己配好 301,否则西语 URL 直接 404。正确的顺序是:先配跳转、再删记录、最后验证。
301 Redirects 插件的数据存储与使用
在排查过程中,顺便研究了 301 Redirects 插件的数据存储方式。如果你需要用插件来管理一对一跳转(而不是 .htaccess),这些信息会很有用。
301 Redirects 插件数据存储
这个免费插件(v2.84,来自 WebFactory Ltd)将跳转规则存储在自定义数据库表中,而不是 WordPress 的 options 表。
表名: {prefix}_redirects
| 字段 | 类型 | 说明 |
|---|---|---|
| id | mediumint (auto_increment) | 主键 |
| url_from | varchar(1024) | 来源 URL(要跳转的路径) |
| url_to | varchar(1024) | 目标 URL |
| status | varchar(12) | HTTP 状态码,默认 “301” |
| type | varchar(12) | 类型,”url” 表示 URL 跳转 |
| count | mediumint | 跳转计数 |
相关 options:
| option_name | 说明 |
|---|---|
| eps_redirects_redirects | 跳转规则数组(旧版存储方式) |
| eps_redirects_404_log | 404 错误日志 |
| eps_redirects_404s | 404 统计 |
| eps_redirects_version | 插件版本 |
注意: 免费版 不支持正则匹配(regex)。正则支持是 PRO 版的功能。
如何批量写入跳转规则
用 SQL 直接 INSERT:
global $wpdb;
$table = $wpdb->prefix . 'redirects';
$wpdb->insert($table, [
'url_from' => 'ventanas-abatibles-de-aluminio',
'url_to' => '/aluminum-casement-windows/',
'status' => '301',
'type' => 'url',
'count' => 0,
]);
批量插入多条:
INSERT INTO wp_redirects (url_from, url_to, status, type, count)
VALUES
('ventanas-abatibles-de-aluminio', '/aluminum-casement-windows/', '301', 'url', 0),
('ventanas-correderas-de-aluminio', '/aluminium-sliding-windows/', '301', 'url', 0),
...
删除一条跳转规则:
global $wpdb;
$wpdb->delete($wpdb->prefix . 'redirects', ['id' => 123]);
查看现有跳转规则:
global $wpdb;
$redirects = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}redirects");
.htaccess 中的正则跳转
当免费版 301 Redirects 不支持正则时,可以用 .htaccess 的 RewriteRule 来处理需要正则匹配的跳转。
哪些场景需要正则跳转
slug 翻译中有几种类型不是一对一的,而是 base slug 的翻译:
| 原始 base slug | 翻译后 | 影响范围 |
|---|---|---|
| `author` → `autor` | 所有作者归档页 | `/autor/kimmy/` → `/author/kimmy/` |
| `category` → `categoria` | 所有分类归档页 | `/categoria/blog/` → `/category/blog/` |
| `page` → `pagina` | 所有分页 URL | `/post/pagina/2/` → `/post/page/2/` |
.htaccess 规则写法
# 放在 # BEGIN WordPress 之前(优先级最高)
RewriteEngine On
# Author base slug: /autor/xxx → /author/xxx
RewriteRule ^autor/(.+)$ /author/$1 [R=301,L]
# Category base slug: /categoria/xxx → /category/xxx
RewriteRule ^categoria/(.+)$ /category/$1 [R=301,L]
# Page base slug: /xxx/pagina/N → /xxx/page/N
RewriteRule ^(.+)/pagina/(\d+)$ /$1/page/$2 [R=301,L]
注意: 这些规则要放在 WordPress 的 # BEGIN WORDDDRESS ... # END WordPress 区块之前。因为 WordPress 的规则是 fallthrough 到 index.php,如果放后面可能永远不会匹配到。
规则解释
^autor/(.+)$— 匹配以/autor/开头的 URL,捕获后面的路径部分$1— 正则中的第一个捕获组[R=301,L]— R=301 表示 301 重定向,L 表示这是最后一条规则(不再继续匹配)
通过 PHP 写入 .htaccess
// 在 WordPress 的 rewrite 规则之前插入自定义规则
add_action('generate_rewrite_rules', function($wp_rewrite) {
$new_rules = [
'^autor/(.+)$' => 'index.php?author_name=$matches[1]',
];
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
});
不过更直接的方式是用 file_get_contents + file_put_contents 编辑 .htaccess 文件。
完整处理流程总结
- 发现阶段:用
SHOW TABLES LIKE '%trp%'或get_option('trp_settings')检查 slug 翻译状态 - 评估阶段:查询
trp_slug_translations统计有多少 slug 被翻译,区分出”一对一”的页面 slug 和”一对多”的 base slug - 测试阶段:删一条记录 → curl 验证是否自动跳转(实测返回 404,TranslatePress 无内置跳转)
- 配置跳转阶段:先配置好所有需要的 301 跳转——base slug 走 .htaccess RewriteRule,其他走 301 Redirects 插件
- 验证阶段:curl 确认跳转生效
- 清理阶段:DELETE 所有
trp_slug_translations记录
关键经验
- TranslatePress 没有 slug 自动跳转,删除记录后西语 URL 直接 404,必须手动配跳转
- 免费版 301 Redirects 不支持正则,base slug 类的跳转需要 .htaccess
- 先测跳转机制、再配规则、再删记录、最后验证,这个顺序不能乱
- 操作前备份数据库永远是好的习惯