🌐Reddit Traducteur Pro

🏷️ Traducteur Reddit amateur — glassmorphism, 100+ langues, Google, MyMemory, DeepL, historique, convertisseur d'unités, easter eggs

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         🌐Reddit Translator Pro
// @name:en      🌐Reddit Translator Pro
// @name:ru      🌐Reddit Переводчик Pro
// @name:uk      🌐Reddit Перекладач Pro
// @name:de      🌐Reddit Übersetzer Pro
// @name:fr      🌐Reddit Traducteur Pro
// @name:es      🌐Reddit Traductor Pro
// @name:it      🌐Reddit Traduttore Pro
// @name:pl      🌐Reddit Tłumacz Pro
// @name:tr      🌐Reddit Çevirmeni Pro
// @name:vi      🌐Reddit Biên Dịch Viên Pro
// @name:ko      🌐Reddit 번역기 Pro
// @name:ja      🌐Reddit 翻訳者 Pro
// @name:zh-CN   🌐Reddit 翻译器 Pro
// @name:zh-HK   🌐Reddit 翻譯器 Pro
// @name:zh-TW   🌐Reddit 翻譯器 Pro
// @namespace    https://github.com/ebayybe
// @homepageURL  https://github.com/ebayybe/reddit-translator
// @supportURL   https://github.com/ebayybe/reddit-translator/issues
// @version      1.0.10
// @description:en      🏷️ Amateur Reddit translator — glassmorphism, 100+ languages, Google, MyMemory, DeepL, history, unit converter, easter eggs
// @description:ru      🏷️ Любительский переводчик Reddit — glassmorphism, 100+ языков, Google, MyMemory, DeepL, история, конвертер единиц, пасхалки
// @description:uk      🏷️ Любительський перекладач Reddit — glassmorphism, 100+ мов, Google, MyMemory, DeepL, історія, конвертер одиниць, пасхалки
// @description:de      🏷️ Amateur-Reddit-Übersetzer — Glassmorphism, 100+ Sprachen, Google, MyMemory, DeepL, Verlauf, Einheitenumrechner, Ostereier
// @description:it      🏷️ Traduttore Reddit amatoriale — glassmorphism, 100+ lingue, Google, MyMemory, DeepL, cronologia, convertitore di unità, easter egg
// @description:fr      🏷️ Traducteur Reddit amateur — glassmorphism, 100+ langues, Google, MyMemory, DeepL, historique, convertisseur d'unités, easter eggs
// @description:es      🏷️ Traductor Reddit amateur — glassmorphism, 100+ idiomas, Google, MyMemory, DeepL, historial, convertidor de unidades, easter eggs
// @description:ko      🏷️ 아마추어 Reddit 번역기 — glassmorphism, 100+ 언어, Google, MyMemory, DeepL, 히스토리, 단위 변환기, 이스터 에그
// @description:pl      🏷️ Amatorski tłumacz Reddit — glassmorphism, 100+ języków, Google, MyMemory, DeepL, historia, konwerter jednostek, easter eggi
// @description:tr      🏷️ Amatör Reddit çevirmeni — glassmorphism, 100+ dil, Google, MyMemory, DeepL, geçmiş, birim dönüştürücü, sürprizler
// @description:vi      🏷️ Trình dịch Reddit nghiệp dư — glassmorphism, 100+ ngôn ngữ, Google, MyMemory, DeepL, lịch sử, bộ chuyển đổi đơn vị, easter egg
// @description:ja      🏷️ アマチュアReddit翻訳者 — glassmorphism, 100以上の言語, Google, MyMemory, DeepL, 履歴, 単位変換器, イースターエッグ
// @description:zh-CN   🏷️ 业余Reddit翻译器 — glassmorphism, 100多种语言, Google, MyMemory, DeepL, 历史记录, 单位转换器, 彩蛋
// @description:zh-HK   🏷️ 業餘Reddit翻譯器 — glassmorphism, 100多種語言, Google, MyMemory, DeepL, 歷史記錄, 單位轉換器, 彩蛋
// @description:zh-TW   🏷️ 業餘Reddit翻譯器 — glassmorphism, 100多種語言, Google, MyMemory, DeepL, 歷史記錄, 單位轉換器, 彩蛋
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij4KICA8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgcng9IjI4IiBmaWxsPSIjZmY0NTAwIi8+CiAgPHRleHQgeD0iNjQiIHk9IjkwIiBmb250LXNpemU9IjcyIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iU2Vnb2UgVUkgRW1vamksQXBwbGUgQ29sb3IgRW1vamksc2Fucy1zZXJpZiI+8J+MkDwvdGV4dD4KPC9zdmc+
// @author       ebayybe
// @license      MIT
// @match        https://www.reddit.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @connect      translate.googleapis.com
// @connect      api.mymemory.translated.net
// @connect      api.deepl.com
// @connect      api-free.deepl.com
// @description 🏷️Любительский переводчик Reddit — glassmorphism, 100+ языков, TTS, история, конвертеры, пасхалки, IntersectionObserver
// ==/UserScript==

(function () {
  'use strict';

  // Unique prefix for this script to avoid conflicts with other scripts
  const PREFIX = '_x9_';

  // ═══════════════════════════════════════════════════════════════════════════
  // § КОНФИГУРАЦИЯ
  // ═══════════════════════════════════════════════════════════════════════════
  // Auto-detect UI language from browser/system language
  const UI_SUPPORTED_LIST = ['ru', 'uk', 'en', 'de', 'fr', 'es', 'pl', 'tr', 'zh', 'ja', 'it', 'pt', 'ko', 'vi', 'ar'];
  function detectUiLang() {
    const langs = navigator.languages || [navigator.language || 'en'];
    for (const lang of langs) {
      const base = lang.split('-')[0].toLowerCase();
      if (UI_SUPPORTED_LIST.includes(base)) return base;
    }
    return 'en';
  }
  const AUTO_UI_LANG = detectUiLang();

  const DEF = {
    targetLang: AUTO_UI_LANG,
    uiLang: AUTO_UI_LANG,
    engine: 'google',
    tone: 'normal',
    theme: 'dark',
    bilingualMode: true,
    autoConvert: true,
    autoScroll: false,
    incognito: false,
    requestDelay: 50,
    totalChars: 0,
    totalCount: 0,
    hotkeyPanel: 'F2',
    customColors: null,
    autoTranslateOnScroll: true,
    deeplApiKeys: '',
    maxRequestsPerSecond: 20,
    maxTextLengthPerRequest: 1800,
    maxParagraphsPerRequest: 6,
    maxConcurrentRequests: 5,
  };

  // Загружаем конфиг
  const cfg = Object.fromEntries(
    Object.entries(DEF).map(([k, def]) => [k, GM_getValue(PREFIX + k, def)])
  );

  function save(key, val) {
    cfg[key] = val;
    GM_setValue(PREFIX + key, val);
  }

  // Батч-сохранение счётчиков
  let statTimer = null;
  function flushStats() {
    clearTimeout(statTimer);
    statTimer = setTimeout(() => {
      GM_setValue(PREFIX + 'totalChars', cfg.totalChars);
      GM_setValue(PREFIX + 'totalCount', cfg.totalCount);
    }, 1200);
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § КЭШ (TTL 24ч, лимит 600 записей, дебаунс записи)
  // ═══════════════════════════════════════════════════════════════════════════
  const CACHE_KEY = 'rtp_v8_cache';
  let cache = {};

  (function loadCache() {
    try {
      const now = Date.now();
      const raw = JSON.parse(GM_getValue(PREFIX + CACHE_KEY, '{}'));
      const entries = Object.entries(raw)
        .filter(([, v]) => now - v.ts < 86_400_000)
        .sort((a, b) => b[1].ts - a[1].ts)
        .slice(0, 600);
      entries.forEach(([k, v]) => (cache[k] = v));
    } catch { }
  })();

  let cacheTimer = null;
  function flushCache() {
    if (cfg.incognito) return;
    clearTimeout(cacheTimer);
    cacheTimer = setTimeout(() => {
      try { GM_setValue(PREFIX + CACHE_KEY, JSON.stringify(cache)); } catch { }
    }, 2000);
  }

  function cacheSet(key, val) {
    const keys = Object.keys(cache);
    if (keys.length >= 600) {
      const oldest = keys.sort((a, b) => cache[a].ts - cache[b].ts)[0];
      delete cache[oldest];
    }
    cache[key] = { val, ts: Date.now() };
    flushCache();
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ИСТОРИЯ (50 записей)
  // ═══════════════════════════════════════════════════════════════════════════
  let history = [];
  try { history = JSON.parse(GM_getValue(PREFIX + 'rtp_v8_history', '[]')); } catch { }

  function pushHistory(orig, translated, lang) {
    if (cfg.incognito) return;
    history.unshift({ orig: orig.slice(0, 130), translated: translated.slice(0, 130), lang, ts: Date.now() });
    if (history.length > 50) history.length = 50;
    GM_setValue(PREFIX + 'rtp_v8_history', JSON.stringify(history));
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § UI — МУЛЬТИЯЗЫЧНОСТЬ ИНТЕРФЕЙСА
  // ═══════════════════════════════════════════════════════════════════════════
  const UI_SUPPORTED = UI_SUPPORTED_LIST;

  const STRINGS = {
    ru: {
      title: 'Reddit Переводчик', ver: 'v1.0.10',
      tabSettings: '⚙️ Настройки', tabHistory: '📖 История', tabExtras: '✨ Дополнения',
      secUiLang: 'Язык интерфейса', applyUi: '✨ ПРИМЕНИТЬ ИНТЕРФЕЙС',
      secTargetLang: 'Язык перевода', saveLang: '💾 СОХРАНИТЬ ЯЗЫК',
      secEngine: 'Движок', secTone: 'Стиль перевода', secTheme: 'Тема',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Ключи DeepL API', saveDeepLApi: '🔑 СОХРАНИТЬ КЛЮЧИ',
      btnTestDeepL: '🧪 ПРОВЕРИТЬ',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Вставьте один или несколько ключей DeepL API, разделённых запятыми',
      deeplApiHelp: 'Поддерживаются Pro и Free ключи. Ключи, оканчивающиеся на :fx, автоматически используют deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Ключи DeepL сохранены',
      toastDeepLKeysMissing: '⚠️ Сначала добавьте хотя бы один ключ DeepL API',
      toastDeepLUnsupported: '⚠️ DeepL не поддерживает этот язык перевода:',
      toastDeepLTesting: '🧪 Проверка DeepL API...',
      toastDeepLOk: '✅ DeepL API доступен',
      toastDeepLFail: '❌ DeepL API недоступен',
      toneNeutral: 'Нейтральный', toneFormal: 'Официальный', toneSlang: 'Разговорный',
      themeDark: 'Тёмная', themeLight: 'Светлая', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Двуязычный режим', togTts: 'Озвучка (TTS)',
      togAutoConvert: 'Авто-конвертация единиц', togAutoScroll: 'Авто-скролл к новым',
      togIncognito: 'Инкогнито (без истории)',
      togAutoTranslateOnScroll: 'Автоперевод при прокрутке',
      btnResetPos: '🏠 Сброс позиции', btnClearCache: '🧹 Очистить кэш',
      btnExport: '📤 Экспорт', btnImport: '📥 Импорт',
      btnSurprise: '🎲 Случайный язык', btnPirate: '🏴‍☠️ Пиратский',
      btnYoda: '🧙 Режим Йоды', btnHide: '👁 Скрыть кнопки', btnShow: '👁 Показать кнопки',
      sliderDelay: 'Задержка запросов', unitMs: 'мс',
      statTranslations: 'Переводов', statChars: 'Символов', statOnPage: 'На странице',
      histEmpty: 'История пуста', histClear: '🗑 Очистить историю',
      searchLang: 'Поиск языка…',
      btnOrig: '↩ Оригинал', btnCopy: '📋 Копировать', btnSpeak: '🔊', btnRetry: '↺ Ещё раз',
      copied: '✅ Скопировано!', cacheCleared: '🧹 Кэш очищен',
      toastApply: '✅ Интерфейс обновлён',
      toastSave: '💾 Сохранено — перезагрузка…', toastSurprise: '🎲 Язык:',
      toastPirateOn: '🏴‍☠️ Arrr! Пиратский режим!', toastPirateOff: '🏴‍☠️ Режим выключен',
      toastYodaOn: '🧙 Включён режим, хммм.', toastYodaOff: '🧙 Выключен он.',
      toastAutoScrollOn: '✅ Автоперевод при прокрутке включён', toastAutoScrollOff: '⭕ Автоперевод при прокрутке выключен',
      shortcutHint: 'F2 = панель',
      secHotkeys: 'Горячие клавиши', hotkeyPanel: 'Открыть панель',
      hotkeyPress: 'Нажмите клавишу…', hotkeyReset: '↺ Сброс',
      secColors: 'Цвета темы', colorAcc: 'Акцент', colorTxt: 'Текст', colorBg: 'Фон', colorOk: 'Успех',
      btnResetColors: '↺ Сброс цветов',
      secRequestLimits: 'Ограничения запросов',
      maxConcurrentRequests: 'Макс. одновременных запросов',
      maxRequestsPerSecond: 'Макс. запросов / сек',
      maxTextLengthPerRequest: 'Макс. символов / запрос',
      maxParagraphsPerRequest: 'Макс. абзацев / запрос',
      btnShowOriginals: '📄 ПОКАЗАТЬ ОРИГИНАЛ',
      btnShowTranslations: '🌐 ПОКАЗАТЬ ПЕРЕВОД',
      toastShowingOriginals: '📄 Показан оригинальный текст',
      toastShowingTranslations: '🌐 Показан переведённый текст',
    },
    uk: {
      title: 'Reddit Перекладач', ver: 'v1.0.10',
      tabSettings: '⚙️ Налаштування', tabHistory: '📖 Історія', tabExtras: '✨ Додатково',
      secUiLang: 'Мова інтерфейсу', applyUi: '✨ ЗАСТОСУВАТИ ІНТЕРФЕЙС',
      secTargetLang: 'Мова перекладу', saveLang: '💾 ЗБЕРЕГТИ МОВУ',
      secEngine: 'Рушій', secTone: 'Стиль', secTheme: 'Тема',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Ключі DeepL API', saveDeepLApi: '🔑 ЗБЕРЕГТИ КЛЮЧІ',
      btnTestDeepL: '🧪 ПЕРЕВІРИТИ',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Вставте один або кілька ключів DeepL API, розділених комами',
      deeplApiHelp: 'Підтримуються Pro і Free ключі. Ключі, що закінчуються на :fx, автоматично використовують deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Ключі DeepL збережено',
      toastDeepLKeysMissing: '⚠️ Спочатку додайте хоча б один ключ DeepL API',
      toastDeepLUnsupported: '⚠️ DeepL не підтримує цю мову перекладу:',
      toastDeepLTesting: '🧪 Перевірка DeepL API...',
      toastDeepLOk: '✅ DeepL API доступний',
      toastDeepLFail: '❌ DeepL API недоступний',
      toneNeutral: 'Нейтральний', toneFormal: 'Офіційний', toneSlang: 'Розмовний',
      themeDark: 'Темна', themeLight: 'Світла', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Двомовний режим', togTts: 'Озвучка (TTS)',
      togAutoConvert: 'Авто-конвертація', togAutoScroll: 'Авто-скрол',
      togIncognito: 'Інкогніто',
      togAutoTranslateOnScroll: 'Автопереклад при прокручуванні',
      btnResetPos: '🏠 Скинути позицію', btnClearCache: '🧹 Очистити кеш',
      btnExport: '📤 Експорт', btnImport: '📥 Імпорт',
      btnSurprise: '🎲 Випадкова мова', btnPirate: '🏴‍☠️ Піратський',
      btnYoda: '🧙 Режим Йоди', btnHide: '👁 Сховати', btnShow: '👁 Показати',
      sliderDelay: 'Затримка запитів', unitMs: 'мс',
      statTranslations: 'Перекладів', statChars: 'Символів', statOnPage: 'На сторінці',
      histEmpty: 'Історія порожня', histClear: '🗑 Очистити',
      searchLang: 'Пошук мови…',
      btnOrig: '↩ Оригінал', btnCopy: '📋 Копіювати', btnSpeak: '🔊', btnRetry: '↺ Ще раз',
      copied: '✅ Скопійовано!', cacheCleared: '🧹 Кеш очищено',
      toastApply: '✅ Інтерфейс оновлено',
      toastSave: '💾 Збережено — перезавантаження…', toastSurprise: '🎲 Мова:',
      toastPirateOn: '🏴‍☠️ Arrr! Піратський режим!', toastPirateOff: '🏴‍☠️ Вимкнено',
      toastYodaOn: '🧙 Увімкнено режим, хммм.', toastYodaOff: '🧙 Вимкнено.',
      toastAutoScrollOn: '✅ Автопереклад при прокручуванні увімкнено', toastAutoScrollOff: '⭕ Автопереклад при прокручуванні вимкнено',
      shortcutHint: 'F2 = панель',
      secHotkeys: 'Гарячі клавіші', hotkeyPanel: 'Відкрити панель',
      hotkeyPress: 'Натисніть клавішу…', hotkeyReset: '↺ Скинути',
      secColors: 'Кольори теми', colorAcc: 'Акцент', colorTxt: 'Текст', colorBg: 'Фон', colorOk: 'Успіх',
      btnResetColors: '↺ Скинути кольори',
      secRequestLimits: 'Обмеження запитів',
      maxConcurrentRequests: 'Макс. одночасних запитів',
      maxRequestsPerSecond: 'Макс. запитів / сек',
      maxTextLengthPerRequest: 'Макс. символів / запит',
      maxParagraphsPerRequest: 'Макс. абзаців / запит',
      btnShowOriginals: '📄 ПОКАЗАТИ ОРИГІНАЛ',
      btnShowTranslations: '🌐 ПОКАЗАТИ ПЕРЕКЛАД',
      toastShowingOriginals: '📄 Показано оригінальний текст',
      toastShowingTranslations: '🌐 Показано перекладений текст',
    },
    en: {
      title: 'Reddit Translator', ver: 'v1.0.10',
      tabSettings: '⚙️ Settings', tabHistory: '📖 History', tabExtras: '✨ Extras',
      secUiLang: 'UI Language', applyUi: '✨ APPLY INTERFACE',
      secTargetLang: 'Target language', saveLang: '💾 SAVE LANGUAGE',
      secEngine: 'Engine', secTone: 'Translation tone', secTheme: 'Theme',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL API keys', saveDeepLApi: '🔑 SAVE DEEPL KEYS',
      btnTestDeepL: '🧪 TEST DEEPL',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Paste one or more DeepL API keys, separated by commas',
      deeplApiHelp: 'Supports both Pro and Free keys. Keys ending with :fx use deepl.com/pro-api automatically.',
      toastDeepLKeysSaved: '🔑 DeepL keys saved',
      toastDeepLKeysMissing: '⚠️ Add at least one DeepL API key first',
      toastDeepLUnsupported: '⚠️ DeepL does not support this target language:',
      toastDeepLTesting: '🧪 Testing DeepL API...',
      toastDeepLOk: '✅ DeepL API is available',
      toastDeepLFail: '❌ DeepL API is unavailable',
      toneNeutral: 'Neutral', toneFormal: 'Formal', toneSlang: 'Casual',
      themeDark: 'Dark', themeLight: 'Light', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Bilingual mode', togTts: 'Text-to-Speech',
      togAutoConvert: 'Auto-convert units', togAutoScroll: 'Auto-scroll to new',
      togIncognito: 'Incognito (no history)',
      togAutoTranslateOnScroll: 'Auto-translate on scroll',
      btnResetPos: '🏠 Reset position', btnClearCache: '🧹 Clear cache',
      btnExport: '📤 Export', btnImport: '📥 Import',
      btnSurprise: '🎲 Surprise me', btnPirate: '🏴‍☠️ Pirate mode',
      btnYoda: '🧙 Yoda mode', btnHide: '👁 Hide buttons', btnShow: '👁 Show buttons',
      sliderDelay: 'Request delay', unitMs: 'ms',
      statTranslations: 'Translations', statChars: 'Characters', statOnPage: 'On page',
      histEmpty: 'No history yet', histClear: '🗑 Clear history',
      searchLang: 'Search language…',
      btnOrig: '↩ Original', btnCopy: '📋 Copy', btnSpeak: '🔊', btnRetry: '↺ Retry',
      copied: '✅ Copied!', cacheCleared: '🧹 Cache cleared',
      toastApply: '✅ Interface updated',
      toastSave: '💾 Saved — reloading…', toastSurprise: '🎲 Language:',
      toastPirateOn: '🏴‍☠️ Arrr! Pirate mode on!', toastPirateOff: '🏴‍☠️ Pirate mode off',
      toastYodaOn: '🧙 Yoda mode on, hmm.', toastYodaOff: '🧙 Yoda mode off.',
      toastAutoScrollOn: '✅ Auto-translate on scroll enabled', toastAutoScrollOff: '⭕ Auto-translate on scroll disabled',
      shortcutHint: 'F2 = panel',
      secHotkeys: 'Hotkeys', hotkeyPanel: 'Open panel',
      hotkeyPress: 'Press a key…', hotkeyReset: '↺ Reset',
      secColors: 'Theme colors', colorAcc: 'Accent', colorTxt: 'Text', colorBg: 'Background', colorOk: 'Success',
      btnResetColors: '↺ Reset colors',
      secRequestLimits: 'Request limits',
      maxConcurrentRequests: 'Max concurrent requests',
      maxRequestsPerSecond: 'Max requests / sec',
      maxTextLengthPerRequest: 'Max chars / request',
      maxParagraphsPerRequest: 'Max paragraphs / request',
      btnShowOriginals: '📄 SHOW ORIGINAL',
      btnShowTranslations: '🌐 SHOW TRANSLATION',
      toastShowingOriginals: '📄 Showing original text',
      toastShowingTranslations: '🌐 Showing translated text',
    },
    de: {
      title: 'Reddit Übersetzer', ver: 'v1.0.10',
      tabSettings: '⚙️ Einstellungen', tabHistory: '📖 Verlauf', tabExtras: '✨ Extras',
      secUiLang: 'UI-Sprache', applyUi: '✨ INTERFACE ANWENDEN',
      secTargetLang: 'Zielsprache', saveLang: '💾 SPRACHE SPEICHERN',
      secEngine: 'Motor', secTone: 'Übersetzungsstil', secTheme: 'Thema',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL API-Schlüssel', saveDeepLApi: '🔑 DEEPL SPEICHERN',
      btnTestDeepL: '🧪 DEEPL TESTEN',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Einen oder mehrere DeepL API-Schlüssel einfügen, durch Kommas getrennt',
      deeplApiHelp: 'Unterstützt Pro- und Free-Schlüssel. Schlüssel, die auf :fx enden, verwenden automatisch deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 DeepL-Schlüssel gespeichert',
      toastDeepLKeysMissing: '⚠️ Bitte zuerst mindestens einen DeepL API-Schlüssel hinzufügen',
      toastDeepLUnsupported: '⚠️ DeepL unterstützt diese Zielsprache nicht:',
      toastDeepLTesting: '🧪 DeepL API wird getestet...',
      toastDeepLOk: '✅ DeepL API ist verfügbar',
      toastDeepLFail: '❌ DeepL API ist nicht verfügbar',
      toneNeutral: 'Neutral', toneFormal: 'Formell', toneSlang: 'Umgangssprachlich',
      themeDark: 'Dunkel', themeLight: 'Hell', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Zweisprachig', togTts: 'Sprachausgabe',
      togAutoConvert: 'Einheiten konvertieren', togAutoScroll: 'Auto-Scrollen',
      togIncognito: 'Inkognito',
      togAutoTranslateOnScroll: 'Automatisch beim Scrollen übersetzen',
      btnResetPos: '🏠 Position reset', btnClearCache: '🧹 Cache leeren',
      btnExport: '📤 Exportieren', btnImport: '📥 Importieren',
      btnSurprise: '🎲 Überrasch mich', btnPirate: '🏴‍☠️ Piraten-Modus',
      btnYoda: '🧙 Yoda-Modus', btnHide: '👁 Ausblenden', btnShow: '👁 Anzeigen',
      sliderDelay: 'Anfrageverzögerung', unitMs: 'ms',
      statTranslations: 'Übersetzungen', statChars: 'Zeichen', statOnPage: 'Auf Seite',
      histEmpty: 'Kein Verlauf', histClear: '🗑 Verlauf löschen',
      searchLang: 'Sprache suchen…',
      btnOrig: '↩ Original', btnCopy: '📋 Kopieren', btnSpeak: '🔊', btnRetry: '↺ Nochmal',
      copied: '✅ Kopiert!', cacheCleared: '🧹 Cache geleert',
      toastApply: '✅ Interface aktualisiert',
      toastSave: '💾 Gespeichert — neu laden…', toastSurprise: '🎲 Sprache:',
      toastPirateOn: '🏴‍☠️ Arrr! Piraten-Modus!', toastPirateOff: '🏴‍☠️ Modus aus',
      toastYodaOn: '🧙 Yoda-Modus an, hmm.', toastYodaOff: '🧙 Modus aus.',
      toastAutoScrollOn: '✅ Automatisches Übersetzen beim Scrollen aktiviert', toastAutoScrollOff: '⭕ Automatisches Übersetzen beim Scrollen deaktiviert',
      shortcutHint: 'F2 = Panel',
      secHotkeys: 'Tastenkürzel', hotkeyPanel: 'Panel öffnen',
      hotkeyPress: 'Taste drücken…', hotkeyReset: '↺ Zurücksetzen',
      secColors: 'Themenfarben', colorAcc: 'Akzent', colorTxt: 'Text', colorBg: 'Hintergrund', colorOk: 'Erfolg',
      btnResetColors: '↺ Farben zurücksetzen',
      secRequestLimits: 'Anfragelimits',
      maxConcurrentRequests: 'Max. gleichzeitige Anfragen',
      maxRequestsPerSecond: 'Max. Anfragen / Sek.',
      maxTextLengthPerRequest: 'Max. Zeichen / Anfrage',
      maxParagraphsPerRequest: 'Max. Absätze / Anfrage',
      btnShowOriginals: '📄 ORIGINAL ANZEIGEN',
      btnShowTranslations: '🌐 ÜBERSETZUNG ANZEIGEN',
      toastShowingOriginals: '📄 Originaltext wird angezeigt',
      toastShowingTranslations: '🌐 Übersetzung wird angezeigt',
    },
    fr: {
      title: 'Traducteur Reddit', ver: 'v1.0.10',
      tabSettings: '⚙️ Paramètres', tabHistory: '📖 Historique', tabExtras: '✨ Extras',
      secUiLang: 'Langue UI', applyUi: '✨ APPLIQUER INTERFACE',
      secTargetLang: 'Langue cible', saveLang: '💾 ENREGISTRER',
      secEngine: 'Moteur', secTone: 'Style', secTheme: 'Thème',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Clés API DeepL', saveDeepLApi: '🔑 ENREGISTRER DEEPL',
      btnTestDeepL: '🧪 TESTER DEEPL',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Collez une ou plusieurs clés API DeepL, séparées par des virgules',
      deeplApiHelp: 'Prend en charge les clés Pro et Free. Les clés se terminant par :fx utilisent automatiquement deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Clés DeepL enregistrées',
      toastDeepLKeysMissing: '⚠️ Ajoutez d\'abord au moins une clé API DeepL',
      toastDeepLUnsupported: '⚠️ DeepL ne prend pas en charge cette langue cible :',
      toastDeepLTesting: '🧪 Test de l\'API DeepL...',
      toastDeepLOk: '✅ API DeepL disponible',
      toastDeepLFail: '❌ API DeepL indisponible',
      toneNeutral: 'Neutre', toneFormal: 'Formel', toneSlang: 'Familier',
      themeDark: 'Sombre', themeLight: 'Clair', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Mode bilingue', togTts: 'Synthèse vocale',
      togAutoConvert: 'Conversion auto', togAutoScroll: 'Défilement auto',
      togIncognito: 'Incognito',
      togAutoTranslateOnScroll: 'Traduction automatique au défilement',
      btnResetPos: '🏠 Réinitialiser', btnClearCache: '🧹 Vider cache',
      btnExport: '📤 Exporter', btnImport: '📥 Importer',
      btnSurprise: '🎲 Surprends-moi', btnPirate: '🏴‍☠️ Mode Pirate',
      btnYoda: '🧙 Mode Yoda', btnHide: '👁 Masquer', btnShow: '👁 Afficher',
      sliderDelay: 'Délai de requête', unitMs: 'ms',
      statTranslations: 'Traductions', statChars: 'Caractères', statOnPage: 'Sur page',
      histEmpty: 'Historique vide', histClear: '🗑 Effacer',
      searchLang: 'Chercher langue…',
      btnOrig: '↩ Original', btnCopy: '📋 Copier', btnSpeak: '🔊', btnRetry: '↺ Réessayer',
      copied: '✅ Copié!', cacheCleared: '🧹 Cache vidé',
      toastApply: '✅ Interface mise à jour',
      toastSave: '💾 Sauvegardé — rechargement…', toastSurprise: '🎲 Langue:',
      toastPirateOn: '🏴‍☠️ Arrr! Mode Pirate!', toastPirateOff: '🏴‍☠️ Mode désactivé',
      toastYodaOn: '🧙 Mode Yoda activé, hmm.', toastYodaOff: '🧙 Mode Yoda désactivé.',
      toastAutoScrollOn: '✅ Traduction automatique au défilement activée', toastAutoScrollOff: '⭕ Traduction automatique au défilement désactivée',
      shortcutHint: 'F2 = panneau',
      secHotkeys: 'Raccourcis', hotkeyPanel: 'Ouvrir panneau',
      hotkeyPress: 'Appuyez sur une touche…', hotkeyReset: '↺ Réinitialiser',
      secColors: 'Couleurs du thème', colorAcc: 'Accent', colorTxt: 'Texte', colorBg: 'Fond', colorOk: 'Succès',
      btnResetColors: '↺ Réinitialiser couleurs',
      secRequestLimits: 'Limites de requêtes',
      maxConcurrentRequests: 'Max. requêtes simultanées',
      maxRequestsPerSecond: 'Max. requêtes / sec',
      maxTextLengthPerRequest: 'Max. caractères / requête',
      maxParagraphsPerRequest: 'Max. paragraphes / requête',
      btnShowOriginals: '📄 AFFICHER L\'ORIGINAL',
      btnShowTranslations: '🌐 AFFICHER LA TRADUCTION',
      toastShowingOriginals: '📄 Texte original affiché',
      toastShowingTranslations: '🌐 Traduction affichée',
    },
    es: {
      title: 'Traductor Reddit', ver: 'v1.0.10',
      tabSettings: '⚙️ Config', tabHistory: '📖 Historial', tabExtras: '✨ Extras',
      secUiLang: 'Idioma UI', applyUi: '✨ APLICAR INTERFAZ',
      secTargetLang: 'Idioma destino', saveLang: '💾 GUARDAR IDIOMA',
      secEngine: 'Motor', secTone: 'Estilo', secTheme: 'Tema',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Claves API de DeepL', saveDeepLApi: '🔑 GUARDAR DEEPL',
      btnTestDeepL: '🧪 PROBAR DEEPL',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Pegue una o más claves API de DeepL, separadas por comas',
      deeplApiHelp: 'Compatible con claves Pro y Free. Las claves que terminan en :fx usan automáticamente deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Claves DeepL guardadas',
      toastDeepLKeysMissing: '⚠️ Primero añada al menos una clave API de DeepL',
      toastDeepLUnsupported: '⚠️ DeepL no admite este idioma de destino:',
      toastDeepLTesting: '🧪 Probando API de DeepL...',
      toastDeepLOk: '✅ API de DeepL disponible',
      toastDeepLFail: '❌ API de DeepL no disponible',
      toneNeutral: 'Neutral', toneFormal: 'Formal', toneSlang: 'Coloquial',
      themeDark: 'Oscuro', themeLight: 'Claro', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Modo bilingüe', togTts: 'Texto a voz',
      togAutoConvert: 'Convertir unidades', togAutoScroll: 'Auto-desplazamiento',
      togIncognito: 'Incógnito',
      togAutoTranslateOnScroll: 'Traducción automática al desplazar',
      btnResetPos: '🏠 Resetear', btnClearCache: '🧹 Limpiar caché',
      btnExport: '📤 Exportar', btnImport: '📥 Importar',
      btnSurprise: '🎲 Sorpréndeme', btnPirate: '🏴‍☠️ Modo Pirata',
      btnYoda: '🧙 Modo Yoda', btnHide: '👁 Ocultar', btnShow: '👁 Mostrar',
      sliderDelay: 'Retraso de solicitud', unitMs: 'ms',
      statTranslations: 'Traducciones', statChars: 'Caracteres', statOnPage: 'En página',
      histEmpty: 'Sin historial', histClear: '🗑 Borrar',
      searchLang: 'Buscar idioma…',
      btnOrig: '↩ Original', btnCopy: '📋 Copiar', btnSpeak: '🔊', btnRetry: '↺ Reintentar',
      copied: '✅ ¡Copiado!', cacheCleared: '🧹 Caché limpiado',
      toastApply: '✅ Interfaz actualizada',
      toastSave: '💾 Guardado — recargando…', toastSurprise: '🎲 Idioma:',
      toastPirateOn: '🏴‍☠️ ¡Arrr! ¡Modo Pirata!', toastPirateOff: '🏴‍☠️ Modo desactivado',
      toastYodaOn: '🧙 Modo Yoda activado, hmm.', toastYodaOff: '🧙 Modo Yoda desactivado.',
      toastAutoScrollOn: '✅ Traducción automática al desplazar activada', toastAutoScrollOff: '⭕ Traducción automática al desplazar desactivada',
      shortcutHint: 'F2 = panel',
      secHotkeys: 'Atajos', hotkeyPanel: 'Abrir panel',
      hotkeyPress: 'Presiona una tecla…', hotkeyReset: '↺ Restablecer',
      secColors: 'Colores del tema', colorAcc: 'Acento', colorTxt: 'Texto', colorBg: 'Fondo', colorOk: 'Éxito',
      btnResetColors: '↺ Restablecer colores',
      secRequestLimits: 'Límites de solicitudes',
      maxConcurrentRequests: 'Máx. solicitudes simultáneas',
      maxRequestsPerSecond: 'Máx. solicitudes / seg',
      maxTextLengthPerRequest: 'Máx. caracteres / solicitud',
      maxParagraphsPerRequest: 'Máx. párrafos / solicitud',
      btnShowOriginals: '📄 MOSTRAR ORIGINAL',
      btnShowTranslations: '🌐 MOSTRAR TRADUCCIÓN',
      toastShowingOriginals: '📄 Mostrando texto original',
      toastShowingTranslations: '🌐 Mostrando traducción',
    },
    pl: {
      title: 'Tłumacz Reddit', ver: 'v1.0.10',
      tabSettings: '⚙️ Ustawienia', tabHistory: '📖 Historia', tabExtras: '✨ Extras',
      secUiLang: 'Język interfejsu', applyUi: '✨ ZASTOSUJ INTERFEJS',
      secTargetLang: 'Język docelowy', saveLang: '💾 ZAPISZ JĘZYK',
      secEngine: 'Silnik', secTone: 'Styl', secTheme: 'Motyw',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Klucze API DeepL', saveDeepLApi: '🔑 ZAPISZ KLUCZE DEEPL',
      btnTestDeepL: '🧪 TESTUJ DEEPL',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Wklej jeden lub więcej kluczy API DeepL, oddzielonych przecinkami',
      deeplApiHelp: 'Obsługuje klucze Pro i Free. Klucze kończące się na :fx automatycznie używają deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Klucze DeepL zapisane',
      toastDeepLKeysMissing: '⚠️ Najpierw dodaj co najmniej jeden klucz API DeepL',
      toastDeepLUnsupported: '⚠️ DeepL nie obsługuje tego języka docelowego:',
      toastDeepLTesting: '🧪 Testowanie API DeepL...',
      toastDeepLOk: '✅ API DeepL dostępne',
      toastDeepLFail: '❌ API DeepL niedostępne',
      toneNeutral: 'Neutralny', toneFormal: 'Formalny', toneSlang: 'Potoczny',
      themeDark: 'Ciemny', themeLight: 'Jasny', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Tryb dwujęzyczny', togTts: 'Mowa syntetyczna',
      togAutoConvert: 'Auto-konwersja', togAutoScroll: 'Auto-przewijanie',
      togIncognito: 'Incognito',
      togAutoTranslateOnScroll: 'Automatyczne tłumaczenie podczas przewijania',
      btnResetPos: '🏠 Resetuj pozycję', btnClearCache: '🧹 Wyczyść cache',
      btnExport: '📤 Eksport', btnImport: '📥 Import',
      btnSurprise: '🎲 Losowy język', btnPirate: '🏴‍☠️ Tryb Pirata',
      btnYoda: '🧙 Tryb Yody', btnHide: '👁 Ukryj', btnShow: '👁 Pokaż',
      sliderDelay: 'Opóźnienie żądania', unitMs: 'ms',
      statTranslations: 'Tłumaczenia', statChars: 'Znaki', statOnPage: 'Na stronie',
      histEmpty: 'Brak historii', histClear: '🗑 Wyczyść',
      searchLang: 'Szukaj języka…',
      btnOrig: '↩ Oryginał', btnCopy: '📋 Kopiuj', btnSpeak: '🔊', btnRetry: '↺ Ponów',
      copied: '✅ Skopiowano!', cacheCleared: '🧹 Cache wyczyszczony',
      toastApply: '✅ Interfejs zaktualizowany',
      toastSave: '💾 Zapisano — przeładowanie…', toastSurprise: '🎲 Język:',
      toastPirateOn: '🏴‍☠️ Arrr! Tryb Pirata!', toastPirateOff: '🏴‍☠️ Tryb wyłączony',
      toastYodaOn: '🧙 Tryb Yody włączony, hmm.', toastYodaOff: '🧙 Tryb wyłączony.',
      toastAutoScrollOn: '✅ Automatyczne tłumaczenie podczas przewijania włączone', toastAutoScrollOff: '⭕ Automatyczne tłumaczenie podczas przewijania wyłączone',
      shortcutHint: 'F2 = panel',
      secHotkeys: 'Skróty klawiszowe', hotkeyPanel: 'Otwórz panel',
      hotkeyPress: 'Naciśnij klawisz…', hotkeyReset: '↺ Resetuj',
      secColors: 'Kolory motywu', colorAcc: 'Akcent', colorTxt: 'Tekst', colorBg: 'Tło', colorOk: 'Sukces',
      btnResetColors: '↺ Resetuj kolory',
      secRequestLimits: 'Limity żądań',
      maxConcurrentRequests: 'Maks. równoległych żądań',
      maxRequestsPerSecond: 'Maks. żądań / sek',
      maxTextLengthPerRequest: 'Maks. znaków / żądanie',
      maxParagraphsPerRequest: 'Maks. akapitów / żądanie',
      btnShowOriginals: '📄 POKAŻ ORYGINAŁ',
      btnShowTranslations: '🌐 POKAŻ TŁUMACZENIE',
      toastShowingOriginals: '📄 Wyświetlany tekst oryginalny',
      toastShowingTranslations: '🌐 Wyświetlane tłumaczenie',
    },
    tr: {
      title: 'Reddit Çevirmeni', ver: 'v1.0.10',
      tabSettings: '⚙️ Ayarlar', tabHistory: '📖 Geçmiş', tabExtras: '✨ Ekstra',
      secUiLang: 'Arayüz dili', applyUi: '✨ ARAYÜZÜ UYGULA',
      secTargetLang: 'Hedef dil', saveLang: '💾 DİLİ KAYDET',
      secEngine: 'Motor', secTone: 'Stil', secTheme: 'Tema',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL API Anahtarları', saveDeepLApi: '🔑 DEEPL KAYDET',
      btnTestDeepL: '🧪 DEEPL TEST ET',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Virgülle ayrılmış bir veya daha fazla DeepL API anahtarı yapıştırın',
      deeplApiHelp: 'Pro ve Free anahtarları destekler. :fx ile biten anahtarlar otomatik olarak deepl.com/pro-api kullanır.',
      toastDeepLKeysSaved: '🔑 DeepL anahtarları kaydedildi',
      toastDeepLKeysMissing: '⚠️ Önce en az bir DeepL API anahtarı ekleyin',
      toastDeepLUnsupported: '⚠️ DeepL bu hedef dili desteklemiyor:',
      toastDeepLTesting: '🧪 DeepL API test ediliyor...',
      toastDeepLOk: '✅ DeepL API kullanılabilir',
      toastDeepLFail: '❌ DeepL API kullanılamıyor',
      toneNeutral: 'Nötr', toneFormal: 'Resmi', toneSlang: 'Günlük',
      themeDark: 'Koyu', themeLight: 'Açık', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Çift dil modu', togTts: 'Metin okuma',
      togAutoConvert: 'Otomatik dönüştürme', togAutoScroll: 'Otomatik kaydırma',
      togIncognito: 'Gizli mod',
      togAutoTranslateOnScroll: 'Kaydırırken otomatik çeviri',
      btnResetPos: '🏠 Konumu sıfırla', btnClearCache: '🧹 Önbelleği temizle',
      btnExport: '📤 Dışa aktar', btnImport: '📥 İçe aktar',
      btnSurprise: '🎲 Rastgele', btnPirate: '🏴‍☠️ Korsan modu',
      btnYoda: '🧙 Yoda modu', btnHide: '👁 Gizle', btnShow: '👁 Göster',
      sliderDelay: 'İstek gecikmesi', unitMs: 'ms',
      statTranslations: 'Çeviriler', statChars: 'Karakterler', statOnPage: 'Sayfada',
      histEmpty: 'Geçmiş yok', histClear: '🗑 Temizle',
      searchLang: 'Dil ara…',
      btnOrig: '↩ Orijinal', btnCopy: '📋 Kopyala', btnSpeak: '🔊', btnRetry: '↺ Tekrar',
      copied: '✅ Kopyalandı!', cacheCleared: '🧹 Önbellek temizlendi',
      toastApply: '✅ Arayüz güncellendi',
      toastSave: '💾 Kaydedildi — yenileniyor…', toastSurprise: '🎲 Dil:',
      toastPirateOn: '🏴‍☠️ Arrr! Korsan modu!', toastPirateOff: '🏴‍☠️ Mod kapatıldı',
      toastYodaOn: '🧙 Yoda modu açık, hmm.', toastYodaOff: '🧙 Yoda modu kapalı.',
      toastAutoScrollOn: '✅ Kaydırırken otomatik çeviri etkin', toastAutoScrollOff: '⭕ Kaydırırken otomatik çeviri devre dışı',
      shortcutHint: 'F2 = panel',
      secHotkeys: 'Kısayollar', hotkeyPanel: 'Paneli aç',
      hotkeyPress: 'Bir tuşa basın…', hotkeyReset: '↺ Sıfırla',
      secColors: 'Tema renkleri', colorAcc: 'Vurgu', colorTxt: 'Metin', colorBg: 'Arka plan', colorOk: 'Başarı',
      btnResetColors: '↺ Renkleri sıfırla',
      secRequestLimits: 'İstek limitleri',
      maxConcurrentRequests: 'Maks. eşzamanlı istek',
      maxRequestsPerSecond: 'Maks. istek / sn',
      maxTextLengthPerRequest: 'Maks. karakter / istek',
      maxParagraphsPerRequest: 'Maks. paragraf / istek',
      btnShowOriginals: '📄 ORİJİNALİ GÖSTER',
      btnShowTranslations: '🌐 ÇEVİRİYİ GÖSTER',
      toastShowingOriginals: '📄 Orijinal metin gösteriliyor',
      toastShowingTranslations: '🌐 Çeviri gösteriliyor',
    },
    zh: {
      title: 'Reddit翻译器', ver: 'v1.0.10',
      tabSettings: '⚙️ 设置', tabHistory: '📖 历史', tabExtras: '✨ 更多',
      secUiLang: '界面语言', applyUi: '✨ 应用界面',
      secTargetLang: '目标语言', saveLang: '💾 保存语言',
      secEngine: '引擎', secTone: '风格', secTheme: '主题',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL API 密钥', saveDeepLApi: '🔑 保存 DeepL 密钥',
      btnTestDeepL: '🧪 测试 DeepL',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: '粘贴一个或多个 DeepL API 密钥,使用英文逗号分隔',
      deeplApiHelp: '同时支持 Pro 和 Free 密钥。以 :fx 结尾的密钥会自动使用 deepl.com/pro-api。',
      toastDeepLKeysSaved: '🔑 DeepL 密钥已保存',
      toastDeepLKeysMissing: '⚠️ 请先添加至少一个 DeepL API 密钥',
      toastDeepLUnsupported: '⚠️ DeepL 暂不支持该目标语言:',
      toastDeepLTesting: '🧪 正在测试 DeepL API...',
      toastDeepLOk: '✅ DeepL API 可用',
      toastDeepLFail: '❌ DeepL API 不可用',
      toneNeutral: '中性', toneFormal: '正式', toneSlang: '口语',
      themeDark: '深色', themeLight: '浅色', themeCyber: '赛博朋克', themeDracula: '德古拉',
      togBilingual: '双语模式', togTts: '文字转语音',
      togAutoConvert: '自动单位转换', togAutoScroll: '自动滚动',
      togIncognito: '隐身模式',
      togAutoTranslateOnScroll: '滚动时自动翻译',
      btnResetPos: '🏠 重置位置', btnClearCache: '🧹 清除缓存',
      btnExport: '📤 导出', btnImport: '📥 导入',
      btnSurprise: '🎲 随机语言', btnPirate: '🏴‍☠️ 海盗模式',
      btnYoda: '🧙 尤达模式', btnHide: '👁 隐藏', btnShow: '👁 显示',
      sliderDelay: '请求延迟', unitMs: '毫秒',
      statTranslations: '翻译', statChars: '字符', statOnPage: '页面上',
      histEmpty: '暂无历史', histClear: '🗑 清除历史',
      searchLang: '搜索语言…',
      btnOrig: '↩ 原文', btnCopy: '📋 复制', btnSpeak: '🔊', btnRetry: '↺ 重试',
      copied: '✅ 已复制!', cacheCleared: '🧹 缓存已清除',
      toastApply: '✅ 界面已更新',
      toastSave: '💾 已保存 — 重新加载…', toastSurprise: '🎲 语言:',
      toastPirateOn: '🏴‍☠️ Arrr! 海盗模式!', toastPirateOff: '🏴‍☠️ 模式关闭',
      toastYodaOn: '🧙 尤达模式已开启,嗯。', toastYodaOff: '🧙 尤达模式已关闭。',
      toastAutoScrollOn: '✅ 滚动时自动翻译已启用', toastAutoScrollOff: '⭕ 滚动时自动翻译已禁用',
      shortcutHint: 'F2 = 面板',
      secHotkeys: '快捷键', hotkeyPanel: '打开面板',
      hotkeyPress: '按下一个键…', hotkeyReset: '↺ 重置',
      secColors: '主题颜色', colorAcc: '强调色', colorTxt: '文字', colorBg: '背景', colorOk: '成功',
      btnResetColors: '↺ 重置颜色',
      secRequestLimits: '请求限制',
      maxConcurrentRequests: '最大并发请求数',
      maxRequestsPerSecond: '每秒最大请求数',
      maxTextLengthPerRequest: '每次请求最大文本长度',
      maxParagraphsPerRequest: '每次请求最大段落数',
      btnShowOriginals: '📄 显示原文',
      btnShowTranslations: '🌐 显示译文',
      toastShowingOriginals: '📄 当前显示原文',
      toastShowingTranslations: '🌐 当前显示译文',
    },
    ja: {
      title: 'Reddit翻訳', ver: 'v1.0.10',
      tabSettings: '⚙️ 設定', tabHistory: '📖 履歴', tabExtras: '✨ その他',
      secUiLang: 'UI言語', applyUi: '✨ UIを適用',
      secTargetLang: '翻訳先言語', saveLang: '💾 言語を保存',
      secEngine: 'エンジン', secTone: 'スタイル', secTheme: 'テーマ',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL APIキー', saveDeepLApi: '🔑 DEEPL キーを保存',
      btnTestDeepL: '🧪 DEEPL テスト',
      btnToggleSecretsShow: '👁',
      btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'DeepL APIキーをカンマ区切りで1つ以上貼り付けてください',
      deeplApiHelp: 'ProおよびFreeキーに対応。:fx で終わるキーは自動的に deepl.com/pro-api を使用します。',
      toastDeepLKeysSaved: '🔑 DeepLキーを保存しました',
      toastDeepLKeysMissing: '⚠️ まずDeepL APIキーを1つ以上追加してください',
      toastDeepLUnsupported: '⚠️ DeepLはこのターゲット言語に対応していません:',
      toastDeepLTesting: '🧪 DeepL APIをテスト中...',
      toastDeepLOk: '✅ DeepL APIは利用可能です',
      toastDeepLFail: '❌ DeepL APIは利用できません',
      toneNeutral: '標準', toneFormal: '公式', toneSlang: 'くだけた',
      themeDark: 'ダーク', themeLight: 'ライト', themeCyber: 'サイバーパンク', themeDracula: 'ドラキュラ',
      togBilingual: 'バイリンガルモード', togTts: '音声合成',
      togAutoConvert: '単位自動変換', togAutoScroll: '自動スクロール',
      togIncognito: 'シークレット',
      togAutoTranslateOnScroll: 'スクロール時に自動翻訳',
      btnResetPos: '🏠 位置リセット', btnClearCache: '🧹 キャッシュ削除',
      btnExport: '📤 エクスポート', btnImport: '📥 インポート',
      btnSurprise: '🎲 ランダム言語', btnPirate: '🏴‍☠️ 海賊モード',
      btnYoda: '🧙 ヨーダモード', btnHide: '👁 ボタンを隠す', btnShow: '👁 ボタンを表示',
      sliderDelay: 'リクエスト遅延', unitMs: 'ms',
      statTranslations: '翻訳数', statChars: '文字数', statOnPage: 'ページ上',
      histEmpty: '履歴なし', histClear: '🗑 履歴を消去',
      searchLang: '言語を検索…',
      btnOrig: '↩ 元テキスト', btnCopy: '📋 コピー', btnSpeak: '🔊', btnRetry: '↺ やり直し',
      copied: '✅ コピーしました!', cacheCleared: '🧹 キャッシュ削除済み',
      toastApply: '✅ UIを更新しました',
      toastSave: '💾 保存しました — 再読込中…', toastSurprise: '🎲 言語:',
      toastPirateOn: '🏴‍☠️ Arrr! 海賊モード!', toastPirateOff: '🏴‍☠️ モードオフ',
      toastYodaOn: '🧙 ヨーダモードオン、ふむ。', toastYodaOff: '🧙 ヨーダモードオフ。',
      toastAutoScrollOn: '✅ スクロール時に自動翻訳が有効', toastAutoScrollOff: '⭕ スクロール時に自動翻訳が無効',
      shortcutHint: 'F2 = パネル',
      secHotkeys: 'ショートカット', hotkeyPanel: 'パネルを開く',
      hotkeyPress: 'キーを押してください…', hotkeyReset: '↺ リセット',
      secColors: 'テーマカラー', colorAcc: 'アクセント', colorTxt: 'テキスト', colorBg: '背景', colorOk: '成功',
      btnResetColors: '↺ 色をリセット',
      secRequestLimits: 'リクエスト制限',
      maxConcurrentRequests: '最大同時リクエスト数',
      maxRequestsPerSecond: '最大リクエスト数 / 秒',
      maxTextLengthPerRequest: '最大文字数 / リクエスト',
      maxParagraphsPerRequest: '最大段落数 / リクエスト',
      btnShowOriginals: '📄 原文を表示',
      btnShowTranslations: '🌐 翻訳を表示',
      toastShowingOriginals: '🌐 翻訳を表示中',
      toastShowingTranslations: '🌐 翻訳を表示中',
    },
    it: {
      title: 'Reddit Traduttore', ver: 'v1.0.10',
      tabSettings: '⚙️ Impostazioni', tabHistory: '📖 Cronologia', tabExtras: '✨ Extra',
      secUiLang: 'Lingua interfaccia', applyUi: '✨ APPLICA INTERFACCIA',
      secTargetLang: 'Lingua di destinazione', saveLang: '💾 SALVA LINGUA',
      secEngine: 'Motore', secTone: 'Tono di traduzione', secTheme: 'Tema',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Chiavi API DeepL', saveDeepLApi: '🔑 SALVA CHIAVI DEEPL',
      btnTestDeepL: '🧪 TESTA DEEPL',
      btnToggleSecretsShow: '👁', btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Incolla una o più chiavi API DeepL, separate da virgole',
      deeplApiHelp: 'Supporta chiavi Pro e Free. Le chiavi che terminano con :fx usano automaticamente deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Chiavi DeepL salvate',
      toastDeepLKeysMissing: '⚠️ Aggiungi prima almeno una chiave API DeepL',
      toastDeepLUnsupported: '⚠️ DeepL non supporta questa lingua di destinazione:',
      toastDeepLTesting: '🧪 Test API DeepL in corso...', toastDeepLOk: '✅ API DeepL disponibile', toastDeepLFail: '❌ API DeepL non disponibile',
      toneNeutral: 'Neutro', toneFormal: 'Formale', toneSlang: 'Colloquiale',
      themeDark: 'Scuro', themeLight: 'Chiaro', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Modalità bilingue', togTts: 'Sintesi vocale',
      togAutoConvert: 'Conversione unità automatica', togAutoScroll: 'Scorri automaticamente',
      togIncognito: 'Incognito (nessuna cronologia)',
      togAutoTranslateOnScroll: 'Traduci automaticamente allo scroll',
      btnResetPos: '🏠 Ripristina posizione', btnClearCache: '🧹 Svuota cache',
      btnExport: '📤 Esporta', btnImport: '📥 Importa',
      btnSurprise: '🎲 Sorprendimi', btnPirate: '🏴‍☠️ Modalità pirata',
      btnYoda: '🧙 Modalità Yoda', btnHide: '👁 Nascondi pulsanti', btnShow: '👁 Mostra pulsanti',
      sliderDelay: 'Ritardo richieste', unitMs: 'ms',
      statTranslations: 'Traduzioni', statChars: 'Caratteri', statOnPage: 'In pagina',
      histEmpty: 'Nessuna cronologia', histClear: '🗑 Cancella cronologia',
      searchLang: 'Cerca lingua…',
      btnOrig: '↩ Originale', btnCopy: '📋 Copia', btnSpeak: '🔊', btnRetry: '↺ Riprova',
      copied: '✅ Copiato!', cacheCleared: '🧹 Cache svuotata',
      toastApply: '✅ Interfaccia aggiornata',
      toastSave: '💾 Salvato — ricaricamento…', toastSurprise: '🎲 Lingua:',
      toastPirateOn: '🏴‍☠️ Arrr! Modalità pirata attiva!', toastPirateOff: '🏴‍☠️ Modalità pirata disattivata',
      toastYodaOn: '🧙 Modalità Yoda attiva, hmm.', toastYodaOff: '🧙 Modalità Yoda disattivata.',
      toastAutoScrollOn: '✅ Traduzione automatica allo scroll attiva', toastAutoScrollOff: '⭕ Traduzione automatica allo scroll disattivata',
      shortcutHint: 'F2 = pannello',
      secHotkeys: 'Tasti rapidi', hotkeyPanel: 'Apri pannello',
      hotkeyPress: 'Premi un tasto…', hotkeyReset: '↺ Reimposta',
      secColors: 'Colori tema', colorAcc: 'Accento', colorTxt: 'Testo', colorBg: 'Sfondo', colorOk: 'Successo',
      btnResetColors: '↺ Reimposta colori',
      secRequestLimits: 'Limiti richieste',
      maxConcurrentRequests: 'Max richieste simultanee',
      maxRequestsPerSecond: 'Max richieste / sec',
      maxTextLengthPerRequest: 'Max caratteri / richiesta',
      maxParagraphsPerRequest: 'Max paragrafi / richiesta',
      btnShowOriginals: '📄 MOSTRA ORIGINALE',
      btnShowTranslations: '🌐 MOSTRA TRADUZIONE',
      toastShowingOriginals: '📄 Testo originale visualizzato',
      toastShowingTranslations: '🌐 Traduzione visualizzata',
    },
    pt: {
      title: 'Reddit Tradutor', ver: 'v1.0.10',
      tabSettings: '⚙️ Configurações', tabHistory: '📖 Histórico', tabExtras: '✨ Extras',
      secUiLang: 'Idioma da interface', applyUi: '✨ APLICAR INTERFACE',
      secTargetLang: 'Idioma de destino', saveLang: '💾 SALVAR IDIOMA',
      secEngine: 'Motor', secTone: 'Tom de tradução', secTheme: 'Tema',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Chaves API DeepL', saveDeepLApi: '🔑 SALVAR CHAVES DEEPL',
      btnTestDeepL: '🧪 TESTAR DEEPL',
      btnToggleSecretsShow: '👁', btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Cole uma ou mais chaves API DeepL, separadas por vírgulas',
      deeplApiHelp: 'Suporta chaves Pro e Free. Chaves terminadas em :fx usam automaticamente deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Chaves DeepL salvas',
      toastDeepLKeysMissing: '⚠️ Adicione pelo menos uma chave API DeepL primeiro',
      toastDeepLUnsupported: '⚠️ DeepL não suporta este idioma de destino:',
      toastDeepLTesting: '🧪 Testando API DeepL...', toastDeepLOk: '✅ API DeepL disponível', toastDeepLFail: '❌ API DeepL indisponível',
      toneNeutral: 'Neutro', toneFormal: 'Formal', toneSlang: 'Informal',
      themeDark: 'Escuro', themeLight: 'Claro', themeCyber: 'Cyberpunk', themeDracula: 'Drácula',
      togBilingual: 'Modo bilíngue', togTts: 'Texto para fala',
      togAutoConvert: 'Converter unidades automaticamente', togAutoScroll: 'Rolar automaticamente',
      togIncognito: 'Incógnito (sem histórico)',
      togAutoTranslateOnScroll: 'Traduzir automaticamente ao rolar',
      btnResetPos: '🏠 Redefinir posição', btnClearCache: '🧹 Limpar cache',
      btnExport: '📤 Exportar', btnImport: '📥 Importar',
      btnSurprise: '🎲 Surpreenda-me', btnPirate: '🏴‍☠️ Modo pirata',
      btnYoda: '🧙 Modo Yoda', btnHide: '👁 Ocultar botões', btnShow: '👁 Mostrar botões',
      sliderDelay: 'Atraso de solicitação', unitMs: 'ms',
      statTranslations: 'Traduções', statChars: 'Caracteres', statOnPage: 'Na página',
      histEmpty: 'Sem histórico', histClear: '🗑 Limpar histórico',
      searchLang: 'Pesquisar idioma…',
      btnOrig: '↩ Original', btnCopy: '📋 Copiar', btnSpeak: '🔊', btnRetry: '↺ Tentar novamente',
      copied: '✅ Copiado!', cacheCleared: '🧹 Cache limpo',
      toastApply: '✅ Interface atualizada',
      toastSave: '💾 Salvo — recarregando…', toastSurprise: '🎲 Idioma:',
      toastPirateOn: '🏴‍☠️ Arrr! Modo pirata ativado!', toastPirateOff: '🏴‍☠️ Modo pirata desativado',
      toastYodaOn: '🧙 Modo Yoda ativado, hmm.', toastYodaOff: '🧙 Modo Yoda desativado.',
      toastAutoScrollOn: '✅ Tradução automática ao rolar ativada', toastAutoScrollOff: '⭕ Tradução automática ao rolar desativada',
      shortcutHint: 'F2 = painel',
      secHotkeys: 'Atalhos', hotkeyPanel: 'Abrir painel',
      hotkeyPress: 'Pressione uma tecla…', hotkeyReset: '↺ Redefinir',
      secColors: 'Cores do tema', colorAcc: 'Destaque', colorTxt: 'Texto', colorBg: 'Fundo', colorOk: 'Sucesso',
      btnResetColors: '↺ Redefinir cores',
      secRequestLimits: 'Limites de solicitações',
      maxConcurrentRequests: 'Máx. solicitações simultâneas',
      maxRequestsPerSecond: 'Máx. solicitações / seg',
      maxTextLengthPerRequest: 'Máx. caracteres / solicitação',
      maxParagraphsPerRequest: 'Máx. parágrafos / solicitação',
      btnShowOriginals: '📄 MOSTRAR ORIGINAL',
      btnShowTranslations: '🌐 MOSTRAR TRADUÇÃO',
      toastShowingOriginals: '📄 Mostrando texto original',
      toastShowingTranslations: '🌐 Mostrando tradução',
    },
    ko: {
      title: 'Reddit 번역기', ver: 'v1.0.10',
      tabSettings: '⚙️ 설정', tabHistory: '📖 기록', tabExtras: '✨ 기타',
      secUiLang: 'UI 언어', applyUi: '✨ 인터페이스 적용',
      secTargetLang: '번역 대상 언어', saveLang: '💾 언어 저장',
      secEngine: '엔진', secTone: '번역 어조', secTheme: '테마',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'DeepL API 키', saveDeepLApi: '🔑 DEEPL 키 저장',
      btnTestDeepL: '🧪 DEEPL 테스트',
      btnToggleSecretsShow: '👁', btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'DeepL API 키를 쉼표로 구분하여 하나 이상 붙여넣으세요',
      deeplApiHelp: 'Pro 및 Free 키를 지원합니다. :fx로 끝나는 키는 자동으로 deepl.com/pro-api를 사용합니다.',
      toastDeepLKeysSaved: '🔑 DeepL 키 저장됨',
      toastDeepLKeysMissing: '⚠️ DeepL API 키를 먼저 하나 이상 추가하세요',
      toastDeepLUnsupported: '⚠️ DeepL이 이 대상 언어를 지원하지 않습니다:',
      toastDeepLTesting: '🧪 DeepL API 테스트 중...', toastDeepLOk: '✅ DeepL API 사용 가능', toastDeepLFail: '❌ DeepL API 사용 불가',
      toneNeutral: '보통', toneFormal: '격식체', toneSlang: '구어체',
      themeDark: '다크', themeLight: '라이트', themeCyber: '사이버펑크', themeDracula: '드라큘라',
      togBilingual: '이중 언어 모드', togTts: '텍스트 음성 변환',
      togAutoConvert: '단위 자동 변환', togAutoScroll: '자동 스크롤',
      togIncognito: '시크릿 (기록 없음)',
      togAutoTranslateOnScroll: '스크롤 시 자동 번역',
      btnResetPos: '🏠 위치 초기화', btnClearCache: '🧹 캐시 지우기',
      btnExport: '📤 내보내기', btnImport: '📥 가져오기',
      btnSurprise: '🎲 랜덤 언어', btnPirate: '🏴‍☠️ 해적 모드',
      btnYoda: '🧙 요다 모드', btnHide: '👁 버튼 숨기기', btnShow: '👁 버튼 표시',
      sliderDelay: '요청 지연', unitMs: 'ms',
      statTranslations: '번역 수', statChars: '문자 수', statOnPage: '페이지 내',
      histEmpty: '기록 없음', histClear: '🗑 기록 지우기',
      searchLang: '언어 검색…',
      btnOrig: '↩ 원문', btnCopy: '📋 복사', btnSpeak: '🔊', btnRetry: '↺ 다시 시도',
      copied: '✅ 복사됨!', cacheCleared: '🧹 캐시 삭제됨',
      toastApply: '✅ 인터페이스 업데이트됨',
      toastSave: '💾 저장됨 — 새로고침 중…', toastSurprise: '🎲 언어:',
      toastPirateOn: '🏴‍☠️ Arrr! 해적 모드 켜짐!', toastPirateOff: '🏴‍☠️ 해적 모드 꺼짐',
      toastYodaOn: '🧙 요다 모드 켜짐, 흠.', toastYodaOff: '🧙 요다 모드 꺼짐.',
      toastAutoScrollOn: '✅ 스크롤 시 자동 번역 켜짐', toastAutoScrollOff: '⭕ 스크롤 시 자동 번역 꺼짐',
      shortcutHint: 'F2 = 패널',
      secHotkeys: '단축키', hotkeyPanel: '패널 열기',
      hotkeyPress: '키를 누르세요…', hotkeyReset: '↺ 초기화',
      secColors: '테마 색상', colorAcc: '강조', colorTxt: '텍스트', colorBg: '배경', colorOk: '성공',
      btnResetColors: '↺ 색상 초기화',
      secRequestLimits: '요청 제한',
      maxConcurrentRequests: '최대 동시 요청 수',
      maxRequestsPerSecond: '최대 요청 수 / 초',
      maxTextLengthPerRequest: '최대 문자 수 / 요청',
      maxParagraphsPerRequest: '최대 단락 수 / 요청',
      btnShowOriginals: '📄 원문 보기',
      btnShowTranslations: '🌐 번역 보기',
      toastShowingOriginals: '📄 원문 표시 중',
      toastShowingTranslations: '🌐 번역 표시 중',
    },
    vi: {
      title: 'Reddit Dịch thuật', ver: 'v1.0.10',
      tabSettings: '⚙️ Cài đặt', tabHistory: '📖 Lịch sử', tabExtras: '✨ Thêm',
      secUiLang: 'Ngôn ngữ giao diện', applyUi: '✨ ÁP DỤNG GIAO DIỆN',
      secTargetLang: 'Ngôn ngữ đích', saveLang: '💾 LƯU NGÔN NGỮ',
      secEngine: 'Công cụ', secTone: 'Giọng dịch', secTheme: 'Chủ đề',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'Khóa API DeepL', saveDeepLApi: '🔑 LƯU KHÓA DEEPL',
      btnTestDeepL: '🧪 KIỂM TRA DEEPL',
      btnToggleSecretsShow: '👁', btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'Dán một hoặc nhiều khóa API DeepL, phân cách bằng dấu phẩy',
      deeplApiHelp: 'Hỗ trợ khóa Pro và Free. Khóa kết thúc bằng :fx tự động dùng deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 Đã lưu khóa DeepL',
      toastDeepLKeysMissing: '⚠️ Vui lòng thêm ít nhất một khóa API DeepL trước',
      toastDeepLUnsupported: '⚠️ DeepL không hỗ trợ ngôn ngữ đích này:',
      toastDeepLTesting: '🧪 Đang kiểm tra API DeepL...', toastDeepLOk: '✅ API DeepL khả dụng', toastDeepLFail: '❌ API DeepL không khả dụng',
      toneNeutral: 'Trung lập', toneFormal: 'Trang trọng', toneSlang: 'Thông thường',
      themeDark: 'Tối', themeLight: 'Sáng', themeCyber: 'Cyberpunk', themeDracula: 'Dracula',
      togBilingual: 'Chế độ song ngữ', togTts: 'Chuyển văn bản thành giọng nói',
      togAutoConvert: 'Tự động chuyển đổi đơn vị', togAutoScroll: 'Tự động cuộn',
      togIncognito: 'Ẩn danh (không lưu lịch sử)',
      togAutoTranslateOnScroll: 'Tự động dịch khi cuộn',
      btnResetPos: '🏠 Đặt lại vị trí', btnClearCache: '🧹 Xóa bộ nhớ đệm',
      btnExport: '📤 Xuất', btnImport: '📥 Nhập',
      btnSurprise: '🎲 Ngẫu nhiên', btnPirate: '🏴‍☠️ Chế độ cướp biển',
      btnYoda: '🧙 Chế độ Yoda', btnHide: '👁 Ẩn nút', btnShow: '👁 Hiện nút',
      sliderDelay: 'Độ trễ yêu cầu', unitMs: 'ms',
      statTranslations: 'Bản dịch', statChars: 'Ký tự', statOnPage: 'Trên trang',
      histEmpty: 'Chưa có lịch sử', histClear: '🗑 Xóa lịch sử',
      searchLang: 'Tìm ngôn ngữ…',
      btnOrig: '↩ Gốc', btnCopy: '📋 Sao chép', btnSpeak: '🔊', btnRetry: '↺ Thử lại',
      copied: '✅ Đã sao chép!', cacheCleared: '🧹 Đã xóa bộ nhớ đệm',
      toastApply: '✅ Giao diện đã cập nhật',
      toastSave: '💾 Đã lưu — đang tải lại…', toastSurprise: '🎲 Ngôn ngữ:',
      toastPirateOn: '🏴‍☠️ Arrr! Chế độ cướp biển bật!', toastPirateOff: '🏴‍☠️ Chế độ cướp biển tắt',
      toastYodaOn: '🧙 Chế độ Yoda bật, hmm.', toastYodaOff: '🧙 Chế độ Yoda tắt.',
      toastAutoScrollOn: '✅ Tự động dịch khi cuộn đã bật', toastAutoScrollOff: '⭕ Tự động dịch khi cuộn đã tắt',
      shortcutHint: 'F2 = bảng điều khiển',
      secHotkeys: 'Phím tắt', hotkeyPanel: 'Mở bảng điều khiển',
      hotkeyPress: 'Nhấn một phím…', hotkeyReset: '↺ Đặt lại',
      secColors: 'Màu chủ đề', colorAcc: 'Nhấn mạnh', colorTxt: 'Văn bản', colorBg: 'Nền', colorOk: 'Thành công',
      btnResetColors: '↺ Đặt lại màu',
      secRequestLimits: 'Giới hạn yêu cầu',
      maxConcurrentRequests: 'Tối đa yêu cầu đồng thời',
      maxRequestsPerSecond: 'Tối đa yêu cầu / giây',
      maxTextLengthPerRequest: 'Tối đa ký tự / yêu cầu',
      maxParagraphsPerRequest: 'Tối đa đoạn / yêu cầu',
      btnShowOriginals: '📄 HIỂN THỊ BẢN GỐC',
      btnShowTranslations: '🌐 HIỂN THỊ BẢN DỊCH',
      toastShowingOriginals: '📄 Đang hiển thị văn bản gốc',
      toastShowingTranslations: '🌐 Đang hiển thị bản dịch',
    },
    ar: {
      title: 'Reddit مترجم', ver: 'v1.0.10',
      tabSettings: '⚙️ الإعدادات', tabHistory: '📖 السجل', tabExtras: '✨ إضافات',
      secUiLang: 'لغة الواجهة', applyUi: '✨ تطبيق الواجهة',
      secTargetLang: 'لغة الهدف', saveLang: '💾 حفظ اللغة',
      secEngine: 'المحرك', secTone: 'أسلوب الترجمة', secTheme: 'المظهر',
      engGoogle: 'Google', engMymemory: 'MyMemory', engDeepL: 'DeepL',
      secDeepLApi: 'مفاتيح DeepL API', saveDeepLApi: '🔑 حفظ مفاتيح DEEPL',
      btnTestDeepL: '🧪 اختبار DEEPL',
      btnToggleSecretsShow: '👁', btnToggleSecretsHide: '🙈',
      deeplApiPlaceholder: 'الصق مفتاحاً أو أكثر من مفاتيح DeepL API، مفصولة بفواصل',
      deeplApiHelp: 'يدعم مفاتيح Pro و Free. المفاتيح التي تنتهي بـ :fx تستخدم تلقائياً deepl.com/pro-api.',
      toastDeepLKeysSaved: '🔑 تم حفظ مفاتيح DeepL',
      toastDeepLKeysMissing: '⚠️ أضف مفتاح DeepL API واحداً على الأقل أولاً',
      toastDeepLUnsupported: '⚠️ DeepL لا يدعم لغة الهدف هذه:',
      toastDeepLTesting: '🧪 جارٍ اختبار DeepL API...', toastDeepLOk: '✅ DeepL API متاح', toastDeepLFail: '❌ DeepL API غير متاح',
      toneNeutral: 'محايد', toneFormal: 'رسمي', toneSlang: 'عامي',
      themeDark: 'داكن', themeLight: 'فاتح', themeCyber: 'سايبربانك', themeDracula: 'دراكولا',
      togBilingual: 'وضع ثنائي اللغة', togTts: 'تحويل النص إلى كلام',
      togAutoConvert: 'تحويل الوحدات تلقائياً', togAutoScroll: 'التمرير التلقائي',
      togIncognito: 'التصفح الخاص (بدون سجل)',
      togAutoTranslateOnScroll: 'ترجمة تلقائية عند التمرير',
      btnResetPos: '🏠 إعادة تعيين الموضع', btnClearCache: '🧹 مسح ذاكرة التخزين المؤقت',
      btnExport: '📤 تصدير', btnImport: '📥 استيراد',
      btnSurprise: '🎲 لغة عشوائية', btnPirate: '🏴‍☠️ وضع القراصنة',
      btnYoda: '🧙 وضع يودا', btnHide: '👁 إخفاء الأزرار', btnShow: '👁 إظهار الأزرار',
      sliderDelay: 'تأخير الطلب', unitMs: 'ms',
      statTranslations: 'الترجمات', statChars: 'الأحرف', statOnPage: 'في الصفحة',
      histEmpty: 'لا يوجد سجل', histClear: '🗑 مسح السجل',
      searchLang: 'البحث عن لغة…',
      btnOrig: '↩ الأصل', btnCopy: '📋 نسخ', btnSpeak: '🔊', btnRetry: '↺ إعادة المحاولة',
      copied: '✅ تم النسخ!', cacheCleared: '🧹 تم مسح ذاكرة التخزين المؤقت',
      toastApply: '✅ تم تحديث الواجهة',
      toastSave: '💾 تم الحفظ — جارٍ إعادة التحميل…', toastSurprise: '🎲 اللغة:',
      toastPirateOn: '🏴‍☠️ Arrr! وضع القراصنة مفعّل!', toastPirateOff: '🏴‍☠️ وضع القراصنة معطّل',
      toastYodaOn: '🧙 وضع يودا مفعّل، همم.', toastYodaOff: '🧙 وضع يودا معطّل.',
      toastAutoScrollOn: '✅ الترجمة التلقائية عند التمرير مفعّلة', toastAutoScrollOff: '⭕ الترجمة التلقائية عند التمرير معطّلة',
      shortcutHint: 'F2 = اللوحة',
      secHotkeys: 'الاختصارات', hotkeyPanel: 'فتح اللوحة',
      hotkeyPress: 'اضغط مفتاحاً…', hotkeyReset: '↺ إعادة تعيين',
      secColors: 'ألوان المظهر', colorAcc: 'لون مميز', colorTxt: 'نص', colorBg: 'خلفية', colorOk: 'نجاح',
      btnResetColors: '↺ إعادة تعيين الألوان',
      secRequestLimits: 'حدود الطلبات',
      maxConcurrentRequests: 'أقصى طلبات متزامنة',
      maxRequestsPerSecond: 'أقصى طلبات / ثانية',
      maxTextLengthPerRequest: 'أقصى أحرف / طلب',
      maxParagraphsPerRequest: 'أقصى فقرات / طلب',
      btnShowOriginals: '📄 إظهار الأصل',
      btnShowTranslations: '🌐 إظهار الترجمة',
      toastShowingOriginals: '📄 يتم عرض النص الأصلي',
      toastShowingTranslations: '🌐 يتم عرض الترجمة',
    },
  };

  const S = (key) => (STRINGS[cfg.uiLang] || STRINGS.en)[key] ?? (STRINGS.en[key] ?? key);

  // ═══════════════════════════════════════════════════════════════════════════
  // § ЯЗЫКИ
  // ═══════════════════════════════════════════════════════════════════════════
  const ALL_LANGS = [
    'af', 'sq', 'am', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs', 'bg', 'ca', 'ceb', 'co', 'hr', 'cs',
    'da', 'nl', 'en', 'eo', 'et', 'tl', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gu', 'ht', 'ha',
    'haw', 'he', 'hi', 'hmn', 'hu', 'is', 'ig', 'id', 'ga', 'it', 'ja', 'jw', 'kn', 'kk', 'km', 'ko',
    'ku', 'ky', 'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mn', 'my',
    'ne', 'no', 'ps', 'fa', 'pl', 'pt', 'pa', 'ro', 'ru', 'sm', 'gd', 'sr', 'st', 'sn', 'sd', 'si',
    'sk', 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tg', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'uz', 'vi',
    'cy', 'xh', 'yi', 'yo', 'zu', 'zh',
  ];

  function langName(code, locale) {
    try {
      const d = new Intl.DisplayNames([locale || cfg.uiLang], { type: 'language' });
      const n = d.of(code);
      return n.charAt(0).toUpperCase() + n.slice(1);
    } catch { return code.toUpperCase(); }
  }

  // Кешируем имена языков
  const langNameCache = {};
  function getLangName(code) {
    const key = `${code}:${cfg.uiLang}`;
    if (!langNameCache[key]) langNameCache[key] = langName(code, cfg.uiLang);
    return langNameCache[key];
  }

  function buildLangSelect(selectEl, searchEl, codes, selected) {
    const sorted = codes.map(c => ({ c, n: getLangName(c) })).sort((a, b) => a.n.localeCompare(b.n));
    function render(q) {
      const f = q.toLowerCase();
      const filtered = f ? sorted.filter(({ c, n }) => n.toLowerCase().includes(f) || c.includes(f)) : sorted;
      selectEl.innerHTML = filtered.map(({ c, n }) =>
        `<option value="${c}" ${c === selected ? 'selected' : ''}>${n} (${c.toUpperCase()})</option>`
      ).join('');
    }
    render('');
    if (searchEl) searchEl.addEventListener('input', () => render(searchEl.value));
    return { refresh: (q = '') => render(q) };
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ТЕМЫ
  // ═══════════════════════════════════════════════════════════════════════════
  const THEMES = {
    dark: { bg: 'rgba(10,10,14,.96)', surf: 'rgba(24,24,32,.9)', brd: 'rgba(255,255,255,.06)', txt: '#e0e0ec', mut: 'rgba(255,255,255,.28)', acc: '#ff4500', glow: 'rgba(255,69,0,.36)', dim: 'rgba(255,69,0,.11)', ok: '#60d394', okd: 'rgba(96,211,148,.11)', biBg: 'rgba(20,22,30,.96)', biTxt: '#fff3ed', biBrd: 'rgba(255,69,0,.42)', btnBg: 'var(--rtp-dim)', btnTxt: '#ff6a2b', btnBrd: 'rgba(255,69,0,.24)', btnDoneBg: 'rgba(96,211,148,.11)', btnDoneTxt: '#60d394', btnDoneBrd: 'rgba(96,211,148,.26)' },
    light: { bg: 'rgba(246,246,250,.97)', surf: 'rgba(255,255,255,.93)', brd: 'rgba(0,0,0,.07)', txt: '#17171e', mut: 'rgba(0,0,0,.36)', acc: '#ff4500', glow: 'rgba(255,69,0,.2)', dim: 'rgba(255,69,0,.09)', ok: '#1a9e5a', okd: 'rgba(26,158,90,.09)', biBg: 'rgba(255,245,240,.95)', biTxt: '#17171e', biBrd: 'rgba(255,69,0,.28)', btnBg: 'rgba(255,241,234,.96)', btnTxt: '#c63a00', btnBrd: 'rgba(255,69,0,.26)', btnDoneBg: 'rgba(226,247,236,.98)', btnDoneTxt: '#157347', btnDoneBrd: 'rgba(26,158,90,.28)' },
    cyberpunk: { bg: 'rgba(3,0,16,.97)', surf: 'rgba(10,3,32,.93)', brd: 'rgba(0,255,255,.11)', txt: '#ddf4ff', mut: 'rgba(0,255,255,.36)', acc: '#00ffff', glow: 'rgba(0,255,255,.44)', dim: 'rgba(0,255,255,.09)', ok: '#ff00aa', okd: 'rgba(255,0,170,.1)', biBg: 'rgba(4,18,34,.97)', biTxt: '#e9feff', biBrd: 'rgba(0,255,255,.5)', btnBg: 'rgba(0,255,255,.09)', btnTxt: '#66ffff', btnBrd: 'rgba(0,255,255,.24)', btnDoneBg: 'rgba(255,0,170,.12)', btnDoneTxt: '#ff6ecf', btnDoneBrd: 'rgba(255,0,170,.3)' },
    dracula: { bg: 'rgba(14,14,26,.97)', surf: 'rgba(26,26,46,.91)', brd: 'rgba(139,92,246,.16)', txt: '#f8f8f2', mut: 'rgba(189,147,249,.5)', acc: '#bd93f9', glow: 'rgba(189,147,249,.42)', dim: 'rgba(189,147,249,.1)', ok: '#50fa7b', okd: 'rgba(80,250,123,.1)', biBg: 'rgba(33,34,54,.97)', biTxt: '#f8f8f2', biBrd: 'rgba(189,147,249,.52)', btnBg: 'rgba(189,147,249,.12)', btnTxt: '#d5b6ff', btnBrd: 'rgba(189,147,249,.26)', btnDoneBg: 'rgba(80,250,123,.12)', btnDoneTxt: '#50fa7b', btnDoneBrd: 'rgba(80,250,123,.28)' },
  };

  function applyTheme(t) {
    const th = Object.assign({}, THEMES[t] || THEMES.dark);
    // Применяем кастомные цвета поверх базовой темы
    if (cfg.customColors) {
      try {
        const cc = typeof cfg.customColors === 'string' ? JSON.parse(cfg.customColors) : cfg.customColors;
        Object.assign(th, cc);
      } catch { }
    }
    const r = document.documentElement;
    Object.entries(th).forEach(([k, v]) => r.style.setProperty(`--rtp-${k}`, v));
    document.body.setAttribute('data-rtp-theme', t);
  }

  function colorToHex(color) {
    // Если уже hex — возвращаем как есть
    if (/^#[0-9a-f]{6}$/i.test(color)) return color;
    // rgba/rgb — конвертируем
    const m = color.match(/[\d.]+/g);
    if (!m) return '#888888';
    const r = (+m[0]).toString(16).padStart(2, '0');
    const g = (+m[1]).toString(16).padStart(2, '0');
    const b = (+m[2]).toString(16).padStart(2, '0');
    return `#${r}${g}${b}`;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § CSS
  // ═══════════════════════════════════════════════════════════════════════════
  GM_addStyle(`
    @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap');

    :root {
        --rtp-bg:rgba(10,10,14,.96); --rtp-surf:rgba(24,24,32,.9);
        --rtp-brd:rgba(255,255,255,.06); --rtp-txt:#e0e0ec; --rtp-mut:rgba(255,255,255,.28);
        --rtp-acc:#ff4500; --rtp-glow:rgba(255,69,0,.36); --rtp-dim:rgba(255,69,0,.11);
        --rtp-ok:#60d394; --rtp-okd:rgba(96,211,148,.11);
        --rtp-biBg:rgba(20,22,30,.96); --rtp-biTxt:#fff3ed; --rtp-biBrd:rgba(255,69,0,.42);
        --rtp-btnBg:rgba(255,69,0,.11); --rtp-btnTxt:#ff6a2b; --rtp-btnBrd:rgba(255,69,0,.24);
        --rtp-btnDoneBg:rgba(96,211,148,.11); --rtp-btnDoneTxt:#60d394; --rtp-btnDoneBrd:rgba(96,211,148,.26);
        --f:'Outfit',sans-serif; --fm:'JetBrains Mono',monospace; --r:15px;
    }

    /* ─ КОНТЕНТНЫЕ КНОПКИ ─ */
    .rtp-btn {
        display:inline-flex; align-items:center; gap:5px;
        margin:3px 6px; padding:3px 11px;
        font-family:var(--f); font-size:10.5px; font-weight:600; letter-spacing:.04em;
        color:var(--rtp-btnTxt); background:var(--rtp-btnBg);
        border:1px solid var(--rtp-btnBrd); border-radius:20px;
        cursor:pointer; vertical-align:middle; white-space:nowrap;
        transition:all .22s cubic-bezier(.34,1.56,.64,1); opacity:.6;
    }
    .rtp-btn:hover { opacity:1; transform:translateY(-1px) scale(1.05); box-shadow:0 4px 14px var(--rtp-glow); }
    .rtp-btn.done  { color:var(--rtp-btnDoneTxt); background:var(--rtp-btnDoneBg); border-color:var(--rtp-btnDoneBrd); opacity:1; }
    .rtp-btn.busy  { opacity:.4; pointer-events:none; }

    /* Спиннер */
    .sp { display:inline-block; width:8px; height:8px; border:1.5px solid currentColor; border-top-color:transparent; border-radius:50%; animation:spin .75s linear infinite; }
    @keyframes spin { to{transform:rotate(360deg)} }

    /* Fade-in при появлении перевода */
    .rtp-fi { animation:fi .4s ease; }
    @keyframes fi { from{opacity:0;transform:translateY(3px)} to{opacity:1;transform:none} }

    /* Двуязычный блок */
    .rtp-ctrl {
        position:relative; z-index:20; display:flex; align-items:center; gap:6px; flex-wrap:wrap;
        width:fit-content; max-width:100%; margin:4px 0;
        pointer-events:auto;
    }
    .rtp-bi {
        position:relative; z-index:20;
        margin:6px 0 8px; padding:10px 14px; background:linear-gradient(180deg, var(--rtp-biBg), var(--rtp-surf));
        border:1px solid var(--rtp-biBrd); border-left:3px solid var(--rtp-acc);
        border-radius:0 11px 11px 0; font-size:13px; line-height:1.65; color:var(--rtp-biTxt); font-family:var(--f);
        box-shadow:0 8px 24px rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.03);
        text-shadow:0 1px 0 rgba(0,0,0,.28);
        white-space:pre-wrap; overflow-wrap:anywhere; word-break:break-word; max-height:none; overflow:visible;
    }

    /* Тулбар */
    .rtp-tb { position:relative; z-index:21; display:inline-flex; gap:3px; margin:0; }
    .rtp-t  { display:inline-flex; align-items:center; padding:2px 8px; font-family:var(--f); font-size:9.5px; font-weight:600; letter-spacing:.04em; color:var(--rtp-mut); border:1px solid var(--rtp-brd); border-radius:12px; cursor:pointer; transition:all .16s; background:transparent; }
    .rtp-t:hover { color:var(--rtp-txt); background:var(--rtp-surf); }

    /* ─ FAB ─ */
    #rtp-fab, #rtp-view-toggle {
        position:fixed; bottom:28px; right:28px; z-index:9998;
        display:flex; align-items:center; justify-content:center;
        min-width:160px; height:42px; padding:0 16px;
        background:rgba(24,24,32,.92); color:#fff; border:1px solid var(--rtp-brd);
        border-radius:22px; font-family:var(--f); font-size:12px; font-weight:700; letter-spacing:.05em;
        cursor:pointer; box-shadow:0 8px 24px rgba(0,0,0,.28);
        backdrop-filter:blur(10px);
        transition:all .22s cubic-bezier(.34,1.56,.64,1);
    }
    #rtp-fab { bottom:78px; min-width:52px; width:52px; padding:0; border-radius:50%; font-size:18px; }
    #rtp-fab.busy { opacity:.62; pointer-events:none; }
    #rtp-fab.done { color:var(--rtp-ok); border-color:var(--rtp-ok); }
    #rtp-fab:hover,
    #rtp-view-toggle:hover { transform:translateY(-3px) scale(1.02); border-color:var(--rtp-acc); box-shadow:0 12px 30px var(--rtp-glow); }
    #rtp-view-toggle.originals { color:#000; background:var(--rtp-dim); border-color:rgba(255,69,0,.24); text-shadow:0 1px 0 rgba(0,0,0,.22); }

    /* ─ Горячие клавиши ─ */
    .hk-row { display:flex; align-items:center; justify-content:space-between; gap:8px; margin:5px 0; }
    .hk-lbl { font-size:12px; color:var(--rtp-mut); flex:1; }
    .hk-btn {
        font-family:var(--fm); font-size:11px; font-weight:600; letter-spacing:.03em;
        padding:5px 12px; border-radius:8px; cursor:pointer; min-width:90px; text-align:center;
        background:var(--rtp-surf); color:var(--rtp-txt); border:1px solid var(--rtp-brd);
        transition:all .16s;
    }
    .hk-btn:hover { border-color:var(--rtp-acc); color:var(--rtp-acc); }
    .hk-btn.capturing { border-color:var(--rtp-acc); color:var(--rtp-acc); background:var(--rtp-dim); animation:hkpulse 1s ease infinite; }
    @keyframes hkpulse { 0%,100%{opacity:1} 50%{opacity:.5} }
    .hk-reset { font-size:11px; padding:4px 8px; cursor:pointer; color:var(--rtp-mut); border:1px solid var(--rtp-brd); border-radius:8px; background:transparent; transition:all .16s; flex-shrink:0; }
    .hk-reset:hover { color:var(--rtp-txt); border-color:var(--rtp-acc); }

    /* ─ Цвета темы ─ */
    .clr-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; margin-top:4px; }
    .clr-row { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px; background:var(--rtp-surf); border:1px solid var(--rtp-brd); border-radius:9px; }
    .clr-lbl { font-size:11px; color:var(--rtp-mut); }
    .clr-inp { width:32px; height:24px; border:none; border-radius:5px; cursor:pointer; background:none; padding:0; }

    /* ─ ПАНЕЛЬ ─ */
    #rtp-panel {
        position:fixed; z-index:10000; width:372px;
        font-family:var(--f);
        border-radius:var(--r);
        background:var(--rtp-bg);
        backdrop-filter:blur(32px) saturate(180%);
        -webkit-backdrop-filter:blur(32px) saturate(180%);
        border:1px solid var(--rtp-brd);
        box-shadow:0 32px 80px rgba(0,0,0,.75), inset 0 1px 0 rgba(255,255,255,.045);
        animation:pan .3s cubic-bezier(.34,1.56,.64,1);
        overflow:hidden; color:var(--rtp-txt);
    }
    @keyframes pan { from{opacity:0;transform:translateY(-14px) scale(.97)} to{opacity:1;transform:none} }

    /* Шапка */
    #rtp-hdr {
        background:linear-gradient(135deg,var(--rtp-dim),transparent 65%);
        border-bottom:1px solid var(--rtp-brd);
        padding:13px 16px; display:flex; align-items:center; justify-content:space-between;
        cursor:move; user-select:none;
    }
    .logo-w { display:flex; align-items:center; gap:10px; }
    .logo-ic { width:35px; height:35px; background:var(--rtp-acc); border-radius:11px; display:flex; align-items:center; justify-content:center; font-size:18px; box-shadow:0 4px 14px var(--rtp-glow); flex-shrink:0; }
    .logo-nm { font-size:13.5px; font-weight:700; letter-spacing:.04em; }
    .logo-vr { font-size:9px; color:var(--rtp-mut); font-family:var(--fm); margin-top:1px; }
    #rtp-close { width:29px; height:29px; display:flex; align-items:center; justify-content:center; border:1px solid var(--rtp-brd); border-radius:8px; cursor:pointer; color:var(--rtp-mut); transition:all .17s; background:transparent; font-size:13px; flex-shrink:0; }
    #rtp-close:hover { color:var(--rtp-txt); background:rgba(255,255,255,.07); }

    /* Статистика */
    #rtp-stats { display:flex; border-bottom:1px solid var(--rtp-brd); }
    .st { flex:1; padding:10px 5px; text-align:center; }
    .st+.st { border-left:1px solid var(--rtp-brd); }
    .st-v { font-family:var(--fm); font-size:18px; font-weight:700; color:var(--rtp-acc); }
    .st-l { font-size:8.5px; color:var(--rtp-mut); text-transform:uppercase; letter-spacing:.1em; margin-top:2px; }

    /* Табы */
    #rtp-tabs { display:flex; border-bottom:1px solid var(--rtp-brd); background:var(--rtp-surf); }
    .tab { flex:1; padding:10px 4px; text-align:center; font-size:10px; font-weight:700; letter-spacing:.06em; color:var(--rtp-mut); cursor:pointer; transition:all .18s; border-bottom:2px solid transparent; text-transform:uppercase; }
    .tab.on { color:var(--rtp-acc); border-bottom-color:var(--rtp-acc); }
    .tab:hover:not(.on) { color:var(--rtp-txt); }

    /* Панели */
    .pane { padding:15px; display:flex; flex-direction:column; gap:12px; max-height:475px; overflow-y:auto; }
    .pane::-webkit-scrollbar { width:3px; }
    .pane::-webkit-scrollbar-thumb { background:var(--rtp-brd); border-radius:2px; }

    /* Лейблы */
    .lbl { display:block; font-size:9.5px; font-weight:700; color:var(--rtp-mut); text-transform:uppercase; letter-spacing:.1em; margin-bottom:6px; }

    /* Языковый блок (поиск + список) */
    .lang-wrap { display:flex; flex-direction:column; }
    .lang-search { background:var(--rtp-surf); border:1px solid var(--rtp-brd); border-bottom:none; color:var(--rtp-txt); padding:8px 12px; border-radius:9px 9px 0 0; font-family:var(--f); font-size:12.5px; outline:none; }
    .lang-search::placeholder { color:var(--rtp-mut); }
    .lang-search:focus { border-color:var(--rtp-acc); }
    .lang-sel {
        background:var(--rtp-surf); border:1px solid var(--rtp-brd); color:var(--rtp-txt);
        padding:7px 10px; border-radius:0 0 9px 9px; font-family:var(--f); font-size:12.5px;
        outline:none; appearance:none; cursor:pointer; max-height:175px;
        transition:border-color .18s;
    }
    .lang-sel:focus { border-color:var(--rtp-acc); }
    .lang-sel option {
        background:var(--rtp-surf);
        color:var(--rtp-txt);
    }
    .lang-sel option:checked,
    .lang-sel option:hover {
        background:var(--rtp-dim);
        color:var(--rtp-txt);
    }

    .api-box {
        width:100%; min-height:84px; resize:vertical; box-sizing:border-box;
        background:var(--rtp-surf); border:1px solid var(--rtp-brd); color:var(--rtp-txt);
        padding:10px 12px; border-radius:9px; font-family:var(--fm); font-size:11.5px; line-height:1.45;
        outline:none; transition:border-color .18s;
    }
    .api-head {
        display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:6px;
    }
    .api-head .lbl { margin-bottom:0; }
    .api-wrap { position:relative; }
    .api-box:focus { border-color:var(--rtp-acc); }
    .api-box::placeholder { color:var(--rtp-mut); }
    .api-box.masked { -webkit-text-security:disc; }
    .api-eye {
        width:32px; height:32px; display:flex; align-items:center; justify-content:center;
        border:1px solid var(--rtp-brd); border-radius:8px; background:var(--rtp-surf);
        color:var(--rtp-mut); cursor:pointer; transition:all .16s;
        flex-shrink:0;
    }
    .api-eye:hover { color:var(--rtp-txt); border-color:var(--rtp-acc); }
    .api-help { margin-top:6px; font-size:10.5px; line-height:1.45; color:var(--rtp-mut); }

    .num-grid { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:7px; }
    .num-card {
        padding:8px 10px; background:var(--rtp-surf); border:1px solid var(--rtp-brd);
        border-radius:10px;
    }
    @media (max-width: 430px) {
        .num-grid { grid-template-columns:1fr; }
    }
    .num-lbl {
        display:block; margin-bottom:6px; font-size:10px; line-height:1.35;
        color:var(--rtp-mut);
    }
    .num-inp {
        width:100%; box-sizing:border-box; background:transparent; border:1px solid var(--rtp-brd);
        color:var(--rtp-txt); padding:7px 9px; border-radius:8px; font-family:var(--fm); font-size:12px;
        outline:none;
    }
    .num-inp:focus { border-color:var(--rtp-acc); }

    /* Пилюли */
    .pills { display:flex; gap:3px; background:var(--rtp-surf); border:1px solid var(--rtp-brd); border-radius:10px; padding:3px; }
    .pill { flex:1; text-align:center; padding:7px 4px; border-radius:8px; font-size:10.5px; font-weight:700; color:var(--rtp-mut); cursor:pointer; transition:all .18s; white-space:nowrap; }
    .pill.on { background:var(--rtp-acc); color:#fff; box-shadow:0 2px 8px var(--rtp-glow); }

    /* Тогглы */
    .tog-row { display:flex; align-items:center; justify-content:space-between; padding:2px 0; }
    .tog-lbl { font-size:12.5px; color:var(--rtp-txt); }
    .tog { position:relative; width:38px; height:21px; flex-shrink:0; }
    .tog input { opacity:0; width:0; height:0; }
    .tog-tr { position:absolute; inset:0; background:var(--rtp-surf); border:1px solid var(--rtp-brd); border-radius:21px; cursor:pointer; transition:all .24s; }
    .tog input:checked+.tog-tr { background:var(--rtp-acc); border-color:var(--rtp-acc); }
    .tog-tr::after { content:''; position:absolute; left:3px; top:3px; width:13px; height:13px; background:#fff; border-radius:50%; transition:.24s; }
    .tog input:checked+.tog-tr::after { transform:translateX(17px); }

    /* Кнопки */
    .btn-p { width:100%; height:44px; background:var(--rtp-acc); border:none; color:#fff; border-radius:11px; font-family:var(--f); font-size:11.5px; font-weight:800; letter-spacing:.06em; cursor:pointer; transition:all .2s; position:relative; overflow:hidden; }
    .btn-p::before { content:''; position:absolute; inset:0; background:linear-gradient(135deg,rgba(255,255,255,.14),transparent); }
    .btn-p:hover { box-shadow:0 8px 22px var(--rtp-glow); transform:translateY(-1px); }
    .btn-p.ghost { background:var(--rtp-surf); border:1px solid var(--rtp-brd); color:var(--rtp-txt); box-shadow:none; }
    .btn-p.ghost:hover { background:rgba(255,255,255,.07); }

    .g2 { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
    .btn-s { padding:9px 7px; background:var(--rtp-surf); border:1px solid var(--rtp-brd); color:var(--rtp-mut); border-radius:10px; font-size:10.5px; font-family:var(--f); font-weight:600; cursor:pointer; transition:all .17s; text-align:center; }
    .btn-s:hover { color:var(--rtp-txt); background:rgba(255,255,255,.06); border-color:rgba(255,255,255,.1); }
    .btn-s.active { color:var(--rtp-acc); border-color:var(--rtp-acc); }

    /* Разделитель */
    .div { height:1px; background:var(--rtp-brd); }

    /* История */
    .hi { padding:9px 12px; background:var(--rtp-surf); border:1px solid var(--rtp-brd); border-radius:10px; cursor:pointer; transition:border-color .18s; }
    .hi:hover { border-color:var(--rtp-acc); }
    .hi-o { font-size:10px; color:var(--rtp-mut); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:3px; }
    .hi-t { font-size:12px; color:var(--rtp-txt); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
    .hi-m { font-size:9px; color:var(--rtp-acc); font-family:var(--fm); margin-top:4px; }

    /* Слайдер */
    .slider { width:100%; accent-color:var(--rtp-acc); cursor:pointer; }
    .slider-v { text-align:right; font-size:10px; color:var(--rtp-mut); margin-top:3px; font-family:var(--fm); }

    /* Тост */
    #rtp-toast { position:fixed; bottom:92px; right:28px; z-index:10010; background:var(--rtp-bg); backdrop-filter:blur(16px); border:1px solid var(--rtp-brd); color:var(--rtp-txt); padding:10px 18px; border-radius:12px; font-family:var(--f); font-size:12.5px; font-weight:600; box-shadow:0 8px 28px rgba(0,0,0,.5); pointer-events:none; opacity:0; transform:translateY(8px); transition:all .24s; }
    #rtp-toast.on { opacity:1; transform:none; }

    /* Cyberpunk overrides */
    [data-rtp-theme=cyberpunk] #rtp-panel { box-shadow:0 0 40px rgba(0,255,255,.1),0 32px 80px rgba(0,0,0,.85); }
    `);

  // ═══════════════════════════════════════════════════════════════════════════
  // § СЛЭНГ
  // ═══════════════════════════════════════════════════════════════════════════
  const SLANG = {
    OP: 'автор поста', 'TL;DR': 'краткое содержание', TIL: 'сегодня узнал',
    AMA: 'задайте любой вопрос', IMO: 'по моему мнению', IMHO: 'по моему скромному мнению',
    IRL: 'в реальной жизни', ELI5: 'объясни как пятилетнему', AFAIK: 'насколько я знаю',
    IIRC: 'если я правильно помню', SMH: 'качаю головой', FTW: 'победа',
    LMK: 'дай знать', NGL: 'не буду врать', YMMV: 'у каждого по-разному',
    FWIW: 'к вашему сведению', ICYMI: 'если вы пропустили', FTFY: 'исправил за тебя',
  };
  const SLANG_RE = new RegExp(`(?<![\\w;])(${Object.keys(SLANG).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})(?![\\w;])`, 'gi');

  function expandSlang(text) {
    return text.replace(SLANG_RE, m => {
      const k = m.toUpperCase();
      return SLANG[k] ? `${m}[=${SLANG[k]}]` : m;
    });
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § КОНВЕРТЕРЫ ЕДИНИЦ
  // ═══════════════════════════════════════════════════════════════════════════
  function convertUnits(text) {
    if (!cfg.autoConvert) return text;
    return text
      .replace(/(-?\d+(?:[.,]\d+)?)\s*°?F\b/g, (_, n) => `${n}°F (${((+n.replace(',', '.') - 32) * 5 / 9).toFixed(1)}°C)`)
      .replace(/(\d+(?:[.,]\d+)?)\s*miles?\b/gi, (_, n) => `${n} миль (≈${(+n.replace(',', '.') * 1.609).toFixed(1)} км)`)
      .replace(/(\d+(?:[.,]\d+)?)\s*lbs?\b/gi, (_, n) => `${n} фунт (≈${(+n.replace(',', '.') * 0.4536).toFixed(1)} кг)`)
      .replace(/(\d+(?:[.,]\d+)?)\s*(?:inch(?:es)?|")\b/gi, (_, n) => `${n}" (≈${(+n.replace(',', '.') * 2.54).toFixed(1)} см)`)
      .replace(/(\d+(?:[.,]\d+)?)\s*(?:foot|feet|ft)\b/gi, (_, n) => `${n} фут (≈${(+n.replace(',', '.') * 0.3048).toFixed(2)} м)`)
      .replace(/(\d+(?:[.,]\d+)?)\s*(?:yard|yd)s?\b/gi, (_, n) => `${n} ярд (≈${(+n.replace(',', '.') * 0.9144).toFixed(2)} м)`);
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ПАСХАЛКИ
  // ═══════════════════════════════════════════════════════════════════════════
  let pirateMode = false;
  let yodaMode = false;

  function pirateify(t) {
    return t.replace(/\bthe\b/gi, "th'").replace(/\byou\b/gi, 'ye').replace(/\bis\b/gi, 'be')
      .replace(/\bmy\b/gi, 'me').replace(/\byes\b/gi, 'aye').replace(/\bno\b/gi, 'nay')
      .replace(/\bfriend\b/gi, 'matey').replace(/\bhello\b/gi, 'ahoy')
      + ' ⚓ Arrr!';
  }

  function yodaify(t) {
    const w = t.split(' ');
    if (w.length < 4) return t + ', hmm.';
    const n = Math.ceil(w.length / 3);
    return [...w.slice(-n), ...w.slice(0, -n)].join(' ') + ', hmm.';
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ПЕРЕВОД
  // ═══════════════════════════════════════════════════════════════════════════
  let reqGen = 0; // поколение очереди — сброс при отмене
  let recentRequestTimes = [];
  let activeTasks = 0;
  let pendingTasks = [];
  let activeNetworkRequests = 0;
  let pendingNetworkRequests = [];
  const AUTO_TRANSLATE_VIEWPORT_MARGIN = 140;
  const AUTO_TRANSLATE_SETTLE_MS = 50;

  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  function getPositiveInt(value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
    const n = Math.floor(Number(value));
    if (!Number.isFinite(n)) return fallback;
    return Math.min(max, Math.max(min, n));
  }

  function getMaxRequestsPerSecond() {
    return getPositiveInt(cfg.maxRequestsPerSecond, DEF.maxRequestsPerSecond, 1, 50);
  }

  function getMaxConcurrentRequests() {
    return getPositiveInt(cfg.maxConcurrentRequests, DEF.maxConcurrentRequests, 1, 20);
  }

  function getMaxTextLengthPerRequest() {
    return getPositiveInt(cfg.maxTextLengthPerRequest, DEF.maxTextLengthPerRequest, 100, 20000);
  }

  function getMaxParagraphsPerRequest() {
    return getPositiveInt(cfg.maxParagraphsPerRequest, DEF.maxParagraphsPerRequest, 1, 100);
  }

  async function waitForRateSlot() {
    const limit = getMaxRequestsPerSecond();
    while (true) {
      const now = Date.now();
      recentRequestTimes = recentRequestTimes.filter(ts => now - ts < 1000);
      if (recentRequestTimes.length < limit) {
        recentRequestTimes.push(now);
        return;
      }
      const oldest = recentRequestTimes[0] || now;
      await sleep(Math.max(20, 1000 - (now - oldest)));
    }
  }

  function pumpNetworkQueue() {
    const limit = getMaxConcurrentRequests();
    const isCurrentTask = (task) => task.gen === reqGen;
    while (activeNetworkRequests < limit && pendingNetworkRequests.length) {
      const task = pendingNetworkRequests.shift();
      activeNetworkRequests++;

      (async () => {
        try {
          if (!isCurrentTask(task)) return task.resolve(null);
          await sleep(cfg.requestDelay);
          if (!isCurrentTask(task)) return task.resolve(null);
          await waitForRateSlot();
          if (!isCurrentTask(task)) return task.resolve(null);
          const result = await task.fn();
          task.resolve(isCurrentTask(task) ? result : null);
        } catch {
          task.resolve(null);
        } finally {
          activeNetworkRequests = Math.max(0, activeNetworkRequests - 1);
          pumpNetworkQueue();
        }
      })();
    }
  }

  function runLimitedRequest(fn) {
    return new Promise(resolve => {
      pendingNetworkRequests.push({ fn, resolve, gen: reqGen });
      pumpNetworkQueue();
    });
  }

  function pumpQueue() {
    const limit = getMaxConcurrentRequests();
    const isCurrentTask = (task) => task.gen === reqGen;
    while (activeTasks < limit && pendingTasks.length) {
      const task = pendingTasks.shift();
      activeTasks++;

      (async () => {
        try {
          if (!isCurrentTask(task)) return task.resolve(null);
          const result = await task.fn();
          task.resolve(isCurrentTask(task) ? result : null);
        } catch {
          task.resolve(null);
        } finally {
          activeTasks = Math.max(0, activeTasks - 1);
          pumpQueue();
        }
      })();
    }
  }

  function enqueue(fn) {
    return new Promise(resolve => {
      pendingTasks.push({ fn, resolve, gen: reqGen });
      pumpQueue();
    });
  }

  function toneHint() {
    if (cfg.tone === 'formal') return 'Translate formally and professionally: ';
    if (cfg.tone === 'slang') return 'Translate casually and colloquially: ';
    return '';
  }

  function escapeHtml(text) {
    return String(text || '').replace(/[&<>"']/g, ch => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;',
    }[ch]));
  }

  function parseJsonSafe(text, fallback = null) {
    try { return JSON.parse(text); } catch { return fallback; }
  }

  function gmRequest({ method = 'GET', url, headers, data, parse, fallback = null, timeout = 15000 }) {
    return new Promise(resolve => GM_xmlhttpRequest({
      method,
      url,
      headers,
      data,
      timeout,
      onload: (r) => {
        let parsed = fallback;
        try {
          parsed = parse ? parse(r) : r.responseText;
        } catch { }
        resolve({
          ok: r.status >= 200 && r.status < 300,
          status: r.status,
          data: parsed ?? fallback,
          raw: r,
        });
      },
      onerror: () => resolve({
        ok: false,
        status: 0,
        data: fallback,
        raw: null,
      }),
      ontimeout: () => resolve({
        ok: false,
        status: 0,
        data: fallback,
        raw: null,
      }),
    }));
  }

  function getDeepLCacheScope() {
    const keys = parseDeepLKeys();
    const tiers = keys.map(key => (key.endsWith(':fx') ? 'free' : 'pro')).join(',');
    return `deepl|keys:${keys.length}|tiers:${tiers}`;
  }

  function getEngineScope() {
    return cfg.engine === 'deepl' ? getDeepLCacheScope() : cfg.engine;
  }

  function getTranslationCacheKey(text) {
    return `${getEngineScope()}|${cfg.targetLang}|${cfg.tone}|${text}`;
  }

  function parseDeepLKeys(raw = cfg.deeplApiKeys) {
    return String(raw || '')
      .split(',')
      .map(k => k.trim())
      .filter(Boolean)
      .filter((k, i, arr) => arr.indexOf(k) === i);
  }

  function getDeepLEndpoint(apiKey) {
    return apiKey.endsWith(':fx') ? 'https://api-free.deepl.com' : 'https://api.deepl.com';
  }

  function splitLongText(text, maxChars) {
    const chunks = [];
    let remaining = String(text || '').trim();

    while (remaining.length > maxChars) {
      let cut = remaining.lastIndexOf('. ', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf('! ', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf('? ', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf('。', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf(',', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf(',', maxChars);
      if (cut < maxChars * 0.5) cut = remaining.lastIndexOf(' ', maxChars);
      if (cut < maxChars * 0.5) cut = maxChars;
      const part = remaining.slice(0, cut + (cut === maxChars ? 0 : 1)).trim();
      if (part) chunks.push(part);
      remaining = remaining.slice(cut + (cut === maxChars ? 0 : 1)).trim();
    }

    if (remaining) chunks.push(remaining);
    return chunks.length ? chunks : [''];
  }

  function buildRequestChunks(text) {
    const source = String(text || '').trim();
    if (!source) return [''];

    const maxChars = getMaxTextLengthPerRequest();
    const maxParagraphs = getMaxParagraphsPerRequest();
    const paragraphs = source
      .split(/\n\s*\n+|\r?\n/)
      .map(p => p.trim())
      .filter(Boolean);

    const normalizedParagraphs = (paragraphs.length ? paragraphs : [source]).flatMap(p =>
      p.length > maxChars ? splitLongText(p, maxChars) : [p]
    );

    const chunks = [];
    let current = [];
    let currentLen = 0;

    normalizedParagraphs.forEach(paragraph => {
      const nextLen = currentLen + paragraph.length + (current.length ? 2 : 0);
      const exceedsChars = nextLen > maxChars;
      const exceedsParagraphs = current.length >= maxParagraphs;

      if (current.length && (exceedsChars || exceedsParagraphs)) {
        chunks.push(current.join('\n\n'));
        current = [];
        currentLen = 0;
      }

      current.push(paragraph);
      currentLen += paragraph.length + (current.length > 1 ? 2 : 0);
    });

    if (current.length) chunks.push(current.join('\n\n'));
    return chunks.length ? chunks : [source];
  }

  function maskDeepLKey(apiKey) {
    const raw = String(apiKey || '');
    const isFree = raw.endsWith(':fx');
    const base = isFree ? raw.slice(0, -3) : raw;
    if (!base) return isFree ? '***:fx' : '***';
    if (base.length <= 8) return `${base.slice(0, 2)}***${isFree ? ':fx' : ''}`;
    return `${base.slice(0, 4)}...${base.slice(-4)}${isFree ? ':fx' : ''}`;
  }

  async function requestDeepLUsage(apiKey) {
    const endpoint = getDeepLEndpoint(apiKey);
    return runLimitedRequest(() => gmRequest({
      url: `${endpoint}/v2/usage`,
      headers: {
        Authorization: `DeepL-Auth-Key ${apiKey}`,
      },
      parse: r => parseJsonSafe(r.responseText),
    }));
  }

  async function testDeepLApi(raw = cfg.deeplApiKeys) {
    const keys = parseDeepLKeys(raw);
    if (!keys.length) {
      return { ok: false, reason: 'missing_keys' };
    }

    let lastError = null;
    for (const apiKey of keys) {
      const result = await requestDeepLUsage(apiKey);
      if (result.ok) {
        return {
          ok: true,
          apiKey,
          status: result.status,
          data: result.data || {},
        };
      }
      lastError = {
        ok: false,
        apiKey,
        status: result.status,
        data: result.data || null,
      };
    }

    return lastError || { ok: false, reason: 'unknown' };
  }

  function getDeepLTargetLang(code) {
    const mapped = {
      en: 'EN',
      de: 'DE',
      fr: 'FR',
      es: 'ES',
      pt: 'PT',
      it: 'IT',
      nl: 'NL',
      pl: 'PL',
      ru: 'RU',
      ja: 'JA',
      zh: 'ZH',
      bg: 'BG',
      cs: 'CS',
      da: 'DA',
      el: 'EL',
      et: 'ET',
      fi: 'FI',
      hu: 'HU',
      id: 'ID',
      ko: 'KO',
      lt: 'LT',
      lv: 'LV',
      nb: 'NB',
      no: 'NB',
      ro: 'RO',
      sk: 'SK',
      sl: 'SL',
      sv: 'SV',
      tr: 'TR',
      uk: 'UK',
    };
    return mapped[String(code || '').toLowerCase()] || null;
  }

  let deeplKeyCursor = 0;

  // 检测文本语言
  async function detectLanguage(text) {
    try {
      const sample = buildRequestChunks(text)[0] || text;
      const q = toneHint() + sample;
      const result = await runLimitedRequest(() => gmRequest({
        url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=${encodeURIComponent(q)}`,
        parse: r => parseJsonSafe(r.responseText, []),
      }));
      return result.data?.[2] || null;
    } catch {
      return null;
    }
  }

  function normalizeLangCode(code) {
    return String(code || '')
      .trim()
      .toLowerCase()
      .replace('_', '-')
      .split('-')[0];
  }

  function getElementSourceText(el) {
    return expandSlang((el?.dataset?.orig || el?.innerText || '').trim());
  }

  function isElementInAutoTranslateViewport(el) {
    if (!el || !el.isConnected) return false;
    const rect = el.getBoundingClientRect();
    return rect.bottom >= -AUTO_TRANSLATE_VIEWPORT_MARGIN &&
      rect.top <= window.innerHeight + AUTO_TRANSLATE_VIEWPORT_MARGIN &&
      rect.right >= 0 &&
      rect.left <= window.innerWidth;
  }

  function canAutoTranslateButton(btn) {
    const el = btn?._targetEl;
    return !!btn &&
      !!el &&
      btn.dataset.st === 'orig' &&
      !btn.dataset.skip &&
      !btn.classList.contains('busy') &&
      isElementInAutoTranslateViewport(el);
  }

  function markButtonBusy(btn) {
    if (!btn || btn.dataset.st !== 'orig') return;
    btn.innerHTML = '<span class="sp"></span>';
    btn.classList.add('busy');
  }

  function restoreIdleButton(btn) {
    if (!btn || btn.dataset.st === 'done') return;
    btn.classList.remove('busy');
    btn.innerHTML = `🌐 ${cfg.targetLang.toUpperCase()}`;
  }

  const autoTranslateTimers = new WeakMap();

  function clearAutoTranslateTimer(btn) {
    const timer = autoTranslateTimers.get(btn);
    if (timer) {
      clearTimeout(timer);
      autoTranslateTimers.delete(btn);
    }
  }

  function scheduleAutoTranslate(btn) {
    if (!cfg.autoTranslateOnScroll || !canAutoTranslateButton(btn)) return;
    clearAutoTranslateTimer(btn);
    const timer = setTimeout(() => {
      autoTranslateTimers.delete(btn);
      if (!canAutoTranslateButton(btn)) return;
      btn.dataset.autoMode = 'scroll';
      btn.click();
    }, AUTO_TRANSLATE_SETTLE_MS);
    autoTranslateTimers.set(btn, timer);
  }

  async function getDetectedLang(el, text) {
    if (!el) return normalizeLangCode(await detectLanguage(text));
    const cached = normalizeLangCode(el.dataset.rtpLang);
    if (cached) return cached;
    const detected = normalizeLangCode(await detectLanguage(text));
    if (detected) el.dataset.rtpLang = detected;
    return detected;
  }

  function shouldSkipDetectedLang(detectedLang) {
    if (!detectedLang) return false;
    const detected = normalizeLangCode(detectedLang);
    return detected === normalizeLangCode(cfg.targetLang) ||
      detected === normalizeLangCode(cfg.uiLang);
  }

  async function doTranslateDeepL(text) {
    const keys = parseDeepLKeys();
    if (!keys.length) {
      toast(S('toastDeepLKeysMissing'));
      return text;
    }

    const targetLang = getDeepLTargetLang(cfg.targetLang);
    if (!targetLang) {
      toast(`${S('toastDeepLUnsupported')} ${cfg.targetLang.toUpperCase()}`);
      return text;
    }

    const chunks = buildRequestChunks(text);
    const start = deeplKeyCursor % keys.length;

    const translatedChunks = [];
    for (const chunk of chunks) {
      const q = toneHint() + chunk;
      let translatedChunk = null;

      for (let offset = 0; offset < keys.length; offset++) {
        const idx = (start + offset) % keys.length;
        const apiKey = keys[idx];
        const endpoint = getDeepLEndpoint(apiKey);

        const result = await runLimitedRequest(() => gmRequest({
          method: 'POST',
          url: `${endpoint}/v2/translate`,
          headers: {
            Authorization: `DeepL-Auth-Key ${apiKey}`,
            'Content-Type': 'application/json',
          },
          data: JSON.stringify({
            text: [q],
            target_lang: targetLang,
          }),
          parse: r => parseJsonSafe(r.responseText),
        }));
        const translated = result.ok ? result.data?.translations?.[0]?.text || null : null;

        if (translated) {
          deeplKeyCursor = idx + 1;
          translatedChunk = translated;
          break;
        }
      }

      translatedChunks.push(translatedChunk || chunk);
    }

    return translatedChunks.join('\n\n');
  }

  const ENGINE_TRANSLATORS = {
    google: async (chunk) => {
      const q = toneHint() + chunk;
      const response = await runLimitedRequest(() => gmRequest({
        url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${cfg.targetLang}&dt=t&q=${encodeURIComponent(q)}`,
        parse: r => parseJsonSafe(r.responseText, []),
        fallback: [],
      }));
      return response.data?.[0]?.map(i => i[0]).join('') || chunk;
    },
    mymemory: async (chunk) => {
      const q = toneHint() + chunk;
      const response = await runLimitedRequest(() => gmRequest({
        url: `https://api.mymemory.translated.net/get?q=${encodeURIComponent(q)}&langpair=auto|${cfg.targetLang}`,
        parse: r => parseJsonSafe(r.responseText),
        fallback: { responseData: { translatedText: chunk } },
      }));
      return response.data?.responseData?.translatedText || chunk;
    },
    deepl: doTranslateDeepL,
  };

  async function doTranslate(text) {
    const key = getTranslationCacheKey(text);
    if (cache[key]) { cache[key].ts = Date.now(); return cache[key].val; }

    let result = text;

    try {
      const translateWithEngine = ENGINE_TRANSLATORS[cfg.engine] || ENGINE_TRANSLATORS.google;
      if (cfg.engine === 'deepl') {
        result = await translateWithEngine(text);
      } else {
        const translatedChunks = [];
        for (const chunk of buildRequestChunks(text)) {
          translatedChunks.push(await translateWithEngine(chunk));
        }
        result = translatedChunks.join('\n\n');
      }
    } catch { }

    if (pirateMode) result = pirateify(result);
    if (yodaMode) result = yodaify(result);
    result = convertUnits(result);

    cacheSet(key, result);
    return result;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § TTS
  // ═══════════════════════════════════════════════════════════════════════════
  // ═══════════════════════════════════════════════════════════════════════════
  // § УТИЛИТЫ
  // ═══════════════════════════════════════════════════════════════════════════
  function toast(msg, ms = 2800) {
    let el = document.getElementById('rtp-toast');
    if (!el) { el = document.createElement('div'); el.id = 'rtp-toast'; document.body.appendChild(el); }
    el.textContent = msg; el.classList.add('on');
    clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('on'), ms);
  }

  function mkToggle(checked, onChange) {
    const wrap = document.createElement('label'); wrap.className = 'tog';
    const inp = document.createElement('input'); inp.type = 'checkbox'; inp.checked = !!checked;
    inp.onchange = () => onChange(inp.checked);
    const tr = document.createElement('span'); tr.className = 'tog-tr';
    wrap.append(inp, tr);
    return wrap;
  }

  function fmt(n) { return Number(n).toLocaleString(); }
  function fmtK(n) { return n >= 10000 ? (n / 1000).toFixed(1) + 'K' : fmt(n); }

  function updateStats() {
    const map = { 'st-cnt': fmt(cfg.totalCount), 'st-chr': fmtK(cfg.totalChars), 'st-pg': document.querySelectorAll('.rtp-btn').length };
    for (const [id, v] of Object.entries(map)) { const el = document.getElementById(id); if (el) el.textContent = v; }
  }

  let translationDisplayMode = 'translated';

  function syncControlVisibility() {
    document.querySelectorAll('.rtp-ctrl').forEach(host => {
      host.style.display = btnsHidden ? 'none' : 'flex';
    });
    document.querySelectorAll('.rtp-btn').forEach(btn => {
      btn.style.display = btnsHidden ? 'none' : '';
    });
    document.querySelectorAll('.rtp-tb').forEach(tb => {
      tb.style.display = btnsHidden ? 'none' : '';
    });
    document.querySelectorAll('.rtp-bi').forEach(bi => {
      bi.style.display = translationDisplayMode === 'original' ? '' : 'none';
    });

    if (!btnsHidden || cfg.bilingualMode) {
      document.querySelectorAll('.rtp-btn.done').forEach(btn => renderTranslatedState(btn, btn._targetEl));
    }
  }

  function getViewToggleLabel() {
    return translationDisplayMode === 'translated' ? S('btnShowOriginals') : S('btnShowTranslations');
  }

  function updateViewToggleButton() {
    const btn = document.getElementById('rtp-view-toggle');
    if (!btn) return;
    btn.textContent = getViewToggleLabel();
    btn.classList.toggle('originals', translationDisplayMode === 'original');
  }

  function setTranslatedContentLayout(el, translated) {
    if (!el) return;

    if (translated) {
      if (el.dataset.rtpOrigStyle == null) {
        el.dataset.rtpOrigStyle = el.getAttribute('style') || '';
      }
      el.style.setProperty('white-space', 'pre-wrap', 'important');
      el.style.setProperty('overflow-wrap', 'anywhere', 'important');
      el.style.setProperty('word-break', 'break-word', 'important');
      el.style.setProperty('max-height', 'none', 'important');
      el.style.setProperty('height', 'auto', 'important');
      el.style.setProperty('overflow', 'visible', 'important');
      el.style.setProperty('-webkit-line-clamp', 'unset', 'important');
      el.style.setProperty('line-clamp', 'unset', 'important');
      return;
    }

    if (el.dataset.rtpOrigStyle == null) return;
    const originalStyle = el.dataset.rtpOrigStyle;
    if (originalStyle) el.setAttribute('style', originalStyle);
    else el.removeAttribute('style');
    delete el.dataset.rtpOrigStyle;
  }

  function ensureBilingualBlock(btn, el, translated) {
    if (!btn || !el || !translated) return null;
    if (!btn._bi || !btn._bi.isConnected) {
      const bi = document.createElement('div');
      bi.className = 'rtp-bi';
      bi.innerText = translated;
      btn._bi = bi;
      (btn._ctrlHost || el).after(bi);
    } else if (btn._bi.innerText !== translated) {
      btn._bi.innerText = translated;
    }
    return btn._bi;
  }

  function renderTranslatedState(btn, el, mode = translationDisplayMode) {
    if (!btn || !el || btn.dataset.st !== 'done') return;
    const orig = el.dataset.orig || '';
    const translated = btn.dataset.translation || '';
    if (!translated) return;

    if (cfg.bilingualMode) {
      const showOriginalWithBilingual = mode === 'original';
      const bi = ensureBilingualBlock(btn, el, translated);
      el.innerText = showOriginalWithBilingual ? orig : translated;
      setTranslatedContentLayout(el, !showOriginalWithBilingual);
      if (bi) bi.style.display = showOriginalWithBilingual ? '' : 'none';
      return;
    }

    if (btn._bi) btn._bi.style.display = 'none';

    el.innerText = mode === 'translated' ? translated : orig;
    setTranslatedContentLayout(el, mode === 'translated');
  }

  function applyDisplayModeToButton(btn, el, mode = translationDisplayMode) {
    renderTranslatedState(btn, el, mode);
  }

  function applyDisplayModeToAll(mode) {
    translationDisplayMode = mode;
    document.querySelectorAll('.rtp-btn.done').forEach(btn => renderTranslatedState(btn, btn._targetEl, mode));
    updateViewToggleButton();
  }

  function resetTranslationView(btn, el) {
    if (btn._bi) {
      btn._bi.remove();
      btn._bi = null;
    }
    setTranslatedContentLayout(el, false);
    if (el.dataset.orig) el.innerText = el.dataset.orig;
    if (btn._tb) {
      btn._tb.remove();
      btn._tb = null;
    }
    btn.innerHTML = `🌐 ${cfg.targetLang.toUpperCase()}`;
    btn.classList.remove('done', 'busy');
    btn.dataset.st = 'orig';
    btn.dataset.skip = '';
    btn.dataset.translation = '';
  }

  function renderTranslationResult(btn, el, text) {
    btn.classList.remove('busy');
    btn.classList.add('done');
    btn.innerHTML = `✓ ${S('btnOrig')}`;
    btn.dataset.st = 'done';
    btn.dataset.translation = text;

    if (cfg.bilingualMode) {
      const bi = ensureBilingualBlock(btn, el, text);
      if (bi) bi.classList.add('rtp-fi');
      renderTranslatedState(btn, el);
      if (bi) setTimeout(() => bi.classList.remove('rtp-fi'), 450);
      return;
    }

    el.classList.add('rtp-fi');
    renderTranslatedState(btn, el);
    setTimeout(() => el.classList.remove('rtp-fi'), 450);
  }

  function finalizeTranslation(btn, el, src, res) {
    renderTranslationResult(btn, el, res);
    if (cfg.autoScroll && !cfg.autoTranslateOnScroll) {
      btn.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
    buildTranslationToolbar(btn, el, src, res);

    cfg.totalCount++;
    cfg.totalChars += (el.dataset.orig || '').length;
    flushStats();
    pushHistory(el.dataset.orig, res, cfg.targetLang);
    updateStats();
  }

  function buildTranslationToolbar(btn, el, src, res) {
    const tb = document.createElement('div');
    tb.className = 'rtp-tb';
    const addAction = (label, fn) => {
      const t = document.createElement('span');
      t.className = 'rtp-t';
      t.textContent = label;
      t.onclick = (e) => {
        e.preventDefault();
        e.stopPropagation();
        fn();
      };
      tb.appendChild(t);
    };

    addAction(S('btnCopy'), () => navigator.clipboard.writeText(res).then(() => toast(S('copied'))));
    addAction(S('btnRetry'), async () => {
      delete cache[getTranslationCacheKey(src)];
      flushCache();
      resetTranslationView(btn, el);
      tb.remove();
      btn._tb = null;
      await new Promise(r => setTimeout(r, 50));
      btn.click();
    });

    btn._tb = tb;
    (btn._ctrlHost || btn).append(tb);
    if (btnsHidden) tb.style.display = 'none';
  }

  function getCustomColors() {
    try {
      return cfg.customColors
        ? (typeof cfg.customColors === 'string' ? JSON.parse(cfg.customColors) : cfg.customColors)
        : {};
    } catch {
      return {};
    }
  }

  function setExclusivePills(panel, attr, onSelect) {
    panel.querySelectorAll(`[${attr}]`).forEach(pill => {
      pill.onclick = () => {
        panel.querySelectorAll(`[${attr}]`).forEach(x => x.classList.remove('on'));
        pill.classList.add('on');
        onSelect(pill.dataset[attr.slice(5)]);
      };
    });
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ИНЖЕКТ КНОПОК
  // ═══════════════════════════════════════════════════════════════════════════
  const SELS = [
    'shreddit-post [slot="title"]', 'h1[slot="title"]', 'a[id^="post-title"]',
    'div[shreddit-comment-content]', '.md:not(.rtp-done)',
  ].join(',');

  function attachBtn(el, opts = {}) {
    const { autoStart = false } = opts;
    if (!el || el.dataset.rtpDone) return;
    const txt = (el.innerText || '').trim();
    if (txt.length < 5) return;
    el.dataset.rtpDone = '1'; el.classList.add('rtp-done');
    el.dataset.rtpLang = ''; // 用于缓存检测到的语言

    const ctrlHost = document.createElement('div');
    ctrlHost.className = 'rtp-ctrl';

    const btn = document.createElement('button');
    btn.className = 'rtp-btn';
    btn.innerHTML = `🌐 ${cfg.targetLang.toUpperCase()}`;
    btn.dataset.st = 'orig';
    btn.dataset.skip = ''; // 初始化跳过标记为空
    btn.dataset.translation = '';
    btn._targetEl = el;
    btn._ctrlHost = ctrlHost;
    el._rtpBtn = btn;

    btn.onclick = async (e) => {
      e.preventDefault(); e.stopPropagation();
      const scrollDriven = btn.dataset.autoMode === 'scroll';
      btn.dataset.autoMode = '';
      clearAutoTranslateTimer(btn);

      if (btn.dataset.st === 'done') return resetTranslationView(btn, el);
      if (btn.classList.contains('busy')) return;

      if (!el.dataset.orig) el.dataset.orig = el.innerText.trim();
      const src = getElementSourceText(el);
      const detectedLang = await getDetectedLang(el, src);
      if (shouldSkipDetectedLang(detectedLang)) {
        btn.dataset.skip = '1';
        return;
      }
      if (scrollDriven && !isElementInAutoTranslateViewport(el)) return;

      markButtonBusy(btn);
      const res = await enqueue(() => doTranslate(src));
      if (res == null) return restoreIdleButton(btn);
      finalizeTranslation(btn, el, src, res);
    };

    ctrlHost.append(btn);
    el.after(ctrlHost);
    if (btnsHidden) ctrlHost.style.display = 'none';
    autoTranslateObserver.observe(el);

    // 如果启用滚动时自动翻译,自动触发翻译
    if (autoStart && cfg.autoTranslateOnScroll) {
      requestAnimationFrame(() => scheduleAutoTranslate(btn));
    }
  }

  // IntersectionObserver — переводим сначала видимые
  const ioQueue = new Set();
  const io = new IntersectionObserver((entries) => {
    entries.forEach(e => { if (e.isIntersecting) { attachBtn(e.target, { autoStart: true }); ioQueue.delete(e.target); io.unobserve(e.target); } });
  }, { rootMargin: '200px' });

  const autoTranslateObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const btn = entry.target._rtpBtn;
      if (!btn) return;
      if (entry.isIntersecting) scheduleAutoTranslate(btn);
      else clearAutoTranslateTimer(btn);
    });
  }, { rootMargin: `${AUTO_TRANSLATE_VIEWPORT_MARGIN}px 0px ${AUTO_TRANSLATE_VIEWPORT_MARGIN}px 0px` });

  function injectButtons() {
    document.querySelectorAll(SELS).forEach(el => {
      if (el.dataset.rtpDone || (el.innerText || '').trim().length < 5) return;
      // 检查元素是否在视口内(包括200px边距,与IntersectionObserver一致)
      const rect = el.getBoundingClientRect();
      const inViewport = (
        rect.top <= window.innerHeight + 200 &&
        rect.bottom >= -200 &&
        rect.left <= window.innerWidth + 200 &&
        rect.right >= -200
      );
      if (inViewport) {
        // 视口内元素也需要遵循自动翻译配置:
        // 否则首屏内容和动态加载后直接落在视口里的内容只会显示按钮,不会自动翻译。
        attachBtn(el, { autoStart: cfg.autoTranslateOnScroll });
        // 如果之前在队列中,移除并取消观察
        if (ioQueue.has(el)) {
          ioQueue.delete(el);
          io.unobserve(el);
        }
      } else {
        // 不在视口内,加入观察队列
        if (!ioQueue.has(el)) { ioQueue.add(el); io.observe(el); }
      }
    });
  }

  function getVisibleTranslateButtons() {
    injectButtons();
    return Array.from(document.querySelectorAll('.rtp-btn'))
      .filter(canAutoTranslateButton);
  }

  async function translateVisibleContent(fab) {
    const buttons = getVisibleTranslateButtons();
    if (!buttons.length) return toast(S('toastDone'));

    if (fab) {
      fab.classList.add('busy');
      fab.innerHTML = '<span class="sp"></span>';
    }

    buttons.forEach(btn => {
      btn.dataset.autoMode = 'scroll';
      btn.click();
    });

    await Promise.all(buttons.map(btn => new Promise(resolve => {
      const startedAt = Date.now();
      const done = () => btn.dataset.st === 'done' || btn.dataset.skip === '1' || !btn.classList.contains('busy');
      const tick = () => {
        if (done() || Date.now() - startedAt > 20000) return resolve();
        setTimeout(tick, 120);
      };
      tick();
    })));

    if (fab) {
      fab.classList.remove('busy');
      fab.classList.add('done');
      fab.textContent = '✓';
      setTimeout(() => {
        fab.classList.remove('done');
        fab.textContent = '🌐';
      }, 900);
    }
  }

  // MutationObserver с debounce
  let mutTimer = null;
  new MutationObserver(() => {
    clearTimeout(mutTimer);
    mutTimer = setTimeout(injectButtons, 400);
  }).observe(document.body, { childList: true, subtree: true });

  // ═══════════════════════════════════════════════════════════════════════════
  // § FAB
  // ═══════════════════════════════════════════════════════════════════════════
  function createTranslateFab() {
    if (document.getElementById('rtp-fab')) return;

    const fab = document.createElement('button');
    fab.id = 'rtp-fab';
    fab.type = 'button';
    fab.textContent = '🌐';
    fab.title = `${S('title')} · ${cfg.hotkeyPanel}`;
    fab.setAttribute('aria-label', S('title'));
    fab.onclick = () => translateVisibleContent(fab);
    fab.ondblclick = (e) => {
      e.preventDefault();
      e.stopPropagation();
      buildPanel();
    };
    document.body.appendChild(fab);
  }

  function createViewToggle() {
    if (document.getElementById('rtp-view-toggle')) return;

    const viewToggleBtn = document.createElement('button');
    viewToggleBtn.id = 'rtp-view-toggle';
    viewToggleBtn.onclick = () => {
      const nextMode = translationDisplayMode === 'translated' ? 'original' : 'translated';
      applyDisplayModeToAll(nextMode);
      toast(S(nextMode === 'original' ? 'toastShowingOriginals' : 'toastShowingTranslations'));
    };
    document.body.appendChild(viewToggleBtn);
    updateViewToggleButton();
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ПАНЕЛЬ
  // ═══════════════════════════════════════════════════════════════════════════
  let activeTab = 'settings';
  let btnsHidden = true;

  function setupPanelLanguageControls(panel) {
    const uiSel = panel.querySelector('#ui-sel');
    const uiSearch = panel.querySelector('#ui-s');
    const uiOpts = UI_SUPPORTED.map(c => ({ c, n: langName(c, c) })).sort((a, b) => a.n.localeCompare(b.n));
    const renderUi = (q = '') => {
      const f = q.toLowerCase();
      uiSel.innerHTML = uiOpts
        .filter(({ c, n }) => !f || n.toLowerCase().includes(f) || c.includes(f))
        .map(({ c, n }) => `<option value="${c}" ${c === cfg.uiLang ? 'selected' : ''}>${n} (${c.toUpperCase()})</option>`)
        .join('');
    };

    renderUi();
    uiSearch.addEventListener('input', () => renderUi(uiSearch.value));
    buildLangSelect(panel.querySelector('#tg-sel'), panel.querySelector('#tg-s'), ALL_LANGS, cfg.targetLang);

    panel.querySelector('#btn-apply-ui').onclick = () => {
      save('uiLang', uiSel.value);
      updateViewToggleButton();
      panel.remove();
      toast(S('toastApply'));
      setTimeout(buildPanel, 180);
    };

    panel.querySelector('#btn-save-lang').onclick = () => {
      save('targetLang', panel.querySelector('#tg-sel').value);
      toast(S('toastSave'));
      setTimeout(() => location.reload(), 900);
    };
  }

  function setupDeepLControls(panel) {
    const box = panel.querySelector('#deepl-api-keys');
    const testBtn = panel.querySelector('#btn-test-deepl');
    const toggleBtn = panel.querySelector('#btn-toggle-deepl-visibility');
    let secretsVisible = false;

    function syncSecretVisibility() {
      if (!box || !toggleBtn) return;
      box.classList.toggle('masked', !secretsVisible);
      const label = S(secretsVisible ? 'btnToggleSecretsHide' : 'btnToggleSecretsShow');
      toggleBtn.textContent = label;
      toggleBtn.title = label;
      toggleBtn.setAttribute('aria-label', label);
    }

    syncSecretVisibility();
    if (toggleBtn) {
      toggleBtn.onclick = () => {
        secretsVisible = !secretsVisible;
        syncSecretVisibility();
      };
    }

    panel.querySelector('#btn-save-deepl').onclick = () => {
      const normalized = parseDeepLKeys(box.value).join(', ');
      save('deeplApiKeys', normalized);
      box.value = normalized;
      toast(S('toastDeepLKeysSaved'));
    };

    testBtn.onclick = async () => {
      const originalLabel = S('btnTestDeepL');
      const normalized = parseDeepLKeys(box.value).join(', ');
      box.value = normalized;

      if (!normalized) return toast(S('toastDeepLKeysMissing'));

      testBtn.disabled = true;
      testBtn.textContent = S('toastDeepLTesting');

      const result = await testDeepLApi(normalized);

      testBtn.disabled = false;
      testBtn.textContent = originalLabel;

      if (result.ok) {
        const used = Number(result.data?.character_count || 0).toLocaleString();
        const limitRaw = Number(result.data?.character_limit || 0);
        const limit = limitRaw > 0 ? limitRaw.toLocaleString() : '∞';
        return toast(`${S('toastDeepLOk')} · ${maskDeepLKey(result.apiKey)} · ${used}/${limit}`);
      }
      if (result.reason === 'missing_keys') return toast(S('toastDeepLKeysMissing'));

      const statusText = result?.status ? `HTTP ${result.status}` : 'NETWORK';
      toast(`${S('toastDeepLFail')} · ${maskDeepLKey(result?.apiKey)} · ${statusText}`, 4200);
    };
  }

  function setupPanelPills(panel) {
    setExclusivePills(panel, 'data-eng', (engine) => {
      save('engine', engine);
      if (engine === 'deepl' && !parseDeepLKeys().length) toast(S('toastDeepLKeysMissing'));
    });
    setExclusivePills(panel, 'data-tone', (tone) => save('tone', tone));
    setExclusivePills(panel, 'data-th', (theme) => {
      save('theme', theme);
      save('customColors', null);
      cfg.customColors = null;
      // Сбрасываем пикеры к значениям новой темы
      const base = THEMES[theme] || THEMES.dark;
      ['clr-acc', 'clr-bg', 'clr-ok'].forEach((id, i) => {
        const key = ['acc', 'bg', 'ok'][i];
        const inp = panel.querySelector(`#${id}`);
        if (inp) inp.value = colorToHex(base[key] || '#888888');
      });
      applyTheme(theme);
    });
  }

  function setupPanelToggles(panel) {
    const toggleKeys = ['bilingualMode', 'autoConvert', 'autoScroll', 'incognito', 'autoTranslateOnScroll'];
    panel.querySelectorAll('.tog-row').forEach((row, i) => {
      const key = toggleKeys[i];
      if (!key) return;
      row.appendChild(mkToggle(cfg[key], v => {
        save(key, v);
        if (key === 'bilingualMode') {
          document.querySelectorAll('.rtp-btn.done').forEach(btn => renderTranslatedState(btn, btn._targetEl));
          syncControlVisibility();
        }
      }));
    });
  }

  function setupRequestLimitControls(panel) {
    const sl = panel.querySelector('#sl-delay');
    sl.oninput = () => {
      save('requestDelay', +sl.value);
      panel.querySelector('#sl-val').textContent = sl.value + ' ' + S('unitMs');
    };

    [
      ['#limit-concurrency', 'maxConcurrentRequests', DEF.maxConcurrentRequests, 1, 20],
      ['#limit-rps', 'maxRequestsPerSecond', DEF.maxRequestsPerSecond, 1, 50],
      ['#limit-chars', 'maxTextLengthPerRequest', DEF.maxTextLengthPerRequest, 100, 20000],
      ['#limit-paragraphs', 'maxParagraphsPerRequest', DEF.maxParagraphsPerRequest, 1, 100],
    ].forEach(([selector, key, fallback, min, max]) => {
      const input = panel.querySelector(selector);
      if (!input) return;
      const sync = () => {
        const value = getPositiveInt(input.value, fallback, min, max);
        input.value = value;
        save(key, value);
      };
      input.addEventListener('change', sync);
      input.addEventListener('blur', sync);
    });
  }

  function setupHotkeyCapturer(panel, btnId, resetId, cfgKey, defaultVal) {
    const btn = panel.querySelector(`#${btnId}`);
    const rst = panel.querySelector(`#${resetId}`);
    if (!btn || !rst) return;

    let capturing = false;
    let captureHandler = null;
    const stopCapture = () => {
      if (captureHandler) document.removeEventListener('keydown', captureHandler, true);
      captureHandler = null;
      capturing = false;
      btn.classList.remove('capturing');
    };

    btn.onclick = () => {
      if (capturing) return;
      capturing = true;
      btn.textContent = S('hotkeyPress');
      btn.classList.add('capturing');

      captureHandler = (e) => {
        e.preventDefault();
        e.stopPropagation();
        if (['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) return;

        const parts = [];
        if (e.ctrlKey) parts.push('Ctrl');
        if (e.altKey) parts.push('Alt');
        if (e.shiftKey) parts.push('Shift');
        if (e.metaKey) parts.push('Meta');
        parts.push(e.key.length === 1 ? e.key.toUpperCase() : e.key);

        const combo = parts.join('+');
        save(cfgKey, combo);
        btn.textContent = combo;
        stopCapture();
      };

      document.addEventListener('keydown', captureHandler, true);
    };

    rst.onclick = () => {
      stopCapture();
      save(cfgKey, defaultVal);
      btn.textContent = defaultVal;
    };
  }

  function setupPanelHotkeys(panel) {
    setupHotkeyCapturer(panel, 'hk-panel', 'hk-panel-r', 'hotkeyPanel', 'F2');
  }

  function setupPanelColorPickers(panel) {
    const base = THEMES[cfg.theme] || THEMES.dark;
    const merged = Object.assign({}, base, getCustomColors());
    const colorInputs = [
      { id: 'clr-acc', key: 'acc' },
      { id: 'clr-bg',  key: 'bg'  },
      { id: 'clr-ok',  key: 'ok'  },
    ];

    colorInputs.forEach(({ id, key }) => {
      const inp = panel.querySelector(`#${id}`);
      if (!inp) return;
      inp.value = colorToHex(merged[key] || '#888888');
      inp.oninput = () => {
        const newCC = getCustomColors();
        newCC[key] = inp.value;

        if (key === 'bg') {
          const r = parseInt(inp.value.slice(1, 3), 16);
          const g = parseInt(inp.value.slice(3, 5), 16);
          const b = parseInt(inp.value.slice(5, 7), 16);
          newCC.surf = `rgba(${r},${g},${b},.85)`;
        }
        if (key === 'acc') {
          const r = parseInt(inp.value.slice(1, 3), 16);
          const g = parseInt(inp.value.slice(3, 5), 16);
          const b = parseInt(inp.value.slice(5, 7), 16);
          newCC.glow = `rgba(${r},${g},${b},.38)`;
          newCC.dim = `rgba(${r},${g},${b},.11)`;
        }
        if (key === 'ok') {
          const r = parseInt(inp.value.slice(1, 3), 16);
          const g = parseInt(inp.value.slice(3, 5), 16);
          const b = parseInt(inp.value.slice(5, 7), 16);
          newCC.okd = `rgba(${r},${g},${b},.11)`;
        }

        save('customColors', JSON.stringify(newCC));
        applyTheme(cfg.theme);
      };
    });

    panel.querySelector('#btn-reset-clr').onclick = () => {
      save('customColors', null);
      applyTheme(cfg.theme);
      colorInputs.forEach(({ id, key }) => {
        const inp = panel.querySelector(`#${id}`);
        if (inp) inp.value = colorToHex(base[key] || '#888888');
      });
    };
  }

  function setupPanelMiscControls(panel) {
    panel.querySelector('#btn-rpos').onclick = () => {
      panel.style.top = '11%';
      panel.style.left = 'calc(50% - 186px)';
      GM_setValue(PREFIX + 'panelX', null);
      GM_setValue(PREFIX + 'panelY', null);
    };

    panel.querySelector('#btn-ccache').onclick = () => {
      cache = {};
      flushCache();
      toast(S('cacheCleared'));
    };

    panel.querySelector('#btn-exp').onclick = () => {
      const a = document.createElement('a');
      a.href = 'data:text/json,' + encodeURIComponent(JSON.stringify({ v: 8, cfg }, null, 2));
      a.download = 'rtp-v8-settings.json';
      a.click();
    };

    panel.querySelector('#btn-imp').onclick = () => {
      const inp = document.createElement('input');
      inp.type = 'file';
      inp.accept = '.json';
      inp.onchange = e => {
        const fr = new FileReader();
        fr.onload = ev => {
          try {
            const d = JSON.parse(ev.target.result);
            const src = d.cfg || d;
            Object.entries(src).forEach(([k, v]) => {
              if (k in DEF) {
                cfg[k] = v;
                GM_setValue(PREFIX + k, v);
              }
            });
            location.reload();
          } catch {
            toast('❌ Ошибка импорта');
          }
        };
        fr.readAsText(e.target.files[0]);
      };
      inp.click();
    };

    panel.querySelector('#btn-surp').onclick = () => {
      const r = ALL_LANGS[Math.floor(Math.random() * ALL_LANGS.length)];
      save('targetLang', r);
      toast(`${S('toastSurprise')} ${getLangName(r)}`);
      setTimeout(() => location.reload(), 1100);
    };

    const pirBtn = panel.querySelector('#btn-pir');
    pirBtn.onclick = () => {
      pirateMode = !pirateMode;
      pirBtn.classList.toggle('active', pirateMode);
      toast(pirateMode ? S('toastPirateOn') : S('toastPirateOff'));
    };
    pirBtn.classList.toggle('active', pirateMode);

    const yodBtn = panel.querySelector('#btn-yoda');
    yodBtn.onclick = () => {
      yodaMode = !yodaMode;
      yodBtn.classList.toggle('active', yodaMode);
      toast(yodaMode ? S('toastYodaOn') : S('toastYodaOff'));
    };
    yodBtn.classList.toggle('active', yodaMode);

    const hideBtn = panel.querySelector('#btn-hide');
    hideBtn.classList.toggle('active', btnsHidden);
    hideBtn.onclick = () => {
      btnsHidden = !btnsHidden;
      syncControlVisibility();
      hideBtn.textContent = btnsHidden ? S('btnShow') : S('btnHide');
      hideBtn.classList.toggle('active', btnsHidden);
    };

  }

  function setupPanelTabs(panel) {
    panel.querySelectorAll('.tab').forEach(tab => tab.onclick = () => {
      activeTab = tab.dataset.tab;
      panel.querySelectorAll('.tab').forEach(t => t.classList.remove('on'));
      tab.classList.add('on');
      panel.querySelectorAll('.pane').forEach(p => p.style.display = 'none');
      panel.querySelector(`#pane-${activeTab}`).style.display = 'flex';
      if (activeTab === 'history') renderHistory(panel);
      if (activeTab === 'settings') updateStats();
    });
  }

  function setupPanelDrag(panel) {
    const hdr = panel.querySelector('#rtp-hdr');
    hdr.onmousedown = e => {
      if (e.target.id === 'rtp-close') return;
      const ox = e.clientX - panel.offsetLeft;
      const oy = e.clientY - panel.offsetTop;
      const mm = ev => {
        panel.style.left = (ev.clientX - ox) + 'px';
        panel.style.top = (ev.clientY - oy) + 'px';
      };
      const cleanup = () => {
        GM_setValue(PREFIX + 'panelX', panel.style.left);
        GM_setValue(PREFIX + 'panelY', panel.style.top);
        document.removeEventListener('mousemove', mm);
        document.removeEventListener('mouseup', cleanup);
      };
      document.addEventListener('mousemove', mm);
      document.addEventListener('mouseup', cleanup, { once: true });
      document.addEventListener('mouseleave', cleanup, { once: true });
    };
  }

  function buildPanel() {
    const old = document.getElementById('rtp-panel');
    if (old) { old.remove(); return; }

    const panel = document.createElement('div'); panel.id = 'rtp-panel';
    panel.style.top = GM_getValue(PREFIX + 'panelY', '11%');
    panel.style.left = GM_getValue(PREFIX + 'panelX', 'calc(50% - 186px)');

    panel.innerHTML = `
        <div id="rtp-hdr">
            <div class="logo-w">
                <div class="logo-ic">🌐</div>
                <div>
                    <div class="logo-nm">${S('title')}</div>
                    <div class="logo-vr">${S('ver')} · ${escapeHtml(cfg.hotkeyPanel)}</div>
                </div>
            </div>
            <button id="rtp-close">✕</button>
        </div>

        <div id="rtp-stats">
            <div class="st"><div class="st-v" id="st-cnt">${fmt(cfg.totalCount)}</div><div class="st-l">${S('statTranslations')}</div></div>
            <div class="st"><div class="st-v" id="st-chr">${fmtK(cfg.totalChars)}</div><div class="st-l">${S('statChars')}</div></div>
            <div class="st"><div class="st-v" id="st-pg">…</div><div class="st-l">${S('statOnPage')}</div></div>
        </div>

        <div id="rtp-tabs">
            <div class="tab ${activeTab === 'settings' ? 'on' : ''}" data-tab="settings">${S('tabSettings')}</div>
            <div class="tab ${activeTab === 'history' ? 'on' : ''}"  data-tab="history">${S('tabHistory')}</div>
            <div class="tab ${activeTab === 'extras' ? 'on' : ''}"   data-tab="extras">${S('tabExtras')}</div>
        </div>

        <!-- НАСТРОЙКИ -->
        <div id="pane-settings" class="pane" style="display:${activeTab === 'settings' ? 'flex' : 'none'}">

            <div>
                <span class="lbl">${S('secUiLang')}</span>
                <div class="lang-wrap">
                    <input class="lang-search" id="ui-s" placeholder="${S('searchLang')}">
                    <select class="lang-sel" id="ui-sel" size="4"></select>
                </div>
                <button class="btn-p" id="btn-apply-ui" style="margin-top:8px;height:40px;font-size:11px;">${S('applyUi')}</button>
            </div>

            <div class="div"></div>

            <div>
                <span class="lbl">${S('secTargetLang')}</span>
                <div class="lang-wrap">
                    <input class="lang-search" id="tg-s" placeholder="${S('searchLang')}">
                    <select class="lang-sel" id="tg-sel" size="4"></select>
                </div>
                <button class="btn-p" id="btn-save-lang" style="margin-top:8px;height:40px;font-size:11px;">${S('saveLang')}</button>
            </div>

            <div class="div"></div>

            <div>
                <span class="lbl">${S('secEngine')}</span>
                <div class="pills">
                    <div class="pill ${cfg.engine === 'google' ? 'on' : ''}" data-eng="google">${S('engGoogle')}</div>
                    <div class="pill ${cfg.engine === 'mymemory' ? 'on' : ''}" data-eng="mymemory">${S('engMymemory')}</div>
                    <div class="pill ${cfg.engine === 'deepl' ? 'on' : ''}" data-eng="deepl">${S('engDeepL')}</div>
                </div>
            </div>

            <div>
                <div class="api-head">
                    <span class="lbl">${S('secDeepLApi')}</span>
                    <button type="button" class="api-eye" id="btn-toggle-deepl-visibility" title="${S('btnToggleSecretsShow')}">${S('btnToggleSecretsShow')}</button>
                </div>
                <div class="api-wrap">
                    <textarea class="api-box masked" id="deepl-api-keys" spellcheck="false" autocapitalize="off" autocomplete="off" placeholder="${S('deeplApiPlaceholder')}">${escapeHtml(cfg.deeplApiKeys || '')}</textarea>
                </div>
                <div class="api-help">${S('deeplApiHelp')}</div>
                <div class="g2" style="margin-top:8px;">
                    <button class="btn-p ghost" id="btn-save-deepl" style="height:40px;font-size:11px;">${S('saveDeepLApi')}</button>
                    <button class="btn-p ghost" id="btn-test-deepl" style="height:40px;font-size:11px;">${S('btnTestDeepL')}</button>
                </div>
            </div>

            <div>
                <span class="lbl">${S('secTone')}</span>
                <div class="pills">
                    <div class="pill ${cfg.tone === 'normal' ? 'on' : ''}" data-tone="normal">${S('toneNeutral')}</div>
                    <div class="pill ${cfg.tone === 'formal' ? 'on' : ''}" data-tone="formal">${S('toneFormal')}</div>
                    <div class="pill ${cfg.tone === 'slang' ? 'on' : ''}"  data-tone="slang">${S('toneSlang')}</div>
                </div>
            </div>

            <div class="div"></div>

            <div>
                <span class="lbl">${S('secTheme')}</span>
                <div class="pills">
                    <div class="pill ${cfg.theme === 'dark' ? 'on' : ''}"      data-th="dark">${S('themeDark')}</div>
                    <div class="pill ${cfg.theme === 'cyberpunk' ? 'on' : ''}" data-th="cyberpunk">${S('themeCyber')}</div>
                    <div class="pill ${cfg.theme === 'dracula' ? 'on' : ''}"   data-th="dracula">${S('themeDracula')}</div>
                </div>
            </div>

            <div class="div"></div>

            <div class="tog-row"><span class="tog-lbl">${S('togBilingual')}</span></div>
            <div class="tog-row"><span class="tog-lbl">${S('togAutoConvert')}</span></div>
            <div class="tog-row"><span class="tog-lbl">${S('togAutoScroll')}</span></div>
            <div class="tog-row"><span class="tog-lbl">${S('togIncognito')}</span></div>
            <div class="tog-row"><span class="tog-lbl">${S('togAutoTranslateOnScroll')}</span></div>

            <div class="div"></div>

            <div class="g2">
                <div class="btn-s" id="btn-rpos">${S('btnResetPos')}</div>
                <div class="btn-s" id="btn-ccache">${S('btnClearCache')}</div>
            </div>
            <div class="g2">
                <div class="btn-s" id="btn-exp">${S('btnExport')}</div>
                <div class="btn-s" id="btn-imp">${S('btnImport')}</div>
            </div>
        </div>

        <!-- ИСТОРИЯ -->
        <div id="pane-history" class="pane" style="display:${activeTab === 'history' ? 'flex' : 'none'}"></div>

        <!-- ДОПОЛНЕНИЯ -->
        <div id="pane-extras" class="pane" style="display:${activeTab === 'extras' ? 'flex' : 'none'}">
            <div class="g2">
                <div class="btn-s" id="btn-surp">${S('btnSurprise')}</div>
                <div class="btn-s" id="btn-pir" >${S('btnPirate')}</div>
            </div>
            <div class="g2">
                <div class="btn-s" id="btn-yoda">${S('btnYoda')}</div>
                <div class="btn-s" id="btn-hide">${btnsHidden ? S('btnShow') : S('btnHide')}</div>
            </div>
            <div class="div"></div>
            <div>
                <span class="lbl">${S('sliderDelay')}</span>
                <input type="range" class="slider" id="sl-delay" min="50" max="600" value="${cfg.requestDelay}">
                <div class="slider-v" id="sl-val">${cfg.requestDelay} ${S('unitMs')}</div>
            </div>
            <div>
                <span class="lbl">${S('secRequestLimits')}</span>
                <div class="num-grid">
                    <div class="num-card">
                        <span class="num-lbl">${S('maxConcurrentRequests')}</span>
                        <input type="number" class="num-inp" id="limit-concurrency" min="1" max="20" step="1" value="${getMaxConcurrentRequests()}">
                    </div>
                    <div class="num-card">
                        <span class="num-lbl">${S('maxRequestsPerSecond')}</span>
                        <input type="number" class="num-inp" id="limit-rps" min="1" max="50" step="1" value="${getMaxRequestsPerSecond()}">
                    </div>
                    <div class="num-card">
                        <span class="num-lbl">${S('maxTextLengthPerRequest')}</span>
                        <input type="number" class="num-inp" id="limit-chars" min="100" max="20000" step="100" value="${getMaxTextLengthPerRequest()}">
                    </div>
                    <div class="num-card">
                        <span class="num-lbl">${S('maxParagraphsPerRequest')}</span>
                        <input type="number" class="num-inp" id="limit-paragraphs" min="1" max="100" step="1" value="${getMaxParagraphsPerRequest()}">
                    </div>
                </div>
            </div>
            <div class="div"></div>
            <div>
                <span class="lbl">⌨️ ${S('secHotkeys')}</span>
                <div class="hk-row">
                    <span class="hk-lbl">${S('hotkeyPanel')}</span>
                    <button class="hk-btn" id="hk-panel">${escapeHtml(cfg.hotkeyPanel)}</button>
                    <button class="hk-reset" id="hk-panel-r">${S('hotkeyReset')}</button>
                </div>
            </div>
            <div class="div"></div>
            <div>
                <span class="lbl">🎨 ${S('secColors')}</span>
                <div class="clr-grid">
                    <div class="clr-row"><span class="clr-lbl">${S('colorAcc')}</span><input type="color" class="clr-inp" id="clr-acc"></div>
                    <div class="clr-row"><span class="clr-lbl">${S('colorBg')}</span><input type="color" class="clr-inp" id="clr-bg"></div>
                    <div class="clr-row"><span class="clr-lbl">${S('colorOk')}</span><input type="color" class="clr-inp" id="clr-ok"></div>
                </div>
                <div class="btn-s" id="btn-reset-clr" style="margin-top:8px;">${S('btnResetColors')}</div>
            </div>
        </div>
        `;

    document.body.appendChild(panel);
    setupPanelLanguageControls(panel);
    setupDeepLControls(panel);
    setupPanelPills(panel);
    setupPanelToggles(panel);
    setupRequestLimitControls(panel);
    setupPanelHotkeys(panel);
    setupPanelColorPickers(panel);
    setupPanelMiscControls(panel);
    setupPanelTabs(panel);
    renderHistory(panel);
    setupPanelDrag(panel);
    panel.querySelector('#rtp-close').onclick = () => panel.remove();
    updateStats();
  }

  function renderHistory(panel) {
    const pane = panel.querySelector('#pane-history');
    if (!pane) return;
    pane.innerHTML = '';

    if (!history.length) {
      pane.innerHTML = `<div style="text-align:center;color:var(--rtp-mut);padding:28px 0;font-size:13px;">📭 ${S('histEmpty')}</div>`;
      return;
    }

    history.forEach(item => {
      const div = document.createElement('div'); div.className = 'hi';
      const o = document.createElement('div'); o.className = 'hi-o'; o.textContent = item.orig;
      const t = document.createElement('div'); t.className = 'hi-t'; t.textContent = item.translated;
      const m = document.createElement('div'); m.className = 'hi-m'; m.textContent = `→ ${item.lang.toUpperCase()} · ${new Date(item.ts).toLocaleTimeString()}`;
      div.append(o, t, m);
      div.onclick = () => navigator.clipboard.writeText(item.translated).then(() => toast(S('copied')));
      pane.appendChild(div);
    });

    const clr = document.createElement('div'); clr.className = 'btn-s'; clr.style.marginTop = '4px';
    clr.textContent = S('histClear');
    clr.onclick = () => { history = []; GM_setValue(PREFIX + 'rtp_v8_history', '[]'); renderHistory(panel); };
    pane.appendChild(clr);
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // § ЗАПУСК
  // ═══════════════════════════════════════════════════════════════════════════
  if (cfg.theme === 'light') { save('theme', 'dark'); cfg.theme = 'dark'; }
  applyTheme(cfg.theme);
  createTranslateFab();
  createViewToggle();
  injectButtons();

  function matchesHotkey(e, combo) {
    if (!combo) return false;
    const parts = combo.split('+');
    const key = parts[parts.length - 1];
    const needCtrl = parts.includes('Ctrl');
    const needAlt = parts.includes('Alt');
    const needShift = parts.includes('Shift');
    const needMeta = parts.includes('Meta');
    return e.key === key &&
      e.ctrlKey === needCtrl &&
      e.altKey === needAlt &&
      e.shiftKey === needShift &&
      e.metaKey === needMeta;
  }

  window.addEventListener('keydown', e => {
    if (matchesHotkey(e, cfg.hotkeyPanel)) { e.preventDefault(); buildPanel(); }
  });

})();