Community编程与开发github.com

wp-plugin-i18n-release

将托管在 plugins.svn.wordpress.org 上的 WordPress 插件进行完整的国际化处理并发布到 SVN。涵盖:将硬编码的西班牙语字符串重写为英语(PHP 和 JS),使用 wp-cli 生成 .pot 文件,创建初始 .po/.mo 翻译(例如中性西班牙语),同步版本号,提交到 trunk 并创建标签。

wp-plugin-i18n-release 是什么?

该技能自动化了 WordPress 插件的国际化(i18n)和发布流程,专门针对托管在 plugins.svn.wordpress.org 上的插件。当插件刚刚在 WP.org 获得批准,可翻译字符串被硬编码为西班牙语并阻碍其他语言环境,或者当需要添加/更新翻译并发布新版本时使用。该技能执行完整的工作流程:将已翻译的字符串重写为英语源字符串,使用 wp-cli 命令 regenerates .pot 和 .po/.mo 文件,将版本号同步到多个文件,构建干净的发布包,并提交到 SVN trunk 和标签。支持通过 TortoiseSVN(Windows)和 PHP 环境进行操作,包括处理 Unicode 编码问题、SVN 工作副本管理以及维护 WordPress.org 的 SVN 布局约定。可选功能包括与本地开发目录同步以保留 Git 文件。

兼容平台~Claude Code~Codex CLI~Cursor✓OpenCode
npx skills add https://github.com/leoarayas/agent-skills/tree/main/wp-plugin-i18n-release

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

WordPress 插件:完整国际化 + 发布到 SVN

何时使用

  • 刚刚在 WP.org 获得批准,需要准备好用于 translate.wordpress.org 的插件。
  • 可翻译字符串(__()、_e()、wp.i18n.__() 等)被硬编码为西班牙语,阻碍了其他语言环境(如 fr、de、pt、ja 等)在 GlotPress 中工作。
  • 需要首次添加初始翻译或 .pot 文件。
  • 任何需要更新版本并发布到 plugins.svn.wordpress.org 的发布。

先决条件

组件验证方式如果缺失
TortoiseSVN (svn.exe)Test-Path "C:\Program Files\TortoiseSVN\bin\svn.exe"从 https://tortoisesvn.net/downloads.html 安装(勾选“命令行工具”)
PATH 中的 PHP 8.x从非 UNC 目录运行 php --versionHerd legacy 可以;或独立的 PHP
wp-cli.phar从 https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 下载Invoke-WebRequest -Uri ... -OutFile wp-cli.phar
插件的 SVN 访问权限来自 [email protected] 的批准邮件,包含 SVN URL、用户名和密码如果缺失,向团队请求

完整工作流程

  1. 验证环境和插件
  2. 插件的 SVN Checkout
  3. 定位代码中的可翻译字符串(PHP + JS)
  4. 将字符串重写为英语,同时保持技术语义
  5. 使用 wp i18n make-pot 重新生成 .pot
  6. 生成初始翻译的 .po + .mo(例如中性西班牙语)
  7. 同步更新版本号(header Version、常量、readme 稳定版标签、CHANGELOG)
  8. 重新生成干净的构建包,排除开发文件(.distignore)
  9. 提交到 trunk + 在 SVN 中创建标签
  10. (可选)同步本地目录,同时保留 Git 文件

1. 验证环境和插件

# Windows 上的 SVN(通常不在 PATH 中;使用完整路径或添加)
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
svn --version

# PHP
php --version

在修改代码之前,阅读插件:

  • 插件头部(名称、版本、文本域、域路径)
  • readme.txt(稳定版标签、更新日志)
  • CHANGELOG.md(如果存在)
  • includes/ 和 assets/ 中的 PHP/JS 文件列表

2. 插件的 SVN Checkout

$svnUser = '<wp-org-user>'       # WP.org 用户名(区分大小写)
$svnPass = 'svn_XXXXX...'     # 来自 profiles.wordpress.org 的“账户与安全”
$slug    = 'mi-plugin'        # 插件的 slug
$checkout = "C:\Temp\opencode\$slug-svn"

svn checkout --non-interactive --trust-server-cert `
  --username $svnUser --password $svnPass `
  "https://plugins.svn.wordpress.org/$slug" $checkout

SVN 已经包含 trunk/、tags/、assets/。如果你刚刚上传了第一个版本,tags/ 会为空,trunk/ 也可能为空(刚刚用初始 trunk 创建)。

3. 定位可翻译字符串

识别所有对 i18n 函数的调用。使用 grep 配合以下模式:

# PHP
Select-String -Path 'includes/*.php' -Pattern '__\(|_e\(|_x\(|esc_html__\(|esc_attr__\(|esc_html_e\(|esc_attr_e\(|_n\(|_nx\('

# JS (wp.i18n 或没有函数的硬编码字符串)
Select-String -Path 'assets/js/*.js' -Pattern 'wp\.i18n|__\('
Select-String -Path 'assets/js/*.js' -Pattern '"[^"]{6,80}"'   # 检测硬编码字符串

常见反模式:JS 中拼接的 HTML 中使用了西班牙语字符串(例如 html += '<option>Todas</option>';)。直接重写为英语是可以的;如果你想在运行时使其可翻译,使用 wp.i18n.__() 更好。

4. 将字符串重写为英语

严格规则:

  1. 只翻译 i18n 函数的第一个参数。不要动文本域(第二个参数)。
  2. 不要动 i18n 调用之外的任何内容。
  3. 不要修改 PHP 格式(空格、引号、括号)。
  4. 技术术语如 ExampleStore、WooCommerce、API、Correlation ID、Premium、JSON、HTTP、URL、CLP 保持英文(它们已经是专有名词/缩写)。
  5. 完全保留 sprintf/printf 的占位符(%s、%d、%1$s 等)。
  6. 如果字符串已经是英文,不要动它。

建议翻译表(根据上下文调整):

西班牙语英语
"Activo""Enabled"
"Título""Title"
"Configurar""Configure"
"Guardar""Save"
"Eliminar""Delete"
"Comuna""Commune"
"Región""Region"
"Selecciona una región""Select a region"
"Sin reglas guardadas""No saved rules"
"Configura la URL de API y el token""Configure API URL and token"
"Diagnóstico ejecutado correctamente""Diagnostic executed successfully"
"No se pudo {动词}""Could not {动词}"
"Debes ingresar X""You must enter X"
"Cotización API recibida""API quote received"
"Tarifa por defecto (CLP)""Default rate (CLP)"

对于大文件,将任务委托给一个代理,并提供精确的指令和翻译表。

5. 使用 wp-cli 重新生成 .pot

# 从插件目录(本地镜像)
$wpCli = 'C:\Temp\opencode\wp-cli.phar'
php $wpCli i18n make-pot . "languages/mi-plugin.pot" --domain="mi-plugin"

.pot 文件必须位于 /languages/ 目录中,文件名为 <text-domain>.pot。

6. 创建翻译 .po + .mo

关键决策:使用哪个语言环境?为了让插件自动翻译给所有西班牙语使用者而无需区域变体,使用 <text-domain>-es.po(根 es)。WordPress 会进行级联回退:es_CL → es → 英语。

推荐策略:

  • es.po/.mo(根)→ 服务于 es_CL、es_ES、es_MX、es_AR、es_PE 等。
  • (可选之后)为智利本地习语创建 es_CL.po/.mo。

生成 .po 文件:使用一个 PHP 脚本,读取 .pot 并应用 EN→ES 的翻译数组。对于带重音符号的字符,使用 mb_chr(0x00f3)(Windows 上的 PHP 在处理 UTF-8 字面量时存在代码页问题)。

// build-es.php
declare(encoding='UTF-8');
$pot = file_get_contents('languages/mi-plugin.pot');
$po_es = 'languages/mi-plugin-es.po';

// 预载 `mb_chr` 用于带重音符号的字符
function A() { return mb_chr(0x00e1); } // á
function E() { return mb_chr(0x00e9); } // é
function O() { return mb_chr(0x00f3); } // ó
// 等等

$t = [
    "Configure API URL and token" => "Configura la URL de API y el token",
    // ...
];

$header = "msgid \"\"\nmsgstr \"\"\n"
        . "\"Project-Id-Version: Mi Plugin 1.0.0\\n\"\n"
        . "\"Language-Team: Español (neutro) <>\\n\"\n"
        . "\"Language: es\\n\"\n"
        . "\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n";

// 解析 .pot,用 $t 填充 msgstr,写入 .po
//(完整逻辑:参见 wp-plugin-i18n-release/build-es.php)

编译 .mo:

php $wpCli i18n make-mo languages/

这将自动创建 languages/<text-domain>-es.mo。

7. 同步更新版本号

更新四个文件:

// 1. my-plugin.php (header)
 * Version: 1.1.0

// 2. my-plugin.php (常量)
define('PLUGIN_VERSION', '1.1.0');

// 3. readme.txt
Stable tag: 1.1.0
/* + 在 == Changelog == 中添加新条目 */

// 4. CHANGELOG.md
## [1.1.0] - 2026-09-14
### Changed
- ...

8. 重新生成干净的构建包(排除开发文件)

插件的 .distignore 文件定义了要排除的内容。如果不存在,使用以下标准列表:

$exclude = @('.git','.github','.gitignore','.gitattributes','.distignore',
             '.idea','.DS_Store','.release-build','bin','node_modules',
             'tests','vendor/bin','phpcs.xml*','phpunit.xml*',
             '.phpunit.result.cache','*.lock','*.log','*.zip',
             'README.md','RELEASING.md','Makefile','.pr-body-*.md')

# 清空目标,复制所有内容,删除排除项
$src  = 'ruta/espejo/plugin'
$dist = 'ruta/build/plugin'
Copy-Item -Path "$src\*" -Destination $dist -Recurse -Force
foreach ($pat in $exclude) {
  Get-ChildItem -Path $dist -Recurse -Force -Filter $pat -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force
}

9. 提交到 trunk + 创建标签

$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"

# 用干净的构建包替换 trunk
$trunk = "C:\Temp\opencode\$slug-svn\trunk"
Get-ChildItem -Path $trunk -Force | Where-Object { $_.Name -ne '.svn' } | Remove-Item -Recurse -Force
Copy-Item -Path "$dist\*" -Destination $trunk -Recurse -Force

# svn add + commit
Set-Location $trunk
svn add --force --depth infinity .

svn commit --non-interactive --trust-server-cert `
  --username $svnUser --password $svnPass `
  -m "Release 1.1.0: English source strings, full i18n rewrite, initial Spanish (es) translation"

# 创建标签(远程 svn copy)
svn copy --non-interactive --trust-server-cert `
  --username $svnUser --password $svnPass `
  -m "Tagging 1.1.0" `
  "https://plugins.svn.wordpress.org/$slug/trunk" `
  "https://plugins.svn.wordpress.org/$slug/tags/1.1.0"

远程标签创建后,强制更新本地工作副本(新标签不会在工作副本中完全体现):

svn cleanup
svn update --depth infinity --force --non-interactive --trust-server-cert `
  --username $svnUser --password $svnPass "$trunk/../"

10. (可选)同步本地目录

保留不在 SVN 导出中的文件(.git/、.gitignore、tests/、bin/ 等):

$local    = 'C:\Users\...\wp-content\plugins\mi-plugin'
$exportDir = 'C:\Users\...\export-1.1.0'

# 导出刚创建的标签
svn export "https://plugins.svn.wordpress.org/$slug/tags/1.1.0" $exportDir

# 仅删除本地目录中属于该发布版本的文件,保留其他内容
$releaseNames = (Get-ChildItem $exportDir -Force).Name
foreach ($name in $releaseNames) {
  $localPath = Join-Path $local $name
  if (Test-Path $localPath) { Remove-Item $localPath -Recurse -Force }
}

# 将导出内容复制到本地目录
Copy-Item -Path "$exportDir\*" -Destination $local -Recurse -Force

11. 最终验证

# 本地版本
Write-Host "header: " (Get-Content "$local\mi-plugin.php" | Select-String -Pattern 'Version:' | Select-Object -First 1).ToString().Trim()
Write-Host "readme: " (Get-Content "$local\readme.txt" | Select-String -Pattern 'Stable tag:' | Select-Object -First 1).ToString().Trim()

# 验证远程 SVN
svn ls --recursive "https://plugins.svn.wordpress.org/$slug/tags/1.1.0/"

WordPress.org 的关键约定

概念规则
文本域必须与插件 slug 完全相同(例如 mi-plugin)。未经重新批准,切勿更改 slug。
域路径/languages/,以便 WP.org 找到 .pot 文件
插件名称头部开头不要包含第三方商标(WP.org 审核会拒绝)
readme.txtWP.org 解析此文件(而不是 README.md)。Stable tag 必须与版本号匹配。
SVN 布局/trunk/(开发)、/tags/X.Y.Z/(发布)、/assets/(横幅、图标、截图)
SVN 作为发布系统“只推送准备就绪的版本”。不要在 trunk 中进行频繁的开发提交。
翻译回退机制WP 级联查找:text-domain-locale.mo → text-domain.mo → text-domain-en_US.mo(核心)。根区域设置 es 服务于所有 es_XX 变体。

陷阱和常见错误

PowerShell 和 UNC 路径

如果当前工作目录是 UNC 路径(\server\share),PHP 和许多二进制文件会失败。在运行命令之前:

Set-Location C:\Users\<usuario>   # 或任何本地目录
php $wpCli ...                # 现在可以正常工作

Windows 上 PHP 中带重音符号字符的编码问题

Windows 上的 PHP 在处理脚本中的 UTF-8 字面量时存在问题。解决方案:对每个带重音符号的字符使用 mb_chr(0x00f3):

"Obtener pa" . mb_chr(0x00ed) . "s"  // "Obtener país"

PowerShell 在执行 Get-Content 时也可能显示 ? 而不是带重音符号的字符。验证文件的字节,而不是渲染:

$bytes = [System.IO.File]::ReadAllBytes('archivo.po')
# 查找 0xC3 0xB3 ('ó' 的 UTF-8 编码)

新标签后 SVN 工作副本似乎为空

SVN 在 trunk 和 tag 之间对文件进行去重(相同的“blob”)。远程 svn copy 后,文件出现在服务器上,但本地工作副本只有占位符。解决方案:svn cleanup + svn update --depth infinity --force。

svn.exe 不在 PATH 中

TortoiseSVN 安装在 C:\Program Files\TortoiseSVN\bin\,但默认不会将 svn.exe 添加到 PATH。临时解决方案:

$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"

或永久添加到用户 PATH 中。

插件在头部使用了西班牙语字符串

插件头部的字符串(插件名称、描述等)不可翻译。但是,如果 wp i18n 将它们包含在 .pot 中,保持 msgstr ""(空)。WP.org 和 GlotPress 知道它们不会被翻译。

不要包含由 GlotPress 管理的语言环境的 .po/.mo 文件

一旦 GlotPress 通过社区为你的插件提供了翻译,WP.org 会自动提供 .mo 文件。你初始的 .po/.mo 文件只是初始种子。不要与 GlotPress 对抗。


发布版本修改的文件(检查清单)

  • my-plugin.php — 头部 Version + PLUGIN_VERSION 常量
  • readme.txt — Stable tag + Changelog 中的条目
  • CHANGELOG.md — 新条目
  • languages/<text-domain>.pot — 重新生成
  • languages/<text-domain>-es.po — 初始翻译(可选)
  • languages/<text-domain>-es.mo — 已编译(可选)
  • 所有 PHP/JS 字符串均为英文

外部资源

…

[为翻译而截断]

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能