Улучшения переводов в WP 6.5 (.l10n.php)

В WordPress 6.5 была переработана внутренняя система загрузки переводов. Основная цель изменения - ускорить работу локализованных сайтов и уменьшить расход памяти.

Раньше WordPress в основном работал с бинарными .mo файлами. Начиная с WP 6.5, ядро умеет использовать новый PHP-формат переводов - .l10n.php.

При этом обратная совместимость осталась: старые .po и .mo файлы продолжают работать.

Подробнее про переводы: https://wp-kama.ru/handbook/codex/translations

Как было

Переводы хранились в двух файлах:

  • .po - исходный редактируемый файл перевода.
  • .mo - бинарный файл, который загружает WordPress.

При загрузке текст-домена WordPress находил нужный .mo файл, парсил его и сохранял переводы в глобальной переменной $l10n.

Основные минусы старой логики:

  • .mo - бинарный формат, его нужно парсить при загрузке.
  • На сайтах с большим количеством переводов это могло заметно влиять на скорость.
  • Переключение локалей работало медленнее.

Как стало (WP 6.5)

В WordPress 6.5 появилась новая библиотека локализации, основанная на плагине Performant Translations.

Она улучшает работу переводов сразу в нескольких местах:

  • быстрее загружает .mo файлы;
  • использует меньше памяти;
  • поддерживает одновременную загрузку нескольких локалей;
  • ускоряет переключение локалей;
  • поддерживает новый формат .l10n.php;
  • использует OPcache для PHP-файлов переводов.

Главное отличие - WordPress теперь предпочитает .l10n.php, если такой файл существует рядом с .mo.

Новый формат .l10n.php

Файл .l10n.php - это PHP-файл, который возвращает массив с переводами.

Пример:

<?php
return array(
	'project-id-version' => 'WordPress - 6.5.x',
	'report-msgid-bugs-to' => 'polyglots@example.com',
	'messages' => array(
		'Original string' => 'Переведенная строка',
		'context' . "\4" . 'Original string' => 'Переведенная строка с контекстом',
		'One product' => 'Один товар' . "\0" . '%s товара' . "\0" . '%s товаров',
		'context' . "\4" . 'One product with context' => 'Один товар' . "\0" . '%s товара' . "\0" . '%s товаров',
	),
);

Особенности формата:

  • ключ массива - оригинальная строка;
  • значение - перевод;
  • контекст отделяется символом "\4";
  • формы множественного числа соединяются через "\0";
  • файл возвращает готовый массив.
  • файл кэшируется OPcache.

Как WordPress выбирает файл перевода

Упрощенно логика такая:

  1. WordPress определяет text domain и локаль.
  2. Находит путь к .mo файлу.
  3. Проверяет предпочитаемый формат перевода (по умолчанию формат - php).
  4. Если рядом есть подходящий .l10n.php, загружается он.
  5. Если .l10n.php нет, используется .mo.

То есть новая логика не ломает старые переводы.

Пример:

wp-content/languages/plugins/my-plugin-ru_RU.mo
wp-content/languages/plugins/my-plugin-ru_RU.l10n.php

Если существуют оба файла, WordPress загрузит:

my-plugin-ru_RU.l10n.php

Если PHP-файла нет, будет загружен:

my-plugin-ru_RU.mo

Что изменилось для разработчиков

Для обычного плагина или темы почти ничего менять не нужно.

Функции перевода остаются прежними:

__( 'Text', 'my-plugin' );
_e( 'Text', 'my-plugin' );
_x( 'Text', 'context', 'my-plugin' );
_n( 'One item', '%s items', $count, 'my-plugin' );

Меняется только внутренний способ загрузки файлов переводов.

Если плагин или тема размещены на WordPress.org, языковые пакеты могут содержать .l10n.php файлы автоматически.

Если переводы хранятся локально или поставляются не через WordPress.org, PHP-файлы можно создать вручную.

Генерация .l10n.php через WP-CLI

WP-CLI умеет создавать PHP-файлы переводов из .po файлов.

Создать PHP-файлы для всех .po файлов в текущей директории:

wp i18n make-php .

Создать PHP-файл из одного .po файла и положить результат в директорию languages:

wp i18n make-php example-plugin-ru_RU.po languages

После этого рядом с .po и .mo появится файл:

example-plugin-ru_RU.l10n.php

Генерация .l10n.php через PHP

Для работы с файлами переводов в WP 6.5 появился класс WP_Translation_File.

Пример преобразования .mo файла в PHP-файл:

$contents = WP_Translation_File::transform( $mofile, 'php' );
if ( $contents ) {
	file_put_contents( $php_file, $contents );
}

Плагин Performant Translations

Плагин Performant Translations больше не нужен для основной логики, потому что большая часть его функциональности вошла в ядро.

Но он все еще полезен, если:

  • переводы не приходят с WordPress.org;
  • используются коммерческие плагины;
  • переводы лежат только локально на сервере;
  • нужно автоматически создавать .l10n.php файлы из .mo.

Плагин может сам конвертировать .mo файлы в новый PHP-формат, если соответствующего .l10n.php файла еще нет.

Фильтр translation_file_format

Фильтр translation_file_format позволяет изменить предпочитаемый формат файла перевода.

По умолчанию WordPress предпочитает php.

Если нужно отключить использование .l10n.php и всегда использовать .mo:

add_filter( 'translation_file_format', static fn() => 'mo' );

Фильтр load_translation_file

Фильтр load_translation_file позволяет изменить путь к файлу перевода перед загрузкой.

В отличие от старого load_textdomain_mofile, этот фильтр работает не только с .mo, но и с .l10n.php.

Пример:

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
);

Фильтр load_textdomain_mofile

Фильтр load_textdomain_mofile остался для обратной совместимости.

Он работает только с путем к .mo файлу:

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
);

Лучше использовать load_translation_file, когда нужно работать с новым PHP-форматом.

Глобальная переменная $l10n

Раньше WordPress сохранял загруженные переводы в $l10n как объект класса MO.

В WordPress 6.5 используется новый класс WP_Translations, который имитирует старое поведение.

Для обычного кода это не важно.

Проверить нужно только проекты, которые напрямую работают с:

  • $GLOBALS['l10n'];
  • классом MO;
  • внутренней структурой загруженных переводов.

Обычные вызовы __(), _e(), _x(), _n() менять не нужно.

Кеширование списка файлов переводов

В WordPress 6.5 также улучшен поиск файлов переводов.

Раньше WordPress мог напрямую сканировать директории через glob(), например при работе с:

На сайтах с большим количеством языковых файлов это могло быть дорогой операцией.

Теперь список найденных файлов кешируется в object cache в группе translations.

Кеш очищается при обновлении языковых пакетов.

WordPress также ищет не только .mo, но и .l10n.php файлы.

Итого

Для разработчика плагина или темы новая логика выглядит так:

  • функции перевода не меняются;
  • text domain не меняется;
  • .po и .mo остаются рабочими;
  • .l10n.php используется автоматически, если он есть;
  • для WordPress.org проектов PHP-файлы могут приходить в языковых пакетах;
  • для локальных и коммерческих проектов .l10n.php можно генерировать через WP-CLI или PHP;
  • для смены пути к файлу лучше использовать load_translation_file;

--