Полное руководство по разработке сервисов для QTranslate на JavaScript (Доработанный вариант)
Размещяю доработанный в Google AI Studio вариант мануала, присланный мне читателем Максом. Спасибо ему за это. Так-же размещу у себя несколько материалов от него.
📘 Разработка сервисов для QTranslate на JavaScript
📑 Оглавление
- 1. Архитектура системы и среда выполнения
- 2. Структура файла service.js и базовый шаблон
- 3. Обязательные функции
- 4. Опциональные функции (Автоопределение, Озвучка, Языки)
- 5. Встроенные объекты, классы и константы
- 6. Критические ограничения JScript (Ловушки ES6)
- 7. Продвинутые возможности: Цепочки запросов и Словари
- 8. Примеры для разных типов API
- 9. Чеклист и отладка перед публикацией
1. 🏗️ Архитектура системы и среда выполнения
Каждый сервис перевода в QTranslate представляет собой изолированный модуль, находящийся в папке Services/[ИмяСервиса]/. Главной точкой входа является файл service.js.
⚠️ Внимание — Среда выполнения: Скрипты выполняются встроенным в ОС Windows движком Active Scripting (JScript), соответствующим стандарту ECMAScript 3 (IE 9 и младше). Современные возможности JavaScript (такие как let, const, стрелочные функции, промисы, нативный JSON или метод строк .trim()) могут быть недоступны и приведут к фатальному сбою сервиса!
Миф о песочнице: В отличие от стандартных браузеров, JScript-код в QTranslate не заблокирован в песочнице. Он имеет полный доступ к COM-технологиям Windows. Вы можете создавать системные объекты через new ActiveXObject(). Это позволяет вызывать внешние консольные утилиты (WScript, PowerShell, curl), работать с реестром и файловой системой для обхода капч, авторизаций и защит (например, Cloudflare или WebSockets, как это сделано в сервисе QuillBot).
2. 📁 Структура файла service.js и базовый шаблон
Для корректного подключения нового сервиса достаточно создать одну папку с единственным скриптом:
Services/
└── MyNewTranslator/
└── service.js ← Файл с логикой сервиса
📋 Безопасный шаблон сервиса:
// ============================================================================
// MyNewTranslator for QTranslate
// ============================================================================
// Автор: Имя_Разработчика
// Дата: 2026
// ============================================================================
var SERVICE_URL = "https://api.example.com";
var SERVICE_ID = 650; // Пользовательские ID должны быть в диапазоне 500-999
function serviceHeader() {
return new ServiceHeader(
SERVICE_ID,
"MyNewTranslator",
"Описание сервиса" + Const.NL2 + SERVICE_URL,
Capability.TRANSLATE
);
}
function serviceHost() {
return SERVICE_URL;
}
function serviceLink(text, from, to) {
if (!text) return SERVICE_URL;
from = isLanguage(from) ? codeFromLanguage(from) : "auto";
to = isLanguage(to) ? codeFromLanguage(to) : "en";
return SERVICE_URL + "/web?text=" + encodeGetParam(text) + "&sl=" + from + "&tl=" + to;
}
function serviceTranslateRequest(text, from, to) {
// limitSource и prepareSource объявлены глобально в Common.js
text = limitSource(prepareSource(text), 5000);
from = (from === "auto" || !isLanguage(from)) ? "auto" : codeFromLanguage(from);
to = isLanguage(to) ? codeFromLanguage(to) : "en";
var body = {
q: text,
source: from,
target: to
};
// Используем stringifyJSON из Common.js вместо нативного JSON.stringify
return new RequestData(
HttpMethod.POST,
"/v1/translate",
stringifyJSON(body),
"Content-Type: application/json"
);
}
function serviceTranslateResponse(original, json, from, to) {
try {
var data = parseJSON(json); // parseJSON встроена в Common.js
if (!data || !data.translatedText) {
throw new Error("Неверный формат ответа API");
}
var result = data.translatedText;
if (isArray(result)) {
result = result.join(Const.NL);
}
// trimString(str) - безопасная замена str.trim() из Common.js
result = trimString(result);
// Нормализация переносов строк и удаление лишних пустых строк
result = removeEmptyLines(result);
return new ResponseData(result, from, to, "");
} catch (e) {
return new ResponseData(original + Const.NL2 + "[⚠️ Ошибка MyNewTranslator: " + e.message + "]", from, to, "");
}
}
3. ✅ Обязательные функции
Каждый файл service.js обязан реализовывать пять фундаментальных функций, которые ядро QTranslate вызывает в строгом порядке.
3.1 serviceHeader()
Инициализирует метаданные сервиса в интерфейсе программы.
function serviceHeader() {
return new ServiceHeader(
650, // ID сервиса
"MyTranslator", // Имя в меню
"Описание" + Const.NL + "© 2026", // Текст во вкладке О Программе
Capability.TRANSLATE | Capability.DETECT_LANGUAGE // Битовая маска возможностей
);
}
3.2 serviceHost(from, to, text)
Должен возвращать базовый URL (хост) API переводчика. Используется для формирования сетевых запросов.
function serviceHost(from, to, text) {
return "https://api.example.com";
}
3.3 serviceLink(text, from, to)
Возвращает URL-адрес для кнопки «Открыть в браузере». Направляя пользователя на веб-версию переводчика с подставленным текстом.
function serviceLink(text, from, to) {
if (!text) return "https://example.com";
return "https://example.com/?text=" + encodeGetParam(text);
}
3.4 serviceTranslateRequest(text, from, to)
Вызывается при клике на перевод. Формирует структуру запроса (метод, путь, заголовки, тело). Возвращает объект RequestData.
function serviceTranslateRequest(text, from, to) {
text = limitSource(prepareSource(text), 3000);
var query = "q=" + encodeGetParam(text) + "&to=" + codeFromLanguage(to);
return new RequestData(HttpMethod.GET, "/v1/translate?" + query, null);
}
3.5 serviceTranslateResponse(original, json, from, to)
Принимает сырой текстовый ответ от сервера (JSON/XML/HTML), парсит его и преобразовывает в объект ResponseData.
function serviceTranslateResponse(original, json, from, to) {
var data = parseJSON(json);
return new ResponseData(data.result, from, to);
}
4. 🔁 Опциональные функции
Если в serviceHeader() вы указали соответствующие битовые маски, необходимо добавить обработчики для расширенного функционала.
4.1 Автоопределение языка (Capability.DETECT_LANGUAGE)
Позволяет QTranslate определять исходный язык текста без отправки полного запроса на перевод.
function serviceDetectLanguageRequest(text) {
text = limitSource(text, 300); // Для определения языка достаточно короткой строки
var body = "text=" + encodePostParam(text);
return new RequestData(
HttpMethod.POST,
"/detect",
body,
"Content-Type: application/x-www-form-urlencoded"
);
}
function serviceDetectLanguageResponse(json) {
try {
var data = parseJSON(json);
var code = trimString(String(data.langCode || ""));
return isLanguage(languageFromCode(code)) ? languageFromCode(code) : UNKNOWN_LANGUAGE;
} catch (e) {
return UNKNOWN_LANGUAGE;
}
}
4.2 Воспроизведение речи / TTS (Capability.LISTEN)
Добавляет иконку «Наушники» для озвучивания текста голосом.
function serviceListenRequest(text, lang, slow) {
// slow = true, если пользователь выбрал медленный режим прослушивания
lang = isLanguage(lang) ? codeFromLanguage(lang) : "en";
var path = "/tts?text=" + encodeGetParam(text) + "&lang=" + lang + (slow ? "&speed=0.5" : "");
return new RequestData(HttpMethod.GET, path, null);
}
4.3 Динамическая загрузка поддерживаемых языков
Если API поддерживает динамический список языков, вы можете переопределить стандартный массив SupportedLanguages, объявленный в Common.js.
function serviceLanguagesRequest() {
return new RequestData(HttpMethod.GET, "/languages", null);
}
function serviceLanguagesResponse(json) {
try {
var data = parseJSON(json); // Ожидаем массив ["en", "ru", "fr"]
if (!isArray(data)) return null;
var codes = [-1, "auto"]; // -1 = UNKNOWN, auto = AUTO DETECT
for (var i = 0; i < data.length; i++) {
codes.push(data[i]);
}
return codes; // Массив автоматически заменит SupportedLanguages для этого сервиса
} catch (e) {
return null; // В случае сбоя QTranslate откатится на стандартный набор языков
}
}
5. 🧰 Встроенные объекты, классы и константы
Все сервисы используют общую инфраструктуру ядра QTranslate и утилиты файла Common.js.
| Класс / Конструктор | Аргументы | Описание |
|---|---|---|
ServiceHeader |
(id, name, info, capabilities) |
Метаданные сервиса для регистрации в главном окне. |
RequestData |
(method, uri, data, headers, codepage, responseHandler) |
Описание HTTP-запроса. responseHandler — имя функции JScript, которая примет ответ (по умолчанию стандартная response-функция). |
ResponseData |
(translation, sourceLanguage, translationLanguage, data, nextRequestHandler) |
Структурированный результат. data — доп. инфо (например, транслит). nextRequestHandler — имя функции для цепочки запросов. |
Битовые константы возможностей (Capability):
Capability.TRANSLATE = 1— Перевод текстаCapability.DETECT_LANGUAGE = 2— Определение языкаCapability.LISTEN = 4— Озвучка текста (Голос)Capability.DICTIONARY = 8— Поддержка словарного формата
Константы форматирования (Const):
Const.NL = "\r\n"— Системный перевод строки Windows (CRLF)Const.NL2 = "\r\n\r\n"— Двойной перевод строкиConst.MAX_URI_LEN = 1800— Лимит длины URL-адреса для GET-запросовConst.MAX_SOURCE_LEN = 5000— Лимит длины переводимого текста по умолчанию
6. 🚨 Критические ограничения JScript (Ловушки ES6)
Несоблюдение этих правил гарантированно сломает ваш скрипт на ПК некоторых пользователей с отличной от вашей конфигурацией IE:
| ❌ НЕЛЬЗЯ использовать | ✔️ НУЖНО использовать | Почему? |
|---|---|---|
const x = 5; let y = 10; |
var x = 5; var y = 10; |
Ключевые слова let и const отсутствуют в стандарте ES3 (движок JScript выдаст синтаксическую ошибку). |
text.trim() |
trimString(text) |
В старых версиях JScript у объекта String нет метода .trim(). Безопасная функция-замена объявлена в Common.js. |
JSON.stringify(obj) |
stringifyJSON(obj) |
Глобальный объект JSON отсутствует на старых версиях Windows (XP/7) с необновленным IE. Используйте встроенную stringifyJSON. |
() => { ... } (стрелочные функции) |
function() { ... } |
Стрелочный синтаксис не поддерживается парсером JScript. |
Array.prototype.find |
Обычный цикл for |
Методы find, includes не реализованы. (В Common.js есть полифиллы только для map, forEach и filter). |
7. ⚡ Продвинутые возможности: Цепочки запросов и Словари
7.1 Построение цепочек (Многоэтапные запросы)
Если API переводчика требует предварительной авторизации или получения CSRF-токена, вы можете организовать последовательное выполнение нескольких запросов.
Шаг 1: В функции serviceTranslateRequest запрашиваем токен и переопределяем callback-обработчик ответа на промежуточный (6-й параметр RequestData):
function serviceTranslateRequest(text, from, to) {
// Первый запрос: идем за токеном на сервер
return new RequestData(
HttpMethod.GET,
"/get-csrf-token",
null,
null,
null,
"onTokenResponse" // Наш промежуточный обработчик
);
}
Шаг 2: Пишем промежуточную функцию, которая считывает токен и передает его в финальный обработчик (5-й параметр ResponseData):
function onTokenResponse(original, json, from, to) {
var data = parseJSON(json);
var token = data.token; // Допустим, получили "xyz123"
// Передаем токен в качестве контекста (4 параметр) и имя следующего обработчика (5 параметр)
return new ResponseData("", from, to, token, "performFinalTranslate");
}
Шаг 3: Пишем финальный обработчик, который получит токен в аргументе token (вместо исходного текста) и выполнит фактический перевод:
function performFinalTranslate(token, dummy, from, to) {
var body = {
q: token, // QTranslate временно подменил параметр 'original' на переданные данные
token: token
};
// Отправляем фактический POST-запрос на перевод.
// Его результат автоматически примет стандартная функция serviceTranslateResponse
return new RequestData(
HttpMethod.POST,
"/translate",
stringifyJSON(body),
"Content-Type: application/json"
);
}
7.2 Интеграция со словарями (Capability.DICTIONARY)
Если ваш сервис поддерживает выдачу словарных статей (части речи, синонимы, транскрипция), вы можете вернуть структурированные данные. QTranslate понимает HTML-разметку или XML внутри перевода.
function serviceHeader() {
return new ServiceHeader(
650,
"MyDict",
"Словарный переводчик",
Capability.TRANSLATE | Capability.DICTIONARY
);
}
function serviceTranslateResponse(original, json, from, to) {
var data = parseJSON(json);
// Формируем красивый HTML для вкладки "Словарь" в QTranslate
var htmlResult = "<b>" + original + "</b> [" + data.transcription + "]<br/>";
htmlResult += "<span style='color:green;'>сущ.</span> " + data.translations.join(", ");
// Передаем HTML-строку. Программа отобразит её с рендером тегов
return new ResponseData(htmlResult, from, to);
}
8. 🌐 Примеры для разных типов API
8.1 Работа с локальной LLM-моделью через Ollama / LM Studio (POST-запрос с JSON)
function serviceTranslateRequest(text, from, to) {
var fromLang = (from === "auto") ? "определённый" : languageFromCode(from);
var toLang = languageFromCode(to);
var prompt = "Переведи следующий текст с языка [" + fromLang + "] на язык [" + toLang + "]. " +
"Выведи ИСКЛЮЧИТЕЛЬНО готовый перевод без каких-либо комментариев, вводных слов или кавычек:\n\n" + text;
var body = {
model: "llama3",
prompt: prompt,
system: "Вы — профессиональный синхронный переводчик.",
stream: false,
options: { temperature: 0.1 }
};
return new RequestData(
HttpMethod.POST,
"/api/generate",
stringifyJSON(body),
"Content-Type: application/json"
);
}
function serviceTranslateResponse(original, json, from, to) {
try {
var data = parseJSON(json);
var result = trimString(data.response || "");
return new ResponseData(result, from, to);
} catch (e) {
return new ResponseData("[⚠️ Ollama Error] " + e.message, from, to);
}
}
8.2 API с классической urlencoded-авторизацией (POST-запрос)
function serviceTranslateRequest(text, from, to) {
var postData = "text=" + encodePostParam(text) +
"&from=" + codeFromLanguage(from) +
"&to=" + codeFromLanguage(to) +
"&key=YOUR_API_KEY_HERE";
return new RequestData(
HttpMethod.POST,
"/v2/translate",
postData,
"Content-Type: application/x-www-form-urlencoded"
);
}
9. 📋 Чеклист и отладка перед публикацией
💡 Чеклист готовности нового сервиса:
- Вы выделили уникальный ID в диапазоне от 500 до 999.
- Никакие переменные не объявлены через
constилиlet. - В коде не вызывается метод
.trim()напрямую (заменено наtrimString()). - Вместо
JSON.stringify()используетсяstringifyJSON(). - Все потенциальные ошибки парсинга ответа обернуты в блоки
try {} catch (e) {}. - Переносы строк в финальном переводе нормализованы через
removeEmptyLines().
Где искать файлы журналов (логов) при сбоях?
Если скрипт аварийно падает, QTranslate прекращает его выполнение. Проверить ошибки синтаксиса и сетевые коды можно в логах программы:
%AppData%\QTranslate\logs\
Для детальной отладки сетевых запросов рекомендуется использовать прокси-анализаторы (например, Fiddler Classic или HTTP Debugger Pro), которые перехватывают исходящий трафик от процесса QTranslate.exe и показывают точные тела запросов и ответы серверов.