How to Compile .po to .mo Files (4 Methods)

You spent an afternoon perfecting a German translation. Every string in your de_DE.po file reads beautifully, the placeholders are intact, the plurals are correct. You upload it to your site, switch the language to German, and... nothing. The page is still in English. You double-check the file name, the folder, the text domain. Everything looks right. So why won't WordPress show your translation?
Nine times out of ten the answer is the same: you edited the .po but never compiled it to .mo. WordPress does not read .po files at runtime — it loads the compiled binary .mo. If you want your translations to actually appear, you need to compile po to mo every single time you change the text. The .po is your editable source; the .mo is what the site serves.
This guide explains why that compilation step exists and then walks through four reliable ways to do it: WP-CLI, the classic msgfmt command, Poedit, and a cloud tool that produces the .mo for you automatically. We will also cover the most common pitfall — forgetting to recompile — and the newer .l10n.php format WordPress now prefers for speed.
Why Does WordPress Load .mo and Not .po?
WordPress loads .mo files because they are compiled binaries optimized for fast lookups, while .po files are plain text built for humans to read and edit. Parsing text on every page load would be slow; reading a pre-built binary hash table is nearly instant.
A .po file is line-based and human-friendly. Each entry pairs a source msgid with a translated msgstr, plus comments noting where the string came from. That is great for editing but terrible for performance — the server would have to re-parse the whole text file on every request.
#: includes/cart.php:88
msgid "Your cart is empty"
msgstr "Ihr Warenkorb ist leer"
A .mo file packs those same string pairs into a binary format with an internal lookup table, so WordPress can jump straight to a translation without scanning text. The trade-off is that .mo is unreadable to humans and must be regenerated whenever the .po changes. If the .po vs .mo distinction is still fuzzy, our explainer on .po vs .mo vs .pot files breaks down each format and how they relate.
Four Ways to Compile po to mo
There is no single "correct" tool — the best choice depends on how you already work. Below are four reliable methods, from a one-line command to a fully automated cloud workflow. All four produce the identical binary .mo that WordPress loads at runtime.
Method 1: WP-CLI
The cleanest modern approach is WP-CLI, which can compile an entire folder of .po files in one command. If you already manage WordPress from the command line, this fits naturally into your workflow.
# Compile every .po file in the languages folder to .mo
wp i18n make-mo languages/
# Compile into a specific destination directory
wp i18n make-mo languages/ build/languages/
The make-mo command scans the target directory, compiles each .po it finds, and writes a matching .mo beside it (or into the destination you specify). It handles batches gracefully, which makes it ideal when you maintain many languages at once. It is the recommended tool for any project already using WP-CLI.
Method 2: msgfmt
The original Gettext utility for this job is msgfmt, part of the GNU gettext package. It compiles a single .po into a single .mo and is available on virtually every Linux and macOS system.
# Compile one file
msgfmt my-plugin-de_DE.po -o my-plugin-de_DE.mo
# Add statistics about translated, fuzzy, and untranslated strings
msgfmt --statistics my-plugin-de_DE.po -o my-plugin-de_DE.mo
# Install it first if missing
# macOS: brew install gettext
# Debian: sudo apt-get install gettext
The -o flag names the output file. The --statistics flag is genuinely useful — it tells you how many strings are translated, fuzzy, or still empty, so you catch an incomplete translation before shipping it. For scripting one file at a time, msgfmt is the dependable, no-frills choice.
Because msgfmt is a plain command-line tool, it slots neatly into automation. You can wrap it in a shell loop to compile a whole folder, or wire it into a CI pipeline so every commit that touches a .po produces a fresh .mo automatically. A common pattern is for f in languages/*.po; do msgfmt "$f" -o "${f%.po}.mo"; done, which compiles every language in one pass. The --check flag adds validation, flagging format-string mismatches between msgid and msgstr before they reach production — a cheap safeguard against the broken-placeholder bugs that silently corrupt translated layouts.
Method 3: Poedit (Compiles on Save)
If you edit translations in a graphical editor, Poedit compiles for you automatically. Every time you save a .po file, Poedit writes the matching .mo right alongside it — no separate command, no extra step.
This is one reason Poedit remains popular with translators who are not comfortable on the command line. You open the .po, type your translations, hit save, and both files update together. You can confirm the behavior under Poedit's preferences, where automatic .mo compilation on save is enabled by default. Poedit and similar desktop tools are reviewed in top 5 free tools to edit and translate PO files on Mac and Windows.
The catch with a desktop editor is that compilation only happens when a human opens the file and saves it. That is fine for a one-person project, but it does not scale to a dozen languages or a team handing files back and forth. If someone edits a .po in a different tool and copies it into the repository without opening Poedit, the .mo never updates. For larger or collaborative projects, an automated method — command line or cloud — removes that dependence on remembering to press save.
Method 4: A Cloud Tool That Generates the .mo for You
The fourth option removes the compile step entirely: use a cloud service that returns the compiled .mo together with the translated .po. You never run a command or save a file twice — you upload, and finished, correctly named files come back.
SimplePoTranslate works exactly this way. You upload a .po or .pot, it translates the strings with Context-Aware AI, and it returns a single ZIP containing the .po and .mo generated side by side — already compiled, already named to match your text domain and locale. There is no separate compilation pass and no chance of forgetting one.
It also handles the details that break manual workflows. Syntax Locking freezes placeholders like %s, %1$s, and {name} plus HTML tags before translation, so your variables survive into the .mo intact. Full Gettext plural support means complex plural forms compile correctly too — a topic worth understanding deeply, which we cover in understanding Gettext plurals. And because it runs in the cloud, there is no plugin to install and no build tooling to maintain.
The Most Common Pitfall: You Forgot to Recompile
The single biggest reason translations fail to appear is editing the .po but forgetting to recompile the .mo. WordPress keeps loading the old binary, so your fresh wording never shows up — and there is no error message to tell you why.
The mental model to lock in: the .po is your draft, the .mo is what ships. Any change to the .po is invisible to WordPress until you regenerate the .mo. Make recompilation a reflex after every edit, or use a tool that compiles automatically so the step can never be skipped.
This pitfall is especially sneaky on staging-to-production handoffs. A developer edits and recompiles locally, sees the translation working, then deploys only the .po and forgets the .mo — so production silently keeps the old text. Whenever you move translation files between environments, move the .po and the freshly compiled .mo together, or compile as part of your deploy step so the binary is always rebuilt from the current source.
If your translations still refuse to appear after recompiling, the problem is usually a naming mismatch, a wrong folder, or a caching layer holding the old file. The full checklist lives in why your translations are not showing up in WordPress.
A Note on the Newer .l10n.php Format
Recent WordPress versions introduced a third runtime format: .l10n.php. Instead of a binary .mo, translations are stored as a plain PHP array that the PHP opcode cache can hold in memory, making lookups even faster than .mo on busy sites.
<?php
return [
'domain' => 'my-plugin',
'messages' => [ 'Your cart is empty' => 'Ihr Warenkorb ist leer' ],
];
WordPress generates .l10n.php files automatically when it has the matching .mo available, so you do not need to build them by hand. For now, compiling a correct .mo remains the foundation — the performance format is derived from it.
Wrapping Up
To compile po to mo reliably, pick the method that matches how you work: WP-CLI for command-line batches, msgfmt for single files with statistics, Poedit for automatic compilation on save, or a cloud tool that builds the .mo for you so the step never gets skipped. All four produce the same binary WordPress needs at runtime.
Whichever route you choose, remember the golden rule: edit the .po, then compile to .mo — every time. That one habit prevents the most frustrating translation bug in WordPress, the one where everything looks right but the site stays stubbornly in English.
Ready to skip the manual compile step for good? Try SimplePoTranslate free — no credit card required. Upload your
.poand download a ready-to-deploy.po+.mobundle on the free tier in minutes.