WordPress Localization - Translating Themes and Plugins Complete Guide
Mahesh Waghmare 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
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
- Open Poedit
- File → New from POT file
- Select your .pot file
- 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"
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:
- Define text domain in setup
- Wrap strings with translation functions
- Generate .pot file for translators
- Create translation files (.po/.mo)
- 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.
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 →