用 pyqt4 编写的一个翻译小工具

  有时候我们在开发时遇到一些陌生的英文单词或者不容易看出某些长句的中文意思时该怎么办呢?打开桌面上的翻译软件?打开浏览器里收藏着的翻译网址或者直接贴上百度的搜索框去查?这些方法固然可以,还很常见,但如果是 linux 系统的话,很难找到像 windows 上那些公司级别来开发的成熟的翻译软件,所以只能打开浏览器来查了。浏览器一般都会装上一些翻译插件,比如我常用的 chrome 的 划词翻译,直接用这些插件来进行翻译比起打开一个翻译网站或者百度google搜索要更快,毕竟因为加载的内容更少,但这终究只是浏览器的插件,在浏览网页时用还方便,对于在其它软件上看到的陌生单词如果也放到这儿来查的话多少就感到有些转折了,所以少数的软件也会有相应的翻译插件,像优秀的编辑器 sublime text 等,就有人专门写了一个翻译的插件,可是像这类有翻译插件的软件毕竟还是极少数,如果用其它编辑器或 IDE 来开发的话也自己写一个翻译插件的话就太折腾了(我之前用 codeblocks 来看代码时遇到不懂的单词也想着能不能自行开发一个翻译插件,上网搜了一下发现开发 codeblocks 的插件需要的技术挺偏的,而且也不容易;有时经常在终端上 man 一个不熟悉的命令时也会遇到很多不懂的英文单词,此时为终端加一个翻译插件感觉就不太现实了吧)。

  所以,我前几天时就在想,能不能开发一个全局的翻译插件呢?也就是在使用电脑上所有软件时都能很方便调用这个插件去获取翻译的结果,因为我大多数时间是在使用 ubuntu,ubuntu 的任务栏默认是位于电脑的上方的,感觉看着很显眼,鼠标触及也方便,所以想就把这个插件放到任务栏上吧,这样子不管是在用 codeblocks / eclipse等 IDE 来进行开发,或者在使用终端进行各种命令的操作时,或者直接是在看网页(用不同的浏览器)等等,都能通过点击任务栏的小图标就能立即进行翻译了。

  我一开始的设计思想就是为了用最少的操作(也就是最少的鼠标点击次数或者键盘打字数目)来达到目的,所以我的这个全局插件的翻译原理是这样的:用 pyqt 构建好的 GUI 程序在后台运行,任务栏上显示,每当点击它在任务栏上的图标菜单里的翻译按钮时,程序会获取剪切版里的内容,然后把这些内容通过有道翻译的API得到翻译的 json 格式的结果,然后把 json 格式的结果处理一下,最后通过弹窗显示或者系统气泡消息的方式展现出来。因为我用的终端是 terminator,能设置成鼠标划过的文本直接进入剪切板,所以当我想要翻译一句英文时,只需要用鼠标选择好这些文本,然后再点击任务栏图标的翻译按钮就能直接能到结果,整个过程不过两次点击鼠标的时间,无须用到键盘,比起用浏览器来查,可以说是大大节省了时间,能显著地提高日常学习/开发的效率。好了,废话不多说了,先放上一张效果图:

  (我能说这个图非常难截么?因为用不了截图软件来截图(鼠标一点击截图软件时任务栏图标的菜单列表就会立刻消失),所以只能用键盘的截图键来截整个屏幕的图,然后再裁剪图片,可是在 ubuntu 下截取瞬间会有闪现的效果,导致这个菜单栏变得很模糊,所以我只能先换一张几乎是纯黑色的桌面背景,然后裁剪好后再作亮度调整等处理,先将就着看一下哦~)

  完整代码如下:

  1 #!/usr/bin/env python
  2 # coding: utf-8
  3 
  4 from PyQt4.QtGui import *
  5 from PyQt4.QtCore import *
  6 import sys
  7 import requests
  8 # import redis
  9 import json
 10 
 11 
 12 class SystemTray(QMainWindow):
 13     """My SystemTray, includes translator and functions of getting commands quickly."""
 14 
 15     YDERRORCODE = {
 16         0: u'正常',
 17         20: u'要翻译的文本过长',
 18         30: u'无法进行有效的翻译',
 19         40: u'不支持的语言类型',
 20         50: u'无效的key',
 21         60: u'无词典结果,仅在获取词典结果生效'
 22     }
 23 
 24     def __init__(self, title="SystemTray", size=[600, 500]):
 25         super(SystemTray, self).__init__()
 26         self.initSelf(title, size)
 27         self.initUI()
 28 
 29     def initSelf(self, title, size):
 30         self.setWindowTitle(title)
 31         self.setWindowIcon(QIcon('./images/window_icon.jpg'))
 32         self.resize(size[0], size[1])
 33         self.showOnCenter()
 34 
 35         self.clipboard = QApplication.clipboard()
 36         self.translateUrl = "http://fanyi.youdao.com/openapi.do?keyfrom=myname&key=xxx&type=data&doctype=json&version=1.1&q="
 37 
 38     def initUI(self):
 39         self.initBoard()
 40         self.statusbar = self.statusBar()
 41         self.initAction()
 42         self.initTrayIcon()
 43 
 44     def initBoard(self):
 45         board = QWidget()
 46         mainLayout = QVBoxLayout()
 47 
 48         gbox = QGroupBox(u'翻译设置')
 49         grid = QGridLayout()
 50         self.cbImmediatelyTranslate = QCheckBox(u'翻译剪切板中的内容')
 51         self.cbImmediatelyTranslate.setChecked(True)
 52         self.cbImmediatelyTranslate.stateChanged.connect(self.translateOption)
 53         grid.addWidget(self.cbImmediatelyTranslate, 0, 0, 1, 2)
 54         label = QLabel(u'字数限制:')
 55         label.setAlignment(Qt.AlignRight)
 56         grid.addWidget(label, 0, 3)
 57         self.sbWordLimit = QSpinBox()
 58         self.sbWordLimit.setRange(1, 800)
 59         self.sbWordLimit.setValue(200)
 60         self.sbWordLimit.valueChanged.connect(lambda x: self.slotWordLimitChange(self.sbWordLimit.value()))
 61         grid.addWidget(self.sbWordLimit, 0, 4)
 62 
 63         self.label_1 = QLabel(u'输入要翻译的文本:')
 64         grid.addWidget(self.label_1, 1, 0)
 65         self.textTranslate = QTextEdit()
 66         grid.addWidget(self.textTranslate, 2, 0, 3, 6)
 67         self.btnTranslate = QPushButton(u'翻译')
 68         self.btnTranslate.clicked.connect(self.btnClicked)
 69         grid.addWidget(self.btnTranslate, 6, 5)
 70         self.label_1.hide()
 71         self.textTranslate.hide()
 72         self.btnTranslate.hide()
 73 
 74         label = QLabel(u'显示方式:')
 75         label.setAlignment(Qt.AlignRight)
 76         grid.addWidget(label, 6, 0)
 77         self.cbbTranslateShowType = QComboBox()
 78         self.cbbTranslateShowType.addItem(QIcon(u'./images/messagebox.png'), u'弹窗显示')
 79         self.cbbTranslateShowType.addItem(self.style().standardIcon(QStyle.SP_MessageBoxInformation), u'系统通知')
 80         self.cbbTranslateShowType.currentIndexChanged.connect(lambda x: self.slotShowTypeChange(self.cbbTranslateShowType.currentIndex()))
 81         grid.addWidget(self.cbbTranslateShowType, 6, 1)
 82 
 83         self.cbShowTranslateDeatil = QCheckBox(u'显示详细的翻译结果')
 84         self.cbShowTranslateDeatil.clicked.connect(lambda x: self.slotShowDetailChange(self.cbShowTranslateDeatil.isChecked()))
 85         self.cbShowTranslateDeatil.setChecked(True)
 86         grid.addWidget(self.cbShowTranslateDeatil, 6, 3)
 87 
 88         grid.setColumnStretch(1, 1)
 89         grid.setColumnStretch(2, 1)
 90         grid.setColumnStretch(4, 1)
 91         grid.setColumnStretch(5, 1)
 92         gbox.setLayout(grid)
 93         mainLayout.addWidget(gbox)
 94 
 95         gbox = QGroupBox(u'快速复制命令到剪切板')
 96         grid = QGridLayout()
 97         label = QLabel(u'历史命令(history):')
 98         grid.addWidget(label, 0, 0)
 99         self.cbbHistoryCommands = QComboBox()
100         grid.addWidget(self.cbbHistoryCommands, 1, 0)
101         gbox.setLayout(grid)
102         mainLayout.addWidget(gbox)
103 
104         mainLayout.addStretch(1)
105         board.setLayout(mainLayout)
106         self.setCentralWidget(board)
107 
108     def slotShowTypeChange(self, index):
109         self.cbbTranslateShowType.setCurrentIndex(index)
110         if index == 0:
111             self.actMessagebox.setChecked(True)
112         else:
113             self.actSystemMessage.setChecked(True)
114 
115     def slotShowDetailChange(self, bShowDeatil):
116         if bShowDeatil == True:
117             self.actShowDetail.setChecked(True)
118             self.cbShowTranslateDeatil.setChecked(True)
119         else:
120             self.actShowDetail.setChecked(False)
121             self.cbShowTranslateDeatil.setChecked(False)
122 
123     def slotWordLimitChange(self, wordLen):
124         if wordLen in [100, 200, 400]:
125             self.sbWordLimit.setValue(wordLen)
126         if wordLen == 100:
127             self.actWordLimit_1.setChecked(True)
128         elif wordLen == 200:
129             self.actWordLimit_2.setChecked(True)
130         elif wordLen == 400:
131             self.actWordLimit_3.setChecked(True)
132         else:
133             self.actWordLimit_4.setChecked(True)
134             self.actWordLimit_4.setText(('其它(' + str(wordLen) + ')').decode('utf8'))
135             self.show()
136 
137     def initAction(self):
138         self.actTranslate = QAction(QIcon('./images/youdao.jpg'), u'立即翻译', self)
139         self.actTranslate.setShortcut('ctrl+alt+f')
140         self.actTranslate.triggered.connect(self.translate_clipboard)
141 
142         menuShowType = QMenu(u'显示方式', self)
143         ag = QActionGroup(self, exclusive=True)
144         self.actMessagebox = QAction(QIcon(u'./images/messagebox.png'), u'弹窗显示', menuShowType, checkable=True)
145         self.actMessagebox.triggered.connect(lambda x: self.slotShowTypeChange(0))
146         self.actMessagebox.setChecked(True)
147         self.actSystemMessage = QAction(self.style().standardIcon(QStyle.SP_MessageBoxInformation), u'系统通知', menuShowType, checkable=True)
148         self.actSystemMessage.triggered.connect(lambda x: self.slotShowTypeChange(1))
149         menuShowType.addActions([ag.addAction(self.actMessagebox), ag.addAction(self.actSystemMessage)])
150 
151         menuTranslateOption = QMenu(u'翻译设置', self)
152         menuTranslateOption.setIcon(QIcon(u'./images/config.png'))
153         self.actShowDetail = QAction(u'显示详细结果', menuTranslateOption, checkable=True)
154         self.actShowDetail.triggered.connect(lambda x: self.slotShowDetailChange(self.actShowDetail.isChecked()))
155         self.actShowDetail.setChecked(True)
156 
157         menuWordLimit = QMenu(u'字数限制', self)
158         ag = QActionGroup(self, exclusive=True)
159         self.actWordLimit_1 = QAction(u'100', menuWordLimit, checkable=True)
160         self.actWordLimit_1.triggered.connect(lambda x: self.slotWordLimitChange(100))
161         self.actWordLimit_2 = QAction(u'200', menuWordLimit, checkable=True)
162         self.actWordLimit_2.triggered.connect(lambda x: self.slotWordLimitChange(200))
163         self.actWordLimit_2.setChecked(True)
164         self.actWordLimit_3 = QAction(u'400', menuWordLimit, checkable=True)
165         self.actWordLimit_3.triggered.connect(lambda x: self.slotWordLimitChange(400))
166         self.actWordLimit_4 = QAction(u'其它', menuWordLimit, checkable=True)
167         self.actWordLimit_4.triggered.connect(lambda x: self.slotWordLimitChange(1))
168         menuWordLimit.addActions([ag.addAction(self.actWordLimit_1), ag.addAction(self.actWordLimit_2), ag.addAction(self.actWordLimit_3), ag.addAction(self.actWordLimit_4)])
169 
170         menubar = self.menuBar()
171         menuTranslate = menubar.addMenu(u'翻译')
172         menuTranslate.addAction(self.actTranslate)
173         menuTranslateOption.addMenu(menuShowType)
174         menuTranslateOption.addAction(self.actShowDetail)
175         menuTranslateOption.addMenu(menuWordLimit)
176         menuTranslate.addMenu(menuTranslateOption)
177 
178         commandsMenu = menubar.addMenu(u'commands设置')
179 
180     def initTrayIcon(self):
181         menuTrayIcon = QMenu(self)
182         menuTrayIcon.addAction(self.actTranslate)
183         
184         menuTranslateOption = QMenu(u'翻译设置', self)
185         menuTranslateOption.setIcon(QIcon(u'./images/config.png'))
186         menuShowType = QMenu(u'显示方式', self)
187         menuShowType.addActions([self.actMessagebox, self.actSystemMessage])
188         menuTranslateOption.addMenu(menuShowType)
189         menuTranslateOption.addAction(self.actShowDetail)
190         menuWordLimit = QMenu(u'字数限制', self)
191         menuWordLimit.addActions([self.actWordLimit_1, self.actWordLimit_2, self.actWordLimit_3, self.actWordLimit_4])
192         menuTranslateOption.addMenu(menuWordLimit)
193 
194         menuTrayIcon.addMenu(menuTranslateOption)
195         # menuTrayIcon.addAction(QAction("Minimize", self, triggered=self.showMinimized))
196         menuTrayIcon.addSeparator()
197         menuTrayIcon.addAction(QAction(QIcon('./images/qt.png'), u"打开主面板", self, triggered=self.showNormal))
198         menuTrayIcon.addAction(QAction(QIcon('./images/quit.png'), u"退出", self, triggered=qApp.quit))
199 
200         self.trayIcon = QSystemTrayIcon(QIcon('./images/tray_icon.jpg'), self)
201         self.trayIcon.setContextMenu(menuTrayIcon)
202         self.trayIcon.show()
203 
204     def btnClicked(self):
205         self.translateText(self.textTranslate.toPlainText())
206 
207     def translateOption(self):
208         if self.cbImmediatelyTranslate.isChecked():
209             self.label_1.hide()
210             self.textTranslate.hide()
211             self.btnTranslate.hide()
212         else:
213             self.label_1.show()
214             self.textTranslate.show()
215             self.btnTranslate.show()
216 
217     def translate(self, text):
218         if len(text) > self.sbWordLimit.value():
219             return (1, u'翻译的文本长度超过字数限制!')
220         text = (str(text.toUtf8())).strip()
221         if text == '':
222             return (2, u'翻译的字符串中没有实际的字符(只包含空格/tab/换行等)')
223         req = self.translateUrl + text
224         r = requests.get(req)
225         if r.ok != True:
226             return (r.status_code, QString(u'网络错误,url请求失败'))
227         else:
228             u_dict = json.loads(r.text)
229             errorCode = u_dict['errorCode']
230             if errorCode != 0:
231                 return (errorCode, QString(self.YDERRORCODE[errorCode]))
232             else:
233                 res = QString(u'')
234                 if u_dict.has_key('translation'):
235                     res += u"<翻译>:  "
236                     for x in u_dict['translation']:
237                         res = res + x + ", "
238                     res = (
239                         res[:-2] + "
") if res[-2:] == ", " else (res + "
")
240                 if u_dict.has_key('basic'):
241                     if u_dict['basic'].has_key('us-phonetic'):
242                         res = res + u"<美式发音>:  " + u_dict['basic']['us-phonetic'] + "	"
243                     if u_dict['basic'].has_key('uk-phonetic'):
244                         res = res + u"<英式发音>:  " + u_dict['basic']['uk-phonetic'] + "
"
245                     if u_dict['basic'].has_key('explains'):
246                         res += u"<解释>:  "
247                         for x in u_dict['basic']['explains']:
248                             res = res + x + ", "
249                         res = (
250                             res[:-2] + "
") if res[-2:] == ", " else (res + "
")
251                 if self.cbShowTranslateDeatil.isChecked():
252                     if u_dict.has_key('web'):
253                         res += u"<网络用语>:  
"
254                         for d in u_dict['web']:
255                             if d.has_key('key'):
256                                 res = res + u"  <关键词>:  " + d['key'] + "
"
257                                 if d.has_key('value'):
258                                     res = res + u"  <意思>:  "
259                                     for v in d['value']:
260                                         res = res + v + ", "
261                                     res = (
262                                         res[:-2] + "
") if res[-2:] == ", " else (res + "
")
263                 return (errorCode, res)
264 
265     def translateText(self, text):
266         if type(text) == type(QString()):
267             text = text.simplified()
268         elif type(text) == str:
269             text = text.strip()
270         result = self.translate(text)
271         showType = self.cbbTranslateShowType.currentIndex()
272         if result[0] != 0:
273             title = QString(u'翻译出错')
274             self.showTranslateResult(title, result[1], QMessageBox.Warning)
275         else:
276             title = (text[0:20] + u'....' if len(text)
277                      >= 20 else text) + u" 的翻译结果 "
278             self.showTranslateResult(title, result[1], QMessageBox.Information)
279 
280     def translate_clipboard(self):
281         clip_data = self.clipboard.mimeData()
282         if clip_data.hasText():
283             src = clip_data.text()
284             self.translateText(src)
285         else:
286             QMessageBox.information(self, u'提示', u'剪切板中的内容为空,无法翻译!')
287 
288     def showTranslateResult(self, title, content, icon):
289         showType = self.cbbTranslateShowType.currentIndex()
290         if showType == 0:
291             # 至关重要的一句,否则当关闭messagebox的窗口时,整个程序自动退出!
292             QApplication.setQuitOnLastWindowClosed(False)
293             QMessageBox(QMessageBox.Icon(icon), title, content).exec_()
294         elif showType == 1:
295             self.trayIcon.showMessage(title, content, QSystemTrayIcon.MessageIcon(icon))
296         else:
297             pass
298 
299     def showOnCenter(self):
300         screen = QDesktopWidget().screenGeometry()
301         self.move((screen.width() - self.width()) / 2,
302                   (screen.height() - self.height()) / 2)
303 
304     def closeEvent(self, event):
305         if self.trayIcon.isVisible():
306             self.trayIcon.showMessage(u'隐藏', u'在任务栏按钮可打开主窗口', QSystemTrayIcon.MessageIcon(QMessageBox.Information), 1000)
307             self.hide()
308             event.ignore()
309 
310 if __name__ == '__main__':
311     app = QApplication(sys.argv)
312     tray = SystemTray()
313     # tray.show()
314     sys.exit(app.exec_())

  只需要安装好 pyqt4 即可。代码写得有些乱,因为一开始就是为了快速达到目的,当复制好要翻译的英文时,再点击任务栏菜单立即翻译的按钮,就能看到翻译的结果了:

或者系统的气泡消息:

  因为用到的外部库只有 pyqt4 一个,其它都是 python2.7 的标准库,而 pyqt4 在 windows 下能用 py2exe 打包成可执行程序,所以在windows下还是能用的,而且因为 qt 对 windows 平台的支持更好,所以我觉得在windows下的体验应该比 ubuntu 要更好一些。

  附上代码中用到的图片下载地址:beautiful_icons

原文地址:https://www.cnblogs.com/Newdawn/p/6257715.html