WordPress Localization (i18n/l10n) API Overview
WordPress internationalization (i18n) and localization (l10n) system for translating text strings.
Core Concepts
Internationalization (i18n)
The process of developing software so it can be localized. In WordPress, this means wrapping all user-facing strings in translation functions.
Localization (l10n)
The process of translating internationalized software for a specific locale. WordPress uses gettext-style .mo and .l10n.php files for translations.
Text Domains
A unique identifier that ties translated strings to a specific plugin, theme, or WordPress core. Each component should use its own text domain to avoid conflicts.
// Core WordPress uses 'default' domain
__( 'Hello', 'default' );
// Plugins/themes use their own domain
__( 'Hello', 'my-plugin' );
Translation File Formats
MO Files (Machine Object)
Binary compiled translation files. The traditional format used by WordPress.
- Location:
wp-content/languages/ - Naming:
{locale}.mofor core,{domain}-{locale}.mofor plugins/themes - Example:
fr_FR.mo,my-plugin-de_DE.mo
PHP Translation Files (since WP 6.5)
New format that’s faster to load than MO files. Preferred format since WP 6.5.
- Extension:
.l10n.php - Location: Same as MO files
- Format: PHP array with translations and metadata
// Example .l10n.php structure
return [
'project-id-version' => 'My Plugin 1.0.0',
'po-revision-date' => '2024-01-15 10:00:00+0000',
'messages' => [
'Hello' => 'Bonjour',
'Goodbye' => 'Au revoir',
],
];
PO Files (Portable Object)
Human-readable translation files used by translators. Not loaded at runtime.
msgid "Hello"
msgstr "Bonjour"
msgctxt "greeting"
msgid "Hi"
msgstr "Salut"
Translation Flow
1. String Extraction
Developers wrap translatable strings in gettext functions. Tools like WP-CLI or POEdit extract these to .pot files.
2. Translation
Translators create .po files from .pot templates, providing translations for each string.
3. Compilation
PO files are compiled to binary .mo files (or converted to .l10n.php files).
4. Loading
WordPress loads translations via:
- Automatic just-in-time loading (since WP 4.6)
- Explicit loading via
load_textdomain(),load_plugin_textdomain(), etc.
5. Translation Lookup
When a translation function is called:
WP_Translation_Controllerchecks loaded translations- Returns translated string or falls back to original
Directory Structure
wp-content/
└── languages/
├── de_DE.mo # Core German translation
├── de_DE.l10n.php # Core German (PHP format)
├── admin-de_DE.mo # Admin German translation
├── plugins/
│ └── my-plugin-de_DE.mo
└── themes/
└── my-theme-de_DE.mo
Locale Determination
WordPress determines the current locale through several methods:
- WPLANG constant in
wp-config.php - Database option
WPLANG - User preference (admin area)
- URL parameter
wp_lang(login page) - Cookie
wp_lang
// Get current locale
$locale = get_locale(); // Site locale
$locale = get_user_locale(); // Current user's locale
$locale = determine_locale(); // Best locale for request
Architecture Components
WP_Translation_Controller
Singleton that manages all translation file loading and lookups. Introduced in WP 6.5.
$controller = WP_Translation_Controller::get_instance();
$controller->load_file( '/path/to/translation.mo', 'my-plugin', 'de_DE' );
$translation = $controller->translate( 'Hello', '', 'my-plugin' );
WP_Translations
Wrapper class providing backward compatibility with the old $l10n global. Acts as bridge to WP_Translation_Controller.
WP_Locale
Stores locale-specific data: weekday/month names, date formats, text direction, number formats.
WP_Locale_Switcher
Manages temporary locale switching, maintaining a stack of locales.
POMO Classes
Legacy classes for reading MO/PO files:
MO– Reads binary MO filesTranslations– Base translation containerNOOP_Translations– No-op fallbackTranslation_Entry– Single translation entry
Plural Forms
Languages have different plural rules. WordPress handles this via the Plural-Forms header:
Plural-Forms: nplurals=2; plural=(n != 1); # English
Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2); # Czech
// Translate with correct plural form
printf(
_n( '%d comment', '%d comments', $count, 'my-plugin' ),
$count
);
Just-in-Time Loading (since WP 4.6)
WordPress automatically loads translations when first needed:
- A translation function is called with a text domain
get_translations_for_domain()checks if domain is loaded- If not,
_load_textdomain_just_in_time()attempts to find and load the translation file WP_Textdomain_Registrytracks custom paths for each domain
This means load_plugin_textdomain() only needs to specify the path, not load immediately.
RTL Support
WordPress automatically handles right-to-left languages:
// Check if current locale is RTL
if ( is_rtl() ) {
// Load RTL stylesheets
}
// WP_Locale stores text direction
$wp_locale->text_direction; // 'ltr' or 'rtl'
Best Practices
- Always use text domains – Never call
__()without a domain (except core) - Use context for ambiguous strings –
_x( 'Post', 'verb', 'domain' )vs_x( 'Post', 'noun', 'domain' ) - Keep strings complete – Don’t concatenate translated strings
- Use placeholders –
sprintf( __( 'Hello %s', 'domain' ), $name ) - Escape output – Use
esc_html__(),esc_attr__()for safe output - Load translations at the right time – Hook to
initor later, not before
Related Files
wp-includes/l10n.php– Core translation functionswp-includes/l10n/class-wp-translation-controller.php– Translation managementwp-includes/l10n/class-wp-translations.php– Compatibility wrapperwp-includes/class-wp-locale.php– Locale datawp-includes/class-wp-locale-switcher.php– Locale switchingwp-includes/pomo/– MO/PO file handling