// ==UserScript== // @name Auto Translate - TWP Engine Full Port // @name:zh-CN 网页自动翻译 — Via浏览器专属 · 苹果安卓都能用 · 249种语言 // @name:zh-TW 網頁自動翻譯 — Via瀏覽器專屬 · 蘋果安卓都能用 · 249種語言 // @name:en Auto Translate — Built for Via Browser · Works on iPhone & Android · 249 Languages // @name:ja 自動翻訳 — Via ブラウザ対応 · iPhone でも Android でも動く · 249言語 // @name:ko 자동 번역 — Via 브라우저 전용 · 아이폰 안드로이드 다 됨 · 249개 언어 // @name:fr Traduction auto — Conçu pour Via · Fonctionne sur iPhone & Android · 249 langues // @name:de Auto-Übersetzer — Für Via Browser · Läuft auf iPhone & Android · 249 Sprachen // @name:es Traducción automática — Hecho para Via · Funciona en iPhone y Android · 249 idiomas // @name:pt Tradução automática — Feito pro Via · Funciona no iPhone e Android · 249 idiomas // @name:pt-BR Tradução automática — Feito pro Via · Funciona no iPhone e Android · 249 idiomas // @name:ru Авто-перевод — Для Via браузера · Работает на iPhone и Android · 249 языков // @name:it Traduzione automatica — Fatto per Via · Funziona su iPhone e Android · 249 lingue // @name:tr Otomatik Çeviri — Via Tarayıcı için · iPhone ve Android'de çalışır · 249 dil // @name:ar ترجمة تلقائية — مصمم لمتصفح Via · يشتغل على آيفون وأندرويد · 249 لغة // @name:th แปลอัตโนมัติ — สำหรับ Via Browser · ใช้ได้ทั้ง iPhone และ Android · 249 ภาษา // @name:vi Tự động dịch — Dành cho Via Browser · Chạy trên cả iPhone lẫn Android · 249 ngôn ngữ // @name:id Terjemahan Otomatis — Buat Via Browser · Jalan di iPhone & Android · 249 bahasa // @name:ms Terjemahan Auto — Untuk Via Browser · Boleh guna di iPhone & Android · 249 bahasa // @name:hi ऑटो ट्रांसलेट — Via ब्राउज़र के लिए · iPhone और Android दोनों पर चलता है · 249 भाषाएँ // @name:bn অটো অনুবাদ — Via Browser-এর জন্য · iPhone ও Android দুটোতেই চলে · ২৪৯ ভাষা // @name:ta தானியங்கி மொழிபெயர்ப்பு — Via Browser-க்கு · iPhone, Android இரண்டிலும் வேலை செய்யும் · 249 மொழிகள் // @name:te ఆటో ట్రాన్స్లేట్ — Via Browser కోసం · iPhone, Android రెండింటిలోనూ పనిచేస్తుంది · 249 భాషలు // @name:ur آٹو ترجمہ — Via براؤزر کے لیے · آئی فون اور اینڈرائیڈ دونوں پر چلتا ہے · 249 زبانیں // @name:fa ترجمه خودکار — برای مرورگر Via · روی آیفون و اندروید کار میکنه · 249 زبان // @name:pl Auto-tłumaczenie — Dla Via Browser · Działa na iPhone i Android · 249 języków // @name:uk Авто-переклад — Для Via браузера · Працює на iPhone та Android · 249 мов // @name:ro Traducere automată — Pentru Via Browser · Merge pe iPhone și Android · 249 limbi // @name:nl Automatisch vertalen — Voor Via Browser · Werkt op iPhone & Android · 249 talen // @name:el Αυτόματη μετάφραση — Για Via Browser · Λειτουργεί σε iPhone & Android · 249 γλώσσες // @name:cs Automatický překlad — Pro Via Browser · Funguje na iPhone i Android · 249 jazyků // @name:hu Automatikus fordítás — Via Böngészőhöz · iPhone-on és Androidon is megy · 249 nyelv // @name:sv Automatisk översättning — För Via Browser · Fungerar på iPhone & Android · 249 språk // @name:da Automatisk oversættelse — Til Via Browser · Virker på iPhone & Android · 249 sprog // @name:fi Automaattinen käännös — Via-selaimelle · Toimii iPhonella ja Androidilla · 249 kieltä // @name:no Automatisk oversettelse — For Via Browser · Fungerer på iPhone og Android · 249 språk // @name:bg Автоматичен превод — За Via браузър · Работи на iPhone и Android · 249 езика // @name:hr Automatski prijevod — Za Via Browser · Radi na iPhoneu i Androidu · 249 jezika // @name:sr Аутоматски превод — За Via прегледач · Ради на iPhone и Android · 249 језика // @name:sk Automatický preklad — Pre Via Browser · Funguje na iPhone aj Android · 249 jazykov // @name:sl Samodejni prevod — Za Via Browser · Deluje na iPhone in Android · 249 jezikov // @name:lt Automatinis vertimas — Via naršyklei · Veikia iPhone ir Android · 249 kalbos // @name:lv Automātiskā tulkošana — Via pārlūkam · Darbojas iPhone un Android · 249 valodas // @name:et Automaatne tõlge — Via brauserile · Töötab iPhone'is ja Androidis · 249 keelt // @name:sw Tafsiri Otomatiki — Kwa Via Browser · Inafanya kazi iPhone na Android · Lugha 249 // @name:fil Auto Translate — Para sa Via Browser · Gumagana sa iPhone at Android · 249 na wika // @name:my အလိုအလျောက် ဘာသာပြန် — Via Browser အတွက် · iPhone နဲ့ Android မှာ အလုပ်လုပ်တယ် · ဘာသာ 249 // @name:km បកប្រែស្វ័យប្រវត្តិ — សម្រាប់ Via Browser · ដំណើរការលើ iPhone និង Android · ២៤៩ ភាសា // @name:lo ແປພາສາອັດຕະໂນມັດ — ສຳລັບ Via Browser · ໃຊ້ໄດ້ທັງ iPhone ແລະ Android · 249 ພາສາ // @name:ka ავტომატური თარგმანი — Via ბრაუზერისთვის · მუშაობს iPhone-ზე და Android-ზე · 249 ენა // @name:hy Delays նակ թարdays — Via դdays · iPhone ays Android-ays · 249 լdays // @name:am ራስ-ሰር ትርጉም — ለ Via Browser · iPhone እና Android ሁለቱም ይሰራል · 249 ቋንቋዎች // @name:ne स्वचालित अनुवाद — Via Browser को लागि · iPhone र Android दुवैमा चल्छ · 249 भाषाहरू // @name:si ස්වයංක්රීය පරිවර්තනය — Via Browser සඳහා · iPhone සහ Android දෙකේම වැඩ කරයි · භාෂා 249 // @name:mn Автомат орчуулга — Via хөтөч дээр · iPhone болон Android дээр ажилладаг · 249 хэл // @name:uz Avtomatik tarjima — Via brauzeri uchun · iPhone va Androidda ishlaydi · 249 til // @name:kk Автоматты аударма — Via браузеріне · iPhone мен Android-та жұмыс істейді · 249 тіл // @name:az Avtomatik tərcümə — Via Browser üçün · iPhone və Androidda işləyir · 249 dil // @name:sq Përkthim automatik — Për Via Browser · Punon në iPhone dhe Android · 249 gjuhë // @name:mk Автоматски превод — За Via Browser · Работи на iPhone и Android · 249 јазици // @name:bs Automatski prijevod — Za Via Browser · Radi na iPhoneu i Androidu · 249 jezika // @name:is Sjálfvirk þýðing — Fyrir Via Browser · Virkar á iPhone og Android · 249 tungumál // @name:af Outomatiese vertaling — Vir Via Browser · Werk op iPhone & Android · 249 tale // @name:yo Ìtumọ̀ Aládàámọ́ — Fún Via Browser · Ó ń ṣiṣẹ́ lórí iPhone àti Android · Èdè 249 // @name:ha Fassara ta Atomatik — Don Via Browser · Yana aiki a iPhone da Android · Harsuna 249 // @name:ig Nsụgharị Akpaaka — Maka Via Browser · Ọ na-arụ ọrụ na iPhone na Android · Asụsụ 249 // @name:zu Ukuhumusha Okuzenzakalelayo — Kwe-Via Browser · Kusebenza ku-iPhone ne-Android · Izilimi 249 // @name:mg Dikanteny Mandeha ho azy — Ho an'ny Via Browser · Mandeha amin'ny iPhone sy Android · 249 fiteny // @name:eo Aŭtomata traduko — Por Via Retumilo · Funkcias ĉe iPhone kaj Android · 249 lingvoj // @name:la Translatio automatica — Pro Via Navigatro · Operatur in iPhone et Android · 249 linguae // @name:he תרגום אוטומטי — לדפדפן Via · עובד באייפון ובאנדרואיד · 249 שפות // @name:gu ઓટો ટ્રાન્સલેટ — Via Browser માટે · iPhone અને Android બંને પર ચાલે છે · 249 ભાષાઓ // @name:mr ऑटो ट्रान्सलेट — Via Browser साठी · iPhone आणि Android दोन्हींवर चालते · 249 भाषा // @name:pa ਆਟੋ ਅਨੁਵਾਦ — Via Browser ਲਈ · iPhone ਅਤੇ Android ਦੋਵਾਂ ਤੇ ਚੱਲਦਾ ਹੈ · 249 ਭਾਸ਼ਾਵਾਂ // @name:kn ಆಟೋ ಅನುವಾದ — Via Browser ಗಾಗಿ · iPhone ಮತ್ತು Android ಎರಡರಲ್ಲೂ ಕೆಲಸ ಮಾಡುತ್ತದೆ · 249 ಭಾಷೆಗಳು // @name:ml ഓട്ടോ ട്രാൻസ്ലേറ്റ് — Via Browser-നായി · iPhone-ലും Android-ലും പ്രവർത്തിക്കുന്നു · 249 ഭാഷകൾ // @description 网页自动翻译,Via浏览器专属优化,苹果安卓通吃,Google/微软/腾讯三引擎,249种语言随便切。 // @description:zh-CN 网页自动翻译,Via浏览器专属优化,苹果安卓通吃,Google/微软/腾讯三引擎,249种语言随便切。 // @description:zh-TW 網頁自動翻譯,Via瀏覽器專屬優化,蘋果安卓通吃,Google/微軟/騰訊三引擎,249種語言隨便切。 // @description:en Auto-translate any webpage. Optimized for Via Browser on both iPhone & Android. 3 engines (Google/Microsoft/Tencent), 249 languages, just works. // @description:ja Webページを自動翻訳。Via ブラウザに最適化、iPhoneでもAndroidでもそのまま動く。Google/Microsoft/Tencentの3エンジン、249言語対応。 // @description:ko 웹페이지 자동 번역. Via 브라우저에 최적화, 아이폰이든 안드로이드든 그냥 됨. Google/Microsoft/Tencent 3개 엔진, 249개 언어. // @description:fr Traduction automatique de pages web. Optimisé pour Via sur iPhone et Android. 3 moteurs (Google/Microsoft/Tencent), 249 langues, ça marche direct. // @description:de Automatische Webseiten-Übersetzung. Optimiert für Via Browser auf iPhone und Android. 3 Engines (Google/Microsoft/Tencent), 249 Sprachen, läuft einfach. // @description:es Traducción automática de páginas web. Optimizado para Via en iPhone y Android. 3 motores (Google/Microsoft/Tencent), 249 idiomas, funciona y ya. // @description:pt Tradução automática de páginas. Otimizado pro Via no iPhone e Android. 3 motores (Google/Microsoft/Tencent), 249 idiomas, só funciona. // @description:pt-BR Tradução automática de páginas. Otimizado pro Via no iPhone e Android. 3 motores (Google/Microsoft/Tencent), 249 idiomas, só funciona. // @description:ru Автоматический перевод страниц. Оптимизирован для Via на iPhone и Android. 3 движка (Google/Microsoft/Tencent), 249 языков, просто работает. // @description:it Traduzione automatica delle pagine web. Ottimizzato per Via su iPhone e Android. 3 motori (Google/Microsoft/Tencent), 249 lingue, funziona e basta. // @description:tr Web sayfalarını otomatik çevir. Via Browser için optimize edildi, iPhone ve Android'de çalışır. 3 motor (Google/Microsoft/Tencent), 249 dil. // @description:ar ترجمة تلقائية لصفحات الويب. مُحسّن لمتصفح Via على آيفون وأندرويد. 3 محركات (Google/Microsoft/Tencent)، 249 لغة، يشتغل وخلاص. // @description:th แปลเว็บอัตโนมัติ ปรับแต่งสำหรับ Via Browser ใช้ได้ทั้ง iPhone และ Android 3 เครื่องยนต์ (Google/Microsoft/Tencent) 249 ภาษา ใช้ได้เลย // @description:vi Tự động dịch trang web. Tối ưu cho Via Browser trên cả iPhone lẫn Android. 3 engine (Google/Microsoft/Tencent), 249 ngôn ngữ, cài vào là chạy. // @description:id Terjemahkan halaman web otomatis. Dioptimalkan buat Via Browser di iPhone dan Android. 3 mesin (Google/Microsoft/Tencent), 249 bahasa, langsung jalan. // @description:ms Terjemah halaman web secara automatik. Dioptimumkan untuk Via Browser di iPhone dan Android. 3 enjin (Google/Microsoft/Tencent), 249 bahasa. // @description:hi वेबपेज ऑटो-ट्रांसलेट। Via Browser के लिए बना है, iPhone और Android दोनों पर चलता है। 3 इंजन (Google/Microsoft/Tencent), 249 भाषाएँ। // @description:bn ওয়েবপেজ অটো-ট্রান্সলেট। Via Browser-এর জন্য অপ্টিমাইজ করা, iPhone ও Android দুটোতেই চলে। 3 ইঞ্জিন, 249 ভাষা। // @description:ta வலைப்பக்கங்களை தானாக மொழிபெயர்க்கும். Via Browser-க்கு உகந்தது, iPhone மற்றும் Android இரண்டிலும் வேலை செய்யும். 3 இயந்திரங்கள், 249 மொழிகள். // @description:te వెబ్పేజీలను ఆటోమేటిక్గా అనువదిస్తుంది. Via Browser కోసం ఆప్టిమైజ్ చేయబడింది, iPhone మరియు Android రెండింటిలోనూ పనిచేస్తుంది. 3 ఇంజన్లు, 249 భాషలు. // @description:ur ویب پیج خودکار ترجمہ۔ Via Browser کے لیے بنایا گیا، آئی فون اور اینڈرائیڈ دونوں پر چلتا ہے۔ 3 انجن، 249 زبانیں۔ // @description:fa ترجمه خودکار صفحات وب. بهینهشده برای مرورگر Via روی آیفون و اندروید. 3 موتور (Google/Microsoft/Tencent)، 249 زبان. // @description:pl Automatyczne tłumaczenie stron. Zoptymalizowane pod Via Browser na iPhone i Android. 3 silniki (Google/Microsoft/Tencent), 249 języków, po prostu działa. // @description:uk Автоматичний переклад сторінок. Оптимізовано для Via на iPhone та Android. 3 рушії (Google/Microsoft/Tencent), 249 мов, просто працює. // @description:ro Traducere automată a paginilor web. Optimizat pentru Via pe iPhone și Android. 3 motoare (Google/Microsoft/Tencent), 249 limbi, merge direct. // @description:nl Automatische vertaling van webpagina's. Geoptimaliseerd voor Via Browser op iPhone en Android. 3 engines, 249 talen, werkt gewoon. // @description:el Αυτόματη μετάφραση ιστοσελίδων. Βελτιστοποιημένο για Via Browser σε iPhone και Android. 3 μηχανές, 249 γλώσσες, απλά δουλεύει. // @description:cs Automatický překlad stránek. Optimalizováno pro Via Browser na iPhone a Android. 3 enginy, 249 jazyků, prostě to funguje. // @description:hu Automatikus weblap-fordítás. Via Böngészőhöz optimalizálva, iPhone-on és Androidon is megy. 3 motor, 249 nyelv. // @description:sv Automatisk översättning av webbsidor. Optimerad för Via Browser på iPhone och Android. 3 motorer, 249 språk, bara funkar. // @description:da Automatisk oversættelse af websider. Optimeret til Via Browser på iPhone og Android. 3 motorer, 249 sprog, virker bare. // @description:fi Automaattinen verkkosivujen käännös. Optimoitu Via-selaimelle iPhonella ja Androidilla. 3 moottoria, 249 kieltä, toimii vain. // @description:no Automatisk oversettelse av nettsider. Optimalisert for Via Browser på iPhone og Android. 3 motorer, 249 språk, bare fungerer. // @description:bg Автоматичен превод на уебстраници. Оптимизиран за Via на iPhone и Android. 3 двигателя, 249 езика, просто работи. // @description:hr Automatski prijevod web stranica. Optimizirano za Via Browser na iPhoneu i Androidu. 3 motora, 249 jezika, jednostavno radi. // @description:sr Аутоматски превод веб страница. Оптимизовано за Via на iPhoneu и Androidu. 3 мотора, 249 језика, једноставно ради. // @description:sk Automatický preklad stránok. Optimalizované pre Via Browser na iPhone a Android. 3 enginy, 249 jazykov, jednoducho funguje. // @description:sl Samodejni prevod spletnih strani. Optimizirano za Via Browser na iPhone in Android. 3 motorji, 249 jezikov, preprosto deluje. // @description:lt Automatinis tinklalapių vertimas. Optimizuota Via naršyklei iPhone ir Android. 3 varikliai, 249 kalbos, tiesiog veikia. // @description:lv Automātiska tīmekļa lapu tulkošana. Optimizēta Via pārlūkam iPhone un Android. 3 dzinēji, 249 valodas, vienkārši strādā. // @description:et Automaatne veebilehtede tõlge. Optimeeritud Via brauserile iPhone'is ja Androidis. 3 mootorit, 249 keelt, lihtsalt töötab. // @description:sw Tafsiri otomatiki ya kurasa za wavuti. Imebuniwa kwa Via Browser kwenye iPhone na Android. Injini 3, lugha 249, inafanya kazi tu. // @description:fil Awtomatikong pagsasalin ng mga webpage. Optimized para sa Via Browser sa iPhone at Android. 3 engine, 249 na wika, gumagana agad. // @description:my ဝဘ်စာမျက်နှာများကို အလိုအလျောက် ဘာသာပြန်ပါ။ Via Browser အတွက် iPhone နဲ့ Android မှာ အလုပ်လုပ်တယ်။ အင်ဂျင် 3 ခု၊ ဘာသာ 249။ // @description:km បកប្រែគេហទំព័រដោយស្វ័យប្រវត្តិ។ ធ្វើឱ្យប្រសើរសម្រាប់ Via Browser នៅលើ iPhone និង Android។ ម៉ាស៊ីន 3, ២៤៩ ភាសា។ // @description:lo ແປໜ້າເວັບອັດຕະໂນມັດ. ປັບແຕ່ງສຳລັບ Via Browser ໃນ iPhone ແລະ Android. 3 ເຄື່ອງຈັກ, 249 ພາສາ, ໃຊ້ໄດ້ເລີຍ. // @description:ka ვებგვერდების ავტომატური თარგმანი. ოპტიმიზებულია Via ბრაუზერისთვის iPhone-ზე და Android-ზე. 3 ძრავი, 249 ენა, უბრალოდ მუშაობს. // @description:hy Վdelays պdelays delays delays. Via delays iPhone days Android days. 3 delays, 249 delays. // @description:am ድረ-ገጾችን በራስ-ሰር ይተርጉሙ። ለ Via Browser iPhone እና Android ላይ ተመቻችቷል። 3 ሞተሮች፣ 249 ቋንቋዎች፣ በቀላሉ ይሰራል። // @description:ne वेबपेज स्वचालित अनुवाद। Via Browser को लागि iPhone र Android दुवैमा अनुकूलित। 3 इन्जिन, 249 भाषा, सिधै चल्छ। // @description:si වෙබ් පිටු ස්වයංක්රීයව පරිවර්තනය කරන්න. Via Browser සඳහා iPhone සහ Android දෙකේම ප්රශස්ත කර ඇත. එන්ජින් 3ක්, භාෂා 249ක්. // @description:mn Вэб хуудсыг автоматаар орчуулна. Via хөтөч дээр iPhone болон Android-д зориулж оновчилсон. 3 хөдөлгүүр, 249 хэл. // @description:uz Veb-sahifalarni avtomatik tarjima qilish. Via brauzeri uchun iPhone va Androidda optimallashtirilgan. 3 ta dvigatel, 249 til, shunchaki ishlaydi. // @description:kk Веб-беттерді автоматты аударма. Via браузеріне iPhone мен Android-та оңтайландырылған. 3 қозғалтқыш, 249 тіл, жай ғана жұмыс істейді. // @description:az Veb səhifələrin avtomatik tərcüməsi. Via Browser üçün iPhone və Androidda optimallaşdırılıb. 3 mühərrik, 249 dil, sadəcə işləyir. // @description:sq Përkthim automatik i faqeve të internetit. I optimizuar për Via Browser në iPhone dhe Android. 3 motorë, 249 gjuhë, thjesht funksionon. // @description:mk Автоматски превод на веб-страници. Оптимизирано за Via Browser на iPhone и Android. 3 мотори, 249 јазици, едноставно работи. // @description:bs Automatski prijevod web stranica. Optimizirano za Via Browser na iPhoneu i Androidu. 3 motora, 249 jezika, jednostavno radi. // @description:is Sjálfvirk þýðing vefsíðna. Fínstillt fyrir Via Browser á iPhone og Android. 3 vélar, 249 tungumál, virkar bara. // @description:af Outomatiese vertaling van webblaaie. Geoptimaliseer vir Via Browser op iPhone en Android. 3 enjins, 249 tale, werk net. // @description:yo Ìtumọ̀ aládàámọ́ àwọn ojú-ìwé wẹ́ẹ̀bù. Ṣe ìmúdàgba fún Via Browser lórí iPhone àti Android. Ẹ̀rọ 3, èdè 249. // @description:ha Fassara shafukan yanar gizo ta atomatik. An inganta don Via Browser a iPhone da Android. Injuna 3, harsuna 249, kawai tana aiki. // @description:ig Nsụgharị akpaaka nke ibe weebụ. Emezigharịrị maka Via Browser na iPhone na Android. Injin 3, asụsụ 249, ọ na-arụ ọrụ. // @description:zu Ukuhumusha okuzenzakalelayo kwamakhasi ewebhu. Kuthuthukiselwe i-Via Browser ku-iPhone ne-Android. Izinjini ezi-3, izilimi ezingu-249. // @description:mg Dikanteny amin'ny pejy tranonkala mandeha ho azy. Natao ho an'ny Via Browser amin'ny iPhone sy Android. Motera 3, fiteny 249, mandeha fotsiny. // @description:eo Aŭtomata traduko de retpaĝoj. Optimumigita por Via Retumilo ĉe iPhone kaj Android. 3 motoroj, 249 lingvoj, simple funkcias. // @description:la Translatio automatica paginarum interretialium. Optimata pro Via Navigatro in iPhone et Android. 3 motores, 249 linguae. // @description:he תרגום אוטומטי של דפי אינטרנט. מותאם לדפדפן Via באייפון ובאנדרואיד. 3 מנועים, 249 שפות, פשוט עובד. // @description:gu વેબપેજ ઓટો-ટ્રાન્સલેટ. Via Browser માટે iPhone અને Android બંને પર ઑપ્ટિમાઇઝ કરેલ. 3 એન્જિન, 249 ભાષાઓ, બસ ચાલે છે. // @description:mr वेबपेज ऑटो-ट्रान्सलेट. Via Browser साठी iPhone आणि Android दोन्हींवर ऑप्टिमाइझ केलेले. 3 इंजिन, 249 भाषा, फक्त चालते. // @description:pa ਵੈੱਬਪੇਜ ਆਟੋ-ਅਨੁਵਾਦ. Via Browser ਲਈ iPhone ਅਤੇ Android ਦੋਵਾਂ ਤੇ ਅਨੁਕੂਲਿਤ. 3 ਇੰਜਣ, 249 ਭਾਸ਼ਾਵਾਂ, ਬੱਸ ਚੱਲਦਾ ਹੈ. // @description:kn ವೆಬ್ಪೇಜ್ ಆಟೋ-ಅನುವಾದ. Via Browser ಗಾಗಿ iPhone ಮತ್ತು Android ಎರಡರಲ್ಲೂ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ. 3 ಎಂಜಿನ್ಗಳು, 249 ಭಾಷೆಗಳು. // @description:ml വെബ്പേജ് ഓട്ടോ-ട്രാൻസ്ലേറ്റ്. Via Browser-നായി iPhone-ലും Android-ലും ഒപ്റ്റിമൈസ് ചെയ്തിരിക്കുന്നു. 3 എഞ്ചിനുകൾ, 249 ഭാഷകൾ. // @version 6.6.6.6.6.6 // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @connect translate-pa.googleapis.com // @connect translate.googleapis.com // @connect api-edge.cognitive.microsofttranslator.com // @connect edge.microsoft.com // @connect transmart.qq.com // @run-at document-idle // @namespace auto-translate-twp // @downloadURL none // ==/UserScript== (async () => { 'use strict'; try { if (document.contentType === 'application/xml') return } catch (_) {} // ══════════════════════════════════════════════════════════ // ★ 优化1: 所有配置并行读取 (原来串行5次await → 1次Promise.all) // ══════════════════════════════════════════════════════════ const deviceLang = (navigator.language || navigator.userLanguage || 'zh-CN').split('-')[0]; const [ _engine, _targetLang, _autoMode, _excludedHosts ] = await Promise.all([ GM_getValue('engine', 'google'), GM_getValue('targetLang', deviceLang === 'zh' ? 'zh-CN' : deviceLang), GM_getValue('autoMode', true), GM_getValue('excludedHosts', '[]'), ]); let currentEngine = _engine; let targetLang = _targetLang; let autoMode = _autoMode; let excludedHosts = JSON.parse(_excludedHosts); if (excludedHosts.includes(location.host)) return; // ══════════════════════════════════════════════════════════ // ★ 优化2: Auth预加载 — 立即启动,不等任何东西 // 与DOM ready并行执行 // ══════════════════════════════════════════════════════════ const ALL_LANGUAGES = { "zh-CN": "中文(简体)", "zh-TW": "中文(繁體)", "en": "English", "ja": "日本語", "ko": "한국어", "fr": "Français", "de": "Deutsch", "es": "Español", "ru": "Русский", "pt": "Português", "pt-PT": "Português (Portugal)", "ar": "العربية", "th": "ไทย", "vi": "Tiếng Việt", "it": "Italiano", "tr": "Türkçe", "id": "Indonesia", "ms": "Bahasa Melayu", "nl": "Nederlands", "pl": "Polski", "uk": "Українська", "cs": "Čeština", "sk": "Slovenčina", "hu": "Magyar", "ro": "Română", "bg": "Български", "hr": "Hrvatski", "sr": "Српски", "sl": "Slovenščina", "lt": "Lietuvių", "lv": "Latviešu", "et": "Eesti", "fi": "Suomi", "sv": "Svenska", "da": "Dansk", "no": "Norsk", "is": "Íslenska", "el": "Ελληνικά", "he": "עברית", "hi": "हिन्दी", "bn": "বাংলা", "ta": "தமிழ்", "te": "తెలుగు", "kn": "ಕನ್ನಡ", "ml": "മലയാളം", "pa": "ਪੰਜਾਬੀ", "gu": "ગુજરાતી", "mr": "मराठी", "ne": "नेपाली", "si": "සිංහල", "ur": "اردو", "fa": "فارسی", "ps": "پښتو", "my": "မြန်မာ", "km": "ខ្មែរ", "lo": "ລາວ", "ka": "ქართული", "hy": "Հայերեն", "az": "Azərbaycan", "kk": "Қазақ", "uz": "Oʻzbek", "mn": "Монгол", "sq": "Shqip", "mk": "Македонски", "be": "Беларуская", "bs": "Bosanski", "ca": "Català", "gl": "Galego", "eu": "Euskara", "mt": "Malti", "cy": "Cymraeg", "ga": "Gaeilge", "gd": "Gàidhlig", "lb": "Lëtzebuergesch", "af": "Afrikaans", "sw": "Kiswahili", "ha": "Hausa", "ig": "Igbo", "yo": "Yorùbá", "zu": "isiZulu", "xh": "isiXhosa", "sn": "chiShona", "st": "Sesotho", "so": "Soomaali", "am": "አማርኛ", "ti": "ትግርኛ", "om": "Oromoo", "mg": "Malagasy", "ny": "Chichewa", "lg": "Luganda", "rw": "Kinyarwanda", "tg": "Тоҷикӣ", "tk": "Türkmen", "ky": "Кыргызча", "tt": "Татар", "eo": "Esperanto", "la": "Latina", "co": "Corsu", "fy": "Frysk", "haw": "ʻŌlelo Hawaiʻi", "sm": "Gagana Samoa", "mi": "Te Reo Māori", "ceb": "Cebuano", "fil": "Filipino", "jv": "Basa Jawa", "su": "Basa Sunda", "hmn": "Hmong", "ht": "Kreyòl Ayisyen", "ku": "Kurdî", "ckb": "کوردی", "sd": "سنڌي", "or": "ଓଡ଼ିଆ", "as": "অসমীয়া", "sa": "संस्कृतम्", "mai": "मैथिली", "bho": "भोजपुरी", "doi": "डोगरी", "ug": "ئۇيغۇرچە", "dv": "ދިވެހި", "ak": "Akan", "ee": "Eʋegbe", "gn": "Guarani", "ay": "Aymar", "bm": "Bamanankan", "ln": "Lingála", "nso": "Sepedi", "ts": "Xitsonga", "qu": "Runasimi", "ilo": "Ilokano", "kri": "Krio", "lus": "Mizo tawng", "mni-Mtei": "ꯃꯤꯇꯩꯂꯣꯟ", "gom": "कोंकणी", "ab": "Аԥсуа", "ace": "Bahsa Acèh", "ach": "Lwo", "aa": "Qafaraf", "alz": "Alur", "av": "Авар", "awa": "अवधी", "ban": "ᬩᬮᬶ", "bal": "بلوچی", "bci": "Baoulé", "ba": "Башҡорт", "btx": "Batak Karo", "bts": "Batak Simalungun", "bbc": "Batak Toba", "bem": "Bemba", "bew": "Betawi", "bik": "Bikol", "br": "Brezhoneg", "bua": "Буряад", "yue": "粵語", "ch": "Chamoru", "ce": "Нохчийн", "chk": "Chuukese", "cv": "Чӑваш", "crh": "Qırımtatar", "prs": "دری", "din": "Thuɔŋjäŋ", "dov": "Dombe", "dyu": "Julakan", "dz": "རྫོང་ཁ", "fo": "Føroyskt", "fj": "Na Vosa Vakaviti", "fon": "Fɔ̀ngbè", "fr-CA": "Français (Canada)", "fur": "Furlan", "ff": "Pulaar", "gaa": "Gã", "cnh": "Lai", "hil": "Hiligaynon", "hrx": "Hunsrik", "iba": "Iban", "iu-Latn": "ᐃᓄᒃᑎᑐᑦ (Latin)", "jam": "Jamaican Patois", "kac": "Jingpo", "kl": "Kalaallisut", "kr": "Kanuri", "pam": "Kapampangan", "kha": "Khasi", "cgg": "Rukiga", "kg": "Kikongo", "mkw": "Kituba", "trp": "Kokborok", "kv": "Коми", "ltg": "Latgaļu", "lij": "Lìgure", "li": "Limburgs", "lmo": "Lombard", "luo": "Dholuo", "mad": "Madhurâ", "mak": "Makassar", "ms-Arab": "بهاس ملايو", "mam": "Mam", "gv": "Gaelg", "mh": "Kajin Majōl", "mwr": "मारवाड़ी", "mfe": "Kreol Morisien", "chm": "Марий", "min": "Minangkabau", "nhe": "Nahuatl", "ndc-ZW": "Ndau", "nr": "isiNdebele", "new": "नेपाल भाषा", "nqo": "ߒߞߏ", "nus": "Thok Nath", "oc": "Occitan", "os": "Ирон", "pag": "Pangasinan", "pap": "Papiamento", "pa-Arab": "پنجابی", "kek": "Qʼeqchiʼ", "rom": "Romani", "rn": "Ikirundi", "se": "Davvisámegiella", "sg": "Sängö", "bo": "བོད་ཡིག", "dsb": "Dolnoserbšćina", "hsb": "Hornjoserbšćina", "ikt": "Inuinnaqtun", "iu": "ᐃᓄᒃᑎᑐᑦ", "lzh": "文言文", "mvf": "ᠮᠣᠩᠭᠣᠯ", "brx": "बर'", "hne": "छत्तीसगढ़ी", "ks": "कॉशुर", "mrj": "Мары", "sa-Latn": "Sanskrit (Latin)", "sc": "Sardu", "scn": "Sicilianu", "szl": "Ślůnski", "su-Latn": "Sunda (Latin)", "tcy": "ತುಳು", "vec": "Vèneto", "war": "Winaray", "wo": "Wolof", "zap": "Zapotec", "ms-Latn": "Malay (Latin)" }; const LANG_GROUPS = { "常用": ["zh-CN","zh-TW","en","ja","ko","fr","de","es","ru","pt","ar","th","vi","it","tr","id"], "欧洲": ["nl","pl","uk","cs","sk","hu","ro","bg","hr","sr","sl","lt","lv","et","fi","sv","da","no","is","el","be","bs","ca","gl","eu","mt","cy","ga","gd","lb","af","eo","la","co","fy","fo","br","oc","sc","scn","szl","fur","lij","lmo","li","vec","ltg","dsb","hsb","gv","se"], "亚洲": ["hi","bn","ta","te","kn","ml","pa","gu","mr","ne","si","ur","fa","ps","my","km","lo","ka","hy","az","kk","uz","mn","tg","tk","ky","tt","ug","dv","or","as","sa","mai","bho","doi","mni-Mtei","gom","awa","ks","brx","hne","mwr","trp","kac","bo","dz","yue","lzh","ms","fil","ceb","jv","su","hmn","ilo","hil","bik","pam","pag","war","ban","mad","mak","min","ace","btx","bts","bbc","bew","iba","ms-Arab","kha"], "非洲": ["sw","ha","ig","yo","zu","xh","sn","st","so","am","ti","om","mg","ny","lg","rw","ak","ee","bm","ln","nso","ts","kri","wo","ff","gaa","fon","bci","dyu","bem","luo","sg","kg","mkw","dov","nus","din","ach","alz","ndc-ZW","nr","rn","mfe"], "美洲/大洋洲": ["pt-PT","fr-CA","ht","qu","gn","ay","haw","sm","mi","fj","mh","ch","chk","jam","nhe","mam","kek","pap","hrx","ikt","iu","iu-Latn","kl"], "其他": ["ab","av","ba","bua","ce","cv","crh","kv","chm","mrj","os","rom","nqo","aa","bal","cnh","kr","prs","pa-Arab","sd","ckb","ku","he"] }; // ══════════════════════════════════════════════════════════ // TWP核心引擎代码(与原版相同,此处不重复注释) // ══════════════════════════════════════════════════════════ const GoogleHelper_v2 = { _lastRequestAuthTime: null, _translateAuth: null, _authNotFound: false, _authPromise: null, get translateAuth() { return this._translateAuth; }, _getAlternativeKey() { return new TextDecoder().decode(new Uint8Array([ 65,73,122,97,83,121,65,84,66,88,97,106,118,122,81, 76,84,68,72,69,81,98,99,112,113,48,73,104,101,48, 118,87,68,72,109,79,53,50,48 ])); }, async findAuth() { if (this._authPromise) return await this._authPromise; this._authPromise = new Promise((resolve) => { let needUpdate = false; if (this._lastRequestAuthTime) { const d = new Date(); if (this._translateAuth) d.setMinutes(d.getMinutes() - 20); else if (this._authNotFound) d.setMinutes(d.getMinutes() - 5); else d.setMinutes(d.getMinutes() - 1); if (d.getTime() > this._lastRequestAuthTime) needUpdate = true; } else { needUpdate = true; } if (needUpdate) { this._lastRequestAuthTime = Date.now(); const altKey = this._getAlternativeKey(); GM_xmlhttpRequest({ method: 'GET', url: 'https://translate.googleapis.com/_/translate_http/_/js/k=translate_http.tr.en_US.YusFYy3P_ro.O/am=AAg/d=1/exm=el_conf/ed=1/rs=AN8SPfq1Hb8iJRleQqQc8zhdzXmF9E56eQ/m=el_main', timeout: 8000, onload: (r) => { if (r.responseText && r.responseText.length > 1) { const m = r.responseText.match(/['"]x-goog-api-key['"]\s*:\s*['"](\w{39})['"]/i); if (m && m.length === 2) { this._translateAuth = m[1]; this._authNotFound = false; } else { this._authNotFound = true; this._translateAuth = altKey; } } else { this._authNotFound = true; this._translateAuth = altKey; } resolve(); }, onerror: () => { this._translateAuth = altKey; resolve(); }, ontimeout: () => { this._translateAuth = altKey; resolve(); } }); } else { resolve(); } }); const p = this._authPromise; p.finally(() => { this._authPromise = null; }); return await p; } }; // ★ 优化: 立即启动Auth预加载(不等待) const authReady = GoogleHelper_v2.findAuth().catch(() => {}); const GoogleHelper = { googleTranslateTKK: "448487.932609646", shiftLeftOrRightThenSumOrXor(num, optString) { for (let i = 0; i < optString.length - 2; i += 3) { let acc = optString.charAt(i + 2); acc = ("a" <= acc) ? acc.charCodeAt(0) - 87 : Number(acc); acc = (optString.charAt(i + 1) === "+") ? num >>> acc : num << acc; num = (optString.charAt(i) === "+") ? (num + acc) & 4294967295 : num ^ acc; } return num; }, transformQuery(query) { const b = []; let idx = 0; for (let i = 0; i < query.length; i++) { let c = query.charCodeAt(i); if (128 > c) { b[idx++] = c; } else { if (2048 > c) { b[idx++] = (c >> 6) | 192; } else { if (55296 === (c & 64512) && i + 1 < query.length && 56320 === (query.charCodeAt(i + 1) & 64512)) { c = 65536 + ((c & 1023) << 10) + (query.charCodeAt(++i) & 1023); b[idx++] = (c >> 18) | 240; b[idx++] = ((c >> 12) & 63) | 128; } else { b[idx++] = (c >> 12) | 224; } b[idx++] = ((c >> 6) & 63) | 128; } b[idx++] = (c & 63) | 128; } } return b; }, calcHash(query) { const s = this.googleTranslateTKK.split("."); const tkkIdx = Number(s[0]) || 0; const tkkKey = Number(s[1]) || 0; const bytes = this.transformQuery(query); let enc = tkkIdx; for (const item of bytes) { enc += item; enc = this.shiftLeftOrRightThenSumOrXor(enc, "+-a^+6"); } enc = this.shiftLeftOrRightThenSumOrXor(enc, "+-3^+b+-f"); enc ^= tkkKey; if (enc <= 0) enc = (enc & 2147483647) + 2147483648; const n = enc % 1000000; return n.toString() + "." + (n ^ tkkIdx); } }; const Utils = { escapeHTML(t) { const d = document.createElement('div'); d.appendChild(document.createTextNode(t)); return d.innerHTML; }, unescapeHTML(t) { const d = new DOMParser().parseFromString(t, 'text/html'); return d.documentElement.textContent; } }; function gmFetch(opts) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ timeout: 20000, ...opts, onload: resolve, onerror: reject, ontimeout: reject }); }); } // ══════════════════════════════════════════════════════════ // 引擎定义 // ══════════════════════════════════════════════════════════ const Engine = { google_v2: { name: 'Google (TWP v2)', _fixLang(lang) { return lang === "prs" ? "fa-AF" : lang; }, _transformResponse(result, dontSort) { if (result.indexOf("
") !== -1) {
result = result.replace("", "");
const i = result.indexOf(">");
result = result.slice(i + 1);
}
const sentences = [];
let idx = 0;
while (true) {
const s = result.indexOf("", idx);
if (s === -1) break;
const e = result.indexOf("", s);
if (e === -1) { sentences.push(result.slice(s + 3)); break; }
else { sentences.push(result.slice(s + 3, e)); }
idx = e;
}
result = sentences.length > 0 ? sentences.join(" ") : result;
result = result.replace(/<\/b>/g, "");
let resultArray = [];
let lastEnd = 0;
for (const r of result.matchAll(/()([^<>]*(?=<\/a>))*/g)) {
const fl = r[0].length, pos = r.index;
if (pos > lastEnd) {
resultArray.push(r[1] + result.slice(lastEnd, pos).replace(/<\/a>/g, "") + (r[2] || ""));
} else { resultArray.push(r[0]); }
lastEnd = pos + fl;
}
let indexes;
if (resultArray.length > 0) {
indexes = resultArray.map(v => parseInt(v.match(/[0-9]+(?=>)/g)?.[0])).filter(v => !isNaN(v));
resultArray = resultArray.map(v => v.slice(v.indexOf(">") + 1));
} else { resultArray = [result]; indexes = [0]; }
resultArray = resultArray.map(v => Utils.unescapeHTML(v));
if (dontSort) return resultArray;
const final = [];
for (const j in indexes) {
if (final[indexes[j]]) final[indexes[j]] += " " + resultArray[j];
else final[indexes[j]] = resultArray[j];
}
return final;
},
async translate(text, toLang) {
const to = this._fixLang(toLang);
await GoogleHelper_v2.findAuth();
if (!GoogleHelper_v2.translateAuth) throw new Error('No auth');
const r = await gmFetch({
method: 'POST',
url: 'https://translate-pa.googleapis.com/v1/translateHtml',
headers: { 'Content-Type': 'application/json+protobuf', 'X-Goog-Api-Key': GoogleHelper_v2.translateAuth },
data: JSON.stringify([[[text], "auto", to], "te"]),
});
if (r.status !== 200) throw new Error(`v2 error: ${r.status}`);
const data = JSON.parse(r.responseText);
if (data && data[0]) {
const raw = Array.isArray(data[0]) ? data[0][0] : data[0];
const parsed = this._transformResponse(raw, false);
return parsed[0] || raw;
}
throw new Error('v2 empty');
},
async translateBatch(texts, toLang) {
const to = this._fixLang(toLang);
await GoogleHelper_v2.findAuth();
if (!GoogleHelper_v2.translateAuth) throw new Error('No auth');
const r = await gmFetch({
method: 'POST',
url: 'https://translate-pa.googleapis.com/v1/translateHtml',
headers: { 'Content-Type': 'application/json+protobuf', 'X-Goog-Api-Key': GoogleHelper_v2.translateAuth },
data: JSON.stringify([[texts, "auto", to], "te"]),
});
if (r.status !== 200) throw new Error(`v2 batch error: ${r.status}`);
const data = JSON.parse(r.responseText);
if (data && data[0] && Array.isArray(data[0])) {
return data[0].map(item => {
const p = this._transformResponse(item, false);
return p[0] || item;
});
}
if (data && data[0]) {
const p = this._transformResponse(Array.isArray(data[0]) ? data[0][0] : data[0], false);
return [p[0]];
}
throw new Error('v2 batch empty');
}
},
google_legacy: {
name: 'Google (Legacy)',
langCode(l) { return l; },
async translate(text, toLang) {
const to = toLang;
const tk = GoogleHelper.calcHash(text);
const r = await gmFetch({
method: 'GET',
url: `https://translate.googleapis.com/translate_a/single?client=webapp&sl=auto&tl=${to}&hl=${to}&dt=t&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=at&ie=UTF-8&oe=UTF-8&otf=1&ssel=0&tsel=0&kc=7&tk=${tk}&q=${encodeURIComponent(text)}`,
});
if (r.status !== 200) return await this._gtx(text, to);
const data = JSON.parse(r.responseText);
return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
},
async _gtx(text, to) {
const r = await gmFetch({ method: 'GET', url: `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=auto&tl=${to}&q=${encodeURIComponent(text)}` });
if (r.status !== 200) throw new Error('gtx error');
const data = JSON.parse(r.responseText);
return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
}
},
google: {
name: 'Google (Auto)',
async translate(text, toLang) {
try { return await Engine.google_v2.translate(text, toLang); }
catch (e) { return await Engine.google_legacy.translate(text, toLang); }
},
async translateBatch(texts, toLang) {
try { return await Engine.google_v2.translateBatch(texts, toLang); }
catch (e) {
const res = [];
for (const t of texts) {
try { res.push(await Engine.google_legacy.translate(t, toLang)); }
catch (_) { res.push(null); }
}
return res;
}
}
},
microsoft: {
name: 'Microsoft',
_token: null, _tokenTime: 0,
async getToken() {
if (this._token && Date.now() - this._tokenTime < 480000) return this._token;
const r = await gmFetch({ method: 'GET', url: 'https://edge.microsoft.com/translate/auth' });
if (r.status !== 200) throw new Error('MS auth');
this._token = r.responseText; this._tokenTime = Date.now();
return this._token;
},
langCode(l) {
const m = { 'zh': 'zh-Hans', 'zh-CN': 'zh-Hans', 'zh-TW': 'zh-Hant', 'no': 'nb', 'sr': 'sr-Cyrl', 'pt-PT': 'pt-pt', 'fr-CA': 'fr-ca' };
return m[l] || l;
},
async translate(text, toLang) {
const token = await this.getToken();
const to = this.langCode(toLang);
const r = await gmFetch({
method: 'POST',
url: `https://api-edge.cognitive.microsofttranslator.com/translate?from=&to=${to}&api-version=3.0`,
headers: { 'authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
data: JSON.stringify([{ Text: text }]),
});
if (r.status !== 200) throw new Error('MS error');
return JSON.parse(r.responseText)[0].translations[0].text;
},
async translateBatch(texts, toLang) {
const token = await this.getToken();
const to = this.langCode(toLang);
const results = [];
for (let b = 0; b < texts.length; b += 25) {
const chunk = texts.slice(b, b + 25);
const r = await gmFetch({
method: 'POST',
url: `https://api-edge.cognitive.microsofttranslator.com/translate?from=&to=${to}&api-version=3.0`,
headers: { 'authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
data: JSON.stringify(chunk.map(t => ({ Text: t }))),
});
if (r.status === 200) {
for (const item of JSON.parse(r.responseText)) results.push(item.translations[0].text);
} else { for (let i = 0; i < chunk.length; i++) results.push(null); }
}
return results;
}
},
tencent: {
name: 'Tencent',
_clientKey: null,
getClientKey() {
if (this._clientKey) return this._clientKey;
this._clientKey = `browser-chrome-120.0-Windows_10-${crypto.randomUUID()}-${Date.now()}`;
return this._clientKey;
},
langCode(l) { const m = { 'zh': 'zh', 'zh-CN': 'zh', 'zh-TW': 'zh-TW' }; return m[l] || l; },
async translate(text, toLang) {
const to = this.langCode(toLang);
const r = await gmFetch({
method: 'POST', url: 'https://transmart.qq.com/api/imt',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ header: { fn: 'auto_translation', session: '', client_key: this.getClientKey(), user: '' }, type: 'plain', model_category: 'normal', text_domain: 'general', source: { lang: 'auto', text_list: [text] }, target: { lang: to } }),
});
if (r.status !== 200) throw new Error('Tencent error');
return JSON.parse(r.responseText).auto_translation[0];
},
async translateBatch(texts, toLang) {
const to = this.langCode(toLang);
const r = await gmFetch({
method: 'POST', url: 'https://transmart.qq.com/api/imt',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ header: { fn: 'auto_translation', session: '', client_key: this.getClientKey(), user: '' }, type: 'plain', model_category: 'normal', text_domain: 'general', source: { lang: 'auto', text_list: texts }, target: { lang: to } }),
});
if (r.status !== 200) throw new Error('Tencent batch error');
return JSON.parse(r.responseText).auto_translation;
}
}
};
// ══════════════════════════════════════════════════════════
// 缓存层
// ══════════════════════════════════════════════════════════
const cache = new Map();
const MAX_CACHE = 3000;
function cacheGet(t) { return cache.get(t); }
function cacheSet(t, v) { if (cache.size >= MAX_CACHE) cache.delete(cache.keys().next().value); cache.set(t, v); }
// ══════════════════════════════════════════════════════════
// 翻译核心
// ══════════════════════════════════════════════════════════
async function translate(text) {
if (!text || !text.trim()) return null;
const trimmed = text.trim();
if (/^\d+$/.test(trimmed)) return null;
const cached = cacheGet(trimmed);
if (cached) return cached;
try {
const result = await Engine[currentEngine].translate(trimmed, targetLang);
if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
} catch (e) {
// 自动降级
const fallbackEngine = currentEngine === 'google' ? 'microsoft' : 'google_legacy';
try {
const result = await Engine[fallbackEngine].translate(trimmed, targetLang);
if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
} catch (_) {}
}
return null;
}
// ══════════════════════════════════════════════════════════
// ★ 优化3: 批量翻译 — 并行分片 + pipeline
// ══════════════════════════════════════════════════════════
async function batchTranslate(texts) {
const results = new Array(texts.length).fill(null);
const uncached = [], uncachedIdx = [];
for (let i = 0; i < texts.length; i++) {
const t = texts[i].trim();
if (!t || /^\d+$/.test(t)) continue;
const c = cacheGet(t);
if (c) { results[i] = c; continue; }
uncached.push(t);
uncachedIdx.push(i);
}
if (uncached.length === 0) return results;
const engine = Engine[currentEngine];
if (engine.translateBatch && uncached.length > 1) {
try {
const BATCH_SIZE = currentEngine === 'microsoft' ? 25 : 50;
// ★ 优化: 所有分片并行发送,不再串行等待
const chunks = [];
for (let b = 0; b < uncached.length; b += BATCH_SIZE) {
chunks.push({ texts: uncached.slice(b, b + BATCH_SIZE), idxs: uncachedIdx.slice(b, b + BATCH_SIZE) });
}
await Promise.all(chunks.map(async ({ texts: chunk, idxs }) => {
try {
const batchResults = await engine.translateBatch(chunk, targetLang);
if (batchResults) {
for (let j = 0; j < batchResults.length; j++) {
if (batchResults[j] && batchResults[j] !== chunk[j]) {
cacheSet(chunk[j], batchResults[j]);
results[idxs[j]] = batchResults[j];
}
}
}
} catch (_) {}
}));
return results;
} catch (e) { /* fallthrough */ }
}
// ★ 优化: 并发数提高到8
const CONCURRENCY = 8;
for (let i = 0; i < uncached.length; i += CONCURRENCY) {
const batch = uncached.slice(i, i + CONCURRENCY);
const batchIdx = uncachedIdx.slice(i, i + CONCURRENCY);
await Promise.allSettled(batch.map(async (text, j) => {
const r = await translate(text);
if (r) results[batchIdx[j]] = r;
}));
}
return results;
}
// ══════════════════════════════════════════════════════════
// DOM遍历
// ══════════════════════════════════════════════════════════
const SKIP_TAGS = /^(script|style|code|pre|svg|math|noscript|iframe|canvas|video|audio|img|br|hr|input|select|option|textarea)$/i;
const SKIP_CLASS = /translate-ui|notranslate|katex|mathjax/i;
function shouldSkip(node) {
if (!node) return true;
if (node.nodeType === Node.ELEMENT_NODE) {
if (SKIP_TAGS.test(node.tagName)) return true;
if (SKIP_CLASS.test(node.className)) return true;
if (node.isContentEditable) return true;
if (node.dataset && node.dataset.translated) return true;
}
return false;
}
// ★ 优化: 预编译正则,避免每次调用都创建
const _langRegex = {};
function getLangRegex(lang) {
if (_langRegex[lang]) return _langRegex[lang];
const patterns = {
'zh': /^[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\d\p{P}]+$/u,
'en': /^[a-zA-Z\s\d\p{P}]+$/u,
'ja': /^[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fff\s\d\p{P}]+$/u,
'ko': /^[\uac00-\ud7af\u1100-\u11ff\s\d\p{P}]+$/u,
'ar': /^[\u0600-\u06ff\u0750-\u077f\s\d\p{P}]+$/u,
'th': /^[\u0e00-\u0e7f\s\d\p{P}]+$/u,
'ru': /^[\u0400-\u04ff\s\d\p{P}]+$/u,
};
_langRegex[lang] = patterns[lang] || null;
return _langRegex[lang];
}
function isTargetLang(text) {
if (!text || !text.trim()) return true;
const lang = targetLang.split('-')[0];
const re = getLangRegex(lang);
return re ? re.test(text.trim()) : false;
}
function collectTextNodes(root) {
const nodes = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
if (shouldSkip(node.parentElement)) return NodeFilter.FILTER_REJECT;
const text = node.textContent.trim();
if (!text || text.length < 2 || /^\d+$/.test(text)) return NodeFilter.FILTER_REJECT;
if (isTargetLang(text)) return NodeFilter.FILTER_REJECT;
if (node.parentElement?.dataset?.translated) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
}
});
while (walker.nextNode()) nodes.push(walker.currentNode);
return nodes;
}
function collectPlaceholders(root) {
return [...root.querySelectorAll('input[placeholder], textarea[placeholder]')]
.filter(el => !el.dataset.translated && el.placeholder.trim() && !isTargetLang(el.placeholder));
}
// ══════════════════════════════════════════════════════════
// ★ 优化4: 翻译执行 — 文本节点和placeholder并行翻译
// ══════════════════════════════════════════════════════════
let isTranslating = false;
// ★ 优化: 翻译队列,防止重复翻译但不丢失请求
let pendingRoot = null;
async function translatePage(root) {
if (isTranslating) {
pendingRoot = root || document.body;
return;
}
isTranslating = true;
try {
root = root || document.body;
do {
pendingRoot = null;
const textNodes = collectTextNodes(root);
const placeholders = collectPlaceholders(root);
if (textNodes.length === 0 && placeholders.length === 0) break;
// ★ 优化: 文本节点和placeholder并行翻译
const allTexts = [];
const allMeta = []; // { type: 'text'|'ph', index: number, node/el }
for (let i = 0; i < textNodes.length; i++) {
allTexts.push(textNodes[i].textContent.trim());
allMeta.push({ type: 'text', node: textNodes[i] });
}
for (let i = 0; i < placeholders.length; i++) {
allTexts.push(placeholders[i].placeholder.trim());
allMeta.push({ type: 'ph', el: placeholders[i] });
}
const results = await batchTranslate(allTexts);
for (let i = 0; i < allMeta.length; i++) {
if (!results[i]) continue;
const meta = allMeta[i];
if (meta.type === 'text') {
const parent = meta.node.parentElement;
if (!parent) continue;
if (!parent.dataset.originalText) parent.dataset.originalText = meta.node.textContent;
parent.dataset.translated = '1';
meta.node.textContent = results[i];
} else {
meta.el.dataset.originalPlaceholder = meta.el.placeholder;
meta.el.placeholder = results[i];
meta.el.dataset.translated = '1';
}
}
// 处理在翻译期间积累的新请求
if (pendingRoot) { root = pendingRoot; }
} while (pendingRoot);
} finally {
isTranslating = false;
}
}
function restorePage() {
document.querySelectorAll('[data-translated]').forEach(el => {
if (el.dataset.originalText) {
for (const child of el.childNodes) {
if (child.nodeType === Node.TEXT_NODE) { child.textContent = el.dataset.originalText; break; }
}
delete el.dataset.originalText;
}
if (el.dataset.originalPlaceholder) {
el.placeholder = el.dataset.originalPlaceholder;
delete el.dataset.originalPlaceholder;
}
delete el.dataset.translated;
});
}
// ══════════════════════════════════════════════════════════
// ★ 优化5: 动态内容监听 — 大幅缩短防抖时间
// ══════════════════════════════════════════════════════════
let lastHeight = 0;
function onScroll() {
const h = document.documentElement.scrollHeight;
if (h > lastHeight) {
lastHeight = h;
if (autoMode) translatePage();
}
}
// ★ 优化: 用 requestAnimationFrame 代替 setTimeout 防抖
let mutationRafId = null;
const pendingMutationRoots = new Set();
const observer = new MutationObserver((mutations) => {
if (!autoMode) return;
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && !shouldSkip(node)) {
pendingMutationRoots.add(node);
}
}
}
if (pendingMutationRoots.size > 0 && !mutationRafId) {
// ★ 优化: 200ms防抖(原来1000ms),用双重RAF近似一帧延迟
mutationRafId = setTimeout(() => {
mutationRafId = null;
const roots = [...pendingMutationRoots];
pendingMutationRoots.clear();
// ★ 合并:如果有多个根,一起翻译body更高效
if (roots.length > 5) {
translatePage(document.body);
} else {
roots.forEach(r => translatePage(r));
}
}, 200);
}
});
// ══════════════════════════════════════════════════════════
// UI(CSS注入和DOM创建延迟到body可用时)
// ══════════════════════════════════════════════════════════
function buildLangOptions() {
let html = '';
for (const [group, codes] of Object.entries(LANG_GROUPS)) {
html += `';
}
return html;
}
function isPageInTargetLang() {
const lang = (document.documentElement.lang || '').split('-')[0].toLowerCase();
const target = targetLang.split('-')[0].toLowerCase();
return lang === target;
}
// ══════════════════════════════════════════════════════════
// ★ 优化6: 0ms启动 — 不等DOMContentLoaded,用轮询等body
// 同时Auth在后台并行加载
// ══════════════════════════════════════════════════════════
function initWhenBodyReady() {
if (document.body) {
init();
} else {
// ★ 用RAF轮询,比DOMContentLoaded更早
requestAnimationFrame(initWhenBodyReady);
}
}
let _initialized = false;
async function init() {
if (_initialized) return;
_initialized = true;
lastHeight = document.documentElement.scrollHeight;
// 注入CSS
GM_addStyle(`
.translate-ui{position:fixed;bottom:20px;right:20px;z-index:999999;font-family:system-ui,-apple-system,sans-serif}
.translate-ui *{box-sizing:border-box;margin:0;padding:0}
.tu-btn{width:42px;height:42px;border-radius:50%;border:none;background:rgba(0,0,0,0.5);color:#fff;cursor:pointer;
display:flex;align-items:center;justify-content:center;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
box-shadow:0 2px 8px rgba(0,0,0,0.15);transition:transform .2s,background .2s;touch-action:manipulation}
.tu-btn:active{transform:scale(0.9)}
.tu-btn.active{background:rgba(34,128,255,0.8)}
.tu-panel{position:absolute;bottom:52px;right:0;width:240px;max-height:80vh;overflow-y:auto;
background:rgba(255,255,255,0.97);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.12);padding:12px;display:none;color:#333;font-size:13px}
.tu-panel.show{display:block}
.tu-panel label{display:block;margin:8px 0 4px;font-size:11px;color:#888;text-transform:uppercase;letter-spacing:.5px}
.tu-panel select{width:100%;padding:6px 8px;border:1px solid #ddd;border-radius:6px;font-size:12px;
background:#fff;color:#333;outline:none;appearance:auto}
.tu-panel select:focus{border-color:#4a9eff}
.tu-panel optgroup{font-weight:bold;color:#666;font-size:11px}
.tu-panel option{font-weight:normal;color:#333;font-size:12px}
.tu-status{margin-top:8px;padding:6px;background:#f8f8f8;border-radius:6px;font-size:11px;color:#666;text-align:center}
.tu-row{display:flex;gap:6px;margin-top:10px}
.tu-row button{flex:1;padding:7px 0;border:none;border-radius:6px;font-size:12px;cursor:pointer;
transition:background .2s;touch-action:manipulation}
.tu-row .tu-restore{background:#f0f0f0;color:#555}
.tu-row .tu-restore:active{background:#ddd}
.tu-row .tu-go{background:#4a9eff;color:#fff}
.tu-row .tu-go:active{background:#3080dd}
.tu-row .tu-exclude{background:#ff6b6b;color:#fff;font-size:11px}
.tu-row .tu-exclude:active{background:#e55}
@media(prefers-color-scheme:dark){
.tu-panel{background:rgba(30,30,30,0.97);color:#eee}
.tu-panel select{background:#2a2a2a;color:#eee;border-color:#444}
.tu-panel optgroup{color:#aaa}
.tu-panel option{color:#eee}
.tu-row .tu-restore{background:#333;color:#ccc}
.tu-status{background:#222;color:#999}
}
`);
// 注入UI
const ui = document.createElement('div');
ui.className = 'translate-ui';
ui.innerHTML = `
Ready
`;
document.body.appendChild(ui);
const btn = document.getElementById('tuBtn');
const panel = document.getElementById('tuPanel');
const engineSelect = document.getElementById('tuEngine');
const langSelect = document.getElementById('tuLang');
const statusEl = document.getElementById('tuStatus');
engineSelect.value = currentEngine;
langSelect.value = targetLang;
function setStatus(msg) { if (statusEl) statusEl.textContent = msg; }
btn.addEventListener('click', (e) => { e.stopPropagation(); panel.classList.toggle('show'); });
document.addEventListener('click', (e) => { if (!ui.contains(e.target)) panel.classList.remove('show'); });
engineSelect.addEventListener('change', () => {
currentEngine = engineSelect.value;
GM_setValue('engine', currentEngine);
cache.clear();
setStatus(`Engine: ${Engine[currentEngine]?.name || currentEngine}`);
});
langSelect.addEventListener('change', () => {
targetLang = langSelect.value;
GM_setValue('targetLang', targetLang);
cache.clear();
setStatus(`Target: ${ALL_LANGUAGES[targetLang] || targetLang}`);
});
document.getElementById('tuGo').addEventListener('click', async () => {
panel.classList.remove('show');
btn.classList.add('active');
autoMode = true;
GM_setValue('autoMode', true);
restorePage();
cache.clear();
lastHeight = document.documentElement.scrollHeight;
setStatus('翻译中...');
const start = performance.now();
await translatePage();
setStatus(`完成 (${((performance.now() - start) / 1000).toFixed(1)}s)`);
});
document.getElementById('tuRestore').addEventListener('click', () => {
panel.classList.remove('show');
btn.classList.remove('active');
autoMode = false;
GM_setValue('autoMode', false);
restorePage();
setStatus('已还原');
});
document.getElementById('tuExclude').addEventListener('click', () => {
if (!excludedHosts.includes(location.host)) {
excludedHosts.push(location.host);
GM_setValue('excludedHosts', JSON.stringify(excludedHosts));
}
restorePage();
ui.remove();
observer.disconnect();
window.removeEventListener('scroll', onScroll);
});
// 菜单
GM_registerMenuCommand('翻译当前页面', () => translatePage());
GM_registerMenuCommand('还原当前页面', () => restorePage());
GM_registerMenuCommand('切换Google引擎', () => { currentEngine = 'google'; engineSelect.value = 'google'; GM_setValue('engine', 'google'); cache.clear(); });
GM_registerMenuCommand('切换Microsoft引擎', () => { currentEngine = 'microsoft'; engineSelect.value = 'microsoft'; GM_setValue('engine', 'microsoft'); cache.clear(); });
// 启动监听
window.addEventListener('scroll', onScroll, { passive: true });
observer.observe(document.body, { childList: true, subtree: true });
// ══════════════════════════════════════════════════════════
// ★ 优化7: 0ms启动翻译 — Auth和翻译并行
// Auth在最上面已经开始加载了,这里直接开始翻译
// translatePage内部会在需要时await auth
// ══════════════════════════════════════════════════════════
if (autoMode && !isPageInTargetLang()) {
// ★ 不用setTimeout!直接用queueMicrotask确保0延迟
queueMicrotask(async () => {
setStatus('自动翻译中...');
const start = performance.now();
await translatePage();
setStatus(`完成 (${((performance.now() - start) / 1000).toFixed(1)}s)`);
});
}
}
// ★ 立即启动body轮询
initWhenBodyReady();
})();