How to load textdomain in plugins and themes with PHP in WordPress

Contents

Introduction — Why load a textdomain?

Translations in WordPress are handled through gettext. To make your plugin or theme translatable you must (1) wrap all user-facing strings with the appropriate translation functions and (2) make sure the correct translation file (.mo) is loaded. Loading the textdomain tells WordPress which translation catalog to use for lookups for that plugin or theme.

Core concepts and vocabulary

  • Text domain — a unique identifier (usually your plugin or theme slug) that associates translation strings with a specific domain.
  • .pot/.po/.mo — .pot is a template, .po is editable text translation, .mo is the compiled binary used at runtime.
  • Translation functions — __(), _e(), _n(), _x(), esc_html__(), esc_attr__(), etc. These look up translations in the loaded domain.
  • load_plugin_textdomain(), load_theme_textdomain(), load_textdomain() — functions to register/load translation files.
  • Hooks — when you call load_ matters. Typical hooks: plugins_loaded, init, after_setup_theme.
  • WordPress automatic language loading (4.6 ) — WP.org-hosted plugins/themes get translation packs automatically, but local .mo files and compatibility still require correct text domain usage and sometimes explicit loads.

Naming and file placement rules

Follow these rules so WordPress and translators find your translations:

  • Text domain matches the plugin/theme slug/folder name. For plugins, the plugin header must include Text Domain: your-slug. For themes, style.css must include Text Domain: your-theme-slug. The slug should be all-lowercase and must match the folder name where the plugin/theme resides.
  • Plugin languages folder — Put your .po and .mo into a languages/ subdirectory in your plugin: my-plugin/languages/my-plugin-fr_FR.mo. At runtime WordPress may prefer wp-content/languages/plugins/ as installed language packs.
  • Theme languages folder — Put in yourtheme/languages/ or let WordPress use wp-content/languages/themes/ for global installations.
  • MO filename format — {text-domain}-{locale}.mo, e.g. my-plugin-fr_FR.mo

Translation functions — quick reference

__() Return translated string. Example: __(Hello, my-domain)
_e() Echo translated string. Example: _e(Welcome, my-domain)
_n() Plural support. Example: _n(%s item, %s items, count, my-domain)
_x() Context-aware translation. Example: _x(Post, noun, my-domain)
esc_html__(), esc_attr__() Escaped versions to safely output translated strings in HTML/attributes.

How WordPress resolves translations (summary)

  1. WordPress looks for a loaded textdomain. If none is loaded, strings fall back to originals.
  2. For a loaded domain, it looks up the string key and returns translation if present in the loaded .mo.
  3. On WP.org-hosted projects, WordPress may automatically install language packs to wp-content/languages/plugins/ or themes/ and load them.

Best practices for loading translations in plugins

  • Set the Text Domain header in the main plugin file. It must match the plugin folder slug:

  • Load translations on the plugins_loaded hook (or earlier if necessary) so translations are available in early actions.
  • Place .mo/.po in a languages/ folder inside your plugin OR let WordPress install language packs to wp-content/languages/plugins/.
  • Use the plugin basename trick to calculate the relative languages path for load_plugin_textdomain.

Plugin example — standard approach

Recommended code to add to the main plugin file or an included loader file:


This tells WordPress to look for translations first in wp-content/languages/plugins/ and then in your plugins languages/ folder.

Plugin example — robust logic (fallbacks and WP_LANG_DIR)

For maximum compatibility (including per-user locale and manual .mo loading), you can try loading the global WP_LANG_DIR first and fallback to your plugin languages:


Notes about hooks

  • plugins_loaded is a reliable choice for most plugins — translations are loaded early enough for most use cases.
  • init can also be used, but if other plugins expect the translations earlier, you might need plugins_loaded.
  • Do not load translations inside functions that are only called after the strings have already been displayed load the textdomain before using translated strings.

Best practices for loading translations in themes

The correct hook for themes is after_setup_theme because the theme environment is fully available there.

Theme example


For child themes, if translations are in the child theme directory use get_stylesheet_directory() instead of get_template_directory().

Child theme example


Manual loading with load_textdomain()

Use load_textdomain( domain, mofile ) when you have an explicit path to a .mo file (e.g., generated or placed elsewhere). It bypasses the normal lookup and loads the given file directly.


Translation generation — obtaining .pot/.po/.mo

There are multiple recommended workflows:

  • Use WP-CLI: wp i18n make-pot . languages/my-plugin.pot
  • Use Poedit to scan your code and generate .po/.mo files.
  • Use plugins like Loco Translate for in-dashboard editing during development.
  • Use online platforms like translate.wordpress.org if your project is hosted on WordPress.org (these deliver automatic language packs to users).
# generate POT using WP-CLI (run from plugin directory)
wp i18n make-pot . languages/my-plugin.pot

After creating a .po you must compile into a .mo for runtime usage. Poedit compiles .mo automatically WP-CLI and gettext tools are also available.

Using translators comments and context

  • Provide context for ambiguous strings using _x() or translators comments in the code, e.g.:

Plural forms

When a string has plural forms, use _n() so translators get both singular and plural translations:


Escaping strings after translation

Never echo translated strings without proper escaping in HTML or attributes. Use esc_html__(), esc_attr__(), esc_html_e(), etc.

 . esc_html__( Hello World, my-plugin ) . 

// Also good with printf and escaping: printf(

%s

, esc_html__( Hello %s, my-plugin ) )

Common pitfalls and how to avoid them

  • Mismatched text domain: Make sure the Text Domain header and your calls to translation functions use the same domain string. If mismatched, translations wont load.
  • Wrong folder name: The text domain should be the plugin folder or theme folder slug. Keep it all lowercase and without spaces.
  • Loading too late: If you call translation functions before load_ runs, strings will not be translated. Hook your loader sufficiently early (plugins_loaded or after_setup_theme).
  • Not providing .mo: .po files are helpful for translators, but runtime uses .mo files — ensure you include or install compiled .mo.
  • Expecting WP.org autoload while testing locally: If your plugin is not hosted on WP.org, autoloaded language packs wont exist include languages/ or instruct translators how to install .mo files.
  • Not handling per-user locale: If your site uses user-specific languages, determine_locale() is the recommended function to calculate which .mo file to load when manually loading files.

Multisite and user locale differences

Since WordPress supports per-user language settings, loading translations manually should use determine_locale() (introduced for contexts where user language differs) when building explicit filenames for .mo files. In many cases using load_plugin_textdomain() or load_theme_textdomain() is enough because WordPress core handles user locales and installed language packs.

Examples: Putting everything together

Complete minimal plugin structure

  1. my-plugin/
    • my-plugin.php (main file, includes header Text Domain)
    • languages/
      • my-plugin.pot
      • my-plugin-fr_FR.po
      • my-plugin-fr_FR.mo
 . esc_html__( Welcome to My Plugin, my-plugin ) . 

}

Complete minimal theme structure

  1. my-theme/
    • style.css (header includes Text Domain: my-theme)
    • functions.php (loads textdomain)
    • languages/my-theme-fr_FR.mo

Working with automatic translation installs (WordPress.org)

If your plugin/theme is hosted on WordPress.org, translation teams contribute translations via translate.wordpress.org. WordPress (4.6 ) will automatically download and load the language packs into wp-content/languages/plugins/ or wp-content/languages/themes/, so often you do not need to include .mo files in your package. Still, ensure your Text Domain header is correct and continue to call load_plugin_textdomain() or load_theme_textdomain() for backward compatibility and local installations.

Testing translations locally

  1. Ensure WP_LOCAL_DEV is false (or use standard environment) so WordPress loads translations normally.
  2. Install a .mo file into wp-content/languages/plugins/ or to your plugins languages/ folder.
  3. Change site language under Settings → General to target locale (e.g., Français — fr_FR) or set a user language if testing per-user locale.
  4. Clear cache and reload pages to verify translated strings appear.

Tools and resources

Checklist before release

  • Text Domain string in plugin header or theme style.css matches folder slug.
  • All user-facing strings wrapped with correct translation functions.
  • Translators comments for ambiguous strings added where helpful.
  • Languages folder structured and .mo files named correctly for distribution or development.
  • load_plugin_textdomain() / load_theme_textdomain() is hooked early (plugins_loaded / after_setup_theme).
  • Tested with multiple locales and edge cases (plural forms, contexts, escaping).

Troubleshooting

  • If translations don’t appear: confirm text domain matches exactly, check .mo presence and filename, verify hook timing, and confirm locale in use.
  • Use tools like Poedit to inspect .mo/.po files and ensure msgid strings match the code string (including whitespace and punctuation).
  • Turn on WP_DEBUG and inspect for hook-related problems or missing files.

Summary

Loading a textdomain is simple when you follow the rules: choose a consistent text domain matching your plugin/theme slug, wrap strings with translation functions, place and name translation files correctly, and load translations early via load_plugin_textdomain() for plugins or load_theme_textdomain() for themes (or use load_textdomain() when loading a specific .mo file). Use determine_locale() for per-user locale-aware manual loads, and prefer WordPress APIs and hooks (plugins_loaded and after_setup_theme) for reliable behavior. Generate and test .pot/.po/.mo files with WP-CLI, Poedit, or similar tools, and provide translators with context to ensure accurate translations.



Acepto donaciones de BAT's mediante el navegador Brave :)



Leave a Reply

Your email address will not be published. Required fields are marked *