Blog / WordPress / WordPress

WordPress Localization - Translating Themes and Plugins Complete Guide

Mahesh Mahesh Waghmare
4 min read

WordPress localization (i18n) allows themes and plugins to be translated into multiple languages. This guide covers the complete process from setup to translation file creation.

Introduction to Localization

Localization (i18n) makes your WordPress theme or plugin translatable. Internationalization prepares code for translation, while localization is the actual translation process.

Key Concepts:

  • Text Domain: Unique identifier for your theme/plugin
  • Translation Functions: Wrap translatable strings
  • .pot Files: Template for translations
  • .po/.mo Files: Actual translation files

Benefits:

  • Reach global audience
  • Better user experience
  • WordPress.org requirements
  • Professional development

Text Domain Setup

Define Text Domain

In functions.php (Theme):

function my_theme_setup() {
    load_theme_textdomain('my-theme', get_template_directory() . '/languages');
}
add_action('after_setup_theme', 'my_theme_setup');

In Main Plugin File:

function my_plugin_load_textdomain() {
    load_plugin_textdomain('my-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'my_plugin_load_textdomain');

Text Domain Naming

Best Practices:

  • Use theme/plugin slug
  • Lowercase, hyphens only
  • Unique identifier
  • Examples: my-theme, my-plugin, custom-post-type
Advertisement

Translation Functions

__() - Return Translated String

$text = __('Hello World', 'my-theme');
echo $text;

_e() - Echo Translated String

_e('Hello World', 'my-theme');
// Outputs translated string directly

_x() - Context-Aware Translation

// Different translations based on context
$text = _x('Post', 'verb', 'my-theme');
$text = _x('Post', 'noun', 'my-theme');

_n() - Plural Translation

$count = 5;
$text = _n('%s item', '%s items', $count, 'my-theme');
printf($text, $count);

esc_html__() and esc_html_e()

// Escape and translate
$safe_text = esc_html__('Hello World', 'my-theme');
esc_html_e('Hello World', 'my-theme');

esc_attr__() and esc_attr_e()

// For HTML attributes
$attr = esc_attr__('Title', 'my-theme');
echo '<div title="' . $attr . '">';

Loading Text Domain

Theme Loading

function my_theme_setup() {
    load_theme_textdomain(
        'my-theme',
        get_template_directory() . '/languages'
    );
}
add_action('after_setup_theme', 'my_theme_setup');

Plugin Loading

function my_plugin_load_textdomain() {
    load_plugin_textdomain(
        'my-plugin',
        false,
        dirname(plugin_basename(__FILE__)) . '/languages'
    );
}
add_action('plugins_loaded', 'my_plugin_load_textdomain');

Language File Structure

theme/
├── languages/
│   ├── my-theme.pot
│   ├── my-theme-en_US.po
│   └── my-theme-en_US.mo

Creating .pot Files

Using WP-CLI

wp i18n make-pot . languages/my-theme.pot --domain=my-theme

Using Poedit

  1. Open Poedit
  2. File → New from POT file
  3. Select your .pot file
  4. Start translating

Using gettext Tools

xgettext --language=PHP --from-code=UTF-8 --output=languages/my-theme.pot *.php

Pot File Headers

msgid ""
msgstr ""
"Project-Id-Version: My Theme 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-01-24 12:00:00+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Language-Team: LANGUAGE\n"
Advertisement

Translation Process

1. Prepare Code

Wrap all user-facing strings:

// Before
echo 'Hello World';

// After
_e('Hello World', 'my-theme');

2. Generate .pot File

wp i18n make-pot . languages/my-theme.pot

3. Create Translation Files

Copy .pot to .po file for each language:

  • my-theme-es_ES.po (Spanish)
  • my-theme-fr_FR.po (French)

4. Translate

Use Poedit or text editor to translate strings.

5. Compile .mo Files

Poedit compiles automatically, or use:

msgfmt my-theme-es_ES.po -o my-theme-es_ES.mo

Best Practices

1. Always Use Text Domain

// Good
_e('Text', 'my-theme');

// Bad
_e('Text'); // Uses default text domain

2. Escape Output

// Good
esc_html_e('Text', 'my-theme');

// Bad
_e('<strong>Text</strong>', 'my-theme');

3. Don’t Translate Code

// Don't translate
$class = __('my-class', 'my-theme'); // Wrong

// Translate user-facing text only
$label = __('Submit', 'my-theme'); // Correct

4. Use Context for Ambiguity

_x('Post', 'verb', 'my-theme');
_x('Post', 'noun', 'my-theme');

5. Handle Plurals

$count = get_count();
printf(
    _n('%s item', '%s items', $count, 'my-theme'),
    $count
);

Conclusion

WordPress localization:

  1. Define text domain in setup
  2. Wrap strings with translation functions
  3. Generate .pot file for translators
  4. Create translation files (.po/.mo)
  5. Load text domain properly

Key Points:

  • Always use text domain
  • Escape translated output
  • Use appropriate translation function
  • Generate .pot files for translators
  • Test translations thoroughly

Proper localization makes your WordPress themes and plugins accessible to global audiences.

Advertisement
Mahesh Waghmare

Written by Mahesh Waghmare

I bridge the gap between WordPress architecture and modern React frontends. Currently building tools for the AI era.

Follow on Twitter

Read Next