publishpress / translations
AI-powered translation automation for PublishPress plugins using Potomatic
Package info
github.com/publishpress/library-publishpress-translator
Language:JavaScript
pkg:composer/publishpress/translations
Requires
- php: >=7.2.5
- ext-json: *
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
This package is auto-updated.
Last update: 2026-04-20 09:42:30 UTC
README
AI-powered translation automation for PublishPress plugins using Potomatic, OpenAI, and Weblate.
Features
- AI-powered translations using OpenAI GPT models
- Weblate integration for translation management and human review
- Automatic upload/download to/from Weblate
- Merges with existing translations (preserves manual edits)
- Cost-effective (~$0.03 per language for 1,744 strings)
- Supports 10+ languages by default
- Dry-run mode for cost estimation
- Automatic detection of
.potfiles
Requirements
- PHP 7.2.5 or higher
- PHP extensions:
json,zip(usually enabled by default) - Node.js 18+ and npm (for Potomatic CLI tool)
- OpenAI API key (Get one here)
- Weblate account and API token (Sign up here)
- Plugin must have a
languages/directory containing one or more.potfiles
Installation
Note: This setup works the same whether you're working from the plugin root or inside dev-workspace.
Recommended setup:
Step 1: Add to root composer.json
{
"require-dev": {
"publishpress/translations": "^1.0.0"
},
"scripts": {
"translate": "vendor/bin/publishpress-translate",
"translate:dry-run": "vendor/bin/publishpress-translate --dry-run",
"translate:download": "vendor/bin/publishpress-translate --download",
"translate:upload": "vendor/bin/publishpress-translate --upload",
"translate:custom": "vendor/bin/publishpress-translate --languages",
"translate:force": "vendor/bin/publishpress-translate --force",
"translate:force-custom": "vendor/bin/publishpress-translate --force --languages",
"translate:repair-plurals": "vendor/bin/publishpress-translate --repair-plurals",
"translate:clean-po": "vendor/bin/publishpress-translate --clean-po",
"translate:sync-files": "vendor/bin/publishpress-translate --sync-files"
}
}
Step 2: Install
composer update
Usage
Set Environment Variables
Before using the translation tools, set your API keys as environment variables:
Create a .env file in your plugin root with your API keys:
OPENAI_API_KEY=sk-proj-your-openai-key
WEBLATE_API_TOKEN=wlu_your-weblate-token
The .env file is automatically loaded when you run the translation tool. No additional configuration needed.
Values can be quoted or unquoted:
OPENAI_API_KEY="sk-proj-your-openai-key"
WEBLATE_API_TOKEN='wlu_your-weblate-token'
Alternatively, you can set environment variables directly in your shell:
Windows (PowerShell):
$env:OPENAI_API_KEY="sk-proj-your-openai-key" $env:WEBLATE_API_TOKEN="wlu_your-weblate-token"
Windows (CMD):
set OPENAI_API_KEY=sk-proj-your-openai-key set WEBLATE_API_TOKEN=wlu_your-weblate-token
Mac/Linux:
export OPENAI_API_KEY=sk-proj-your-openai-key export WEBLATE_API_TOKEN=wlu_your-weblate-token
Or create a .env file in your plugin root (don't commit this!):
OPENAI_API_KEY=sk-proj-your-openai-key
WEBLATE_API_TOKEN=wlu_your-weblate-token
Note: Shell environment variables take precedence over
.envfile values.
Get your Weblate API token:
- Sign up at weblate.publishpress.com
- Go to your profile: https://weblate.publishpress.com/accounts/profile/#api
- Copy your personal API key
Additional configuration
The following environment variables control advanced behaviour:
-
OPENAI_API_KEY(required for live translation) Used to call the OpenAI API. If it is missing:- In dry run mode, the tool prints a warning but continues so you can verify the workflow without incurring cost.
- In live mode, the tool prints a clear warning and exits before making any API calls.
-
WEBLATE_API_TOKEN(optional for AI generation, required for Weblate sync) If not set, Weblate integration is disabled:- You can still generate local translations.
- Upload/download with Weblate will be skipped and a warning will be printed.
-
WEBLATE_API_URL(optional, default:https://hosted.weblate.org/api/) Set this to your Weblate base URL (ending in/api/) when using a self-hosted instance. -
WEBLATE_SKIP_VCS(optional, default:true) Skip all VCS (repository) operations when interacting with Weblate. By default, VCS is skipped to avoid requiring repository configuration. Set tofalseor0to enable VCS operations if your project has a configured repository URL in Weblate. -
WEBLATE_API_TIMEOUT(optional, default:120seconds) HTTP timeout used for Weblate API requests. For large projects or slow connections this may be too short. You can increase it, for example:export WEBLATE_API_TIMEOUT=300 -
WEBLATE_UPLOAD_DELAY(optional, default:2seconds) Delay between uploading translation files to Weblate. Useful to avoid rate limiting or server overload when uploading many languages.export WEBLATE_UPLOAD_DELAY=5 -
WEBLATE_PROJECT_SLUG(optional) Override the Weblate project slug. By default, uses the plugin slug fromcomposer.json. -
WEBLATE_COMPONENT_SLUG(optional) Override the Weblate component slug. By default, uses the text domain from the.potfile. -
WEBLATE_GIT_BRANCH(optional, default:development) Specify which Git branch Weblate should use for the component. -
WEBLATE_REPO_TYPE(optional, default:https) Repository access type:httpsorssh. Usesshif you have SSH keys configured in Weblate. -
WEBLATE_REPO_URL(optional) Override the repository URL for Weblate. Useful for private repositories or custom Git hosting. Examples:- HTTPS with credentials:
https://username:token@github.com/owner/repo.git - SSH:
git@github.com:owner/repo.git
- HTTPS with credentials:
-
WEBLATE_PUSH_URL(optional) Override the push URL separately from the repository URL. Only needed if push and pull URLs differ. -
WEBLATE_PREFER_BASE_LANGUAGE(optional, default:false) When downloading from Weblate, prefer base language codes (e.g.,deoverde_DE) when duplicate locale variants exist. Set totrueor1to enable.export WEBLATE_PREFER_BASE_LANGUAGE=true -
WEBLATE_CLEAN_EXISTING_TRANSLATIONS(optional, default:false) Delete all existing.pofiles before downloading from Weblate. Useful for a clean slate when syncing translations. Set totrueor1to enable.export WEBLATE_CLEAN_EXISTING_TRANSLATIONS=true -
SKIP_LANGUAGES(optional, default:it_IT,es_ES,fr_FR) Comma-separated list of language codes to skip during translation and upload (downloads are still allowed). These languages are typically handled by human translators on Weblate. The default skipped languages are merged with any custom ones you specify.export SKIP_LANGUAGES=it_IT,es_ES,fr_FR
SKIP_LANGUAGES=it_IT,es_ES,fr_FR,de_DE
Complete Translation Workflow
1. Run Translation (Full Cycle)
From dev-workspace:
# Enter dev-workspace ./run # Dry run (preview cost, no API calls) composer translate:dry-run # Full translation cycle composer translate
From plugin root:
# Dry run composer translate:dry-run # Full translation cycle composer translate
What happens when you run composer translate:
- 📥 Download - Pulls existing translations from Weblate (if project exists)
- 🤖 AI Translate - Potomatic adds translations for new/missing strings
- 📤 Upload - Pushes updated translations back to Weblate
This ensures:
- Existing translations are preserved
- Only new/missing strings are translated by AI
- Weblate always has the latest translations
2. Review & Improve in Weblate
After running translate, you can visit your project in Weblate:
- Hosted Weblate: https://hosted.weblate.org/projects/YOUR-PROJECT/
- Self-hosted Weblate: https://YOUR-WEBLATE-DOMAIN/projects/YOUR-PROJECT/
- Review and improve AI-generated translations
- Use Weblate's translation memory and suggestions
- Collaborate with community translators
3. Download Only
If you just want to download the latest translations without running AI translation:
# Download latest from Weblate (no AI translation)
composer translate:download
Use this when:
- Translators made changes in Weblate
- You want to sync before building plugin
- You don't need to add new translations
Advanced options:
# Translate custom languages only vendor/bin/publishpress-translate --languages=de_DE,fr_FR,es_ES # Force re-translate all strings (ignore existing translations) vendor/bin/publishpress-translate --force # Download specific languages only vendor/bin/publishpress-translate --download --languages=de_DE,fr_FR # Upload specific languages only (no AI translation) vendor/bin/publishpress-translate --upload --languages=de_DE,fr_FR # Repair malformed plural entries in existing .po files vendor/bin/publishpress-translate --repair-plurals # Clean duplicate entries from all .po files vendor/bin/publishpress-translate --clean-po # Verify .po files are present (external tools compile to .mo, .json, .l10n.php) vendor/bin/publishpress-translate --sync-files
4. Repair Malformed Plural Entries
If you have existing .po files with malformed plural entries (a known issue with older Potomatic versions where msgstr[0] contains "singular|plural" instead of separate msgstr[0]/msgstr[1] lines), you can fix them:
# Scan and repair all .po files in the languages directory
vendor/bin/publishpress-translate --repair-plurals
What this fixes:
- Detects plural entries where
msgstr[0]contains pipe-delimited forms - Splits them into proper separate
msgstr[N]lines
Note: New translations are automatically repaired during the translation process, so you only need this for existing files.
Note: The library automatically detects your environment (dev-workspace vs plugin root) and uses the correct vendor path.
5. Clean Duplicate Entries
Remove duplicate extracted comments from all .po files:
# Clean duplicates from all .po files in the languages directory
vendor/bin/publishpress-translate --clean-po
What this does:
- Scans all
.pofiles for duplicate extracted comments (#.lines) - Removes redundant comment lines within each entry
- Reports which files were cleaned
When to use:
- After downloading from Weblate (if duplicates accumulated)
- Before uploading to Weblate to keep files clean
- As maintenance on skipped languages (which shouldn't be touched by AI translation)
- Works independently of translation workflow
Important: This command only processes .po files and doesn't interact with Weblate or run AI translation.
6. Check Translation File Status
Check whether .po source files and compiled formats (.mo, .json, .l10n.php) are in sync:
# Check status of all translation files
vendor/bin/publishpress-translate --sync-files
What this does:
- Lists all
.posource files found - Checks if corresponding
.mo,.json, and.l10n.phpfiles exist - Reports whether compiled files are up-to-date or outdated compared to
.pofiles - Recommends running compile step if any files are missing or outdated
When to use:
- Before deploying to ensure all translation formats are current
- In CI/CD pipelines as a verification step
- After updating
.pofiles to see what needs recompilation
Compiling Translation Files:
Compile .mo, .json, and .l10n.php files from .po sources using:
.mo(compiled binary) -wp i18n make-mo(WP-CLI) orcomposer translate:compile.json(JSON format) -composer translate:compileor custom build script.l10n.php(PHP format) -composer translate:compileor custom build script
The library verifies compilation status but delegates actual compilation to your build/deployment tools.
Default Languages
The tool translates into these languages by default:
- Arabic (ar)
- Bulgarian (bg_BG)
- Catalan (ca)
- Czech (cs_CZ)
- Danish (da_DK)
- German (de_DE)
- Greek (el)
- Estonian (et_EE)
- Persian (fa_IR)
- Finnish (fi)
- Filipino (fil)
- Hebrew (he_IL)
- Croatian (hr)
- Hungarian (hu_HU)
- Indonesian (id_ID)
- Japanese (ja)
- Korean (ko_KR)
- Lithuanian (lt_LT)
- Norwegian Bokmål (nb_NO)
- Dutch (nl_NL)
- Polish (pl_PL)
- Portuguese (Portugal) (pt_PT)
- Romanian (ro_RO)
- Russian (ru_RU)
- Slovak (sk_SK)
- Slovenian (sl_SI)
- Swedish (sv_SE)
- Thai (th)
- Turkish (tr_TR)
- Ukrainian (uk)
- Vietnamese (vi)
- Yoruba (yor)
- Chinese (China) (zh_CN)
- Chinese (Taiwan) (zh_TW)
Skipped Languages
The following languages should not be translated by Potomatic, they are handled by human translators:
- Italian (it_IT)
- Spanish (es_ES)
- French (fr_FR)
These languages will be skipped during translation and upload processes, even if PO files exist for them.
How Skipped Languages Work:
- Translation (
composer translate) - Skipped languages are not passed to Potomatic AI translation - Upload (
composer translate:upload) - Skipped languages are NOT uploaded to Weblate - Download (
composer translate:download) - Skipped languages ARE downloaded from Weblate (translations can be pulled but not replaced by AI) - Cleaning/Syncing - Skipped languages ARE operated on by
--clean-poand--sync-filescommands (useful for maintenance without risk of overwriting with AI)
Preventing Plugin Name Translation
By default, all strings in your plugin are translated, including the plugin name. To keep your plugin name untranslated, add it to your composer.json file:
{
"extra": {
"plugin_name": "your plugin name"
}
}
The translation tool will then automatically keep the plugin name untranslated in all PO files, both when:
- Running AI translations with Potomatic
- Downloading translations from Weblate
Translation Overrides and Exclusions
You can control which words or phrases should be kept untranslated (excluded) or have specific translations enforced across all languages or per-language using environment variables.
Global Overrides (All Languages)
Set TRANSLATION_OVERRIDES to specify words that should be kept untranslated in all languages:
export TRANSLATION_OVERRIDES="Dashboard,Shortlinks,Upgrade to Pro"
Or in your .env file:
TRANSLATION_OVERRIDES="Dashboard,Shortlinks,Upgrade to Pro"
Per-Language Overrides
Use TRANSLATION_OVERRIDES_{language} to override specific words for individual languages:
export TRANSLATION_OVERRIDES_yor="Shortlinks" export TRANSLATION_OVERRIDES_de_DE="Dashboard,Shortlinks"
Or in your .env file:
TRANSLATION_OVERRIDES_yor="Shortlinks,Upgrade to Pro"
TRANSLATION_OVERRIDES_de_DE="Dashboard"
Custom Translations (Overrides)
You can also force specific translations using the source=target format:
# Force "Pro" to be translated as "Premium" in all languages export TRANSLATION_OVERRIDES="Pro=Premium" # Language-specific custom translation export TRANSLATION_OVERRIDES_fr_FR="Upgrade to Pro=Passer à Premium"
How Overrides Work
Priority: Language-specific overrides take precedence over global overrides.
Example:
TRANSLATION_OVERRIDES="Dashboard,Shortlinks" TRANSLATION_OVERRIDES_yor="Upgrade to Pro"
- Yoruba (yor): "Dashboard", "Shortlinks", and "Upgrade to Pro" kept untranslated
- All other languages: Only "Dashboard" and "Shortlinks" kept untranslated
Automatic Dictionary Integration:
The library includes a built-in dictionary (config/dictionaries.json) with common brand names and technical terms that are automatically kept untranslated:
- PublishPress, TaxoPress, MetaSlider, WordPress, WooCommerce
- Technical terms: taxonomy, taxonomies, post type, custom field, shortcode, widget, admin, dashboard
These are applied automatically without needing to set them in TRANSLATION_OVERRIDES.
How It Works
Translation Cycle (composer translate)
Step 1: Download from Weblate
- Pulls existing translations from Weblate
- Preserves human edits and community contributions
- Creates project if it doesn't exist yet
Step 2: AI Translation with Potomatic
- Scans your plugin's
languages/directory for.potfiles - Generates AI translations for new/missing strings only
- Merges with existing translations (preserves manual edits)
- Creates/updates
.poand.mofiles for each target language
Step 3: Upload to Weblate
- Creates project on Weblate (using plugin slug as project slug)
- Creates component for each text domain
- Uploads POT template and all PO translations
- Provides link to view/edit in Weblate
Download Only (composer translate:download)
- Connects to Weblate using your API token
- Finds your plugin's project and components
- Downloads latest
.pofiles for all languages - Converts to
.mofiles for WordPress - Saves to your
languages/folder
Use this when:
- You want to sync translations before building
- Translators made changes in Weblate
- You don't need to run AI translation
Weblate Integration
- Automatic sync - Download → Translate → Upload in one command
- Preserves human edits - Existing translations are never overwritten
- Automatic project creation - Uses plugin slug as project name
- Component per text domain - Each
.potfile becomes a component - Optional - Works without Weblate if token not set
One-Time Setup
Set your API keys permanently:
Windows:
[System.Environment]::SetEnvironmentVariable('OPENAI_API_KEY', 'sk-proj-your-key', 'User') [System.Environment]::SetEnvironmentVariable('WEBLATE_API_TOKEN', 'wlu_your-token', 'User')
Mac/Linux (add to ~/.bashrc or ~/.zshrc):
export OPENAI_API_KEY=sk-proj-your-key export WEBLATE_API_TOKEN=wlu_your-token
Troubleshooting
"Potomatic not found" Error
This shouldn't happen if you installed via Composer. If it does, please report it as a bug.
"OPENAI_API_KEY not set" warning / exit
If OPENAI_API_KEY is not configured:
- In dry run (
composer translate:dry-run), the tool prints a warning but continues so you can verify configuration without any API calls. - In live mode (
composer translate), the tool prints a clear message and exits before attempting any OpenAI requests.
Make sure you've set the environment variable before running live translations.
"Weblate not configured" Error
This appears when running --download without WEBLATE_API_TOKEN set. Weblate integration is optional for generation but required for download.
"No .pot files found" Error
Ensure your plugin has a languages/ directory with .pot translation template files. Generate these using tools like:
Weblate Upload Fails
If Weblate upload fails, the translation process continues (translations are still saved locally). Check:
- API token is correct
- You have permissions on Weblate
- Project/component names are valid (no special characters)
Development
Clone the Repository
git clone https://github.com/publishpress/translations.git
cd translations
composer install
Testing Locally
To test the library before publishing:
- In your plugin's
composer.json, add a repository:
{
"repositories": [
{
"type": "path",
"url": "../publishpress-translations"
}
],
"require": {
"publishpress/translations": "@dev"
}
}
- Run
composer install
License
GPL-3.0-or-later
Credits
Built with Potomatic by GravityKit.