Поэкспериментировал с ИИ. Скормил ему содержимое папки Services программы и он написал мануал. Размещаю его как есть, с парой мелких правок.

 


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

Версия документации: 2026

Целевая аудитория: разработчики, желающие добавить поддержку новых переводчиков в QTranslate

Требования: базовое знание JavaScript, понимание HTTP/API

1. 🏗️ Архитектура системы

QTranslate (C++ ядро) Вызывает функции из service.js service.js (ваш код) Возвращает RequestData/ResponseData Внешний API (Google, Yandex, Ollama...) Ответ API

🔑 Ключевые принципы:

  • Изоляция: каждый сервис — отдельная папка с service.js
  • Синхронность: все функции выполняются синхронно, асинхронность обеспечивается ядром
  • Безопасность: код выполняется в песочнице, нет доступа к файловой системе
  • Совместимость: интерфейс стабилен, обратная совместимость поддерживается

2. 📁 Структура файла service.js

Services/
└── MyService/
    └── service.js  ← единственный обязательный файл

📋 Минимальный шаблон:

// ============================================================================
// НАЗВАНИЕ СЕРВИСА для QTranslate
// Краткое описание
// ============================================================================

// 🔧 Настройки (вверху файла для удобства)
var SERVICE_URL = "https://api.example.com";
var SERVICE_ID = 600;  // Уникальный ID (500-999 для пользовательских)

// ============================================================================

function serviceHeader() {
    return new ServiceHeader(
        SERVICE_ID,
        "MyService",
        "Описание" + Const.NL2 + SERVICE_URL + Const.NL2 + "© 2026",
        Capability.TRANSLATE
    );
}

function serviceHost() { return SERVICE_URL; }
function serviceLink(text, from, to) { return SERVICE_URL; }

function serviceTranslateRequest(text, from, to) {
    // Формируем запрос
    return new RequestData(HttpMethod.GET, "/translate?q=" + encodeGetParam(text), null);
}

function serviceTranslateResponse(original, json, from, to) {
    // Парсим ответ
    var data = parseJSON(json);
    return new ResponseData(data.result, from, to, "");
}

3. ✅ Обязательные функции

3.1 serviceHeader()ServiceHeader

function serviceHeader() {
    return new ServiceHeader(
        id,              // number: уникальный ID (1-999)
        name,            // string: название в интерфейсе
        description,     // string: описание (поддерживает Const.NL, Const.NL2)
        capabilities     // bitmask: Capability.TRANSLATE|DETECT_LANGUAGE|LISTEN
    );
}
ПараметрТипОписаниеПример
id number Уникальный идентификатор 600
name string Отображаемое имя "MyTranslate"
description string Подробное описание (мультистрока) "API v2" + Const.NL2 + "© Company"
capabilities bitmask Возможности сервиса Capability.TRANSLATE | Capability.DETECT_LANGUAGE

3.2 serviceHost(from, to, text)string

function serviceHost(from, to, text) {
    // Возвращает базовый URL API
    // Параметры можно использовать для динамического выбора домена
    return "https://api.myservice.com";
}

3.3 serviceLink(text, from, to)string

function serviceLink(text, from, to) {
    // Возвращает ссылку на веб-версию сервиса
    // Используется для кнопки "Открыть в браузере"
    if (!text) return "https://myservice.com";
    
    from = isLanguage(from) ? codeFromLanguage(from) : "auto";
    to = isLanguage(to) ? codeFromLanguage(to) : "en";
    
    return "https://myservice.com/translate?text=" + encodeGetParam(text) + 
           "&sl=" + from + "&tl=" + to;
}

3.4 serviceTranslateRequest(text, from, to)RequestData

function serviceTranslateRequest(text, from, to) {
    // 1. Ограничиваем длину текста
    text = limitSource(text, 5000);
    
    // 2. Нормализуем языковые коды
    from = (from === "auto" || !isLanguage(from)) ? "auto" : codeFromLanguage(from);
    to = isLanguage(to) ? codeFromLanguage(to) : "en";
    
    // 3. Формируем запрос (пример GET)
    var url = "/api/translate?source=" + from + "&target=" + to + 
              "&q=" + encodeGetParam(text);
    
    // 4. Возвращаем объект запроса
    return new RequestData(
        HttpMethod.GET,   // или HttpMethod.POST
        url,              // путь или полный URL
        null,             // postData для GET = null
        null,             // заголовки (опционально)
        null,             // тип ответа (опционально)
        null              // callback для JSONP (опционально)
    );
}

3.5 serviceTranslateResponse(original, json, from, to)ResponseData

function serviceTranslateResponse(original, json, from, to) {
    try {
        // 1. Парсим JSON
        var data = parseJSON(json);
        if (!data || !data.translation) {
            throw new Error("Invalid response format");
        }
        
        // 2. Извлекаем перевод
        var translated = data.translation;
        
        // 3. Пост-обработка (очистка, форматирование)
        translated = translated.trim();
        
        // 4. Возвращаем результат
        return new ResponseData(
            translated,  // string: переведённый текст
            from,        // string: код исходного языка
            to,          // string: код целевого языка
            ""           // string: доп. информация (словарь, транслит и т.п.)
        );
        
    } catch (e) {
        // Обработка ошибок
        return new ResponseData("[Error] " + e.message, from, to, "");
    }
}

4. 🔁 Опциональные функции

4.1 Автоопределение языка

// Запрос на определение языка
function serviceDetectLanguageRequest(text) {
    text = limitSource(text, 500);  // Короткий текст для скорости
    return new RequestData(
        HttpMethod.POST,
        "/detect",
        "q=" + encodeGetParam(text),
        "Content-Type: application/x-www-form-urlencoded"
    );
}

// Парсинг ответа
function serviceDetectLanguageResponse(json) {
    try {
        var data = parseJSON(json);
        var code = (data.language || "").toLowerCase();
        return isLanguage(code) ? code : UNKNOWN_LANGUAGE;
    } catch (e) {
        return UNKNOWN_LANGUAGE;
    }
}
⚠️ Важно: Не забудьте добавить Capability.DETECT_LANGUAGE в serviceHeader()!

4.2 Озвучка (Text-to-Speech)

function serviceListenRequest(text, lang, slow) {
    // slow = true → медленная скорость воспроизведения
    lang = isLanguage(lang) ? codeFromLanguage(lang) : "en";
    
    return new RequestData(
        HttpMethod.GET,
        "/tts?text=" + encodeGetParam(text) + "&lang=" + lang + 
        (slow ? "&speed=0.5" : ""),
        null
    );
}
⚠️ Важно: Добавьте Capability.LISTEN в serviceHeader()!

4.3 Динамический список языков

// Запрос списка поддерживаемых языков
function serviceLanguagesRequest() {
    return new RequestData(HttpMethod.GET, "/languages", null);
}

// Парсинг ответа
function serviceLanguagesResponse(json) {
    try {
        var data = parseJSON(json);
        if (!Array.isArray(data)) return null;
        
        // Формат: [-1, "auto", "en", "ru", ...]
        var codes = [-1, "auto"];
        for (var i = 0; i < data.length; i++) {
            if (data[i].code) codes.push(data[i].code.toLowerCase());
        }
        return codes;
    } catch (e) {
        return null;  // При ошибке используется стандартный список
    }
}

5. 🧰 Встроенные объекты и константы

5.1 Конструкторы

ServiceHeader(id, name, description, capabilities)

new ServiceHeader(
    600,  // ID
    "MyService",  // Название
    "Описание" + Const.NL2 + "© 2026",  // Описание
    Capability.TRANSLATE | Capability.DETECT_LANGUAGE  // Возможности
)

RequestData(method, url, postData, headers, responseType, callbackName)

new RequestData(
    HttpMethod.POST,           // Метод
    "/api/translate",          // URL или путь
    "q=текст&source=ru",       // Тело POST-запроса
    "Content-Type: application/x-www-form-urlencoded",  // Заголовки
    null,                      // Тип ответа (обычно null)
    null                       // Callback для JSONP
)

ResponseData(translated, sourceLang, targetLang, extra, type)

new ResponseData(
    "Привет",      // Переведённый текст
    "en",          // Исходный язык
    "ru",          // Целевой язык
    "Hello",       // Доп. инфо (транслит, словарь)
    null           // Тип: "dictionaryRequest" для словарных запросов
)

5.2 Константы

// Возможности сервиса (битовая маска)
Capability.TRANSLATE          // 1
Capability.DETECT_LANGUAGE    // 2
Capability.LISTEN             // 4

// HTTP-методы
HttpMethod.GET
HttpMethod.POST

// Строковые константы
Const.NL        // "\n"
Const.NL2       // "\n\n"
Const.MAX_URI_LEN  // ~2000

// Языковые константы
UNKNOWN_LANGUAGE      // "-1"
AUTO_DETECT_LANGUAGE  // "auto"

5.3 Глобальные объекты

Options.PreferredDomain    // Предпочтительный домен
Options.LanguageCode       // Код языка интерфейса
Options.GoogleTkk          // Токен для Google (если нужен)

SupportedLanguages         // Массив кодов: [-1, "auto", "en", "ru", ...]

6. 🛠️ Вспомогательные функции

Работа с языками

isLanguage(code)                    // true если код валиден
codeFromLanguage(langName)          // "Russian" → "ru"
languageFromCode(code)              // "ru" → "Russian"
getSourceLanguage(json)             // Извлечение языка из авто-определения

Кодирование и парсинг

encodeGetParam(str)                 // Для query string: ?q=...
encodeUriParam(str)                 // Для пути URL: /translate/...
encodePostParam(str)                // Для тела POST-запроса
parseJSON(str)                      // Безопасный JSON.parse

Утилиты

format(template, ...args)           // "#{0}/{1}" → "a/b"
limitSource(text, maxLen)           // Обрезка текста под лимиты API

7. 🔄 Поток выполнения запроса

1 Пользователь нажимает "Перевести" 2 QTranslate вызывает: serviceTranslateRequest(text, from, to) 3 Ваш код возвращает RequestData (метод, URL, данные, заголовки) 4 QTranslate выполняет HTTP-запрос к внешнему API 5 Получен ответ (JSON/текст) QTranslate вызывает serviceTranslateResponse() 6 Ваш код возвращает ResponseData с переведённым текстом 7 QTranslate отображает результат пользователю

8. 🐛 Обработка ошибок и отладка

8.1 Шаблоны обработки ошибок

function serviceTranslateResponse(original, json, from, to) {
    try {
        var data = parseJSON(json);
        // ... ваша логика ...
        return new ResponseData(result, from, to, "");
        
    } catch (e) {
        // Вариант 1: Строгий режим — показать ошибку
        return new ResponseData("[ERROR] " + e.message, from, to, "");
        
        // Вариант 2: Мягкий режим — вернуть исходный текст
        // return new ResponseData(original + "\n\n[⚠️ " + e.message + "]", from, to, "");
    }
}

8.2 Отладочные техники

// 1. Логирование в поле "extra"
var extra = "[DEBUG] URL: " + serviceHost() + "/api";
return new ResponseData(result, from, to, extra);

// 2. Проверка структуры ответа
if (typeof data !== "object") {
    throw new Error("Expected object, got: " + typeof data);
}

// 3. Тестовый вывод через parseJSON
var debug = parseJSON(json);
if (debug && debug.error) {
    throw new Error("API Error: " + debug.error.message);
}

8.3 Где смотреть логи

ИсточникПуть / Способ
QTranslate логи %AppData%\QTranslate\logs\
Консоль отладки Включите DEBUG_MODE в коде сервиса
Сетевые запросы Process Monitor, Fiddler, браузерные DevTools
Ответы API В поле extra через JSON.stringify(data)

9. 🌐 Примеры для разных типов API

9.1 REST API с JSON (пример: LibreTranslate)

function serviceTranslateRequest(text, from, to) {
    text = limitSource(text, 5000);
    from = (from === "auto") ? "auto" : codeFromLanguage(from);
    to = codeFromLanguage(to);
    
    var body = {
        q: text,
        source: from,
        target: to,
        format: "text"
    };
    
    return new RequestData(
        HttpMethod.POST,
        "/translate",
        JSON.stringify(body),
        "Content-Type: application/json"
    );
}

function serviceTranslateResponse(original, json, from, to) {
    var data = parseJSON(json);
    // LibreTranslate может вернуть строку или массив абзацев
    var result = Array.isArray(data.translatedText) 
        ? data.translatedText.join(Const.NL) 
        : data.translatedText;
    return new ResponseData(result, from, to, "");
}

9.2 API с form-urlencoded (пример: старые сервисы)

function serviceTranslateRequest(text, from, to) {
    var postData = "text=" + encodePostParam(text) + 
                   "&from=" + codeFromLanguage(from) + 
                   "&to=" + codeFromLanguage(to) +
                   "&format=plain";
    
    return new RequestData(
        HttpMethod.POST,
        "/api/v2/translate",
        postData,
        "Content-Type: application/x-www-form-urlencoded"
    );
}

9.3 API с заголовками авторизации

function serviceTranslateRequest(text, from, to) {
    var headers = "Content-Type: application/json\r\n" +
                  "Authorization: Bearer YOUR_API_KEY\r\n" +
                  "X-Custom-Header: value";
    
    return new RequestData(
        HttpMethod.POST,
        "/v1/translate",
        JSON.stringify({q: text, sl: from, tl: to}),
        headers
    );
}

9.4 JSONP API (устаревший, но встречается)

function serviceTranslateRequest(text, from, to) {
    return new RequestData(
        HttpMethod.GET,
        "/translate?callback=handleResponse&q=" + encodeGetParam(text),
        null,
        null,
        null,
        "handleResponse"  // Имя callback-функции
    );
}

9.5 ИИ-модели с промптами (Ollama / LM Studio)

function serviceTranslateRequest(text, from, to) {
    var fromLang = (from === "auto") ? "определённый" : languageFromCode(from);
    var toLang = languageFromCode(to);
    
    var prompt = "Переведи с " + fromLang + " на " + toLang + ": " + text;
    
    var body = {
        model: "llama3",
        prompt: prompt,
        system: "Вы — профессиональный переводчик. Возвращайте только перевод.",
        stream: false,
        options: { temperature: 0.1, num_predict: 512 }
    };
    
    return new RequestData(
        HttpMethod.POST,
        "/api/generate",
        JSON.stringify(body),
        "Content-Type: application/json"
    );
}

10. ✅ Best Practices и частые ошибки

🔹 Что делать

ПрактикаПримерПочему
Ограничивайте длину текста text = limitSource(text, 5000); Избегайте ошибок 413/400
Нормализуйте переносы text = text.replace(/\r\n/g, "\n"); Консистентность между ОС
Используйте Const.NL desc = "Line1" + Const.NL + "Line2"; Кроссплатформенные переносы
Обрабатывайте массивы в ответах if (Array.isArray(x)) x.join(Const.NL); LibreTranslate и др. возвращают массивы
Добавляйте таймауты в промпты Указывайте num_predict, max_tokens Предотвращение зависаний
Тестируйте обратный перевод EN→RU→EN должен давать исходник Контроль качества

🔸 Чего избегать

ОшибкаПоследствиеИсправление
.trim() без проверки Потеря абзацев в начале/конце replace(/^\n+|\n+$/g, "")
Жёсткий список языков Неактуальные/отсутствующие языки Используйте serviceLanguagesRequest
Игнорирование from/to Неправильный перевод при смене направления Всегда используйте параметры в промпте
Отсутствие обработки ошибок Краш сервиса при сбое API try/catch + возврат ResponseData с ошибкой
Дублирование SERVICE_ID Конфликты, сервис не загружается Уникальный ID в диапазоне 500-999

11. 📋 Чеклист перед публикацией










🎁 Бонус: Шаблон для копирования

// ============================================================================
// {{SERVICE_NAME}} for QTranslate
// {{DESCRIPTION}}
// ============================================================================
// Автор: {{ВАШЕ_ИМЯ}}
// Дата: {{ДАТА}}
// Документация API: {{ССЫЛКА}}
// ============================================================================

// 🔧 НАСТРОЙКИ
var SERVICE_URL = "{{API_BASE_URL}}";
var SERVICE_ID = {{600}};  // Уникальный ID
var MAX_LENGTH = 5000;

// ============================================================================

function serviceHeader() {
    return new ServiceHeader(
        SERVICE_ID,
        "{{SERVICE_NAME}}",
        "{{DESCRIPTION}}" + Const.NL2 + SERVICE_URL + Const.NL2 + "© {{YEAR}}",
        Capability.TRANSLATE {{| Capability.DETECT_LANGUAGE}}
    );
}

function serviceHost() { return SERVICE_URL; }
function serviceLink(text, from, to) { return SERVICE_URL; }

function serviceTranslateRequest(text, from, to) {
    text = limitSource(text, MAX_LENGTH);
    text = text.replace(/\r\n/g, "\n");
    
    from = (from === "auto" || !isLanguage(from)) ? "auto" : codeFromLanguage(from);
    to = isLanguage(to) ? codeFromLanguage(to) : "en";
    
    // {{АДАПТИРУЙТЕ ПОД ВАШ API}}
    var url = "/endpoint?source=" + from + "&target=" + to + "&q=" + encodeGetParam(text);
    
    return new RequestData(HttpMethod.GET, url, null, null, null, null);
}

function serviceTranslateResponse(original, json, from, to) {
    try {
        var data = parseJSON(json);
        if (!data || !data.result) throw new Error("Invalid response");
        
        var result = data.result;
        if (Array.isArray(result)) result = result.join(Const.NL);
        result = result.replace(/^\n+|\n+$/g, "");
        
        return new ResponseData(result, from, to, "");
    } catch (e) {
        return new ResponseData(original + "\n\n[⚠️ {{SERVICE_NAME}}: " + e.message + "]", from, to, "");
    }
}

// {{ОПЦИОНАЛЬНО: serviceDetectLanguageRequest/Response}}
// {{ОПЦИОНАЛЬНО: serviceListenRequest}}
// {{ОПЦИОНАЛЬНО: serviceLanguagesRequest/Response}}
💡 Совет: Храните шаблоны и готовые сервисы в Git-репозитории — это упростит поддержку и обновление.

🆘 Где искать помощь

РесурсОписание
Русскоязычный форум QTranslate Документация, ссылки для скачивания.
IP России блокированы, нужен VPN.
Форум 52pojie.cn Разборы архитектуры (китайский, но с кодом)
Этот мануал Используйте как справочник при разработке

Удачной разработки! 🚀

Если остались вопросы — присылайте код, помогу отладить.

© 2026 Руководство по разработке сервисов для QTranslate