Полное руководство по разработке сервисов для QTranslate на JavaScript
Поэкспериментировал с ИИ. Скормил ему содержимое папки Services программы и он написал мануал. Размещаю его как есть, с парой мелких правок.
📘 Полное руководство по разработке сервисов для QTranslate на JavaScript
📑 Оглавление
- 1. Архитектура системы
- 2. Структура файла service.js
- 3. Обязательные функции
- 4. Опциональные функции
- 5. Встроенные объекты и константы
- 6. Вспомогательные функции
- 7. Поток выполнения запроса
- 8. Обработка ошибок и отладка
- 9. Примеры для разных типов API
- 10. Best Practices и частые ошибки
- 11. Чеклист перед публикацией
1. 🏗️ Архитектура системы
🔑 Ключевые принципы:
- Изоляция: каждый сервис — отдельная папка с
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. 🔄 Поток выполнения запроса
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 | Разборы архитектуры (китайский, но с кодом) |
| Этот мануал | Используйте как справочник при разработке |