Skip to content

Переводчики (PRO) ​

Машинный перевод контента. Переводчик — это строка в PageBlocks → Мультиязычность → Переводчики; компонент дёргает API провайдера и сохраняет результат как перевод оригинала.

Речь про контент, а не про строки интерфейса — те живут в лексиконах, ими занимается Локализация.

Строка — это профиль, а не «переводчик сайта» ​

Переводчиков может быть сколько угодно, и выбор делает язык, а не сайт: у каждого языка есть поле Переводчик, в котором выбирается один из профилей. Отсюда обычная рабочая схема — DeepL на европейские языки, языковая модель на остальные.

Язык без выбранного переводчика не переводится вовсе: в интерфейсе это «Для этого языка не настроен переводчик».

Драйверы ​

ДрайверПровайдерМодельПромпт
chatgptOpenAIgpt-4o по умолчаниюда
claudeAnthropicclaude-opus-4-8 по умолчаниюда
geminiGoogleобязательнада
grokxAIобязательнада
deeplDeepLне используетсяне используется

Четыре из них — языковые модели, один специализированный переводчик. Интерфейс у всех общий, поэтому смена драйвера это правка одной строки, а не переделка настроек.

SDK провайдеров компонент не тащит: каждый драйвер — обычный cURL-клиент на пять методов. Поэтому и «свой шлюз» работает без оговорок — достаточно, чтобы он отвечал в формате провайдера.

Общие поля ​

ПолеЧто это
НазваниеЧеловеческое имя профиля — его видно в списке у языка
ПровайдерОдин из пяти драйверов
Ключ APIКлюч провайдера. Только на запись: обратно в браузер не отдаётся
МодельИдентификатор модели провайдера
Базовый URLПереопределить адрес — прокси, свой шлюз или выбор тарифа у DeepL
ПромптШаблон инструкции, только для LLM-драйверов
ОпцииJSON с дополнительными настройками — см. ниже
АктивенНеопубликованный переводчик не используется

Ключ при правке остаётся пустым — так и надо

Он хранится только на запись и в браузер не возвращается, поэтому в окне правки поле пустое, а не заполненное звёздочками. Оставили пустым — сохранится прежний ключ. Вписали новый — заменится. Стереть ключ через это поле нельзя, и это сознательно: пустая форма, отправленная случайно, не должна обнулять доступ.

В списке вместо ключа колонка Ключ API с галочкой: видно, что ключ есть, и не видно, какой.

Настройка по провайдерам ​

ChatGPT (OpenAI) ​

  1. Ключ — platform.openai.com/api-keys. Нужен пополненный баланс в Billing: бесплатного уровня у API нет, и свежий ключ без оплаты отвечает отказом по квоте.
  2. Провайдер: ChatGPT (OpenAI).
  3. Ключ API: строка вида sk-….
  4. Модель: можно не заполнять — возьмётся gpt-4o. Любой другой идентификатор из консоли подставляется как есть.
  5. Базовый URL: пусто. Заполняется только под прокси или совместимый шлюз — к адресу дописывается /v1/chat/completions, то есть указывать нужно корень, а не эндпоинт.

Запрос уходит с temperature: 0, промпт едет системным сообщением.

Claude (Anthropic) ​

  1. Ключ — console.anthropic.com, раздел API Keys. Баланс так же нужен.
  2. Провайдер: Claude (Anthropic).
  3. Ключ API: строка вида sk-ant-….
  4. Модель: можно не заполнять — возьмётся claude-opus-4-8.
  5. Базовый URL: пусто; к нему дописывается /v1/messages.

Две особенности, обе уже стоили времени:

  • Параметров выборки нет. temperature, top_p, top_k на Claude Opus 4.7 и новее отклоняются с 400, поэтому драйвер их не отправляет вовсе.
  • Размышление выключено явно. На новых моделях оно включено по умолчанию, и ответ приходит с блоками thinking впереди текста: на одном описании это 5,6 с и 226 выходных токенов против 2,2 с и 114 без него. Переводу строки бюджет на размышление не нужен. Модель, которая отказывается его выключать (отвечает 400), повторяется один раз без параметра — неверная догадка про одну модель не должна уронить весь прогон.

Отказ классификатора приходит с HTTP 200, пустым содержимым и stop_reason: refusal. Драйвер пишет в лог «Провайдер отказался переводить этот текст», а не молча возвращает пустоту.

Gemini (Google) ​

  1. Ключ — aistudio.google.com/apikey.
  2. Провайдер: Gemini (Google).
  3. Модель — обязательно. Значения по умолчанию здесь нет: идентификатор модели у Google едет в адресе запроса, а не в теле, и подставлять туда догадку нельзя. Берите из AI Studio — например gemini-2.5-flash для массового перевода или gemini-2.5-pro там, где качество важнее цены.
  4. Базовый URL: пусто (https://generativelanguage.googleapis.com); к нему дописывается /v1beta/models/<модель>:generateContent.

Пустая модель здесь даёт внятную ошибку

Драйвер проверяет её сам и пишет «Для этого переводчика не задана модель», не отправляя запрос. У Grok такой проверки нет — там пустая модель уходит провайдеру и возвращается его собственной ошибкой HTTP.

Grok (xAI) ​

  1. Ключ — console.x.ai, раздел API Keys.
  2. Провайдер: Grok (xAI).
  3. Модель — обязательно, значения по умолчанию нет. Идентификатор берите из списка моделей в консоли xAI.
  4. Базовый URL: пусто (https://api.x.ai); к нему дописывается /v1/chat/completions.

xAI отдаёт API, совместимый с OpenAI, поэтому драйвер целиком наследует запрос, заголовок авторизации и разбор ответа от chatgpt — различается только адрес.

DeepL ​

  1. Ключ — личный кабинет DeepL, раздел API keys. Бесплатный тариф существует: 500 тысяч символов в месяц.
  2. Провайдер: DeepL.
  3. Модель и промпт не заполняются — DeepL не языковая модель. Ему отдаётся текст и два кода языка, инструкции он не понимает.
  4. Базовый URL — здесь это выбор тарифа, а не прокси:
ТарифБазовый URL
DeepL API Freehttps://api-free.deepl.com (подставляется, если поле пусто)
DeepL API Prohttps://api.deepl.com

Ключ Pro на бесплатном адресе не работает, и наоборот

Это самая частая поломка на DeepL: ключ правильный, адрес от другого тарифа — и провайдер отвечает отказом авторизации. Купили Pro — впишите https://api.deepl.com вручную, само оно не переключится.

Разметка сохраняется: запрос уходит с tag_handling: html, потому что значения блоков сплошь и рядом несут инлайновый HTML.

Промпт ​

По умолчанию просит вернуть только перевод, сохранив HTML и плейсхолдеры:

Translate the following text from [[+from]] to [[+to]].
Reply with the translation only, preserving any HTML markup and placeholders:

Переводимый текст дописывается драйвером сам — в шаблоне его подставлять не надо и экранировать нечего.

[[+from]] и [[+to]] подставляются НАЗВАНИЕМ языка, а не кодом

UKRAINIAN, а не UK. И это не косметика: uk — украинский по ISO 639-1, но «UK» для любой модели ещё и United Kingdom. Промпт «translate to UK» давал английский текст в шести случаях из тридцати, с UKRAINIAN — ноль из тридцати. На боевом так испортилось 604 украинских описания, прежде чем это заметили.

Ловушка не одна: cs путают с сербским, sl со словацким, hy с венгерским. Поэтому название подставляется всем языкам, а не только украинскому. Чего нет в списке названий, уходит кодом, как раньше.

Промпт — та ручка, за которую надо браться, когда провайдер упорно дописывает вокруг перевода пояснения или корёжит ваши {$плейсхолдеры}.

Опции ​

Поле Опции — это JSON, который складывается в data строки. Всё необязательное.

КлючПо умолчаниюЧто делает
timeout60Таймаут запроса, секунды
retries3Сколько раз повторять запрос, упёршийся в частоту
model_long—Вторая модель, для длинных текстов
model_long_from400Порог в символах, с которого берётся model_long
model_by_language—Модель, назначенная конкретным языкам: {"de": "…"}
json
{
    "timeout": 90,
    "model_long": "gpt-4o-mini",
    "model_long_from": 400,
    "model_by_language": {"ro": "gpt-4o"}
}

Две модели: по длине и по языку ​

Сайту обычно нужны две. Сильная — для заголовков и описаний: они попадают в выдачу, и сухой, глотающий артикли перевод там виден. Дешёвая и быстрая — для текста под сгибом, где та же сухость ничего не стоит.

Выбор идёт по длине, а не по имени поля: драйвер получает одну строку и не знает, что это такое, а протащить имя поля через три слоя значило бы выражать то же, что уже выражает длина (мета — пара сотен символов, текст — тысячи). Пока model_long не задан, работает одна модель и поведение ровно прежнее.

model_by_language старше выбора по длине. Длина говорит о тексте, язык — о том, насколько дорого ошибиться: витринный язык ведут целиком, и там качество важнее цены. Ключ — код языка в том виде, в каком его получает драйвер.

Расход считается в разрезе моделей, иначе общий счёт при двух моделях бесполезен: дешёвая и дорогая складываются в одно число, по которому деньги не посчитать. Модель берётся из ответа провайдера, а не из настройки, — при разделении по длине настройка назвала бы одну.

Что провайдеру не отправляется ​

Три вида значений отсеиваются до запроса и возвращаются как есть:

  • пустая строка — переводить нечего;
  • число — отправить 2024 в языковую модель и получить обратно пересказ вполне реальный риск, а смысла нет;
  • адрес, а не фраза — значение без пробелов, содержащее /, ?, = или &.

Третье правило появилось не из теории. Поля конструктора типизированы виджетом, а не смыслом: ссылка в textfield для фильтра по типу выглядит точно так же, как заголовок. На одном сайте order_url хранил …/dobavit-gruz?transport_type=2 и уезжал в переводчик на каждой странице каждого языка — десятки тысяч платных запросов за тем же самым ответом, и битая ссылка в день, когда модель решит перевести слаг.

Правило намеренно узкое: нужны оба признака — нет пробелов и есть символ адреса. Одно обычное слово («Компании») не имеет ни того ни другого и переводится как обычно.

Короткая строка переводится в одну строку ​

Если в исходнике нет перевода строки, из ответа берётся только первая непустая строка.

На коротких надписях модель иногда не останавливается после перевода, а продолжает «похожим» текстом: к переводу «Кто это видит» трижды подряд приходил верный «Cine vede acest lucru», а следом — десяток чужих фраз про cookies, выход из аккаунта и даже CSS с другого сайта. Повтор не спасает, на этой строке срабатывает устойчиво. Верный перевод при этом всегда идёт первой строкой. Многострочные исходники не трогаются — там разбивка осмысленна.

Он никогда не бросает исключение ​

translate() всегда возвращает строку. Любой сбой возвращает исходный текст и пишет причину в лог MODX:

СитуацияРезультат
Пустой текст, число или адресВозвращается как есть, запрос не уходит
Нет ключа APIИсходный текст, причина в логе
Нет модели у geminiИсходный текст, причина в логе
Сбой транспорта или ошибка провайдераИсходный текст, причина в логе
Отказ провайдера переводитьИсходный текст, причина в логе
Ответ не разобралсяИсходный текст, ответ в логе
Неизвестный драйверФабрика возвращает пустоту, строка в логе

Так задумано: прогон перевода по сотням строк не должен обрываться на середине из-за одного таймаута. Но из этого же следует, что тихий сбой выглядит как «ничего не перевелось». Если так вышло — сперва читайте лог, а уже потом меняйте настройки.

Частота и суточный лимит — это разные отказы ​

Провайдер отвечает 429 в двух случаях, и путать их дорого.

  • «Слишком часто» лечится паузой. Если провайдер сам назвал её («try again in 8.64s»), берётся она, не больше минуты; не назвал — пять секунд. Попыток столько, сколько в retries.
  • «Запросов на сегодня больше нет» паузой не лечится: до полуночи ответ не изменится. Такой отказ узнаётся по тексту (per day, RPD, daily) и останавливает весь прогон, а не одну строку.

Второе отделено от первого не на всякий случай: прогон, который этого не понимал, выпустил 74 165 отказов за один вечер и раздул лог.

Бюджет токенов ​

У LLM-драйверов лимит на ответ считается от входа — примерно двадцать токенов на слово плюс минимум для коротких строк, потолок 8192. Перевод по длине сопоставим с оригиналом, а потолок стоит, чтобы не оплатить сорвавшийся ответ.

Что переводится ​

Переводы хранятся по языкам в pb_translations, языки настраиваются в pb_languages. Страница показывает оригинал, когда перевода нет, поэтому недоделанный перевод никогда не опустошает сайт.

Платите вы ​

Все эти провайдеры берут деньги за запрос. Компонент не группирует запросы, не кэширует их и не сверяется с прошлыми прогонами — перевести одно и то же поле дважды стоит вдвое. Начните с нескольких строк и посмотрите на результат, прежде чем натравливать его на целую таблицу.

© PageBlocks 2019-present