Chrome Translator

Chrome 浏览器原生翻译功能的沉浸式翻译脚本,支持整页翻译、保留原文对照和自动翻译新增内容

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Chrome Translator
// @namespace    https://ndllz.cn/
// @version      1.2.0
// @description  Chrome 浏览器原生翻译功能的沉浸式翻译脚本,支持整页翻译、保留原文对照和自动翻译新增内容
// @author       ndllz
// @match        *://*/*
// @run-at       document-end
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// ==/UserScript==
(function () {
    'use strict';

    // --------------------------
    // Config
    // --------------------------
    const LANGUAGES = [
      'zh-Hans','zh-Hant','en','ja','ru','ko','es','fr','de','pt','it','nl','sv',
      'da','fi','no','id','th','pl','tr','vi','ar','hi','bn','kn','mr','hr','cs',
      'hu','uk','he','bg','ro','te','lt','sl','el','ta'
    ];
    const STORAGE_KEYS = {
      source: 'ft_source_lang',
      target: 'ft_target_lang',
      autoObserve: 'ft_auto_observe_',
      position: 'ft_position',
      disabled: 'ft_disabled',
      siteDisabled: 'ft_site_disabled_',
      wordSelection: 'ft_word_selection_global', // 全局划词翻译开关
    };


    // --------------------------
    // Utils: page-context API access
    // --------------------------
    const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
    function hasTranslator() { return 'Translator' in pageWindow; }
    function hasLanguageDetector() { return 'LanguageDetector' in pageWindow; }
    const DEFAULTS = inferDefaultPair();

    // DisplayNames for language labels
    const displayNames = (() => {
      try {
        if ('Intl' in window && 'DisplayNames' in Intl) {
          const locales = navigator.languages && navigator.languages.length ? navigator.languages : [navigator.language || 'en-US'];
          return new Intl.DisplayNames(locales, { type: 'language' });
        }
      } catch {}
      return null;
    })();
    function langLabel(code) {
      if (!displayNames) return code;
      try {
        const l = displayNames.of(code);
        return l ? `${l} <${code}>` : code;
      } catch {
        return code;
      }
    }

    function inferDefaultPair() {
      const nav = navigator.language || 'en-US';
      const isZh = /^zh\b/i.test(nav);
      return {
        source: hasLanguageDetector() ? 'auto' : (isZh ? 'zh-Hans' : 'en'),
        target: 'zh-Hans',
        autoObserve: false,
        position: { top: 50, right: 12 },
      };
    }

    function getStored(key, fallback) {
      try { return GM_getValue ? GM_getValue(key, fallback) : fallback; } catch { return fallback; }
    }
    function setStored(key, val) {
      try { GM_setValue && GM_setValue(key, val); } catch {}
    }

    // --------------------------
    // Register menu commands
    // --------------------------
    if (typeof GM_registerMenuCommand === 'function') {
      GM_registerMenuCommand('重新启用 Chrome Translator', () => {
        setStored(STORAGE_KEYS.disabled, false);
        setStored(STORAGE_KEYS.siteDisabled + location.hostname, false);
        location.reload();
      });

      GM_registerMenuCommand('清除本站禁用状态', () => {
        setStored(STORAGE_KEYS.siteDisabled + location.hostname, false);
        location.reload();
      });
    }

    // --------------------------
    // Check if disabled
    // --------------------------
    if (getStored(STORAGE_KEYS.disabled, false)) {
      console.log('[ChromeTranslator] Permanently disabled');
      return;
    }
    if (getStored(STORAGE_KEYS.siteDisabled + location.hostname, false)) {
      console.log('[ChromeTranslator] Disabled for this site');
      return;
    }

    // --------------------------
    // State variables - 需要在buildUI之前定义
    // --------------------------
  let sourceLang = getStored(STORAGE_KEYS.source, DEFAULTS.source);
  let targetLang = getStored(STORAGE_KEYS.target, DEFAULTS.target);
  let autoObserve = !!getStored(STORAGE_KEYS.autoObserve + location.hostname, DEFAULTS.autoObserve);
  let detectorInstance = null;
  let translatorInstance = null;
  let inProgress = false;
  let observer = null;
  let lastPairKey = '';
  const MAX_CONCURRENCY = Math.min(4, Math.max(1, (navigator.hardwareConcurrency || 4) - 2));
  let keepOriginal = true; // 默认保留原文
  let isPageTranslated = false; // 跟踪页面翻译状态
  let wordSelectionEnabled = !!getStored(STORAGE_KEYS.wordSelection, false); // 全局划词翻译开关
  let selectionBubble = null; // 划词翻译悬浮框
  let translationPopup = null; // 翻译结果弹窗
  let currentAbortController = null; // 用于取消翻译的控制器

    // --------------------------
    // UI
    // --------------------------
    let ui;
    try {
      injectStyles();
      ui = buildUI();
      document.documentElement.appendChild(ui.container);
      console.log('[ChromeTranslator] UI initialized successfully');
    } catch (error) {
      console.error('[ChromeTranslator] UI initialization failed:', error);
      return;
    }

  // 检查页面是否已翻译
  function checkPageTranslationStatus() {
    const pairs = document.querySelectorAll('span.ft-pair');
    const oldTranslated = document.querySelectorAll('[data-ft-original]');
    const newTranslated = document.querySelectorAll('[data-ft-original-html]');
    return pairs.length > 0 || oldTranslated.length > 0 || newTranslated.length > 0;
  }

    // init UI selections
    populateSelect(ui.sourceSelect, buildSourceOptions());
    populateSelect(ui.targetSelect, buildTargetOptions());
    ui.sourceSelect.value = sourceLang;
    ui.targetSelect.value = targetLang;
    ui.observeCheckbox.checked = autoObserve;
    // keepOriginal will be bound after UI is extended below

    // bind events
    ui.translateBtn.addEventListener('click', () => translatePage(false));
    ui.observeCheckbox.addEventListener('change', (e) => {
      autoObserve = e.target.checked;
      setStored(STORAGE_KEYS.autoObserve + location.hostname, autoObserve);
      setupObserver(autoObserve);
    });
    // 保留原文功能已默认启用
    ui.sourceSelect.addEventListener('change', () => {
      sourceLang = ui.sourceSelect.value;
      setStored(STORAGE_KEYS.source, sourceLang);
    });
    ui.targetSelect.addEventListener('change', () => {
      targetLang = ui.targetSelect.value;
      setStored(STORAGE_KEYS.target, targetLang);
      ui.container.updateFabTooltip(); // 更新提示词
    });

      // auto observe initially
  setupObserver(autoObserve);

  // 初始化保留原文功能(默认启用)
  toggleKeepOriginal(keepOriginal);

  // 初始化时检查页面翻译状态
  isPageTranslated = checkPageTranslationStatus();

  // 根据页面状态设置悬浮按钮样式
  if (isPageTranslated) {
    ui.fab.classList.add('ft-translated');
  }

  // 初始化划词翻译
  if (wordSelectionEnabled) {
    setupSelectionListeners();
  }

    // --------------------------
    // Core: Translate workflow
    // --------------------------
    async function translatePage(isFromObserver) {
      if (inProgress) return;
      inProgress = true;
      
      // 创建新的AbortController用于取消翻译
      currentAbortController = new AbortController();
      
      // 添加翻译中状态指示器
      ui.fab.classList.add('ft-translating');
      ui.fab.classList.remove('ft-translated');
      
      setPanelBusy(true);
      setButtonState('loading', '准备翻译...');
      try {
        const availabilityOk = await ensureAvailability();
        if (!availabilityOk) return;

        const realSource = await resolveRealSourceLanguage();
        const pairKey = `${realSource}->${targetLang}`;
        const instanceOk = await ensureTranslator(realSource, targetLang);
        if (!instanceOk) return;

        if (pairKey !== lastPairKey) {
          // pair changed; helpful to re-run from scratch to avoid mixed language
          lastPairKey = pairKey;
        }

                const nodes = collectTextElements(document.body);
        const scanText = `扫描到 ${nodes.length} 个文本元素,开始并行翻译(并发 ${MAX_CONCURRENCY})`;
        ui.progressText.textContent = scanText + '...';
        setButtonState('loading', scanText);
        setProgress(0, true);

        const useWorker = await canUseWorkerTranslator(realSource, targetLang);
        let done = 0;
        const updateProgress = () => {
          if (done % 20 === 0 || done === nodes.length) {
            const percentage = nodes.length > 0 ? (done / nodes.length) * 100 : 0;
            const progressText = `已翻译 ${done}/${nodes.length}`;
            ui.progressText.textContent = progressText;
            setButtonState('loading', progressText);
            setProgress(percentage, true);
          }
        };

        if (useWorker) {
          const pool = await createWorkerPool(MAX_CONCURRENCY, realSource, targetLang);
          try {
            const tasks = nodes.map(({ element, original }) => async () => {
              if (currentAbortController?.signal.aborted) return;
              if (!original.trim()) { done++; updateProgress(); return; }
              try {
                const translated = await pool.translate(original.replace(/\n/g, '<br>'));
                if (currentAbortController?.signal.aborted) return;
                const pretty = (translated || '').replace(/<br>/g, '\n').trim();
                applyTranslationToElement(element, original, pretty);
              } catch {}
              finally { done++; updateProgress(); }
            });
            await runWithConcurrency(tasks, MAX_CONCURRENCY);
          } finally { pool.terminate(); }
        }
        else {
          // Fallback: main-thread concurrency using a single translator instance
          const limit = createLimiter(Math.max(2, Math.min(3, MAX_CONCURRENCY)));
          await Promise.all(nodes.map(({ element, original }) => limit(async () => {
            if (currentAbortController?.signal.aborted) return;
            if (!original.trim()) { done++; updateProgress(); return; }
            try {
              const translated = await translateStreaming(`${original.replace(/\n/g, '<br>')}`);
              if (currentAbortController?.signal.aborted) return;
              const pretty = (translated || '').replace(/<br>/g, '\n').trim();
              applyTranslationToElement(element, original, pretty);
            } catch {}
            finally { done++; updateProgress(); }
          })));
        }

        // 检查翻译是否被取消
        if (currentAbortController?.signal.aborted) {
          const cancelText = '翻译已取消';
          ui.progressText.textContent = cancelText;
          ui.fab.classList.remove('ft-translating');
          setButtonState('idle', cancelText);
          setProgress(0, false);
          return;
        }

              const completeText = `翻译完成:${done}/${nodes.length}`;
              ui.progressText.textContent = completeText;

      // 更新翻译状态
      isPageTranslated = true;

      // 移除翻译中状态,添加已翻译状态
      ui.fab.classList.remove('ft-translating');
      ui.fab.classList.add('ft-success', 'ft-translated');
      ui.container.updateFabTooltip(); // 更新提示词
      setButtonState('success', `翻译完成 ${done}/${nodes.length}`);
      setProgress(100, false); // 隐藏进度条
      setTimeout(() => {
        ui.fab.classList.remove('ft-success');
        // 保留ft-translated状态指示
      }, 3000); // 3秒后移除成功状态

      } catch (err) {
        showError(err);
        ui.fab.classList.remove('ft-translating');
        setButtonState('error', '翻译失败');
        setProgress(0, false); // 隐藏进度条
      } finally {
        // 确保移除翻译中状态
        ui.fab.classList.remove('ft-translating');
        setPanelBusy(false);
        inProgress = false;
        currentAbortController = null;

        // ensure UI is still present after translation
        setTimeout(() => {
          if (!document.querySelector('.ft-ui')) {
            console.warn('[ChromeTranslator] UI missing after translation, restoring...');
            try {
              document.documentElement.appendChild(ui.container);
            } catch (e) {
              console.error('[ChromeTranslator] Failed to restore UI after translation:', e);
            }
          }
        }, 100);
      }
    }

    function cancelTranslation() {
      if (currentAbortController) {
        currentAbortController.abort();
        console.log('[ChromeTranslator] 翻译已取消');
        const restoreText = '翻译已取消,正在还原...';
        ui.progressText.textContent = restoreText;
        setButtonState('loading', restoreText);
      }
    }

    function restorePage() {
      // 如果正在翻译,先取消翻译
      if (inProgress && currentAbortController) {
        cancelTranslation();
        // 给一点时间让取消操作生效
        setTimeout(() => {
          performRestore();
        }, 100);
        return;
      }
      
      performRestore();
    }

    function performRestore() {
      let restored = 0;
      
      // 首先处理新的元素级翻译还原
      const translatedElements = Array.from(document.querySelectorAll('[data-ft-original-html]'));
      for (const element of translatedElements) {
        const originalHTML = element.getAttribute('data-ft-original-html');
        if (originalHTML) {
          element.innerHTML = originalHTML;
          element.removeAttribute('data-ft-original');
          element.removeAttribute('data-ft-original-html');
          element.removeAttribute('data-ft-original-text');
          restored++;
        }
      }
      
      // unwrap ft-pair wrappers (旧的文本节点级翻译)
      const pairs = Array.from(document.querySelectorAll('span.ft-pair'));
      for (const w of pairs) {
        const original = w.getAttribute('data-ft-original-text') || w.querySelector('.ft-original')?.textContent || '';
        const leading = w.getAttribute('data-ft-leading') || '';
        const trailing = w.getAttribute('data-ft-trailing') || '';
        const textNode = document.createTextNode(leading + original + trailing);
        w.replaceWith(textNode);
        restored++;
      }
      
      // compatibility: old style replacements
      const iter = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
      let node;
      while ((node = iter.nextNode())) {
        const parent = node.parentElement;
        if (!parent) continue;
        if (parent.closest('.ft-ui')) continue;
        if (parent.hasAttribute('data-ft-original')) {
          node.textContent = parent.getAttribute('data-ft-original-text') || node.textContent;
          parent.removeAttribute('data-ft-original');
          parent.removeAttribute('data-ft-original-text');
          restored++;
        }
      }
          const restoreCompleteText = `已还原 ${restored} 个元素`;
          ui.progressText.textContent = restoreCompleteText;

    // 更新翻译状态
    isPageTranslated = false;

    // 显示成功状态并移除翻译状态
    ui.fab.classList.add('ft-success');
    ui.fab.classList.remove('ft-translated');
          ui.container.updateFabTooltip(); // 更新提示词
    setButtonState('success', `已还原 ${restored} 个节点`);
    setTimeout(() => {
      ui.fab.classList.remove('ft-success');
    }, 2000); // 2秒后移除成功状态
    }

    function setupObserver(enabled) {
      if (observer) {
        observer.disconnect();
        observer = null;
      }
      if (!enabled) return;
      observer = new MutationObserver((mutations) => {
        // check if UI was accidentally removed and restore it
        if (!document.querySelector('.ft-ui')) {
          console.warn('[ChromeTranslator] UI was removed, restoring...');
          try {
            document.documentElement.appendChild(ui.container);
          } catch (e) {
            console.error('[ChromeTranslator] Failed to restore UI:', e);
          }
        }

        // debounce: translate new nodes only
        const added = [];
        for (const m of mutations) {
          m.addedNodes && added.push(...m.addedNodes);
        }
        const needTranslation = added.some(n => n.nodeType === 1 && !n.closest?.('.ft-ui'));
        if (needTranslation) {
          // schedule lightly
          setTimeout(() => translatePage(true), 600);
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });
    }

    // --------------------------
    // Translator/Detector helpers
    // --------------------------
    async function ensureAvailability() {
      if (!hasTranslator()) {
        ui.statusText.textContent = '此浏览器不支持原生翻译(需 Chrome 138+)';
        return false;
      }
      return true;
    }

    async function ensureDetector() {
      if (!hasLanguageDetector() || sourceLang !== 'auto') return null;
      if (detectorInstance) return detectorInstance;
      setPanelBusy(true, '下载语言检测模型中...');
      try {
        detectorInstance = await pageWindow.LanguageDetector.create({
          monitor(m) {
            m.addEventListener('downloadprogress', (e) => {
              if (typeof e.loaded === 'number') {
                ui.statusText.textContent = `检测模型下载 ${(e.loaded * 100).toFixed(2)}%`;
              }
            });
          },
        });
        ui.statusText.textContent = '检测模型已就绪';
        return detectorInstance;
      } catch (e) {
        ui.statusText.textContent = '检测模型加载失败';
        return null;
      } finally {
        setPanelBusy(false);
      }
    }

    async function resolveRealSourceLanguage() {
      if (sourceLang !== 'auto') return sourceLang;
      const det = await ensureDetector();
      if (!det) return 'und';
      // sample: title + a bit of body text
      const sample = getSampleText(1500);
      const list = await det.detect(sample).catch(() => []);
      const lang = (list && list[0] && list[0].detectedLanguage) || 'und';
      return lang === 'und' ? 'und' : lang;
    }

    async function ensureTranslator(src, tgt) {
      setPanelBusy(true, '准备翻译模型...');
      try {
        // 检查当前实例是否匹配语言对且仍然有效
        if (translatorInstance &&
            translatorInstance.sourceLanguage === src &&
            translatorInstance.targetLanguage === tgt) {
          try {
            // 验证实例是否仍然可用(通过检查配额)
            await translatorInstance.measureInputUsage('test');
            ui.statusText.textContent = '翻译模型就绪';
            return true;
          } catch (error) {
            console.log('[ChromeTranslator] 现有翻译器实例无效,重新创建:', error.message);
            // 实例无效,销毁并重新创建
            try {
              translatorInstance.destroy();
            } catch (destroyError) {
              console.warn('[ChromeTranslator] 销毁翻译器实例失败:', destroyError);
            }
            translatorInstance = null;
          }
        } else if (translatorInstance) {
          // 语言对不匹配,销毁旧实例
          console.log('[ChromeTranslator] 语言对改变,销毁旧翻译器实例');
          try {
            translatorInstance.destroy();
          } catch (destroyError) {
            console.warn('[ChromeTranslator] 销毁翻译器实例失败:', destroyError);
          }
          translatorInstance = null;
        }

        // 检查翻译器可用性
        const availability = await pageWindow.Translator.availability({
          sourceLanguage: src,
          targetLanguage: tgt,
        });

        if (availability === 'unavailable') {
          ui.statusText.textContent = `不支持 ${src} -> ${tgt} 翻译`;
          return false;
        }

        ui.statusText.textContent = availability === 'available' ? '翻译模型已缓存,正在加载...' : '翻译模型下载中...';

        // 创建新的翻译器实例
        translatorInstance = await pageWindow.Translator.create({
          sourceLanguage: src,
          targetLanguage: tgt,
          monitor(m) {
            m.addEventListener('downloadprogress', (e) => {
              if (typeof e.loaded === 'number') {
                ui.statusText.textContent = `翻译模型 ${(e.loaded * 100).toFixed(2)}%`;
              }
            });
          },
        });

        ui.statusText.textContent = '翻译模型就绪';
        return true;
      } catch (e) {
        console.error('[ChromeTranslator] 翻译器初始化失败:', e);
        showError(e);
        // 确保清理无效实例
        if (translatorInstance) {
          try {
            translatorInstance.destroy();
          } catch (destroyError) {
            console.warn('[ChromeTranslator] 清理失败的翻译器实例时出错:', destroyError);
          }
          translatorInstance = null;
        }
        return false;
      } finally {
        setPanelBusy(false);
      }
    }

    async function translateStreaming(text) {
      const res = translatorInstance.translateStreaming(text);
      const reader = res.getReader();
      let out = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        out += value;
      }
      return out;
    }

    // --------------------------
    // Concurrency helpers and Worker pool
    // --------------------------
    function createLimiter(limit) {
      let active = 0;
      const queue = [];
      const next = () => {
        if (active >= limit || queue.length === 0) return;
        const { fn, resolve, reject } = queue.shift();
        active++;
        Promise.resolve()
          .then(fn)
          .then(resolve, reject)
          .finally(() => { active--; next(); });
      };
      return (fn) => new Promise((resolve, reject) => { queue.push({ fn, resolve, reject }); next(); });
    }

    async function runWithConcurrency(tasks, limit) {
      const limiter = createLimiter(limit);
      await Promise.all(tasks.map(task => limiter(task)));
    }

      async function canUseWorkerTranslator(src, tgt) {
    try {
      // 首先检测CSP是否允许创建Worker
      if (!canCreateWorker()) {
        console.log('[ChromeTranslator] Web Workers blocked by CSP, falling back to main thread');
        return false;
      }

      const url = createWorkerURL();
      const worker = new Worker(url);
      URL.revokeObjectURL(url);
      const ready = new Promise((resolve) => {
        const timer = setTimeout(() => { resolve(false); worker.terminate(); }, 3000);
        worker.onmessage = (e) => {
          if (e.data && e.data.type === 'ready') { clearTimeout(timer); resolve(true); worker.terminate(); }
          else if (e.data && e.data.type === 'error') { clearTimeout(timer); resolve(false); worker.terminate(); }
        };
      });
      worker.postMessage({ type: 'init-check' });
      return await ready;
    } catch (e) {
      console.log('[ChromeTranslator] Worker creation failed:', e.message);
      return false;
    }
  }

  function canCreateWorker() {
    try {
      // 尝试创建一个简单的Worker来检测CSP
      const testWorkerCode = 'self.postMessage("test");';
      const blob = new Blob([testWorkerCode], { type: 'application/javascript' });
      const workerURL = URL.createObjectURL(blob);
      const testWorker = new Worker(workerURL);
      testWorker.terminate();
      URL.revokeObjectURL(workerURL);
      return true;
    } catch (e) {
      // 如果创建Worker失败,说明被CSP阻止
      console.log('[ChromeTranslator] Worker creation blocked by CSP:', e.message);
      return false;
    }
  }

    function createWorkerURL() {
      const code = `self.onmessage = async (e) => {
    const data = e.data || {};
    try {
      if (data.type === 'init-check') {
        if ('Translator' in self) postMessage({ type: 'ready' });
        else postMessage({ type: 'error', message: 'Translator not in worker' });
        return;
      }
    } catch (err) { postMessage({ type: 'error', message: String(err) }); }
  };`;
      const blob = new Blob([code], { type: 'application/javascript' });
      return URL.createObjectURL(blob);
    }

      async function createWorkerPool(size, src, tgt) {
    // 在创建Worker池之前检查CSP
    if (!canCreateWorker()) {
      throw new Error('Web Workers are blocked by Content Security Policy. Falling back to main thread.');
    }

    // Build full worker script with translator support
    const code = `let translator = null;
self.onmessage = async (e) => {
  const msg = e.data || {};
  try {
    if (msg.type === 'init') {
      const availability = await self.Translator.availability({ sourceLanguage: msg.src, targetLanguage: msg.tgt });
      if (availability === 'unavailable') { postMessage({ type: 'init-error', message: 'unavailable' }); return; }
      translator = await self.Translator.create({ sourceLanguage: msg.src, targetLanguage: msg.tgt });
      postMessage({ type: 'inited' });
      return;
    }
    if (msg.type === 'translate') {
      if (!translator) { postMessage({ type: 'translate-error', id: msg.id, message: 'no-translator' }); return; }
      const res = translator.translateStreaming(msg.text);
      const reader = res.getReader();
      let out = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        out += value;
      }
      postMessage({ type: 'translated', id: msg.id, text: out });
      return;
    }
  } catch (err) {
    if (msg.type === 'translate') postMessage({ type: 'translate-error', id: msg.id, message: String(err) });
    else postMessage({ type: 'init-error', message: String(err) });
  }
};`;

    let url, workers;
    try {
      url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' }));
      workers = Array.from({ length: size }, () => new Worker(url));
      URL.revokeObjectURL(url);
    } catch (e) {
      if (url) URL.revokeObjectURL(url);
      console.log('[ChromeTranslator] Failed to create worker pool:', e.message);
      throw new Error('Failed to create worker pool due to CSP restrictions.');
    }

      await Promise.all(workers.map((w) => new Promise((resolve, reject) => {
        const timer = setTimeout(() => { w.terminate(); reject(new Error('init timeout')); }, 10000);
        w.onmessage = (e) => {
          if (e.data && e.data.type === 'inited') { clearTimeout(timer); resolve(null); }
        };
        w.onerror = (e) => { clearTimeout(timer); reject(new Error('worker error')); };
        w.postMessage({ type: 'init', src, tgt });
      })).catch((e) => {
        workers.forEach(w => w.terminate());
        throw e;
      }));

      let nextId = 1;
      const pending = new Map();
      let idx = 0;
      workers.forEach((w) => {
        w.onmessage = (e) => {
          const data = e.data || {};
          if (data.type === 'translated') {
            const entry = pending.get(data.id);
            if (entry) { pending.delete(data.id); entry.resolve(data.text); }
          } else if (data.type === 'translate-error') {
            const entry = pending.get(data.id);
            if (entry) { pending.delete(data.id); entry.reject(new Error(data.message || 'translate error')); }
          }
        };
      });

      function pick() { const w = workers[idx]; idx = (idx + 1) % workers.length; return w; }

      return {
        translate(text) {
          const id = nextId++;
          const worker = pick();
          const p = new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
          worker.postMessage({ type: 'translate', id, text });
          return p;
        },
        terminate() { workers.forEach(w => w.terminate()); pending.forEach(p => p.reject(new Error('terminated'))); pending.clear(); },
      };
    }

    // --------------------------
    // DOM: text nodes
    // --------------------------
    function collectTextElements(root) {
      const elements = [];
      const processedElements = new Set();
      
      // 获取所有包含文本的元素
      const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
        acceptNode(element) {
          // skip UI panel - critical to prevent UI removal
          if (element.closest('.ft-ui')) return NodeFilter.FILTER_REJECT;
          // skip hidden
          const style = getComputedStyle(element);
          if (style && (style.display === 'none' || style.visibility === 'hidden')) return NodeFilter.FILTER_REJECT;
          // skip code/script/etc
          const tag = element.tagName.toLowerCase();
          if (['script','style','noscript','textarea','input','code','pre','svg','math','kbd','samp'].includes(tag)) {
            return NodeFilter.FILTER_REJECT;
          }
          // skip marked notranslate
          if (element.closest('.notranslate,[translate="no"]')) return NodeFilter.FILTER_REJECT;
          // already translated using legacy flag or ft-pair wrapper
          if (element.hasAttribute('data-ft-original') || element.closest('.ft-pair')) return NodeFilter.FILTER_REJECT;
          
          return NodeFilter.FILTER_ACCEPT;
        }
      });
      
      let element;
      while ((element = walker.nextNode())) {
        // 检查这个元素是否包含直接的文本内容(非空白)
        const textContent = getElementTextContent(element);
        if (!textContent.trim()) continue;
        
        // 检查是否是文本容器(段落级元素)
        if (isTextContainer(element) && !hasTextContainerAncestor(element, processedElements)) {
          const original = textContent;
          elements.push({ 
            element: element, 
            original: original,
            leading: '',
            trailing: ''
          });
          processedElements.add(element);
        }
      }
      
      return elements;
    }
    
    function getElementTextContent(element) {
      // 获取元素的完整文本内容,保留内联元素的结构
      let text = '';
      for (const node of element.childNodes) {
        if (node.nodeType === Node.TEXT_NODE) {
          text += node.textContent;
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          // 对于内联元素,保留其文本内容
          const tag = node.tagName.toLowerCase();
          if (['a', 'span', 'strong', 'em', 'b', 'i', 'code', 'mark', 'small', 'sub', 'sup', 'u'].includes(tag)) {
            text += node.textContent;
          }
        }
      }
      return text;
    }
    
    function isTextContainer(element) {
      const tag = element.tagName.toLowerCase();
      // 段落级元素
      if (['p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'td', 'th', 'blockquote', 'article', 'section'].includes(tag)) {
        return true;
      }
      // 检查是否有文本内容且包含内联元素
      const hasText = Array.from(element.childNodes).some(node => 
        node.nodeType === Node.TEXT_NODE && node.textContent.trim()
      );
      const hasInlineElements = Array.from(element.childNodes).some(node =>
        node.nodeType === Node.ELEMENT_NODE && 
        ['a', 'span', 'strong', 'em', 'b', 'i', 'code', 'mark', 'small', 'sub', 'sup', 'u'].includes(node.tagName.toLowerCase())
      );
      return hasText && hasInlineElements;
    }
    
    function hasTextContainerAncestor(element, processedElements) {
      let parent = element.parentElement;
      while (parent && parent !== document.body) {
        if (processedElements.has(parent) && isTextContainer(parent)) {
          return true;
        }
        parent = parent.parentElement;
      }
      return false;
    }

    function markTranslated(textNode, original) {
      const el = textNode.parentElement;
      if (!el) return;
      if (!el.hasAttribute('data-ft-original')) {
        el.setAttribute('data-ft-original', '1');
        el.setAttribute('data-ft-original-text', original);
      }
    }

    function getSampleText(limit) {
      const parts = [document.title || ''];
      const nodes = collectTextElements(document.body).slice(0, 200);
      for (const it of nodes) {
        parts.push(it.original);
        if (parts.join('\n').length > limit) break;
      }
      return parts.join('\n').slice(0, limit);
    }

    // --------------------------
    // UI building
    // --------------------------
    function buildSourceOptions() {
      const options = [];
      options.push({ value: 'auto', label: `自动检测` + (hasLanguageDetector() ? '' : '(不支持)'), disabled: !hasLanguageDetector() });
      for (const l of LANGUAGES) options.push({ value: l, label: langLabel(l) });
      return options;
    }
    function buildTargetOptions() {
      return LANGUAGES.map(l => ({ value: l, label: langLabel(l) }));
    }

    function populateSelect(select, options) {
      select.innerHTML = '';
      for (const opt of options) {
        const o = document.createElement('option');
        o.value = opt.value;
        o.textContent = opt.label;
        if (opt.disabled) o.disabled = true;
        select.appendChild(o);
      }
    }

        function setPanelBusy(busy, text) {
      ui.container.classList.toggle('ft-busy', !!busy);
      if (text) ui.statusText.textContent = text;
    }

    function showError(e) {
      console.error('[ChromeTranslator] error:', e);
      ui.statusText.textContent = `错误:${e?.message || e}`;
    }

    // 按钮状态管理
    function setButtonState(state, text) {
      const btn = ui.translateBtn;
      if (!btn) return;

      // 清除所有状态类
      btn.classList.remove('success', 'error');
      btn.disabled = false;

      // 移除加载动画
      const existingSpinner = btn.querySelector('.ft-loading-spinner');
      if (existingSpinner) {
        existingSpinner.remove();
      }

      switch (state) {
        case 'loading':
          btn.disabled = true;
          btn.innerHTML = `<div class="ft-loading-spinner"></div><span>翻译中...</span>`;
          // 进度信息显示在状态文本中,不在按钮上
          if (ui.statusText) {
            ui.statusText.textContent = text || '翻译中...';
          }
          break;
        case 'success':
          btn.classList.add('success');
          btn.textContent = '翻译';
          if (ui.statusText) {
            ui.statusText.textContent = text || '翻译完成';
            setTimeout(() => {
              if (ui.statusText) {
                ui.statusText.textContent = '翻译模型就绪';
              }
            }, 2000);
          }
          break;
        case 'error':
          btn.classList.add('error');
          btn.textContent = '翻译';
          if (ui.statusText) {
            ui.statusText.textContent = text || '翻译失败';
            setTimeout(() => {
              if (ui.statusText) {
                ui.statusText.textContent = '翻译模型就绪';
              }
            }, 3000);
          }
          break;
        case 'idle':
        default:
          btn.textContent = '翻译';
          if (ui.statusText && text) {
            ui.statusText.textContent = text;
          }
      }
    }

    // 进度条管理
    function setProgress(percentage, show = true) {
      if (!ui.progressContainer || !ui.progressBar) return;

      if (show) {
        ui.progressContainer.classList.remove('hidden');
        ui.progressBar.style.width = `${Math.max(0, Math.min(100, percentage))}%`;
      } else {
        ui.progressContainer.classList.add('hidden');
        ui.progressBar.style.width = '0%';
      }
    }

    function buildUI() {
      const container = document.createElement('div');
      container.className = 'ft-ui';

      // 恢复位置
      const savedPos = getStored(STORAGE_KEYS.position, DEFAULTS.position);
      container.style.top = savedPos.top + '%';
      container.style.right = savedPos.right + 'px';

      // 主悬浮按钮
      const fab = document.createElement('div');
      fab.className = 'ft-fab';
      fab.title = 'Chrome 翻译';

      // 状态指示器圆圈
      const statusIndicator = document.createElement('div');
      statusIndicator.className = 'ft-status-indicator';
      fab.appendChild(statusIndicator);

      // 移除拖拽手柄,直接使用悬浮按钮拖拽

      // 设置按钮(悬停显示)
      const settingsBtn = document.createElement('div');
      settingsBtn.className = 'ft-settings-btn';
      settingsBtn.innerHTML = '⚙';
      settingsBtn.title = '设置';

          // 移除关闭按钮

      // 设置面板(独立弹窗)
      const settingsPanel = document.createElement('div');
      settingsPanel.className = 'ft-settings-panel';

          // 移除关闭选项面板

      const panel = document.createElement('div');
      panel.className = 'ft-panel';

      const row1 = document.createElement('div');
      row1.className = 'ft-row';
      const sourceSelect = document.createElement('select');
      sourceSelect.className = 'ft-select';
      const arrow = document.createElement('span'); arrow.textContent = '→';
      const targetSelect = document.createElement('select');
      targetSelect.className = 'ft-select';
      row1.append(sourceSelect, arrow, targetSelect);

      const row2 = document.createElement('div');
      row2.className = 'ft-row';
      const translateBtn = document.createElement('button');
      translateBtn.className = 'ft-btn';
      translateBtn.textContent = '翻译整页';
      const restoreBtn = document.createElement('button');
      restoreBtn.className = 'ft-btn ghost';
      restoreBtn.textContent = '还原';
      row2.append(translateBtn, restoreBtn);

      const row3 = document.createElement('label');
      row3.className = 'ft-row ft-check';
      const observeCheckbox = document.createElement('input');
      observeCheckbox.type = 'checkbox';
      const observeText = document.createElement('span');
      observeText.textContent = '自动翻译新增内容';
      row3.append(observeCheckbox, observeText);

      const statusText = document.createElement('div');
      statusText.className = 'ft-status';
      statusText.textContent = hasTranslator() ? '就绪' : '此浏览器不支持原生翻译(需 Chrome 138+)';

      const progressText = document.createElement('div');
      progressText.className = 'ft-status ft-progress';
      progressText.textContent = '';

            // 构建设置面板内容 - 参考沉浸式翻译设计
      settingsPanel.innerHTML = `
        <div class="ft-settings-header">
          <span>翻译设置</span>
          <div class="ft-settings-close">×</div>
        </div>
        <div class="ft-lang-section">
          <div class="ft-lang-row">
            <select class="ft-lang-select ft-source-select">${row1.querySelector('select:first-of-type').innerHTML}</select>
            <div class="ft-lang-arrow">→</div>
            <select class="ft-lang-select ft-target-select">${row1.querySelector('select:last-of-type').innerHTML}</select>
          </div>
        </div>
        <div class="ft-main-action">
          <button class="ft-translate-btn">翻译</button>
        </div>
        <div class="ft-switches">
          <div class="ft-switch-item">
            <span class="ft-switch-label">自动翻译新增内容</span>
            <div class="ft-switch ft-auto-switch" data-checked="false">
              <input type="checkbox" style="display:none;">
            </div>
          </div>
          <div class="ft-switch-item">
            <span class="ft-switch-label">划词翻译(全局)</span>
            <div class="ft-switch ft-word-switch" data-checked="false">
              <input type="checkbox" style="display:none;">
            </div>
          </div>
          <div class="ft-shortcut-tip">
            <span class="ft-shortcut-text">💡 选中文本后按 <kbd>F2</kbd> 快速翻译</span>
          </div>
        </div>
        <div class="ft-status-section">
          <div class="ft-status-text">${statusText.textContent}</div>
          <div class="ft-progress-container hidden">
            <div class="ft-progress-bar" style="width:0%"></div>
          </div>
        </div>
      `;

          // 移除关闭选项面板

          // 组装容器
    container.append(fab, settingsBtn, panel, settingsPanel);

          // 拖拽功能 - 长按拖拽
    let isDragging = false;
    let dragStarted = false;
    let startY = 0;
    let startTop = 0;
    let longPressTimer = null;
    let mouseMoved = false;

    fab.addEventListener('mousedown', (e) => {
      // 只响应左键
      if (e.button !== 0) return;

      startY = e.clientY;
      startTop = parseFloat(container.style.top) || 50;
      dragStarted = false;
      mouseMoved = false;

      // 长按500ms后才能拖拽
      longPressTimer = setTimeout(() => {
        if (!mouseMoved) { // 只有在没有鼠标移动的情况下才进入拖拽模式
          isDragging = true;
          fab.classList.add('ft-dragging'); // 添加拖拽状态类
          document.addEventListener('mousemove', onDrag);
          document.addEventListener('mouseup', onDragEnd);
        }
      }, 500);

      // 监听鼠标移动,如果在长按期间移动则取消拖拽
      const onMouseMove = (e) => {
        const deltaY = Math.abs(e.clientY - startY);
        if (deltaY > 5) { // 移动超过5px就算移动
          mouseMoved = true;
          clearTimeout(longPressTimer);
          document.removeEventListener('mousemove', onMouseMove);
        }
      };

      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', () => {
        clearTimeout(longPressTimer);
        document.removeEventListener('mousemove', onMouseMove);
      }, { once: true });

      e.preventDefault();
    });

      function onDrag(e) {
        if (!isDragging) return;
        const deltaY = e.clientY - startY;
        const newTop = Math.max(5, Math.min(95, startTop + (deltaY / window.innerHeight) * 100));
        container.style.top = newTop + '%';
      }

          function onDragEnd() {
      clearTimeout(longPressTimer);

      if (isDragging) {
        isDragging = false;
        dragStarted = true;
        fab.classList.remove('ft-dragging'); // 移除拖拽状态类

        const newPos = {
          top: parseFloat(container.style.top) || 50,
          right: parseFloat(container.style.right) || 12
        };
        setStored(STORAGE_KEYS.position, newPos);
        document.removeEventListener('mousemove', onDrag);
        document.removeEventListener('mouseup', onDragEnd);

        // 延迟重置dragStarted状态,防止立即触发点击
        setTimeout(() => {
          dragStarted = false;
        }, 100);
      }
    }

    // 更新悬浮按钮的提示词
    function updateFabTooltip() {
      if (isPageTranslated) {
        fab.title = '点击切换到原文';
      } else {
        const targetLangName = langLabel(targetLang);
        fab.title = `点击翻译为${targetLangName}`;
      }
    }

    // 将更新函数添加到返回对象中
    container.updateFabTooltip = updateFabTooltip;

    // 处理点击事件(区分拖拽和点击)
    fab.addEventListener('click', (e) => {
      // 如果刚完成拖拽,不触发点击
      if (dragStarted || isDragging) {
        return;
      }

      // 智能切换:翻译和还原
      if (isPageTranslated || inProgress) {
        restorePage();
      } else {
        translatePage(false);
      }
    });

    // 初始化提示词
    updateFabTooltip();

      // 悬停显示控制按钮
      let hoverTimer;
      container.addEventListener('mouseenter', () => {
        clearTimeout(hoverTimer);
        container.classList.add('ft-hover');
      });
      container.addEventListener('mouseleave', () => {
        hoverTimer = setTimeout(() => container.classList.remove('ft-hover'), 300);
      });

          // 设置按钮点击
    settingsBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      settingsPanel.classList.toggle('ft-show');
    });

          // 移除关闭按钮相关事件监听器

      // 设置面板关闭
      settingsPanel.querySelector('.ft-settings-close').addEventListener('click', () => {
        settingsPanel.classList.remove('ft-show');
      });

          // 点击外部关闭面板
    document.addEventListener('click', (e) => {
      if (!container.contains(e.target)) {
        settingsPanel.classList.remove('ft-show');
      }
    });

    // 自定义开关功能
    const autoSwitch = settingsPanel.querySelector('.ft-auto-switch');
    const wordSwitch = settingsPanel.querySelector('.ft-word-switch');

    // 初始化自动翻译开关
    const autoCheckbox = autoSwitch.querySelector('input[type="checkbox"]');
    autoSwitch.setAttribute('data-checked', autoObserve);
    autoCheckbox.checked = autoObserve;
    autoSwitch.classList.toggle('active', autoObserve);

    // 初始化划词翻译开关
    const wordCheckbox = wordSwitch.querySelector('input[type="checkbox"]');
    wordSwitch.setAttribute('data-checked', wordSelectionEnabled);
    wordCheckbox.checked = wordSelectionEnabled;
    wordSwitch.classList.toggle('active', wordSelectionEnabled);

    // 自动翻译开关事件
    autoSwitch.addEventListener('click', () => {
      const isChecked = autoSwitch.getAttribute('data-checked') === 'true';
      const newChecked = !isChecked;
      autoSwitch.setAttribute('data-checked', newChecked);
      autoCheckbox.checked = newChecked;
      autoSwitch.classList.toggle('active', newChecked);
      autoCheckbox.dispatchEvent(new Event('change'));
    });

    // 划词翻译开关事件
    wordSwitch.addEventListener('click', () => {
      const isChecked = wordSwitch.getAttribute('data-checked') === 'true';
      const newChecked = !isChecked;
      wordSwitch.setAttribute('data-checked', newChecked);
      wordCheckbox.checked = newChecked;
      wordSwitch.classList.toggle('active', newChecked);

      // 更新全局状态
      wordSelectionEnabled = newChecked;
      setStored(STORAGE_KEYS.wordSelection, newChecked);

      // 启用或禁用划词翻译监听器
      if (newChecked) {
        setupSelectionListeners();
      } else {
        removeSelectionListeners();
        hideSelectionBubble();
        hideTranslationPopup();
      }
    });

          return {
      container,
      sourceSelect: settingsPanel.querySelector('.ft-source-select'),
      targetSelect: settingsPanel.querySelector('.ft-target-select'),
      translateBtn: settingsPanel.querySelector('.ft-translate-btn'),
      restoreBtn: null, // 移除还原按钮,改为点击悬浮按钮切换
      observeCheckbox: autoCheckbox,
      statusText: settingsPanel.querySelector('.ft-status-text'),
      progressText: progressText,
      progressContainer: settingsPanel.querySelector('.ft-progress-container'),
      progressBar: settingsPanel.querySelector('.ft-progress-bar'),
      fab,
      statusIndicator,
      panel,
      settingsPanel,
    };
    }

    function injectStyles() {
      const css = `
  .ft-ui{ position:fixed !important; right:0 !important; transform:translateY(-50%) !important; z-index:2147483647 !important; font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell !important; color:#333 !important; pointer-events:auto !important; }
  .ft-ui *{ box-sizing:border-box; }

  /* 主悬浮按钮 - 紧贴侧边,保持黑色 */
  .ft-fab{
    width:36px; height:36px;
    border-radius:18px 0 0 18px;
    background:#333;
    border:1px solid #555;
    box-shadow:0 4px 12px rgba(0,0,0,.2);
    cursor:pointer; display:flex; align-items:center; justify-content:center;
    transition:all .3s ease; position:relative;
    transform:translateX(0); /* 始终紧贴右侧 */
    overflow:visible;
  }
  .ft-ui:hover .ft-fab{
    transform:translateX(-2px); /* 悬停时稍微左移 */
    background:#444;
  }

  /* 主图标 */
  .ft-fab::before{
    content:""; position:absolute; width:20px; height:20px;
    background:#fff;
    mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M12.87 15.07l-2.54-2.51.03-.03A17.52 17.52 0 0014.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z' fill='currentColor'/%3E%3C/svg%3E") no-repeat center/contain;
    transition:all .3s ease;
    z-index:1;
  }

  /* 状态指示器 */
  .ft-fab::after{
    content:'';
    position:absolute;
    top:-2px;
    right:-2px;
    width:10px;
    height:10px;
    border-radius:50%;
    border:2px solid #fff;
    opacity:0;
    transform:scale(0.5);
    transition:all 0.3s ease;
    z-index:2;
  }

  /* 翻译进行中状态 - 显示黄色小圆圈 */
  .ft-fab.ft-translating::after{
    background:#f59e0b;
    opacity:1;
    transform:scale(1);
  }

  /* 翻译状态 - 已翻译页面,图标保持黑色,只显示绿色小圆圈 */
  .ft-fab.ft-translated::after{
    background:#10b981;
    opacity:1;
    transform:scale(1);
  }

  /* 成功状态 - 临时动画,图标保持黑色 */
  .ft-fab.ft-success{
    transform:translateX(-2px) scale(1.05) !important;
  }
  .ft-fab.ft-success::after{
    background:#10b981;
    opacity:1;
    transform:scale(1.2);
    box-shadow:0 0 8px rgba(16,185,129,0.5);
  }

  /* 错误状态 - 图标保持黑色,只显示红色小圆圈 */
  .ft-fab.ft-error::after{
    background:#ef4444;
    opacity:1;
    transform:scale(1);
  }

  /* 悬浮按钮拖拽状态 */
.ft-fab:active{ cursor:ns-resize !important; }
.ft-fab.ft-dragging{
  cursor:ns-resize !important;
  transform:translateX(-4px) scale(1.05) !important;
  box-shadow:0 8px 24px rgba(0,0,0,.4) !important;
}

  /* 设置按钮 */
  .ft-settings-btn{
    position:absolute; right:8px; bottom:-30px;
    width:24px; height:24px;
    border-radius:12px;
    background:rgba(255,255,255,.9);
    border:1px solid rgba(0,0,0,.1);
    display:flex; align-items:center; justify-content:center;
    cursor:pointer; font-size:14px;
    opacity:0; transition:all .2s ease;
    box-shadow:0 2px 8px rgba(0,0,0,.1);
  }
  .ft-ui.ft-hover .ft-settings-btn{ opacity:1; }

  /* 设置面板 - 美观重设计 */
  .ft-settings-panel{
    position:absolute;
    top:-40px; right:40px;
    width:280px;
    background:#1e1e1e;
    border:1px solid #333;
    border-radius:16px;
    box-shadow:-12px 0 40px rgba(0,0,0,.5), 0 12px 40px rgba(0,0,0,.3);
    opacity:0; pointer-events:none;
    transform:translateX(24px) scale(0.9);
    transition:all .35s cubic-bezier(0.34,1.56,0.64,1);
    z-index:10;
    color:#fff;
    overflow:hidden;
    backdrop-filter:blur(20px);
  }
  .ft-settings-panel.ft-show{
    opacity:1; pointer-events:auto;
    transform:translateX(0) scale(1);
  }

  /* 标题栏 */
  .ft-settings-header{
    padding:18px 20px;
    background:linear-gradient(135deg, #2d2d2d 0%, #1f1f1f 100%);
    border-bottom:1px solid rgba(255,255,255,0.1);
    display:flex; justify-content:space-between; align-items:center;
    font-weight:600; font-size:15px;
    color:#f5f5f5;
  }
  .ft-settings-close{
    cursor:pointer;
    font-size:20px;
    color:#888;
    width:24px; height:24px;
    display:flex; align-items:center; justify-content:center;
    border-radius:12px;
    transition:all 0.2s ease;
  }
  .ft-settings-close:hover{
    background:rgba(255,255,255,0.1);
    color:#fff;
  }

  /* 语言选择区域 */
  .ft-lang-section{
    padding:16px;
    background:rgba(255,255,255,0.02);
  }
  .ft-lang-row{
    display:flex;
    align-items:center;
    gap:8px;
  }
  .ft-lang-select{
    flex:1;
    padding:8px 12px;
    background:#2a2a2a;
    border:1px solid #444;
    border-radius:8px;
    color:#fff;
    font-size:12px;
    cursor:pointer;
    transition:all 0.2s ease;
    outline:none;
    min-width:0;
    max-width:none;
  }
  .ft-lang-select:hover{
    border-color:#555;
    background:#333;
  }
  .ft-lang-select:focus{
    border-color:#e91e63;
    box-shadow:0 0 0 2px rgba(233,30,99,0.2);
  }
  .ft-lang-arrow{
    color:#888;
    font-size:14px;
    font-weight:bold;
    min-width:14px;
    text-align:center;
    flex-shrink:0;
  }

  /* 主要操作按钮 */
  .ft-main-action{
    padding:16px;
  }
  .ft-translate-btn{
    width:100%;
    padding:14px 24px;
    background:linear-gradient(135deg, #e91e63 0%, #c2185b 100%);
    border:none;
    border-radius:12px;
    color:#fff;
    font-weight:600;
    font-size:15px;
    cursor:pointer;
    transition:all 0.3s ease;
    box-shadow:0 6px 20px rgba(233,30,99,0.25);
    position:relative;
    overflow:hidden;
    display:flex;
    align-items:center;
    justify-content:center;
    gap:8px;
  }
  .ft-translate-btn::before{
    content:'';
    position:absolute;
    top:0; left:-100%;
    width:100%; height:100%;
    background:linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
    transition:left 0.6s ease;
  }
  .ft-translate-btn:hover{
    background:linear-gradient(135deg, #c2185b 0%, #ad1457 100%);
    transform:translateY(-2px);
    box-shadow:0 8px 25px rgba(233,30,99,0.35);
  }
  .ft-translate-btn:hover::before{
    left:100%;
  }
  .ft-translate-btn:active{
    transform:translateY(0);
  }
  .ft-translate-btn:disabled{
    background:#666 !important;
    cursor:not-allowed !important;
    transform:none !important;
    box-shadow:0 2px 8px rgba(0,0,0,0.2) !important;
  }

  /* 加载动画 */
  .ft-loading-spinner{
    width:16px;
    height:16px;
    border:2px solid rgba(255,255,255,0.3);
    border-top:2px solid #fff;
    border-radius:50%;
    animation:ft-spin 1s linear infinite;
  }
  @keyframes ft-spin{
    0%{ transform:rotate(0deg); }
    100%{ transform:rotate(360deg); }
  }

  /* 成功/错误状态 */
  .ft-translate-btn.success{
    background:linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
  }
  .ft-translate-btn.error{
    background:linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
  }

  /* 功能开关区域 */
  .ft-switches{
    padding:16px;
    border-top:1px solid rgba(255,255,255,0.05);
  }
  .ft-switch-item{
    display:flex;
    justify-content:space-between;
    align-items:center;
    padding:12px 0;
  }
  .ft-switch-label{
    font-size:14px;
    color:#ddd;
    font-weight:500;
  }
  .ft-switch{
    position:relative;
    width:48px;
    height:24px;
    background:#3a3a3a;
    border-radius:12px;
    cursor:pointer;
    transition:all 0.3s ease;
    border:1px solid #555;
  }
  .ft-switch.active{
    background:linear-gradient(135deg, #e91e63 0%, #c2185b 100%);
    border-color:#e91e63;
    box-shadow:0 0 12px rgba(233,30,99,0.3);
  }
  .ft-switch::after{
    content:'';
    position:absolute;
    top:2px;
    left:2px;
    width:18px;
    height:18px;
    background:#fff;
    border-radius:50%;
    transition:all 0.3s cubic-bezier(0.34,1.56,0.64,1);
    box-shadow:0 2px 6px rgba(0,0,0,0.3);
  }
  .ft-switch.active::after{
    transform:translateX(24px);
    box-shadow:0 2px 8px rgba(0,0,0,0.4);
  }

  /* 快捷键提示 */
  .ft-shortcut-tip{
    padding:8px 0 4px 0;
    margin-top:8px;
    border-top:1px solid rgba(255,255,255,0.05);
  }
  .ft-shortcut-text{
    font-size:11px;
    color:#888;
    display:flex;
    align-items:center;
    gap:4px;
  }
  .ft-shortcut-text kbd{
    background:rgba(255,255,255,0.1);
    border:1px solid rgba(255,255,255,0.2);
    border-radius:4px;
    padding:2px 6px;
    font-size:10px;
    font-family:monospace;
    color:#fff;
    box-shadow:0 1px 2px rgba(0,0,0,0.2);
  }

  /* 状态区域 */
  .ft-status-section{
    padding:12px 16px;
    background:rgba(0,0,0,0.2);
    border-top:1px solid rgba(255,255,255,0.05);
  }
  .ft-status-text{
    font-size:12px;
    color:#999;
    text-align:center;
    margin-bottom:8px;
  }

  /* 进度条 */
  .ft-progress-container{
    width:100%;
    height:4px;
    background:rgba(255,255,255,0.1);
    border-radius:2px;
    overflow:hidden;
    position:relative;
  }
  .ft-progress-bar{
    height:100%;
    background:linear-gradient(90deg, #e91e63 0%, #c2185b 100%);
    border-radius:2px;
    transition:width 0.3s ease;
    position:relative;
    overflow:hidden;
  }
  .ft-progress-bar::after{
    content:'';
    position:absolute;
    top:0;
    left:-100%;
    width:100%;
    height:100%;
    background:linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
    animation:ft-progress-shine 1.5s infinite;
  }
  @keyframes ft-progress-shine{
    0%{ left:-100%; }
    100%{ left:100%; }
  }

  /* 隐藏进度条 */
  .ft-progress-container.hidden{
    opacity:0;
    height:0;
    margin:0;
    transition:all 0.3s ease;
  }

  /* 移除关闭面板样式 */

  /* 旧面板隐藏 */
  .ft-panel{ display:none; }
  .ft-panel{ position:absolute; right:50px; top:0; min-width:320px; max-width:420px; padding:12px; border-radius:14px; background:rgba(255,255,255,.95); border:1px solid rgba(0,0,0,.12); box-shadow:0 12px 32px rgba(0,0,0,.18); backdrop-filter:saturate(1.2) blur(8px); opacity:0; pointer-events:none; transform:translateX(10px); transition:all .15s ease; }
  .ft-ui.open .ft-panel{ opacity:1; pointer-events:auto; transform:translateX(0); }
  .ft-row{ display:flex; align-items:center; gap:6px; margin-bottom:10px; }
  .ft-select{ flex:1; padding:5px 8px; border-radius:6px; border:1px solid #ddd; background:#fff; font-size:13px; }
  .ft-btn{ padding:6px 12px; border-radius:6px; border:1px solid #3b82f6; background:#3b82f6; color:#fff; cursor:pointer; font-size:13px; transition:background .2s ease; }
  .ft-btn:hover{ background:#2563eb; }
  .ft-btn.ghost{ background:#fff; color:#3b82f6; border-color:#3b82f6; }
  .ft-btn.ghost:hover{ background:#f1f5f9; }
  .ft-status{ font-size:11px; color:#666; margin-top:4px; }
  .ft-progress{ color:#444; font-size:11px; }
  .ft-check{ gap:6px; user-select:none; align-items:center; margin-bottom:8px; }
  .ft-check input{ margin:0; }
  .ft-check label{ font-size:13px; }
  .ft-busy .ft-btn{ opacity:.7; pointer-events:none; }
  .ft-pair{ display:inline; }
  .ft-pair .ft-original{ opacity:.6; margin-right:.35em; }
  .ft-pair:not(.show-original) .ft-original{ display:none; }
  @media (prefers-color-scheme: dark){
  .ft-fab{ background:#222; border-color:#444; color:#eee; }
  .ft-ui:hover .ft-fab{ background:#333; }
  .ft-settings-panel{ background:rgba(24,24,24,.95); color:#ddd; }
  .ft-settings-header{ border-color:rgba(255,255,255,.1); }
  .ft-panel{ background:rgba(24,24,24,.95); border-color:rgba(255,255,255,.12); color:#ddd; }
  .ft-select{ background:#1f2937; border-color:#374151; color:#e5e7eb; }
  .ft-btn{ background:#3b82f6; color:#fff; border-color:#3b82f6; }
  .ft-btn:hover{ background:#2563eb; }
  .ft-btn.ghost{ background:#1f2937; color:#3b82f6; border-color:#3b82f6; }
  .ft-btn.ghost:hover{ background:#374151; }
  .ft-status{ color:#aaa; }
  .ft-progress{ color:#ccc; }
}

/* fallback for browsers without mask support */
@supports not (mask: url()) {
  .ft-fab::before{ display:none; }
  .ft-fab::after{
    content:"A文"; position:absolute;
    font:bold 10px/1 sans-serif; color:#fff;
    left:50%; top:50%; transform:translate(-50%,-50%);
    transition:color .3s ease, text-shadow .3s ease;
    z-index:1;
  }

  /* 状态指示器 - fallback模式 */
  .ft-fab.ft-translated::after{
    color:#fff;
    text-shadow:0 0 4px rgba(255,255,255,0.5);
  }
  .ft-fab.ft-success::after{
    color:#fff !important;
    text-shadow:0 0 4px rgba(255,255,255,0.8);
  }
  .ft-fab.ft-error::after{
    color:#fff;
    text-shadow:0 0 4px rgba(255,255,255,0.5);
  }

  /* 为fallback模式添加状态指示点 */
  .ft-fab.ft-translated::before,
  .ft-fab.ft-error::before{
    content:'';
    display:block;
    position:absolute;
    top:-2px;
    right:-2px;
    width:8px;
    height:8px;
    border-radius:50%;
    border:1px solid #fff;
    z-index:2;
  }
  .ft-fab.ft-translated::before{
    background:#10b981;
  }
  .ft-fab.ft-error::before{
    background:#ef4444;
  }
}

/* 划词翻译样式 */
.ft-selection-bubble{
  position:absolute;
  width:26px;
  height:26px;
  background:rgba(156, 163, 175, 0.9);
  border:1px solid rgba(156, 163, 175, 0.3);
  border-radius:50%;
  box-shadow:0 3px 10px rgba(156, 163, 175, 0.3);
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:2147483646;
  transition:all 0.2s ease;
  font-size:12px;
  color:#fff;
  user-select:none;
  opacity:0;
  transform:scale(0.5);
  animation:ft-bubble-appear 0.2s ease forwards;
}
@keyframes ft-bubble-appear{
  to{
    opacity:1;
    transform:scale(1);
  }
}
.ft-selection-bubble:hover{
  transform:scale(1.1);
  box-shadow:0 5px 15px rgba(156, 163, 175, 0.4);
}
.ft-selection-bubble::before{
  content:"A";
  font-weight:bold;
}

.ft-translation-popup{
  position:absolute;
  min-width:200px;
  max-width:300px;
  background:#1e1e1e;
  border:1px solid #333;
  border-radius:12px;
  box-shadow:0 8px 32px rgba(0,0,0,.3);
  color:#fff;
  z-index:2147483646;
  overflow:hidden;
  opacity:0;
  transform:scale(0.9);
  transition:all 0.2s ease;
  font-size:13px;
}
.ft-translation-popup.show{
  opacity:1;
  transform:scale(1);
}
.ft-translation-popup-header{
  padding:8px 12px;
  background:rgba(255,255,255,0.05);
  border-bottom:1px solid rgba(255,255,255,0.1);
  font-size:11px;
  color:#999;
  display:flex;
  justify-content:space-between;
  align-items:center;
}
.ft-translation-popup-actions{
  display:flex;
  gap:4px;
}
.ft-translation-popup-refresh,
.ft-translation-popup-close{
  cursor:pointer;
  width:16px;
  height:16px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:8px;
  transition:background 0.2s ease;
  font-size:12px;
}
.ft-translation-popup-refresh:hover,
.ft-translation-popup-close:hover{
  background:rgba(255,255,255,0.1);
}
.ft-translation-popup-refresh{
  color:#3b82f6;
}
.ft-translation-popup-refresh:hover{
  background:rgba(59,130,246,0.2);
}
.ft-translation-popup-content{
  padding:12px;
  line-height:1.4;
}
.ft-translation-original{
  color:#999;
  font-size:12px;
  margin-bottom:8px;
  word-break:break-word;
}
.ft-translation-result{
  color:#fff;
  word-break:break-word;
}
.ft-translation-loading{
  display:flex;
  align-items:center;
  gap:8px;
  color:#999;
}
.ft-translation-loading .ft-loading-spinner{
  width:12px;
  height:12px;
  border:1px solid rgba(255,255,255,0.3);
  border-top:1px solid #fff;
}
.ft-translation-error{
  color:#ef4444;
  line-height:1.4;
}
`;
    try {
      if (typeof GM_addStyle === 'function') GM_addStyle(css);
      else {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
      }
    } catch {}
  }

    function toggleKeepOriginal(on) {
      const pairs = document.querySelectorAll('span.ft-pair');
      pairs.forEach((w) => {
        if (on) w.classList.add('show-original');
        else w.classList.remove('show-original');
      });
    }

    function applyTranslationToElement(element, original, translatedText) {
      // 保存原始HTML内容
      const originalHTML = element.innerHTML;
      
      // 标记为已翻译
      element.setAttribute('data-ft-original', '1');
      element.setAttribute('data-ft-original-html', originalHTML);
      element.setAttribute('data-ft-original-text', original);
      
      // 如果保留原文,创建切换结构
      if (keepOriginal) {
        const wrapper = document.createElement('span');
        wrapper.className = 'ft-pair show-original';
        wrapper.setAttribute('data-ft-original-text', original);
        
        const o = document.createElement('span');
        o.className = 'ft-original';
        o.innerHTML = originalHTML;
        
        const t = document.createElement('span');
        t.className = 'ft-translated';
        t.textContent = translatedText;
        
        wrapper.append(o, t);
        element.innerHTML = '';
        element.appendChild(wrapper);
      } else {
        // 直接替换文本内容
        element.textContent = translatedText;
      }
    }
    
    // 向后兼容的文本节点处理函数
    function applyTranslationToNode(node, original, leading, trailing, translatedPretty) {
      // Wrap the original text node with a pair container to support toggling
      const wrapper = document.createElement('span');
      wrapper.className = 'ft-pair';
      if (keepOriginal) wrapper.classList.add('show-original');
      wrapper.setAttribute('data-ft-original-text', original);
      wrapper.setAttribute('data-ft-leading', leading);
      wrapper.setAttribute('data-ft-trailing', trailing);

      const o = document.createElement('span');
      o.className = 'ft-original';
      o.textContent = original.trim();

      const t = document.createElement('span');
      t.className = 'ft-translated';
      t.textContent = leading + translatedPretty + trailing;

      wrapper.append(o, t);
      node.parentNode && node.parentNode.replaceChild(wrapper, node);
    }

    // --------------------------
    // 划词翻译功能
    // --------------------------
    function setupSelectionListeners() {
      document.addEventListener('mouseup', handleTextSelection);
      document.addEventListener('touchend', handleTextSelection);
    }

    function removeSelectionListeners() {
      document.removeEventListener('mouseup', handleTextSelection);
      document.removeEventListener('touchend', handleTextSelection);
    }

    function handleTextSelection(e) {
      // 延迟处理,确保选择已完成
      setTimeout(() => {
        const selection = window.getSelection();
        const selectedText = selection.toString().trim();

        if (selectedText && selectedText.length > 0 && selectedText.length < 500) {
          // 排除在翻译UI内的选择
          if (e.target.closest('.ft-ui, .ft-selection-bubble, .ft-translation-popup')) {
            return;
          }

          // 获取鼠标/触摸位置
          let mouseX, mouseY;
          if (e.type === 'touchend' && e.changedTouches && e.changedTouches.length > 0) {
            // 触摸事件
            mouseX = e.changedTouches[0].clientX;
            mouseY = e.changedTouches[0].clientY;
          } else {
            // 鼠标事件
            mouseX = e.clientX || e.pageX;
            mouseY = e.clientY || e.pageY;
          }

          showSelectionBubble(selectedText, { x: mouseX, y: mouseY });
        } else {
          hideSelectionBubble();
        }
      }, 100);
    }

    function showSelectionBubble(text, mousePos) {
      hideSelectionBubble();
      hideTranslationPopup();

      selectionBubble = document.createElement('div');
      selectionBubble.className = 'ft-selection-bubble';

      // 使用鼠标位置,添加少量偏移避免遮挡光标
      const offsetX = 10;
      const offsetY = 10;

      // 计算最终位置,确保不超出视窗边界
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;
      const bubbleSize = 26; // 悬浮框尺寸(26px)

      let left = mousePos.x + offsetX;
      let top = mousePos.y + offsetY;

      // 边界检查和调整
      if (left + bubbleSize > viewportWidth) {
        left = mousePos.x - bubbleSize - offsetX;
      }
      if (top + bubbleSize > viewportHeight) {
        top = mousePos.y - bubbleSize - offsetY;
      }

      // 确保不小于0
      left = Math.max(0, left);
      top = Math.max(0, top);

      selectionBubble.style.left = (left + window.scrollX) + 'px';
      selectionBubble.style.top = (top + window.scrollY) + 'px';

      selectionBubble.addEventListener('click', (e) => {
        e.stopPropagation();
        showTranslationPopup(text, selectionBubble);
      });

      document.body.appendChild(selectionBubble);

      // 自动隐藏机制
      setTimeout(() => {
        if (selectionBubble && !selectionBubble.matches(':hover')) {
          hideSelectionBubble();
        }
      }, 3000);
    }

    function hideSelectionBubble() {
      if (selectionBubble) {
        selectionBubble.remove();
        selectionBubble = null;
      }
    }

    function showTranslationPopupAtPosition(text, x, y) {
      hideTranslationPopup();

      translationPopup = document.createElement('div');
      translationPopup.className = 'ft-translation-popup';

      // 计算弹窗位置,确保不超出视窗边界
      const popupWidth = 300; // 最大宽度
      const popupHeight = 150; // 估计高度
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;

      let left = x + window.scrollX;
      let top = y + window.scrollY + 10; // 在选中文本下方10px

      // 边界检查
      if (left + popupWidth > viewportWidth + window.scrollX) {
        left = x + window.scrollX - popupWidth;
      }
      if (top + popupHeight > viewportHeight + window.scrollY) {
        top = y + window.scrollY - popupHeight - 10; // 在选中文本上方
      }

      // 确保不超出左边界和上边界
      left = Math.max(window.scrollX + 10, left);
      top = Math.max(window.scrollY + 10, top);

      translationPopup.style.left = left + 'px';
      translationPopup.style.top = top + 'px';

      translationPopup.innerHTML = `
        <div class="ft-translation-popup-header">
          <span>快捷翻译 (F2)</span>
          <div class="ft-translation-popup-actions">
            <div class="ft-translation-popup-refresh" title="重新翻译">↻</div>
            <div class="ft-translation-popup-close" title="关闭">×</div>
          </div>
        </div>
        <div class="ft-translation-popup-content">
          <div class="ft-translation-original">${text}</div>
          <div class="ft-translation-loading">
            <div class="ft-loading-spinner"></div>
            <span>翻译中...</span>
          </div>
        </div>
      `;

      document.body.appendChild(translationPopup);

      // 添加关闭和刷新事件
      translationPopup.querySelector('.ft-translation-popup-close').addEventListener('click', hideTranslationPopup);
      translationPopup.querySelector('.ft-translation-popup-refresh').addEventListener('click', () => {
        manualRefreshTranslation(text);
      });

      // 显示动画
      setTimeout(() => {
        translationPopup.classList.add('show');
      }, 10);

      // 开始翻译
      retryTranslation(text, 0);
    }

    function showTranslationPopup(text, anchorElement) {
      hideTranslationPopup();

      translationPopup = document.createElement('div');
      translationPopup.className = 'ft-translation-popup';

      const rect = anchorElement.getBoundingClientRect();
      translationPopup.style.left = (rect.left + window.scrollX) + 'px';
      translationPopup.style.top = (rect.bottom + window.scrollY + 5) + 'px';

      translationPopup.innerHTML = `
        <div class="ft-translation-popup-header">
          <span>划词翻译</span>
          <div class="ft-translation-popup-actions">
            <div class="ft-translation-popup-refresh" title="重新翻译">↻</div>
            <div class="ft-translation-popup-close" title="关闭">×</div>
          </div>
        </div>
        <div class="ft-translation-popup-content">
          <div class="ft-translation-original">${text}</div>
          <div class="ft-translation-loading">
            <div class="ft-loading-spinner"></div>
            <span>翻译中...</span>
          </div>
        </div>
      `;

      document.body.appendChild(translationPopup);

      // 添加关闭和刷新事件
      translationPopup.querySelector('.ft-translation-popup-close').addEventListener('click', hideTranslationPopup);
      translationPopup.querySelector('.ft-translation-popup-refresh').addEventListener('click', () => {
        manualRefreshTranslation(text); // 手动刷新,只执行一次翻译
      });

      // 显示动画
      setTimeout(() => {
        translationPopup.classList.add('show');
      }, 10);

      // 开始翻译
      retryTranslation(text, 0);
    }

    function hideTranslationPopup() {
      if (translationPopup) {
        translationPopup.remove();
        translationPopup = null;
      }
    }

    async function manualRefreshTranslation(text) {
      if (!translationPopup) return;

      // 显示加载状态
      const content = translationPopup.querySelector('.ft-translation-popup-content');
      content.innerHTML = `
        <div class="ft-translation-original">${text}</div>
        <div class="ft-translation-loading">
          <div class="ft-loading-spinner"></div>
          <span>重新初始化翻译器...</span>
        </div>
      `;

      try {
        // 强制重新初始化翻译器
        console.log('[ChromeTranslator] 手动刷新:强制重新初始化翻译器');

        // 销毁现有翻译器和语言检测器实例
        if (translatorInstance) {
          try {
            translatorInstance.destroy();
            console.log('[ChromeTranslator] 已销毁现有翻译器实例');
          } catch (destroyError) {
            console.warn('[ChromeTranslator] 销毁翻译器实例失败:', destroyError);
          }
          translatorInstance = null;
        }

        if (detectorInstance) {
          try {
            detectorInstance.destroy();
            console.log('[ChromeTranslator] 已销毁现有语言检测器实例');
          } catch (destroyError) {
            console.warn('[ChromeTranslator] 销毁语言检测器实例失败:', destroyError);
          }
          detectorInstance = null;
        }

        // 更新状态显示
        if (translationPopup) {
          const loadingSpan = content.querySelector('.ft-translation-loading span');
          if (loadingSpan) loadingSpan.textContent = '重新翻译中...';
        }

        // 重新初始化并翻译
        const result = await translateSelectedText(text);

        // 翻译成功,更新UI
        if (translationPopup && result) {
          content.innerHTML = `
            <div class="ft-translation-original">${text}</div>
            <div class="ft-translation-result">${result}</div>
          `;
        }
      } catch (error) {
        console.error('[ChromeTranslator] 手动刷新翻译失败:', error);

        // 手动刷新失败,显示错误但不自动重试
        if (translationPopup) {
          content.innerHTML = `
            <div class="ft-translation-original">${text}</div>
            <div class="ft-translation-error">
              <div style="color:#ef4444; margin-bottom:8px;">翻译失败: ${error.message}</div>
              <div style="font-size:11px; color:#999;">请点击刷新按钮重试</div>
            </div>
          `;
        }
      }
    }

    async function retryTranslation(text, retryCount = 0) {
      const maxRetries = 3;

      if (!translationPopup) return;

      // 显示加载状态
      const content = translationPopup.querySelector('.ft-translation-popup-content');
      const isInitError = retryCount > 0; // 重试时检查是否需要重新初始化

      content.innerHTML = `
        <div class="ft-translation-original">${text}</div>
        <div class="ft-translation-loading">
          <div class="ft-loading-spinner"></div>
          <span>${isInitError ? '重新初始化翻译器' : '翻译中'}${retryCount > 0 ? ` (重试 ${retryCount}/${maxRetries})` : ''}...</span>
        </div>
      `;

      try {
        // 如果是重试且上次失败可能是初始化问题,强制重新初始化
        if (retryCount > 0) {
          console.log(`[ChromeTranslator] 自动重试第${retryCount}次:重新初始化翻译器`);

          // 销毁现有实例
          if (translatorInstance) {
            try {
              translatorInstance.destroy();
              console.log('[ChromeTranslator] 已销毁现有翻译器实例');
            } catch (destroyError) {
              console.warn('[ChromeTranslator] 销毁翻译器实例失败:', destroyError);
            }
            translatorInstance = null;
          }

          if (detectorInstance) {
            try {
              detectorInstance.destroy();
              console.log('[ChromeTranslator] 已销毁现有语言检测器实例');
            } catch (destroyError) {
              console.warn('[ChromeTranslator] 销毁语言检测器实例失败:', destroyError);
            }
            detectorInstance = null;
          }

          // 更新状态显示
          if (translationPopup) {
            const loadingSpan = content.querySelector('.ft-translation-loading span');
            if (loadingSpan) {
              loadingSpan.textContent = `翻译中 (重试 ${retryCount}/${maxRetries})...`;
            }
          }
        }

        const result = await translateSelectedText(text);

        // 翻译成功,更新UI
        if (translationPopup && result) {
          content.innerHTML = `
            <div class="ft-translation-original">${text}</div>
            <div class="ft-translation-result">${result}</div>
          `;
        }
      } catch (error) {
        console.error(`[ChromeTranslator] 划词翻译失败 (第${retryCount + 1}次):`, error);

        if (retryCount < maxRetries) {
          // 还有重试次数,1秒后重试
          if (translationPopup) {
            content.innerHTML = `
              <div class="ft-translation-original">${text}</div>
              <div class="ft-translation-loading">
                <div class="ft-loading-spinner"></div>
                <span>翻译失败,${1}秒后重试 (${retryCount + 1}/${maxRetries})...</span>
              </div>
            `;
          }

          setTimeout(() => {
            retryTranslation(text, retryCount + 1);
          }, 1000);
        } else {
          // 重试次数用完,显示最终错误
          if (translationPopup) {
            content.innerHTML = `
              <div class="ft-translation-original">${text}</div>
              <div class="ft-translation-error">
                <div style="color:#ef4444; margin-bottom:8px;">翻译失败: ${error.message}</div>
                <div style="font-size:11px; color:#999;">已重试 ${maxRetries} 次,请检查网络或稍后再试</div>
              </div>
            `;
          }
        }
      }
    }

    async function translateSelectedText(text) {
      if (!hasTranslator()) throw new Error('翻译器不可用');

      // 确保翻译器可用
      const availabilityOk = await ensureAvailability();
      if (!availabilityOk) throw new Error('翻译器不可用');

      const realSource = await resolveRealSourceLanguage();
      const instanceOk = await ensureTranslator(realSource, targetLang);
      if (!instanceOk) throw new Error('翻译器初始化失败');

      // 执行翻译
      const translated = await translateStreaming(text.replace(/\n/g, '<br>'));
      const result = (translated || '').replace(/<br>/g, '\n').trim();

      if (!result) throw new Error('翻译结果为空');

      return result;
    }

    // 点击其他地方关闭划词翻译相关UI
    document.addEventListener('click', (e) => {
      if (!e.target.closest('.ft-selection-bubble, .ft-translation-popup')) {
        hideSelectionBubble();
        hideTranslationPopup();
      }
    });

    // F2快捷键快速翻译选中文本
    document.addEventListener('keydown', (e) => {
      if (e.key === 'F2' && wordSelectionEnabled) {
        e.preventDefault();

        const selection = window.getSelection();
        const selectedText = selection.toString().trim();

        if (selectedText && selectedText.length > 0 && selectedText.length < 500) {
          // 排除在翻译UI内的选择
          const range = selection.getRangeAt(0);
          const container = range.commonAncestorContainer;
          const element = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;

          if (element && element.closest('.ft-ui, .ft-selection-bubble, .ft-translation-popup')) {
            return;
          }

          // 获取选中文本的位置,用于显示翻译弹窗
          const rect = range.getBoundingClientRect();
          const centerX = rect.left + rect.width / 2;
          const centerY = rect.top + rect.height / 2;

          // 直接显示翻译弹窗,跳过悬浮按钮
          showTranslationPopupAtPosition(selectedText, centerX, centerY);
        }
      }
    });
  })();