Complete Translation Workflow: POT to PO to MO
If you're new to WordPress internationalization, the POT → PO → MO workflow can feel like alphabet soup. What are these files? Why do you need all three? How do they work together?
Let me walk you through the complete translation workflow from start to finish, explaining every step, every file type, and how they all connect.
By the end of this guide, you'll understand exactly how to take a WordPress theme or plugin from English-only to fully multilingual.
The Big Picture: Why Three File Types?
Before we dive into the workflow, let's understand why WordPress uses three different file formats.
POT (Portable Object Template)
- Purpose: The master template containing all translatable strings
- Content: Source strings only, no translations
- Who uses it: Developers and translators
- Human-readable: Yes (plain text)
PO (Portable Object)
- Purpose: Language-specific translation file
- Content: Source strings + translations
- Who uses it: Translators
- Human-readable: Yes (plain text)
MO (Machine Object)
- Purpose: Compiled binary for WordPress to use
- Content: Same as PO, but optimized for speed
- Who uses it: WordPress (automatically)
- Human-readable: No (binary format)
Why not just use one file?
Because each serves a different purpose:
- POT: The reusable template for creating translations in any language
- PO: The editable source of truth for translators
- MO: The performance-optimized file WordPress loads on every page
Think of it like source code vs. compiled binaries. You write source code (PO), compile it (MO), and distribute templates (POT).
Step-by-Step Workflow
Let's walk through the complete workflow with a real example: translating a WordPress plugin into Spanish and French.
Step 1: Prepare Your Code for Translation
Before you can create translation files, your code needs to be internationalized (i18n).
This means wrapping all user-facing strings in WordPress translation functions:
Before (not translatable):
echo "Welcome to my plugin";
After (translatable):
echo __('Welcome to my plugin', 'my-plugin');
Common translation functions:
__()- Returns translated string_e()- Echoes translated string_n()- Handles plural forms_x()- Provides context for ambiguous strings
Example plugin code:
<?php
// functions.php
function my_plugin_welcome_message() {
echo '<h1>' . __('Welcome to My Plugin', 'my-plugin') . '</h1>';
echo '<p>' . __('Click the button below to get started.', 'my-plugin') . '</p>';
echo '<button>' . __('Get Started', 'my-plugin') . '</button>';
}
function my_plugin_comment_count($count) {
return sprintf(
_n('%d comment', '%d comments', $count, 'my-plugin'),
$count
);
}
Notice the text domain ('my-plugin'). This is critical—it tells WordPress which translation file to load.
Step 2: Generate the POT File
Once your code is internationalized, you extract all the translatable strings into a POT file.
Method A: Using WP-CLI (Recommended)
wp i18n make-pot /path/to/my-plugin /path/to/my-plugin/languages/my-plugin.pot
This scans all your PHP files, finds translation functions, and generates a POT file.
Method B: Using Poedit
- Open Poedit
- File → New from source code
- Select your plugin directory
- Poedit scans and creates a POT file
What the POT file looks like:
# Translation template for My Plugin
# Copyright (C) 2024
# This file is distributed under the same license as the plugin.
msgid ""
msgstr ""
"Project-Id-Version: My Plugin 1.0\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
#: functions.php:5
msgid "Welcome to My Plugin"
msgstr ""
#: functions.php:6
msgid "Click the button below to get started."
msgstr ""
#: functions.php:7
msgid "Get Started"
msgstr ""
#: functions.php:12
msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] ""
msgstr[1] ""
Key points:
- Each
msgidis a source string - All
msgstrfields are empty (this is just a template) - File references show where each string appears in your code
- Plural forms are defined with
msgid_plural
Step 3: Create Language-Specific PO Files
Now you take the POT template and create a PO file for each language you want to support.
Option 1: Manual Creation
Copy the POT file and rename it:
my-plugin.pot→my-plugin-es_ES.po(Spanish)my-plugin.pot→my-plugin-fr_FR.po(French)
Option 2: Using Poedit
- Open Poedit
- File → New from POT file
- Select your POT file
- Choose target language (e.g., Spanish)
- Save as
my-plugin-es_ES.po
Option 3: Using POForge (Automated)
- Upload POT file to POForge
- Select target language (Spanish)
- Let AI translate
- Download
my-plugin-es_ES.po
POForge automatically creates the PO file and fills in all translations—no manual work.
Step 4: Translate the Strings
This is where the actual translation happens.
Option A: Manual Translation
Open the PO file in a text editor and fill in each msgstr:
#: functions.php:5
msgid "Welcome to My Plugin"
msgstr "Bienvenido a Mi Plugin"
#: functions.php:6
msgid "Click the button below to get started."
msgstr "Haga clic en el botón de abajo para comenzar."
#: functions.php:7
msgid "Get Started"
msgstr "Comenzar"
Time required: Hours to days for large projects.
Option B: Using POForge AI
Upload POT → Choose language → Download translated PO file.
Time required: 5-10 minutes for most projects.
Step 5: Handle Plural Forms
Plural forms are tricky because different languages have different rules.
English has 2 forms:
- 1 comment
- 2 comments
Spanish also has 2 forms:
- 1 comentario
- 2 comentarios
But Russian has 3 forms, and Arabic has 6 forms!
The PO file header defines the plural rule for each language:
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
For Spanish, the translation looks like:
msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] "%d comentario"
msgstr[1] "%d comentarios"
POForge handles this automatically—you don't need to know the plural rules for every language.
Step 6: Compile PO to MO
Once your PO file is fully translated, you need to compile it into an MO file.
Why? Because WordPress can't use PO files directly. It needs the binary MO format for performance.
Option A: Command Line
msgfmt my-plugin-es_ES.po -o my-plugin-es_ES.mo
Option B: WP-CLI
wp i18n make-mo my-plugin-es_ES.po
Option C: Poedit
Save the PO file in Poedit → It automatically generates the MO file.
Option D: POForge
POForge automatically generates both PO and MO files when you download. No manual compilation needed.
Step 7: Deploy to WordPress
Upload both files to your WordPress site:
wp-content/
languages/
plugins/
my-plugin-es_ES.po
my-plugin-es_ES.mo
my-plugin-fr_FR.po
my-plugin-fr_FR.mo
Or place them in your plugin's languages/ directory:
wp-content/
plugins/
my-plugin/
languages/
my-plugin-es_ES.po
my-plugin-es_ES.mo
Step 8: Test Your Translations
- Go to Settings → General in WordPress
- Change Site Language to Spanish (Español)
- Visit your plugin's page
- All text should now appear in Spanish
If something's not translating:
- Check that the text domain matches
- Verify file naming (e.g.,
my-plugin-es_ES.mo) - Clear WordPress cache
- Check file permissions
Real-World Example: Full Workflow with POForge
Let's put it all together with a real example.
Goal: Translate a WordPress plugin into Spanish, French, and German.
Traditional Workflow (Without POForge)
- Internationalize code: Wrap strings in
__(),_e(), etc. - Generate POT file:
wp i18n make-pot - Create PO files: Copy POT 3 times, rename for each language
- Translate manually: 1,000 strings × 3 languages = 3,000 translations (weeks of work)
- Compile to MO:
msgfmtfor each language - Upload to WordPress: 6 files (3 PO + 3 MO)
- Test: Switch language in WordPress and verify
Time: Days to weeks
Cost: $500-2,000 (if hiring translators)
POForge Workflow
- Internationalize code: Same as above
- Generate POT file:
wp i18n make-pot - Upload POT to POForge
- Select Spanish → AI translates in 5 minutes
- Download Spanish PO + MO
- Repeat for French → 5 minutes
- Repeat for German → 5 minutes
- Upload 6 files to WordPress
- Test: Everything works
Time: 20-30 minutes
Cost: $5-10 (depending on string count)
POForge doesn't just make it faster—it makes it accessible to anyone.
What If You Update Your Plugin?
Here's where the workflow really shines.
You release version 2.0 of your plugin. You added 50 new strings but kept 950 old ones.
Traditional Workflow:
- Regenerate POT (now has 1,000 strings)
- Update PO files (manually translate 50 new strings × 3 languages)
- Recompile to MO
Time: Hours
Cost: $50-200
POForge Workflow:
- Regenerate POT (1,000 strings)
- Upload to POForge
- Translation memory recognizes 950 existing strings (0 credits)
- AI translates 50 new strings (50 credits per language)
- Download updated PO + MO files
Time: 10 minutes
Cost: $1-2 (only new strings)
Translation memory is a huge cost saver over time.
Common Mistakes to Avoid
1. Wrong File Naming
WordPress expects specific filename patterns:
✅ Correct: my-plugin-es_ES.mo
❌ Wrong: my-plugin-spanish.mo
❌ Wrong: es_ES.mo
The filename must match: {text-domain}-{locale}.mo
2. Missing Text Domain
// ❌ Wrong (no text domain)
__('Hello World');
// ✅ Correct
__('Hello World', 'my-plugin');
Without the text domain, WordPress won't know which translation file to load.
3. Hard-Coded Strings
// ❌ Wrong (not translatable)
echo "Click here";
// ✅ Correct
echo __('Click here', 'my-plugin');
If it's not wrapped in a translation function, it won't appear in the POT file.
4. Forgetting to Compile PO to MO
WordPress only uses MO files. Even if you have a perfect PO file, without the MO file, nothing will translate.
Tools Summary
Here's a quick reference for all the tools mentioned:
| Task | Tool Options |
|---|---|
| Internationalize code | Manual (WordPress functions) |
| Generate POT | WP-CLI, Poedit, Grunt/Gulp plugins |
| Create PO files | Poedit, POForge |
| Translate strings | Manual, Poedit, professional translators, POForge AI |
| Compile to MO | msgfmt, WP-CLI, Poedit, POForge |
| Test translations | WordPress language switcher |
POForge handles 3 out of 7 steps automatically (create PO, translate, compile MO)—saving you the most time-consuming parts.
Final Thoughts
The POT → PO → MO workflow is the backbone of WordPress internationalization. It's designed to:
- Separate code from content
- Enable community translation
- Optimize performance with compiled binaries
The workflow is simple:
- Write internationalized code
- Generate a POT file
- Create PO files for each language
- Translate the strings
- Compile to MO files
- Deploy to WordPress
With POForge, steps 3-5 become:
- Upload POT
- Download PO + MO
No command-line tools. No desktop software. No weeks of manual translation.
Whether you're translating a small theme or a massive plugin, POForge makes the workflow fast, simple, and affordable.
Ready to translate your WordPress project? Upload your POT file to POForge and complete the workflow in minutes.