.pot, .po and .mo: what WordPress language files are and how to translate one
· screens checked September 23, 2026
A theme or plugin ships a .pot file: every translatable string, with no translations. You copy it to a .po for your language and fill in the translations. Then you compile that .po into a .mo, because .mo is the format WordPress reads at runtime — a .po on its own changes nothing on the site.
If a translation you filled in is not showing up, the answer is almost always in the last two sections of this page: the file was never compiled, or its filename is wrong.
The three files, in one line each
- .pot — the template. Every translatable string in the theme or plugin, with empty translations. Generated from the source code by the developer; you never edit it, you copy it.
- .po — one language. Human-readable text: each original string with your translation under it. This is the file you actually work in.
- .mo — the compiled version of a .po. Binary, not meant to be read. This is the one WordPress loads.
Since WordPress 6.5 there is a fourth: a .l10n.php file, which loads faster and is preferred when present, with .mo used as the fallback. You do not have to produce it — WordPress can generate it — but it explains why you may see a file you did not put there.
From .pot to a working translation, in four steps
- Find the .pot. It is usually in the plugin or theme folder, under
languages/. - Make a copy named for your language, with the
.poextension, following the naming rule in the next section. - Translate the entries — by hand in a PO editor, or in bulk. Keep every placeholder exactly as it appears in the original.
- Compile the .po into a .mo and put both files where WordPress looks. A free browser tool does the compile step if you do not have gettext installed.
If the file is large, translating it by hand is the part that takes days. The PO / POT translator does the whole file in one pass and exports back to the same format; it only sends the strings, never the file.
The filename is not a detail — it is the whole thing
WordPress looks for {text-domain}-{locale}.mo. If that name is off by one character, the file does not exist as far as WordPress is concerned: it silently falls back to English. No error, no notice, no hint. This is the single most common reason a finished translation does nothing.
- The text domain is the plugin or theme’s own —
woocommerce,twentytwentyfive,my-cool-plugin. It is declared in the plugin header, and it is not always the folder name. - The locale is WordPress’s, not the two-letter language code. Simplified Chinese is
zh_CN, English isen_US, French isfr_FR— but Japanese isja, Arabic isar, Finnish isfi, with no region at all. - If your source file already carries a locale —
woocommerce-en_US.po— strip it before adding the new one, or you end up withwoocommerce-en_US-zh_CN.mo, which matches nothing.
Where to put the files
Two places work, and the difference matters when the plugin updates.
wp-content/languages/plugins/andwp-content/languages/themes/— use these. WordPress checks here first, and an update to the plugin or theme cannot overwrite your file.- The plugin’s or theme’s own
languages/folder — works, but the next update replaces the folder and your translation is gone.
A translation that looks right but does nothing — check in this order
- Is there a
.moat all? A.poalone does nothing. This is the first thing to rule out. - Is the filename exactly
{text-domain}-{locale}.mo? Check the text domain in the plugin header and the locale against WordPress’s own list. - Is the site actually set to that language, in Settings → General?
- Is the file in
wp-content/languages/…? If it is only in the plugin folder and the plugin was updated, it is gone. - Still nothing: clear any object or page cache. Translations are loaded once per request and cached aggressively.
Placeholders: the one thing you cannot be loose about
Strings like Showing %1$s of %2$s results carry placeholders that PHP fills in. If a translation drops one, changes its order without the numbered form, or turns %s into %s with a full-width character, that is not a display problem — it is a runtime error on a real page. Check placeholders before you ship the file; a good compiler will flag them for you.
Questions people ask
- Can I just upload the .po file?
- No. WordPress reads `.mo` (and `.l10n.php` since 6.5), never `.po`. A `.po` in the languages folder changes nothing on the site, which is why this catches so many people — nothing is broken, nothing is reported, and the site stays in English.
- Do I need to install gettext or Poedit?
- Not for the compile step. A browser tool can turn a .po into a .mo without anything installed — [ours does it locally](/tools/po-compiler?utm_source=guide&utm_medium=tutorial&utm_campaign=po-files), so the file never leaves your machine. A desktop PO editor is still the nicer place to do careful line-by-line editing.
- What about the strings that are not in the .pot?
- They were not marked as translatable in the source code, so no file will ever reach them. That is a change in the theme or plugin itself, not something a language file can fix. Content you wrote — pages, products, posts — is a different problem again, and needs a multilingual plugin rather than a .po file.
- My theme is translated but the plugins are not. Why?
- Every theme and plugin has its own text domain and its own language file. Translating one does nothing for the others. A site in a language that WordPress supports gets many of them automatically from translate.wordpress.org; the gaps are usually commercial plugins, which nobody has translated for you.
The plugin in this guide
AumLangTranslates Elementor, Gutenberg and WooCommerce content without breaking the layout — and gets the SEO side right: a URL per language, hreflang, canonicals, sitemaps.Plugin page →