Skip to content

Instantly share code, notes, and snippets.

@mitry
Last active April 21, 2025 23:29
Show Gist options
  • Select an option

  • Save mitry/8d1c40f7b9c7a246b9de6973fbf5a126 to your computer and use it in GitHub Desktop.

Select an option

Save mitry/8d1c40f7b9c7a246b9de6973fbf5a126 to your computer and use it in GitHub Desktop.
Умный Переводчик (Via)
// ==UserScript==
// @name Умный Переводчик (Via)
// @namespace http://tampermonkey.net/
// @version 6.2.1
// @description Фикс позиционирования над панелью браузера
// @match *://*/*
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function() {
'use strict';
const id = 'via-trans-popup';
const config = {
pressDuration: 600,
maxWordLength: 25,
source: {
url: (word, fromLang) => `https://api.mymemory.translated.net/get?q=${encodeURIComponent(word)}&langpair=${fromLang}|${fromLang === 'en' ? 'ru' : 'en'}`,
parser: res => {
try {
const data = JSON.parse(res);
return data.responseData.translatedText || '';
} catch (e) {
return '';
}
}
}
};
// Обновленные стили с фиксом позиционирования
const css = `
#${id} {
display: none;
position: fixed;
top: 20px; /* Изменено с bottom на top */
left: 10px;
right: 10px;
background: #424242;
border-radius: 15px;
padding: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 999;
font-size: 16px;
font-family: system-ui;
max-height: 70vh;
overflow-y: auto;
}
.via-trans-close {
position: absolute;
top: 5px;
right: 10px;
font-size: 24px;
color: #666;
cursor: pointer;
}
.via-trans-content {
padding-right: 30px;
}
.lang-direction {
color: #888;
font-size: 12px;
margin-bottom: 5px;
}`;
// Создаем элементы
document.head.insertAdjacentHTML('beforeend', `<style>${css}</style>`);
document.body.insertAdjacentHTML('beforeend', `
<div id="${id}">
<div class="via-trans-close">×</div>
<div class="via-trans-content"></div>
</div>
`);
const popup = document.getElementById(id);
const content = popup.querySelector('.via-trans-content');
const closeBtn = popup.querySelector('.via-trans-close');
// Обработчики событий
let longPressTimer;
document.addEventListener('touchstart', e => {
longPressTimer = setTimeout(() => {
const text = window.getSelection()
.toString()
.trim();
if (text && isValidWord(text)) {
showPopup();
translateWord(text);
}
}, config.pressDuration);
}, {
passive: true
});
document.addEventListener('touchend', () => clearTimeout(longPressTimer));
document.addEventListener('touchmove', () => clearTimeout(longPressTimer));
closeBtn.addEventListener('click', () => popup.style.display = 'none');
// Основные функции
function detectLanguage(word) {
const hasCyrillic = /[а-яё]/i.test(word);
const hasLatin = /[a-z]/i.test(word);
if (hasCyrillic && !hasLatin) return 'ru';
if (hasLatin && !hasCyrillic) return 'en';
return 'auto';
}
function isValidWord(text) {
return text.length <= config.maxWordLength &&
(/^[a-zA-Z'-]+$/.test(text) || /^[а-яёА-ЯЁ'-]+$/.test(text));
}
async function translateWord(word) {
const fromLang = detectLanguage(word);
if (fromLang === 'auto') {
content.textContent = 'Смешанный язык не поддерживается';
return;
}
content.textContent = 'Загрузка...';
try {
const translation = await fetchTranslation(word, fromLang);
const langPair = `${fromLang}→${fromLang === 'en' ? 'ru' : 'en'}`;
content.innerHTML = `
<div class="lang-direction">Направление: ${langPair}</div>
<div style="color: #1a73e8; margin-bottom: 8px">${word}</div>
<div>${translation}</div>
`;
} catch (e) {
content.textContent = 'Ошибка перевода';
}
}
function fetchTranslation(word, fromLang) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: config.source.url(word, fromLang),
timeout: 5000,
onload: res => {
const result = config.source.parser(res.responseText);
result ? resolve(result) : reject();
},
onerror: reject
});
});
}
function showPopup() {
popup.style.display = 'block';
// Прокрутка к началу попапа
popup.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment