Размещяю доработанный в Google AI Studio вариант мануала, присланный мне читателем Максом. Спасибо ему за это. Так-же размещу у себя несколько материалов от него.

📘 Разработка сервисов для QTranslate на JavaScript

Версия документации: 2026 Целевая аудитория: Разработчики расширений QTranslate Требования: JScript (ES3), понимание HTTP/API и ActiveX

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. 📋 Чеклист и отладка перед публикацией

💡 Чеклист готовности нового сервиса:

  1. Вы выделили уникальный ID в диапазоне от 500 до 999.
  2. Никакие переменные не объявлены через const или let.
  3. В коде не вызывается метод .trim() напрямую (заменено на trimString()).
  4. Вместо JSON.stringify() используется stringifyJSON().
  5. Все потенциальные ошибки парсинга ответа обернуты в блоки try {} catch (e) {}.
  6. Переносы строк в финальном переводе нормализованы через removeEmptyLines().

Где искать файлы журналов (логов) при сбоях?

Если скрипт аварийно падает, QTranslate прекращает его выполнение. Проверить ошибки синтаксиса и сетевые коды можно в логах программы:

%AppData%\QTranslate\logs\

Для детальной отладки сетевых запросов рекомендуется использовать прокси-анализаторы (например, Fiddler Classic или HTTP Debugger Pro), которые перехватывают исходящий трафик от процесса QTranslate.exe и показывают точные тела запросов и ответы серверов.