Greasy Fork is available in English.

Touhou.AI | Manga Translator (Regular Edition)

(WIP) Translate texts in images on Pixiv, Twitter

Per 18-08-2022. Zie de nieuwste versie.

// ==UserScript==
// @name              Touhou.AI | Manga Translator (Regular Edition)
// @name:zh-CN        Touhou.AI | 图片翻译器 (常规版)
// @namespace         https://github.com/VoileLabs/imgtrans-userscript#regular
// @version           0.7.12
// @description       (WIP) Translate texts in images on Pixiv, Twitter
// @description:zh-CN (WIP) 一键翻译图片内文字,支持 Pixiv、Twitter
// @author            QiroNT
// @license           MIT
// @contributionURL   https://ko-fi.com/voilelabs
// @supportURL        https://github.com/VoileLabs/imgtrans-userscript/issues
// @source            https://github.com/VoileLabs/imgtrans-userscript
// @require https://cdn.jsdelivr.net/combine/npm/vue@3.2.37/dist/vue.runtime.global.prod.js,npm/@vueuse/shared@9.1.0/index.iife.min.js,npm/@vueuse/core@9.1.0/index.iife.min.js
// @resource wasmjs https://cdn.jsdelivr.net/gh/VoileLabs/imgtrans-userscript@40fc71c9589935551ca3f98842b7531f005589e5/wasm_bg.js
// @resource wasm https://cdn.jsdelivr.net/gh/VoileLabs/imgtrans-userscript@40fc71c9589935551ca3f98842b7531f005589e5/wasm_bg.wasm
// @include http*://www.pixiv.net/*
// @match http://www.pixiv.net/
// @include http*://twitter.com/*
// @match http://twitter.com/
// @connect pixiv.net
// @connect pximg.net
// @connect twitter.com
// @connect twimg.com
// @connect touhou.ai
// @connect *
// @grant GM.xmlHttpRequest
// @grant GM_xmlhttpRequest
// @grant GM.setValue
// @grant GM_setValue
// @grant GM.getValue
// @grant GM_getValue
// @grant GM.deleteValue
// @grant GM_deleteValue
// @grant GM.addValueChangeListener
// @grant GM_addValueChangeListener
// @grant GM.removeValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM.getResourceText
// @grant GM_getResourceText
// @grant GM.getResourceUrl
// @grant GM_getResourceURL
// @grant window.onurlchange
// @run-at document-end
// ==/UserScript==

/**
MIT License

Copyright (c) 2020-2022, VoileLabs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/* eslint-disable no-undef, @typescript-eslint/no-unused-vars */
const VERSION = '0.7.12'
const EDITION = 'regular'
let GMP
{
  // polyfill functions
  const GMPFunctionMap = {
    xmlHttpRequest: typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : undefined,
    setValue: typeof GM_setValue !== 'undefined' ? GM_setValue : undefined,
    getValue: typeof GM_getValue !== 'undefined' ? GM_getValue : undefined,
    deleteValue: typeof GM_deleteValue !== 'undefined' ? GM_deleteValue : undefined,
    addValueChangeListener: typeof GM_addValueChangeListener !== 'undefined' ? GM_addValueChangeListener : undefined,
    removeValueChangeListener: typeof GM_removeValueChangeListener !== 'undefined' ? GM_removeValueChangeListener : undefined,
    getResourceText: typeof GM_getResourceText !== 'undefined' ? GM_getResourceText : undefined,
    getResourceUrl: typeof GM_getResourceURL !== 'undefined' ? GM_getResourceURL : undefined,
  }
  const xmlHttpRequest = GM.xmlHttpRequest.bind(GM) || GMPFunctionMap.xmlHttpRequest
  GMP = new Proxy(GM, {
    get(target, prop) {
      if (prop === 'xmlHttpRequest') {
        return (context) => {
          return new Promise((resolve, reject) => {
            xmlHttpRequest({
              ...context,
              onload(event) {
                context.onload?.()
                resolve(event)
              },
              onerror(event) {
                context.onerror?.()
                reject(event)
              },
            })
          })
        }
      }
      if (prop in target) {
        const v = target[prop]
        return typeof v === 'function' ? v.bind(target) : v
      }
      if (prop in GMPFunctionMap && typeof GMPFunctionMap[prop] === 'function')
        return GMPFunctionMap[prop]

      console.error(
        `[Touhou.AI | Manga Translator] GM.${prop} isn't supported in your userscript engine and it's required by this script. This may lead to unexpected behavior.`,
      )
    },
  })
}

(function (vue, shared) {
    'use strict';

    const import_meta = {};
    let wasm;
    let cachedUint8Memory0 = new Uint8Array();
    function getUint8Memory0() {
      if (cachedUint8Memory0.byteLength === 0) {
        cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
      }
      return cachedUint8Memory0;
    }
    let WASM_VECTOR_LEN = 0;
    function passArray8ToWasm0(arg, malloc) {
      const ptr = malloc(arg.length * 1);
      getUint8Memory0().set(arg, ptr / 1);
      WASM_VECTOR_LEN = arg.length;
      return ptr;
    }
    function isLikeNone(x) {
      return x === void 0 || x === null;
    }
    let cachedInt32Memory0 = new Int32Array();
    function getInt32Memory0() {
      if (cachedInt32Memory0.byteLength === 0) {
        cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
      }
      return cachedInt32Memory0;
    }
    const cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true });
    cachedTextDecoder.decode();
    function getStringFromWasm0(ptr, len) {
      return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
    }
    function phash$1(rgba, width, height, hash_size) {
      try {
        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
        const ptr0 = passArray8ToWasm0(rgba, wasm.__wbindgen_export_0);
        const len0 = WASM_VECTOR_LEN;
        wasm.phash(retptr, ptr0, len0, width, height, !isLikeNone(hash_size), isLikeNone(hash_size) ? 0 : hash_size);
        var r0 = getInt32Memory0()[retptr / 4 + 0];
        var r1 = getInt32Memory0()[retptr / 4 + 1];
        return getStringFromWasm0(r0, r1);
      } finally {
        wasm.__wbindgen_add_to_stack_pointer(16);
        wasm.__wbindgen_export_1(r0, r1);
      }
    }
    function getArrayU8FromWasm0(ptr, len) {
      return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
    }
    function resize$1(rgba, width, height, new_width, new_height) {
      try {
        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
        const ptr0 = passArray8ToWasm0(rgba, wasm.__wbindgen_export_0);
        const len0 = WASM_VECTOR_LEN;
        wasm.resize(retptr, ptr0, len0, width, height, new_width, new_height);
        var r0 = getInt32Memory0()[retptr / 4 + 0];
        var r1 = getInt32Memory0()[retptr / 4 + 1];
        var v1 = getArrayU8FromWasm0(r0, r1).slice();
        wasm.__wbindgen_export_1(r0, r1 * 1);
        return v1;
      } finally {
        wasm.__wbindgen_add_to_stack_pointer(16);
      }
    }
    async function load(module, imports) {
      if (typeof Response === "function" && module instanceof Response) {
        if (typeof WebAssembly.instantiateStreaming === "function") {
          try {
            return await WebAssembly.instantiateStreaming(module, imports);
          } catch (e) {
            if (module.headers.get("Content-Type") != "application/wasm") {
              console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
            } else {
              throw e;
            }
          }
        }
        const bytes = await module.arrayBuffer();
        return await WebAssembly.instantiate(bytes, imports);
      } else {
        const instance = await WebAssembly.instantiate(module, imports);
        if (instance instanceof WebAssembly.Instance) {
          return { instance, module };
        } else {
          return instance;
        }
      }
    }
    function getImports() {
      const imports = {};
      imports.wbg = {};
      return imports;
    }
    function finalizeInit(instance, module) {
      wasm = instance.exports;
      init.__wbindgen_wasm_module = module;
      cachedInt32Memory0 = new Int32Array();
      cachedUint8Memory0 = new Uint8Array();
      return wasm;
    }
    async function init(input) {
      if (typeof input === "undefined") {
        input = new URL("wasm_bg.wasm", import_meta.url);
      }
      const imports = getImports();
      if (typeof input === "string" || typeof Request === "function" && input instanceof Request || typeof URL === "function" && input instanceof URL) {
        input = fetch(input);
      }
      const { instance, module } = await load(await input, imports);
      return finalizeInit(instance, module);
    }
    function setWasm(w) {
      wasm = w;
    }

    const css = `
@keyframes imgtrans-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
`;
    const cssEl = document.createElement("style");
    cssEl.innerHTML = css;
    function checkCSS() {
      if (!document.head.contains(cssEl))
        document.head.appendChild(cssEl);
    }

    function useGMStorage(key, initialValue) {
      const data = vue.ref(initialValue);
      let listener;
      if (GMP.addValueChangeListener) {
        (async () => {
          listener = await GMP.addValueChangeListener(key, (name, oldValue, newValue, remote) => {
            if (name === key && (remote === void 0 || remote === true))
              read(newValue);
          });
        })();
      }
      const {
        pause: pauseWatch,
        resume: resumeWatch,
        stop: stopWatch
      } = shared.pausableWatch(
        data,
        async (newValue, oldValue) => {
          if (newValue == null) {
            await GMP.deleteValue(key);
            pauseWatch();
            data.value = initialValue;
            resumeWatch();
          } else {
            await GMP.setValue(key, newValue);
          }
        },
        { flush: "sync" }
      );
      async function read(newValue) {
        pauseWatch();
        const rawValue = newValue != null ? newValue : await GMP.getValue(key);
        if (rawValue == null)
          data.value = initialValue;
        else
          data.value = rawValue;
        resumeWatch();
      }
      data.ready = read().then(() => {
        data.isReady = true;
      });
      vue.onScopeDispose(() => {
        stopWatch();
        if (GMP.removeValueChangeListener && listener)
          GMP.removeValueChangeListener(listener);
      });
      return data;
    }
    const detectionResolution = useGMStorage("detectionResolution", "M");
    const textDetector = useGMStorage("textDetector", "auto");
    const translatorService = useGMStorage("translator", "youdao");
    const renderTextOrientation = useGMStorage("renderTextOrientation", "auto");
    const targetLang = useGMStorage("targetLang");
    const scriptLang = useGMStorage("scriptLanguage");
    const storageReady = Promise.all([
      detectionResolution.ready,
      textDetector.ready,
      translatorService.ready,
      renderTextOrientation.ready,
      targetLang.ready,
      scriptLang.ready
    ]);

    var data$1 = { common:{ source:{ "download-image":"正在拉取原图",
          "download-image-progress":"正在拉取原图({progress})",
          "download-image-error":"拉取原图出错" },
        client:{ submit:"正在提交翻译",
          "submit-progress":"正在提交翻译({progress})",
          "submit-error":"提交翻译出错",
          "download-image":"正在下载图片",
          "download-image-progress":"正在下载图片({progress})",
          "download-image-error":"下载图片出错",
          hash:"正在哈希图片",
          resize:"正在缩放图片" },
        status:{ "default":"未知状态",
          pending:"正在等待",
          pending_pos:"正在等待,列队还有 {pos} 张图片",
          detection:"正在检测文本",
          ocr:"正在识别文本",
          mask_generation:"正在生成文本掩码",
          inpainting:"正在修补图片",
          translating:"正在翻译文本",
          render:"正在渲染",
          error:"翻译出错",
          "error-lang":"不支持的语言" },
        control:{ translate:"翻译",
          batch:"翻译全部",
          reset:"还原" },
        batch:{ progress:"翻译中({count}/{total})",
          finish:"翻译完成",
          error:"翻译完成(有失败)" } },
      settings:{ title:"Touhou.AI | 图片翻译器设置",
        "inline-options-title":"设置当前翻译",
        "detection-resolution":"文本扫描清晰度",
        "text-detector":"文本扫描器",
        "text-detector-options":{ auto:"默认" },
        translator:"翻译服务",
        "render-text-orientation":"渲染字体方向",
        "render-text-orientation-options":{ auto:"跟随原文本",
          horizontal:"仅限水平" },
        "target-language":"翻译语言",
        "target-language-options":{ auto:"跟随网页语言" },
        "script-language":"用户脚本语言",
        "script-language-options":{ auto:"跟随网页语言" },
        reset:"重置所有设置",
        "detection-resolution-desc":"设置检测图片文本所用的清晰度,更高的清晰度会使文本检测时间更长但精准度更高。",
        "text-detector-desc":"设置使用的文本扫描器。",
        "translator-desc":"设置翻译图片所用的翻译服务。",
        "render-text-orientation-desc":"设置嵌字的文本方向。",
        "target-language-desc":"设置图片翻译后的语言。",
        "script-language-desc":"设置此用户脚本的语言。" },
      sponsor:{ text:"制作不易,请考虑赞助我们!" } };
    data$1.common;
    data$1.settings;
    data$1.sponsor;

    var data = { common:{ source:{ "download-image":"Downloading original image",
          "download-image-progress":"Downloading original image ({progress})",
          "download-image-error":"Error during original image download" },
        client:{ submit:"Submitting translation",
          "submit-progress":"Submitting translation ({progress})",
          "submit-error":"Error during translation submission",
          "download-image":"Downloading translated image",
          "download-image-progress":"Downloading translated image ({progress})",
          "download-image-error":"Error during translated image download",
          hash:"Hashing image",
          resize:"Resizing image" },
        status:{ "default":"Unknown status",
          pending:"Pending",
          pending_pos:"Pending, {pos} in queue",
          detection:"Detecting text",
          ocr:"Scanning text",
          mask_generation:"Generating mask",
          inpainting:"Inpainting",
          translating:"Translating",
          render:"Rendering",
          error:"Error during translation",
          "error-lang":"Unsupported language" },
        control:{ translate:"Translate",
          batch:"Translate all",
          reset:"Reset" },
        batch:{ progress:"Translating ({count}/{total} finished)",
          finish:"Translation finished",
          error:"Translation finished with errors" } },
      settings:{ "detection-resolution":"Text detection resolution",
        "render-text-orientation":"Render text orientation",
        "render-text-orientation-options":{ auto:"Follow original orientation",
          horizontal:"Horizontal only" },
        reset:"Reset Settings",
        "target-language":"Translate target language",
        "target-language-options":{ auto:"Follow website language" },
        "text-detector":"Text detector",
        "text-detector-options":{ auto:"Default" },
        title:"Touhou.AI | Manga Translator Settings",
        translator:"Translator",
        "script-language":"Userscript language",
        "script-language-options":{ auto:"Follow website language" },
        "inline-options-title":"Current Settings",
        "detection-resolution-desc":"The resolution used to scan texts on an image, higher value will result in a longer processing time with better accuracy.",
        "script-language-desc":"Language of this userscript.",
        "render-text-orientation-desc":"Overwrite the orientation of texts rendered in the translated image.",
        "target-language-desc":"The language that images are translated to.",
        "text-detector-desc":"The detector used to scan texts in an image.",
        "translator-desc":"The translate service used to translate texts." },
      sponsor:{ text:"If you find this script helpful, please consider supporting us!" } };
    data.common;
    data.settings;
    data.sponsor;

    const messages = {
      "zh-CN": data$1,
      "en-US": data
    };
    function tryMatchLang(lang2) {
      if (lang2.startsWith("zh"))
        return "zh-CN";
      if (lang2.startsWith("en"))
        return "en-US";
      return "zh-CN";
    }
    const realLang = vue.ref(navigator.language);
    const lang = vue.computed(() => scriptLang.value || tryMatchLang(realLang.value));
    const t = (key, props = {}) => {
      return { key, props };
    };
    const tt = ({ key, props }) => {
      const msg = key.split(".").reduce((obj, k) => obj[k], messages[lang.value]) || key.split(".").reduce((obj, k) => obj[k], messages["zh-CN"]);
      if (!msg)
        return key;
      return msg.replace(/\{([^}]+)\}/g, (_, k) => {
        var _a;
        return (_a = String(props[k])) != null ? _a : "";
      });
    };
    const untt = (state) => {
      if (typeof state === "string")
        return state;
      else
        return tt(state);
    };
    let langEL;
    let langObserver;
    const changeLangEl = (el) => {
      if (langEL === el)
        return;
      if (langObserver)
        langObserver.disconnect();
      const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          if (mutation.type === "attributes" && mutation.attributeName === "lang") {
            const target = mutation.target;
            if (target.lang)
              realLang.value = target.lang;
            break;
          }
        }
      });
      observer.observe(el, { attributes: true });
      langObserver = observer;
      langEL = el;
      realLang.value = el.lang;
    };
    function BCP47ToISO639(code) {
      try {
        const lo = new Intl.Locale(code);
        switch (lo.language) {
          case "zh": {
            switch (lo.script) {
              case "Hans":
                return "CHS";
              case "Hant":
                return "CHT";
            }
            switch (lo.region) {
              case "CN":
                return "CHS";
              case "HK":
              case "TW":
                return "CHT";
            }
            return "CHS";
          }
          case "ja":
            return "JPN";
          case "en":
            return "ENG";
          case "ko":
            return "KOR";
          case "vi":
            return "VIE";
          case "cs":
            return "CSY";
          case "nl":
            return "NLD";
          case "fr":
            return "FRA";
          case "de":
            return "DEU";
          case "hu":
            return "HUN";
          case "it":
            return "ITA";
          case "pl":
            return "PLK";
          case "pt":
            return "PTB";
          case "ro":
            return "ROM";
          case "ru":
            return "RUS";
          case "es":
            return "ESP";
          case "tr":
            return "TRK";
        }
        return "CHS";
      } catch (e) {
        return "CHS";
      }
    }

    function createScopedInstance(cb) {
      const scope = vue.effectScope();
      const i = scope.run(cb);
      scope.run(() => {
        vue.onScopeDispose(() => {
          var _a;
          (_a = i.stop) == null ? void 0 : _a.call(i);
        });
      });
      return { scope, i };
    }
    async function initWasm() {
      const uri = await GMP.getResourceUrl("wasm");
      try {
        if (/^data:.+;base64,/.test(uri)) {
          const data = window.atob(uri.split(";base64,", 2)[1]);
          const buffer = new Uint8Array(data.length);
          for (let i = 0; i < data.length; i++)
            buffer[i] = data.charCodeAt(i);
          await init(buffer);
        } else {
          await init(uri);
        }
      } catch (e) {
        const ImgTransWasmJsModule = new Function(`${await GM.getResourceText("wasmjs")}
return ImgTransWasmJsModule`)();
        setWasm(ImgTransWasmJsModule);
      }
    }
    Promise.allSettled = Promise.allSettled || ((promises) => Promise.all(
      promises.map(
        (p) => p.then((value) => ({
          status: "fulfilled",
          value
        })).catch((reason) => ({
          status: "rejected",
          reason
        }))
      )
    ));
    let currentURL;
    let translator$2;
    let settingsInjector$2;
    async function start(translators, settingsInjectors) {
      await storageReady;
      const results = await Promise.allSettled([initWasm()]);
      for (const result of results) {
        if (result.status === "rejected")
          console.warn(result.reason);
      }
      function onUpdate() {
        var _a, _b, _c, _d;
        if (currentURL !== location.href) {
          currentURL = location.href;
          checkCSS();
          changeLangEl(document.documentElement);
          if (!((_b = translator$2 == null ? void 0 : (_a = translator$2.i).canKeep) == null ? void 0 : _b.call(_a, currentURL))) {
            translator$2 == null ? void 0 : translator$2.scope.stop();
            translator$2 = void 0;
            const url = new URL(location.href);
            const matched = translators.find((t) => t.match(url));
            if (matched)
              translator$2 = createScopedInstance(matched.mount);
          }
          if (!((_d = settingsInjector$2 == null ? void 0 : (_c = settingsInjector$2.i).canKeep) == null ? void 0 : _d.call(_c, currentURL))) {
            settingsInjector$2 == null ? void 0 : settingsInjector$2.scope.stop();
            settingsInjector$2 = void 0;
            const url = new URL(location.href);
            const matched = settingsInjectors.find((t) => t.match(url));
            if (matched)
              settingsInjector$2 = createScopedInstance(matched.mount);
          }
        }
      }
      if (window.onurlchange === null) {
        window.addEventListener("urlchange", onUpdate);
      } else {
        const installObserver = new MutationObserver(shared.useThrottleFn(onUpdate, 200, true, false));
        installObserver.observe(document.body, { childList: true, subtree: true });
      }
      onUpdate();
    }

    function formatSize(bytes) {
      const k = 1024;
      const sizes = ["B", "KB", "MB", "GB", "TB"];
      if (bytes === 0)
        return "0B";
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return `${(bytes / k ** i).toFixed(2)}${sizes[i]}`;
    }
    function formatProgress(loaded, total) {
      return `${formatSize(loaded)}/${formatSize(total)}`;
    }
    function phash(image) {
      return phash$1(new Uint8Array(image.data), image.width, image.height);
    }
    function resize(image, width, height) {
      const data = resize$1(new Uint8Array(image.data), image.width, image.height, width, height);
      return new ImageData(new Uint8ClampedArray(data), width, height);
    }

    async function resizeToSubmit(blob, suffix) {
      const imageData = await blobToImageData(blob);
      if (imageData.width <= 4e3 && imageData.height <= 4e3)
        return { blob, suffix };
      const scale = Math.min(4e3 / imageData.width, 4e3 / imageData.height);
      const width = Math.floor(imageData.width * scale);
      const height = Math.floor(imageData.height * scale);
      const newImageData = resize(imageData, width, height);
      const newBlob = await imageDataToBlob(newImageData);
      console.log(`resized from ${imageData.width}x${imageData.height}(${formatSize(blob.size)},${suffix}) to ${width}x${height}(${formatSize(newBlob.size)},png)`);
      return {
        blob: newBlob,
        suffix: "png"
      };
    }
    async function submitTranslate(blob, suffix, listeners = {}, optionsOverwrite) {
      var _a, _b, _c, _d;
      const { onProgress } = listeners;
      const formData = new FormData();
      formData.append("file", blob, `image.${suffix}`);
      formData.append("size", (_a = optionsOverwrite == null ? void 0 : optionsOverwrite.detectionResolution) != null ? _a : detectionResolution.value);
      formData.append("translator", (_b = optionsOverwrite == null ? void 0 : optionsOverwrite.translator) != null ? _b : translatorService.value);
      formData.append("tgt_lang", targetLang.value || BCP47ToISO639(realLang.value));
      formData.append("dir", (_c = optionsOverwrite == null ? void 0 : optionsOverwrite.renderTextOrientation) != null ? _c : renderTextOrientation.value);
      formData.append("detector", (_d = optionsOverwrite == null ? void 0 : optionsOverwrite.textDetector) != null ? _d : textDetector.value);
      const result = await GMP.xmlHttpRequest({
        method: "POST",
        url: "https://touhou.ai/imgtrans/submit",
        data: formData,
        upload: {
          onprogress: onProgress ? (e) => {
            if (e.lengthComputable) {
              const p = formatProgress(e.loaded, e.total);
              onProgress(p);
            }
          } : void 0
        }
      });
      console.log(result.responseText);
      const json = JSON.parse(result.responseText);
      const id = json.task_id;
      return id;
    }
    async function getTranslateStatus(id) {
      const result = await GMP.xmlHttpRequest({
        method: "GET",
        url: `https://touhou.ai/imgtrans/task-state?taskid=${id}`
      });
      const data = JSON.parse(result.responseText);
      return {
        state: data.state,
        waiting: data.waiting || 0
      };
    }
    function getStatusText(status) {
      switch (status.state) {
        case "pending":
          if (status.waiting > 0)
            return t("common.status.pending_pos", { pos: status.waiting });
          else
            return t("common.status.pending");
        case "detection":
          return t("common.status.detection");
        case "ocr":
          return t("common.status.ocr");
        case "mask_generation":
          return t("common.status.mask_generation");
        case "inpainting":
          return t("common.status.inpainting");
        case "translating":
          return t("common.status.translating");
        case "render":
          return t("common.status.render");
        case "error":
          return t("common.status.error");
        case "error-lang":
          return t("common.status.error-lang");
        default:
          return t("common.status.default");
      }
    }
    async function pullTransStatusUntilFinish(id, cb) {
      for (; ; ) {
        const timer = new Promise((resolve) => setTimeout(resolve, 500));
        const status = await getTranslateStatus(id);
        if (status.state === "finished")
          return;
        else if (status.state === "error")
          throw t("common.status.error");
        else if (status.state === "error-lang")
          throw t("common.status.error-lang");
        else
          cb(status);
        await timer;
      }
    }
    async function downloadResultBlob(id, listeners = {}) {
      const { onProgress } = listeners;
      const res = await GMP.xmlHttpRequest({
        method: "GET",
        responseType: "blob",
        url: `https://touhou.ai/imgtrans/result/${id}/final.png`,
        onprogress: onProgress ? (e) => {
          if (e.lengthComputable) {
            const p = formatProgress(e.loaded, e.total);
            onProgress(p);
          }
        } : void 0
      });
      return res.response;
    }
    function blobToImageData(blob) {
      const blobUrl = URL.createObjectURL(blob);
      return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = (err) => reject(err);
        img.src = blobUrl;
      }).then((img) => {
        URL.revokeObjectURL(blobUrl);
        const w = img.width;
        const h = img.height;
        const canvas = document.createElement("canvas");
        canvas.width = w;
        canvas.height = h;
        const ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);
        return ctx.getImageData(0, 0, w, h);
      });
    }
    async function imageDataToBlob(imageData) {
      const canvas = document.createElement("canvas");
      canvas.width = imageData.width;
      canvas.height = imageData.height;
      const ctx = canvas.getContext("2d");
      ctx.putImageData(imageData, 0, 0);
      const blob = await new Promise((resolve, reject) => {
        canvas.toBlob((blob2) => {
          if (blob2)
            resolve(blob2);
          else
            reject(new Error("Canvas toBlob failed"));
        }, "image/png");
      });
      return blob;
    }

    const detectResOptionsMap = {
      S: "1024px",
      M: "1536px",
      L: "2048px",
      X: "2560px"
    };
    const detectResOptions = Object.keys(detectResOptionsMap);
    const renderTextDirOptionsMap = {
      auto: t("settings.render-text-orientation-options.auto"),
      horizontal: t("settings.render-text-orientation-options.horizontal")
    };
    const renderTextDirOptions = Object.keys(renderTextDirOptionsMap);
    const textDetectorOptionsMap = {
      auto: t("settings.text-detector-options.auto"),
      ctd: "CTD"
    };
    const textDetectorOptions = Object.keys(textDetectorOptionsMap);
    const translatorOptionsMap = {
      youdao: "Youdao",
      baidu: "Baidu",
      google: "Google",
      deepl: "DeepL",
      papago: "Papago"
    };
    const translatorOptions = Object.keys(translatorOptionsMap);
    function renderSettings(options) {
      const { itemOrientation = "vertical", textStyle = {} } = options != null ? options : {};
      return vue.h("div", {
        style: {
          display: "flex",
          flexDirection: "column",
          gap: "8px"
        }
      }, [
        vue.h("div", {}, [
          `"${EDITION}" edition, v${VERSION}`
        ]),
        vue.h("div", {
          style: {
            display: "flex",
            flexDirection: "row",
            flexWrap: "nowrap",
            gap: "4px"
          }
        }, [
          tt(t("sponsor.text")),
          vue.h("a", {
            href: "https://ko-fi.com/voilelabs",
            target: "_blank",
            rel: "noopener noreferrer",
            style: {
              color: "#2563EB",
              textDecoration: "underline"
            }
          }, "ko-fi"),
          vue.h("a", {
            href: "https://patreon.com/voilelabs",
            target: "_blank",
            rel: "noopener noreferrer",
            style: {
              color: "#2563EB",
              textDecoration: "underline"
            }
          }, "Patreon"),
          vue.h("a", {
            href: "https://afdian.net/@voilelabs",
            target: "_blank",
            rel: "noopener noreferrer",
            style: {
              color: "#2563EB",
              textDecoration: "underline"
            }
          }, "爱发电")
        ]),
        ...[
          [
            t("settings.detection-resolution"),
            detectionResolution,
            detectResOptionsMap,
            t("settings.detection-resolution-desc")
          ],
          [
            t("settings.text-detector"),
            textDetector,
            textDetectorOptionsMap,
            t("settings.text-detector-desc")
          ],
          [
            t("settings.translator"),
            translatorService,
            translatorOptionsMap,
            t("settings.translator-desc")
          ],
          [
            t("settings.render-text-orientation"),
            renderTextOrientation,
            renderTextDirOptionsMap,
            t("settings.render-text-orientation-desc")
          ],
          [
            t("settings.target-language"),
            targetLang,
            {
              "": tt(t("settings.target-language-options.auto")),
              "CHS": "简体中文",
              "CHT": "繁體中文",
              "JPN": "日本語",
              "ENG": "English",
              "KOR": "한국어",
              "VIN": "Tiếng Việt",
              "CSY": "čeština",
              "NLD": "Nederlands",
              "FRA": "français",
              "DEU": "Deutsch",
              "HUN": "magyar nyelv",
              "ITA": "italiano",
              "PLK": "polski",
              "PTB": "português",
              "ROM": "limba română",
              "RUS": "русский язык",
              "ESP": "español",
              "TRK": "Türk dili"
            },
            t("settings.target-language-desc")
          ],
          [
            t("settings.script-language"),
            scriptLang,
            {
              "": tt(t("settings.script-language-options.auto")),
              "zh-CN": "简体中文",
              "en-US": "English"
            },
            t("settings.script-language-desc")
          ]
        ].map(
          ([title, opt, optMap, desc]) => vue.h("div", {
            style: {
              ...itemOrientation === "horizontal" ? {
                display: "flex",
                flexDirection: "row",
                alignItems: "center"
              } : {}
            }
          }, [
            vue.h("div", {
              style: {
                ...textStyle
              }
            }, tt(title)),
            vue.h("div", {}, [
              vue.h("select", {
                value: opt.value,
                onChange(e) {
                  opt.value = e.target.value;
                }
              }, Object.entries(optMap).map(([key, value]) => vue.h("option", {
                value: key
              }, untt(value)))),
              desc ? vue.h("div", {
                style: {
                  fontSize: "13px"
                }
              }, tt(desc)) : void 0
            ])
          ])
        ),
        vue.h("div", [
          vue.h("button", {
            onClick: vue.withModifiers(() => {
              detectionResolution.value = null;
              textDetector.value = null;
              translatorService.value = null;
              renderTextOrientation.value = null;
              targetLang.value = null;
              scriptLang.value = null;
            }, ["stop", "prevent"])
          }, tt(t("settings.reset")))
        ])
      ]);
    }

    const _hoisted_1$3 = {
      preserveAspectRatio: "xMidYMid meet",
      viewBox: "0 0 32 32",
      width: "1.2em",
      height: "1.2em"
    };
    const _hoisted_2$3 = /*#__PURE__*/vue.createElementVNode("path", {
      fill: "currentColor",
      d: "M27.85 29H30l-6-15h-2.35l-6 15h2.15l1.6-4h6.85zm-7.65-6l2.62-6.56L25.45 23zM18 7V5h-7V2H9v3H2v2h10.74a14.71 14.71 0 0 1-3.19 6.18A13.5 13.5 0 0 1 7.26 9h-2.1a16.47 16.47 0 0 0 3 5.58A16.84 16.84 0 0 1 3 18l.75 1.86A18.47 18.47 0 0 0 9.53 16a16.92 16.92 0 0 0 5.76 3.84L16 18a14.48 14.48 0 0 1-5.12-3.37A17.64 17.64 0 0 0 14.8 7z"
    }, null, -1 /* HOISTED */);
    const _hoisted_3$3 = [
      _hoisted_2$3
    ];

    function render$3(_ctx, _cache) {
      return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1$3, _hoisted_3$3))
    }

    var IconCarbonTranslate = { name: 'carbon-translate', render: render$3 };
    /* vite-plugin-components disabled */

    const _hoisted_1$2 = {
      preserveAspectRatio: "xMidYMid meet",
      viewBox: "0 0 32 32",
      width: "1.2em",
      height: "1.2em"
    };
    const _hoisted_2$2 = /*#__PURE__*/vue.createElementVNode("path", {
      fill: "currentColor",
      d: "M18 28A12 12 0 1 0 6 16v6.2l-3.6-3.6L1 20l6 6l6-6l-1.4-1.4L8 22.2V16a10 10 0 1 1 10 10Z"
    }, null, -1 /* HOISTED */);
    const _hoisted_3$2 = [
      _hoisted_2$2
    ];

    function render$2(_ctx, _cache) {
      return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1$2, _hoisted_3$2))
    }

    var IconCarbonReset = { name: 'carbon-reset', render: render$2 };
    /* vite-plugin-components disabled */

    const _hoisted_1$1 = {
      preserveAspectRatio: "xMidYMid meet",
      viewBox: "0 0 32 32",
      width: "1.2em",
      height: "1.2em"
    };
    const _hoisted_2$1 = /*#__PURE__*/vue.createElementVNode("path", {
      fill: "currentColor",
      d: "M10 16L20 6l1.4 1.4l-8.6 8.6l8.6 8.6L20 26z"
    }, null, -1 /* HOISTED */);
    const _hoisted_3$1 = [
      _hoisted_2$1
    ];

    function render$1(_ctx, _cache) {
      return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1$1, _hoisted_3$1))
    }

    var IconCarbonChevronLeft = { name: 'carbon-chevron-left', render: render$1 };
    /* vite-plugin-components disabled */

    const _hoisted_1 = {
      preserveAspectRatio: "xMidYMid meet",
      viewBox: "0 0 32 32",
      width: "1.2em",
      height: "1.2em"
    };
    const _hoisted_2 = /*#__PURE__*/vue.createElementVNode("path", {
      fill: "currentColor",
      d: "M22 16L12 26l-1.4-1.4l8.6-8.6l-8.6-8.6L12 6z"
    }, null, -1 /* HOISTED */);
    const _hoisted_3 = [
      _hoisted_2
    ];

    function render(_ctx, _cache) {
      return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1, _hoisted_3))
    }

    var IconCarbonChevronRight = { name: 'carbon-chevron-right', render };
    /* vite-plugin-components disabled */

    function mount$3() {
      const images = /* @__PURE__ */ new Set();
      const instances = /* @__PURE__ */ new Map();
      const translatedMap = /* @__PURE__ */ new Map();
      const translateEnabledMap = /* @__PURE__ */ new Map();
      function findImageNodes(node) {
        return Array.from(node.querySelectorAll("img")).filter(
          (node2) => {
            var _a, _b;
            return node2.hasAttribute("srcset") || node2.hasAttribute("data-trans") || ((_a = node2.parentElement) == null ? void 0 : _a.classList.contains("sc-1pkrz0g-1")) || ((_b = node2.parentElement) == null ? void 0 : _b.classList.contains("gtm-expand-full-size-illust"));
          }
        );
      }
      function rescanImages() {
        const imageNodes = findImageNodes(document.body);
        const removedImages = new Set(images);
        for (const node of imageNodes) {
          removedImages.delete(node);
          if (images.has(node))
            continue;
          try {
            instances.set(node, mountToNode(node));
            images.add(node);
          } catch (e) {
          }
        }
        for (const node of removedImages) {
          if (!instances.has(node))
            continue;
          const instance = instances.get(node);
          instance.stop();
          instances.delete(node);
          images.delete(node);
        }
      }
      function mountToNode(imageNode) {
        const src = imageNode.getAttribute("src");
        const srcset = imageNode.getAttribute("srcset");
        const parent = imageNode.parentElement;
        if (!parent)
          throw new Error("no parent");
        const originalSrc = parent.getAttribute("href") || src;
        const originalSrcSuffix = originalSrc.split(".").pop();
        let originalImage;
        let translatedImage = translatedMap.get(originalSrc);
        const translateMounted = vue.ref(false);
        let buttonDisabled = false;
        const buttonProcessing = vue.ref(false);
        const buttonTranslated = vue.ref(false);
        const buttonText = vue.ref();
        const buttonHint = vue.ref("");
        parent.style.position = "relative";
        const container = document.createElement("div");
        parent.appendChild(container);
        const buttonApp = vue.createApp(vue.defineComponent(() => {
          const content = vue.computed(() => (buttonText.value ? tt(buttonText.value) : "") + buttonHint.value);
          const advancedMenuOpen = vue.ref(false);
          const advDetectRes = vue.ref(detectionResolution.value);
          const advDetectResIndex = vue.computed(() => detectResOptions.indexOf(advDetectRes.value));
          const advRenderTextDir = vue.ref(renderTextOrientation.value);
          const advRenderTextDirIndex = vue.computed(() => renderTextDirOptions.indexOf(advRenderTextDir.value));
          const advTextDetector = vue.ref(textDetector.value);
          const advTextDetectorIndex = vue.computed(() => textDetectorOptions.indexOf(advTextDetector.value));
          const advTranslator = vue.ref(translatorService.value);
          const advTranslatorIndex = vue.computed(() => translatorOptions.indexOf(advTranslator.value));
          return () => vue.h(
            "div",
            {
              style: {
                position: "absolute",
                zIndex: "1",
                bottom: "4px",
                left: "8px"
              }
            },
            [
              vue.h(
                "div",
                {
                  style: {
                    position: "relative"
                  }
                },
                [
                  vue.h(
                    "div",
                    {
                      style: {
                        fontSize: "16px",
                        lineHeight: "16px",
                        padding: "2px",
                        paddingLeft: translateMounted.value ? "2px" : "24px",
                        border: "2px solid #D1D5DB",
                        borderRadius: "6px",
                        background: "#fff",
                        cursor: "default"
                      }
                    },
                    content.value ? content.value : !translateMounted.value ? advancedMenuOpen.value ? [
                      vue.h(
                        "div",
                        {
                          style: {
                            display: "flex",
                            flexDirection: "row",
                            justifyContent: "space-between",
                            alignItems: "center",
                            paddingBottom: "2px"
                          },
                          onClick: vue.withModifiers(() => {
                            advancedMenuOpen.value = false;
                          }, ["stop", "prevent"])
                        },
                        [
                          vue.h("div", {}, tt(t("settings.inline-options-title"))),
                          vue.h(IconCarbonChevronLeft, {
                            style: {
                              verticalAlign: "middle",
                              cursor: "pointer"
                            }
                          })
                        ]
                      ),
                      vue.h("div", {
                        style: {
                          display: "flex",
                          flexDirection: "column",
                          gap: "4px"
                        }
                      }, [
                        [
                          [
                            t("settings.detection-resolution"),
                            advDetectRes,
                            advDetectResIndex,
                            detectResOptions,
                            detectResOptionsMap
                          ],
                          [
                            t("settings.text-detector"),
                            advTextDetector,
                            advTextDetectorIndex,
                            textDetectorOptions,
                            textDetectorOptionsMap
                          ],
                          [
                            t("settings.translator"),
                            advTranslator,
                            advTranslatorIndex,
                            translatorOptions,
                            translatorOptionsMap
                          ],
                          [
                            t("settings.render-text-orientation"),
                            advRenderTextDir,
                            advRenderTextDirIndex,
                            renderTextDirOptions,
                            Object.fromEntries(
                              Object.entries(renderTextDirOptionsMap).map(([k, v]) => [k, tt(v)])
                            )
                          ]
                        ].map(
                          ([title, opt, optIndex, opts, optMap]) => vue.h("div", {}, [
                            vue.h(
                              "div",
                              {
                                style: {
                                  fontSize: "12px"
                                }
                              },
                              tt(title)
                            ),
                            vue.h(
                              "div",
                              {
                                style: {
                                  display: "flex",
                                  flexDirection: "row",
                                  justifyContent: "space-between",
                                  alignItems: "center",
                                  userSelect: "none"
                                }
                              },
                              [
                                vue.h(optIndex.value <= 0 ? "div" : IconCarbonChevronLeft, {
                                  style: {
                                    width: "1.2em",
                                    cursor: "pointer"
                                  },
                                  onClick: vue.withModifiers(() => {
                                    if (optIndex.value <= 0)
                                      return;
                                    opt.value = opts[optIndex.value - 1];
                                  }, ["stop", "prevent"])
                                }),
                                vue.h("div", {}, untt(optMap[opt.value])),
                                vue.h(optIndex.value >= opts.length - 1 ? "div" : IconCarbonChevronRight, {
                                  style: {
                                    width: "1.2em",
                                    cursor: "pointer"
                                  },
                                  onClick: vue.withModifiers(() => {
                                    if (optIndex.value >= opts.length - 1)
                                      return;
                                    opt.value = opts[optIndex.value + 1];
                                  }, ["stop", "prevent"])
                                })
                              ]
                            )
                          ])
                        ),
                        vue.h("div", {
                          style: {
                            padding: "2px 0px 1px 0px",
                            border: "1px solid #A1A1AA",
                            borderRadius: "4px",
                            textAlign: "center",
                            cursor: "pointer"
                          },
                          onClick: vue.withModifiers(() => {
                            if (buttonDisabled)
                              return;
                            if (translateMounted.value)
                              return;
                            enable({
                              detectionResolution: advDetectRes.value,
                              renderTextOrientation: advRenderTextDir.value,
                              textDetector: advTextDetector.value,
                              translator: advTranslator.value
                            });
                            advancedMenuOpen.value = false;
                          }, ["stop", "prevent"])
                        }, tt(t("common.control.translate")))
                      ])
                    ] : vue.h(IconCarbonChevronRight, {
                      style: {
                        cursor: "pointer"
                      },
                      onClick: vue.withModifiers(() => {
                        advancedMenuOpen.value = true;
                      }, ["stop", "prevent"])
                    }) : vue.h("div", {
                      style: {
                        width: "1px",
                        height: "16px"
                      }
                    })
                  ),
                  vue.h("div", {
                    style: {
                      position: "absolute",
                      left: "-5px",
                      top: "-2px",
                      background: "#fff",
                      borderRadius: "24px"
                    }
                  }, [
                    vue.h(buttonTranslated.value ? IconCarbonReset : IconCarbonTranslate, {
                      style: {
                        fontSize: "18px",
                        lineHeight: "18px",
                        width: "18px",
                        height: "18px",
                        padding: "6px",
                        cursor: "pointer"
                      },
                      onClick: vue.withModifiers(() => {
                        if (advancedMenuOpen.value)
                          return;
                        toggle();
                      }, ["stop", "prevent"]),
                      onContextmenu: vue.withModifiers(() => {
                        if (translateMounted.value)
                          advancedMenuOpen.value = false;
                        else
                          advancedMenuOpen.value = !advancedMenuOpen.value;
                      }, ["stop", "prevent"])
                    }),
                    vue.h("div", {
                      style: {
                        position: "absolute",
                        top: "0",
                        left: "0",
                        right: "0",
                        bottom: "0",
                        border: "2px solid #D1D5DB",
                        ...buttonProcessing.value ? {
                          borderTop: "2px solid #7DD3FC",
                          animation: "imgtrans-spin 1s linear infinite"
                        } : {},
                        borderRadius: "24px",
                        pointerEvents: "none"
                      }
                    })
                  ])
                ]
              )
            ]
          );
        }));
        buttonApp.mount(container);
        async function getTranslatedImage(optionsOverwrite) {
          if (!optionsOverwrite && translatedImage)
            return translatedImage;
          buttonDisabled = true;
          const text = buttonText.value;
          buttonHint.value = "";
          buttonProcessing.value = true;
          buttonText.value = t("common.source.download-image");
          if (!originalImage) {
            const result = await GMP.xmlHttpRequest({
              method: "GET",
              responseType: "blob",
              url: originalSrc,
              headers: { referer: "https://www.pixiv.net/" },
              overrideMimeType: "text/plain; charset=x-user-defined",
              onprogress(e) {
                if (e.lengthComputable) {
                  buttonText.value = t("common.source.download-image-progress", {
                    progress: formatProgress(e.loaded, e.total)
                  });
                }
              }
            }).catch((e) => {
              buttonText.value = t("common.source.download-image-error");
              throw e;
            });
            originalImage = result.response;
          }
          buttonText.value = t("common.client.resize");
          await vue.nextTick();
          const { blob: resizedImage, suffix: resizedSuffix } = await resizeToSubmit(originalImage, originalSrcSuffix);
          buttonText.value = t("common.client.hash");
          await vue.nextTick();
          try {
            const imageData = await blobToImageData(resizedImage);
            console.log("phash", phash(imageData));
          } catch (e) {
            console.warn(e);
          }
          buttonText.value = t("common.client.submit");
          const id = await submitTranslate(
            resizedImage,
            resizedSuffix,
            {
              onProgress(progress) {
                buttonText.value = t("common.client.submit-progress", { progress });
              }
            },
            optionsOverwrite
          ).catch((e) => {
            buttonText.value = t("common.client.submit-error");
            throw e;
          });
          buttonText.value = t("common.status.pending");
          await pullTransStatusUntilFinish(id, (status) => {
            buttonText.value = getStatusText(status);
          }).catch((e) => {
            buttonText.value = e;
            throw e;
          });
          buttonText.value = t("common.client.download-image");
          const image = await downloadResultBlob(id, {
            onProgress(progress) {
              buttonText.value = t("common.client.download-image-progress", { progress });
            }
          }).catch((e) => {
            buttonText.value = t("common.client.download-image-error");
            throw e;
          });
          const imageUri = URL.createObjectURL(image);
          translatedImage = imageUri;
          translatedMap.set(originalSrc, translatedImage);
          buttonText.value = text;
          buttonProcessing.value = false;
          buttonDisabled = false;
          return imageUri;
        }
        async function enable(optionsOverwrite) {
          try {
            const translated = await getTranslatedImage(optionsOverwrite);
            imageNode.setAttribute("data-trans", src);
            imageNode.setAttribute("src", translated);
            imageNode.removeAttribute("srcset");
            translateMounted.value = true;
            buttonTranslated.value = true;
          } catch (e) {
            buttonDisabled = false;
            translateMounted.value = false;
            throw e;
          }
        }
        function disable() {
          imageNode.setAttribute("src", src);
          if (srcset)
            imageNode.setAttribute("srcset", srcset);
          imageNode.removeAttribute("data-trans");
          translateMounted.value = false;
          buttonTranslated.value = false;
        }
        function toggle() {
          if (buttonDisabled)
            return;
          if (!translateMounted.value) {
            translateEnabledMap.set(originalSrc, true);
            enable();
          } else {
            translateEnabledMap.delete(originalSrc);
            disable();
          }
        }
        if (translateEnabledMap.get(originalSrc))
          enable();
        return {
          imageNode,
          stop: () => {
            buttonApp.unmount();
            parent.removeChild(container);
            if (translateMounted.value)
              disable();
          },
          async enable() {
            translateEnabledMap.set(originalSrc, true);
            return await enable();
          },
          disable() {
            translateEnabledMap.delete(originalSrc);
            return disable();
          },
          isEnabled() {
            return translateMounted.value;
          }
        };
      }
      const transAllComp = vue.defineComponent(() => {
        const started = vue.ref(false);
        const total = vue.ref(0);
        const finished = vue.ref(0);
        const erred = vue.ref(false);
        return () => vue.h("div", {
          "data-transall": "true",
          "style": {
            display: "inline-block",
            marginRight: "13px",
            padding: "0",
            color: "inherit",
            height: "32px",
            lineHeight: "32px",
            cursor: "pointer",
            fontWeight: "700"
          },
          "onClick": vue.withModifiers(() => {
            if (started.value)
              return;
            started.value = true;
            total.value = instances.size;
            const inc = () => {
              finished.value++;
            };
            const err = () => {
              erred.value = true;
              finished.value++;
            };
            for (const instance of instances.values()) {
              if (instance.isEnabled())
                inc();
              else
                instance.enable().then(inc).catch(err);
            }
          }, ["stop", "prevent"])
        }, [
          tt(
            started.value ? finished.value === total.value ? erred.value ? t("common.batch.error") : t("common.batch.finish") : t("common.batch.progress", {
              count: finished.value,
              total: total.value
            }) : t("common.control.batch")
          )
        ]);
      });
      let removeTransAll;
      function refreshTransAll() {
        if (document.querySelector(".sc-emr523-2"))
          return;
        const section = document.querySelector(".sc-181ts2x-0");
        if (section) {
          if (section.querySelector("[data-transall]"))
            return;
          const container = document.createElement("div");
          section.appendChild(container);
          const transAllApp = vue.createApp(transAllComp);
          transAllApp.mount(container);
          removeTransAll = () => {
            transAllApp.unmount();
            section.removeChild(container);
          };
        }
      }
      let removeMangaViewerTransAll;
      function refreshManagaViewerTransAll() {
        var _a, _b;
        const mangaViewer = (_b = (_a = document.querySelector(".gtm-manga-viewer-change-direction")) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.parentElement;
        if (mangaViewer) {
          if (removeMangaViewerTransAll)
            return;
          const container = document.createElement("div");
          mangaViewer.prepend(container);
          const transAllApp = vue.createApp(transAllComp);
          transAllApp.mount(container);
          removeMangaViewerTransAll = () => {
            transAllApp.unmount();
            mangaViewer.removeChild(container);
          };
        } else {
          if (removeMangaViewerTransAll) {
            removeMangaViewerTransAll();
            removeMangaViewerTransAll = void 0;
          }
        }
      }
      const imageObserver = new MutationObserver(
        shared.useThrottleFn(() => {
          rescanImages();
          refreshTransAll();
          refreshManagaViewerTransAll();
        }, 200, true, false)
      );
      imageObserver.observe(document.body, { childList: true, subtree: true });
      rescanImages();
      refreshTransAll();
      return {
        stop() {
          imageObserver.disconnect();
          instances.forEach((instance) => instance.stop());
          removeTransAll == null ? void 0 : removeTransAll();
          removeMangaViewerTransAll == null ? void 0 : removeMangaViewerTransAll();
        }
      };
    }
    const translator$1 = {
      match(url) {
        return url.hostname.endsWith("pixiv.net") && url.pathname.match(/\/artworks\//);
      },
      mount: mount$3
    };

    function mount$2() {
      const wrapper = document.getElementById("wrapper");
      if (!wrapper)
        return {};
      const adFooter = wrapper.querySelector(".ad-footer");
      if (!adFooter)
        return {};
      const settingsContainer = document.createElement("div");
      const settingsApp = vue.createApp(
        () => vue.h(
          "div",
          {
            style: {
              paddingTop: "10px",
              paddingLeft: "20px",
              paddingRight: "20px",
              paddingBottom: "15px",
              marginBottom: "10px",
              background: "#fff",
              border: "1px solid #d6dee5"
            }
          },
          [
            vue.h("h2", {
              style: {
                fontSize: "18px",
                fontWeight: "bold"
              }
            }, tt(t("settings.title"))),
            vue.h(
              "div",
              {
                style: {
                  width: "665px",
                  margin: "10px auto"
                }
              },
              renderSettings({
                itemOrientation: "horizontal",
                textStyle: {
                  width: "185px",
                  fontWeight: "bold"
                }
              })
            )
          ]
        )
      );
      settingsApp.mount(settingsContainer);
      wrapper.insertBefore(settingsContainer, adFooter);
      return {
        stop() {
          settingsApp.unmount();
          settingsContainer.remove();
        }
      };
    }
    const settingsInjector$1 = {
      match(url) {
        return url.hostname.endsWith("pixiv.net") && url.pathname.match(/\/setting_user\.php/);
      },
      mount: mount$2
    };

    function mount$1() {
      var _a;
      const statusId = (_a = location.pathname.match(/\/status\/(\d+)/)) == null ? void 0 : _a[1];
      const translatedMap = vue.reactive({});
      const translateStatusMap = vue.shallowReactive({});
      const translateEnabledMap = vue.reactive({});
      const originalImageMap = {};
      let initObserver;
      let layersObserver;
      let layers = document.getElementById("layers");
      let dialog;
      const createDialogInstance = () => {
        const active = vue.ref(0);
        const updateRef = vue.ref();
        const buttonParent = dialog.querySelector('[aria-labelledby="modal-header"][role="dialog"]').firstChild.firstChild;
        const images = vue.computed(() => {
          updateRef.value;
          return [].slice.call(buttonParent.firstChild.querySelectorAll("img"));
        });
        const currentImg = vue.computed(() => {
          const img = images.value[active.value];
          if (!img)
            return void 0;
          return img.getAttribute("data-transurl") || img.src;
        });
        const stopImageWatch = vue.watch(
          [images, translateEnabledMap, translatedMap],
          () => {
            for (const img of images.value) {
              const div = img.previousSibling;
              if (img.hasAttribute("data-transurl")) {
                const transurl = img.getAttribute("data-transurl");
                if (!translateEnabledMap[transurl]) {
                  if (div)
                    div.style.backgroundImage = `url("${transurl}")`;
                  img.src = transurl;
                  img.removeAttribute("data-transurl");
                }
              } else if (translateEnabledMap[img.src] && translatedMap[img.src]) {
                const ori = img.src;
                img.setAttribute("data-transurl", ori);
                img.src = translatedMap[ori];
                if (div)
                  div.style.backgroundImage = `url("${translatedMap[ori]}")`;
              }
            }
          }
        );
        const getTranslatedImage = async (url, optionsOverwrite) => {
          if (!optionsOverwrite && translatedMap[url])
            return translatedMap[url];
          translateStatusMap[url] = vue.computed(() => tt(t("common.source.download-image")));
          if (!originalImageMap[url]) {
            const result = await GMP.xmlHttpRequest({
              method: "GET",
              responseType: "blob",
              url,
              headers: { referer: "https://twitter.com/" },
              overrideMimeType: "text/plain; charset=x-user-defined",
              onprogress(e) {
                if (e.lengthComputable) {
                  translateStatusMap[url] = vue.computed(() => tt(t("common.source.download-image-progress", {
                    progress: formatProgress(e.loaded, e.total)
                  })));
                }
              }
            }).catch((e) => {
              translateStatusMap[url] = vue.computed(() => tt(t("common.source.download-image-error")));
              throw e;
            });
            originalImageMap[url] = result.response;
          }
          const originalImage = originalImageMap[url];
          const originalSrcSuffix = new URL(url).searchParams.get("format") || url.split(".")[1] || "jpg";
          translateStatusMap[url] = vue.computed(() => tt(t("common.client.resize")));
          await vue.nextTick();
          const { blob: resizedImage, suffix: resizedSuffix } = await resizeToSubmit(originalImage, originalSrcSuffix);
          translateStatusMap[url] = vue.computed(() => tt(t("common.client.hash")));
          await vue.nextTick();
          try {
            const imageData = await blobToImageData(resizedImage);
            console.log("phash", phash(imageData));
          } catch (e) {
            console.warn(e);
          }
          translateStatusMap[url] = vue.computed(() => tt(t("common.client.submit")));
          const id = await submitTranslate(
            resizedImage,
            resizedSuffix,
            {
              onProgress(progress) {
                translateStatusMap[url] = vue.computed(() => tt(t("common.client.submit-progress", { progress })));
              }
            },
            optionsOverwrite
          ).catch((e) => {
            translateStatusMap[url] = vue.computed(() => tt(t("common.client.submit-error")));
            throw e;
          });
          translateStatusMap[url] = vue.computed(() => tt(t("common.status.pending")));
          await pullTransStatusUntilFinish(id, (status) => {
            translateStatusMap[url] = vue.computed(() => tt(getStatusText(status)));
          }).catch((e) => {
            translateStatusMap[url] = vue.computed(() => tt(e));
            throw e;
          });
          translateStatusMap[url] = vue.computed(() => tt(t("common.client.download-image")));
          const image = await downloadResultBlob(id, {
            onProgress(progress) {
            }
          }).catch((e) => {
            translateStatusMap[url] = vue.computed(() => tt(t("common.client.download-image-error")));
            throw e;
          });
          const imageUri = URL.createObjectURL(image);
          translatedMap[url] = imageUri;
          translateStatusMap[url] = vue.computed(() => void 0);
          return imageUri;
        };
        const enable = async (url, optionsOverwrite) => {
          await getTranslatedImage(url, optionsOverwrite);
          translateEnabledMap[url] = true;
        };
        const disable = (url) => {
          translateEnabledMap[url] = false;
        };
        const buttonProcessing = vue.computed(() => {
          var _a2;
          return currentImg.value && !!((_a2 = translateStatusMap[currentImg.value]) == null ? void 0 : _a2.value);
        });
        const buttonTranslated = vue.computed(() => currentImg.value && !!translateEnabledMap[currentImg.value]);
        const buttonContent = vue.computed(() => {
          var _a2;
          return currentImg.value ? (_a2 = translateStatusMap[currentImg.value]) == null ? void 0 : _a2.value : "";
        });
        const advancedMenuOpen = vue.ref(false);
        const referenceEl = buttonParent.children[2];
        const container = referenceEl.cloneNode(true);
        container.style.top = "48px";
        const stopDisplayWatch = vue.watchEffect(() => {
          container.style.display = currentImg.value ? "flex" : "none";
          container.style.alignItems = advancedMenuOpen.value ? "start" : "center";
        });
        container.style.flexDirection = "row";
        container.style.flexWrap = "nowrap";
        const child = container.firstChild;
        const referenceChild = referenceEl.firstChild;
        const backgroundColor = vue.ref(referenceChild.style.backgroundColor);
        buttonParent.appendChild(container);
        const submitTranslateTest = () => {
          var _a2;
          if (!currentImg.value)
            return false;
          if ((_a2 = translateStatusMap[currentImg.value]) == null ? void 0 : _a2.value)
            return false;
          return true;
        };
        container.onclick = vue.withModifiers(() => {
          if (advancedMenuOpen.value)
            return;
          if (!submitTranslateTest())
            return;
          if (translateEnabledMap[currentImg.value])
            disable(currentImg.value);
          else
            enable(currentImg.value);
        }, ["stop", "prevent"]);
        container.oncontextmenu = vue.withModifiers(() => {
          if (currentImg.value && translateEnabledMap[currentImg.value])
            advancedMenuOpen.value = false;
          else
            advancedMenuOpen.value = !advancedMenuOpen.value;
        }, ["stop", "prevent"]);
        const spinnerContainer = container.firstChild;
        const processingSpinner = document.createElement("div");
        processingSpinner.style.position = "absolute";
        processingSpinner.style.top = "0";
        processingSpinner.style.left = "0";
        processingSpinner.style.bottom = "0";
        processingSpinner.style.right = "0";
        processingSpinner.style.borderTop = "1px solid #A1A1AA";
        processingSpinner.style.animation = "imgtrans-spin 1s linear infinite";
        processingSpinner.style.borderRadius = "9999px";
        const stopSpinnerWatch = vue.watch(
          buttonProcessing,
          (p, o) => {
            if (p === o)
              return;
            if (p && !spinnerContainer.contains(processingSpinner))
              spinnerContainer.appendChild(processingSpinner);
            else if (spinnerContainer.contains(processingSpinner))
              spinnerContainer.removeChild(processingSpinner);
          },
          { immediate: true }
        );
        const svg = container.querySelector("svg");
        const svgParent = svg.parentElement;
        const buttonIconContainer = document.createElement("div");
        svgParent.insertBefore(buttonIconContainer, svg);
        svgParent.removeChild(svg);
        const buttonIconApp = vue.createApp(
          () => vue.h(buttonTranslated.value ? IconCarbonReset : IconCarbonTranslate, {
            style: {
              width: "20px",
              height: "20px",
              marginTop: "4px"
            }
          })
        );
        buttonIconApp.mount(buttonIconContainer);
        const buttonStatusContainer = document.createElement("div");
        container.insertBefore(buttonStatusContainer, container.firstChild);
        const buttonStatusApp = vue.createApp(vue.defineComponent(() => {
          const borderRadius = vue.computed(() => advancedMenuOpen.value || buttonContent.value ? "4px" : "16px");
          const advDetectRes = vue.ref(detectionResolution.value);
          const advDetectResIndex = vue.computed(() => detectResOptions.indexOf(advDetectRes.value));
          const advRenderTextDir = vue.ref(renderTextOrientation.value);
          const advRenderTextDirIndex = vue.computed(() => renderTextDirOptions.indexOf(advRenderTextDir.value));
          const advTextDetector = vue.ref(textDetector.value);
          const advTextDetectorIndex = vue.computed(() => textDetectorOptions.indexOf(advTextDetector.value));
          const advTranslator = vue.ref(translatorService.value);
          const advTranslatorIndex = vue.computed(() => translatorOptions.indexOf(advTranslator.value));
          vue.watch(currentImg, (n, o) => {
            if (n !== o) {
              advDetectRes.value = detectionResolution.value;
              advRenderTextDir.value = renderTextOrientation.value;
            }
          });
          return () => vue.h(
            "div",
            {
              style: {
                marginRight: "-12px",
                padding: "2px",
                paddingLeft: "4px",
                paddingRight: "8px",
                color: "#fff",
                backgroundColor: backgroundColor.value,
                borderRadius: "4px",
                borderTopLeftRadius: borderRadius.value,
                borderBottomLeftRadius: borderRadius.value,
                cursor: "default"
              }
            },
            buttonContent.value ? vue.h("div", {
              style: {
                paddingRight: "8px"
              }
            }, buttonContent.value) : currentImg.value && !translateEnabledMap[currentImg.value] ? advancedMenuOpen.value ? [
              vue.h("div", {
                style: {
                  display: "flex",
                  flexDirection: "row",
                  alignItems: "center",
                  paddingRight: "8px",
                  paddingBottom: "2px"
                },
                onClick: vue.withModifiers(() => {
                  advancedMenuOpen.value = false;
                }, ["stop", "prevent"])
              }, [
                vue.h(IconCarbonChevronRight, {
                  style: {
                    verticalAlign: "middle",
                    cursor: "pointer"
                  }
                }),
                vue.h("div", {}, tt(t("settings.inline-options-title")))
              ]),
              vue.h("div", {
                style: {
                  display: "flex",
                  flexDirection: "column",
                  gap: "4px",
                  marginLeft: "18px"
                }
              }, [
                [
                  [
                    t("settings.detection-resolution"),
                    advDetectRes,
                    advDetectResIndex,
                    detectResOptions,
                    detectResOptionsMap
                  ],
                  [
                    t("settings.text-detector"),
                    advTextDetector,
                    advTextDetectorIndex,
                    textDetectorOptions,
                    textDetectorOptionsMap
                  ],
                  [
                    t("settings.translator"),
                    advTranslator,
                    advTranslatorIndex,
                    translatorOptions,
                    translatorOptionsMap
                  ],
                  [
                    t("settings.render-text-orientation"),
                    advRenderTextDir,
                    advRenderTextDirIndex,
                    renderTextDirOptions,
                    Object.fromEntries(Object.entries(renderTextDirOptionsMap).map(([k, v]) => [k, tt(v)]))
                  ]
                ].map(
                  ([title, opt, optIndex, opts, optMap]) => vue.h("div", {}, [
                    vue.h(
                      "div",
                      {
                        style: {
                          fontSize: "12px"
                        }
                      },
                      tt(title)
                    ),
                    vue.h("div", {
                      style: {
                        display: "flex",
                        flexDirection: "row",
                        justifyContent: "space-between",
                        alignItems: "center",
                        userSelect: "none"
                      }
                    }, [
                      vue.h(optIndex.value <= 0 ? "div" : IconCarbonChevronLeft, {
                        style: {
                          width: "1.2em",
                          cursor: "pointer"
                        },
                        onClick: vue.withModifiers(() => {
                          if (optIndex.value <= 0)
                            return;
                          opt.value = opts[optIndex.value - 1];
                        }, ["stop", "prevent"])
                      }),
                      vue.h("div", {}, untt(optMap[opt.value])),
                      vue.h(optIndex.value >= opts.length - 1 ? "div" : IconCarbonChevronRight, {
                        style: {
                          width: "1.2em",
                          cursor: "pointer"
                        },
                        onClick: vue.withModifiers(() => {
                          if (optIndex.value >= opts.length - 1)
                            return;
                          opt.value = opts[optIndex.value + 1];
                        }, ["stop", "prevent"])
                      })
                    ])
                  ])
                ),
                vue.h("div", {
                  style: {
                    padding: "2px 0px 1px 0px",
                    border: "1px solid #A1A1AA",
                    borderRadius: "2px",
                    textAlign: "center",
                    cursor: "pointer"
                  },
                  onClick: vue.withModifiers(() => {
                    if (!submitTranslateTest())
                      return;
                    if (translateEnabledMap[currentImg.value])
                      return;
                    enable(currentImg.value, {
                      detectionResolution: advDetectRes.value,
                      renderTextOrientation: advRenderTextDir.value,
                      textDetector: advTextDetector.value,
                      translator: advTranslator.value
                    });
                    advancedMenuOpen.value = false;
                  }, ["stop", "prevent"])
                }, tt(t("common.control.translate")))
              ])
            ] : vue.h(IconCarbonChevronLeft, {
              style: {
                verticalAlign: "middle",
                paddingBottom: "3px",
                cursor: "pointer"
              },
              onClick: vue.withModifiers(() => {
                advancedMenuOpen.value = true;
              }, ["stop", "prevent"])
            }) : []
          );
        }));
        buttonStatusApp.mount(buttonStatusContainer);
        return {
          active,
          update() {
            vue.triggerRef(updateRef);
            if (referenceChild.style.backgroundColor)
              child.style.backgroundColor = backgroundColor.value = referenceChild.style.backgroundColor;
          },
          stop() {
            stopDisplayWatch();
            stopSpinnerWatch();
            stopImageWatch();
            buttonIconApp.unmount();
            buttonStatusApp.unmount();
            buttonParent.removeChild(container);
            for (const img of images.value) {
              if (img.hasAttribute("data-transurl")) {
                const transurl = img.getAttribute("data-transurl");
                img.src = transurl;
                img.removeAttribute("data-transurl");
              }
            }
          }
        };
      };
      let dialogInstance;
      const rescanLayers = () => {
        var _a2;
        const [newDialog] = Array.from(layers.children).filter((el) => {
          var _a3, _b, _c;
          return (_c = (_b = (_a3 = el.querySelector('[aria-labelledby="modal-header"][role="dialog"]')) == null ? void 0 : _a3.firstChild) == null ? void 0 : _b.firstChild) == null ? void 0 : _c.childNodes[2];
        });
        if (newDialog !== dialog || !newDialog) {
          dialogInstance == null ? void 0 : dialogInstance.stop();
          dialogInstance = void 0;
          dialog = newDialog;
          if (!dialog)
            return;
          dialogInstance = createDialogInstance();
        }
        const newIndex = Number((_a2 = location.pathname.match(/\/status\/\d+\/photo\/(\d+)/)) == null ? void 0 : _a2[1]) - 1;
        if (newIndex !== dialogInstance.active.value)
          dialogInstance.active.value = newIndex;
        dialogInstance.update();
      };
      const onLayersUpdate = () => {
        layersObserver = new MutationObserver(
          shared.useThrottleFn(() => {
            rescanLayers();
          }, 200, true, false)
        );
        layersObserver.observe(layers, { childList: true, subtree: true });
        rescanLayers();
      };
      if (layers) {
        onLayersUpdate();
      } else {
        initObserver = new MutationObserver(
          shared.useThrottleFn(() => {
            layers = document.getElementById("layers");
            if (layers) {
              onLayersUpdate();
              initObserver == null ? void 0 : initObserver.disconnect();
            }
          }, 200, true, false)
        );
        initObserver.observe(document.body, { childList: true, subtree: true });
      }
      return {
        canKeep(url) {
          var _a2;
          const urlStatusId = (_a2 = url.match(/\/status\/(\d+)/)) == null ? void 0 : _a2[1];
          return urlStatusId === statusId;
        },
        stop() {
          layersObserver == null ? void 0 : layersObserver.disconnect();
          initObserver == null ? void 0 : initObserver.disconnect();
        }
      };
    }
    const translator = {
      match(url) {
        return url.hostname.endsWith("twitter.com") && url.pathname.match(/\/status\//);
      },
      mount: mount$1
    };

    function mount() {
      let settingsTab;
      let textApp;
      const checkTab = () => {
        const tablist = document.querySelector('[role="tablist"]') || document.querySelector('[data-testid="loggedOutPrivacySection"]');
        if (!tablist) {
          if (textApp) {
            textApp.unmount();
            textApp = void 0;
          }
          return;
        }
        if (tablist.querySelector(`div[data-imgtrans-settings-${EDITION}]`))
          return;
        const inactiveRefrenceEl = Array.from(tablist.children).find((el) => el.children.length < 2 && el.querySelector("a"));
        if (!inactiveRefrenceEl)
          return;
        settingsTab = inactiveRefrenceEl.cloneNode(true);
        settingsTab.setAttribute(`data-imgtrans-settings-${EDITION}`, "true");
        const textEl = settingsTab.querySelector("span");
        if (textEl) {
          textApp = vue.createApp(() => tt(t("settings.title")));
          textApp.mount(textEl);
        }
        const linkEl = settingsTab.querySelector("a");
        if (linkEl)
          linkEl.href = `/settings/__imgtrans_${EDITION}`;
        tablist.appendChild(settingsTab);
      };
      let settingsApp;
      const checkSettings = () => {
        var _a, _b;
        const section = (_b = (_a = document.querySelector('[data-testid="error-detail"]')) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.parentElement;
        if (!(section == null ? void 0 : section.querySelector(`[data-imgtrans-settings-${EDITION}-section]`))) {
          if (settingsApp) {
            settingsApp.unmount();
            settingsApp = void 0;
          }
          if (!section)
            return;
        }
        const title = `${tt(t("settings.title"))} / Twitter`;
        if (document.title !== title)
          document.title = title;
        if (settingsApp)
          return;
        const errorPage = section.firstChild;
        errorPage.style.display = "none";
        const settingsContainer = document.createElement("div");
        settingsContainer.setAttribute(`data-imgtrans-settings-${EDITION}-section`, "true");
        section.appendChild(settingsContainer);
        settingsApp = vue.createApp(vue.defineComponent(() => {
          vue.onUnmounted(() => {
            errorPage.style.display = "";
          });
          return () => vue.h("div", {
            style: {
              paddingLeft: "16px",
              paddingRight: "16px"
            }
          }, [
            vue.h("div", {
              style: {
                display: "flex",
                height: "53px",
                alignItems: "center"
              }
            }, [
              vue.h("h2", {
                style: {
                  fontSize: "20px",
                  lineHeight: "24px"
                }
              }, tt(t("settings.title")))
            ]),
            renderSettings()
          ]);
        }));
        settingsApp.mount(settingsContainer);
      };
      const listObserver = new MutationObserver(
        shared.useThrottleFn(() => {
          checkTab();
          if (location.pathname.match(`/settings/__imgtrans_${EDITION}`)) {
            if (settingsTab && settingsTab.children.length < 2) {
              settingsTab.style.backgroundColor = "#F7F9F9";
              const activeIndicator = document.createElement("div");
              activeIndicator.style.position = "absolute";
              activeIndicator.style.zIndex = "1";
              activeIndicator.style.top = "0";
              activeIndicator.style.left = "0";
              activeIndicator.style.bottom = "0";
              activeIndicator.style.right = "0";
              activeIndicator.style.borderRight = "2px solid #1D9Bf0";
              activeIndicator.style.pointerEvents = "none";
              settingsTab.appendChild(activeIndicator);
            }
            checkSettings();
          } else {
            if (settingsTab && settingsTab.children.length > 1) {
              settingsTab.style.backgroundColor = "";
              settingsTab.removeChild(settingsTab.lastChild);
            }
            if (settingsApp) {
              settingsApp.unmount();
              settingsApp = void 0;
            }
          }
        }, 200, true, false)
      );
      listObserver.observe(document.body, { childList: true, subtree: true });
      return {
        canKeep(url) {
          return url.includes("twitter.com") && url.includes("settings/");
        },
        stop() {
          settingsApp == null ? void 0 : settingsApp.unmount();
          listObserver.disconnect();
        }
      };
    }
    const settingsInjector = {
      match(url) {
        return url.hostname.endsWith("twitter.com") && url.pathname.match(/\/settings\//);
      },
      mount
    };

    start([translator$1, translator], [settingsInjector$1, settingsInjector]);

})(Vue, VueUse);