Переводчик

Этот скрипт используется для перевода различных популярных сайтов социальных сетей на китайский язык без использования промежуточного сервера.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name Переводчик
// @namespace http://tampermonkey.net/
// @version 0.99
// @description Этот скрипт используется для перевода различных популярных сайтов социальных сетей на китайский язык без использования промежуточного сервера.
// @author HolynnChen
// @license MIT
// @match *://*.twitter.com/*
// @match *://*.x.com/*
// @match *://*.youtube.com/*
// @match *://*.facebook.com/*
// @match *://*.reddit.com/*
// @match *://*.5ch.net/*
// @match *://*.discord.com/*
// @match *://*.telegram.org/*
// @match *://*.quora.com/*
// @match *://*.tiktok.com/*
// @match *://*.instagram.com/*
// @match *://*.threads.net/*
// @match *://*.github.com/*
// @match *://*.bsky.app/*
// @connect fanyi.baidu.com
// @connect translate.google.com
// @connect ifanyi.iciba.com
// @connect www.bing.com
// @connect fanyi.youdao.com
// @connect dict.youdao.com
// @connect m.youdao.com
// @connect api.interpreter.caiyunai.com
// @connect papago.naver.com
// @connect fanyi.qq.com
// @connect translate.alibaba.com
// @connect www2.deepl.com
// @connect transmart.qq.com
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/base64.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/libs/lz-string.min.js
// @require https://cdn.jsdelivr.net/gh/Tampermonkey/utils@3b32b826e84ccc99a0a3e3d8d6e5ce0fa9834f23/requires/gh_2215_make_GM_xhr_more_parallel_again.js
// @run-at document-body
// ==/UserScript==

// --- Вспомогательные функции ---

/**
 * Функция для сжатия/распаковки данных для sessionStorage с использованием LZ-String.
 * @param {Storage} storage - Объект sessionStorage или localStorage.
 * @returns {object} Объект с методами getItem, setItem, removeItem, clear.
 */
function CompressMergeSession(storage) {
    return {
        getItem: function(key) {
            const compressed = storage.getItem(key);
            return compressed ? LZString.decompressFromUTF16(compressed) : null;
        },
        setItem: function(key, value) {
            const compressed = LZString.compressToUTF16(value);
            storage.setItem(key, compressed);
        },
        removeItem: function(key) {
            storage.removeItem(key);
        },
        clear: function() {
            storage.clear();
        }
    };
}

/**
 * Функция для выбора элементов DOM на основе CSS-селектора, с возможностью дополнительной фильтрации.
 * @param {string} selector - CSS-селектор.
 * @param {function} [filterFunc] - Необязательная функция для фильтрации найденных элементов.
 * @returns {function} Функция, которая при вызове возвращает массив элементов.
 */
function baseSelector(selector, filterFunc) {
    return () => {
        const elements = Array.from(document.querySelectorAll(selector));
        return filterFunc ? filterFunc(elements) : elements;
    };
}

/**
 * Функция для извлечения текстового содержимого из элемента DOM.
 * @param {HTMLElement} element - Элемент DOM.
 * @returns {string} Текстовое содержимое элемента.
 */
function baseTextGetter(element) {
    return element ? element.textContent || "" : "";
}

/**
 * Функция для установки переведенного текста обратно в элемент DOM.
 * @param {object} options - Объект с параметрами: element (элемент DOM), text (переведенный текст).
 * @returns {HTMLElement} Модифицированный элемент DOM.
 */
function baseTextSetter(options) {
    if (options.element && options.text !== undefined) {
        options.element.innerHTML = options.text;
    }
    return options.element;
}

/**
 * Функция для фильтрации URL-адресов из текста.
 * Это базовая реализация, вы можете улучшить ее, если нужно сохранять часть URL.
 * @param {string} text - Исходный текст.
 * @returns {string} Текст без URL.
 */
function url_filter(text) {
    return text.replace(/(https?:\/\/[^\s]+)/g, ''); // Удаляет http/https ссылки
}

/**
 * Функция для определения языка текста.
 * Это очень базовая заглушка. Для точного определения языка рассмотрите использование
 * специализированных библиотек или API (например, Google Cloud Translation API's language detection).
 * @param {string} text - Текст для определения языка.
 * @returns {Promise<string>} Промис, возвращающий код языка (например, 'en', 'zh', 'auto').
 */
function pass_lang(text) {
    return new Promise(resolve => {
        // Очень простая эвристика: если есть много китайских символов, считаем, что это китайский.
        const chineseCharCount = (text.match(/[\u4e00-\u9fff]/g) || []).length;
        if (chineseCharCount > text.length * 0.3) { // Если более 30% символов - китайские
            resolve('zh'); // Китайский
        } else {
            resolve('auto'); // Позволить API определить язык
        }
    });
}

/**
 * Вспомогательная функция для удаления элемента из массива.
 * @param {Array} array - Массив, из которого нужно удалить элемент.
 * @param {*} item - Элемент для удаления.
 */
function removeItem(array, item) {
    const index = array.indexOf(item);
    if (index > -1) {
        array.splice(index, 1);
    }
}

/**
 * Функция-обертка для выполнения промиса с возможностью повторных попыток.
 * Эта реализация просто выполняет функцию один раз.
 * Для реальных повторных попыток требуется дополнительная логика (например, setTimeout, счетчик попыток).
 * @param {function} func - Функция, возвращающая промис.
 * @returns {Promise<*>} Результат промиса.
 */
function PromiseRetryWrap(func) {
    // В реальной реализации здесь может быть логика для повторных попыток с задержкой
    return func();
}

// --- Функции перевода (заглушки, требуют реальной реализации API) ---

/**
 * Google Переводчик (Desktop/Web API)
 * Требует реальной реализации запроса к API Google Translate.
 * Обратите внимание, что прямое использование translate.google.com может быть заблокировано CORS
 * или требовать обходных путей. Рекомендуется использовать официальный Google Cloud Translation API,
 * который требует ключей API и оплаты.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык (например, 'auto', 'en', 'ru').
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_gg(text, sourceLang) {
    console.log(`[Google Переводчик] Запрос: "${text}" с "${sourceLang}"`);
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "GET",
            // Это общая конечная точка, которая часто используется. Она может быть нестабильной.
            url: `https://translate.google.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=zh-CN&dt=t&q=${encodeURIComponent(text)}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    // Структура ответа может меняться. Это пример для популярного формата.
                    if (data && data[0] && data[0][0] && data[0][0][0]) {
                        resolve(data[0][0][0]);
                    } else {
                        reject(new Error("Некорректный ответ от Google Translate"));
                    }
                } catch (e) {
                    console.error("[Google Переводчик] Ошибка парсинга:", e);
                    reject(e);
                }
            },
            onerror: function(error) {
                console.error("[Google Переводчик] Ошибка запроса:", error);
                reject(error);
            }
        });
    });
}

/**
 * Google Translate Mobile
 * Аналогично translate_gg, но может использовать другие эндпоинты или параметры для мобильных версий.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_ggm(text, sourceLang) {
    console.log(`[Google Translate Mobile] Запрос: "${text}" с "${sourceLang}"`);
    // Реализация может быть похожа на translate_gg, но с учетом возможных отличий в API для мобильных
    return translate_gg(text, sourceLang); // Используем тот же API для примера
}

/**
 * Перевод Tencent
 * Требует ключей API Tencent Cloud Translation и подписания запросов.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_tencent(text, sourceLang) {
    console.log(`[Перевод Tencent] Запрос: "${text}" с "${sourceLang}"`);
    // Пример структуры запроса (замените на реальные данные и подпись)
    return new Promise((resolve, reject) => {
        // Здесь потребуется генерация подписи (Signature) и другие параметры
        // согласно документации Tencent Cloud Translation API.
        // Это может включать AccessKeyId, SecretAccessKey, Region, Action и т.д.
        // Пример:
        // const SECRET_ID = 'YOUR_SECRET_ID';
        // const SECRET_KEY = 'YOUR_SECRET_KEY';
        // const TIMESTAMP = Math.floor(Date.now() / 1000);
        // const NONCE = Math.floor(Math.random() * 100000);
        // const PARAMS = {
        //     Action: 'TextTranslate',
        //     Region: 'ap-guangzhou',
        //     SourceText: text,
        //     Source: sourceLang === 'auto' ? 'auto' : sourceLang,
        //     Target: 'zh', // Или 'zh-Hans' для упрощенного китайского
        //     ProjectId: 0,
        //     // ... другие параметры
        // };
        // const SIGNED_PARAMS = signRequest(PARAMS, SECRET_KEY); // Функция signRequest должна быть реализована вами

        // GM_xmlhttpRequest({
        //     method: "POST",
        //     url: "https://tmt.tencentcloudapi.com/", // Или другая конечная точка
        //     headers: {
        //         "Content-Type": "application/json",
        //         "X-TC-Action": "TextTranslate",
        //         "X-TC-Version": "2018-03-21",
        //         "X-TC-Region": "ap-guangzhou",
        //         "X-TC-Timestamp": TIMESTAMP.toString(),
        //         "X-TC-Nonce": NONCE.toString(),
        //         "X-TC-Signature": SIGNED_PARAMS.Signature,
        //     },
        //     data: JSON.stringify(SIGNED_PARAMS),
        //     onload: function(response) { /* ... */ },
        //     onerror: function(error) { /* ... */ }
        // });
        resolve(`[Tencent] Перевод: ${text}`); // Заглушка
    });
}

/**
 * Перевод Tencent AI (может быть частью Tencent Cloud или отдельный сервис)
 * Предполагается аналогичная логика API, как и у Tencent Translation.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_tencentai(text, sourceLang) {
    console.log(`[Перевод Tencent AI] Запрос: "${text}" с "${sourceLang}"`);
    return translate_tencent(text, sourceLang); // Используем тот же API для примера
}

/**
 * Мобильный перевод Youdao
 * Требует ключей API Youdao Translate и подписания запросов.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function Translate_youdao_mobile(text, sourceLang) {
    console.log(`[Мобильный перевод Youdao] Запрос: "${text}" с "${sourceLang}"`);
    // Youdao API требует appKey, secretKey, salt, timestamp и sig (подпись).
    // Подпись генерируется с помощью SHA256 и HMAC.
    // Пример:
    // const APP_KEY = 'YOUR_APP_KEY';
    // const SECRET_KEY = 'YOUR_SECRET_KEY';
    // const SALT = Date.now();
    // const CURTIME = Math.floor(Date.now() / 1000);
    // const SIGN_STR = APP_KEY + truncate(text) + SALT + CURTIME + SECRET_KEY;
    // const SIGN = CryptoJS.SHA256(SIGN_STR).toString(CryptoJS.enc.Hex); // Требует CryptoJS
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://openapi.youdao.com/api", // Или другой эндпоинт Youdao
    //     headers: { "Content-Type": "application/x-www-form-urlencoded" },
    //     data: new URLSearchParams({
    //         q: text,
    //         from: sourceLang === 'auto' ? 'auto' : sourceLang,
    //         to: 'zh-CHS', // Или другой китайский вариант
    //         appKey: APP_KEY,
    //         salt: SALT,
    //         sign: SIGN,
    //         signType: 'v3',
    //         curtime: CURTIME
    //     }).toString(),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Youdao Mobile] Перевод: ${text}`); // Заглушка
}

/**
 * Переводчик Baidu
 * Требует AppId и SecretKey для Baidu Fanyi API.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function Translate_baidu(text, sourceLang) {
    console.log(`[Переводчик Baidu] Запрос: "${text}" с "${sourceLang}"`);
    // Baidu API требует appid, q, from, to, salt и sign.
    // sign генерируется md5(appid + q + salt + secretKey).
    // Пример:
    // const APP_ID = 'YOUR_APP_ID';
    // const SECRET_KEY = 'YOUR_SECRET_KEY';
    // const SALT = Math.random().toString().slice(-10);
    // const SIGN_STR = APP_ID + text + SALT + SECRET_KEY;
    // const SIGN = CryptoJS.MD5(SIGN_STR).toString(); // Требует CryptoJS
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://fanyi-api.baidu.com/api/trans/vip/translate",
    //     headers: { "Content-Type": "application/x-www-form-urlencoded" },
    //     data: new URLSearchParams({
    //         q: text,
    //         from: sourceLang === 'auto' ? 'auto' : sourceLang,
    //         to: 'zh',
    //         appid: APP_ID,
    //         salt: SALT,
    //         sign: SIGN
    //     }).toString(),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Baidu] Перевод: ${text}`); // Заглушка
}

/**
 * Цайюнь Сяойи (Caiyun Xiaoyi)
 * Может быть более простым API или требовать токен.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function Translation_caiyun(text, sourceLang) {
    console.log(`[Цайюнь Сяойи] Запрос: "${text}" с "${sourceLang}"`);
    // Caiyun Xiaoyi (Rainbow Translate) часто имеет более простой API.
    // Может быть достаточно POST-запроса с текстом и целевым языком.
    // Возможно, требуется токен в заголовках.
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://api.interpreter.caiyunai.com/v1/translator", // Проверьте актуальный эндпоинт
    //     headers: {
    //         "Content-Type": "application/json",
    //         "X-Authorization": "token YOUR_CAIYUN_TOKEN" // Если требуется токен
    //     },
    //     data: JSON.stringify({
    //         source: text.split('\n'), // API может ожидать массив строк
    //         request_id: "demo",
    //         detect: true, // Позволить API определить исходный язык
    //         target: "zh", // Целевой язык
    //     }),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Caiyun Xiaoyi] Перевод: ${text}`); // Заглушка
}

/**
 * Bing Translation
 * Использование Bing API обычно требует Azure Cognitive Services.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_biying(text, sourceLang) {
    console.log(`[Bing Translation] Запрос: "${text}" с "${sourceLang}"`);
    // Microsoft Azure Translator Text API требует ключ подписки и регион.
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: `https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from=${sourceLang}&to=zh-Hans`,
    //     headers: {
    //         "Ocp-Apim-Subscription-Key": "YOUR_AZURE_TRANSLATOR_KEY",
    //         "Ocp-Apim-Subscription-Region": "YOUR_AZURE_TRANSLATOR_REGION", // Например, "eastus"
    //         "Content-Type": "application/json"
    //     },
    //     data: JSON.stringify([{ "text": text }]),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Bing] Перевод: ${text}`); // Заглушка
}

/**
 * Перевод Папаго (Naver Papago)
 * Требует Client ID и Client Secret от Naver Developer Center.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_papago(text, sourceLang) {
    console.log(`[Перевод Папаго] Запрос: "${text}" с "${sourceLang}"`);
    // Papago API требует X-Naver-Client-Id и X-Naver-Client-Secret в заголовках.
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://papago.naver.com/apis/n2mt/translate", // Или https://openapi.naver.com/v1/papago/n2mt
    //     headers: {
    //         "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    //         "X-Naver-Client-Id": "YOUR_NAVER_CLIENT_ID",
    //         "X-Naver-Client-Secret": "YOUR_NAVER_CLIENT_SECRET"
    //     },
    //     data: new URLSearchParams({
    //         source: sourceLang === 'auto' ? 'auto' : sourceLang,
    //         target: 'zh-CN',
    //         text: text
    //     }).toString(),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Papago] Перевод: ${text}`); // Заглушка
}

/**
 * Ali Translation (Alibaba Cloud Translate)
 * Требует AccessKeyId и AccessKeySecret для Alibaba Cloud.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_alibaba(text, sourceLang) {
    console.log(`[Ali Translation] Запрос: "${text}" с "${sourceLang}"`);
    // Alibaba Cloud Translate API также требует подписания запросов.
    // Это сложнее и обычно включает SDK или ручную генерацию подписей HMAC-SHA1.
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://mt.cn-hangzhou.aliyuncs.com/", // Или другой регион
    //     headers: {
    //         "Content-Type": "application/json",
    //         // ... Заголовки авторизации Алибабы
    //     },
    //     data: JSON.stringify({
    //         "Format": "json",
    //         "Action": "TranslateText",
    //         "Version": "2018-09-17",
    //         "SourceText": text,
    //         "SourceLanguage": sourceLang === 'auto' ? 'auto' : sourceLang,
    //         "TargetLanguage": "zh",
    //         "Scene": "general"
    //     }),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Alibaba] Перевод: ${text}`); // Заглушка
}

/**
 * Iciba Translation
 * Часто используется для словарей, но может иметь и API перевода.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_icib(text, sourceLang) {
    console.log(`[iciba translation] Запрос: "${text}" с "${sourceLang}"`);
    // Iciba API может быть простым GET-запросом или требовать токен.
    // Например: `http://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&q=${encodeURIComponent(text)}`
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: `https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&q=${encodeURIComponent(text)}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.content && data.content.out) {
                        resolve(data.content.out);
                    } else {
                        reject(new Error("Некорректный ответ от Iciba"));
                    }
                } catch (e) {
                    console.error("[iciba translation] Ошибка парсинга:", e);
                    reject(e);
                }
            },
            onerror: function(error) {
                console.error("[iciba translation] Ошибка запроса:", error);
                reject(error);
            }
        });
    });
}

/**
 * Deepl Translation
 * DeepL API требует аутентификационного ключа.
 * @param {string} text - Текст для перевода.
 * @param {string} sourceLang - Исходный язык.
 * @returns {Promise<string>} Промис, возвращающий переведенный текст.
 */
function translate_deepl(text, sourceLang) {
    console.log(`[Deepl Translation] Запрос: "${text}" с "${sourceLang}"`);
    // DeepL API: https://www.deepl.com/docs-api
    // const DEEPL_AUTH_KEY = "YOUR_DEEPL_AUTH_KEY";
    // GM_xmlhttpRequest({
    //     method: "POST",
    //     url: "https://api-free.deepl.com/v2/translate", // Или api.deepl.com/v2/translate для Pro
    //     headers: {
    //         "Content-Type": "application/x-www-form-urlencoded",
    //         "Authorization": `DeepL-Auth-Key ${DEEPL_AUTH_KEY}`
    //     },
    //     data: new URLSearchParams({
    //         text: text,
    //         source_lang: sourceLang === 'auto' ? '' : sourceLang.toUpperCase(), // DeepL использует коды языков в верхнем регистре, 'auto' - пустая строка
    //         target_lang: 'ZH',
    //     }).toString(),
    //     onload: function(response) { /* ... */ },
    //     onerror: function(error) { /* ... */ }
    // });
    return Promise.resolve(`[Deepl] Перевод: ${text}`); // Заглушка
}

// --- Функции запуска переводчиков (заглушки) ---

/**
 * Функция, выполняемая при запуске для Tencent Translation.
 * Может использоваться для инициализации, загрузки зависимостей или проверки состояния API.
 * @returns {Promise<void>} Промис, который разрешается после выполнения стартовых операций.
 */
function translate_tencent_startup() {
    console.log("[Tencent Translation] Выполняется запуск...");
    // Здесь может быть код для предварительной проверки API,
    // загрузки необходимых библиотек или инициализации.
    return Promise.resolve();
}

/**
 * Функция, выполняемая при запуске для Caiyun Xiaoyi.
 * @returns {Promise<void>} Промис, который разрешается после выполнения стартовых операций.
 */
function Translation_caiyun_startup() {
    console.log("[Цайюнь Сяойи] Выполняется запуск...");
    return Promise.resolve();
}

/**
 * Функция, выполняемая при запуске для Papago Translation.
 * @returns {Promise<void>} Промис, который разрешается после выполнения стартовых операций.
 */
function translate_papago_startup() {
    console.log("[Papago Translation] Выполняется запуск...");
    return Promise.resolve();
}

// --- Основной код скрипта (остается без изменений) ---

GM_registerMenuCommand('Сбросить положение панели управления (обновить приложение)', () => {
    GM_setValue('position_top', '9px');
    GM_setValue('position_right', '9px');
    location.reload(); // Добавлено для обновления приложения, как указано в комментарии
});

GM_registerMenuCommand('Глобально скрыть/показать плавающий шар (обновить приложение)', () => {
    GM_setValue('show_translate_ball', !GM_getValue('show_translate_ball', true));
    location.reload(); // Добавлено для обновления приложения
});

const transdict = {
    'Google Переводчик': translate_gg,
    'Google Translate mobile': translate_ggm,
    'Перевод Tencent': translate_tencent,
    'Перевод Tencent AI': translate_tencentai,
    //'Перевод Youdao': Translate_youdao, // Закомментировано в оригинале
    'Мобильный перевод Youdao': Translate_youdao_mobile,
    'Переводчик Baidu': Translate_baidu,
    'Цайюнь Сяойи': Translation_caiyun,
    'Bing Translation': translate_biying,
    'Перевод Папаго': translate_papago,
    'Ali Translation': translate_alibaba,
    'iciba translation': translate_icib,
    'Deepl Translation': translate_deepl,
    'Отключить перевод': () => {}
};

const startup = {
    // 'Перевод Youdao': Translate_youdao_startup, // Закомментировано в оригинале
    'Перевод Tencent': translate_tencent_startup,
    'Цайюнь Сяойи': Translation_caiyun_startup,
    'Papago Translation': translate_papago_startup
};

const baseoptions = {
    'enable_pass_lang': {
        declare: 'Не переводить китайский (упрощенный)',
        default_value: true,
        change_func: self => {
            if (self.checked) sessionStorage.clear();
        }
    },
    'enable_pass_lang_cht': {
        declare: 'Не переводите китайский (традиционный)',
        default_value: true,
        change_func: self => {
            if (self.checked) sessionStorage.clear();
        }
    },
    'remove_url': {
        declare: 'Автоматически фильтровать URL',
        default_value: true,
    },
    'show_info': {
        declare: 'Показать исходный текст перевода',
        default_value: true,
        option_enable: true
    },
    'fullscrenn_hidden': {
        declare: 'Не отображать на весь экран',
        default_value: true,
    },
    'replace_translate': {
        declare: 'заменяющий перевод',
        default_value: false,
        option_enable: true
    },
    'compress_storage': {
        declare: 'Сжатый кэш',
        default_value: false,
    }
};

const [enable_pass_lang, enable_pass_lang_cht, remove_url, show_info, fullscrenn_hidden, replace_translate, compress_storage] =
    Object.keys(baseoptions).map(key => GM_getValue(key, baseoptions[key].default_value));

const globalProcessingSave = [];

const sessionStorage = compress_storage ? CompressMergeSession(window.sessionStorage) : window.sessionStorage;

const p = window.trustedTypes !== undefined ? window.trustedTypes.createPolicy('translator', {
    createHTML: (string, sink) => string
}) : {
    createHTML: (string, sink) => string
};

function initPanel() {
    let choice = GM_getValue('translate_choice', 'Google Переводчик');
    let select = document.createElement("select");
    select.className = 'js_translate';
    select.style = 'height: 35px; width: 100px; background-color: #fff; border-radius: 17.5px; text-align: center; color: #000000; margin: 5px 0;';
    select.onchange = () => {
        GM_setValue('translate_choice', select.value);
        title.innerText = "Панель управления (обновите, чтобы применить)";
    };
    for (let i in transdict) select.innerHTML = p.createHTML(select.innerHTML + '<option value="' + i + '">' + i + '</option>');

    let enable_details = document.createElement('details');
    enable_details.innerHTML = p.createHTML(enable_details.innerHTML + "<summary>Включить правила</summary>");
    for (let i of rules) {
        let temp = document.createElement('input');
        temp.type = 'checkbox';
        temp.name = i.name;
        if (GM_getValue("enable_rule:" + temp.name, true)) temp.setAttribute('checked', true);
        enable_details.appendChild(temp);
        enable_details.innerHTML = p.createHTML(enable_details.innerHTML + "<span>" + i.name + "</span><br>");
    }

    let current_details = document.createElement('details');
    let mask = document.createElement('div'),
        dialog = document.createElement("div"),
        js_dialog = document.createElement("div"),
        title = document.createElement('p');

    let shadowRoot = document.createElement('div');
    shadowRoot.style = "position: absolute; visibility: hidden;";
    window.top.document.body.appendChild(shadowRoot);
    let shadow = shadowRoot.attachShadow({
        mode: "closed"
    });
    shadow.appendChild(mask);

    dialog.appendChild(js_dialog);
    mask.appendChild(dialog);
    js_dialog.appendChild(title);
    js_dialog.appendChild(document.createElement('p')).appendChild(select);
    js_dialog.appendChild(document.createElement('p')).appendChild(enable_details);
    js_dialog.appendChild(document.createElement('p')).appendChild(current_details);

    mask.style = "display: none; position: fixed; height: 100vh; width: 100vw; z-index: 99999; top: 0; left: 0; overflow: hidden; background-color: rgba(0,0,0,0.4); justify-content: center; align-items: center; visibility: visible;";
    mask.addEventListener('click', event => {
        if (event.target === mask) mask.style.display = 'none';
    });
    dialog.style = 'padding: 0; border-radius: 10px; background-color: #fff; box-shadow: 0 0 5px 4px rgba(0,0,0,0.3);';
    js_dialog.style = "min-height: 10vh; min-width: 10vw; display: flex; flex-direction: column; align-items: center; padding: 10px; border-radius: 4px; color: #000;";
    title.style = 'margin: 5px 0; font-size: 20px;';
    title.innerText = "Панель управления";

    for (let i in baseoptions) {
        let temp = document.createElement('input'),
            temp_p = document.createElement('p');
        js_dialog.appendChild(temp_p);
        temp_p.appendChild(temp);
        temp.type = 'checkbox';
        temp.name = i;
        temp_p.style = "display:flex;align-items: center;margin:5px 0";
        temp_p.innerHTML = p.createHTML(temp_p.innerHTML + baseoptions[i].declare);
    }

    for (let i of js_dialog.querySelectorAll('input')) {
        if (i.name && baseoptions[i.name]) {
            i.onclick = _ => {
                title.innerText = "Панель управления (обновите, чтобы применить)";
                GM_setValue(i.name, i.checked);
                if (baseoptions[i.name].change_func) baseoptions[i.name].change_func(i);
            };
            i.checked = GM_getValue(i.name, baseoptions[i.name].default_value);
        }
    }

    for (let i of enable_details.querySelectorAll('input')) {
        i.onclick = _ => {
            title.innerText = "Панель управления (обновите, чтобы применить)";
            GM_setValue('enable_rule:' + i.name, i.checked);
        };
    }

    let open = document.createElement('div');
    open.style = `z-index: 9999; height: 35px; width: 35px; background-color: #fff; position: fixed; border: 1px solid rgba(0,0,0,0.2); border-radius: 17.5px; right: ${GM_getValue('position_right', '9px')}; top: ${GM_getValue('position_top', '9px')}; text-align-last: center; color: #000000; display: flex; align-items: center; align-content: center; cursor: pointer; font-size: 15px; user-select: none; visibility: visible;`;
    open.innerHTML = p.createHTML("Перевести");

    const renderCurrentRule = () => {
        current_details.style.display = "none";
        current_details.innerHTML = p.createHTML('');
        const currentRule = GetActiveRule();
        if (currentRule) {
            current_details.style.display = "flex";
            current_details.innerHTML = p.createHTML(`<summary>В настоящее время включено - ${currentRule.name}</summary>`);
            for (const option of currentRule.options) {
                const fieldset = document.createElement("fieldset");
                fieldset.innerHTML = p.createHTML(fieldset.innerHTML + `<legend>${option.name}</legend>`);
                current_details.appendChild(fieldset);
                fieldset.innerHTML = p.createHTML(fieldset.innerHTML + `<div style="display:flex;align-items:center"><span>отображает теги в исходном тексте</span><input type="checkbox"></div>`);
                for (const key in baseoptions) {
                    if (!baseoptions[key].option_enable) {
                        continue;
                    }
                    fieldset.innerHTML = p.createHTML(fieldset.innerHTML + `<span>${baseoptions[key].declare}</span><br>`);
                    const baseValueList = [
                        ["", "default"],
                        ["true", "enabled"],
                        ["false", "disabled"]
                    ];
                    fieldset.innerHTML = p.createHTML(fieldset.innerHTML + "<div>" + baseValueList.map(value => `<input type="radio" value="${value[0]}" name="${key}:${currentRule.name}-${option.name}">${value[1]}</input>`).join('') + "</div>");
                }
                const enableInput = fieldset.querySelector('input[type=checkbox]');
                const enableKey = `enable_option:${currentRule.name}-${option.name}`;
                enableInput.checked = GM_getValue(enableKey, true);
                enableInput.onchange = () => {
                    title.innerText = "Панель управления (обновите, чтобы применить)";
                    GM_setValue(enableKey, enableInput.checked);
                };
                const optionInputs = fieldset.querySelectorAll("input[type=radio]");
                for (const input of optionInputs) {
                    const key = `option_setting:${input.name}`;
                    if (GM_getValue(key, '').toString() === input.value) {
                        input.checked = true;
                    }
                    input.onchange = () => {
                        title.innerText = "Панель управления (обновите, чтобы применить)";
                        switch (input.value) {
                            case 'true':
                                GM_setValue(key, true);
                                break;
                            case 'false':
                                GM_setValue(key, false);
                                break;
                            case '':
                                GM_deleteValue(key);
                                break;
                        }
                    };
                }
            }
        }
    };

    open.onclick = () => {
        renderCurrentRule();
        mask.style.display = 'flex';
    };

    open.draggable = true;
    open.addEventListener("dragstart", function(ev) {
        ev.stopImmediatePropagation();
        this.tempNode = document.createElement('div');
        this.tempNode.style = "width: 1px; height: 1px; opacity: 0;";
        document.body.appendChild(this.tempNode);
        ev.dataTransfer.setDragImage(this.tempNode, 0, 0);
        this.oldX = ev.offsetX - Number(this.style.width.replace('px', ''));
        this.oldY = ev.offsetY;
    });
    open.addEventListener("drag", function(ev) {
        ev.stopImmediatePropagation();
        if (!ev.x && !ev.y) return;
        this.style.right = Math.max(window.innerWidth - ev.x + this.oldX, 0) + "px";
        this.style.top = Math.max(ev.y - this.oldY, 0) + "px";
    });
    open.addEventListener("dragend", function(ev) {
        ev.stopImmediatePropagation();
        GM_setValue("position_right", this.style.right);
        GM_setValue("position_top", this.style.top);
        document.body.removeChild(this.tempNode);
    });

    open.addEventListener("touchstart", ev => {
        ev.stopImmediatePropagation();
        ev.preventDefault();
        ev = ev.touches[0];
        open._tempTouch = {};
        const base = open.getBoundingClientRect();
        open._tempTouch.oldX = ev.clientX - base.left;
        open._tempTouch.oldY = ev.clientY - base.top;
        open._tempIsMove = false;
    });

    open.addEventListener("touchmove", ev => {
        ev.stopImmediatePropagation();
        ev = ev.touches[0];
        open.style.right = Math.max(window.innerWidth - (ev.clientX + open._tempTouch.oldX), 0) + 'px';
        open.style.top = Math.max(ev.clientY - open._tempTouch.oldY, 0) + 'px';
        open._tempIsMove = true;
    });

    open.addEventListener("touchend", ev => {
        ev.stopImmediatePropagation();
        GM_setValue("position_right", open.style.right);
        GM_setValue("position_top", open.style.top);
        if (!open._tempIsMove) {
            renderCurrentRule();
            mask.style.display = 'flex';
        }
        open._tempIsMove = false;
    });

    shadow.appendChild(open);
    shadow.querySelector('.js_translate option[value="' + choice + '"]').selected = true;

    if (fullscrenn_hidden) window.top.document.addEventListener('fullscreenchange', () => {
        open.style.display = window.top.document.fullscreenElement ? "none" : "flex";
    });
}

const rules = [{
        name: 'Twitter General',
        matcher: /https:\/\/(?:[a-zA-Z.]*?\.|)twitter\.com/,
        options: [{
                name: "Твит",
                selector: baseSelector('div[dir="auto"][lang]'),
                textGetter: baseTextGetter,
                textSetter: options => {
                    options.element.style.cssText = options.element.style.cssText.replace(/-webkit-line-clamp.*?;/, '');
                    baseTextSetter(options).style.display = 'flex';
                }
            },
            {
                name: "Справочная информация",
                selector: baseSelector('div[data-testid=birdwatch-pivot]>div[dir=ltr]'),
                textGetter: baseTextGetter,
                textSetter: options => {
                    options.element.style.cssText = options.element.style.cssText.replace(/-webkit-line-clamp.*?;/, '');
                    baseTextSetter(options).style.display = 'flex';
                }
            }
        ]
    },
    {
        name: 'x General',
        matcher: /https:\/\/(?:[a-zA-Z.]*?\.|)x\.com/,
        options: [{
                name: "Твит",
                selector: baseSelector('div[dir="auto"][lang]'),
                textGetter: baseTextGetter,
                textSetter: options => {
                    options.element.style.cssText = options.element.style.cssText.replace(/-webkit-line-clamp.*?;/, '');
                    baseTextSetter(options).style.display = 'flex';
                }
            },
            {
                name: "Справочная информация",
                selector: baseSelector('div[data-testid=birdwatch-pivot]>div[dir=ltr]'),
                textGetter: baseTextGetter,
                textSetter: options => {
                    options.element.style.cssText = options.element.style.cssText.replace(/-webkit-line-clamp.*?;/, '');
                    baseTextSetter(options).style.display = 'flex';
                }
            }
        ]
    },
    {
        name: 'youtube pc universal',
        matcher: /https:\/\/www\.youtube\.com\/(?:watch|shorts|results\?)/,
        options: [{
                name: "Область комментариев",
                selector: baseSelector("#content>#content-text"),
                textGetter: baseTextGetter,
                textSetter: options => {
                    baseTextSetter(options);
                    options.element.parentNode.parentNode.removeAttribute('collapsed');
                }
            },
            {
                name: "Видеовведение",
                selector: baseSelector("#content>#description>.content,.ytd-text-inline-expander>.yt-core-attributed-string"),
                textGetter: baseTextGetter,
                textSetter: options => {
                    baseTextSetter(options);
                    options.element.parentNode.parentNode.removeAttribute('collapsed');
                }
            },
            {
                name: "CC Subtitles",
                selector: baseSelector(".ytp-caption-segment"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter
            }
        ]
    },
    {
        name: 'youtube mobile universal',
        matcher: /https:\/\/m\.youtube\.com\/watch/,
        options: [{
                name: "Область комментариев",
                selector: baseSelector(".comment-text.user-text"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "Видеовведение",
                selector: baseSelector(".slim-video-metadata-description"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            }
        ]
    },
    {
        name: 'короткое видео YouTube',
        matcher: /https:\/\/(?:www|m)\.youtube\.com\/shorts/,
        options: [{
            name: "Область комментариев",
            selector: baseSelector("#comment-content #content-text,.comment-content .comment-text"),
            textGetter: baseTextGetter,
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'сообщество YouTube',
        matcher: /https:\/\/(?:www|m)\.youtube\.com\/(?:.*?\/community|post)/,
        options: [{
            name: "Область комментариев",
            selector: baseSelector("#post #content #content-text,#comment #content #content-text,#replies #content #content-text"),
            textGetter: baseTextGetter,
            textSetter: options => {
                baseTextSetter(options);
                options.element.parentNode.parentNode.removeAttribute('collapsed');
            }
        }]
    },
    {
        name: 'facebook-universal',
        matcher: /https:\/\/www\.facebook\.com\/.+/,
        options: [{
                name: "Опубликовать контент",
                selector: baseSelector("div[data-ad-comet-preview=message],div[role=article] div[id]"),
                textGetter: baseTextGetter,
                textSetter: options => setTimeout(baseTextSetter, 0, options),
            },
            {
                name: "Область комментариев",
                selector: baseSelector("div[role=article] div>span[dir=auto][lang]"),
                textGetter: baseTextGetter,
                textSetter: options => setTimeout(baseTextSetter, 0, options),
            }
        ]
    },
    {
        name: 'reddit general',
        matcher: /https:\/\/www\.reddit\.com\/.*/,
        options: [{
                name: 'Заголовок сообщения',
                selector: baseSelector("*[slot=title][id|=post-title]"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "Опубликовать контент",
                selector: baseSelector("div[slot=text-body]>div>div[id*=-post-rtjson-content]"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "Область комментариев",
                selector: baseSelector("div[slot=comment]>div[id$=-post-rtjson-content]"),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            }
        ]
    },
    {
        name: '5ch Comments',
        matcher: /https?:\/\/(?:.*?\.|)5ch\.net\/.*/,
        options: [{
                name: "Название",
                selector: baseSelector('.post>.post-content,#threadtitle,.thread_title'),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "контент",
                selector: baseSelector('.threadview_response_body'),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            }
        ]
    },
    {
        name: 'чат Discord',
        matcher: /https:\/\/discord\.com\/.+/,
        options: [{
            name: "Содержимое чата",
            selector: baseSelector('div[class*=messageContent]'),
            textGetter: baseTextGetter,
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'телеграм чат новый',
        matcher: /https:\/\/.*\.telegram\.org\/(?:a|z)\//,
        options: [{
            name: "Содержимое чата",
            selector: baseSelector('p.text-content[dir=auto],div.text-content'),
            textGetter: e => Array.from(e.childNodes).filter(item => !item.className).map(item => item.nodeName === "BR" ? "\n" : item.textContent).join(' '),
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'чат телеграмм',
        matcher: /https:\/\/.*\.telegram\.org\/.+/,
        options: [{
            name: "Содержимое чата",
            selector: baseSelector('div.message[dir=auto],div.im_message_text'),
            textGetter: e => Array.from(e.childNodes).filter(item => !item.className || item.className === 'translatable-message').map(item => item.nodeValue || item.innerText).join(" "),
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'quora general',
        matcher: /https:\/\/www\.quora\.com/,
        options: [{
                name: "Название",
                selector: baseSelector(".puppeteer_test_question_title>span>span"),
                textGetter: baseTextGetter,
                textSetter: options => {
                    options.element.parentNode.parentNode.style.cssText = options.element.parentNode.parentNode.style.cssText.replace(/-webkit-line-clamp.*?;/, '');
                    baseTextSetter(options).style.display = 'flex';
                },
            },
            {
                name: "Опубликовать контент",
                selector: baseSelector('div.q-text>span>span.q-box:has(pq-text),div.q-box>div.q-box>div.q-text>span.q-box:has(pq-text)'),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            }
        ]
    },
    {
        name: 'комментарии тикток',
        matcher: /https:\/\/www\.tiktok\.com/,
        options: [{
            name: "Область комментариев",
            selector: baseSelector('p[data-e2e|=comment-level]'),
            textGetter: baseTextGetter,
            textSetter: baseTextSetter,
        }]
    },
    {
        name: "Комментарии Instagram",
        matcher: /https:\/\/www\.instagram\.com/,
        options: [{
            name: "Область комментариев",
            selector: baseSelector('li>div>div>div>div>span[dir=auto]'),
            textGetter: baseTextGetter,
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'threads',
        matcher: /https:\/\/www\.threads\.net/,
        options: [{
            name: "Пост",
            selector: baseSelector('div[data-pressable-container=true][data-interactive-id]>div>div:last-child>div>div:has(span[dir=auto]):not(:has(div[role=button]))'),
            textGetter: baseTextGetter,
            textSetter: baseTextSetter,
        }]
    },
    {
        name: 'github',
        matcher: /https:\/\/github\.com\/.+\/.+\/\w+\/\d+/,
        options: [{
                name: "проблемы",
                selector: baseSelector(".edit-comment-hide > task-lists > table > tbody > tr > td > p", items => items.filter(i => {
                    const nodeNameList = [...new Set([...i.childNodes].map(i => i.nodeName))];
                    return nodeNameList.length > 1 || (nodeNameList.length == 1 && nodeNameList[0] == "#text");
                })),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "обсуждения",
                selector: baseSelector(".edit-comment-hide > task-lists > table > tbody > tr > td > p", items => items.filter(i => {
                    const nodeNameList = [...new Set([...i.childNodes].map(i => i.nodeName))];
                    return nodeNameList.length > 1 || (nodeNameList.length == 1 && nodeNameList[0] == "#text");
                })),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
        ]
    },
    {
        name: 'bsky',
        matcher: /https:\/\/bsky\.app/,
        options: [{
                name: "Домашняя запись",
                selector: baseSelector('div[dir=auto][data-testid=postText]'),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            },
            {
                name: "Контент-посты и ответы",
                selector: baseSelector('div[data-testid^="postThreadItem-by"] div[dir=auto][data-word-wrap]'),
                textGetter: baseTextGetter,
                textSetter: baseTextSetter,
            }
        ]
    },
];

const GetActiveRule = () => rules.find(item => item.matcher.test(document.location.href) && GM_getValue('enable_rule:' + item.name, true));

(function() {
    'use strict';
    let url = document.location.href;
    let rule = GetActiveRule();
    setInterval(() => {
        if (document.location.href !== url) {
            url = document.location.href;
            const ruleNew = GetActiveRule();
            if (ruleNew !== rule) {
                if (ruleNew !== null) {
                    console.log(`[Переводческая машина] обнаружила изменение URL и перешла на использование правила [${ruleNew.name}]`);
                } else {
                    console.log("[Переводческая машина] обнаружила изменение URL-адреса, в настоящее время нет соответствующих правил");
                }
                rule = ruleNew;
            }
        }
    }, 200);
    console.log(rule ? `[Машина перевода] использует правило [${rule.name}]` : "[Машина перевода] в настоящее время не имеет соответствующего правила");
    console.log(document.location.href);

    let main = _ => {
        if (!rule) return;
        const choice = GM_getValue('translate_choice', 'Google Переводчик');
        for (const option of rule.options) {
            if (!GM_getValue("enable_option:" + rule.name + "-" + option.name, true)) {
                continue;
            }
            const temp = [...new Set(option.selector())];
            for (let i = 0; i < temp.length; i++) {
                const now = temp[i];
                if (globalProcessingSave.includes(now)) continue;
                globalProcessingSave.push(now);
                const rawText = option.textGetter(now);
                const text = remove_url ? url_filter(rawText) : rawText;
                if (text.length === 0) {
                    removeItem(globalProcessingSave, now);
                    continue;
                }
                const setterParams = {
                    element: now,
                    translateName: choice,
                    rawText: rawText,
                    rule: rule,
                    option: option
                };
                if (sessionStorage.getItem(choice + '-' + text)) {
                    setterParams.text = sessionStorage.getItem(choice + '-' + text);
                    option.textSetter(setterParams);
                    removeItem(globalProcessingSave, now);
                } else {
                    pass_lang(text).then(lang => transdict[choice](text, lang)).then(s => {
                        setterParams.text = s;
                        option.textSetter(setterParams);
                        if (s) sessionStorage.setItem(choice + '-' + text, s);
                        removeItem(globalProcessingSave, now);
                    }).catch(error => {
                        console.error("Ошибка перевода:", error);
                        removeItem(globalProcessingSave, now);
                    });
                }
            }
        }
    };
    PromiseRetryWrap(startup[GM_getValue('translate_choice', 'Google Переводчик')]).then(() => {
        document.js_translater = setInterval(main, 20);
        initPanel();
    }).catch(error => {
        console.error("Ошибка при запуске: ", error);
        initPanel();
    });
})();