Если в теме или плагине остаются английские фразы, хотя локализация вроде бы настроена, чаще всего проблема не в «сломанных переводах», а в том, что строка выводится не через стандартные функции WordPress, либо фильтруется слишком поздно. В таких случаях помогает точечная работа через gettext, gettext_with_context и, где нужно, ngettext.
Этот подход полезен для мелких правок интерфейса: заменить текст кнопки, подпись в уведомлении, заголовок блока, системную фразу плагина. Но он не должен становиться заменой нормальной локализации. Если есть доступ к исходникам темы или плагина, правильнее сначала проверить, можно ли перевести строку через __(), _e(), _x() и файл перевода.
Когда gettext — это правильный инструмент
Фильтр gettext срабатывает на уже загруженной строке перевода. То есть вы не переводите текст «с нуля», а подменяете результат перед выводом. Это удобно в трех сценариях:
- строка жестко зашита в тему или плагин, а обновлять код вы не хотите;
- перевод в
.po/.moесть, но конкретная фраза не устраивает по смыслу; - нужно быстро исправить одну-две строки без создания отдельного языкового пакета.
Но есть и ограничения. Если строка вообще не проходит через функции локализации WordPress, фильтр ее не увидит. Если текст собирается динамически через конкатенацию или выводится напрямую, сначала нужно исправить сам код.
Что проверить до правки
Перед тем как писать фильтр, откройте исходник темы или плагина и найдите, как именно выводится строка. Ищите вызовы __(), _e(), esc_html__(), esc_attr__(), _x(), _n(). Если строка там есть, фильтр сработает. Если видите обычный echo 'Text';, gettext не поможет.
Полезно также проверить домен перевода. Если плагин использует свой text domain, а вы фильтруете слишком широко, можно случайно заменить одинаковую строку в другом месте сайта.
Диагностика проблемы: почему строка не меняется
Самая частая ошибка — фильтр написан, но условие не совпадает с реальной строкой. В gettext вы получаете уже переведенный текст, исходную строку и text domain. Если сайт работает на русском, а строка уже переведена в файле языка, сравнивать нужно именно с тем значением, которое приходит в фильтр.
Еще одна типичная причина — строка выводится в админке, а фильтр подключен только на фронтенде. Или наоборот: вы правите текст в шаблоне письма WooCommerce, а код выполняется только при отправке письма, не на обычной странице.
Для быстрой проверки можно временно залогировать входящие значения. Это безопаснее, чем гадать по памяти, особенно если строк несколько и они похожи друг на друга.
add_filter('gettext', function ($translated, $text, $domain) {
if ($domain === 'woocommerce') {
error_log('TEXT: ' . $text . ' | TRANSLATED: ' . $translated);
}
return $translated;
}, 10, 3);После этого откройте нужную страницу и посмотрите debug.log. Если строка не появляется, значит она либо не проходит через gettext, либо выводится в другом контексте.
Пошаговое решение через gettext
Надежнее всего не править ядро темы или плагина, а добавить небольшой MU-плагин или код в собственный мини-плагин. Так вы не потеряете изменения после обновления.
Шаг 1. Создайте отдельный файл с фильтром
Например, в wp-content/mu-plugins/ можно положить файл custom-text-fixes.php. MU-плагин загрузится автоматически и не зависит от активации в админке.
<?php
/**
* Plugin Name: Custom Text Fixes
*/
add_filter('gettext', 'wptranslate_replace_specific_strings', 20, 3);
function wptranslate_replace_specific_strings($translated, $text, $domain) {
if ($domain !== 'woocommerce') {
return $translated;
}
$map = array(
'Place order' => 'Оформить заказ',
'Add to cart' => 'В корзину',
);
if (isset($map[$text])) {
return $map[$text];
}
return $translated;
}Здесь важно сравнивать именно $text, а не только $translated. Так вы не зависите от того, есть ли уже готовый перевод в языковом файле.
Шаг 2. Если строка зависит от контекста, используйте gettext_with_context
Одинаковая фраза может переводиться по-разному в зависимости от контекста. Например, Order как «заказ» и как «порядок». В таких случаях нужен фильтр gettext_with_context.
add_filter('gettext_with_context', 'wptranslate_replace_contextual_strings', 20, 4);
function wptranslate_replace_contextual_strings($translated, $text, $context, $domain) {
if ($domain !== 'woocommerce') {
return $translated;
}
if ($text === 'Order' && $context === 'noun') {
return 'Заказ';
}
return $translated;
}Контекст имеет смысл проверять только тогда, когда вы точно видите его в исходнике. Иначе фильтр будет слишком хрупким.
Шаг 3. Для множественного числа используйте ngettext
Если нужно подменить строку с учетом числа, обычного gettext недостаточно. Для этого есть ngettext.
add_filter('ngettext', 'wptranslate_replace_plural_strings', 20, 5);
function wptranslate_replace_plural_strings($translated, $single, $plural, $number, $domain) {
if ($domain !== 'woocommerce') {
return $translated;
}
if ($single === '%s item' && $plural === '%s items') {
return _n('%s товар', '%s товара', $number, 'your-textdomain');
}
return $translated;
}Здесь есть тонкость: если вы возвращаете свою форму, следите, чтобы она была совместима с sprintf и количеством плейсхолдеров. Иначе получите битый вывод.
Сравнение подходов: перевод через фильтр, языковой файл или правка шаблона
| Подход | Когда подходит | Минус |
|---|---|---|
Фильтр gettext | Нужно быстро заменить отдельные строки без правки исходников | Сложнее поддерживать, если строк много |
.po/.mo перевод | Строка уже локализуется штатно | Не помогает, если текст не обернут в функции перевода |
| Правка шаблона/кода | Строка выводится напрямую или нужна системная доработка | Нужно следить за обновлениями темы или плагина |
Если задача разовая, фильтр обычно быстрее. Если вы переводите большой набор строк, лучше собрать нормальный языковой файл или вынести правки в дочернюю тему/собственный плагин.
Как проверить, что решение сработало
Проверка должна быть не «на глаз один раз», а по нескольким сценариям. Откройте страницу в обычном режиме, в инкогнито и, если строка относится к WooCommerce, протестируйте ее в корзине и на странице оформления заказа.
- строка изменилась именно там, где нужно;
- не сломались другие одинаковые фразы;
- в HTML не появились лишние символы и не поехала верстка;
- после очистки кэша результат сохраняется;
- в
debug.logнет предупреждений по фильтру илиsprintf.
Если используете кэш-плагин или серверный кэш, очистите его после правки. Иначе вы можете проверить старую версию страницы и сделать ложный вывод, что фильтр не работает.
Частые ошибки и как их исправить
Фильтр слишком общий
Если вы не ограничили $domain, можно случайно заменить строку в другой теме или плагине. Это особенно заметно, когда одинаковые фразы используются в нескольких местах сайта. Исправление простое: всегда проверяйте text domain и, при необходимости, еще и точное значение $text.
Сравнение идет с неправильной строкой
Иногда разработчик сравнивает с английским текстом, а в фильтр уже приходит переведенная версия. Это зависит от того, есть ли активный перевод и как именно строка проходит по цепочке. Чтобы не ошибиться, сначала залогируйте значения и посмотрите реальный вход.
Строка выводится не через gettext
Если текст собран вручную, через echo или JavaScript-шаблон, фильтр не поможет. В таком случае нужно менять исходный код: обернуть строку в __() или передавать ее через wp_localize_script(), если речь о фронтенд-скрипте.
Ломается множественное число
Часто это происходит, когда в ответ возвращают строку с другим количеством плейсхолдеров. Проверьте, что %s, %d и другие маркеры совпадают с тем, что ожидает исходный код.
Практические советы по безопасности и производительности
Фильтр gettext вызывается часто, поэтому не стоит превращать его в тяжелый обработчик. Не делайте внутри него запросы к базе, не дергайте внешние API и не гоняйте большие массивы без необходимости. Для десятка строк достаточно обычного массива соответствий.
Если правок много, лучше:
- ограничить фильтр конкретным доменом;
- использовать точные совпадения, а не поиск по подстроке, где это возможно;
- держать код в MU-плагине или отдельном мини-плагине, а не в
functions.phpактивной темы; - проверять изменения после обновления темы или плагина, чтобы не потерять совместимость.
Для крупных проектов иногда выгоднее не городить ручные подмены, а привести локализацию в порядок на уровне исходников. Это проще сопровождать и безопаснее для команды, которая не помнит все точечные замены через полгода.
Если нужно быстро закрыть проблему с несколькими строками в интерфейсе магазина, фильтр gettext — рабочий инструмент. Но он хорош именно как точечная правка, а не как замена нормальной локализации WordPress.