ePubc
Senior Member
Thôi,, tốn lúa lắm.. Vẫn mặn mà với winMua đi người anh em M4 rẻ rồi

Thôi,, tốn lúa lắm.. Vẫn mặn mà với winMua đi người anh em M4 rẻ rồi

khổ cái là tui xài cái gemini lúc được lúc không, đổi key mà vẫn thế, h xài script bấm phát nào ăn phát đấySao phải khổ thế, dùng cái này là xong mà Easydict nhé
![]()
GitHub - tisfeng/Easydict: 一个简洁优雅的词典翻译 macOS App。开箱即用,支持离线 OCR 识别,支持有道词典,🍎 苹果系统词典,🍎 苹果系统翻译,OpenAI,Gemini,DeepL,Google,Bing,腾讯,百度,阿里,小牛,彩云和火山翻译。A concise and elegant Dictionary and Translator macOS App for looking up words and translating text.
一个简洁优雅的词典翻译 macOS App。开箱即用,支持离线 OCR 识别,支持有道词典,🍎 苹果系统词典,🍎 苹果系统翻译,OpenAI,Gemini,DeepL,Google,Bing,腾讯,百度,阿里,小牛,彩云和火山翻译。A concise and elegant Dictionary and Translator macOS App for looking up words an...github.com
thím đổi prompt theo nhu cầu của thím thì dịch chắc sẽ sát nghĩa hơnCách này dịch các tài liệu chuyên ngành có ok không bác. Cảm ơn bác.
Bác vô win/mac cài app tên Calibre. Cài ebook translator plugin (phải tải trên github vì bản trong calibre đã outdated). Lấy Gemini API key từ aistudio.google.com. Sửa settings của plugins như trong video cho mình.Cách này dịch các tài liệu chuyên ngành có ok không bác. Cảm ơn bác.
Dạ nghe phức tạp phết. Không biết có video nào hướng dẫn không bác nhỉBác vô win/mac cài app tên Calibre. Cài ebook translator plugin (phải tải trên github vì bản trong calibre đã outdated). Lấy Gemini API key từ aistudio.google.com. Sửa settings của plugins như trong video cho mình.
Vấn đề là dịch cuốn tầm 500 trang dày thì cần 2-3 api key. Bạn lên taphoammo mua google account giaz 30 đồng 1 acc. Vô trình duyệt khách cài Eban VPN trong kho Chrome extension. Bật VPN login account rẻ vô lấy api. Lấy xong xóa cache, dữ liệu trình duyệt đó đi. Spawn cách này để có vô hạn api key rẻ, nhớ chú ý mỗi lần như vậy nên đổi VPN quốc gia khác nhau.
Treo máy cho AI dịch form sách đồ ok luôn. AI dịch được tầm 200 trang nó sẽ treo không dịch nữa (chờ 15' để check key chết chưa vì Gemini có rate per limit cho mỗi model khác nhau, con gemini 2.0 pro lite có 4000rpm, flash thinking có 1500rpm,... check trong aistudio), khi này bạn out ra chèn key api khác vào Plugin của Calibre, tiếp tục dịch. Nhược điểm duy nhất của cách này là nếu sách có nhiều hình ảnh thì sẽ gây lỗi form mỗi chút. Còn lại thì đa phần trả form rất ok nhé.
Bạn cũng có thể vứt file vô aistudio để cho nó dịch (chỉ có con gemini flash 2.0 là cho dịch thoải mái) vì gemini đa phần model có tới 2 triệu tokens.
Lí do Gemini là vì key API của nó spawn được, mua cũng rẻ nhất trong dàn AI uy tín r và trong livebench (AI benchmark có thể coi là tiêu chuẩn của ngành công nghiệp) thì Gemini bản mới nhất ở trong top 3, tokens cao, hack được.
Gửi qua zalo nha bác, voz bị sao k xuất video lén đây được.Dạ nghe phức tạp phết. Không biết có video nào hướng dẫn không bác nhỉ
làm sao sài được trên mobile zạ Bác, chỉ em vớimình chỉnh rồi thêm luôn nút biểu tượng nhỏ để dịch trên cả Pc lẫn Moblie (vẫn hỗ trợ phím tắt).
1. select text rồi nhấn nút dịch sẽ tương đương: Ctrl+t
2. select text rồi giữ nút dịch (trên 500ms) sẽ tương đương phím tắt Alt+q
ai cần thì thêm nhé.
JavaScript:// ==UserScript== // @name Gemini AI Inline Translator (Popup) // @namespace Violentmonkey Scripts // @version 2.8 // @description Dịch văn bản bôi đen bằng Google Gemini API, có phím tắt, sửa lỗi ký tự, đảm bảo chỉ dịch một lần và có nút đóng. Cải thiện chất lượng dịch. Hỗ trợ phân tích từ vựng (Alt + M) hiển thị trong popup và dịch nhanh (Alt + T). // @author Voodanh, king1x32 // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript== (function () { "use strict"; // Cấu hình API const API_CONFIG = { providers: { gemini: { url: (apiKey) => `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite-preview-02-05:generateContent?key=${apiKey}`, headers: { "Content-Type": "application/json", }, body: (prompt) => ({ contents: [ { role: "user", parts: [{ text: prompt }], }, ], generationConfig: { temperature: 0.7 }, }), responseParser: (response) => { if (!response.candidates || response.candidates.length === 0) { throw new Error("Gemini API: No candidates in response"); } if (!response.candidates[0].content?.parts?.[0]?.text) { throw new Error("Gemini API: Invalid response format"); } return response.candidates[0].content.parts[0].text; }, }, openai: { url: () => "https://api.groq.com/openai/v1/chat/completions", headers: (apiKey) => ({ "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }), body: (prompt) => ({ model: "llama-3.3-70b-versatile", messages: [{ role: "user", content: prompt }], temperature: 0.7, }), responseParser: (response) => response.choices?.[0]?.message?.content, }, }, currentProvider: "gemini", apiKey: "AIzaSCqCxFH32-luLxrdPH9p5FEmxk", // Gemini maxRetries: 3, retryDelay: 1000, rateLimit: { maxRequests: 5, perMilliseconds: 10000, }, }; // Biến trạng thái let isTranslating = false; let requestQueue = []; let requestCount = 0; let lastRequestTime = 0; // Add CSS for the draggable popup GM_addStyle(` .draggable { cursor: move; } `); // Hàm gọi API Gemini để dịch văn bản async function translateText( text, targetElement, isAdvanced = false, displaySimple = false, ) { // Thêm vào hàng đợi và xử lý tuần tự return new Promise((resolve) => { requestQueue.push(async () => { try { if (isTranslating) return; isTranslating = true; // Kiểm tra rate limiting const now = Date.now(); if (now - lastRequestTime < API_CONFIG.rateLimit.perMilliseconds) { if (requestCount >= API_CONFIG.rateLimit.maxRequests) { const delay = API_CONFIG.rateLimit.perMilliseconds - (now - lastRequestTime); await new Promise((res) => setTimeout(res, delay)); requestCount = 0; } } else { requestCount = 0; lastRequestTime = now; } // Tạo prompt let prompt = `Cho bạn đoạn văn bản: "${text}". Hãy dịch đoạn văn bản đó thành Tiếng Việt (Vietnamese) với các điều kiện sau: - Tuân thủ chặt chẽ bối cảnh và sắc thái ban đầu. - Sự lưu loát tự nhiên như người bản xứ. - Không có thêm giải thích/diễn giải. - Bảo toàn thuật ngữ 1:1 cho các thuật ngữ/danh từ riêng. Chỉ in ra bản dịch mà không có dấu ngoặc kép.`; if (isAdvanced) { prompt = `Dịch và phân tích từ khóa: "${text}"`; } const provider = API_CONFIG.providers[API_CONFIG.currentProvider]; let translatedText = ""; let attempts = 0; // Retry logic với exponential backoff while (attempts < API_CONFIG.maxRetries) { try { translatedText = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: provider.url(API_CONFIG.apiKey), headers: typeof provider.headers === "function" ? provider.headers(API_CONFIG.apiKey) : provider.headers, data: JSON.stringify(provider.body(prompt)), onload: function (response) { if (response.status >= 200 && response.status < 300) { const result = JSON.parse(response.responseText); const text = provider.responseParser(result); text ? resolve(text) : reject("Invalid response format"); } else if (response.status === 429) { reject("Rate limit exceeded"); } else { reject(`API Error: ${response.status}`); } }, onerror: function (error) { reject(`Connection error: ${error}`); }, }); }); requestCount++; break; // Thoát vòng lặp nếu thành công } catch (error) { attempts++; if (attempts >= API_CONFIG.maxRetries) throw error; await new Promise((res) => setTimeout(res, API_CONFIG.retryDelay * Math.pow(2, attempts)), ); } } // Hiển thị kết quả if (isAdvanced) { displayPopup(translatedText, text); } else if (displaySimple) { displaySimplePopup(text, translatedText); } else { showTranslationBelow(targetElement, translatedText); } } catch (error) { console.error("Translation failed:", error); const errorMessage = error instanceof Error ? error.message : String(error); showErrorBelow( targetElement, errorMessage.includes("Rate limit") ? "Vui lòng chờ giữa các lần dịch" : errorMessage.includes("Gemini API") ? "Lỗi Gemini: " + errorMessage : errorMessage.includes("API Key") ? "Lỗi xác thực API" : "Lỗi dịch thuật: " + errorMessage, ); } finally { isTranslating = false; requestQueue.shift(); if (requestQueue.length > 0) requestQueue[0](); } }); if (!isTranslating && requestQueue.length === 1) { requestQueue[0](); } }); } // Hàm hiển thị bản dịch ngay bên dưới đoạn văn bản được bôi đen function showTranslationBelow(targetElement, translatedText) { // Tìm đoạn văn cuối cùng được bôi đen const selection = window.getSelection(); const lastSelectedNode = selection.focusNode; let lastSelectedParagraph = lastSelectedNode.parentElement; // Đảm bảo phần tử cuối cùng là một đoạn văn while (lastSelectedParagraph && lastSelectedParagraph.tagName !== "P") { lastSelectedParagraph = lastSelectedParagraph.parentElement; } // Nếu không tìm thấy, sử dụng targetElement (đoạn đầu tiên) if (!lastSelectedParagraph) { lastSelectedParagraph = targetElement; } // Kiểm tra xem đã có bản dịch nào được hiển thị chưa if ( lastSelectedParagraph.nextElementSibling && lastSelectedParagraph.nextElementSibling.classList.contains( "translation-div", ) ) { return; // Nếu đã có bản dịch, không hiển thị thêm } const translationDiv = document.createElement("div"); translationDiv.classList.add("translation-div"); // Thêm class để nhận diện translationDiv.style.marginTop = "10px"; translationDiv.style.padding = "10px"; translationDiv.style.backgroundColor = "#f0f0f0"; translationDiv.style.borderLeft = "3px solid #4CAF50"; translationDiv.style.color = "#333"; translationDiv.style.position = "relative"; // Thêm thuộc tính position: relative // Thiết lập font chữ và cỡ chữ translationDiv.style.fontFamily = "SF Pro Rounded, sans-serif"; translationDiv.style.fontSize = "16px"; // Cỡ chữ 16px (có thể điều chỉnh từ 15-17px) translationDiv.textContent = `Dịch: ${translatedText}`; // Thêm nút đóng (nút "x") const closeButton = document.createElement("span"); closeButton.textContent = "x"; closeButton.style.position = "absolute"; closeButton.style.top = "5px"; closeButton.style.right = "5px"; closeButton.style.cursor = "pointer"; closeButton.style.color = "#999"; closeButton.style.fontSize = "14px"; closeButton.style.fontWeight = "bold"; closeButton.addEventListener("click", function () { translationDiv.remove(); }); translationDiv.appendChild(closeButton); lastSelectedParagraph.parentNode.insertBefore( translationDiv, lastSelectedParagraph.nextSibling, ); } // Function to display the summary on the webpage with dynamic width and scrollable content function displayPopup(translatedText, originalText) { const summaryDiv = document.createElement("div"); summaryDiv.classList.add("draggable"); summaryDiv.style.position = "fixed"; summaryDiv.style.top = "50%"; summaryDiv.style.left = "50%"; summaryDiv.style.transform = "translate(-50%, -50%)"; summaryDiv.style.backgroundColor = "#fff"; summaryDiv.style.border = "1px solid #ccc"; summaryDiv.style.padding = "20px"; summaryDiv.style.zIndex = "2147483647"; summaryDiv.style.width = "90vw"; // Responsive width summaryDiv.style.maxHeight = "80vh"; // Responsive height summaryDiv.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.1)"; summaryDiv.style.borderRadius = "15px"; summaryDiv.style.fontFamily = "SF Pro Rounded, Arial, sans-serif"; summaryDiv.style.fontSize = "16px"; // Font size 16px summaryDiv.style.display = "flex"; summaryDiv.style.flexDirection = "column"; summaryDiv.style.overflowY = "auto"; // Enable vertical scrolling // Add summary section with scrollable content const summarySection = document.createElement("div"); const cleanedSummary = translatedText.replace( /(\*\*)(.*?)\1/g, "<b>$2</b>", ); // Remove ** markers const formattedSummary = cleanedSummary .split("<br>") .map((line) => { if (line.startsWith("<b>KEYWORD</b>:")) { return `<h4 style="margin-bottom: 5px;">${line}</h4>`; } else if (line.startsWith("+ Định nghĩa:")) { const parts = line.split(":"); if (parts.length > 1) { const definition = parts.slice(1).join(":").trim(); const definitionParts = definition.split( "<br> - Bản dịch định nghĩa:", ); if (definitionParts.length === 2) { return `<p style="margin-left: 20px; margin-bottom: 5px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">+ Định nghĩa:<br>${definitionParts[0].trim()}</p><p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;"> - Bản dịch định nghĩa: ${definitionParts[1].trim()}</p>`; } } return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } else if (line.startsWith("+ Ví dụ:")) { const parts = line.split(":"); if (parts.length > 1) { const example = parts.slice(1).join(":").trim(); const exampleParts = example.split("<br> - Bản dịch ví dụ:"); if (exampleParts.length === 2) { // Replace example with a sentence from originalText if available const keywordMatch = line.match(/<b>KEYWORD<\/b>:\s*(\w+)/i); let exampleFromText = null; if (keywordMatch) { const keyword = keywordMatch[1]; const regex = new RegExp(`\\b${keyword}\\b`, "i"); const sentences = originalText .split(/[.?!]/) .filter((sentence) => regex.test(sentence)); if (sentences.length > 0) exampleFromText = sentences[0].trim() + "."; } const displayExample = exampleFromText ? exampleFromText : exampleParts[0].trim(); return `<p style="margin-left: 20px; margin-bottom: 5px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">+ Ví dụ: ${displayExample}</p><p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;"> - Bản dịch ví dụ: ${exampleParts[1].trim()}</p>`; } } return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } else { return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } }) .join(""); summarySection.innerHTML = `<h3 style="color: #333;">Dịch</h3><div style="overflow-y: auto; max-height: 400px; color: #555; font-size: 16px;">${formattedSummary}</div>`; // Font size 16px and lower max-height summaryDiv.appendChild(summarySection); // Add close button const closeButton = document.createElement("button"); closeButton.innerText = "Đóng"; closeButton.style.marginTop = "10px"; closeButton.style.padding = "8px 16px"; closeButton.style.backgroundColor = "#ff4444"; closeButton.style.color = "#fff"; closeButton.style.border = "none"; closeButton.style.borderRadius = "4px"; closeButton.style.cursor = "pointer"; closeButton.onclick = () => summaryDiv.remove(); summaryDiv.appendChild(closeButton); makeDraggable(summaryDiv); document.body.appendChild(summaryDiv); } // Function to display the simple translation popup function displaySimplePopup(originalText, translatedText) { const simplePopupDiv = document.createElement("div"); simplePopupDiv.classList.add("draggable"); simplePopupDiv.style.position = "fixed"; simplePopupDiv.style.top = "50%"; simplePopupDiv.style.left = "50%"; simplePopupDiv.style.transform = "translate(-50%, -50%)"; simplePopupDiv.style.backgroundColor = "#fff"; simplePopupDiv.style.border = "1px solid #ccc"; simplePopupDiv.style.padding = "20px"; simplePopupDiv.style.zIndex = "2147483647"; simplePopupDiv.style.width = "90vw"; // Responsive width for simple popup simplePopupDiv.style.maxHeight = "80vh"; // Responsive height for simple popup simplePopupDiv.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.1)"; simplePopupDiv.style.borderRadius = "15px"; simplePopupDiv.style.fontFamily = "SF Pro Rounded, Arial, sans-serif"; simplePopupDiv.style.fontSize = "16px"; // Font size 16px simplePopupDiv.style.display = "flex"; simplePopupDiv.style.flexDirection = "column"; simplePopupDiv.style.overflowY = "auto"; // Enable vertical scrolling if needed // Translated Text Section (Only translation, no original text) const translatedTextSection = document.createElement("div"); translatedTextSection.innerHTML = `<div style="white-space: pre-wrap; word-wrap: break-word; text-align: justify; font-size: 16px; overflow-y: auto; max-height: calc(100% - 50px); padding-right: 10px;">${translatedText}</div>`; // Increased font size, added scroll and padding simplePopupDiv.appendChild(translatedTextSection); // Close Button const closeButton = document.createElement("button"); closeButton.innerText = "Đóng"; closeButton.style.marginTop = "10px"; closeButton.style.padding = "8px 16px"; closeButton.style.backgroundColor = "#ff4444"; closeButton.style.color = "#fff"; closeButton.style.border = "none"; closeButton.style.borderRadius = "4px"; closeButton.style.cursor = "pointer"; closeButton.onclick = () => simplePopupDiv.remove(); simplePopupDiv.appendChild(closeButton); makeDraggable(simplePopupDiv); document.body.appendChild(simplePopupDiv); } // Cache các bản dịch const translationCache = new Map(); const CACHE_EXPIRATION = 300000; // 5 phút // Hàm thêm vào cache function addToCache(original, translated) { translationCache.set(original, { text: translated, timestamp: Date.now(), }); } // Hàm kiểm tra cache function checkCache(text) { const entry = translationCache.get(text); if (entry && Date.now() - entry.timestamp < CACHE_EXPIRATION) { return entry.text; } translationCache.delete(text); return null; } // Hàm hiển thị lỗi function showErrorBelow(targetElement, errorMessage) { // Tìm đoạn văn cuối cùng được bôi đen (tương tự như trên) const selection = window.getSelection(); const lastSelectedNode = selection.focusNode; let lastSelectedParagraph = lastSelectedNode.parentElement; while (lastSelectedParagraph && lastSelectedParagraph.tagName !== "P") { lastSelectedParagraph = lastSelectedParagraph.parentElement; } if (!lastSelectedParagraph) { lastSelectedParagraph = targetElement; } if ( lastSelectedParagraph.nextElementSibling && lastSelectedParagraph.nextElementSibling.classList.contains( "translation-div", ) ) { return; } const errorDiv = document.createElement("div"); errorDiv.classList.add("translation-div"); errorDiv.style.marginTop = "10px"; errorDiv.style.padding = "10px"; errorDiv.style.backgroundColor = "#fdd"; errorDiv.style.borderLeft = "3px solid #faa"; errorDiv.style.color = "#a00"; errorDiv.style.fontFamily = "SF Pro Rounded Medium, sans-serif"; errorDiv.style.fontSize = "16px"; errorDiv.style.position = "relative"; // Thêm thuộc tính position: relative errorDiv.textContent = errorMessage; // Thêm nút đóng (nút "x") const closeButton = document.createElement("span"); closeButton.textContent = "x"; closeButton.style.position = "absolute"; closeButton.style.top = "5px"; closeButton.style.right = "5px"; closeButton.style.cursor = "pointer"; closeButton.style.color = "#999"; closeButton.style.fontSize = "14px"; closeButton.style.fontWeight = "bold"; closeButton.addEventListener("click", function () { errorDiv.remove(); }); errorDiv.appendChild(closeButton); lastSelectedParagraph.parentNode.insertBefore( errorDiv, lastSelectedParagraph.nextSibling, ); } // Function to make the summary popup draggable function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // get the mouse cursor position at startup pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // call a function whenever the cursor moves document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // set the element's new position: element.style.top = element.offsetTop - pos2 + "px"; element.style.left = element.offsetLeft - pos1 + "px"; } function closeDragElement() { // stop moving when mouse button is released: document.onmouseup = null; document.onmousemove = null; } } // Biến lưu trữ nút dịch hiện tại let currentTranslateButton = null; let touchStartTime = 0; let longPressTimeout; // Hàm tạo nút dịch function createTranslateButton(selection) { if (currentTranslateButton) { currentTranslateButton.remove(); } const buttonDich = document.createElement("button"); buttonDich.textContent = "Dịch"; buttonDich.style.position = "fixed"; buttonDich.style.backgroundColor = "#007BFF"; buttonDich.style.color = "#fff"; buttonDich.style.border = "none"; buttonDich.style.borderRadius = "3px"; buttonDich.style.padding = "5px 10px"; buttonDich.style.cursor = "pointer"; buttonDich.style.zIndex = "2147483647"; buttonDich.style.fontSize = "14px"; const rect = selection.getRangeAt(0).getBoundingClientRect(); const buttonHeight = 30; buttonDich.style.top = `${rect.bottom + buttonHeight + window.scrollY}px`; buttonDich.style.left = `${rect.left + window.scrollX}px`; let pressTimer; let isLongPress = false; // Xử lý sự kiện bắt đầu nhấn const handlePressStart = (e) => { isLongPress = false; pressTimer = setTimeout(() => { isLongPress = true; const selectedText = selection.toString().trim(); const targetElement = selection.anchorNode.parentElement; translateText(selectedText, targetElement, true); // Dịch nâng cao khi giữ nút dịch buttonDich.remove(); currentTranslateButton = null; }, 500); // 500ms để xác định là giữ nút }; // Xử lý sự kiện kết thúc nhấn const handlePressEnd = (e) => { clearTimeout(pressTimer); if (!isLongPress) { const selectedText = selection.toString().trim(); const targetElement = selection.anchorNode.parentElement; translateText(selectedText, targetElement, false, true); // Dịch nhanh popup khi nhấn nút dịch buttonDich.remove(); currentTranslateButton = null; } }; // Xử lý sự kiện di chuyển ra khỏi nút const handlePressCancel = () => { clearTimeout(pressTimer); }; // Thêm các event listener cho desktop buttonDich.addEventListener("mousedown", handlePressStart); buttonDich.addEventListener("mouseup", handlePressEnd); buttonDich.addEventListener("mouseleave", handlePressCancel); // Thêm các event listener cho mobile buttonDich.addEventListener("touchstart", (e) => { e.preventDefault(); // Ngăn chặn các hành vi mặc định handlePressStart(e); }); buttonDich.addEventListener("touchend", (e) => { e.preventDefault(); handlePressEnd(e); }); buttonDich.addEventListener("touchcancel", handlePressCancel); document.body.appendChild(buttonDich); currentTranslateButton = buttonDich; } // Xử lý sự kiện chọn văn bản function handleTextSelection(event) { // Đợi một chút để selection được cập nhật setTimeout(() => { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (selectedText.length > 0) { createTranslateButton(selection); } else if (currentTranslateButton) { currentTranslateButton.remove(); currentTranslateButton = null; } }, 100); } // Xử lý sự kiện touch trên mobile document.addEventListener("touchstart", (e) => { touchStartTime = Date.now(); longPressTimeout = setTimeout(() => { handleTextSelection(e); }, 500); // Đợi 500ms để xác định là long press }); document.addEventListener("touchend", (e) => { clearTimeout(longPressTimeout); if (Date.now() - touchStartTime < 500) { // Nếu không phải long press, xử lý như click thường handleTextSelection(e); } }); // Xử lý sự kiện mouse trên desktop document.addEventListener("mouseup", handleTextSelection); // Thêm style để đảm bảo nút dịch hiển thị đúng trên mobile GM_addStyle(` @media (max-width: 768px) { buttonDich { touch-action: manipulation; -webkit-tap-highlight-color: transparent; user-select: none; } } `); // Lắng nghe sự kiện phím tắt (Ctrl + Q hoặc Cmd + Q, Alt + Q và Alt + T) document.addEventListener("keydown", function (event) { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (!selectedText) return; const targetElement = selection.anchorNode.parentElement; if ((event.ctrlKey || event.metaKey) && event.key === "q") { event.preventDefault(); // Ngăn chặn hành động mặc định của trình duyệt translateText(selectedText, targetElement); } else if (event.altKey && event.key === "q") { event.preventDefault(); translateText(selectedText, targetElement, true); // Dịch nâng cao khi ấn Alt + Q } else if (event.altKey && event.key === "t") { event.preventDefault(); translateText(selectedText, targetElement, false, true); // Dịch nhanh popup khi ấn Alt + T } }); })();
Thay đổi qua dịch tiếng trung kiểu gì nhỉ bHôm bữa mình có lên bài tìm app dịch giống Qtranslate nhưng không thấy ổn cái nào.
Do đó mình nhờ đến AI để nó giúp tìm giải pháp.
Đầu tiên, mọi người tải Violenmonkey về vì mình sẽ tạo scripts để dùng API của AI Studio để dịch (free 100%)
Tiếp đó mọi người dùng đoạn scripts bến dưới add vào, thay API và địa chỉ API rồi sau đó sử dụng.
*Lưu ý: Mình không biết gì về code, chỉ lên ý tưởng để AI làm và đến giờ dùng thì thấy ổn nên nếu hỏi chuyên môn code thì mình ko biết TL. MN có thể thêm ý tưởng vào các AI rồi bắt nó sửa cho mình.
Mọi người có thể lấy API tại đây hoặc gõ vào gg AI Studio (CỦa google nên ko lo virus j nhé)
"Sign in - Google Accounts (https://aistudio.google.com/apikey)"
Mình tạo ra 3 nút phím tắt:
Xem tệp đính kèm 2913915 (CTRL + m : Tạo 1 bản dịch bên dưới bản tiếng anh)
Xem tệp đính kèm 2913916 (ALT + m : Cái này mụcđíchđể học tiếng anh, sẽ tạo popup dịch, xácđịnh keywords , coi nhưđể học từ mới)
Cách dùng scripts này để dịch file pdf (theo đoạn bôi đen, không dịch được cả file 1 lần)
Xem tệp đính kèm 2913919 ( ALT + t :Giống CTRL m nhưng tạo popup)
Tuy nhiên, với các file pdf như mình dùng edge sẽ không cho dùng extention nên mọi người dùng link này để mở file pdf, sẽ cho phép dùng extension.
"PDF.js viewer (https://pdf.translatewebpages.org/pdf.js/web/viewer)"
Mã:// ==UserScript== // @name Gemini AI Inline Translator (Popup) // @namespace http://tampermonkey.net/ // @version 2.8 // @description Dịch văn bản bôi đen bằng Google Gemini API, có phím tắt, sửa lỗi ký tự, đảm bảo chỉ dịch một lần và có nút đóng. Cải thiện chất lượng dịch. Hỗ trợ phân tích từ vựng (Alt + M) hiển thị trong popup và dịch nhanh (Alt + T). // @author Your Name // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; // API Key của bạn const API_KEY = 'THAY API KEY VÀO ĐÂY NH MN'; // Thay thế bằng API Key của bạn const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${API_KEY}`; // Biến để theo dõi trạng thái dịch let isTranslating = false; // Add CSS for the draggable popup GM_addStyle(` .draggable { cursor: move; } `); // Hàm gọi API Gemini để dịch văn bản function translateText(text, targetElement, isAdvanced = false, displaySimple = false) { if (isTranslating) return; // Ngăn chặn dịch trùng lặp isTranslating = true; // Prompt cải tiến để dịch tự nhiên hơn, không word-by-word let prompt = `Translate the following text to Vietnamese in a natural and fluent way, avoiding word-by-word translation. Focus on conveying the meaning and intent of the original text, rather than a literal conversion: "${text}"`; if (isAdvanced) { prompt = `Translate the following text to Vietnamese in a natural and fluent way, avoiding word-by-word translation. After that, analyze the following text and select 2-5 relevant keywords. For each keyword, provide the definition from the Oxford Dictionary, along with its part of speech, a Vietnamese translation of the definition, synonyms (separated by commas), antonyms (separated by commas). Select an example sentence for each keyword from the original text and provide a Vietnamese translation of that example sentence. Format the output as follows: <br><b>KEYWORD</b>: Keyword<br> + Định nghĩa:<br>Definition from Oxford dictionary<br> - Bản dịch định nghĩa: vietnamese definition <br> + Loại từ: Part of speech <br> + Từ đồng nghĩa: synonym1, synonym2 <br> + Từ trái nghĩa: antonym1, antonym2 <br> + Ví dụ: example sentence from original text<br> - Bản dịch ví dụ: example sentence translation <br> Text to analyze: "${text}"`; } const requestBody = { contents: [ { role: "user", parts: [ { text: prompt, }, ], }, ], generationConfig: { temperature: 0.7, } }; GM_xmlhttpRequest({ method: 'POST', url: API_URL, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify(requestBody), onload: function (response) { if (response.status >= 200 && response.status < 300) { const result = JSON.parse(response.responseText); if (result.candidates && result.candidates[0].content.parts[0].text) { const translatedText = result.candidates[0].content.parts[0].text; if(isAdvanced) { displayPopup(translatedText, text); // Show in Popup for Alt+M } else if (displaySimple) { displaySimplePopup(text, translatedText); // Show simple Popup for Alt+T } else { showTranslationBelow(targetElement, translatedText); // Show Inline for Ctrl+M } } else { console.error('Lỗi dịch thuật:', result); showErrorBelow(targetElement, "Lỗi dịch thuật"); } } else { console.error('Lỗi API:', response.status, response.statusText); showErrorBelow(targetElement, "Lỗi API: " + response.status); } isTranslating = false; // Đặt lại trạng thái dịch }, onerror: function (error) { console.error('Lỗi kết nối API:', error); showErrorBelow(targetElement, "Lỗi kết nối"); isTranslating = false; // Đặt lại trạng thái dịch }, }); } // Hàm hiển thị bản dịch ngay bên dưới đoạn văn bản được bôi đen function showTranslationBelow(targetElement, translatedText) { // Tìm đoạn văn cuối cùng được bôi đen const selection = window.getSelection(); const lastSelectedNode = selection.focusNode; let lastSelectedParagraph = lastSelectedNode.parentElement; // Đảm bảo phần tử cuối cùng là một đoạn văn while (lastSelectedParagraph && lastSelectedParagraph.tagName !== 'P') { lastSelectedParagraph = lastSelectedParagraph.parentElement; } // Nếu không tìm thấy, sử dụng targetElement (đoạn đầu tiên) if (!lastSelectedParagraph) { lastSelectedParagraph = targetElement; } // Kiểm tra xem đã có bản dịch nào được hiển thị chưa if (lastSelectedParagraph.nextElementSibling && lastSelectedParagraph.nextElementSibling.classList.contains('translation-div')) { return; // Nếu đã có bản dịch, không hiển thị thêm } const translationDiv = document.createElement('div'); translationDiv.classList.add('translation-div'); // Thêm class để nhận diện translationDiv.style.marginTop = '10px'; translationDiv.style.padding = '10px'; translationDiv.style.backgroundColor = '#f0f0f0'; translationDiv.style.borderLeft = '3px solid #4CAF50'; translationDiv.style.color = '#333'; translationDiv.style.position = 'relative'; // Thêm thuộc tính position: relative // Thiết lập font chữ và cỡ chữ translationDiv.style.fontFamily = 'SF Pro Rounded, sans-serif'; translationDiv.style.fontSize = '16px'; // Cỡ chữ 16px (có thể điều chỉnh từ 15-17px) translationDiv.textContent = `Dịch: ${translatedText}`; // Thêm nút đóng (nút "x") const closeButton = document.createElement('span'); closeButton.textContent = 'x'; closeButton.style.position = 'absolute'; closeButton.style.top = '5px'; closeButton.style.right = '5px'; closeButton.style.cursor = 'pointer'; closeButton.style.color = '#999'; closeButton.style.fontSize = '14px'; closeButton.style.fontWeight = 'bold'; closeButton.addEventListener('click', function () { translationDiv.remove(); }); translationDiv.appendChild(closeButton); lastSelectedParagraph.parentNode.insertBefore(translationDiv, lastSelectedParagraph.nextSibling); } // Function to display the summary on the webpage with dynamic width and scrollable content function displayPopup(translatedText, originalText) { const summaryDiv = document.createElement('div'); summaryDiv.classList.add('draggable'); summaryDiv.style.position = 'fixed'; summaryDiv.style.top = '50%'; summaryDiv.style.left = '50%'; summaryDiv.style.transform = 'translate(-50%, -50%)'; summaryDiv.style.backgroundColor = '#fff'; summaryDiv.style.border = '1px solid #ccc'; summaryDiv.style.padding = '20px'; summaryDiv.style.zIndex = '10000'; summaryDiv.style.width = '700px'; // Wider width summaryDiv.style.maxHeight = '500px'; // Lower max height summaryDiv.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)'; summaryDiv.style.borderRadius = '15px'; summaryDiv.style.fontFamily = 'SF Pro Rounded, Arial, sans-serif'; summaryDiv.style.fontSize = '16px'; // Font size 16px summaryDiv.style.display = 'flex'; summaryDiv.style.flexDirection = 'column'; summaryDiv.style.overflow = 'hidden'; // Add summary section with scrollable content const summarySection = document.createElement('div'); const cleanedSummary = translatedText.replace(/(\*\*)(.*?)\1/g, '<b>$2</b>'); // Remove ** markers const formattedSummary = cleanedSummary.split('<br>').map(line => { if (line.startsWith('<b>KEYWORD</b>:')) { return `<h4 style="margin-bottom: 5px;">${line}</h4>`; } else if (line.startsWith('+ Định nghĩa:')) { const parts = line.split(':'); if (parts.length > 1) { const definition = parts.slice(1).join(':').trim(); const definitionParts = definition.split('<br> - Bản dịch định nghĩa:'); if (definitionParts.length === 2) { return `<p style="margin-left: 20px; margin-bottom: 5px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">+ Định nghĩa:<br>${definitionParts[0].trim()}</p><p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;"> - Bản dịch định nghĩa: ${definitionParts[1].trim()}</p>`; } } return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } else if (line.startsWith('+ Ví dụ:')) { const parts = line.split(':'); if (parts.length > 1) { const example = parts.slice(1).join(':').trim(); const exampleParts = example.split('<br> - Bản dịch ví dụ:'); if (exampleParts.length === 2) { // Replace example with a sentence from originalText if available const keywordMatch = line.match(/<b>KEYWORD<\/b>:\s*(\w+)/i); let exampleFromText = null; if (keywordMatch) { const keyword = keywordMatch[1]; const regex = new RegExp(`\\b${keyword}\\b`, 'i'); const sentences = originalText.split(/[.?!]/).filter(sentence => regex.test(sentence)); if (sentences.length > 0) exampleFromText = sentences[0].trim() + '.'; } const displayExample = exampleFromText ? exampleFromText : exampleParts[0].trim(); return `<p style="margin-left: 20px; margin-bottom: 5px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">+ Ví dụ: ${displayExample}</p><p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;"> - Bản dịch ví dụ: ${exampleParts[1].trim()}</p>`; } } return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } else { return `<p style="margin-left: 20px; margin-bottom: 10px; white-space: pre-wrap; word-wrap: break-word; text-align: justify;">${line}</p>`; } }).join(''); summarySection.innerHTML = `<h3 style="color: #333;">Dịch</h3><div style="overflow-y: auto; max-height: 400px; color: #555; font-size: 16px;">${formattedSummary}</div>`; // Font size 16px and lower max-height summaryDiv.appendChild(summarySection); // Add close button const closeButton = document.createElement('button'); closeButton.innerText = 'Đóng'; closeButton.style.marginTop = '10px'; closeButton.style.padding = '8px 16px'; closeButton.style.backgroundColor = '#ff4444'; closeButton.style.color = '#fff'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '4px'; closeButton.style.cursor = 'pointer'; closeButton.onclick = () => summaryDiv.remove(); summaryDiv.appendChild(closeButton); makeDraggable(summaryDiv); document.body.appendChild(summaryDiv); } // Function to display the simple translation popup function displaySimplePopup(originalText, translatedText) { const simplePopupDiv = document.createElement('div'); simplePopupDiv.classList.add('draggable'); simplePopupDiv.style.position = 'fixed'; simplePopupDiv.style.top = '50%'; simplePopupDiv.style.left = '50%'; simplePopupDiv.style.transform = 'translate(-50%, -50%)'; simplePopupDiv.style.backgroundColor = '#fff'; simplePopupDiv.style.border = '1px solid #ccc'; simplePopupDiv.style.padding = '20px'; simplePopupDiv.style.zIndex = '10000'; simplePopupDiv.style.width = '400px'; // Wider width for simple popup simplePopupDiv.style.maxHeight = '400px'; // Lower max height for simple popup simplePopupDiv.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)'; simplePopupDiv.style.borderRadius = '15px'; simplePopupDiv.style.fontFamily = 'SF Pro Rounded, Arial, sans-serif'; simplePopupDiv.style.fontSize = '16px'; // Font size 16px simplePopupDiv.style.display = 'flex'; simplePopupDiv.style.flexDirection = 'column'; simplePopupDiv.style.overflow = 'hidden'; // Keep overflow hidden for container, content will scroll // Translated Text Section (Only translation, no original text) const translatedTextSection = document.createElement('div'); translatedTextSection.innerHTML = `<div style="white-space: pre-wrap; word-wrap: break-word; text-align: justify; font-size: 16px; overflow-y: auto; max-height: 350px; padding-right: 10px;">${translatedText}</div>`; // Increased font size, added scroll and padding, lower max-height simplePopupDiv.appendChild(translatedTextSection); // Close Button const closeButton = document.createElement('button'); closeButton.innerText = 'Đóng'; closeButton.style.marginTop = '10px'; closeButton.style.padding = '8px 16px'; closeButton.style.backgroundColor = '#ff4444'; closeButton.style.color = '#fff'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '4px'; closeButton.style.cursor = 'pointer'; closeButton.onclick = () => simplePopupDiv.remove(); simplePopupDiv.appendChild(closeButton); makeDraggable(simplePopupDiv); document.body.appendChild(simplePopupDiv); } // Hàm hiển thị lỗi bên dưới function showErrorBelow(targetElement, errorMessage) { // Tìm đoạn văn cuối cùng được bôi đen (tương tự như trên) const selection = window.getSelection(); const lastSelectedNode = selection.focusNode; let lastSelectedParagraph = lastSelectedNode.parentElement; while (lastSelectedParagraph && lastSelectedParagraph.tagName !== 'P') { lastSelectedParagraph = lastSelectedParagraph.parentElement; } if (!lastSelectedParagraph) { lastSelectedParagraph = targetElement; } if (lastSelectedParagraph.nextElementSibling && lastSelectedParagraph.nextElementSibling.classList.contains('translation-div')) { return; } const errorDiv = document.createElement('div'); errorDiv.classList.add('translation-div'); errorDiv.style.marginTop = '10px'; errorDiv.style.padding = '10px'; errorDiv.style.backgroundColor = '#fdd'; errorDiv.style.borderLeft = '3px solid #faa'; errorDiv.style.color = '#a00'; errorDiv.style.fontFamily = 'SF Pro Rounded Medium, sans-serif'; errorDiv.style.fontSize = '16px'; errorDiv.style.position = 'relative'; // Thêm thuộc tính position: relative errorDiv.textContent = errorMessage; // Thêm nút đóng (nút "x") const closeButton = document.createElement('span'); closeButton.textContent = 'x'; closeButton.style.position = 'absolute'; closeButton.style.top = '5px'; closeButton.style.right = '5px'; closeButton.style.cursor = 'pointer'; closeButton.style.color = '#999'; closeButton.style.fontSize = '14px'; closeButton.style.fontWeight = 'bold'; closeButton.addEventListener('click', function () { errorDiv.remove(); }); errorDiv.appendChild(closeButton); lastSelectedParagraph.parentNode.insertBefore(errorDiv, lastSelectedParagraph.nextSibling); } // Function to make the summary popup draggable function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // get the mouse cursor position at startup pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // call a function whenever the cursor moves document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // set the element's new position: element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { // stop moving when mouse button is released: document.onmouseup = null; document.onmousemove = null; } } // Lắng nghe sự kiện phím tắt (Ctrl + M hoặc Cmd + M, Alt + M và Alt + T) document.addEventListener('keydown', function (event) { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (!selectedText) return; const targetElement = selection.anchorNode.parentElement; if ((event.ctrlKey || event.metaKey) && event.key === 'm') { event.preventDefault(); // Ngăn chặn hành động mặc định của trình duyệt translateText(selectedText, targetElement); } else if (event.altKey && event.key === 'm') { event.preventDefault(); translateText(selectedText, targetElement, true); // Dịch nâng cao khi ấn Alt + M } else if (event.altKey && event.key === 't') { event.preventDefault(); translateText(selectedText, targetElement, false, true); // Dịch nhanh popup khi ấn Alt + T } }); })();
ngôn ngữ nào cũng dịch được màThay đổi qua dịch tiếng trung kiểu gì nhỉ b
bro sử dụng những trình duyệt hỗ trợ cài addons Violenmonkey là được: kiwi browser, Firefox, edge, ...làm sao sài được trên mobile zạ Bác, chỉ em với
ý bro dịch qua tiếng trung hay dịch từ tiếng trungThay đổi qua dịch tiếng trung kiểu gì nhỉ b
Ý em là trên mobile ladm sao bấm hotkey alt + T, alt+q hay M á Brobro sử dụng những trình duyệt hỗ trợ cài addons Violenmonkey là được: kiwi browser, Firefox, edge, ...
bác chỉ cần to đen chữ cần dịch r nó sẽ hiện nút Dịch kế bên r bấm vào nó th mà, chứ làm gì xài phím tắt đượcÝ em là trên mobile ladm sao bấm hotkey alt + T, alt+q hay M á Bro
khổ râm quáÝ em là trên mobile ladm sao bấm hotkey alt + T, alt+q hay M á Bro
tải bàn phím có hotkey đấy là đcÝ em là trên mobile ladm sao bấm hotkey alt + T, alt+q hay M á Bro
Dịch: di động x
được nha brobác chỉ cần to đen chữ cần dịch r nó sẽ hiện nút Dịch kế bên r bấm vào nó th mà, chứ làm gì xài phím tắt được

xịn xò quá, chừng nào release Bác

em cài đc addon trên firefox mobile rồi , thím cho em hỏi khi em select text , thì cái bàn phím cũng biến mất tiêu luôn, làm sao để vừa select text bàn phím xuất hiện để bấm ATL + Q hay T thím.