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.

Files changed (85) hide show
  1. Supervertaler.py +47886 -0
  2. modules/__init__.py +10 -0
  3. modules/ai_actions.py +964 -0
  4. modules/ai_attachment_manager.py +343 -0
  5. modules/ai_file_viewer_dialog.py +210 -0
  6. modules/autofingers_engine.py +466 -0
  7. modules/cafetran_docx_handler.py +379 -0
  8. modules/config_manager.py +469 -0
  9. modules/database_manager.py +1878 -0
  10. modules/database_migrations.py +417 -0
  11. modules/dejavurtf_handler.py +779 -0
  12. modules/document_analyzer.py +427 -0
  13. modules/docx_handler.py +689 -0
  14. modules/encoding_repair.py +319 -0
  15. modules/encoding_repair_Qt.py +393 -0
  16. modules/encoding_repair_ui.py +481 -0
  17. modules/feature_manager.py +350 -0
  18. modules/figure_context_manager.py +340 -0
  19. modules/file_dialog_helper.py +148 -0
  20. modules/find_replace.py +164 -0
  21. modules/find_replace_qt.py +457 -0
  22. modules/glossary_manager.py +433 -0
  23. modules/image_extractor.py +188 -0
  24. modules/keyboard_shortcuts_widget.py +571 -0
  25. modules/llm_clients.py +1211 -0
  26. modules/llm_leaderboard.py +737 -0
  27. modules/llm_superbench_ui.py +1401 -0
  28. modules/local_llm_setup.py +1104 -0
  29. modules/model_update_dialog.py +381 -0
  30. modules/model_version_checker.py +373 -0
  31. modules/mqxliff_handler.py +638 -0
  32. modules/non_translatables_manager.py +743 -0
  33. modules/pdf_rescue_Qt.py +1822 -0
  34. modules/pdf_rescue_tkinter.py +909 -0
  35. modules/phrase_docx_handler.py +516 -0
  36. modules/project_home_panel.py +209 -0
  37. modules/prompt_assistant.py +357 -0
  38. modules/prompt_library.py +689 -0
  39. modules/prompt_library_migration.py +447 -0
  40. modules/quick_access_sidebar.py +282 -0
  41. modules/ribbon_widget.py +597 -0
  42. modules/sdlppx_handler.py +874 -0
  43. modules/setup_wizard.py +353 -0
  44. modules/shortcut_manager.py +932 -0
  45. modules/simple_segmenter.py +128 -0
  46. modules/spellcheck_manager.py +727 -0
  47. modules/statuses.py +207 -0
  48. modules/style_guide_manager.py +315 -0
  49. modules/superbench_ui.py +1319 -0
  50. modules/superbrowser.py +329 -0
  51. modules/supercleaner.py +600 -0
  52. modules/supercleaner_ui.py +444 -0
  53. modules/superdocs.py +19 -0
  54. modules/superdocs_viewer_qt.py +382 -0
  55. modules/superlookup.py +252 -0
  56. modules/tag_cleaner.py +260 -0
  57. modules/tag_manager.py +333 -0
  58. modules/term_extractor.py +270 -0
  59. modules/termbase_entry_editor.py +842 -0
  60. modules/termbase_import_export.py +488 -0
  61. modules/termbase_manager.py +1060 -0
  62. modules/termview_widget.py +1172 -0
  63. modules/theme_manager.py +499 -0
  64. modules/tm_editor_dialog.py +99 -0
  65. modules/tm_manager_qt.py +1280 -0
  66. modules/tm_metadata_manager.py +545 -0
  67. modules/tmx_editor.py +1461 -0
  68. modules/tmx_editor_qt.py +2784 -0
  69. modules/tmx_generator.py +284 -0
  70. modules/tracked_changes.py +900 -0
  71. modules/trados_docx_handler.py +430 -0
  72. modules/translation_memory.py +715 -0
  73. modules/translation_results_panel.py +2134 -0
  74. modules/translation_services.py +282 -0
  75. modules/unified_prompt_library.py +659 -0
  76. modules/unified_prompt_manager_qt.py +3951 -0
  77. modules/voice_commands.py +920 -0
  78. modules/voice_dictation.py +477 -0
  79. modules/voice_dictation_lite.py +249 -0
  80. supervertaler-1.9.153.dist-info/METADATA +896 -0
  81. supervertaler-1.9.153.dist-info/RECORD +85 -0
  82. supervertaler-1.9.153.dist-info/WHEEL +5 -0
  83. supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
  84. supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
  85. 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
+ }