Add support for Yandex Translate API
This commit is contained in:
parent
3e60d9e4d8
commit
fc03fea00a
|
@ -16,6 +16,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
import { splitRecursive } from '../../utils.js';
|
import { splitRecursive } from '../../utils.js';
|
||||||
|
import { renderTemplateAsync } from '../../templates.js';
|
||||||
|
|
||||||
export const autoModeOptions = {
|
export const autoModeOptions = {
|
||||||
NONE: 'none',
|
NONE: 'none',
|
||||||
|
@ -340,6 +341,34 @@ async function translateProviderBing(text, lang) {
|
||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates text using the Yandex Translate API
|
||||||
|
* @param {string} text Text to translate
|
||||||
|
* @param {string} lang Target language code
|
||||||
|
* @returns {Promise<string>} Translated text
|
||||||
|
*/
|
||||||
|
async function translateProviderYandex(text, lang) {
|
||||||
|
let chunks = [];
|
||||||
|
const chunkSize = 5000;
|
||||||
|
if (text.length <= chunkSize) {
|
||||||
|
chunks.push(text);
|
||||||
|
} else {
|
||||||
|
chunks = splitRecursive(text, chunkSize);
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/translate/yandex', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({ chunks: chunks, lang: lang }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.text();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits text into chunks and translates each chunk separately
|
* Splits text into chunks and translates each chunk separately
|
||||||
* @param {string} text Text to translate
|
* @param {string} text Text to translate
|
||||||
|
@ -389,6 +418,8 @@ async function translate(text, lang) {
|
||||||
return await translateProviderOneRing(text, lang);
|
return await translateProviderOneRing(text, lang);
|
||||||
case 'bing':
|
case 'bing':
|
||||||
return await chunkedTranslate(text, lang, translateProviderBing, 1000);
|
return await chunkedTranslate(text, lang, translateProviderBing, 1000);
|
||||||
|
case 'yandex':
|
||||||
|
return await translateProviderYandex(text, lang);
|
||||||
default:
|
default:
|
||||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||||
return text;
|
return text;
|
||||||
|
@ -532,56 +563,10 @@ const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
|
||||||
|
|
||||||
window['translate'] = translate;
|
window['translate'] = translate;
|
||||||
|
|
||||||
jQuery(() => {
|
jQuery(async() => {
|
||||||
const html = `
|
const html = await renderTemplateAsync('translateIndex');
|
||||||
<div class="translation_settings">
|
|
||||||
<div class="inline-drawer">
|
|
||||||
<div class="inline-drawer-toggle inline-drawer-header">
|
|
||||||
<b>Chat Translation</b>
|
|
||||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-drawer-content">
|
|
||||||
<label for="translation_auto_mode" class="checkbox_label">Auto-mode</label>
|
|
||||||
<select id="translation_auto_mode">
|
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="responses">Translate responses</option>
|
|
||||||
<option value="inputs">Translate inputs</option>
|
|
||||||
<option value="both">Translate both</option>
|
|
||||||
</select>
|
|
||||||
<label for="translation_provider">Provider</label>
|
|
||||||
<div class="flex-container gap5px flexnowrap marginBot5">
|
|
||||||
<select id="translation_provider" name="provider" class="margin0">
|
|
||||||
<option value="libre">Libre</option>
|
|
||||||
<option value="google">Google</option>
|
|
||||||
<option value="lingva">Lingva</option>
|
|
||||||
<option value="deepl">DeepL</option>
|
|
||||||
<option value="deeplx">DeepLX</option>
|
|
||||||
<option value="bing">Bing</option>
|
|
||||||
<option value="oneringtranslator">OneRingTranslator</option>
|
|
||||||
<select>
|
|
||||||
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
|
||||||
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
|
|
||||||
</div>
|
|
||||||
<label for="translation_target_language">Target Language</label>
|
|
||||||
<select id="translation_target_language" name="target_language"></select>
|
|
||||||
<div id="translation_clear" class="menu_button">
|
|
||||||
<i class="fa-solid fa-trash-can"></i>
|
|
||||||
<span>Clear Translations</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
const buttonHtml = `
|
const buttonHtml = await renderTemplateAsync('translateButtons');
|
||||||
<div id="translate_chat" class="list-group-item flex-container flexGap5">
|
|
||||||
<div class="fa-solid fa-language extensionsMenuExtensionButton" /></div>
|
|
||||||
Translate Chat
|
|
||||||
</div>
|
|
||||||
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
|
|
||||||
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton" /></div>
|
|
||||||
Translate Input
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
$('#extensionsMenu').append(buttonHtml);
|
$('#extensionsMenu').append(buttonHtml);
|
||||||
$('#extensions_settings2').append(html);
|
$('#extensions_settings2').append(html);
|
||||||
$('#translate_chat').on('click', onTranslateChatClick);
|
$('#translate_chat').on('click', onTranslateChatClick);
|
||||||
|
@ -624,7 +609,7 @@ jQuery(() => {
|
||||||
'libre': 'http://127.0.0.1:5000/translate',
|
'libre': 'http://127.0.0.1:5000/translate',
|
||||||
'lingva': 'https://lingva.ml/api/v1',
|
'lingva': 'https://lingva.ml/api/v1',
|
||||||
'oneringtranslator': 'http://127.0.0.1:4990/translate',
|
'oneringtranslator': 'http://127.0.0.1:4990/translate',
|
||||||
'deeplx': 'http://127.0.0.1:1188/translate',
|
'deeplx': 'http://127.0.0.1:1188/translate'
|
||||||
};
|
};
|
||||||
const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`;
|
const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div id="translate_chat" class="list-group-item flex-container flexGap5">
|
||||||
|
<div class="fa-solid fa-language extensionsMenuExtensionButton" /></div>
|
||||||
|
<span data-i18n="ext_translate_btn_chat">Translate Chat</span>
|
||||||
|
</div>
|
||||||
|
<div id="translate_input_message" class="list-group-item flex-container flexGap5">
|
||||||
|
<div class="fa-solid fa-keyboard extensionsMenuExtensionButton" /></div>
|
||||||
|
<span data-i18n="ext_translate_btn_input">Translate Input</span>
|
||||||
|
</div>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<div class="translation_settings">
|
||||||
|
<div class="inline-drawer">
|
||||||
|
<div class="inline-drawer-toggle inline-drawer-header">
|
||||||
|
<b data-i18n="Chat Translation">Chat Translation</b>
|
||||||
|
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||||
|
</div>
|
||||||
|
<div class="inline-drawer-content">
|
||||||
|
<label for="translation_auto_mode" class="checkbox_label" data-i18n="ext_translate_auto_mode">Auto-mode</label>
|
||||||
|
<select id="translation_auto_mode">
|
||||||
|
<option data-i18n="ext_translate_mode_none" value="none">None</option>
|
||||||
|
<option data-i18n="ext_translate_mode_responses" value="responses">Translate responses</option>
|
||||||
|
<option data-i18n="ext_translate_mode_inputs" value="inputs">Translate inputs</option>
|
||||||
|
<option data-i18n="ext_translate_mode_both" value="both">Translate both</option>
|
||||||
|
</select>
|
||||||
|
<label data-i18n="ext_translate_mode_provider" for="translation_provider">Provider</label>
|
||||||
|
<div class="flex-container gap5px flexnowrap marginBot5">
|
||||||
|
<select id="translation_provider" name="provider" class="margin0">
|
||||||
|
<option value="libre">Libre</option>
|
||||||
|
<option value="google">Google</option>
|
||||||
|
<option value="lingva">Lingva</option>
|
||||||
|
<option value="deepl">DeepL</option>
|
||||||
|
<option value="deeplx">DeepLX</option>
|
||||||
|
<option value="bing">Bing</option>
|
||||||
|
<option value="oneringtranslator">OneRingTranslator</option>
|
||||||
|
<option value="yandex">Yandex</option>
|
||||||
|
<select>
|
||||||
|
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
||||||
|
<div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div>
|
||||||
|
</div>
|
||||||
|
<label data-i18n="ext_translate_target_lang" for="translation_target_language">Target Language</label>
|
||||||
|
<select id="translation_target_language" name="target_language"></select>
|
||||||
|
<div id="translation_clear" class="menu_button">
|
||||||
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
|
<span data-i18n="ext_translate_clear">Clear Translations</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -102,6 +102,55 @@ router.post('/google', jsonParser, async (request, response) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/yandex', jsonParser, async (request, response) => {
|
||||||
|
|
||||||
|
const chunks = request.body.chunks;
|
||||||
|
const lang = request.body.lang;
|
||||||
|
|
||||||
|
if (!chunks || !lang) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconstruct original text to log
|
||||||
|
let inputText = '';
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
params.append('text', chunk);
|
||||||
|
inputText += chunk;
|
||||||
|
}
|
||||||
|
params.append('lang', lang);
|
||||||
|
const ucid = crypto.randomUUID().replaceAll('-', '');
|
||||||
|
|
||||||
|
console.log('Input text: ' + inputText);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetch(`https://translate.yandex.net/api/v1/tr.json/translate?ucid=${ucid}&srv=android&format=text`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: params,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
timeout: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
const error = await result.message();
|
||||||
|
console.log('Yandex error: ', result.code, error);
|
||||||
|
return response.sendStatus(result.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await result.json();
|
||||||
|
const translated = json.text.join();
|
||||||
|
console.log('Translated text: ' + translated);
|
||||||
|
|
||||||
|
return response.send(translated);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Translation error: ' + error.message);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/lingva', jsonParser, async (request, response) => {
|
router.post('/lingva', jsonParser, async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const baseUrl = readSecret(request.user.directories, SECRET_KEYS.LINGVA_URL);
|
const baseUrl = readSecret(request.user.directories, SECRET_KEYS.LINGVA_URL);
|
||||||
|
|
Loading…
Reference in New Issue