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.org 審核但原始碼中仍包含西班牙文可翻譯字串的外掛而設計。它能自動將 PHP 和 JavaScript 檔案內的硬編碼字串改寫為英文,確保外掛能透過 translate.wordpress.org 進行多語系翻譯。接著會使用 wp-cli 重新產生 .pot 範本檔,並建立初始的 .po/.mo 翻譯檔案 (例如針對中性西班牙文 `es`,該翻譯會作為所有西班牙語系的後備),同時在外掛標頭、readme.txt 與 CHANGELOG 中同步增加版本號。最後階段會將清理後的建置版本提交至 SVN 的 trunk,並建立對應的版本標籤,完成發佈流程。整個流程會嚴格遵守 WordPress.org 的 SVN 佈局與上傳規範,並包含處理 Windows 環境常見問題 (如 UTF-8 編碼、UNC 路徑、SVN 工作副本狀態) 的解決方案。支援平台包括 TortoiseSVN (Windows CLI)、PHP 8.x 與 wp-cli,並且可選用於將發佈的版本同步回本機開發目錄。

相容平台~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 外掛:完整 i18n + 發佈至 .org 的 SVN

使用時機

  • 外掛剛通過 WP.org 審核,需要準備好讓 translate.wordpress.org 進行翻譯。
  • 可翻譯字串 (__(), _e(), wp.i18n.__() 等) 是硬編碼的西班牙文,導致其他語系 (法文、德文、葡萄牙文、日文等) 在 GlotPress 上無法翻譯。
  • 需要首次加入初始翻譯或 .pot 檔案。
  • 任何需要增加版本號並發佈至 plugins.svn.wordpress.org 的版本。

前置需求

元件如何驗證如果缺少
TortoiseSVN (svn.exe)Test-Path "C:\Program Files\TortoiseSVN\bin\svn.exe"從 https://tortoisesvn.net/downloads.html 安裝 (勾選「command line tools」)
PHP 8.x 在 PATH 中在非 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. 同步增加版本號 (標頭 Version、常數、readme 穩定標籤、CHANGELOG)
  8. 重新產生乾淨的建置版本,排除開發檔案 (.distignore)
  9. 提交至 trunk + 在 SVN 中建立標籤
  10. (可選) 同步本機目錄,保留 Git 檔案

1. 驗證環境與外掛

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

# PHP
php --version

在修改程式碼之前,先閱讀外掛:

  • 外掛標頭 (Name, Version, Text Domain, Domain Path)
  • readme.txt (Stable tag, Changelog)
  • CHANGELOG.md (如果存在)
  • includes/ 和 assets/ 目錄中的 PHP/JS 檔案列表

2. 從 SVN 取出 (Checkout) 外掛

$svnUser = '<wp-org-user>'       # WP.org 的使用者名稱 (區分大小寫)
$svnPass = 'svn_XXXXX...'     # 來自 profiles.wordpress.org 的「Account & Security」
$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 函式的第一個參數。不要動到 text-domain (第二個參數)。
  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 {verbo}""Could not {verb}"
"Debes ingresar X""You must enter X"
"Cotización API recibida""API quote received"
"Tarifa por defecto (CLP)""Default rate (CLP)"

對於大型檔案,將任務委派給一個代理 (agent),提供精確的指示和翻譯對照表。

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:使用一個讀取 .pot 並套用 EN→ES 翻譯陣列的 PHP 腳本。使用 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 (標頭)
 * 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  = '路徑/鏡像/plugin'
$dist = '路徑/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 + 提交
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 的關鍵約定

概念規則
Text Domain必須與外掛的 slug 完全相同 (例如:mi-plugin)。未經重新核准外掛,切勿更改 slug。
Domain Path/languages/ 以便 WP.org 找到 .pot 檔案
Plugin Name 標頭開頭不要包含第三方商標 (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 路徑

如果當前工作目錄 (CWD) 是 UNC 路徑 (\\server\share),PHP 和許多二進位檔會失敗。在執行命令之前:

Set-Location C:\Users\<使用者>   # 或任何本機目錄
php $wpCli ...                # 現在可以正常運作

PHP (Windows) 中帶重音符號字元的編碼問題

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。

外掛在標頭中使用西班牙文字串

外掛標頭的字串 (Plugin Name、Description 等) 是不可翻譯的。但如果 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 — 編譯的二進位檔案
  • .distignore — 如有需要則建立/更新
  • 在 plugins.svn.wordpress.org 上建立 tags/X.Y.Z/

Individual skills in this repo

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

相關技能