Making a theme or plugin translation-ready means wrapping every user-facing string in one of WordPress’s i18n functions. When done correctly, translators can provide .po/.mo files without touching any code.
Problem: How do you make a WordPress theme or plugin ready for translation into other languages?
Solution: Wrap all user-facing strings in __(), _e(), or esc_html__() with your text domain, load the domain in after_setup_theme (themes) or plugins_loaded (plugins), and generate a .pot file for translators.
The core functions you need:
// Basic string — use for labels, headings, button text
__( 'Read more', 'textdomain' )
// Echo directly
_e( 'Read more', 'textdomain' )
// Singular/plural form
sprintf(
_n( '%d comment', '%d comments', $count, 'textdomain' ),
$count
)
// String with context (disambiguates identical strings with different meanings)
_x( 'Draft', 'post status', 'textdomain' )
// Safe output in attributes — escaping + translation in one call
esc_attr_e( 'Search...', 'textdomain' )
esc_html_e( 'Submit', 'textdomain' )
Load the text domain in functions.php (theme) or the main plugin file:
// Theme
add_action( 'after_setup_theme', function() {
load_theme_textdomain( 'textdomain', get_template_directory() . '/languages' );
} );
// Plugin
add_action( 'init', function() {
load_plugin_textdomain( 'textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
} );
Generate a .pot template file for translators using WP-CLI:
wp i18n make-pot . languages/textdomain.pot --domain=textdomain
NOTE: Never concatenate translated strings to form sentences — word order differs across languages. Instead, use sprintf() with named placeholders so translators can reorder them: sprintf( __( 'Posted by %s on %s', 'td' ), $author, $date ).