name: wp-plugin-i18n-release description: Prepara um plugin WordPress hospedado em plugins.svn.wordpress.org para internacionalização completa e publica o release no SVN. Abrange: reescrever strings hardcoded do espanhol para inglês (PHP e JS), gerar .pot com wp-cli, criar tradução inicial .po/.mo (ex: espanhol neutro), incrementar versão sincronizada, commitar no trunk e criar tag. Use quando o plugin acabou de ser aprovado no WP.org, quando as strings traduzíveis estão em espanhol e bloqueiam outras locales, ou quando deseja adicionar/atualizar traduções e enviar um novo release.
Plugin WordPress: i18n completo + Release ao SVN do .org
Quando usar
- Plugin recém-aprovado no WP.org que precisa estar pronto para translate.wordpress.org.
- Strings traduzíveis (
__(),_e(),wp.i18n.__(), etc.) estão hardcoded em espanhol e bloqueiam outras locales (fr, de, pt, ja, etc.) no GlotPress. - É necessário adicionar uma tradução inicial ou um
.potpela primeira vez. - Qualquer release que incrementa versão e é publicada no SVN de plugins.svn.wordpress.org.
Pré-requisitos
| Componente | Como verificar | Se faltar |
|---|---|---|
| TortoiseSVN (svn.exe) | Test-Path "C:\Program Files\TortoiseSVN\bin\svn.exe" | Instalar de https://tortoisesvn.net/downloads.html (marcar "command line tools") |
| PHP 8.x no PATH | php --version a partir de um dir não-UNC | Herd legacy está ok; ou PHP standalone |
| wp-cli.phar | Baixar de https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar | Invoke-WebRequest -Uri ... -OutFile wp-cli.phar |
| Acesso SVN ao plugin | Email de aprovação de [email protected] com URL SVN, username e password | Solicitar à equipe se faltar |
Fluxo de trabalho completo
- Verificar ambiente e plugin
- Fazer checkout do SVN do plugin
- Localizar strings traduzíveis no código (PHP + JS)
- Reescrever strings para inglês mantendo semântica técnica
- Regenerar
.potcomwp i18n make-pot - Gerar
.po+.moda tradução inicial (ex: espanhol neutro) - Incrementar versão sincronizada (header
Version, constante, stable tag no readme, CHANGELOG) - Regenerar build limpo excluindo arquivos de desenvolvimento (
.distignore) - Commit ao trunk + criar tag no SVN
- (Opcional) Sincronizar o diretório local preservando arquivos do Git
1. Verificar ambiente e plugin
# SVN no Windows (geralmente não está no PATH; usar caminho completo ou adicionar)
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
svn --version
# PHP
php --version
Antes de tocar no código, ler o plugin:
- Plugin header (Name, Version, Text Domain, Domain Path)
readme.txt(Stable tag, Changelog)CHANGELOG.md(se existir)- Lista de arquivos PHP/JS em
includes/eassets/
2. Checkout do SVN
$svnUser = '<wp-org-user>' # username WP.org (case-sensitive)
$svnPass = 'svn_XXXXX...' # de "Account & Security" em profiles.wordpress.org
$slug = 'meu-plugin' # o slug do plugin
$checkout = "C:\Temp\opencode\$slug-svn"
svn checkout --non-interactive --trust-server-cert `
--username $svnUser --password $svnPass `
"https://plugins.svn.wordpress.org/$slug" $checkout
O SVN já contém trunk/, tags/, assets/. Se você acabou de enviar o primeiro release, tags/ estará vazio e trunk/ também (recém-criado com o trunk inicial).
3. Localizar strings traduzíveis
Identifique TODAS as chamadas para funções i18n. Use grep com estes padrões:
# PHP
Select-String -Path 'includes/*.php' -Pattern '__\(|_e\(|_x\(|esc_html__\(|esc_attr__\(|esc_html_e\(|esc_attr_e\(|_n\(|_nx\('
# JS (wp.i18n ou strings hardcoded sem função)
Select-String -Path 'assets/js/*.js' -Pattern 'wp\.i18n|__\('
Select-String -Path 'assets/js/*.js' -Pattern '"[^"]{6,80}"' # detecta strings hardcoded
Anti-padrão comum: strings em espanhol dentro de HTML concatenado em JS (ex: html += '<option>Todas</option>';). Reescrever para inglês diretamente é válido; usar wp.i18n.__() é melhor se você quiser que sejam traduzíveis em tempo de execução.
4. Reescrever strings para inglês
Regras estritas:
- SOMENTE traduza o primeiro argumento das funções i18n. NÃO toque no text-domain (segundo argumento).
- NÃO toque em nada fora de uma chamada i18n.
- NÃO modifique o formato PHP (espaços, aspas, parênteses).
- Termos técnicos como
ExampleStore,WooCommerce,API,Correlation ID,Premium,JSON,HTTP,URL,CLPpermanecem em inglês (já são nomes próprios / siglas). - PRESERVE placeholders de sprintf/printf (
%s,%d,%1$s, etc.) exatamente como estão. - Se uma string já está em inglês, NÃO a toque.
Tabela de tradução recomendada (ajuste ao contexto):
| Espanhol | Inglês |
|---|---|
| "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 {verbo}" |
| "Debes ingresar X" | "You must enter X" |
| "Cotización API recibida" | "API quote received" |
| "Tarifa por defecto (CLP)" | "Default rate (CLP)" |
Para arquivos grandes, delegue a um agente com instruções precisas e a tabela de traduções.
5. Regenerar .pot com wp-cli
# Do diretório do plugin (espelho local)
$wpCli = 'C:\Temp\opencode\wp-cli.phar'
php $wpCli i18n make-pot . "languages/meu-plugin.pot" --domain="meu-plugin"
O .pot deve estar em /languages/ com o nome <text-domain>.pot.
6. Criar tradução .po + .mo
Decisão chave: qual locale? Para que o plugin seja traduzido automaticamente para todos os falantes de espanhol sem variantes regionais, use <text-domain>-es.po (raiz es). O WordPress faz fallback em cascata: es_CL → es → inglês.
Estratégia recomendada:
es.po/.mo(raiz) → atende es_CL, es_ES, es_MX, es_AR, es_PE, etc.- (Opcional depois)
es_CL.po/.moespecífico para Chile com regionalismos.
Gerar o .po: use um script PHP que leia o .pot e aplique um array de traduções EN→ES. Use mb_chr(0x00f3) para caracteres acentuados (PHP no Windows tem problemas de codepage com literais UTF-8).
// build-es.php
declare(encoding='UTF-8');
$pot = file_get_contents('languages/meu-plugin.pot');
$po_es = 'languages/meu-plugin-es.po';
// Pré-carregar `mb_chr` para caracteres acentuados
function A() { return mb_chr(0x00e1); } // á
function E() { return mb_chr(0x00e9); } // é
function O() { return mb_chr(0x00f3); } // ó
// etc.
$t = [
"Configure API URL and token" => "Configura la URL de API y el token",
// ...
];
$header = "msgid \"\"\nmsgstr \"\"\n"
. "\"Project-Id-Version: Meu 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";
// Parsear .pot, preencher msgstr com $t, escrever .po
// (lógica completa: ver wp-plugin-i18n-release/build-es.php)
Compilar .mo:
php $wpCli i18n make-mo languages/
Isso cria automaticamente languages/<text-domain>-es.mo.
7. Incrementar versão sincronizada
Atualize quatro arquivos:
// 1. meu-plugin.php (header)
* Version: 1.1.0
// 2. meu-plugin.php (constante)
define('PLUGIN_VERSION', '1.1.0');
// 3. readme.txt
Stable tag: 1.1.0
/* + nova entrada em == Changelog == */
// 4. CHANGELOG.md
## [1.1.0] - 2026-09-14
### Changed
- ...
8. Regenerar build limpo (excluindo arquivos de desenvolvimento)
O .distignore do plugin define o que excluir. Se não existir, use esta lista padrão:
$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')
# Limpar destino, copiar tudo, remover excluídos
$src = 'caminho/espelho/plugin'
$dist = 'caminho/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. Commit ao trunk + criar tag
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
# Substituir trunk pelo build limpo
$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"
# Criar tag (svn copy remoto)
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"
Após tag remoto, forçar atualização local (as novas tags não são materializadas completamente na working copy):
svn cleanup
svn update --depth infinity --force --non-interactive --trust-server-cert `
--username $svnUser --password $svnPass "$trunk/../"
10. (Opcional) Sincronizar diretório local
Preservar arquivos que NÃO estão no SVN export (.git/, .gitignore, tests/, bin/, etc.):
$local = 'C:\Users\...\wp-content\plugins\meu-plugin'
$exportDir = 'C:\Users\...\export-1.1.0'
# Export da tag recém-criada
svn export "https://plugins.svn.wordpress.org/$slug/tags/1.1.0" $exportDir
# Remover APENAS os arquivos do release do local, manter o resto
$releaseNames = (Get-ChildItem $exportDir -Force).Name
foreach ($name in $releaseNames) {
$localPath = Join-Path $local $name
if (Test-Path $localPath) { Remove-Item $localPath -Recurse -Force }
}
# Copiar o export sobre o local
Copy-Item -Path "$exportDir\*" -Destination $local -Recurse -Force
11. Verificação final
# Versões no local
Write-Host "header: " (Get-Content "$local\meu-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()
# Verificar SVN remoto
svn ls --recursive "https://plugins.svn.wordpress.org/$slug/tags/1.1.0/"
Convenções críticas do WordPress.org
| Conceito | Regra |
|---|---|
| Text Domain | Deve ser idêntico ao slug do plugin (ex: meu-plugin). Nunca mude o slug sem re-aprovar o plugin. |
| Domain Path | /languages/ para que o WP.org encontre o .pot |
| Plugin Name header | Não incluir marcas registradas de terceiros no início (revisão WP.org rejeita) |
| readme.txt | WP.org interpreta este arquivo (não README.md). O Stable tag deve coincidir com a versão. |
| SVN layout | /trunk/ (desenvolvimento), /tags/X.Y.Z/ (releases), /assets/ (banners, ícone, screenshots) |
| SVN como sistema de release | "Apenas envie versões prontas para uso". Não faça commits de desenvolvimento frequentes no trunk. |
| Fallback de traduções | WP busca em cascata: text-domain-locale.mo → text-domain.mo → text-domain-en_US.mo (núcleo). O locale raiz es atende TODAS as variantes es_XX. |
Armadilhas e erros comuns
PowerShell e UNC paths
PHP e muitos binários falham se o CWD é um caminho UNC (\\server\share). Antes de executar comandos:
Set-Location C:\Users\<usuario> # ou qualquer dir local
php $wpCli ... # agora funciona
Codificação de caracteres acentuados no PHP (Windows)
PHP no Windows tem problemas com literais UTF-8 em scripts. Solução: usar mb_chr(0x00f3) para cada caractere acentuado:
"Obtener pa" . mb_chr(0x00ed) . "s" // "Obtener país"
PowerShell também pode exibir ? em vez de caracteres acentuados ao fazer Get-Content. Verifique os bytes do arquivo, não a renderização:
$bytes = [System.IO.File]::ReadAllBytes('arquivo.po')
# Procurar 0xC3 0xB3 (UTF-8 de 'ó')
Working copy SVN parece vazia após uma nova tag
SVN deduplica arquivos entre trunk e tag (mesmo "blob"). Após svn copy remoto, os arquivos aparecem no servidor, mas a working copy local tem placeholders. Solução: svn cleanup + svn update --depth infinity --force.
svn.exe não está no PATH
TortoiseSVN é instalado em C:\Program Files\TortoiseSVN\bin\ mas não adiciona svn.exe ao PATH por padrão. Solução temporária:
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
Ou adicionar permanentemente ao PATH do usuário.
O plugin usa strings em espanhol no header
As strings do header do plugin (Plugin Name, Description, etc.) NÃO são traduzíveis. Mas se wp i18n as incluir no .pot, deixe-as com msgstr "" (vazio). WP.org e GlotPress sabem que não são traduzidas.
Não incluir .po/.mo para locales gerenciados pelo GlotPress
Uma vez que o GlotPress tenha traduções para seu plugin (através da comunidade), o WP.org fornece automaticamente os .mo. Seus .po/.mo iniciais são apenas a semente inicial. Não lute contra o GlotPress.
Arquivos modificados por release (checklist)
-
meu-plugin.php— header Version + PLUGIN_VERSION -
readme.txt— Stable tag + entrada no Changelog -
CHANGELOG.md— nova entrada -
languages/<text-domain>.pot— regenerado -
languages/<text-domain>-es.po— criado/atualizado -
languages/<text-domain>-es.mo— compilado