使用 ChatGPT 突破能力边界 之 前端小白实现沉浸式翻译的核心功能 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
zapll
V2EX    程序员

使用 ChatGPT 突破能力边界 之 前端小白实现沉浸式翻译的核心功能

  •  
  •   zapll 2024-01-03 16:20:28 +08:00 2627 次点击
    这是一个创建于 716 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作为一个后端开发, 一直比较好奇沉浸式翻译的效果是如何实现的, 所以咱也当了回甲方, 让 ChatGPT 帮我实现想要的效果

    场景

    为了锻炼英文阅读能力, 在英文文章阅读时还是期望能多读外文, 如果一把梭翻译一遍, 普遍是只读中文不看英文了, 那么也就起不到我们锻炼英文能力的要求了.

    需求

    如果我遇到了一些不认识或者不确定意思的单词,我希望得到一个贴合当前语境的翻译,这样既能理解文章,又能在语境中学习单词。

    需求拆解

    如果鼠标在某个单词上方悬浮超过 xx 毫秒, 那么则结合当前语境翻译这个单词及这个句子.

    好的, 需求有了, 思路也有了, 具体咋做咱就不知道了, 现在轮到我最强的小弟 ChatGPT 大显身手了

    效果图

    image.png

    具体功能点:

    1. 关键词的翻译, 译文紧跟在关键词后面
    2. 当前语句的翻译

    实现此效果的完整 ChatGPT 对话: 实现沉浸式翻译的核心功能

    插播广告:

    推荐一个 ChatGPT Plus 拼车服务, 客服wx, 无需科学上网/会话隔离

    最终完整代码

    import type { PlasmoCSConfig } from "plasmo" import { generate } from 'short-uuid' export const config: PlasmoCSCOnfig= { matches: ["<all_urls>"], all_frames: true } const loading = ` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><radialGradient id="a11" cx=".66" fx=".66" cy=".3125" fy=".3125" gradientTransform="scale(1.5)"><stop offset="0" stop-color="#85A2B6"></stop><stop offset=".3" stop-color="#85A2B6" stop-opacity=".9"></stop><stop offset=".6" stop-color="#85A2B6" stop-opacity=".6"></stop><stop offset=".8" stop-color="#85A2B6" stop-opacity=".3"></stop><stop offset="1" stop-color="#85A2B6" stop-opacity="0"></stop></radialGradient><circle transform-origin="center" fill="none" stroke="url(#a11)" stroke-width="15" stroke-linecap="round" stroke-dasharray="200 1000" stroke-dashoffset="0" cx="100" cy="100" r="70"><animateTransform type="rotate" attributeName="transform" calcMode="spline" dur="2" values="360;0" keyTimes="0;1" keySplines="0 0 1 1" repeatCount="indefinite"></animateTransform></circle><circle transform-origin="center" fill="none" opacity=".2" stroke="#85A2B6" stroke-width="15" stroke-linecap="round" cx="100" cy="100" r="70"></circle></svg> ` const cssCode = ` .ct-loading { width: 1em; display: inline-block; } .ct-span { display: inline-block; margin-right: 2px; margin-left: 2px; } ` var xy = { x: 0, y: 0 } document.head.insertAdjacentHTML('beforeend', '<style type="text/css">' + cssCode + '</style>'); // 存储已经翻译过的单词,以避免重复翻译 var translatedWords = new Set(); // 用于存储已翻译的单词及其父元素 var pool = {} document.addEventListener('mousemove', function (event) { // 获取鼠标位置 var x = event.clientX, y = event.clientY; xy = { x, y } // 获取鼠标位置下的文本范围 var range, textNode, offset; range = document.caretRangeFromPoint(x, y); textNode = range.startContainer; if (!textNode) { return } if (textNode.parentElement.hasAttribute('data-ct-translation')) { // 翻译结果节点 return; } if (hasPreOrCodeParent(textNode)) { return } offset = range.startOffset; var text = textNode.textContent; var words = text.split(/\s+/); for (var i = 0; i < words.length; i++) { var word = removeSpecialCharacters(words[i]); if (!isEnglishWord(word)) { continue; } // 检测鼠标是否在当前单词上 var wordRange = document.createRange(); wordRange.setStart(textNode, text.indexOf(word)); wordRange.setEnd(textNode, text.indexOf(word) + word.length); var rects = wordRange.getClientRects(); for (var j = 0; j < rects.length; j++) { var rect = rects[j]; if (!(x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom)) { continue; } var parentElementId = textNode.parentElement.getAttribute('data-ct-id'); if (parentElementId) { translatedWords[word] = parentElementId; } else { parentElementId = generate(); textNode.parentElement.setAttribute('data-ct-id', parentElementId); } if (translatedWords.has(word + '-' + parentElementId)) { continue; } // 创建包装的 span 元素 // console.log(); var spanElement = document.createElement('span'); const wordId = generate() spanElement.setAttribute('id', wordId); spanElement.setAttribute('data-pid', parentElementId); spanElement.setAttribute('data-ct-translation', '0'); spanElement.setAttribute('data-ct-word', word); spanElement.setAttribute('data-ct-rect', `${rect.left},${rect.right},${rect.top},${rect.bottom}`); spanElement.setAttribute('data-ct-parent', getFullSentenceContainingWord(textNode.parentElement)); wordRange.collapse(false); // 将光标移到范围的末尾 wordRange.insertNode(spanElement); translatedWords.add(word + '-' + parentElementId); pool[wordId] = {} // console.log('Word under mouse:', word); } } }); setInterval(() => { for (const wordId in pool) { const wordElement = document.getElementById(wordId); if (!wordElement) { continue; } const rect = wordElement.getAttribute('data-ct-rect').split(',').map(Number); const word = wordElement.getAttribute('data-ct-word'); const pid = wordElement.getAttribute('data-pid'); if (!isMouseOnTranslation({ left: rect[0], right: rect[1], top: rect[2], bottom: rect[3] }, xy.x, xy.y)) { translatedWords.delete(word + '-' + pid) wordElement.remove() delete pool[wordId] continue } wordElement.classList.add('ct-loading') wordElement.innerHTML = loading fetch('http://localhost:8787/translate', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ word: word, sentence: wordElement.getAttribute('data-ct-parent') }) }).then(res => res.json()).then(res => { console.log(res) wordElement.classList.remove('ct-loading') wordElement.classList.add('ct-span') wordElement.innerHTML = `(${res.word_result})` const p = document.querySelector(`[data-ct-id="${pid}"]`) const cp = p.cloneNode(true) cp.textCOntent= res.sentence_result p.after(cp) }).catch(e => console.log) wordElement.setAttribute('data-ct-translation', '1'); delete pool[wordId] } }, 1500) // 判断鼠标是否在翻译内容上 function isMouseOnTranslation(rect, mouseX, mouseY) { return rect && mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom } function isEnglishWord(word) { // 使用正则表达式匹配英文单词,这里假设一个英文单词由大小写字母组成 var regex = /^[a-zA-Z0-9]+$/; return regex.test(removePunctuation(word)); } function removePunctuation(word) { // 使用正则表达式匹配标点符号并替换为空字符串 return word.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, ''); } function getFullSentenceContainingWord(parentNode) { // const tokens = parentNode.innerText.replce(/\n/, '').split('.'); // return tokens[0]; return parentNode.innerText.replace(/\n/, '') } function removeSpecialCharacters(str) { return str.replace(/[^a-zA-Z0-9]/g, ''); } function hasPreOrCodeParent(node) { let parent = node.parentNode; while (parent) { if (parent.tagName === 'PRE' || parent.tagName === 'CODE') { return true; } parent = parent.parentNode; } return false; } 
    14 条回复    2024-01-06 12:33:04 +08:00
    w2er
        1
    w2er  
       2024-01-03 16:23:06 +08:00
    很棒,谢谢分享,在用一个类似的插件
    vipfts
        2
    vipfts  
       2024-01-03 16:26:42 +08:00
    我倒希望有那种点一下单词然后详细翻译单词的沉浸式翻译
    0o0O0o0O0o
        3
    0o0O0o0O0o  
       2024-01-03 16:27:51 +08:00 via iPhone   2
    虽然是推广,但是比没有内容的推广好很多,建议多来点
    zapll
        4
    zapll  
    OP
       2024-01-03 16:28:19 +08:00
    @vipfts #2 这个触发方式跟悬浮类似, 应该也不难, 可以扔个 ChatGPT 让他改改,
    zapll
        5
    zapll  
    OP
       2024-01-03 16:30:29 +08:00
    @0o0O0o0O0o 感谢认可, 后续多分享一些我日常使用 ChatGPT 的体验, 在创造性工作上, 帮助我们突破一下现在的能力边界还是蛮有用的
    XMV2e4PmK5F85h17
        6
    XMV2e4PmK5F85h17  
       2024-01-03 18:45:38 +08:00
    现在 mac 上用的比较香的有三个,1 ,bob 翻译 option+s 直接截图翻译类似
    2. 这个也相当好用 选中文字后,cmd+cc 多点击一下 C ,就呼出来了。
    SayHelloHi
        7
    SayHelloHi  
       2024-01-04 10:07:33 +08:00
    @zapll

    楼主 有 GitHub 项目地址么
    这还得用 plasmo 创建一个开发插件项目 把代码放进去
    sma210
        8
    sma210  
       2024-01-04 10:21:08 +08:00
    @Sniper000 第二款有 URL 的不
    unco020511
        9
    unco020511  
       2024-01-04 10:46:33 +08:00
    GPT 特别擅长编写像浏览器插件、IDEA 插件这种小型应用程序,事实上,在我完全没有基础的情况下也曾让 GPT 帮助我创建了一个 IDEA 插件。
    XMV2e4PmK5F85h17
        10
    XMV2e4PmK5F85h17  
       2024-01-04 14:19:56 +08:00
    @sma210 好像没有
    zapll
        11
    zapll  
    OP
       2024-01-04 18:51:22 +08:00
    @Sniper000 这几个工具好用嘞
    @SayHelloHi 没有建 github 仓库, 代码量比较小, create plasmo 然后 代码 贴到 context.ts 里就行了
    XMV2e4PmK5F85h17
        12
    XMV2e4PmK5F85h17  
       2024-01-04 20:04:44 +08:00
    @zapll #11 哈哈哈 确实是好用 用习惯了 只能说离不开了已经
    ludage
        13
    ludage  
       2024-01-05 14:25:49 +08:00
    请多来一些跟 gpt 的对话,我学习一下,谢谢大佬
    zapll
    14
    zapll  
    OP
       2024-01-06 12:33:04 +08:00
    @ludage 我努力
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2195 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 00:32 PVG 08:32 LAX 16:32 JFK 19:32
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86