Skip to content

Commit bef3fa3

Browse files
committed
feat:将选中的中文转为英文快捷键,并原地粘贴,并修改剪切板。fix:首字母大写不影响字典翻译
1 parent a38c0ff commit bef3fa3

6 files changed

Lines changed: 97 additions & 23 deletions

File tree

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
44
![UI](https://img.shields.io/badge/UI-PySide6-lightgrey.svg)
55

6-
> 离线翻译 & 截屏翻译,轻巧强大,作者自用!
6+
> 离线翻译 & 截屏翻译 & 复制翻译,轻巧强大,作者自用!
77
88
由于市面上的翻译软件越做越烂,作者一怒之下开发出了自用的翻译软件——LingZero!
99

@@ -17,19 +17,25 @@ LingZero 能够为你带来截屏翻译 + 离线翻译的极致体验!没有
1717
- 🛸 **卡片式悬浮窗**:左键可以任意拖动,点击周围即可关闭,点击切换原文和译文。
1818
- 📸 **截屏翻译**:快速截图识别 + 翻译(基于 [pytesseract](https://github.com/madmaze/pytesseract))。
1919
- 📋 **复制翻译**:复制即翻译,提升阅读效率。
20-
- 💎 **词典翻译**翻译单个词语时,首选调用词典翻译,直接查询本地词典,包含690000个单词或短语(基于[ECDICT](https://github.com/skywind3000/ECDICT))。
20+
- 💎 **词典翻译**翻译单个词语(或短语)时,首选调用词典翻译,直接查询本地词典,包含690000个单词或短语(基于[ECDICT](https://github.com/skywind3000/ECDICT))。
2121
- 🧠 **离线翻译**:翻译段落或句子时,集成深度学习翻译引擎(基于 [Argos Translate](https://github.com/argosopentech/argos-translate))。
2222
- 🐧 **腾讯翻译**:翻译段落或句子时,假如联网,还能调用腾讯翻译(每月500万字免费,含标点),自动优化翻译结果。
2323

24-
## 推荐使用场景
24+
## 推荐使用的场景或用户
2525

26-
- 阅读英文新闻、论文、技术文档等。
26+
- 希望阅读英文新闻,缩小语言问题带来的信息查。
27+
- 需要阅读英文文献的科研人员等。
28+
- 需要阅读技术文档的工程师、程序员等。
29+
- 中文 ➝ 英文的一般质量翻译。
30+
- 内网电脑使用的场景。
31+
- 习惯使用快捷键的用户。
2732

28-
## 不推荐使用场景
33+
## 不推荐使用的场景或用户
2934

30-
- 中文 ➝ 英文 翻译(通常使用聊天机器人)。
35+
- 中文 ➝ 英文的SCI写作级超高质量翻译(通常使用聊天机器人)。
3136
- 英文 ➝ 非中文 翻译(本人只使用中文)。
32-
- 不推荐国外使用(接入了腾讯在上海的服务器)。
37+
- 排斥使用快捷键的用户,因为部分功能只能使用快捷键。
38+
- 暂时不推荐国外使用(接入了腾讯在上海的服务器),因为服务器位置暂时不支持配置。但是离线翻译可以正常工作。
3339

3440
## 💻 系统兼容
3541

config.ini

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ copy_trans_fixed_width = 300
55
background_style = "background-color:rgb(255, 255, 255); border-radius: 3px; color: black; font-size: 16px;"
66
; 截图的快捷键,可以设置多个,逗号分隔
77
capture_triggered_hotkey = ctrl+space,ctrl+b,ctrl+k
8-
; 截图复制的快捷键,可以设置多个,逗号分隔
8+
; 截图复制的快捷键,可以设置多个,逗号分隔(未实现)
99
copy_triggered_hotkey = ctrl+shift+d,ctrl+8
10+
; 将选中的中文转为英文快捷键,并原地粘贴,并修改剪切板的快捷键,该功能使用腾讯翻译,
11+
; 可以设置多个,逗号分隔
12+
copy_into_english_triggered_hotkey = alt+c,alt+v
1013
; 停用/启用复制翻译的快捷键,可以设置多个,逗号分隔
1114
stoptrans_triggered_hotkey = ctrl+1,f8
1215
; 悬浮窗的文本内容改为可复制的快捷键,可以设置多个,逗号分隔

main.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import sys, os
1+
import sys, os, time
22
import configparser
33
config = configparser.ConfigParser()
44
config.read('config.ini', encoding="utf-8")
5+
secret_config = configparser.ConfigParser()
6+
secret_config.read('secret.ini', encoding="utf-8")
57
import pytesseract
68
# 判断是开发环境还是生产环境
79
if os.path.exists("./.github"):
@@ -16,16 +18,48 @@
1618
QSystemTrayIcon, QMenu, QLabel, QStyle, QVBoxLayout,
1719
QGraphicsDropShadowEffect)
1820

19-
from pynput import mouse
21+
from pynput import mouse as pynput_mouse
22+
from pynput import keyboard as pynput_keyboard
2023
import keyboard
24+
import pyperclip
2125
from PIL import Image
2226
import ctypes
27+
import win32clipboard
28+
import win32con
2329
user32 = ctypes.windll.user32
2430
kernel32 = ctypes.windll.kernel32
2531

2632
from translator import Translator
2733
trans = Translator()
2834

35+
from translator import tencent
36+
tencent_trans = tencent.Trans(secret_config)
37+
38+
def wait_for_all_keys_up(timeout=2.0, interval=0.1):
39+
"""等待所有物理按键抬起(超时时间内)"""
40+
start_time = time.time()
41+
while time.time() - start_time < timeout:
42+
if not any(keyboard.is_pressed(key) for key in keyboard.all_modifiers | set("abcdefghijklmnopqrstuvwxyz1234567890")):
43+
return True
44+
time.sleep(interval)
45+
return False
46+
47+
def copy_selected_text():
48+
"""模拟 Ctrl+C 并返回复制的文本"""
49+
if not wait_for_all_keys_up():
50+
return None
51+
52+
# 模拟 Ctrl+C
53+
keyboard.press('ctrl')
54+
keyboard.press('c')
55+
time.sleep(0.1) # 等待复制完成
56+
keyboard.release('c')
57+
keyboard.release('ctrl')
58+
59+
# 获取剪贴板内容
60+
return pyperclip.paste()
61+
62+
2963
# 翻译弹窗,用于展示翻译结果
3064
class TextWindow(QWidget):
3165
open_or_close_triggered = Signal()
@@ -253,6 +287,7 @@ class TrayApp(QMainWindow):
253287
clipboard_changed_triggered = Signal()
254288
esc_triggered = Signal()
255289
click_triggered = Signal(int, int, str, bool)
290+
copy_into_english_triggered = Signal()
256291
def __init__(self):
257292
super().__init__()
258293
self.tray = QSystemTrayIcon(self)
@@ -276,29 +311,39 @@ def __init__(self):
276311
self.tray.setContextMenu(self.menu)
277312
self.tray.show()
278313

314+
# 连接截屏翻译
279315
self.capture_triggered.connect(self.capture)
280-
281316
capture_triggered_hotkeys = config.get('DEFAULT', 'capture_triggered_hotkey').split(",")
282317
self.capture_handle_hotkeys = []
283318
for hotkey in capture_triggered_hotkeys:
284319
self.capture_handle_hotkeys.append(
285320
keyboard.add_hotkey(hotkey, self.capture_triggered.emit))
286321
self.stop_trans = False
322+
323+
# 连接停止翻译
287324
stoptrans_triggered_hotkeys = config.get('DEFAULT', 'stoptrans_triggered_hotkey').split(",")
288325
for hotkey in stoptrans_triggered_hotkeys:
289326
keyboard.add_hotkey(hotkey, self.on_hotkey_stoptrans)
327+
328+
# 连接停止截屏
290329
keyboard.add_hotkey('esc', self.esc_triggered.emit)
291330

331+
# 连接将选中的中文转为英文
332+
copy_into_english_triggered_hotkeys =\
333+
config.get('DEFAULT', 'copy_into_english_triggered_hotkey').split(",")
334+
for hotkey in copy_into_english_triggered_hotkeys:
335+
keyboard.add_hotkey(hotkey, self.on_hotkey_copy_into_english)
336+
292337
self.clipboard = QApplication.clipboard()
293338
self.on_hotkey_stoptrans()
294339

340+
# 创建鼠标监听器
295341
def on_click_wrapper(x, y, button, pressed):
296342
self.click_triggered.emit(x, y, button.name, pressed)
297-
# 创建鼠标监听器
298-
self.listener = mouse.Listener(
343+
self.mouse_listener = pynput_mouse.Listener(
299344
on_click=on_click_wrapper,
300345
)
301-
self.listener.start()
346+
self.mouse_listener.start()
302347

303348
def clipboard_changed_trans(self):
304349
clipboard_text = self.clipboard.text().strip()
@@ -322,7 +367,15 @@ def on_hotkey_stoptrans(self):
322367
self.action_stoptrans.setText(f"启用复制翻译({config.get('DEFAULT', 'stoptrans_triggered_hotkey')})")
323368
self.stop_trans = not self.stop_trans
324369

325-
370+
# 将选中的中文转为英文快捷键,并原地粘贴,并修改剪切板
371+
def on_hotkey_copy_into_english(self):
372+
self.on_hotkey_stoptrans()
373+
clipboard_text = copy_selected_text()
374+
if trans_result := tencent_trans.translate(clipboard_text, target="en"):
375+
pyperclip.copy(trans_result)
376+
keyboard.press_and_release('ctrl+v')
377+
self.on_hotkey_stoptrans()
378+
326379
def capture(self):
327380
self.shot_window = ScreenShotWindow()
328381
self.shot_window.text_window_open_or_close_triggered.connect(self.on_hotkey_stoptrans)

translator/ecdict.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ def translate(self, text: str) -> str:
88
if text in self.ecdict:
99
translated_text = self.ecdict[text].replace("\\n", "\n")
1010
return translated_text
11+
elif (text_lower := text.lower()) in self.ecdict:
12+
translated_text = self.ecdict[text_lower].replace("\\n", "\n")
13+
return translated_text
1114
else:
1215
return ""

translator/tencent.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def sign(key, msg):
1717
class Trans():
1818
def __init__(self, config):
1919
self.config = config
20-
def translate(self, input_text: str) -> str:
20+
def translate(self, input_text: str, target = "zh") -> str:
2121
# 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
2222
# 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
2323
# 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
@@ -30,8 +30,12 @@ def translate(self, input_text: str) -> str:
3030
region = "ap-shanghai"
3131
version = "2018-03-21"
3232
action = "TextTranslate"
33-
payload = "{\"SourceText\":\"" + input_text +\
34-
"\",\"Source\":\"en\",\"Target\":\"zh\",\"ProjectId\":0,\"UntranslatedText\":\"\",\"TermRepoIDList\":[\"\"],\"SentRepoIDList\":[\"\"]}"
33+
if target == "zh":
34+
payload = "{\"SourceText\":\"" + input_text +\
35+
"\",\"Source\":\"en\",\"Target\":\"zh\",\"ProjectId\":0,\"UntranslatedText\":\"\",\"TermRepoIDList\":[\"\"],\"SentRepoIDList\":[\"\"]}"
36+
elif target == "en":
37+
payload = "{\"SourceText\":\"" + input_text +\
38+
"\",\"Source\":\"zh\",\"Target\":\"en\",\"ProjectId\":0,\"UntranslatedText\":\"\",\"TermRepoIDList\":[\"\"],\"SentRepoIDList\":[\"\"]}"
3539
# params = json.loads(payload)
3640
# endpoint = "https://tmt.tencentcloudapi.com"
3741
algorithm = "TC3-HMAC-SHA256"

translator/translate.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44
from . import ecdict, tencent, argos
55

66
# 太长或太短,或英文占比没有达到60%,都不翻译
7-
def is_translation_needed(text) -> bool:
7+
def is_translation_needed(text, target = "zh") -> bool:
88
total_chars = len(text)
99
if not text or total_chars > 10000:
1010
return False
1111
# 复制了文件
1212
if r"///" in text:
1313
return False
14-
english_chars = sum(1 for char in text if char.isalpha() and ('a' <= char.lower() <= 'z'))
15-
percentage = english_chars / total_chars
16-
return percentage >= 0.6
14+
if target == "zh":
15+
english_chars = sum(1 for char in text if char.isalpha() and ('a' <= char.lower() <= 'z'))
16+
percentage = english_chars / total_chars
17+
return percentage >= 0.6
18+
elif target == "en":
19+
english_chars = sum(1 for char in text if char.isalpha() and ('a' <= char.lower() <= 'z'))
20+
percentage = english_chars / total_chars
21+
return percentage <= 0.4
1722

1823
def data_cleaning(text: str) -> List[str]:
1924
texts = text.split("\n\n")
@@ -35,7 +40,7 @@ def set_ui(self, ui):
3540
def notify(self, text: str):
3641
self.ui.update_result(text)
3742

38-
# 翻译段落、单词,结果为空表明不满足翻译要求
43+
# 翻译英文段落、单词为中文,结果为空表明不满足翻译要求
3944
def translate(self, text) -> bool:
4045
if not is_translation_needed(text):
4146
return False

0 commit comments

Comments
 (0)