// ==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 ഭാഷകൾ. // @namespace auto-translate-twp // @version 2.2.0 // @author YourName // @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 // @license GPL-3.0-only // @downloadURL none // ==/UserScript== (async () => { 'use strict'; try { if (document.contentType === 'application/xml') return } catch (_) {} // ═══════════════════════════════════════════════════════════════ // TWP完整移植:GoogleHelper_v2 + GoogleHelper(旧版TKK) + 全部249种语言 // ═══════════════════════════════════════════════════════════════ const deviceLang = (navigator.language || navigator.userLanguage || 'zh-CN').split('-')[0]; // ── TWP完整语言列表(249种语言,从TWP源码options.html提取)── 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": "कोंकणी", // ===== TWP v10.x 新增的114种语言(从Release日志提取)===== "ab": "Аԥсуа (Abkhaz)", "ace": "Bahsa Acèh (Acehnese)", "ach": "Lwo (Acholi)", "aa": "Qafaraf (Afar)", "alz": "Alur", "av": "Авар (Avar)", "awa": "अवधी (Awadhi)", "ban": "ᬩᬮᬶ (Balinese)", "bal": "بلوچی (Baluchi)", "bci": "Baoulé", "ba": "Башҡорт (Bashkir)", "btx": "Batak Karo", "bts": "Batak Simalungun", "bbc": "Batak Toba", "bem": "Bemba", "bew": "Betawi", "bik": "Bikol", "br": "Brezhoneg (Breton)", "bua": "Буряад (Buryat)", "yue": "粵語 (Cantonese)", "ch": "Chamoru (Chamorro)", "ce": "Нохчийн (Chechen)", "chk": "Chuukese", "cv": "Чӑваш (Chuvash)", "crh": "Qırımtatar (Crimean Tatar)", "prs": "دری (Dari)", "din": "Thuɔŋjäŋ (Dinka)", "dov": "Dombe", "dyu": "Julakan (Dyula)", "dz": "རྫོང་ཁ (Dzongkha)", "fo": "Føroyskt (Faroese)", "fj": "Na Vosa Vakaviti (Fijian)", "fon": "Fɔ̀ngbè (Fon)", "fr-CA": "Français (Canada)", "fur": "Furlan (Friulian)", "ff": "Pulaar (Fulani)", "gaa": "Gã (Ga)", "cnh": "Lai (Hakha Chin)", "hil": "Hiligaynon", "hrx": "Hunsrik", "iba": "Iban", "iu-Latn": "ᐃᓄᒃᑎᑐᑦ (Inuktut Latin)", "jam": "Jamaican Patois", "kac": "Jingpo (景颇语)", "kl": "Kalaallisut", "kr": "Kanuri", "pam": "Kapampangan", "kha": "Khasi", "cgg": "Rukiga (Kiga)", "kg": "Kikongo", "mkw": "Kituba", "trp": "Kokborok", "kv": "Коми (Komi)", "ltg": "Latgaļu (Latgalian)", "lij": "Lìgure (Ligurian)", "li": "Limburgs (Limburgish)", "lmo": "Lombard", "luo": "Dholuo (Luo)", "mad": "Madhurâ (Madurese)", "mak": "Makassar", "ms-Arab": "بهاس ملايو (Malay Jawi)", "mam": "Mam", "gv": "Gaelg (Manx)", "mh": "Kajin Majōl (Marshallese)", "mwr": "मारवाड़ी (Marwadi)", "mfe": "Kreol Morisien (Mauritian Creole)", "chm": "Марий (Meadow Mari)", "min": "Minangkabau", "nhe": "Nahuatl", "ndc-ZW": "Ndau", "nr": "isiNdebele (South Ndebele)", "new": "नेपाल भाषा (Newari)", "nqo": "ߒߞߏ (NKo)", "nus": "Thok Nath (Nuer)", "oc": "Occitan", "os": "Ирон (Ossetian)", "pag": "Pangasinan", "pap": "Papiamento", "pa-Arab": "پنجابی (Punjabi Shahmukhi)", "kek": "Qʼeqchiʼ", "rom": "Romani", "rn": "Ikirundi (Rundi)", "se": "Davvisámegiella (North Sami)", "sg": "Sängö (Sango)", "bo": "བོད་ཡིག (Tibetan)", "dsb": "Dolnoserbšćina (Lower Sorbian)", "hsb": "Hornjoserbšćina (Upper Sorbian)", "ikt": "Inuinnaqtun", "iu": "ᐃᓄᒃᑎᑐᑦ (Inuktitut)", "lzh": "文言文 (Classical Chinese)", "mvf": "ᠮᠣᠩᠭᠣᠯ (Mongolian Traditional)", "brx": "बर' (Bodo)", "hne": "छत्तीसगढ़ी (Chhattisgarhi)", "ks": "कॉशुर (Kashmiri)", "mrj": "Мары (Hill Mari)", "sa-Latn": "Sanskrit (Latin)", "sc": "Sardu (Sardinian)", "scn": "Sicilianu (Sicilian)", "szl": "Ślůnski (Silesian)", "su-Latn": "Sunda (Latin)", "tcy": "ತುಳು (Tulu)", "vec": "Vèneto (Venetian)", "war": "Winaray (Waray)", "wo": "Wolof", "zap": "Zapotec", "ms-Latn": "Malay (Latin)" }; // ── 语言代码到显示名称的简短映射(用于UI下拉菜单分组)── 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","yi"] }; // ══════════════════════════════════════════════ // TWP核心:GoogleHelper_v2 - API密钥认证管理 // 完整从TWP translationService.js移植 // ══════════════════════════════════════════════ const GoogleHelper_v2 = { _lastRequestAuthTime: null, _translateAuth: null, _authNotFound: false, _authPromise: null, get translateAuth() { return this._translateAuth; }, // TWP源码中的备用密钥(字节数组解码) _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 updateGoogleAuth = false; if (this._lastRequestAuthTime) { const date = new Date(); if (this._translateAuth) { date.setMinutes(date.getMinutes() - 20); // 有效密钥20分钟刷新 } else if (this._authNotFound) { date.setMinutes(date.getMinutes() - 5); // 未找到5分钟重试 } else { date.setMinutes(date.getMinutes() - 1); // 其他1分钟重试 } if (date.getTime() > this._lastRequestAuthTime) { updateGoogleAuth = true; } } else { updateGoogleAuth = true; } if (updateGoogleAuth) { this._lastRequestAuthTime = Date.now(); const alternativeKey = this._getAlternativeKey(); // TWP从Google的JS文件中动态提取x-goog-api-key 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', onload: (r) => { if (r.responseText && r.responseText.length > 1) { const result = r.responseText.match( /['"]x-goog-api-key['"]\s*:\s*['"](\w{39})['"]/i ); if (result && result.length === 2) { this._translateAuth = result[1]; this._authNotFound = false; } else { this._authNotFound = true; this._translateAuth = alternativeKey; } } else { this._authNotFound = true; this._translateAuth = alternativeKey; } resolve(); }, onerror: () => { this._translateAuth = alternativeKey; resolve(); }, ontimeout: () => { this._translateAuth = alternativeKey; resolve(); } }); } else { resolve(); } }); const p = this._authPromise; p.finally(() => { this._authPromise = null; }); return await p; } }; // ══════════════════════════════════════════════ // TWP核心:GoogleHelper旧版 - TKK哈希令牌计算 // 完整从TWP translationService.js移植 // ══════════════════════════════════════════════ const GoogleHelper = { googleTranslateTKK: "448487.932609646", shiftLeftOrRightThenSumOrXor(num, optString) { for (let i = 0; i < optString.length - 2; i += 3) { let acc = optString.charAt(i + 2); if ("a" <= acc) { acc = acc.charCodeAt(0) - 87; } else { acc = Number(acc); } if (optString.charAt(i + 1) === "+") { acc = num >>> acc; } else { acc = num << acc; } if (optString.charAt(i) === "+") { num = (num + acc) & 4294967295; } else { num = num ^ acc; } } return num; }, transformQuery(query) { const bytesArray = []; let idx = 0; for (let i = 0; i < query.length; i++) { let charCode = query.charCodeAt(i); if (128 > charCode) { bytesArray[idx++] = charCode; } else { if (2048 > charCode) { bytesArray[idx++] = (charCode >> 6) | 192; } else { if ( 55296 === (charCode & 64512) && i + 1 < query.length && 56320 === (query.charCodeAt(i + 1) & 64512) ) { charCode = 65536 + ((charCode & 1023) << 10) + (query.charCodeAt(++i) & 1023); bytesArray[idx++] = (charCode >> 18) | 240; bytesArray[idx++] = ((charCode >> 12) & 63) | 128; } else { bytesArray[idx++] = (charCode >> 12) | 224; } bytesArray[idx++] = ((charCode >> 6) & 63) | 128; } bytesArray[idx++] = (charCode & 63) | 128; } } return bytesArray; }, calcHash(query) { const windowTkk = this.googleTranslateTKK; const tkkSplited = windowTkk.split("."); const tkkIndex = Number(tkkSplited[0]) || 0; const tkkKey = Number(tkkSplited[1]) || 0; const bytesArray = this.transformQuery(query); let encondingRound = tkkIndex; for (const item of bytesArray) { encondingRound += item; encondingRound = this.shiftLeftOrRightThenSumOrXor( encondingRound, "+-a^+6" ); } encondingRound = this.shiftLeftOrRightThenSumOrXor( encondingRound, "+-3^+b+-f" ); encondingRound ^= tkkKey; if (encondingRound <= 0) { encondingRound = (encondingRound & 2147483647) + 2147483648; } const normalizedResult = encondingRound % 1000000; return normalizedResult.toString() + "." + (normalizedResult ^ tkkIndex); } }; // ══════════════════════════════════════════════ // TWP核心:HTML转义/反转义工具 // 从TWP translationService.js移植 // ══════════════════════════════════════════════ const Utils = { escapeHTML(text) { const div = document.createElement('div'); div.appendChild(document.createTextNode(text)); return div.innerHTML; }, unescapeHTML(text) { const doc = new DOMParser().parseFromString(text, 'text/html'); return doc.documentElement.textContent; } }; // ── 网络层 ── function gmFetch(opts) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ timeout: 30000, ...opts, onload: resolve, onerror: reject, ontimeout: reject, }); }); } // ══════════════════════════════════════════════ // 引擎定义 - 完整移植TWP的三种Google通道 + MS + Tencent // ══════════════════════════════════════════════ const Engine = { // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 引擎1:Google v2(TWP新版API - translateHtml端点) // 完整移植自TWP googleService // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ google_v2: { name: 'Google (TWP v2)', // TWP中的语言代码替换规则 _fixLang(lang) { const replacements = [ { search: "prs", replace: "fa-AF" }, ]; for (const r of replacements) { if (lang === r.search) return r.replace; } return lang; }, // TWP的cbTransformRequest:将文本数组包装为带索引的HTML _transformRequest(sourceArray) { sourceArray = sourceArray.map(text => Utils.escapeHTML(text)); if (sourceArray.length > 1) { sourceArray = sourceArray.map( (text, index) => `${text}` ); } return `
${sourceArray.join("")}
`; }, // TWP的cbTransformResponse:解析Google返回的HTML格式翻译结果 _transformResponse(result, dontSortResults) { // 移除
标签
        if (result.indexOf("
") !== -1) {
          result = result.replace("
", "");
          const index = result.indexOf(">");
          result = result.slice(index + 1);
        }

        const sentences = [];
        let idx = 0;
        while (true) {
          const sentenceStartIndex = result.indexOf("", idx);
          if (sentenceStartIndex === -1) break;
          const sentenceFinalIndex = result.indexOf("", sentenceStartIndex);
          if (sentenceFinalIndex === -1) {
            sentences.push(result.slice(sentenceStartIndex + 3));
            break;
          } else {
            sentences.push(result.slice(sentenceStartIndex + 3, sentenceFinalIndex));
          }
          idx = sentenceFinalIndex;
        }

        result = sentences.length > 0 ? sentences.join(" ") : result;
        result = result.replace(/<\/b>/g, "");

        // 提取带索引的标签
        let resultArray = [];
        let lastEndPos = 0;
        for (const r of result.matchAll(/()([^<>]*(?=<\/a>))*/g)) {
          const fullLength = r[0].length;
          const pos = r.index;
          if (pos > lastEndPos) {
            const aTag = r[1];
            const insideText = r[2] || "";
            const outsideText = result.slice(lastEndPos, pos).replace(/<\/a>/g, "");
            resultArray.push(aTag + outsideText + insideText);
          } else {
            resultArray.push(r[0]);
          }
          lastEndPos = pos + fullLength;
        }

        let indexes;
        if (resultArray && resultArray.length > 0) {
          indexes = resultArray
            .map(value => parseInt(value.match(/[0-9]+(?=>)/g)?.[0]))
            .filter(value => !isNaN(value));
          resultArray = resultArray.map(value => {
            const resultStartAtIndex = value.indexOf(">");
            return value.slice(resultStartAtIndex + 1);
          });
        } else {
          resultArray = [result];
          indexes = [0];
        }

        resultArray = resultArray.map(value => Utils.unescapeHTML(value));

        if (dontSortResults) {
          return resultArray;
        } else {
          const finalResultArray = [];
          for (const j in indexes) {
            if (finalResultArray[indexes[j]]) {
              finalResultArray[indexes[j]] += " " + resultArray[j];
            } else {
              finalResultArray[indexes[j]] = resultArray[j];
            }
          }
          return finalResultArray;
        }
      },

      async translate(text, toLang) {
        const to = this._fixLang(toLang);
        await GoogleHelper_v2.findAuth();
        if (!GoogleHelper_v2.translateAuth) throw new Error('Google auth not available');

        const requestBody = JSON.stringify([
          [[text], "auto", to],
          "te"
        ]);

        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: requestBody,
        });

        if (r.status !== 200) throw new Error(`Google v2 API error: ${r.status}`);
        const data = JSON.parse(r.responseText);

        // 解析响应 - data[0] 是翻译结果数组
        if (data && data[0]) {
          const rawResult = Array.isArray(data[0]) ? data[0][0] : data[0];
          // 通过TWP的响应解析器处理
          const parsed = this._transformResponse(rawResult, false);
          return parsed[0] || rawResult;
        }
        throw new Error('Google v2: empty response');
      },

      // TWP风格批量翻译(将多个文本打包为一个请求)
      async translateBatch(texts, toLang) {
        const to = this._fixLang(toLang);
        await GoogleHelper_v2.findAuth();
        if (!GoogleHelper_v2.translateAuth) throw new Error('Google auth not available');

        const requestBody = JSON.stringify([
          [texts, "auto", to],
          "te"
        ]);

        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: requestBody,
        });

        if (r.status !== 200) throw new Error(`Google 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 parsed = this._transformResponse(item, false);
            return parsed[0] || item;
          });
        }
        // fallback: 单条结果
        if (data && data[0]) {
          const parsed = this._transformResponse(
            Array.isArray(data[0]) ? data[0][0] : data[0], false
          );
          return [parsed[0]];
        }
        throw new Error('Google v2 batch: empty response');
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎2:Google旧版(gtx端点 + TKK哈希)
    //  从TWP旧版代码移植,作为v2的降级备份
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    google_legacy: {
      name: 'Google (Legacy)',

      langCode(lang) {
        const map = {
          'zh': 'zh-CN', 'zh-CN': 'zh-CN', 'zh-TW': 'zh-TW',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr',
          'de': 'de', 'es': 'es', 'ru': 'ru', 'pt': 'pt',
        };
        return map[lang] || lang;
      },

      async translate(text, toLang) {
        const to = this.langCode(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) {
          // 降级到gtx(无需TKK)
          return await this._translateGtx(text, to);
        }
        const data = JSON.parse(r.responseText);
        return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
      },

      async _translateGtx(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('Google gtx error');
        const data = JSON.parse(r.responseText);
        return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎3:Google自动(先v2,失败降级legacy)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    google: {
      name: 'Google (Auto)',
      async translate(text, toLang) {
        try {
          return await Engine.google_v2.translate(text, toLang);
        } catch (e) {
          console.warn('Google v2 failed, falling back to legacy:', e.message);
          return await Engine.google_legacy.translate(text, toLang);
        }
      },
      async translateBatch(texts, toLang) {
        try {
          return await Engine.google_v2.translateBatch(texts, toLang);
        } catch (e) {
          console.warn('Google v2 batch failed, falling back to single:', e.message);
          // 逐条降级
          const results = [];
          for (const text of texts) {
            try {
              results.push(await Engine.google_legacy.translate(text, toLang));
            } catch (_) {
              results.push(null);
            }
          }
          return results;
        }
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎4:Microsoft(保留原有实现)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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 error');
        this._token = r.responseText;
        this._tokenTime = Date.now();
        return this._token;
      },

      langCode(lang) {
        const map = {
          'zh': 'zh-Hans', 'zh-CN': 'zh-Hans', 'zh-TW': 'zh-Hant',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr', 'de': 'de',
          'es': 'es', 'ru': 'ru', 'pt': 'pt', 'pt-PT': 'pt-pt',
          'ar': 'ar', 'th': 'th', 'vi': 'vi', 'it': 'it', 'tr': 'tr',
          'id': 'id', 'fil': 'fil', 'ms': 'ms', 'nl': 'nl', 'pl': 'pl',
          'uk': 'uk', 'cs': 'cs', 'sk': 'sk', 'hu': 'hu', 'ro': 'ro',
          'bg': 'bg', 'hr': 'hr', 'sr': 'sr-Cyrl', 'sl': 'sl',
          'lt': 'lt', 'lv': 'lv', 'et': 'et', 'fi': 'fi', 'sv': 'sv',
          'da': 'da', 'no': 'nb', 'is': 'is', 'el': 'el', 'he': 'he',
          'hi': 'hi', 'bn': 'bn', 'ta': 'ta', 'te': 'te', 'kn': 'kn',
          'ml': 'ml', 'pa': 'pa', 'gu': 'gu', 'mr': 'mr', 'ne': 'ne',
          'ur': 'ur', 'fa': 'fa', 'ps': 'ps', 'my': 'my', 'km': 'km',
          'lo': 'lo', 'ka': 'ka', 'az': 'az', 'kk': 'kk', 'uz': 'uz',
          'mn': 'mn', 'sq': 'sq', 'mk': 'mk', 'bs': 'bs',
          'ca': 'ca', 'gl': 'gl', 'mt': 'mt', 'cy': 'cy', 'ga': 'ga',
          'af': 'af', 'sw': 'sw', 'ha': 'ha', 'ig': 'ig', 'yo': 'yo',
          'zu': 'zu', 'mg': 'mg', 'am': 'am', 'ht': 'ht',
          'bo': 'bo', 'dsb': 'dsb', 'hsb': 'hsb', 'ikt': 'ikt',
          'iu': 'iu', 'iu-Latn': 'iu-Latn', 'lzh': 'lzh',
          'fj': 'fj', 'fo': 'fo', 'fr-CA': 'fr-ca',
        };
        return map[lang] || lang;
      },

      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 translate 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 batch = 0; batch < texts.length; batch += 25) {
          const chunk = texts.slice(batch, batch + 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) {
            const data = JSON.parse(r.responseText);
            for (const item of data) {
              results.push(item.translations[0].text);
            }
          } else {
            for (let i = 0; i < chunk.length; i++) results.push(null);
          }
        }
        return results;
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎5:Tencent(保留原有实现)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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(lang) {
        const map = {
          'zh': 'zh', 'zh-CN': 'zh', 'zh-TW': 'zh-TW',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr',
          'de': 'de', 'es': 'es', 'ru': 'ru', 'pt': 'pt',
          'ar': 'ar', 'th': 'th', 'vi': 'vi', 'it': 'it',
          'tr': 'tr', 'id': 'id',
        };
        return map[lang] || lang;
      },

      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 translate 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;
      }
    }
  };

  // ── 持久化状态 ──
  let currentEngine = await GM_getValue('engine', 'google');
  let targetLang = await GM_getValue('targetLang', deviceLang === 'zh' ? 'zh-CN' : deviceLang);
  let autoMode = await GM_getValue('autoMode', true);
  let excludedHosts = JSON.parse(await GM_getValue('excludedHosts', '[]'));

  if (excludedHosts.includes(location.host)) return;

  // ── 缓存层 ──
  const cache = new Map();
  const MAX_CACHE = 3000;

  function cacheGet(text) { return cache.get(text); }
  function cacheSet(text, translated) {
    if (cache.size >= MAX_CACHE) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
    cache.set(text, translated);
  }

  // ── 翻译核心(带引擎自动降级)──
  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 engine = Engine[currentEngine];
      const result = await engine.translate(trimmed, targetLang);
      if (result && result !== trimmed) {
        cacheSet(trimmed, result);
        return result;
      }
    } catch (e) {
      console.warn(`[${currentEngine}] translate error:`, e.message);
      // 自动降级:Google失败用Microsoft,Microsoft失败用Google legacy
      if (currentEngine === 'google') {
        try {
          const result = await Engine.microsoft.translate(trimmed, targetLang);
          if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
        } catch (_) {}
      } else if (currentEngine === 'microsoft') {
        try {
          const result = await Engine.google_legacy.translate(trimmed, targetLang);
          if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
        } catch (_) {}
      }
    }
    return null;
  }

  // ── 批量翻译(利用引擎原生批量能力)──
  async function batchTranslate(texts) {
    const results = new Array(texts.length).fill(null);
    const uncached = [];
    const 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 {
        // 分批处理(每批最多50条,避免请求过大)
        const BATCH_SIZE = currentEngine === 'microsoft' ? 25 : 50;
        for (let batch = 0; batch < uncached.length; batch += BATCH_SIZE) {
          const chunk = uncached.slice(batch, batch + BATCH_SIZE);
          const chunkIdx = uncachedIdx.slice(batch, batch + BATCH_SIZE);

          const batchResults = await engine.translateBatch(chunk, targetLang);
          if (batchResults) {
            for (let j = 0; j < batchResults.length; j++) {
              const translated = batchResults[j];
              if (translated && translated !== chunk[j]) {
                cacheSet(chunk[j], translated);
                results[chunkIdx[j]] = translated;
              }
            }
          }
        }
        return results;
      } catch (e) {
        console.warn('Batch translate failed, falling back to single:', e.message);
      }
    }

    // 逐条翻译(fallback)
    const concurrency = 5;
    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 result = await translate(text);
          if (result) results[batchIdx[j]] = result;
        })
      );
    }

    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;
  }

  function isTargetLang(text) {
    if (!text || !text.trim()) return true;
    const lang = targetLang.split('-')[0];
    if (lang === 'zh') return /^[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'en') return /^[a-zA-Z\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ja') return /^[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ko') return /^[\uac00-\ud7af\u1100-\u11ff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ar') return /^[\u0600-\u06ff\u0750-\u077f\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'th') return /^[\u0e00-\u0e7f\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ru') return /^[\u0400-\u04ff\s\d\p{P}]+$/u.test(text.trim());
    return 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) return NodeFilter.FILTER_REJECT;
        if (/^\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));
  }

  // ── 页面翻译执行 ──
  let isTranslating = false;

  async function translatePage(root) {
    if (isTranslating) return;
    isTranslating = true;

    try {
      root = root || document.body;
      const textNodes = collectTextNodes(root);
      const placeholders = collectPlaceholders(root);

      if (textNodes.length === 0 && placeholders.length === 0) return;

      // 文本节点翻译
      if (textNodes.length > 0) {
        const texts = textNodes.map(n => n.textContent.trim());
        const results = await batchTranslate(texts);

        for (let i = 0; i < textNodes.length; i++) {
          if (!results[i]) continue;
          const node = textNodes[i];
          const parent = node.parentElement;
          if (!parent) continue;
          if (!parent.dataset.originalText) {
            parent.dataset.originalText = node.textContent;
          }
          parent.dataset.translated = '1';
          node.textContent = results[i];
        }
      }

      // placeholder翻译
      if (placeholders.length > 0) {
        const phTexts = placeholders.map(el => el.placeholder.trim());
        const phResults = await batchTranslate(phTexts);
        for (let i = 0; i < placeholders.length; i++) {
          if (!phResults[i]) continue;
          placeholders[i].dataset.originalPlaceholder = placeholders[i].placeholder;
          placeholders[i].placeholder = phResults[i];
          placeholders[i].dataset.translated = '1';
        }
      }
    } 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;
    });
  }

  // ── 动态内容监听 ──
  let scrollTimer = null;
  let lastHeight = document.documentElement.scrollHeight;

  function onScroll() {
    if (scrollTimer) clearTimeout(scrollTimer);
    scrollTimer = setTimeout(() => {
      const h = document.documentElement.scrollHeight;
      if (h > lastHeight) {
        lastHeight = h;
        if (autoMode) translatePage();
      }
    }, 800);
  }

  let mutationTimer = null;
  const observer = new MutationObserver((mutations) => {
    if (!autoMode) return;
    if (mutationTimer) clearTimeout(mutationTimer);
    mutationTimer = setTimeout(() => {
      const roots = new Set();
      for (const m of mutations) {
        for (const node of m.addedNodes) {
          if (node.nodeType === Node.ELEMENT_NODE && !shouldSkip(node)) {
            roots.add(node);
          }
        }
      }
      roots.forEach(root => translatePage(root));
    }, 1000);
  });

  // ══════════════════════════════════════════════
  //  UI - 支持249种语言的完整选择器
  // ══════════════════════════════════════════════

  // 构建语言选项HTML
  function buildLangOptions() {
    let html = '';
    for (const [group, codes] of Object.entries(LANG_GROUPS)) {
      html += ``;
      for (const code of codes) {
        const name = ALL_LANGUAGES[code] || code;
        const selected = code === targetLang ? ' selected' : '';
        html += ``;
      }
      html += '';
    }
    return html;
  }

  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}
    }
  `);

  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 = Date.now(); await translatePage(); setStatus(`完成 (${((Date.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(); }); // ── 启动 ── function isPageInTargetLang() { const lang = (document.documentElement.lang || '').split('-')[0].toLowerCase(); const target = targetLang.split('-')[0].toLowerCase(); return lang === target; } window.addEventListener('scroll', onScroll, { passive: true }); observer.observe(document.body, { childList: true, subtree: true }); // 预初始化Google Auth(后台静默执行) GoogleHelper_v2.findAuth().catch(() => {}); if (autoMode && !isPageInTargetLang()) { setTimeout(async () => { setStatus('自动翻译中...'); const start = Date.now(); await translatePage(); setStatus(`完成 (${((Date.now() - start) / 1000).toFixed(1)}s)`); }, 1500); } })(); (async () => { 'use strict'; try { if (document.contentType === 'application/xml') return } catch (_) {} // ═══════════════════════════════════════════════════════════════ // TWP完整移植:GoogleHelper_v2 + GoogleHelper(旧版TKK) + 全部249种语言 // ═══════════════════════════════════════════════════════════════ const deviceLang = (navigator.language || navigator.userLanguage || 'zh-CN').split('-')[0]; // ── TWP完整语言列表(249种语言,从TWP源码options.html提取)── 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": "कोंकणी", // ===== TWP v10.x 新增的114种语言(从Release日志提取)===== "ab": "Аԥсуа (Abkhaz)", "ace": "Bahsa Acèh (Acehnese)", "ach": "Lwo (Acholi)", "aa": "Qafaraf (Afar)", "alz": "Alur", "av": "Авар (Avar)", "awa": "अवधी (Awadhi)", "ban": "ᬩᬮᬶ (Balinese)", "bal": "بلوچی (Baluchi)", "bci": "Baoulé", "ba": "Башҡорт (Bashkir)", "btx": "Batak Karo", "bts": "Batak Simalungun", "bbc": "Batak Toba", "bem": "Bemba", "bew": "Betawi", "bik": "Bikol", "br": "Brezhoneg (Breton)", "bua": "Буряад (Buryat)", "yue": "粵語 (Cantonese)", "ch": "Chamoru (Chamorro)", "ce": "Нохчийн (Chechen)", "chk": "Chuukese", "cv": "Чӑваш (Chuvash)", "crh": "Qırımtatar (Crimean Tatar)", "prs": "دری (Dari)", "din": "Thuɔŋjäŋ (Dinka)", "dov": "Dombe", "dyu": "Julakan (Dyula)", "dz": "རྫོང་ཁ (Dzongkha)", "fo": "Føroyskt (Faroese)", "fj": "Na Vosa Vakaviti (Fijian)", "fon": "Fɔ̀ngbè (Fon)", "fr-CA": "Français (Canada)", "fur": "Furlan (Friulian)", "ff": "Pulaar (Fulani)", "gaa": "Gã (Ga)", "cnh": "Lai (Hakha Chin)", "hil": "Hiligaynon", "hrx": "Hunsrik", "iba": "Iban", "iu-Latn": "ᐃᓄᒃᑎᑐᑦ (Inuktut Latin)", "jam": "Jamaican Patois", "kac": "Jingpo (景颇语)", "kl": "Kalaallisut", "kr": "Kanuri", "pam": "Kapampangan", "kha": "Khasi", "cgg": "Rukiga (Kiga)", "kg": "Kikongo", "mkw": "Kituba", "trp": "Kokborok", "kv": "Коми (Komi)", "ltg": "Latgaļu (Latgalian)", "lij": "Lìgure (Ligurian)", "li": "Limburgs (Limburgish)", "lmo": "Lombard", "luo": "Dholuo (Luo)", "mad": "Madhurâ (Madurese)", "mak": "Makassar", "ms-Arab": "بهاس ملايو (Malay Jawi)", "mam": "Mam", "gv": "Gaelg (Manx)", "mh": "Kajin Majōl (Marshallese)", "mwr": "मारवाड़ी (Marwadi)", "mfe": "Kreol Morisien (Mauritian Creole)", "chm": "Марий (Meadow Mari)", "min": "Minangkabau", "nhe": "Nahuatl", "ndc-ZW": "Ndau", "nr": "isiNdebele (South Ndebele)", "new": "नेपाल भाषा (Newari)", "nqo": "ߒߞߏ (NKo)", "nus": "Thok Nath (Nuer)", "oc": "Occitan", "os": "Ирон (Ossetian)", "pag": "Pangasinan", "pap": "Papiamento", "pa-Arab": "پنجابی (Punjabi Shahmukhi)", "kek": "Qʼeqchiʼ", "rom": "Romani", "rn": "Ikirundi (Rundi)", "se": "Davvisámegiella (North Sami)", "sg": "Sängö (Sango)", "bo": "བོད་ཡིག (Tibetan)", "dsb": "Dolnoserbšćina (Lower Sorbian)", "hsb": "Hornjoserbšćina (Upper Sorbian)", "ikt": "Inuinnaqtun", "iu": "ᐃᓄᒃᑎᑐᑦ (Inuktitut)", "lzh": "文言文 (Classical Chinese)", "mvf": "ᠮᠣᠩᠭᠣᠯ (Mongolian Traditional)", "brx": "बर' (Bodo)", "hne": "छत्तीसगढ़ी (Chhattisgarhi)", "ks": "कॉशुर (Kashmiri)", "mrj": "Мары (Hill Mari)", "sa-Latn": "Sanskrit (Latin)", "sc": "Sardu (Sardinian)", "scn": "Sicilianu (Sicilian)", "szl": "Ślůnski (Silesian)", "su-Latn": "Sunda (Latin)", "tcy": "ತುಳು (Tulu)", "vec": "Vèneto (Venetian)", "war": "Winaray (Waray)", "wo": "Wolof", "zap": "Zapotec", "ms-Latn": "Malay (Latin)" }; // ── 语言代码到显示名称的简短映射(用于UI下拉菜单分组)── 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","yi"] }; // ══════════════════════════════════════════════ // TWP核心:GoogleHelper_v2 - API密钥认证管理 // 完整从TWP translationService.js移植 // ══════════════════════════════════════════════ const GoogleHelper_v2 = { _lastRequestAuthTime: null, _translateAuth: null, _authNotFound: false, _authPromise: null, get translateAuth() { return this._translateAuth; }, // TWP源码中的备用密钥(字节数组解码) _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 updateGoogleAuth = false; if (this._lastRequestAuthTime) { const date = new Date(); if (this._translateAuth) { date.setMinutes(date.getMinutes() - 20); // 有效密钥20分钟刷新 } else if (this._authNotFound) { date.setMinutes(date.getMinutes() - 5); // 未找到5分钟重试 } else { date.setMinutes(date.getMinutes() - 1); // 其他1分钟重试 } if (date.getTime() > this._lastRequestAuthTime) { updateGoogleAuth = true; } } else { updateGoogleAuth = true; } if (updateGoogleAuth) { this._lastRequestAuthTime = Date.now(); const alternativeKey = this._getAlternativeKey(); // TWP从Google的JS文件中动态提取x-goog-api-key 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', onload: (r) => { if (r.responseText && r.responseText.length > 1) { const result = r.responseText.match( /['"]x-goog-api-key['"]\s*:\s*['"](\w{39})['"]/i ); if (result && result.length === 2) { this._translateAuth = result[1]; this._authNotFound = false; } else { this._authNotFound = true; this._translateAuth = alternativeKey; } } else { this._authNotFound = true; this._translateAuth = alternativeKey; } resolve(); }, onerror: () => { this._translateAuth = alternativeKey; resolve(); }, ontimeout: () => { this._translateAuth = alternativeKey; resolve(); } }); } else { resolve(); } }); const p = this._authPromise; p.finally(() => { this._authPromise = null; }); return await p; } }; // ══════════════════════════════════════════════ // TWP核心:GoogleHelper旧版 - TKK哈希令牌计算 // 完整从TWP translationService.js移植 // ══════════════════════════════════════════════ const GoogleHelper = { googleTranslateTKK: "448487.932609646", shiftLeftOrRightThenSumOrXor(num, optString) { for (let i = 0; i < optString.length - 2; i += 3) { let acc = optString.charAt(i + 2); if ("a" <= acc) { acc = acc.charCodeAt(0) - 87; } else { acc = Number(acc); } if (optString.charAt(i + 1) === "+") { acc = num >>> acc; } else { acc = num << acc; } if (optString.charAt(i) === "+") { num = (num + acc) & 4294967295; } else { num = num ^ acc; } } return num; }, transformQuery(query) { const bytesArray = []; let idx = 0; for (let i = 0; i < query.length; i++) { let charCode = query.charCodeAt(i); if (128 > charCode) { bytesArray[idx++] = charCode; } else { if (2048 > charCode) { bytesArray[idx++] = (charCode >> 6) | 192; } else { if ( 55296 === (charCode & 64512) && i + 1 < query.length && 56320 === (query.charCodeAt(i + 1) & 64512) ) { charCode = 65536 + ((charCode & 1023) << 10) + (query.charCodeAt(++i) & 1023); bytesArray[idx++] = (charCode >> 18) | 240; bytesArray[idx++] = ((charCode >> 12) & 63) | 128; } else { bytesArray[idx++] = (charCode >> 12) | 224; } bytesArray[idx++] = ((charCode >> 6) & 63) | 128; } bytesArray[idx++] = (charCode & 63) | 128; } } return bytesArray; }, calcHash(query) { const windowTkk = this.googleTranslateTKK; const tkkSplited = windowTkk.split("."); const tkkIndex = Number(tkkSplited[0]) || 0; const tkkKey = Number(tkkSplited[1]) || 0; const bytesArray = this.transformQuery(query); let encondingRound = tkkIndex; for (const item of bytesArray) { encondingRound += item; encondingRound = this.shiftLeftOrRightThenSumOrXor( encondingRound, "+-a^+6" ); } encondingRound = this.shiftLeftOrRightThenSumOrXor( encondingRound, "+-3^+b+-f" ); encondingRound ^= tkkKey; if (encondingRound <= 0) { encondingRound = (encondingRound & 2147483647) + 2147483648; } const normalizedResult = encondingRound % 1000000; return normalizedResult.toString() + "." + (normalizedResult ^ tkkIndex); } }; // ══════════════════════════════════════════════ // TWP核心:HTML转义/反转义工具 // 从TWP translationService.js移植 // ══════════════════════════════════════════════ const Utils = { escapeHTML(text) { const div = document.createElement('div'); div.appendChild(document.createTextNode(text)); return div.innerHTML; }, unescapeHTML(text) { const doc = new DOMParser().parseFromString(text, 'text/html'); return doc.documentElement.textContent; } }; // ── 网络层 ── function gmFetch(opts) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ timeout: 30000, ...opts, onload: resolve, onerror: reject, ontimeout: reject, }); }); } // ══════════════════════════════════════════════ // 引擎定义 - 完整移植TWP的三种Google通道 + MS + Tencent // ══════════════════════════════════════════════ const Engine = { // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 引擎1:Google v2(TWP新版API - translateHtml端点) // 完整移植自TWP googleService // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ google_v2: { name: 'Google (TWP v2)', // TWP中的语言代码替换规则 _fixLang(lang) { const replacements = [ { search: "prs", replace: "fa-AF" }, ]; for (const r of replacements) { if (lang === r.search) return r.replace; } return lang; }, // TWP的cbTransformRequest:将文本数组包装为带索引的HTML _transformRequest(sourceArray) { sourceArray = sourceArray.map(text => Utils.escapeHTML(text)); if (sourceArray.length > 1) { sourceArray = sourceArray.map( (text, index) => `
${text}` ); } return `
${sourceArray.join("")}
`; }, // TWP的cbTransformResponse:解析Google返回的HTML格式翻译结果 _transformResponse(result, dontSortResults) { // 移除
标签
        if (result.indexOf("
") !== -1) {
          result = result.replace("
", "");
          const index = result.indexOf(">");
          result = result.slice(index + 1);
        }

        const sentences = [];
        let idx = 0;
        while (true) {
          const sentenceStartIndex = result.indexOf("", idx);
          if (sentenceStartIndex === -1) break;
          const sentenceFinalIndex = result.indexOf("", sentenceStartIndex);
          if (sentenceFinalIndex === -1) {
            sentences.push(result.slice(sentenceStartIndex + 3));
            break;
          } else {
            sentences.push(result.slice(sentenceStartIndex + 3, sentenceFinalIndex));
          }
          idx = sentenceFinalIndex;
        }

        result = sentences.length > 0 ? sentences.join(" ") : result;
        result = result.replace(/<\/b>/g, "");

        // 提取带索引的标签
        let resultArray = [];
        let lastEndPos = 0;
        for (const r of result.matchAll(/()([^<>]*(?=<\/a>))*/g)) {
          const fullLength = r[0].length;
          const pos = r.index;
          if (pos > lastEndPos) {
            const aTag = r[1];
            const insideText = r[2] || "";
            const outsideText = result.slice(lastEndPos, pos).replace(/<\/a>/g, "");
            resultArray.push(aTag + outsideText + insideText);
          } else {
            resultArray.push(r[0]);
          }
          lastEndPos = pos + fullLength;
        }

        let indexes;
        if (resultArray && resultArray.length > 0) {
          indexes = resultArray
            .map(value => parseInt(value.match(/[0-9]+(?=>)/g)?.[0]))
            .filter(value => !isNaN(value));
          resultArray = resultArray.map(value => {
            const resultStartAtIndex = value.indexOf(">");
            return value.slice(resultStartAtIndex + 1);
          });
        } else {
          resultArray = [result];
          indexes = [0];
        }

        resultArray = resultArray.map(value => Utils.unescapeHTML(value));

        if (dontSortResults) {
          return resultArray;
        } else {
          const finalResultArray = [];
          for (const j in indexes) {
            if (finalResultArray[indexes[j]]) {
              finalResultArray[indexes[j]] += " " + resultArray[j];
            } else {
              finalResultArray[indexes[j]] = resultArray[j];
            }
          }
          return finalResultArray;
        }
      },

      async translate(text, toLang) {
        const to = this._fixLang(toLang);
        await GoogleHelper_v2.findAuth();
        if (!GoogleHelper_v2.translateAuth) throw new Error('Google auth not available');

        const requestBody = JSON.stringify([
          [[text], "auto", to],
          "te"
        ]);

        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: requestBody,
        });

        if (r.status !== 200) throw new Error(`Google v2 API error: ${r.status}`);
        const data = JSON.parse(r.responseText);

        // 解析响应 - data[0] 是翻译结果数组
        if (data && data[0]) {
          const rawResult = Array.isArray(data[0]) ? data[0][0] : data[0];
          // 通过TWP的响应解析器处理
          const parsed = this._transformResponse(rawResult, false);
          return parsed[0] || rawResult;
        }
        throw new Error('Google v2: empty response');
      },

      // TWP风格批量翻译(将多个文本打包为一个请求)
      async translateBatch(texts, toLang) {
        const to = this._fixLang(toLang);
        await GoogleHelper_v2.findAuth();
        if (!GoogleHelper_v2.translateAuth) throw new Error('Google auth not available');

        const requestBody = JSON.stringify([
          [texts, "auto", to],
          "te"
        ]);

        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: requestBody,
        });

        if (r.status !== 200) throw new Error(`Google 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 parsed = this._transformResponse(item, false);
            return parsed[0] || item;
          });
        }
        // fallback: 单条结果
        if (data && data[0]) {
          const parsed = this._transformResponse(
            Array.isArray(data[0]) ? data[0][0] : data[0], false
          );
          return [parsed[0]];
        }
        throw new Error('Google v2 batch: empty response');
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎2:Google旧版(gtx端点 + TKK哈希)
    //  从TWP旧版代码移植,作为v2的降级备份
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    google_legacy: {
      name: 'Google (Legacy)',

      langCode(lang) {
        const map = {
          'zh': 'zh-CN', 'zh-CN': 'zh-CN', 'zh-TW': 'zh-TW',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr',
          'de': 'de', 'es': 'es', 'ru': 'ru', 'pt': 'pt',
        };
        return map[lang] || lang;
      },

      async translate(text, toLang) {
        const to = this.langCode(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) {
          // 降级到gtx(无需TKK)
          return await this._translateGtx(text, to);
        }
        const data = JSON.parse(r.responseText);
        return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
      },

      async _translateGtx(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('Google gtx error');
        const data = JSON.parse(r.responseText);
        return data[0].filter(s => s && s[0]).map(s => s[0]).join('');
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎3:Google自动(先v2,失败降级legacy)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    google: {
      name: 'Google (Auto)',
      async translate(text, toLang) {
        try {
          return await Engine.google_v2.translate(text, toLang);
        } catch (e) {
          console.warn('Google v2 failed, falling back to legacy:', e.message);
          return await Engine.google_legacy.translate(text, toLang);
        }
      },
      async translateBatch(texts, toLang) {
        try {
          return await Engine.google_v2.translateBatch(texts, toLang);
        } catch (e) {
          console.warn('Google v2 batch failed, falling back to single:', e.message);
          // 逐条降级
          const results = [];
          for (const text of texts) {
            try {
              results.push(await Engine.google_legacy.translate(text, toLang));
            } catch (_) {
              results.push(null);
            }
          }
          return results;
        }
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎4:Microsoft(保留原有实现)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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 error');
        this._token = r.responseText;
        this._tokenTime = Date.now();
        return this._token;
      },

      langCode(lang) {
        const map = {
          'zh': 'zh-Hans', 'zh-CN': 'zh-Hans', 'zh-TW': 'zh-Hant',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr', 'de': 'de',
          'es': 'es', 'ru': 'ru', 'pt': 'pt', 'pt-PT': 'pt-pt',
          'ar': 'ar', 'th': 'th', 'vi': 'vi', 'it': 'it', 'tr': 'tr',
          'id': 'id', 'fil': 'fil', 'ms': 'ms', 'nl': 'nl', 'pl': 'pl',
          'uk': 'uk', 'cs': 'cs', 'sk': 'sk', 'hu': 'hu', 'ro': 'ro',
          'bg': 'bg', 'hr': 'hr', 'sr': 'sr-Cyrl', 'sl': 'sl',
          'lt': 'lt', 'lv': 'lv', 'et': 'et', 'fi': 'fi', 'sv': 'sv',
          'da': 'da', 'no': 'nb', 'is': 'is', 'el': 'el', 'he': 'he',
          'hi': 'hi', 'bn': 'bn', 'ta': 'ta', 'te': 'te', 'kn': 'kn',
          'ml': 'ml', 'pa': 'pa', 'gu': 'gu', 'mr': 'mr', 'ne': 'ne',
          'ur': 'ur', 'fa': 'fa', 'ps': 'ps', 'my': 'my', 'km': 'km',
          'lo': 'lo', 'ka': 'ka', 'az': 'az', 'kk': 'kk', 'uz': 'uz',
          'mn': 'mn', 'sq': 'sq', 'mk': 'mk', 'bs': 'bs',
          'ca': 'ca', 'gl': 'gl', 'mt': 'mt', 'cy': 'cy', 'ga': 'ga',
          'af': 'af', 'sw': 'sw', 'ha': 'ha', 'ig': 'ig', 'yo': 'yo',
          'zu': 'zu', 'mg': 'mg', 'am': 'am', 'ht': 'ht',
          'bo': 'bo', 'dsb': 'dsb', 'hsb': 'hsb', 'ikt': 'ikt',
          'iu': 'iu', 'iu-Latn': 'iu-Latn', 'lzh': 'lzh',
          'fj': 'fj', 'fo': 'fo', 'fr-CA': 'fr-ca',
        };
        return map[lang] || lang;
      },

      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 translate 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 batch = 0; batch < texts.length; batch += 25) {
          const chunk = texts.slice(batch, batch + 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) {
            const data = JSON.parse(r.responseText);
            for (const item of data) {
              results.push(item.translations[0].text);
            }
          } else {
            for (let i = 0; i < chunk.length; i++) results.push(null);
          }
        }
        return results;
      }
    },

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  引擎5:Tencent(保留原有实现)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    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(lang) {
        const map = {
          'zh': 'zh', 'zh-CN': 'zh', 'zh-TW': 'zh-TW',
          'en': 'en', 'ja': 'ja', 'ko': 'ko', 'fr': 'fr',
          'de': 'de', 'es': 'es', 'ru': 'ru', 'pt': 'pt',
          'ar': 'ar', 'th': 'th', 'vi': 'vi', 'it': 'it',
          'tr': 'tr', 'id': 'id',
        };
        return map[lang] || lang;
      },

      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 translate 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;
      }
    }
  };

  // ── 持久化状态 ──
  let currentEngine = await GM_getValue('engine', 'google');
  let targetLang = await GM_getValue('targetLang', deviceLang === 'zh' ? 'zh-CN' : deviceLang);
  let autoMode = await GM_getValue('autoMode', true);
  let excludedHosts = JSON.parse(await GM_getValue('excludedHosts', '[]'));

  if (excludedHosts.includes(location.host)) return;

  // ── 缓存层 ──
  const cache = new Map();
  const MAX_CACHE = 3000;

  function cacheGet(text) { return cache.get(text); }
  function cacheSet(text, translated) {
    if (cache.size >= MAX_CACHE) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
    cache.set(text, translated);
  }

  // ── 翻译核心(带引擎自动降级)──
  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 engine = Engine[currentEngine];
      const result = await engine.translate(trimmed, targetLang);
      if (result && result !== trimmed) {
        cacheSet(trimmed, result);
        return result;
      }
    } catch (e) {
      console.warn(`[${currentEngine}] translate error:`, e.message);
      // 自动降级:Google失败用Microsoft,Microsoft失败用Google legacy
      if (currentEngine === 'google') {
        try {
          const result = await Engine.microsoft.translate(trimmed, targetLang);
          if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
        } catch (_) {}
      } else if (currentEngine === 'microsoft') {
        try {
          const result = await Engine.google_legacy.translate(trimmed, targetLang);
          if (result && result !== trimmed) { cacheSet(trimmed, result); return result; }
        } catch (_) {}
      }
    }
    return null;
  }

  // ── 批量翻译(利用引擎原生批量能力)──
  async function batchTranslate(texts) {
    const results = new Array(texts.length).fill(null);
    const uncached = [];
    const 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 {
        // 分批处理(每批最多50条,避免请求过大)
        const BATCH_SIZE = currentEngine === 'microsoft' ? 25 : 50;
        for (let batch = 0; batch < uncached.length; batch += BATCH_SIZE) {
          const chunk = uncached.slice(batch, batch + BATCH_SIZE);
          const chunkIdx = uncachedIdx.slice(batch, batch + BATCH_SIZE);

          const batchResults = await engine.translateBatch(chunk, targetLang);
          if (batchResults) {
            for (let j = 0; j < batchResults.length; j++) {
              const translated = batchResults[j];
              if (translated && translated !== chunk[j]) {
                cacheSet(chunk[j], translated);
                results[chunkIdx[j]] = translated;
              }
            }
          }
        }
        return results;
      } catch (e) {
        console.warn('Batch translate failed, falling back to single:', e.message);
      }
    }

    // 逐条翻译(fallback)
    const concurrency = 5;
    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 result = await translate(text);
          if (result) results[batchIdx[j]] = result;
        })
      );
    }

    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;
  }

  function isTargetLang(text) {
    if (!text || !text.trim()) return true;
    const lang = targetLang.split('-')[0];
    if (lang === 'zh') return /^[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'en') return /^[a-zA-Z\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ja') return /^[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ko') return /^[\uac00-\ud7af\u1100-\u11ff\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ar') return /^[\u0600-\u06ff\u0750-\u077f\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'th') return /^[\u0e00-\u0e7f\s\d\p{P}]+$/u.test(text.trim());
    if (lang === 'ru') return /^[\u0400-\u04ff\s\d\p{P}]+$/u.test(text.trim());
    return 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) return NodeFilter.FILTER_REJECT;
        if (/^\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));
  }

  // ── 页面翻译执行 ──
  let isTranslating = false;

  async function translatePage(root) {
    if (isTranslating) return;
    isTranslating = true;

    try {
      root = root || document.body;
      const textNodes = collectTextNodes(root);
      const placeholders = collectPlaceholders(root);

      if (textNodes.length === 0 && placeholders.length === 0) return;

      // 文本节点翻译
      if (textNodes.length > 0) {
        const texts = textNodes.map(n => n.textContent.trim());
        const results = await batchTranslate(texts);

        for (let i = 0; i < textNodes.length; i++) {
          if (!results[i]) continue;
          const node = textNodes[i];
          const parent = node.parentElement;
          if (!parent) continue;
          if (!parent.dataset.originalText) {
            parent.dataset.originalText = node.textContent;
          }
          parent.dataset.translated = '1';
          node.textContent = results[i];
        }
      }

      // placeholder翻译
      if (placeholders.length > 0) {
        const phTexts = placeholders.map(el => el.placeholder.trim());
        const phResults = await batchTranslate(phTexts);
        for (let i = 0; i < placeholders.length; i++) {
          if (!phResults[i]) continue;
          placeholders[i].dataset.originalPlaceholder = placeholders[i].placeholder;
          placeholders[i].placeholder = phResults[i];
          placeholders[i].dataset.translated = '1';
        }
      }
    } 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;
    });
  }

  // ── 动态内容监听 ──
  let scrollTimer = null;
  let lastHeight = document.documentElement.scrollHeight;

  function onScroll() {
    if (scrollTimer) clearTimeout(scrollTimer);
    scrollTimer = setTimeout(() => {
      const h = document.documentElement.scrollHeight;
      if (h > lastHeight) {
        lastHeight = h;
        if (autoMode) translatePage();
      }
    }, 800);
  }

  let mutationTimer = null;
  const observer = new MutationObserver((mutations) => {
    if (!autoMode) return;
    if (mutationTimer) clearTimeout(mutationTimer);
    mutationTimer = setTimeout(() => {
      const roots = new Set();
      for (const m of mutations) {
        for (const node of m.addedNodes) {
          if (node.nodeType === Node.ELEMENT_NODE && !shouldSkip(node)) {
            roots.add(node);
          }
        }
      }
      roots.forEach(root => translatePage(root));
    }, 1000);
  });

  // ══════════════════════════════════════════════
  //  UI - 支持249种语言的完整选择器
  // ══════════════════════════════════════════════

  // 构建语言选项HTML
  function buildLangOptions() {
    let html = '';
    for (const [group, codes] of Object.entries(LANG_GROUPS)) {
      html += ``;
      for (const code of codes) {
        const name = ALL_LANGUAGES[code] || code;
        const selected = code === targetLang ? ' selected' : '';
        html += ``;
      }
      html += '';
    }
    return html;
  }

  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}
    }
  `);

  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 = Date.now(); await translatePage(); setStatus(`完成 (${((Date.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(); }); // ── 启动 ── function isPageInTargetLang() { const lang = (document.documentElement.lang || '').split('-')[0].toLowerCase(); const target = targetLang.split('-')[0].toLowerCase(); return lang === target; } window.addEventListener('scroll', onScroll, { passive: true }); observer.observe(document.body, { childList: true, subtree: true }); // 预初始化Google Auth(后台静默执行) GoogleHelper_v2.findAuth().catch(() => {}); if (autoMode && !isPageInTargetLang()) { setTimeout(async () => { setStatus('自动翻译中...'); const start = Date.now(); await translatePage(); setStatus(`完成 (${((Date.now() - start) / 1000).toFixed(1)}s)`); }, 1500); } })();