之前做了一个翻译软件专门用来实时翻译和提供提示词扩展,借助了智谱清言的智能体。
用了一段时间后发现还是有比较明显的缺陷。比如生成的文本没法直观地与原文对照。
碍于我py代码水平实在不咋样,改功能只能通过问ai,这样效率太低了。所以打算换成前端语言来重构。
electron是一个桌面端开发框架
electron打包包体大小基本都100m起步,毕竟每个软件都自带一个小浏览器。理论上来说我这种小工具不适合用electron开发。
功能需求
- 置顶
- 关键词翻译
- 鼠标选中输入的提示词时,会在结果页面高亮显示对应的翻译结果(或者相反)
Electron Fiddle
因为我的需求只是做一个应用,所以根据官方推荐,安装了Electron Fiddle
,直接能使用Electron。并且我看了下介绍这个也可以打包应用,那么有何不可呢。
官网:https://www.electronjs.org/fiddle
下载完直接双击就能打开
开发
经过一晚上的拷打ai,终于完成了初步的效果实现。以下是代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>简单翻译软件</title> <link rel="stylesheet" href="styles.css"> </head> <body> <h2>百度API翻译</h2> <div class="input-output-container"> <div id="inputField" class="input-field" contenteditable="true"></div> <div id="outputWindow" class="output-window" contenteditable="false" spellcheck="false"> </div> </div> <script src="renderer.js"></script> </body> </html>
|
main.js
const { app, BrowserWindow,ipcMain,Menu} = require('electron'); let mainWindow; let isAlwaysOnTop = false; const https = require('https');
function createWindow() { mainWindow = new BrowserWindow({ width: 730, height: 400, webPreferences: { nodeIntegration: true, contextIsolation: false }, } );
mainWindow.loadFile('index.html'); mainWindow.on('closed', function () { mainWindow = null; });
function updateMenu() { const menuTemplate = [ { role: 'reload' }, { role: 'toggledevtools' }, { label: isAlwaysOnTop ? '📌置顶(c+t)' : '置顶(c+t)', type: 'checkbox', accelerator: 'CommandOrControl+T', checked: isAlwaysOnTop, click: () => { isAlwaysOnTop = !isAlwaysOnTop; mainWindow.setAlwaysOnTop(isAlwaysOnTop); updateMenu(); } } ];
const menu = Menu.buildFromTemplate(menuTemplate); Menu.setApplicationMenu(menu); } updateMenu();
}
app.on('ready', createWindow);
app.on('window-all-closed', function () { if (process.platform !== 'darwin') { app.quit(); } });
app.on('activate', function () { if (mainWindow === null) { createWindow(); } });
ipcMain.on('translate', (event, text) => { const appid = '20240409002018364'; const secretKey = 'hKiK_xVALpvs0qXdLCp7'; const from = 'auto'; const to = 'en'; const salt = (new Date).getTime(); const query = text;
const md5 = require('md5'); const sign = md5(appid + query + salt + secretKey);
const params = { q: query, appid: appid, salt: salt, from: from, to: to, sign: sign };
const queryStr = Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&');
const options = { hostname: 'api.fanyi.baidu.com', path: '/api/trans/vip/translate?' + queryStr, method: 'GET' };
const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { const result = JSON.parse(data); if (result && result.trans_result) { event.reply('translated', result.trans_result[0].dst); } }); });
req.on('error', (e) => { console.error(e); });
req.end(); });
|
renderer.js
const { ipcRenderer } = require('electron');
document.addEventListener('DOMContentLoaded', () => { const inputField = document.querySelector('.input-field'); const outputWindow = document.querySelector('.output-window'); inputField.addEventListener('input', () => { const text = inputField.innerText; if (text) { ipcRenderer.send('translate', text); } });
ipcRenderer.on('translated', (event, translatedText) => { outputWindow.innerHTML = ''; const words = translatedText.split(','); words.forEach((word, index) => { const span = document.createElement('span'); span.className = 'word'; span.textContent = word + ','; span.dataset.index = index; span.addEventListener('mouseover', highlightCorrespondingWord); span.addEventListener('mouseout', removeHighlight); outputWindow.appendChild(span); }); }); });
function highlightCorrespondingWord(event) { const highlightedWord = event.target.textContent.trim().replace(/, /g, ''); const spans = document.querySelectorAll('.output-window .word'); const index = event.target.dataset.index; spans.forEach(span => { if (span.dataset.index === index) { span.classList.add('active'); } });
const inputWords = inputField.innerText.split(','); const correspondingWord = inputWords[index]; const range = document.createRange(); const sel = window.getSelection(); let found = false; for (let i = 0; i < inputWords.length; i++) { const word = inputWords[i]; if (word === correspondingWord && !found) { found = true; range.setStart(inputField.childNodes[0], inputField.childNodes[0].textContent.indexOf(word)); range.setEnd(inputField.childNodes[0], inputField.childNodes[0].textContent.indexOf(word) + word.length); sel.removeAllRanges(); sel.addRange(range); break; } } }
function removeHighlight() { const spans = document.querySelectorAll('.output-window .word'); spans.forEach(span => { span.classList.remove('active'); }); window.getSelection().removeAllRanges(); }
const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` .word.active, .input-field::selection { background-color: yellow; } `; document.head.appendChild(style);
|
css
body { display: flex; flex-direction: column; align-items: center; margin: 0; padding: 0 30px 30px 30px; } h2 { text-align: center; } .input-output-container { display: flex; justify-content: space-between; width: 100%; margin-bottom: 10px; } .input-field { width: 45%; margin-right: 10px; min-height: 200px; padding: 10px; border: 1px solid #ccc; text-align: left; overflow-y: auto; outline: none; } .output-window { width: 45%; margin-left: 10px; border: 1px solid #ccc; min-height: 200px; padding: 10px; text-align: left; }
|
主页面
这次没有用智谱清言的ai翻译,而是直接采用百度翻译的api。
因为我考虑到只是关键词翻译的话不需要用到ai,而且扩展关键词功能有点鸡肋,平时也不怎么用。
亮点
- 界面简洁(完全没有搞排版和颜色搭配,功能太少了主要是)
- 实时翻译,省去了点翻译按钮或者是按回车的操作。
- 鼠标移到生成的单词中会高亮显示,并且同步到对应的输入框中的词语。(我专门为了这个功能而重构的程序)
还未完成的内容
- 自动识别输入的是中或英文,并进行翻译…
- 点击复制结果
- 一键复制结果
2024年9月21日 06:04:37,该睡觉了。
最终成品
又经过了一晚上的鏖战终于达到了我满意的效果。先展示下画面和功能。
主页面
鼠标移上来会显示按键提示
功能
鼠标移到输出窗口的单词,可以中英对照查看,目前只支持中英文互译。
点击单个词组可以进行单独复制。
仔细看输出窗口的右下角有一个按钮,它可以复制所有的内容。
制作过程遇到的问题很多,不过都一一解决了。
比如英译中的过程中,由于中文词语是一整个字符串,刚开始没做分割,没法与左侧对照显示。解决方法是用正则重新加了判断。
英译中
关于百度翻译的api我也单独用一个config.json
来收集,这样就不用担心自己的key的安全问题。用户自己申请key就能够使用。
只花了两天时间(昨天没写)就能够完成这个项目让我有了不少成就感。
成品源码就先不发了,留一个github链接
https://github.com/Niaoyu00/AI-Prompt-Word-Translation
使用
2024年9月23日 05:41:56
打包
前几天睡眠不太好,导致干活效率极低。今天终于恢复活力。卡了一天的打包问题也解决了。
文件打包不全的话要注意每一个需要的文件都写进files
。打包没有图标的话也要注意不能只写一个assets
,必须加上文件夹里面的内容。
"files": [ "assets/*", "*.js", "*.css", "*.html", "config.json" ], "win": { "target": "nsis", "icon": "assets/myicon.ico" },
|
下面是我的package.json
内容。
参考了这篇知乎文章https://www.zhihu.com/question/55656662/answer/2968071709
{ "name": "baidu_translate", "productName": "BaiduTranslate", "description": "ai绘画提示词翻译软件,支持一键复制,对照显示中英翻译内容", "keywords": [], "main": "main.js", "version": "1.0.0", "author": "Niaoyu", "scripts": { "start": "electron .", "build": "electron-builder build --dir" }, "dependencies": { "md5": "2.3.0" }, "devDependencies": { "electron": "31.2.1", "electron-builder": "^25.0.5" }, "build": { "asar": true, "asarUnpack": [ "node_modules/some-native-module/**/*" ], "productName": "promptsTranslate", "appId": "com.zhyny.BaiduTranslate", "directories": { "buildResources": "assets", "output": "dist" }, "nsis": { "oneClick": false, "language": "2052", "perMachine": true, "allowToChangeInstallationDirectory": true }, "extraResources": [ "config.json" ], "win": { "target": "nsis", "icon": "assets/myicon.ico" }, "files": [ "assets/*", "*.js", "*.css","*.html","config.json" ], "extends": null } }
|