Translation improvements in WP 6.5 (.l10n.php)
In WordPress 6.5, the internal translation loading system was redesigned. The main goal of the change is to speed up localized sites and reduce memory usage.
Previously WordPress mainly worked with binary .mo files. Starting with WP 6.5, the core can use the new PHP-format translations - .l10n.php.
At the same time, backward compatibility remained: old .po and .mo files continue to work.
How it was
Translations were stored in two files:
.po- the original editable translation file..mo- binary file that WordPress loads.
When loading a text-domain, WordPress found the needed .mo file, parsed it, and saved translations in the global variable $l10n.
Main downsides of the old logic:
.mo- binary format, it needs to be parsed on load.- On sites with a large number of translations, this could noticeably affect speed.
- Locale switching worked slower.
What happened (WP 6.5)
In WordPress 6.5 a new localization library appeared, based on the plugin Performant Translations.
It improves translation work in several places at once:
- loads
.mofiles faster; - uses less memory;
- supports simultaneous loading of several locales;
- speeds up locale switching;
- supports new format
.l10n.php; - uses OPcache for translation PHP files.
The main difference is that WordPress now prefers .l10n.php if such a file exists next to the .mo.
New format .l10n.php
The file .l10n.php is a PHP file that returns an array with translations.
Example:
<?php return array( 'project-id-version' => 'WordPress - 6.5.x', 'report-msgid-bugs-to' => '[email protected]', 'messages' => array( 'Original string' => 'Translated string', 'context' . "\4" . 'Original string' => 'Translated string with context', 'One product' => 'One product' . "\0" . '%s items' . "\0" . '%s items', 'context' . "\4" . 'One product with context' => 'One product' . "\0" . '%s items' . "\0" . '%s items', ), );
Format features:
- key of the array - original string;
- value - translation;
- context is separated by the character
"\4"; - plural forms are joined via
"\0"; - the file returns a ready array.
- the file is cached by OPcache.
How WordPress selects a translation file
Simplified, the logic is as follows:
- WordPress determines the text domain and locale.
- It finds the path to the
.mofile. - It checks the preferred translation format (default format -
php). - If there is a suitable
.l10n.phpnearby, it is loaded. - If there is no
.l10n.php,.mois used.
So the new logic does not break old translations.
Example:
wp-content/languages/plugins/my-plugin-ru_RU.mo wp-content/languages/plugins/my-plugin-ru_RU.l10n.php
If both files exist, WordPress will load:
my-plugin-ru_RU.l10n.php
If the PHP file is not present, the following will be loaded:
my-plugin-ru_RU.mo
What changed for developers
For a normal plugin or theme, almost nothing needs to be changed.
Translation functions remain the same:
__( 'Text', 'my-plugin' ); _e( 'Text', 'my-plugin' ); _x( 'Text', 'context', 'my-plugin' ); _n( 'One item', '%s items', $count, 'my-plugin' );
Only the internal method of loading translation files changes.
If a plugin or theme is hosted on WordPress.org, language packs may automatically contain .l10n.php files.
If translations are stored locally or not provided via WordPress.org, PHP files can be created manually.
Generating .l10n.php via WP-CLI
WP-CLI can create PHP translation files from .po files.
Create PHP files for all .po files in the current directory:
wp i18n make-php .
Create a PHP file from a single .po file and place the result in the languages directory:
wp i18n make-php example-plugin-ru_RU.po languages
After that, alongside the .po and .mo file, you will see:
example-plugin-ru_RU.l10n.php
Generating .l10n.php via PHP
To work with translation files in WP 6.5, the class WP_Translation_File appeared.
Example of converting a .mo file to a PHP file:
$contents = WP_Translation_File::transform( $mofile, 'php' );
if ( $contents ) {
file_put_contents( $php_file, $contents );
}
Performant Translations Plugin
The plugin Performant Translations is no longer required for core logic, because much of its functionality has been built into the core.
But it is still useful if:
- translations do not come from WordPress.org;
- commercial plugins are used;
- translations are stored only locally on the server;
- you need to automatically create
.l10n.phpfiles from.mo.
The plugin can itself convert .mo files to the new PHP format if the corresponding .l10n.php file does not yet exist.
Filter translation_file_format
The filter translation_file_format allows changing the preferred translation file format.
By default, WordPress prefers php.
If you want to disable the use of .l10n.php and always use .mo:
add_filter( 'translation_file_format', static fn() => 'mo' );
Filter load_translation_file
The filter load_translation_file allows changing the translation file path before loading.
Unlike the old load_textdomain_mofile, this filter works not only with .mo, but also with .l10n.php.
Example:
add_filter(
'load_translation_file',
static function ( $file, $domain, $locale ) {
if ( 'my-plugin' !== $domain ) {
return $file;
}
$custom_file = WP_LANG_DIR . "/my-plugin/my-plugin-{$locale}.l10n.php";
if ( file_exists( $custom_file ) ) {
return $custom_file;
}
return $file;
},
10,
3
);
Filter load_textdomain_mofile
The filter load_textdomain_mofile remains for backward compatibility.
It only works with the path to the .mo file:
add_filter(
'load_textdomain_mofile',
static function ( $mofile, $domain ) {
if ( 'my-plugin' !== $domain ) {
return $mofile;
}
return WP_LANG_DIR . '/my-plugin/custom-ru_RU.mo';
},
10,
2
);
It is better to use load_translation_file when you need to work with the new PHP format.
Global variable $l10n
Previously WordPress stored loaded translations in $l10n as an object of class MO.
In WordPress 6.5 a new class WP_Translations is used, which mimics the old behavior.
For ordinary code this does not matter.
You need to check only projects that directly work with:
$GLOBALS['l10n'];- the
MOclass; - the internal structure of loaded translations.
Ordinary calls to __(), _e(), _x(), _n() do not need to change.
Caching the list of translation files
In WordPress 6.5, the search for translation files was also improved.
Previously WordPress could directly scan directories via glob(), for example when working with:
- get_available_languages();
- WP_Textdomain_Registry;
- just-in-time loading of translations.
On sites with a large number of language files, this could be a costly operation.
Now the list of found files is cached in the object cache in the translations group.
The cache is cleared when updating language packs.
WordPress also looks for not only .mo, but also .l10n.php files.
Summary
For a plugin or theme developer, the new logic looks like this:
- translation functions do not change;
- text domain does not change;
.poand.moremain working;.l10n.phpis used automatically if it exists;- for WordPress.org projects PHP files can come in language packs;
- for local and commercial projects
.l10n.phpcan be generated via WP-CLI or PHP; - to change the file path, it is better to use load_translation_file;
--