WordPress-Plugin: Vollständige i18n + Release ins .org-SVN
Wann verwenden
- Neu genehmigtes Plugin auf WP.org, das für translate.wordpress.org bereit sein muss.
- Übersetzbare Strings (
__(),_e(),wp.i18n.__(), etc.) sind hartcodiert auf Spanisch und sperren andere Locales (fr, de, pt, ja, etc.) in GlotPress. - Eine initiale Übersetzung oder eine erste
.pot-Datei muss hinzugefügt werden. - Jedes Release, das die Version erhöht und im SVN von plugins.svn.wordpress.org veröffentlicht wird.
Voraussetzungen
| Komponente | Überprüfung | Fehlt? |
|---|---|---|
| TortoiseSVN (svn.exe) | Test-Path "C:\Program Files\TortoiseSVN\bin\svn.exe" | Installieren von https://tortoisesvn.net/downloads.html (Haken bei "Command Line Tools" setzen) |
| PHP 8.x im PATH | php --version von einem Nicht-UNC-Verzeichnis | Herd legacy ist okay; oder eigenständiges PHP |
| wp-cli.phar | Herunterladen von https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar | Invoke-WebRequest -Uri ... -OutFile wp-cli.phar |
| SVN-Zugriff auf das Plugin | E-Mail zur Genehmigung von [email protected] mit SVN-URL, Benutzername und Passwort | Beim Team anfragen, falls nicht vorhanden |
Kompletter Workflow
- Umgebung und Plugin überprüfen
- SVN-Checkout des Plugins
- Übersetzbare Strings im Code lokalisieren (PHP + JS)
- Strings ins Englische umschreiben unter Beibehaltung der technischen Semantik
.pot-Datei neu generieren mitwp i18n make-pot.po+.moerstellen der initialen Übersetzung (z.B. neutrales Spanisch)- Synchronisierte Version erhöhen (Header
Version, Konstante, readme stable tag, CHANGELOG) - Sauberen Build neu generieren unter Ausschluss von Entwicklungsdateien (
.distignore) - Commit ins trunk + Tag im SVN erstellen
- (Optional) Lokales Verzeichnis synchronisieren unter Beibehaltung von Git-Dateien
1. Umgebung und Plugin überprüfen
# SVN unter Windows (normalerweise nicht im PATH; vollständigen Pfad verwenden oder hinzufügen)
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
svn --version
# PHP
php --version
Vor dem Ändern von Code: Plugin lesen:
- Plugin-Header (Name, Version, Text Domain, Domain Path)
readme.txt(Stable tag, Changelog)CHANGELOG.md(falls vorhanden)- Liste der PHP/JS-Dateien in
includes/undassets/
2. SVN-Checkout
$svnUser = '<wp-org-user>' # WP.org-Benutzername (Groß-/Kleinschreibung beachten)
$svnPass = 'svn_XXXXX...' # Von "Account & Security" in profiles.wordpress.org
$slug = 'mein-plugin' # Der 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 enthält bereits trunk/, tags/, assets/. Wenn Sie gerade das erste Release hochgeladen haben, ist tags/ leer und trunk/ ebenfalls (gerade mit dem initialen trunk erstellt).
3. Übersetzbare Strings lokalisieren
Identifizieren Sie ALLE Aufrufe von i18n-Funktionen. Verwenden Sie grep mit diesen Mustern:
# PHP
Select-String -Path 'includes/*.php' -Pattern '__\(|_e\(|_x\(|esc_html__\(|esc_attr__\(|esc_html_e\(|esc_attr_e\(|_n\(|_nx\('
# JS (wp.i18n oder hartcodierte Strings ohne Funktion)
Select-String -Path 'assets/js/*.js' -Pattern 'wp\.i18n|__\('
Select-String -Path 'assets/js/*.js' -Pattern '"[^"]{6,80}"' # erkennt hartcodierte Strings
Häufiges Anti-Pattern: spanische Strings in HTML, das in JS konkateniert wird (z.B. html += '<option>Todas</option>';). Umschreiben ins Englische ist direkt gültig; die Verwendung von wp.i18n.__() ist besser, wenn sie zur Laufzeit übersetzbar sein sollen.
4. Strings ins Englische umschreiben
Strenge Regeln:
- Übersetzen Sie NUR das erste Argument der i18n-Funktionen. Berühren Sie NICHT die Text-Domain (zweites Argument).
- Berühren Sie NICHTS außerhalb eines i18n-Aufrufs.
- Ändern Sie NICHT das PHP-Format (Leerzeichen, Anführungszeichen, Klammern).
- Technische Begriffe wie
ExampleStore,WooCommerce,API,Correlation ID,Premium,JSON,HTTP,URL,CLPbleiben auf Englisch (sie sind bereits Eigennamen / Akronyme). - BEHALTEN Sie Platzhalter von sprintf/printf (
%s,%d,%1$s, etc.) exakt gleich bei. - Wenn ein String bereits auf Englisch ist, berühren Sie ihn NICHT.
Empfohlene Übersetzungstabelle (an den Kontext anpassen):
| Spanisch | Englisch |
|---|---|
| "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)" |
Bei großen Dateien: Delegieren Sie an einen Agenten mit genauen Anweisungen und der Übersetzungstabelle.
5. .pot mit wp-cli neu generieren
# Vom Plugin-Verzeichnis (lokaler Spiegel)
$wpCli = 'C:\Temp\opencode\wp-cli.phar'
php $wpCli i18n make-pot . "languages/mein-plugin.pot" --domain="mein-plugin"
Die .pot-Datei muss sich in /languages/ mit dem Namen <text-domain>.pot befinden.
6. Übersetzung .po + .mo erstellen
Entscheidende Frage: Welches Locale? Damit das Plugin automatisch für alle spanischsprachigen Nutzer ohne regionale Abweichungen übersetzt wird, verwenden Sie <text-domain>-es.po (Wurzel es). WordPress führt ein kaskadierendes Fallback durch: es_CL → es → Englisch.
Empfohlene Strategie:
es.po/.mo(Wurzel) → bedient es_CL, es_ES, es_MX, es_AR, es_PE, etc.- (Optional später)
es_CL.po/.mospezifisch für Chile mit lokalen Redewendungen.
Erstellen der .po-Datei: Verwenden Sie ein PHP-Skript, das die .pot-Datei einliest und ein Array von Übersetzungen EN→ES anwendet. Verwenden Sie mb_chr(0x00f3) für akzentuierte Zeichen (PHP unter Windows hat Probleme mit der Codepage bei UTF-8-Literalen).
// build-es.php
declare(encoding='UTF-8');
$pot = file_get_contents('languages/mein-plugin.pot');
$po_es = 'languages/mein-plugin-es.po';
// `mb_chr` für akzentuierte Zeichen vorladen
function A() { return mb_chr(0x00e1); } // á
function E() { return mb_chr(0x00e9); } // é
function O() { return mb_chr(0x00f3); } // ó
// usw.
$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 parsen, msgstr mit $t füllen, .po schreiben
// (vollständige Logik: siehe wp-plugin-i18n-release/build-es.php)
.mo kompilieren:
php $wpCli i18n make-mo languages/
Dies erstellt automatisch languages/<text-domain>-es.mo.
7. Synchronisierte Version erhöhen
Aktualisieren Sie vier Dateien:
// 1. my-plugin.php (Header)
* Version: 1.1.0
// 2. my-plugin.php (Konstante)
define('PLUGIN_VERSION', '1.1.0');
// 3. readme.txt
Stable tag: 1.1.0
/* + neuer Eintrag in == Changelog == */
// 4. CHANGELOG.md
## [1.1.0] - 2026-09-14
### Changed
- ...
8. Sauberen Build neu generieren (Entwicklungsdateien ausschließen)
Die .distignore-Datei des Plugins definiert, was ausgeschlossen wird. Wenn sie nicht existiert, verwenden Sie diese Standardliste:
$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')
# Ziel bereinigen, alles kopieren, Ausgeschlossenes löschen
$src = 'pfad/spiegel/plugin'
$dist = 'pfad/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 ins trunk + Tag erstellen
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
# trunk mit sauberem Build ersetzen
$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"
# Tag erstellen (remote 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"
Nach dem remote Tag: Lokales Update erzwingen (neue Tags werden in der Working Copy nicht vollständig materialisiert):
svn cleanup
svn update --depth infinity --force --non-interactive --trust-server-cert `
--username $svnUser --password $svnPass "$trunk/../"
10. (Optional) Lokales Verzeichnis synchronisieren
Behalten Sie Dateien bei, die NICHT im SVN-Export sind (.git/, .gitignore, tests/, bin/, etc.):
$local = 'C:\Users\...\wp-content\plugins\mein-plugin'
$exportDir = 'C:\Users\...\export-1.1.0'
# Export des neu erstellten Tags
svn export "https://plugins.svn.wordpress.org/$slug/tags/1.1.0" $exportDir
# NUR die Release-Dateien aus dem lokalen Verzeichnis löschen, den Rest behalten
$releaseNames = (Get-ChildItem $exportDir -Force).Name
foreach ($name in $releaseNames) {
$localPath = Join-Path $local $name
if (Test-Path $localPath) { Remove-Item $localPath -Recurse -Force }
}
# Export über das lokale Verzeichnis kopieren
Copy-Item -Path "$exportDir\*" -Destination $local -Recurse -Force
11. Abschließende Überprüfung
# Versionen im lokalen Verzeichnis
Write-Host "header: " (Get-Content "$local\mein-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()
# Remote SVN überprüfen
svn ls --recursive "https://plugins.svn.wordpress.org/$slug/tags/1.1.0/"
Wichtige Konventionen von WordPress.org
| Konzept | Regel |
|---|---|
| Text Domain | Muss identisch mit dem Plugin-Slug sein (z.B. mein-plugin). Ändern Sie den Slug niemals ohne erneute Genehmigung des Plugins. |
| Domain Path | /languages/ damit WP.org die .pot-Datei findet |
| Plugin Name Header | Keine Marken Dritter am Anfang (WP.org-Prüfung lehnt ab) |
| readme.txt | WP.org parst diese Datei (nicht README.md). Der Stable tag muss mit der Version übereinstimmen. |
| SVN-Layout | /trunk/ (Entwicklung), /tags/X.Y.Z/ (Releases), /assets/ (Banner, Icons, Screenshots) |
| SVN als Release-System | "Nur gebrauchsfertige Versionen pushen". Keine häufigen Entwicklungs-Commits in den trunk. |
| Fallback von Übersetzungen | WP sucht kaskadierend: text-domain-locale.mo → text-domain.mo → text-domain-en_US.mo (Core). Das Wurzel-Locale es bedient ALLE Varianten es_XX. |
Häufige Fallstricke und Fehler
PowerShell und UNC-Pfade
PHP und viele Binärdateien schlagen fehl, wenn das CWD ein UNC-Pfad ist (\\server\share). Vor dem Ausführen von Befehlen:
Set-Location C:\Users\<Benutzername> # oder ein beliebiges lokales Verzeichnis
php $wpCli ... # funktioniert jetzt
Kodierung akzentuierter Zeichen in PHP (Windows)
PHP unter Windows hat Probleme mit UTF-8-Literalen in Skripten. Lösung: Verwenden Sie mb_chr(0x00f3) für jedes akzentuierte Zeichen:
"Obtener pa" . mb_chr(0x00ed) . "s" // "Obtener país"
PowerShell kann bei Get-Content ebenfalls ? anstelle von akzentuierten Zeichen anzeigen. Überprüfen Sie die Bytes der Datei, nicht die Darstellung:
$bytes = [System.IO.File]::ReadAllBytes('datei.po')
# Suche nach 0xC3 0xB3 (UTF-8 von 'ó')
SVN-Working-Copy scheint nach einem neuen Tag leer zu sein
SVN dedupliziert Dateien zwischen trunk und tag (gleicher "Blob"). Nach einem remote svn copy erscheinen die Dateien auf dem Server, aber die lokale Working Copy hat Platzhalter. Lösung: svn cleanup + svn update --depth infinity --force.
svn.exe ist nicht im PATH
TortoiseSVN wird in C:\Program Files\TortoiseSVN\bin\ installiert, fügt svn.exe aber standardmäßig nicht zum PATH hinzu. Temporäre Lösung:
$env:Path = "$env:ProgramFiles\TortoiseSVN\bin;$env:Path"
Oder dauerhaft zum PATH des Benutzers hinzufügen.
Das Plugin verwendet spanische Strings im Header
Die Strings des Plugin-Headers (Plugin Name, Description, etc.) sind NICHT übersetzbar. Wenn wp i18n sie jedoch in die .pot-Datei aufnimmt, lassen Sie sie mit msgstr "" (leer) stehen. WP.org und GlotPress wissen, dass sie nicht übersetzt werden.
Keine .po/.mo für von GlotPress verwaltete Locales einfügen
Sobald GlotPress Übersetzungen für Ihr Plugin hat (durch die Community), stellt WP.org automatisch die .mo-Dateien bereit. Ihre initialen .po/.mo sind nur der anfängliche Seed. Kämpfen Sie nicht gegen GlotPress.
Dateien, die durch das Release geändert wurden (Checkliste)
-
my-plugin.php— Header Version + PLUGIN_VERSION -
readme.txt— Stable tag + Eintrag im Changelog -
CHANGELOG.md— neuer Eintrag -
languages/<text-domain>.p...
... (abgeschnitten für die Übersetzung