FeaturesPluginPricingResources
Change Language
ResourcesHow to Translate a WordPress Plugin (Step-by-Step)

How to Translate a WordPress Plugin (Step-by-Step)

SimplePoTranslate TeamMay 7, 2026
How to Translate a WordPress Plugin (Step-by-Step)

You found the perfect WordPress plugin for your project. It does exactly what you need, the reviews are glowing, and then you activate it and realize every button, label, and error message is in English. Your German, French, or Japanese visitors are about to hit a wall of text they cannot read.

The good news is that most well-built plugins are translation-ready, meaning the developer wrapped their strings in Gettext functions specifically so people like you can translate them. The challenge is knowing where the files live, what to name your translation, and where to put it so the next plugin update does not wipe your work.

This guide shows you how to translate a WordPress plugin from start to finish: finding the text domain and language folder, getting or generating the .pot template, building correctly named .po and .mo files, and placing them where WordPress will reliably load them. We will also cover the one type of plugin that this approach cannot fully translate, so you do not waste hours fighting an impossible case.

How Do You Find a Plugin's Translation Files?

Before translating anything, you need two pieces of information from the plugin: its text domain and its languages folder. The text domain is the unique string the plugin uses to identify its own translations, and the languages folder is where the source template lives.

Locating the Languages Folder and Text Domain

Open the plugin directory at wp-content/plugins/your-plugin/ and look for a /languages subfolder. Inside, you will usually find a .pot file, the empty template containing every translatable string.

To confirm the text domain, open the plugin's main PHP file and look at its header block:

/**
 * Plugin Name: Awesome Plugin
 * Text Domain: awesome-plugin
 * Domain Path: /languages
 */

Here the text domain is awesome-plugin. This value is critical, because your translation files must include it in their names or WordPress will never match them to the plugin. You will also see it in every string call throughout the code:

echo __( 'Settings saved successfully.', 'awesome-plugin' );

That second argument, awesome-plugin, is the text domain again. Every string that uses it can be translated by your catalog. If a string in the plugin's interface is missing this wrapper, for example a developer who hardcoded echo 'Save' without __(), then that string is not translatable at all, and no .po file can reach it. Most reputable plugins wrap everything correctly, but if you notice one or two stubborn strings that refuse to translate, a missing Gettext wrapper in the source is a likely culprit.

A quick way to gauge whether a plugin is genuinely translation-ready is to check its listing on the WordPress.org directory, which shows a translation percentage and links to a /languages template. A plugin with an active translation project is far more likely to have clean, fully wrapped strings than an abandoned one.

What If the Plugin Has No POT File?

Some plugins ship without a .pot template. If the /languages folder is empty or missing, you need to generate the template yourself by scanning the source code for translatable strings.

The standard tool for this is WP-CLI, which extracts every Gettext call into a fresh .pot.

wp i18n make-pot wp-content/plugins/awesome-plugin \
  wp-content/plugins/awesome-plugin/languages/awesome-plugin.pot

This command walks the plugin's PHP and JavaScript files, finds every __(), _e(), _n(), and related call, and writes a complete template. If you prefer a graphical route, Poedit Pro can scan source code the same way. Either way, the result is a .pot containing all source strings with empty translations, ready to be turned into a real language file.

Generating your own template has a hidden benefit: it captures strings the original developer may have forgotten to include in a shipped .pot. Plugins evolve quickly, and a template that came with version 2.1 might be missing strings added in 2.4. Regenerating from the current source guarantees your catalog reflects what the plugin actually displays today, not what it displayed two releases ago. If you maintain translations for a plugin over time, re-running make-pot after each major update is a reliable habit.

Creating the PO and MO Files With the Correct Name

The single most common mistake when translating a plugin is the filename. WordPress finds plugin translations by matching a very specific pattern, and one wrong character means your translation is ignored entirely.

The Filename Formula

For plugin translations, the pattern is text-domain-locale. So for the awesome-plugin text domain translated into German, the files must be named:

awesome-plugin-de_DE.po
awesome-plugin-de_DE.mo

The locale code is the WordPress site language code: de_DE for German, fr_FR for French, es_ES for Spanish, pt_BR for Brazilian Portuguese. Get the locale wrong and WordPress silently skips your file. You create these from the .pot template in an editor like Poedit, which compiles the binary .mo automatically when you save. If you are new to that editor, the complete Poedit guide walks through creating a translation from a template step by step.

Where to Place the Files So Updates Do Not Erase Them

There are two possible locations, and choosing the right one matters.

# RISKY: inside the plugin — wiped on every plugin update
wp-content/plugins/awesome-plugin/languages/awesome-plugin-de_DE.mo

# SAFE: the global languages override folder — survives updates
wp-content/languages/plugins/awesome-plugin-de_DE.mo

Always use wp-content/languages/plugins/. WordPress checks this override directory first, and because it sits outside the plugin folder, updating the plugin never touches your translations. Files placed inside the plugin's own folder get overwritten the moment a new version installs.

This override behavior is one of the most useful and least known features of WordPress localization. It means you can ship custom or corrected translations for any third-party plugin without ever forking it, and your changes are completely update-safe. If you manage several client sites running the same plugin, you can even keep a single set of override .mo files and deploy them across all of them, knowing each site will pick them up automatically as long as the locale matches.

How WordPress Loads Your Translation

A translation-ready plugin tells WordPress to load its catalog by calling load_plugin_textdomain() during initialization:

add_action( 'init', function() {
    load_plugin_textdomain(
        'awesome-plugin',
        false,
        dirname( plugin_basename( __FILE__ ) ) . '/languages'
    );
} );

For most modern plugins on WordPress 4.6 and later, you do not need to touch this. WordPress automatically loads translations from wp-content/languages/plugins/ as long as your filename and site locale match. If your finished translation still does not appear, the cause is almost always a filename mismatch, a stale .mo, or a site language set to the wrong locale. Our troubleshooting guide for translations not showing up walks through every one of these failure points.

Plugins That Store Strings in the Database

Here is the important limitation. The Gettext approach only translates strings that live in the plugin's code. Some plugins, especially form builders, page builders, and e-commerce extensions, let you create content (form labels, product attributes, custom messages) that gets saved in the WordPress database. Those strings are user-generated data, not part of the .pot, so a .po file cannot reach them. For database content you need a multilingual plugin like WPML or Polylang, or the plugin's own string-translation feature. Always test whether a given string lives in code or in the database before assuming a .po file can fix it.

A simple test tells you which kind of string you are dealing with. If the text is something you typed into a settings field, a form label you created, a custom thank-you message, a product name, it is database content and a .po cannot touch it. If the text is part of the plugin's built-in interface, button labels, validation errors, admin notices that ship with the plugin itself, it lives in the code and a .po file is exactly the right tool. Drawing this line early saves hours of frustration, because no amount of perfect .po editing will ever translate a string the plugin pulls from the database at runtime.

Translating at Scale Without the Manual Grind

Translating one plugin into one language by hand is manageable. Translating it into ten languages, or translating a whole stack of plugins for a client site, turns into days of repetitive work, and every manual edit risks fumbling a placeholder like %s or %1$s and breaking the output.

This is where a cloud workflow changes the math. Upload the plugin's .po or .pot to SimplePoTranslate, pick your target languages, and context-aware AI translates the whole catalog while Syntax Locking freezes every placeholder, HTML tag, and code token so nothing can break. Smart Batching handles large catalogs in a single pass, and you download a ZIP containing .po, .mo, .json, .php, and .xliff together, correctly formatted and ready to drop into wp-content/languages/plugins/. It runs entirely in the cloud, so there is no desktop install and no bloat on your site.

Note that translating a plugin differs from translating a theme, which uses a slightly different folder convention and loading function. If a theme is your target, follow our dedicated guide on localizing any WordPress theme even if you are not a developer instead.

Use the manual path when you have one plugin and one language. Use the cloud when you need many languages, fast, without trading away placeholder safety.

Ready to translate your WordPress plugin into every language your audience speaks without breaking a single variable? Try SimplePoTranslate free — no credit card required. The free tier lets you translate your first plugin .po file in minutes, with Syntax Locking on every string.