How to internationalize strings with __ and e in PHP in WordPress

Contents

Introduction

This article is a comprehensive, practical guide to internationalizing strings in WordPress PHP code using the two most common gettext helpers: __() and _e(), plus related functions and workflow. It covers the concepts, API differences, context, plurals, escaping, text domains, generating .pot/.po/.mo files, JavaScript translation considerations, and best practices translators and developers need to follow to produce robust, translatable code.

Why internationalize?

  • Make your theme or plugin accessible to non-English speakers.
  • Enable site owners to translate your strings into any language.
  • Comply with WordPress best practices and the global user base.

Core concepts

  • gettext — underlying system used by WordPress for translations.
  • Text domain — a unique identifier for your theme or plugin translations.
  • .pot/.po/.mo — POT is a template, PO is editable translations, MO is compiled machine object used by PHP at runtime.
  • Context — disambiguates identical source strings with different meanings.
  • Plural forms — languages have different plural rules you must use plural-aware functions.

Basic functions: __() vs _e()

These are the two most-used functions:

  • __(text, domain) — returns the translated string. Use when you need the string for concatenation, formatting or passing to other functions.
  • _e(text, domain) — echoes the translated string immediately. It is a shorthand for echo __().

Simple examples


Escaping Internationalization

Always combine translation functions with proper escaping depending on the output context:

  • esc_html__() / esc_html_e() — use when outputting inside HTML content.
  • esc_attr__() / esc_attr_e() — use when outputting inside attributes.
  • Prefer returning then escaping when the same translated string will be used in multiple contexts (avoid double-escaping).

Escaping examples

 . esc_html__( here, my-text-domain ) .  )
?>

Context: _x() and _ex()

When a single English word can have multiple meanings (e.g., Post as a noun vs post as a verb), provide context so translators can pick the right translation.


Plurals: _n(), _nx(), and _n_noop()

Handle pluralization with the plural-aware functions. Never rely on ad-hoc string concatenation for plurals: different languages have different plural rules.

Simple plural example


Plural with context


Translators comments

Add comments for translators when context or placeholders are non-obvious. Use the syntax / translators: … / immediately above the translation call. Po tools (Poedit) will extract these comments.


Avoid concatenation — use placeholders

Do not build translatable sentences by concatenating smaller strings. That breaks translation grammars. Instead use placeholders with sprintf/printf and provide translators full sentences.


Text domains: themes vs plugins

The text domain identifies translations belonging to your project. Use a single unique domain per plugin or per theme.

  • Plugins: typically the plugin directory or slug (lowercase, hyphen-separated).
  • Themes: use the theme slug include the Text Domain in the style.css header.

Plugin header example


Plugin load translations


Theme load translations


File layout and naming

  • Place .po/.mo files in a languages/ directory inside your plugin or theme.
  • Filename convention: text-domain-locale.mo e.g., my-plugin-fr_FR.mo.
  • Core translations live in WP_LANG_DIR and may be used if text domain matches.

Generating POT files and translation workflow

Common workflow steps:

  1. Scan your PHP, JS source to extract translatable strings into a .pot template.
  2. Provide the .pot to translators.
  3. Translators create .po files and compile .mo files.
  4. Place compiled .mo files in the languages folder or install via tools/plugins.

WP-CLI: generate a POT

# From plugin root (requires WP-CLI with i18n commands)
wp i18n make-pot . languages/my-plugin.pot

Common tools

  • Poedit — GUI for .po files, can also generate .pot/.mo.
  • Loco Translate — in-dashboard translation and management.
  • GlotPress — collaborative translation platform.

JavaScript translations

Modern WordPress supports translation in JavaScript via the @wordpress/i18n package and server-side registration through wp_set_script_translations(). Avoid wp_localize_script for strings that should be translated — prefer the i18n pipeline.

Registering script translations (PHP)


Using translations in JS (ESNext)

import { __, _x } from @wordpress/i18n

console.log( __( Hello from JS, my-plugin ) )
console.log( _x( Post, noun, my-plugin ) )

Table: Common translation functions

Function Purpose
__() Return translated string
_e() Echo translated string
_x(), _ex() Translations with context (return/echo)
_n(), _nx() Plural translations (with optional context)
esc_html__(), esc_html_e() Return/echo translated string escaped for HTML output
esc_attr__(), esc_attr_e() Return/echo translated string escaped for attribute output
load_plugin_textdomain() Loads translations for plugins
load_theme_textdomain() Loads translations for themes
wp_set_script_translations() Register translations for JavaScript

Common pitfalls and troubleshooting

  • Wrong text domain: Strings must use the exact text domain declared in the plugin/theme header.
  • No .mo file: Remember to compile .po into .mo WordPress reads .mo at runtime.
  • Concatenation: Avoid concatenating translatable pieces translators need full sentences.
  • Missing translators comments: Provide comments when placeholders or ambiguous words exist.
  • Escaping twice: Avoid applying escaping functions both before and after translation use appropriate escape at output time.

Best practices checklist

  1. Always wrap user-facing strings with translation functions.
  2. Use placeholders (%s, %d) instead of string concatenation.
  3. Include translators comments for ambiguous strings or placeholders.
  4. Use the correct text domain and declare it in the header for plugins/themes.
  5. Load textdomain early (plugins_loaded/after_setup_theme) so translations are available.
  6. Escaping: escape at the point of output with esc_html__ / esc_attr__ or escape the returned string before echoing.
  7. Handle plurals using _n() / _nx() and format numbers with number_format_i18n().
  8. Use wp_set_script_translations() @wordpress/i18n for JS translations prefer this over wp_localize_script for text strings.
  9. Provide a .pot file and update it as strings change keep translators informed.

Practical full example — plugin snippet


Wrapping up

Internationalization is essential for building inclusive WordPress products. Use __() to return translated strings when you need to manipulate them and _e() to echo quickly. Combine translation functions with correct escaping, supply translators with context and comments, and use the proper text domain lifecycle for themes and plugins. Follow the best practices outlined above to make your strings translatable and to avoid common mistakes.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

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