您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
One-click fixer for mistyped Arabic↔English text in input fields and contentEditable elements.
// ==UserScript== // @name Bilingual Typing Fixer // @namespace https://greasyfork.org/en/users/1444872-tlbstation // @version 2.3 // @description One-click fixer for mistyped Arabic↔English text in input fields and contentEditable elements. // @icon https://i.ibb.co/Zpv1gJTj/logo.png // @author TLBSTATION // @match *://*/* // @grant none // @run-at document-idle // @homepage https://bilingualtypingfixer.netlify.app // @homepageURL https://bilingualtypingfixer.netlify.app // @license MIT // ==/UserScript== (function () { 'use strict'; const arToEnMap = { ض: "q", ص: "w", ث: "e", ق: "r", ف: "t", غ: "y", ع: "u", ه: "i", خ: "o", ح: "p", ج: "[", د: "]", ش: "a", س: "s", ي: "d", ب: "f", ل: "g", ا: "h", ت: "j", ن: "k", م: "l", ك: ";", ط: "'", ئ: "z", ء: "x", ؤ: "c", ر: "v", لا: "b", ى: "n", ة: "m", و: ",", ز: ".", ظ: "/", ذ: "`" }; const enToArMap = Object.fromEntries( Object.entries(arToEnMap).flatMap(([ar, en]) => [[en, ar]]) ); function detectLanguage(text) { const arabicChars = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/; const englishChars = /[a-zA-Z]/; let ar = 0, en = 0; for (let ch of text) { if (arabicChars.test(ch)) ar++; else if (englishChars.test(ch)) en++; } return ar >= en ? "arToEn" : "enToAr"; } function translate(text) { const dir = detectLanguage(text); let result = ""; if (dir === "arToEn") { text = text.replace(/لا/g, "b"); for (let ch of text) result += arToEnMap[ch] || ch; } else { for (let ch of text.toLowerCase()) result += enToArMap[ch] || ch; } return result; } let currentButton = null; let lastTarget = null; function createButton(target) { if (currentButton) currentButton.remove(); const btn = document.createElement("button"); btn.type = "button"; btn.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" style="vertical-align:middle;"> <path fill="currentColor" d="M7.5 5.6L5 7l1.4-2.5L5 2l2.5 1.4L10 2L8.6 4.5L10 7zm12 9.8L22 14l-1.4 2.5L22 19l-2.5-1.4L17 19l1.4-2.5L17 14zM22 2l-1.4 2.5L22 7l-2.5-1.4L17 7l1.4-2.5L17 2l2.5 1.4zm-8.66 10.78l2.44-2.44l-2.12-2.12l-2.44 2.44zm1.03-5.49l2.34 2.34c.39.37.39 1.02 0 1.41L5.04 22.71c-.39.39-1.04.39-1.41 0l-2.34-2.34c-.39-.37-.39-1.02 0-1.41L12.96 7.29c.39-.39 1.04-.39 1.41 0" /> </svg> Fix Text `; btn.style.position = "absolute"; btn.style.fontSize = "12px"; btn.style.background = "#222"; btn.style.color = "white"; btn.style.border = "1px solid #555"; btn.style.borderRadius = "5px"; btn.style.padding = "3px 6px"; btn.style.cursor = "pointer"; btn.style.zIndex = "9999"; btn.style.boxShadow = "0 2px 5px rgba(0,0,0,0.5)"; btn.style.display = "flex"; btn.style.gap = "5px"; btn.style.alignItems = "center"; const rect = target.getBoundingClientRect(); btn.style.top = `${Math.max(0, window.scrollY + rect.top - 30)}px`; btn.style.left = `${Math.min(window.innerWidth - 120, window.scrollX + rect.left + rect.width - 100)}px`; btn.addEventListener("mousedown", e => e.preventDefault()); btn.addEventListener("click", e => { e.preventDefault(); let text = ""; let lexicalSpans = []; if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") { text = target.value; } else if (target.isContentEditable) { lexicalSpans = target.querySelectorAll('[data-lexical-text]'); text = lexicalSpans.length ? Array.from(lexicalSpans).map(s => s.textContent).join('') : target.innerText || target.textContent || ''; } else { text = target.innerText || target.textContent || ""; } const translated = translate(text); if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") { target.value = translated; target.focus(); target.selectionStart = target.selectionEnd = translated.length; } else if (target.isContentEditable) { if (lexicalSpans.length && translated.length >= lexicalSpans.length) { lexicalSpans.forEach((span, i) => { span.textContent = translated[i] || ""; }); target.focus(); } else { target.innerText = translated; const range = document.createRange(); range.selectNodeContents(target); range.collapse(false); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); target.focus(); } } else { target.innerText = translated; target.focus(); } }); document.body.appendChild(btn); currentButton = btn; } document.addEventListener("focusin", e => { const el = e.target; if ( (el.tagName === "INPUT" && el.type === "text") || el.tagName === "TEXTAREA" || el.isContentEditable ) { lastTarget = el; createButton(el); } else { if (currentButton) { currentButton.remove(); currentButton = null; } } }); window.addEventListener("scroll", () => { if (lastTarget && currentButton) { createButton(lastTarget); } }); const observer = new MutationObserver(() => { const active = document.activeElement; if ( (active && active.tagName === "INPUT" && active.type === "text") || active.tagName === "TEXTAREA" || active.isContentEditable ) { if (active !== lastTarget) { lastTarget = active; createButton(active); } } }); observer.observe(document.body, { childList: true, subtree: true }); setInterval(() => { const active = document.activeElement; if ( (active && (active.tagName === "INPUT" && active.type === "text")) || active.tagName === "TEXTAREA" || active.isContentEditable ) { if (active !== lastTarget) { lastTarget = active; createButton(lastTarget); } } }, 1000); })();