supervertaler 1.9.153__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervertaler might be problematic. Click here for more details.
- Supervertaler.py +47886 -0
- modules/__init__.py +10 -0
- modules/ai_actions.py +964 -0
- modules/ai_attachment_manager.py +343 -0
- modules/ai_file_viewer_dialog.py +210 -0
- modules/autofingers_engine.py +466 -0
- modules/cafetran_docx_handler.py +379 -0
- modules/config_manager.py +469 -0
- modules/database_manager.py +1878 -0
- modules/database_migrations.py +417 -0
- modules/dejavurtf_handler.py +779 -0
- modules/document_analyzer.py +427 -0
- modules/docx_handler.py +689 -0
- modules/encoding_repair.py +319 -0
- modules/encoding_repair_Qt.py +393 -0
- modules/encoding_repair_ui.py +481 -0
- modules/feature_manager.py +350 -0
- modules/figure_context_manager.py +340 -0
- modules/file_dialog_helper.py +148 -0
- modules/find_replace.py +164 -0
- modules/find_replace_qt.py +457 -0
- modules/glossary_manager.py +433 -0
- modules/image_extractor.py +188 -0
- modules/keyboard_shortcuts_widget.py +571 -0
- modules/llm_clients.py +1211 -0
- modules/llm_leaderboard.py +737 -0
- modules/llm_superbench_ui.py +1401 -0
- modules/local_llm_setup.py +1104 -0
- modules/model_update_dialog.py +381 -0
- modules/model_version_checker.py +373 -0
- modules/mqxliff_handler.py +638 -0
- modules/non_translatables_manager.py +743 -0
- modules/pdf_rescue_Qt.py +1822 -0
- modules/pdf_rescue_tkinter.py +909 -0
- modules/phrase_docx_handler.py +516 -0
- modules/project_home_panel.py +209 -0
- modules/prompt_assistant.py +357 -0
- modules/prompt_library.py +689 -0
- modules/prompt_library_migration.py +447 -0
- modules/quick_access_sidebar.py +282 -0
- modules/ribbon_widget.py +597 -0
- modules/sdlppx_handler.py +874 -0
- modules/setup_wizard.py +353 -0
- modules/shortcut_manager.py +932 -0
- modules/simple_segmenter.py +128 -0
- modules/spellcheck_manager.py +727 -0
- modules/statuses.py +207 -0
- modules/style_guide_manager.py +315 -0
- modules/superbench_ui.py +1319 -0
- modules/superbrowser.py +329 -0
- modules/supercleaner.py +600 -0
- modules/supercleaner_ui.py +444 -0
- modules/superdocs.py +19 -0
- modules/superdocs_viewer_qt.py +382 -0
- modules/superlookup.py +252 -0
- modules/tag_cleaner.py +260 -0
- modules/tag_manager.py +333 -0
- modules/term_extractor.py +270 -0
- modules/termbase_entry_editor.py +842 -0
- modules/termbase_import_export.py +488 -0
- modules/termbase_manager.py +1060 -0
- modules/termview_widget.py +1172 -0
- modules/theme_manager.py +499 -0
- modules/tm_editor_dialog.py +99 -0
- modules/tm_manager_qt.py +1280 -0
- modules/tm_metadata_manager.py +545 -0
- modules/tmx_editor.py +1461 -0
- modules/tmx_editor_qt.py +2784 -0
- modules/tmx_generator.py +284 -0
- modules/tracked_changes.py +900 -0
- modules/trados_docx_handler.py +430 -0
- modules/translation_memory.py +715 -0
- modules/translation_results_panel.py +2134 -0
- modules/translation_services.py +282 -0
- modules/unified_prompt_library.py +659 -0
- modules/unified_prompt_manager_qt.py +3951 -0
- modules/voice_commands.py +920 -0
- modules/voice_dictation.py +477 -0
- modules/voice_dictation_lite.py +249 -0
- supervertaler-1.9.153.dist-info/METADATA +896 -0
- supervertaler-1.9.153.dist-info/RECORD +85 -0
- supervertaler-1.9.153.dist-info/WHEEL +5 -0
- supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.153.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Deprecated Superdocs viewer module.
|
|
3
|
+
|
|
4
|
+
The in-app Superdocs viewer was removed in favor of the online GitBook documentation.
|
|
5
|
+
This file is kept as a small shim to prevent import-time crashes in older packaged builds.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __getattr__(name):
|
|
9
|
+
raise ImportError(
|
|
10
|
+
"The 'modules.superdocs_viewer_qt' module has been removed. Visit https://supervertaler.gitbook.io/superdocs/ for documentation."
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__: list[str] = []
|
|
15
|
+
|
|
16
|
+
# NOTE: The remainder of this file contains legacy code that is intentionally kept
|
|
17
|
+
# as inert source text to avoid import-time crashes in older packaged builds.
|
|
18
|
+
_DEPRECATED_SOURCE = r'''
|
|
19
|
+
self.open_external_btn.clicked.connect(self.open_in_external_editor)
|
|
20
|
+
self.open_external_btn.setEnabled(False)
|
|
21
|
+
title_bar_layout.addWidget(self.open_external_btn)
|
|
22
|
+
|
|
23
|
+
viewer_layout.addLayout(title_bar_layout)
|
|
24
|
+
|
|
25
|
+
# Markdown viewer
|
|
26
|
+
self.doc_viewer = QTextBrowser()
|
|
27
|
+
self.doc_viewer.setOpenExternalLinks(False)
|
|
28
|
+
self.doc_viewer.anchorClicked.connect(self.on_link_clicked)
|
|
29
|
+
viewer_layout.addWidget(self.doc_viewer) # This gets all the stretch
|
|
30
|
+
|
|
31
|
+
splitter.addWidget(viewer_widget)
|
|
32
|
+
|
|
33
|
+
# Set splitter proportions (25% tree, 75% viewer for more reading space)
|
|
34
|
+
splitter.setSizes([250, 750])
|
|
35
|
+
|
|
36
|
+
main_layout.addWidget(splitter, 1) # 1 = stretch to fill available space
|
|
37
|
+
|
|
38
|
+
def load_documentation_tree(self):
|
|
39
|
+
"""Load documentation structure into tree view"""
|
|
40
|
+
self.doc_tree.clear()
|
|
41
|
+
|
|
42
|
+
if not self.docs_dir.exists():
|
|
43
|
+
self.status_label.setText("⚠ Documentation not generated yet. Click 'Generate Documentation' to create it.")
|
|
44
|
+
self.stats_label.setText("No documentation files found")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Add root-level documents
|
|
48
|
+
root_files = ["index.md", "architecture.md", "dependencies.md"]
|
|
49
|
+
file_count = 0
|
|
50
|
+
|
|
51
|
+
for filename in root_files:
|
|
52
|
+
file_path = self.docs_dir / filename
|
|
53
|
+
if file_path.exists():
|
|
54
|
+
icon = "📄"
|
|
55
|
+
if filename == "index.md":
|
|
56
|
+
icon = "🏠"
|
|
57
|
+
elif filename == "architecture.md":
|
|
58
|
+
icon = "🏗"
|
|
59
|
+
elif filename == "dependencies.md":
|
|
60
|
+
icon = "🔗"
|
|
61
|
+
|
|
62
|
+
item = QTreeWidgetItem([f"{icon} {filename[:-3].title()}"])
|
|
63
|
+
item.setData(0, Qt.ItemDataRole.UserRole, str(file_path))
|
|
64
|
+
self.doc_tree.addTopLevelItem(item)
|
|
65
|
+
file_count += 1
|
|
66
|
+
|
|
67
|
+
# Add modules folder
|
|
68
|
+
modules_dir = self.docs_dir / "modules"
|
|
69
|
+
if modules_dir.exists():
|
|
70
|
+
modules_item = QTreeWidgetItem(["📁 Modules"])
|
|
71
|
+
self.doc_tree.addTopLevelItem(modules_item)
|
|
72
|
+
|
|
73
|
+
# Add all module documentation files
|
|
74
|
+
module_files = sorted(modules_dir.glob("*.md"))
|
|
75
|
+
for module_file in module_files:
|
|
76
|
+
item = QTreeWidgetItem([f"📄 {module_file.stem}"])
|
|
77
|
+
item.setData(0, Qt.ItemDataRole.UserRole, str(module_file))
|
|
78
|
+
modules_item.addChild(item)
|
|
79
|
+
file_count += 1
|
|
80
|
+
|
|
81
|
+
modules_item.setExpanded(True)
|
|
82
|
+
|
|
83
|
+
# Update stats
|
|
84
|
+
self.stats_label.setText(f"📊 {file_count} documentation files")
|
|
85
|
+
self.status_label.setText(f"Documentation loaded - Last generated: {self.get_last_generation_time()}")
|
|
86
|
+
|
|
87
|
+
# Auto-select index if available
|
|
88
|
+
if self.doc_tree.topLevelItemCount() > 0:
|
|
89
|
+
first_item = self.doc_tree.topLevelItem(0)
|
|
90
|
+
self.doc_tree.setCurrentItem(first_item)
|
|
91
|
+
self.on_tree_item_clicked(first_item, 0)
|
|
92
|
+
|
|
93
|
+
def get_last_generation_time(self):
|
|
94
|
+
"""Get timestamp of last documentation generation"""
|
|
95
|
+
index_file = self.docs_dir / "index.md"
|
|
96
|
+
if index_file.exists():
|
|
97
|
+
timestamp = datetime.fromtimestamp(index_file.stat().st_mtime)
|
|
98
|
+
return timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
|
99
|
+
return "Unknown"
|
|
100
|
+
|
|
101
|
+
def on_tree_item_clicked(self, item, column):
|
|
102
|
+
"""Handle tree item click - load and display document"""
|
|
103
|
+
file_path = item.data(0, Qt.ItemDataRole.UserRole)
|
|
104
|
+
|
|
105
|
+
if not file_path:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
self.current_file = Path(file_path)
|
|
109
|
+
self.load_document(self.current_file)
|
|
110
|
+
|
|
111
|
+
def load_document(self, file_path):
|
|
112
|
+
"""Load and display markdown document"""
|
|
113
|
+
try:
|
|
114
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
115
|
+
markdown_content = f.read()
|
|
116
|
+
|
|
117
|
+
# Convert markdown to HTML
|
|
118
|
+
html_content = markdown.markdown(
|
|
119
|
+
markdown_content,
|
|
120
|
+
extensions=['extra', 'codehilite', 'tables', 'fenced_code']
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Add CSS styling for better readability
|
|
124
|
+
styled_html = f"""
|
|
125
|
+
<html>
|
|
126
|
+
<head>
|
|
127
|
+
<style>
|
|
128
|
+
body {{
|
|
129
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
130
|
+
line-height: 1.6;
|
|
131
|
+
padding: 20px;
|
|
132
|
+
color: #333;
|
|
133
|
+
}}
|
|
134
|
+
h1 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }}
|
|
135
|
+
h2 {{ color: #34495e; border-bottom: 1px solid #bdc3c7; padding-bottom: 5px; margin-top: 30px; }}
|
|
136
|
+
h3 {{ color: #555; margin-top: 20px; }}
|
|
137
|
+
code {{
|
|
138
|
+
background-color: #f4f4f4;
|
|
139
|
+
padding: 2px 6px;
|
|
140
|
+
border-radius: 3px;
|
|
141
|
+
font-family: "Courier New", monospace;
|
|
142
|
+
}}
|
|
143
|
+
pre {{
|
|
144
|
+
background-color: #f8f8f8;
|
|
145
|
+
border: 1px solid #ddd;
|
|
146
|
+
border-radius: 5px;
|
|
147
|
+
padding: 15px;
|
|
148
|
+
overflow-x: auto;
|
|
149
|
+
}}
|
|
150
|
+
table {{
|
|
151
|
+
border-collapse: collapse;
|
|
152
|
+
width: 100%;
|
|
153
|
+
margin: 20px 0;
|
|
154
|
+
}}
|
|
155
|
+
th, td {{
|
|
156
|
+
border: 1px solid #ddd;
|
|
157
|
+
padding: 12px;
|
|
158
|
+
text-align: left;
|
|
159
|
+
}}
|
|
160
|
+
th {{
|
|
161
|
+
background-color: #3498db;
|
|
162
|
+
color: white;
|
|
163
|
+
}}
|
|
164
|
+
tr:nth-child(even) {{ background-color: #f9f9f9; }}
|
|
165
|
+
a {{ color: #3498db; text-decoration: none; }}
|
|
166
|
+
a:hover {{ text-decoration: underline; }}
|
|
167
|
+
blockquote {{
|
|
168
|
+
border-left: 4px solid #3498db;
|
|
169
|
+
padding-left: 15px;
|
|
170
|
+
color: #666;
|
|
171
|
+
font-style: italic;
|
|
172
|
+
}}
|
|
173
|
+
</style>
|
|
174
|
+
</head>
|
|
175
|
+
<body>
|
|
176
|
+
{html_content}
|
|
177
|
+
</body>
|
|
178
|
+
</html>
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
self.doc_viewer.setHtml(styled_html)
|
|
182
|
+
self.doc_title_label.setText(f"📄 {file_path.stem}")
|
|
183
|
+
self.open_external_btn.setEnabled(True)
|
|
184
|
+
|
|
185
|
+
# Update word count
|
|
186
|
+
word_count = len(markdown_content.split())
|
|
187
|
+
self.word_count_label.setText(f"📝 {word_count:,} words")
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.doc_viewer.setPlainText(f"Error loading document: {e}")
|
|
191
|
+
self.doc_title_label.setText("Error")
|
|
192
|
+
self.open_external_btn.setEnabled(False)
|
|
193
|
+
|
|
194
|
+
def on_link_clicked(self, url):
|
|
195
|
+
"""Handle clicks on internal documentation links"""
|
|
196
|
+
url_str = url.toString()
|
|
197
|
+
|
|
198
|
+
# Handle relative links to other documentation files
|
|
199
|
+
if url_str.endswith('.md'):
|
|
200
|
+
target_file = self.docs_dir / url_str
|
|
201
|
+
if target_file.exists():
|
|
202
|
+
self.load_document(target_file)
|
|
203
|
+
# Update tree selection
|
|
204
|
+
self.select_tree_item_by_path(target_file)
|
|
205
|
+
else:
|
|
206
|
+
# External links - open in browser
|
|
207
|
+
import webbrowser
|
|
208
|
+
webbrowser.open(url_str)
|
|
209
|
+
|
|
210
|
+
def select_tree_item_by_path(self, file_path):
|
|
211
|
+
"""Select tree item by file path"""
|
|
212
|
+
iterator = QTreeWidgetItem(self.doc_tree.invisibleRootItem())
|
|
213
|
+
for i in range(self.doc_tree.topLevelItemCount()):
|
|
214
|
+
item = self.doc_tree.topLevelItem(i)
|
|
215
|
+
if self._check_item_path(item, file_path):
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
def _check_item_path(self, item, target_path):
|
|
219
|
+
"""Recursively check item and children for matching path"""
|
|
220
|
+
item_path = item.data(0, Qt.ItemDataRole.UserRole)
|
|
221
|
+
if item_path and Path(item_path) == target_path:
|
|
222
|
+
self.doc_tree.setCurrentItem(item)
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
for i in range(item.childCount()):
|
|
226
|
+
if self._check_item_path(item.child(i), target_path):
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
def open_in_external_editor(self):
|
|
232
|
+
"""Open current document in external editor"""
|
|
233
|
+
if not self.current_file or not self.current_file.exists():
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
import subprocess
|
|
237
|
+
import sys
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
if sys.platform == 'win32':
|
|
241
|
+
subprocess.run(['start', '', str(self.current_file)], shell=True)
|
|
242
|
+
elif sys.platform == 'darwin':
|
|
243
|
+
subprocess.run(['open', str(self.current_file)])
|
|
244
|
+
else:
|
|
245
|
+
subprocess.run(['xdg-open', str(self.current_file)])
|
|
246
|
+
except Exception as e:
|
|
247
|
+
QMessageBox.warning(self, "Error", f"Could not open file in external editor: {e}")
|
|
248
|
+
|
|
249
|
+
def generate_documentation(self):
|
|
250
|
+
"""Generate documentation in background thread"""
|
|
251
|
+
if self.generator_thread and self.generator_thread.isRunning():
|
|
252
|
+
QMessageBox.information(self, "In Progress", "Documentation generation is already running.")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Confirm action
|
|
256
|
+
reply = QMessageBox.question(
|
|
257
|
+
self,
|
|
258
|
+
"Generate Documentation",
|
|
259
|
+
"This will scan the entire codebase and regenerate all documentation files.\n\n"
|
|
260
|
+
"This may take a few moments. Continue?",
|
|
261
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# Disable buttons during generation
|
|
268
|
+
self.generate_btn.setEnabled(False)
|
|
269
|
+
self.refresh_btn.setEnabled(False)
|
|
270
|
+
self.progress_bar.setVisible(True)
|
|
271
|
+
self.progress_bar.setRange(0, 0) # Indeterminate progress
|
|
272
|
+
|
|
273
|
+
# Start generation thread
|
|
274
|
+
self.generator_thread = SuperdocsGeneratorThread()
|
|
275
|
+
self.generator_thread.progress.connect(self.on_generation_progress)
|
|
276
|
+
self.generator_thread.finished.connect(self.on_generation_finished)
|
|
277
|
+
self.generator_thread.start()
|
|
278
|
+
|
|
279
|
+
def on_generation_progress(self, message):
|
|
280
|
+
"""Update progress status"""
|
|
281
|
+
self.status_label.setText(f"⏳ {message}")
|
|
282
|
+
self.progress_bar.setFormat(message)
|
|
283
|
+
|
|
284
|
+
def on_generation_finished(self, success, message):
|
|
285
|
+
"""Handle generation completion"""
|
|
286
|
+
# Re-enable buttons
|
|
287
|
+
self.generate_btn.setEnabled(True)
|
|
288
|
+
self.refresh_btn.setEnabled(True)
|
|
289
|
+
self.progress_bar.setVisible(False)
|
|
290
|
+
|
|
291
|
+
if success:
|
|
292
|
+
self.status_label.setText(f"✅ {message}")
|
|
293
|
+
QMessageBox.information(self, "Success", message)
|
|
294
|
+
# Reload documentation tree
|
|
295
|
+
self.load_documentation_tree()
|
|
296
|
+
else:
|
|
297
|
+
self.status_label.setText(f"❌ {message}")
|
|
298
|
+
QMessageBox.critical(self, "Error", message)
|
|
299
|
+
|
|
300
|
+
self.generator_thread = None
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class CheckmarkCheckBox(QCheckBox):
|
|
304
|
+
"""Custom checkbox with green background and white checkmark when checked"""
|
|
305
|
+
|
|
306
|
+
def __init__(self, text="", parent=None):
|
|
307
|
+
super().__init__(text, parent)
|
|
308
|
+
self.setCheckable(True)
|
|
309
|
+
self.setEnabled(True)
|
|
310
|
+
self.setStyleSheet("""
|
|
311
|
+
QCheckBox {
|
|
312
|
+
font-size: 9pt;
|
|
313
|
+
spacing: 6px;
|
|
314
|
+
}
|
|
315
|
+
QCheckBox::indicator {
|
|
316
|
+
width: 16px;
|
|
317
|
+
height: 16px;
|
|
318
|
+
border: 2px solid #999;
|
|
319
|
+
border-radius: 3px;
|
|
320
|
+
background-color: white;
|
|
321
|
+
}
|
|
322
|
+
QCheckBox::indicator:checked {
|
|
323
|
+
background-color: #4CAF50;
|
|
324
|
+
border-color: #4CAF50;
|
|
325
|
+
}
|
|
326
|
+
QCheckBox::indicator:hover {
|
|
327
|
+
border-color: #666;
|
|
328
|
+
}
|
|
329
|
+
QCheckBox::indicator:checked:hover {
|
|
330
|
+
background-color: #45a049;
|
|
331
|
+
border-color: #45a049;
|
|
332
|
+
}
|
|
333
|
+
""")
|
|
334
|
+
|
|
335
|
+
def paintEvent(self, event):
|
|
336
|
+
"""Override paint event to draw white checkmark when checked"""
|
|
337
|
+
super().paintEvent(event)
|
|
338
|
+
|
|
339
|
+
if self.isChecked():
|
|
340
|
+
from PyQt6.QtWidgets import QStyleOptionButton
|
|
341
|
+
from PyQt6.QtGui import QPainter, QPen, QColor
|
|
342
|
+
from PyQt6.QtCore import QPointF, Qt
|
|
343
|
+
|
|
344
|
+
opt = QStyleOptionButton()
|
|
345
|
+
self.initStyleOption(opt)
|
|
346
|
+
indicator_rect = self.style().subElementRect(
|
|
347
|
+
self.style().SubElement.SE_CheckBoxIndicator,
|
|
348
|
+
opt,
|
|
349
|
+
self
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if indicator_rect.isValid():
|
|
353
|
+
painter = QPainter(self)
|
|
354
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
355
|
+
pen_width = max(2.0, min(indicator_rect.width(), indicator_rect.height()) * 0.12)
|
|
356
|
+
painter.setPen(QPen(QColor(255, 255, 255), pen_width, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin))
|
|
357
|
+
painter.setBrush(QColor(255, 255, 255))
|
|
358
|
+
|
|
359
|
+
x = indicator_rect.x()
|
|
360
|
+
y = indicator_rect.y()
|
|
361
|
+
w = indicator_rect.width()
|
|
362
|
+
h = indicator_rect.height()
|
|
363
|
+
|
|
364
|
+
padding = min(w, h) * 0.15
|
|
365
|
+
x += padding
|
|
366
|
+
y += padding
|
|
367
|
+
w -= padding * 2
|
|
368
|
+
h -= padding * 2
|
|
369
|
+
|
|
370
|
+
check_x1 = x + w * 0.10
|
|
371
|
+
check_y1 = y + h * 0.50
|
|
372
|
+
check_x2 = x + w * 0.35
|
|
373
|
+
check_y2 = y + h * 0.70
|
|
374
|
+
check_x3 = x + w * 0.90
|
|
375
|
+
check_y3 = y + h * 0.25
|
|
376
|
+
|
|
377
|
+
painter.drawLine(QPointF(check_x2, check_y2), QPointF(check_x3, check_y3))
|
|
378
|
+
painter.drawLine(QPointF(check_x1, check_y1), QPointF(check_x2, check_y2))
|
|
379
|
+
|
|
380
|
+
painter.end()
|
|
381
|
+
|
|
382
|
+
'''
|
modules/superlookup.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Superlookup Engine
|
|
3
|
+
==================
|
|
4
|
+
System-wide translation lookup that works anywhere on your computer.
|
|
5
|
+
Captures text from any application and provides:
|
|
6
|
+
- TM matches from Supervertaler database
|
|
7
|
+
- Glossary term lookups
|
|
8
|
+
- MT/AI translations
|
|
9
|
+
- Web search integration
|
|
10
|
+
|
|
11
|
+
Can operate in different modes:
|
|
12
|
+
- memoQ mode (with CAT tool shortcuts)
|
|
13
|
+
- Trados mode
|
|
14
|
+
- CafeTran mode
|
|
15
|
+
- Universal mode (works in any text box)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import pyperclip
|
|
19
|
+
import time
|
|
20
|
+
from typing import Dict, List, Tuple, Optional
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class LookupResult:
|
|
26
|
+
"""Single lookup result"""
|
|
27
|
+
source: str
|
|
28
|
+
target: str
|
|
29
|
+
match_percent: int
|
|
30
|
+
source_type: str # 'tm', 'glossary', 'mt', 'ai'
|
|
31
|
+
metadata: Dict = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if self.metadata is None:
|
|
35
|
+
self.metadata = {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SuperlookupEngine:
|
|
39
|
+
"""
|
|
40
|
+
Superlookup text lookup engine.
|
|
41
|
+
Captures text from any application and provides translation results.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, mode='universal'):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the lookup engine.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
mode: Operating mode - 'memoq', 'trados', 'cafetran', or 'universal'
|
|
50
|
+
"""
|
|
51
|
+
self.mode = mode
|
|
52
|
+
self.tm_database = None
|
|
53
|
+
self.glossary_database = None
|
|
54
|
+
self.mt_providers = []
|
|
55
|
+
|
|
56
|
+
# Mode-specific shortcuts
|
|
57
|
+
self.mode_shortcuts = {
|
|
58
|
+
'memoq': {
|
|
59
|
+
'copy_source_to_target': ('ctrl', 'shift', 's'),
|
|
60
|
+
'select_all': ('ctrl', 'a'),
|
|
61
|
+
'copy': ('ctrl', 'c'),
|
|
62
|
+
'paste': ('ctrl', 'v'),
|
|
63
|
+
},
|
|
64
|
+
'trados': {
|
|
65
|
+
'copy_source_to_target': ('ctrl', 'insert'), # Example - verify
|
|
66
|
+
'select_all': ('ctrl', 'a'),
|
|
67
|
+
'copy': ('ctrl', 'c'),
|
|
68
|
+
'paste': ('ctrl', 'v'),
|
|
69
|
+
},
|
|
70
|
+
'cafetran': {
|
|
71
|
+
'copy_source_to_target': ('ctrl', 'g'), # Example - verify
|
|
72
|
+
'select_all': ('ctrl', 'a'),
|
|
73
|
+
'copy': ('ctrl', 'c'),
|
|
74
|
+
'paste': ('ctrl', 'v'),
|
|
75
|
+
},
|
|
76
|
+
'universal': {
|
|
77
|
+
'select_all': ('ctrl', 'a'),
|
|
78
|
+
'copy': ('ctrl', 'c'),
|
|
79
|
+
'paste': ('ctrl', 'v'),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def capture_text(self) -> Optional[str]:
|
|
84
|
+
"""
|
|
85
|
+
Capture text - just copy what's selected and get clipboard.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Captured text or None if failed
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
import keyboard
|
|
92
|
+
|
|
93
|
+
# Wait for hotkey to release before sending Ctrl+C
|
|
94
|
+
time.sleep(0.2)
|
|
95
|
+
|
|
96
|
+
# Use keyboard library to send Ctrl+C
|
|
97
|
+
keyboard.press_and_release('ctrl+c')
|
|
98
|
+
time.sleep(0.2)
|
|
99
|
+
|
|
100
|
+
# Get clipboard
|
|
101
|
+
text = pyperclip.paste()
|
|
102
|
+
return text if text else None
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"Error capturing text: {e}")
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def set_tm_database(self, tm_db):
|
|
109
|
+
"""Set the TM database for lookups"""
|
|
110
|
+
self.tm_database = tm_db
|
|
111
|
+
|
|
112
|
+
def set_enabled_tm_ids(self, tm_ids: List[str]):
|
|
113
|
+
"""Set which TM IDs to search (for independent Superlookup selection)"""
|
|
114
|
+
self.enabled_tm_ids = tm_ids if tm_ids else None
|
|
115
|
+
|
|
116
|
+
def set_glossary_database(self, glossary_db):
|
|
117
|
+
"""Set the glossary database for term lookups"""
|
|
118
|
+
self.glossary_database = glossary_db
|
|
119
|
+
|
|
120
|
+
def search_tm(self, text: str, max_results: int = 10, direction: str = 'both',
|
|
121
|
+
source_lang: str = None, target_lang: str = None) -> List[LookupResult]:
|
|
122
|
+
"""
|
|
123
|
+
Search translation memory for matches.
|
|
124
|
+
Uses concordance search to find entries containing the search text.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
text: Source text to search for
|
|
128
|
+
max_results: Maximum number of results to return
|
|
129
|
+
direction: 'source' = search source only, 'target' = search target only, 'both' = bidirectional
|
|
130
|
+
source_lang: Filter by source language (None = any)
|
|
131
|
+
target_lang: Filter by target language (None = any)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of TM match results
|
|
135
|
+
"""
|
|
136
|
+
results = []
|
|
137
|
+
|
|
138
|
+
if not self.tm_database:
|
|
139
|
+
print(f"[DEBUG] SuperlookupEngine.search_tm: tm_database is None!")
|
|
140
|
+
return results
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Use concordance search - finds entries CONTAINING the search text
|
|
144
|
+
# This is better for Superlookup than fuzzy matching
|
|
145
|
+
tm_ids_to_use = self.enabled_tm_ids if hasattr(self, 'enabled_tm_ids') and self.enabled_tm_ids else None
|
|
146
|
+
print(f"[DEBUG] SuperlookupEngine.search_tm: Using concordance_search with tm_ids={tm_ids_to_use}, direction={direction}, source_lang={source_lang}, target_lang={target_lang}")
|
|
147
|
+
|
|
148
|
+
if hasattr(self.tm_database, 'concordance_search'):
|
|
149
|
+
matches = self.tm_database.concordance_search(
|
|
150
|
+
query=text,
|
|
151
|
+
tm_ids=tm_ids_to_use,
|
|
152
|
+
direction=direction,
|
|
153
|
+
source_lang=source_lang,
|
|
154
|
+
target_lang=target_lang
|
|
155
|
+
)
|
|
156
|
+
print(f"[DEBUG] SuperlookupEngine.search_tm: Concordance search returned {len(matches)} matches")
|
|
157
|
+
|
|
158
|
+
# Convert to LookupResult format (limit results)
|
|
159
|
+
for match in matches[:max_results]:
|
|
160
|
+
# Use 'source' and 'target' keys (matches database column names)
|
|
161
|
+
source_text = match.get('source', '')
|
|
162
|
+
target_text = match.get('target', '')
|
|
163
|
+
print(f"[Superlookup] Extracted: source='{source_text[:50]}...', target='{target_text[:50]}...'")
|
|
164
|
+
results.append(LookupResult(
|
|
165
|
+
source=source_text,
|
|
166
|
+
target=target_text,
|
|
167
|
+
match_percent=100, # Concordance = contains the text
|
|
168
|
+
source_type='tm',
|
|
169
|
+
metadata={
|
|
170
|
+
'match_type': 'concordance',
|
|
171
|
+
'tm_name': match.get('tm_name', 'Unknown'),
|
|
172
|
+
'tm_id': match.get('tm_id', '')
|
|
173
|
+
}
|
|
174
|
+
))
|
|
175
|
+
else:
|
|
176
|
+
print(f"[DEBUG] SuperlookupEngine.search_tm: tm_database has no concordance_search method!")
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"Error searching TM: {e}")
|
|
180
|
+
import traceback
|
|
181
|
+
traceback.print_exc()
|
|
182
|
+
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
def search_glossary(self, text: str) -> List[LookupResult]:
|
|
186
|
+
"""
|
|
187
|
+
Search glossary for term matches.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
text: Text to extract terms from and search
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
List of glossary term matches
|
|
194
|
+
"""
|
|
195
|
+
results = []
|
|
196
|
+
|
|
197
|
+
if not self.glossary_database:
|
|
198
|
+
return results
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
# Simple word-by-word lookup for now
|
|
202
|
+
# TODO: Implement proper term extraction
|
|
203
|
+
words = text.lower().split()
|
|
204
|
+
|
|
205
|
+
if hasattr(self.glossary_database, 'search_terms'):
|
|
206
|
+
terms = self.glossary_database.search_terms(words)
|
|
207
|
+
for term, translation in terms:
|
|
208
|
+
results.append(LookupResult(
|
|
209
|
+
source=term,
|
|
210
|
+
target=translation,
|
|
211
|
+
match_percent=100,
|
|
212
|
+
source_type='glossary',
|
|
213
|
+
metadata={'context': text}
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f"Error searching glossary: {e}")
|
|
218
|
+
|
|
219
|
+
return results
|
|
220
|
+
|
|
221
|
+
def get_mt_translations(self, text: str) -> List[LookupResult]:
|
|
222
|
+
"""
|
|
223
|
+
Get machine translation suggestions.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
text: Text to translate
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of MT results
|
|
230
|
+
"""
|
|
231
|
+
results = []
|
|
232
|
+
|
|
233
|
+
# TODO: Integrate with MT providers (DeepL, Google, OpenAI)
|
|
234
|
+
# For now, return placeholder
|
|
235
|
+
|
|
236
|
+
return results
|
|
237
|
+
|
|
238
|
+
def lookup_all(self, text: str) -> Dict[str, List[LookupResult]]:
|
|
239
|
+
"""
|
|
240
|
+
Perform all types of lookups on the text.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
text: Text to look up
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dictionary with 'tm', 'glossary', 'mt' keys containing results
|
|
247
|
+
"""
|
|
248
|
+
return {
|
|
249
|
+
'tm': self.search_tm(text),
|
|
250
|
+
'glossary': self.search_glossary(text),
|
|
251
|
+
'mt': self.get_mt_translations(text)
|
|
252
|
+
}
|