supervertaler 1.9.181__py3-none-any.whl → 1.9.184__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 +674 -416
- modules/extract_tm.py +518 -0
- modules/project_tm.py +320 -0
- modules/termbase_manager.py +0 -1
- modules/termview_widget.py +58 -29
- modules/translation_memory.py +3 -12
- modules/translation_results_panel.py +0 -7
- modules/unified_prompt_manager_qt.py +12 -0
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/METADATA +1 -1
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/RECORD +14 -12
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.181.dist-info → supervertaler-1.9.184.dist-info}/top_level.txt +0 -0
Supervertaler.py
CHANGED
|
@@ -32,9 +32,9 @@ License: MIT
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
# Version Information.
|
|
35
|
-
__version__ = "1.9.
|
|
35
|
+
__version__ = "1.9.184"
|
|
36
36
|
__phase__ = "0.9"
|
|
37
|
-
__release_date__ = "2026-01
|
|
37
|
+
__release_date__ = "2026-02-01"
|
|
38
38
|
__edition__ = "Qt"
|
|
39
39
|
|
|
40
40
|
import sys
|
|
@@ -1401,10 +1401,15 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1401
1401
|
self.setMouseTracking(True)
|
|
1402
1402
|
|
|
1403
1403
|
# Add syntax highlighter for tags (no spellcheck for source cells)
|
|
1404
|
-
# Get invisible char color from main window
|
|
1404
|
+
# Get invisible char color and tag color from main window (theme-aware)
|
|
1405
1405
|
main_window = self._get_main_window()
|
|
1406
1406
|
invisible_char_color = main_window.invisible_char_color if main_window and hasattr(main_window, 'invisible_char_color') else '#999999'
|
|
1407
|
-
|
|
1407
|
+
|
|
1408
|
+
# Use theme-aware tag color (light pink in dark mode, dark red in light mode)
|
|
1409
|
+
is_dark = main_window and hasattr(main_window, 'theme_manager') and main_window.theme_manager and main_window.theme_manager.current_theme.name == "Dark"
|
|
1410
|
+
tag_color = '#FFB6C1' if is_dark else self.tag_highlight_color # Light pink in dark mode
|
|
1411
|
+
|
|
1412
|
+
self.highlighter = TagHighlighter(self.document(), tag_color, invisible_char_color, enable_spellcheck=False)
|
|
1408
1413
|
|
|
1409
1414
|
# Store raw text (with tags) for mode switching
|
|
1410
1415
|
self._raw_text = text
|
|
@@ -1659,16 +1664,12 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1659
1664
|
matches_dict: Dictionary of {term: {'translation': str, 'priority': int}} or {term: str}
|
|
1660
1665
|
"""
|
|
1661
1666
|
from PyQt6.QtGui import QTextCursor, QTextCharFormat, QColor, QFont
|
|
1662
|
-
|
|
1663
|
-
print(f"[HIGHLIGHT DEBUG] highlight_termbase_matches called with {len(matches_dict) if matches_dict else 0} matches")
|
|
1664
|
-
|
|
1667
|
+
|
|
1665
1668
|
# Get the document and create a cursor
|
|
1666
1669
|
doc = self.document()
|
|
1667
1670
|
text = self.toPlainText()
|
|
1668
1671
|
text_lower = text.lower()
|
|
1669
1672
|
|
|
1670
|
-
print(f"[HIGHLIGHT DEBUG] Widget text length: {len(text)}, text preview: {text[:60]}...")
|
|
1671
|
-
|
|
1672
1673
|
# IMPORTANT: Always clear all previous formatting first to prevent inconsistent highlighting
|
|
1673
1674
|
cursor = QTextCursor(doc)
|
|
1674
1675
|
cursor.select(QTextCursor.SelectionType.Document)
|
|
@@ -1677,7 +1678,6 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1677
1678
|
|
|
1678
1679
|
# If no matches, we're done (highlighting has been cleared)
|
|
1679
1680
|
if not matches_dict:
|
|
1680
|
-
print(f"[HIGHLIGHT DEBUG] No matches, returning after clear")
|
|
1681
1681
|
return
|
|
1682
1682
|
|
|
1683
1683
|
# Get highlight style from main window settings
|
|
@@ -1695,9 +1695,7 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1695
1695
|
dotted_color = settings.get('termbase_dotted_color', '#808080')
|
|
1696
1696
|
break
|
|
1697
1697
|
parent = parent.parent() if hasattr(parent, 'parent') else None
|
|
1698
|
-
|
|
1699
|
-
print(f"[HIGHLIGHT DEBUG] Using style: {highlight_style}")
|
|
1700
|
-
|
|
1698
|
+
|
|
1701
1699
|
# Sort matches by source term length (longest first) to avoid partial matches
|
|
1702
1700
|
# Since dict keys are now term_ids, we need to extract source terms first
|
|
1703
1701
|
term_entries = []
|
|
@@ -1706,11 +1704,7 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1706
1704
|
source_term = match_info.get('source', '')
|
|
1707
1705
|
if source_term:
|
|
1708
1706
|
term_entries.append((source_term, term_id, match_info))
|
|
1709
|
-
|
|
1710
|
-
print(f"[HIGHLIGHT DEBUG] Built {len(term_entries)} term entries from matches")
|
|
1711
|
-
if term_entries:
|
|
1712
|
-
print(f"[HIGHLIGHT DEBUG] First few terms to search: {[t[0] for t in term_entries[:3]]}")
|
|
1713
|
-
|
|
1707
|
+
|
|
1714
1708
|
# Sort by source term length (longest first)
|
|
1715
1709
|
term_entries.sort(key=lambda x: len(x[0]), reverse=True)
|
|
1716
1710
|
|
|
@@ -1835,8 +1829,6 @@ class ReadOnlyGridTextEditor(QTextEdit):
|
|
|
1835
1829
|
highlighted_ranges.append((idx, end_idx))
|
|
1836
1830
|
|
|
1837
1831
|
start = end_idx
|
|
1838
|
-
|
|
1839
|
-
print(f"[HIGHLIGHT DEBUG] Applied formatting to {found_count} term occurrences in text")
|
|
1840
1832
|
|
|
1841
1833
|
def highlight_non_translatables(self, nt_matches: list, highlighted_ranges: list = None):
|
|
1842
1834
|
"""
|
|
@@ -2739,10 +2731,15 @@ class EditableGridTextEditor(QTextEdit):
|
|
|
2739
2731
|
self.setPalette(palette)
|
|
2740
2732
|
|
|
2741
2733
|
# Add syntax highlighter for tags (with spellcheck enabled for target cells)
|
|
2742
|
-
# Get invisible char color from main window
|
|
2734
|
+
# Get invisible char color and tag color from main window (theme-aware)
|
|
2743
2735
|
main_window = self._get_main_window()
|
|
2744
2736
|
invisible_char_color = main_window.invisible_char_color if main_window and hasattr(main_window, 'invisible_char_color') else '#999999'
|
|
2745
|
-
|
|
2737
|
+
|
|
2738
|
+
# Use theme-aware tag color (light pink in dark mode, dark red in light mode)
|
|
2739
|
+
is_dark = main_window and hasattr(main_window, 'theme_manager') and main_window.theme_manager and main_window.theme_manager.current_theme.name == "Dark"
|
|
2740
|
+
tag_color = '#FFB6C1' if is_dark else self.tag_highlight_color # Light pink in dark mode
|
|
2741
|
+
|
|
2742
|
+
self.highlighter = TagHighlighter(self.document(), tag_color, invisible_char_color, enable_spellcheck=True)
|
|
2746
2743
|
|
|
2747
2744
|
# Style to look like a normal cell with subtle selection
|
|
2748
2745
|
# Background and text colors now managed by theme system
|
|
@@ -6224,6 +6221,12 @@ class SupervertalerQt(QMainWindow):
|
|
|
6224
6221
|
self.termbase_cache_lock = threading.Lock() # Thread-safe cache access
|
|
6225
6222
|
self.termbase_batch_worker_thread = None # Background worker thread
|
|
6226
6223
|
self.termbase_batch_stop_event = threading.Event() # Signal to stop background worker
|
|
6224
|
+
|
|
6225
|
+
# In-memory termbase index for instant lookups (v1.9.182)
|
|
6226
|
+
# Loaded once on project load, contains ALL terms from activated termbases
|
|
6227
|
+
# Structure: list of term dicts with pre-compiled regex patterns
|
|
6228
|
+
self.termbase_index = []
|
|
6229
|
+
self.termbase_index_lock = threading.Lock()
|
|
6227
6230
|
|
|
6228
6231
|
# TM/MT/LLM prefetch cache for instant segment switching (like memoQ)
|
|
6229
6232
|
# Maps segment ID → {"TM": [...], "MT": [...], "LLM": [...]}
|
|
@@ -6237,9 +6240,9 @@ class SupervertalerQt(QMainWindow):
|
|
|
6237
6240
|
self.idle_prefetch_timer = None # QTimer for triggering prefetch after typing pause
|
|
6238
6241
|
self.idle_prefetch_delay_ms = 1500 # Start prefetch 1.5s after user stops typing
|
|
6239
6242
|
|
|
6240
|
-
#
|
|
6243
|
+
# Cache kill switch for performance testing
|
|
6241
6244
|
# When True, all caches are bypassed - direct lookups every time
|
|
6242
|
-
self.disable_all_caches =
|
|
6245
|
+
self.disable_all_caches = False # v1.9.183: Default to False (caches ENABLED)
|
|
6243
6246
|
|
|
6244
6247
|
# Undo/Redo stack for grid edits
|
|
6245
6248
|
self.undo_stack = [] # List of (segment_id, old_target, new_target, old_status, new_status)
|
|
@@ -6367,7 +6370,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
6367
6370
|
self.termview_widget.theme_manager = self.theme_manager
|
|
6368
6371
|
if hasattr(self.termview_widget, 'apply_theme'):
|
|
6369
6372
|
self.termview_widget.apply_theme()
|
|
6370
|
-
|
|
6373
|
+
|
|
6374
|
+
# Also update the Match Panel TermView (right panel)
|
|
6375
|
+
if hasattr(self, 'termview_widget_match') and self.termview_widget_match:
|
|
6376
|
+
self.termview_widget_match.theme_manager = self.theme_manager
|
|
6377
|
+
if hasattr(self.termview_widget_match, 'apply_theme'):
|
|
6378
|
+
self.termview_widget_match.apply_theme()
|
|
6379
|
+
|
|
6371
6380
|
if hasattr(self, 'translation_results_panel') and self.translation_results_panel:
|
|
6372
6381
|
self.translation_results_panel.theme_manager = self.theme_manager
|
|
6373
6382
|
# Also update class-level theme_manager for CompactMatchItem
|
|
@@ -10335,12 +10344,9 @@ class SupervertalerQt(QMainWindow):
|
|
|
10335
10344
|
|
|
10336
10345
|
# Superdocs removed (online GitBook will be used instead)
|
|
10337
10346
|
|
|
10338
|
-
print("[DEBUG] About to create SuperlookupTab...")
|
|
10339
10347
|
lookup_tab = SuperlookupTab(self, user_data_path=self.user_data_path)
|
|
10340
|
-
print("[DEBUG] SuperlookupTab created successfully")
|
|
10341
10348
|
self.lookup_tab = lookup_tab # Store reference for later use
|
|
10342
10349
|
modules_tabs.addTab(lookup_tab, "🔍 Superlookup")
|
|
10343
|
-
print("[DEBUG] Superlookup tab added to modules_tabs")
|
|
10344
10350
|
|
|
10345
10351
|
# Supervoice - Voice Commands & Dictation
|
|
10346
10352
|
supervoice_tab = self._create_voice_dictation_settings_tab()
|
|
@@ -12231,6 +12237,46 @@ class SupervertalerQt(QMainWindow):
|
|
|
12231
12237
|
except Exception as e:
|
|
12232
12238
|
self.log(f"Error updating Match Panel termview: {e}")
|
|
12233
12239
|
|
|
12240
|
+
def _update_termview_for_segment(self, segment):
|
|
12241
|
+
"""Explicitly update termview for a segment (v1.9.182).
|
|
12242
|
+
|
|
12243
|
+
This is called directly from Ctrl+Enter navigation to ensure
|
|
12244
|
+
the termview updates immediately, bypassing the deferred timer approach.
|
|
12245
|
+
"""
|
|
12246
|
+
if not segment or not hasattr(self, 'termview_widget'):
|
|
12247
|
+
return
|
|
12248
|
+
|
|
12249
|
+
try:
|
|
12250
|
+
# Use in-memory index for fast lookup
|
|
12251
|
+
stored_matches = self.find_termbase_matches_in_source(segment.source)
|
|
12252
|
+
|
|
12253
|
+
# Convert dict format to list format for termview
|
|
12254
|
+
termbase_matches = [
|
|
12255
|
+
{
|
|
12256
|
+
'source_term': match_data.get('source', ''),
|
|
12257
|
+
'target_term': match_data.get('translation', ''),
|
|
12258
|
+
'termbase_name': match_data.get('termbase_name', ''),
|
|
12259
|
+
'ranking': match_data.get('ranking', 99),
|
|
12260
|
+
'is_project_termbase': match_data.get('is_project_termbase', False),
|
|
12261
|
+
'term_id': match_data.get('term_id'),
|
|
12262
|
+
'termbase_id': match_data.get('termbase_id'),
|
|
12263
|
+
'notes': match_data.get('notes', '')
|
|
12264
|
+
}
|
|
12265
|
+
for match_data in stored_matches.values()
|
|
12266
|
+
] if stored_matches else []
|
|
12267
|
+
|
|
12268
|
+
# Get NT matches
|
|
12269
|
+
nt_matches = self.find_nt_matches_in_source(segment.source)
|
|
12270
|
+
|
|
12271
|
+
# Get status hint
|
|
12272
|
+
status_hint = self._get_termbase_status_hint()
|
|
12273
|
+
|
|
12274
|
+
# Update both Termview widgets
|
|
12275
|
+
self._update_both_termviews(segment.source, termbase_matches, nt_matches, status_hint)
|
|
12276
|
+
|
|
12277
|
+
except Exception as e:
|
|
12278
|
+
self.log(f"Error in _update_termview_for_segment: {e}")
|
|
12279
|
+
|
|
12234
12280
|
def _get_termbase_status_hint(self) -> str:
|
|
12235
12281
|
"""Check termbase activation status and return appropriate hint.
|
|
12236
12282
|
|
|
@@ -12263,7 +12309,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
12263
12309
|
project_target = (self.current_project.target_lang or '').lower()
|
|
12264
12310
|
|
|
12265
12311
|
# Get all termbases and check language pairs
|
|
12266
|
-
all_termbases = self.termbase_mgr.
|
|
12312
|
+
all_termbases = self.termbase_mgr.get_all_termbases()
|
|
12267
12313
|
has_matching_language = False
|
|
12268
12314
|
|
|
12269
12315
|
for tb in all_termbases:
|
|
@@ -12818,6 +12864,39 @@ class SupervertalerQt(QMainWindow):
|
|
|
12818
12864
|
# Use term_id as key to avoid duplicates
|
|
12819
12865
|
self.termbase_cache[segment_id][term_id] = new_match
|
|
12820
12866
|
self.log(f"⚡ Added term directly to cache (instant update)")
|
|
12867
|
+
|
|
12868
|
+
# v1.9.182: Also add to in-memory termbase index for future lookups
|
|
12869
|
+
import re
|
|
12870
|
+
source_lower = source_text.lower().strip()
|
|
12871
|
+
try:
|
|
12872
|
+
if any(c in source_lower for c in '.%,/-'):
|
|
12873
|
+
pattern = re.compile(r'(?<!\w)' + re.escape(source_lower) + r'(?!\w)')
|
|
12874
|
+
else:
|
|
12875
|
+
pattern = re.compile(r'\b' + re.escape(source_lower) + r'\b')
|
|
12876
|
+
except re.error:
|
|
12877
|
+
pattern = None
|
|
12878
|
+
|
|
12879
|
+
index_entry = {
|
|
12880
|
+
'term_id': term_id,
|
|
12881
|
+
'source_term': source_text,
|
|
12882
|
+
'source_term_lower': source_lower,
|
|
12883
|
+
'target_term': target_text,
|
|
12884
|
+
'termbase_id': target_termbase['id'],
|
|
12885
|
+
'priority': 99,
|
|
12886
|
+
'domain': '',
|
|
12887
|
+
'notes': '',
|
|
12888
|
+
'project': '',
|
|
12889
|
+
'client': '',
|
|
12890
|
+
'forbidden': False,
|
|
12891
|
+
'is_project_termbase': False,
|
|
12892
|
+
'termbase_name': target_termbase['name'],
|
|
12893
|
+
'ranking': glossary_rank,
|
|
12894
|
+
'pattern': pattern,
|
|
12895
|
+
}
|
|
12896
|
+
with self.termbase_index_lock:
|
|
12897
|
+
self.termbase_index.append(index_entry)
|
|
12898
|
+
# Re-sort by length (longest first) for proper phrase matching
|
|
12899
|
+
self.termbase_index.sort(key=lambda x: len(x['source_term_lower']), reverse=True)
|
|
12821
12900
|
|
|
12822
12901
|
# Update TermView widget with the new term
|
|
12823
12902
|
if hasattr(self, 'termview_widget') and self.termview_widget:
|
|
@@ -13703,15 +13782,16 @@ class SupervertalerQt(QMainWindow):
|
|
|
13703
13782
|
# Use 0 (global) when no project is loaded - allows Superlookup to work
|
|
13704
13783
|
curr_proj = self.current_project if hasattr(self, 'current_project') else None
|
|
13705
13784
|
curr_proj_id = curr_proj.id if (curr_proj and hasattr(curr_proj, 'id')) else 0 # 0 = global
|
|
13706
|
-
|
|
13785
|
+
|
|
13707
13786
|
if checked:
|
|
13708
13787
|
termbase_mgr.activate_termbase(tb_id, curr_proj_id)
|
|
13709
13788
|
else:
|
|
13710
13789
|
termbase_mgr.deactivate_termbase(tb_id, curr_proj_id)
|
|
13711
|
-
|
|
13712
|
-
# Clear cache and
|
|
13790
|
+
|
|
13791
|
+
# Clear cache and rebuild in-memory index (v1.9.182)
|
|
13713
13792
|
with self.termbase_cache_lock:
|
|
13714
13793
|
self.termbase_cache.clear()
|
|
13794
|
+
self._build_termbase_index() # Rebuild index with new activation state
|
|
13715
13795
|
refresh_termbase_list()
|
|
13716
13796
|
|
|
13717
13797
|
read_checkbox.toggled.connect(on_read_toggle)
|
|
@@ -17040,7 +17120,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
17040
17120
|
|
|
17041
17121
|
# Cache kill switch
|
|
17042
17122
|
disable_cache_cb = CheckmarkCheckBox("Disable ALL caches (direct lookups every time)")
|
|
17043
|
-
disable_cache_cb.setChecked(general_settings.get('disable_all_caches',
|
|
17123
|
+
disable_cache_cb.setChecked(general_settings.get('disable_all_caches', False))
|
|
17044
17124
|
disable_cache_cb.setToolTip(
|
|
17045
17125
|
"When enabled, ALL caching is bypassed:\n"
|
|
17046
17126
|
"• Termbase cache\n"
|
|
@@ -19684,7 +19764,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
19684
19764
|
'results_compare_font_size': 9,
|
|
19685
19765
|
'autohotkey_path': ahk_path_edit.text().strip() if ahk_path_edit is not None else existing_settings.get('autohotkey_path', ''),
|
|
19686
19766
|
'enable_sound_effects': sound_effects_cb.isChecked() if sound_effects_cb is not None else existing_settings.get('enable_sound_effects', False),
|
|
19687
|
-
'disable_all_caches': disable_cache_cb.isChecked() if disable_cache_cb is not None else existing_settings.get('disable_all_caches',
|
|
19767
|
+
'disable_all_caches': disable_cache_cb.isChecked() if disable_cache_cb is not None else existing_settings.get('disable_all_caches', False)
|
|
19688
19768
|
}
|
|
19689
19769
|
|
|
19690
19770
|
# Keep a fast-access instance value
|
|
@@ -22252,7 +22332,154 @@ class SupervertalerQt(QMainWindow):
|
|
|
22252
22332
|
except Exception as e:
|
|
22253
22333
|
QMessageBox.critical(self, "Error", f"Failed to load project:\n{str(e)}")
|
|
22254
22334
|
self.log(f"✗ Error loading project: {e}")
|
|
22255
|
-
|
|
22335
|
+
|
|
22336
|
+
def _build_termbase_index(self):
|
|
22337
|
+
"""
|
|
22338
|
+
Build in-memory index of ALL terms from activated termbases (v1.9.182).
|
|
22339
|
+
|
|
22340
|
+
This is called ONCE on project load and replaces thousands of per-word
|
|
22341
|
+
database queries with a single bulk load + fast in-memory lookups.
|
|
22342
|
+
|
|
22343
|
+
Performance: Reduces 349-segment termbase search from 365 seconds to <1 second.
|
|
22344
|
+
"""
|
|
22345
|
+
import re
|
|
22346
|
+
import time
|
|
22347
|
+
start_time = time.time()
|
|
22348
|
+
|
|
22349
|
+
if not self.current_project or not hasattr(self, 'db_manager') or not self.db_manager:
|
|
22350
|
+
return
|
|
22351
|
+
|
|
22352
|
+
project_id = self.current_project.id if hasattr(self.current_project, 'id') else None
|
|
22353
|
+
|
|
22354
|
+
# Query ALL terms from activated termbases in ONE query
|
|
22355
|
+
# This replaces ~17,500 individual queries (349 segments × 50 words each)
|
|
22356
|
+
query = """
|
|
22357
|
+
SELECT
|
|
22358
|
+
t.id, t.source_term, t.target_term, t.termbase_id, t.priority,
|
|
22359
|
+
t.domain, t.notes, t.project, t.client, t.forbidden,
|
|
22360
|
+
tb.is_project_termbase, tb.name as termbase_name,
|
|
22361
|
+
COALESCE(ta.priority, tb.ranking) as ranking
|
|
22362
|
+
FROM termbase_terms t
|
|
22363
|
+
LEFT JOIN termbases tb ON CAST(t.termbase_id AS INTEGER) = tb.id
|
|
22364
|
+
LEFT JOIN termbase_activation ta ON ta.termbase_id = tb.id
|
|
22365
|
+
AND ta.project_id = ? AND ta.is_active = 1
|
|
22366
|
+
WHERE (ta.is_active = 1 OR tb.is_project_termbase = 1)
|
|
22367
|
+
"""
|
|
22368
|
+
|
|
22369
|
+
new_index = []
|
|
22370
|
+
try:
|
|
22371
|
+
self.db_manager.cursor.execute(query, [project_id or 0])
|
|
22372
|
+
rows = self.db_manager.cursor.fetchall()
|
|
22373
|
+
|
|
22374
|
+
for row in rows:
|
|
22375
|
+
source_term = row[1] # source_term
|
|
22376
|
+
if not source_term:
|
|
22377
|
+
continue
|
|
22378
|
+
|
|
22379
|
+
source_term_lower = source_term.lower().strip()
|
|
22380
|
+
if len(source_term_lower) < 2:
|
|
22381
|
+
continue
|
|
22382
|
+
|
|
22383
|
+
# Pre-compile regex pattern for word-boundary matching
|
|
22384
|
+
# This avoids recompiling the same pattern thousands of times
|
|
22385
|
+
try:
|
|
22386
|
+
# Handle terms with punctuation differently
|
|
22387
|
+
if any(c in source_term_lower for c in '.%,/-'):
|
|
22388
|
+
pattern = re.compile(r'(?<!\w)' + re.escape(source_term_lower) + r'(?!\w)')
|
|
22389
|
+
else:
|
|
22390
|
+
pattern = re.compile(r'\b' + re.escape(source_term_lower) + r'\b')
|
|
22391
|
+
except re.error:
|
|
22392
|
+
# If regex fails, use simple substring matching
|
|
22393
|
+
pattern = None
|
|
22394
|
+
|
|
22395
|
+
new_index.append({
|
|
22396
|
+
'term_id': row[0],
|
|
22397
|
+
'source_term': source_term,
|
|
22398
|
+
'source_term_lower': source_term_lower,
|
|
22399
|
+
'target_term': row[2],
|
|
22400
|
+
'termbase_id': row[3],
|
|
22401
|
+
'priority': row[4],
|
|
22402
|
+
'domain': row[5],
|
|
22403
|
+
'notes': row[6],
|
|
22404
|
+
'project': row[7],
|
|
22405
|
+
'client': row[8],
|
|
22406
|
+
'forbidden': row[9],
|
|
22407
|
+
'is_project_termbase': row[10],
|
|
22408
|
+
'termbase_name': row[11],
|
|
22409
|
+
'ranking': row[12],
|
|
22410
|
+
'pattern': pattern, # Pre-compiled regex
|
|
22411
|
+
})
|
|
22412
|
+
|
|
22413
|
+
# Sort by term length (longest first) for better phrase matching
|
|
22414
|
+
new_index.sort(key=lambda x: len(x['source_term_lower']), reverse=True)
|
|
22415
|
+
|
|
22416
|
+
# Thread-safe update of the index
|
|
22417
|
+
with self.termbase_index_lock:
|
|
22418
|
+
self.termbase_index = new_index
|
|
22419
|
+
|
|
22420
|
+
elapsed = time.time() - start_time
|
|
22421
|
+
self.log(f"✅ Built termbase index: {len(new_index)} terms in {elapsed:.2f}s")
|
|
22422
|
+
|
|
22423
|
+
except Exception as e:
|
|
22424
|
+
self.log(f"❌ Failed to build termbase index: {e}")
|
|
22425
|
+
import traceback
|
|
22426
|
+
self.log(traceback.format_exc())
|
|
22427
|
+
|
|
22428
|
+
def _search_termbase_in_memory(self, source_text: str) -> dict:
|
|
22429
|
+
"""
|
|
22430
|
+
Search termbase using in-memory index (v1.9.182).
|
|
22431
|
+
|
|
22432
|
+
This replaces _search_termbases_thread_safe() for batch operations.
|
|
22433
|
+
Instead of N database queries (one per word), we do:
|
|
22434
|
+
- 1 pass through the index (typically ~1000 terms)
|
|
22435
|
+
- Fast string 'in' check + pre-compiled regex validation
|
|
22436
|
+
|
|
22437
|
+
Performance: <1ms per segment vs 1+ second per segment.
|
|
22438
|
+
"""
|
|
22439
|
+
if not source_text:
|
|
22440
|
+
return {}
|
|
22441
|
+
|
|
22442
|
+
with self.termbase_index_lock:
|
|
22443
|
+
if not self.termbase_index:
|
|
22444
|
+
return {}
|
|
22445
|
+
index = self.termbase_index # Local reference for thread safety
|
|
22446
|
+
|
|
22447
|
+
source_lower = source_text.lower()
|
|
22448
|
+
matches = {}
|
|
22449
|
+
|
|
22450
|
+
for term in index:
|
|
22451
|
+
term_lower = term['source_term_lower']
|
|
22452
|
+
|
|
22453
|
+
# Quick substring check first (very fast, implemented in C)
|
|
22454
|
+
if term_lower not in source_lower:
|
|
22455
|
+
continue
|
|
22456
|
+
|
|
22457
|
+
# Word boundary validation using pre-compiled pattern
|
|
22458
|
+
pattern = term.get('pattern')
|
|
22459
|
+
if pattern:
|
|
22460
|
+
if not pattern.search(source_lower):
|
|
22461
|
+
continue
|
|
22462
|
+
|
|
22463
|
+
# Term matches! Add to results
|
|
22464
|
+
term_id = term['term_id']
|
|
22465
|
+
matches[term_id] = {
|
|
22466
|
+
'source': term['source_term'],
|
|
22467
|
+
'translation': term['target_term'],
|
|
22468
|
+
'term_id': term_id,
|
|
22469
|
+
'termbase_id': term['termbase_id'],
|
|
22470
|
+
'termbase_name': term['termbase_name'],
|
|
22471
|
+
'priority': term['priority'],
|
|
22472
|
+
'ranking': term['ranking'],
|
|
22473
|
+
'is_project_termbase': term['is_project_termbase'],
|
|
22474
|
+
'forbidden': term['forbidden'],
|
|
22475
|
+
'domain': term['domain'],
|
|
22476
|
+
'notes': term['notes'],
|
|
22477
|
+
'project': term['project'],
|
|
22478
|
+
'client': term['client'],
|
|
22479
|
+
}
|
|
22480
|
+
|
|
22481
|
+
return matches
|
|
22482
|
+
|
|
22256
22483
|
def _start_termbase_batch_worker(self):
|
|
22257
22484
|
"""
|
|
22258
22485
|
Start background thread to batch-process termbase matches for all segments.
|
|
@@ -22260,21 +22487,25 @@ class SupervertalerQt(QMainWindow):
|
|
|
22260
22487
|
"""
|
|
22261
22488
|
if not self.current_project or len(self.current_project.segments) == 0:
|
|
22262
22489
|
return
|
|
22263
|
-
|
|
22490
|
+
|
|
22491
|
+
# Build in-memory termbase index FIRST (v1.9.182)
|
|
22492
|
+
# This is the key optimization: load all terms once, then do fast in-memory lookups
|
|
22493
|
+
self._build_termbase_index()
|
|
22494
|
+
|
|
22264
22495
|
# 🧪 EXPERIMENTAL: Skip batch worker if cache kill switch is enabled
|
|
22265
22496
|
if getattr(self, 'disable_all_caches', False):
|
|
22266
22497
|
self.log("🧪 Termbase batch worker SKIPPED (caches disabled)")
|
|
22267
22498
|
return
|
|
22268
|
-
|
|
22499
|
+
|
|
22269
22500
|
# Stop any existing worker thread
|
|
22270
22501
|
self.termbase_batch_stop_event.set()
|
|
22271
22502
|
if self.termbase_batch_worker_thread and self.termbase_batch_worker_thread.is_alive():
|
|
22272
22503
|
self.log("⏹️ Stopping existing termbase batch worker...")
|
|
22273
22504
|
self.termbase_batch_worker_thread.join(timeout=2)
|
|
22274
|
-
|
|
22505
|
+
|
|
22275
22506
|
# Reset stop event for new worker
|
|
22276
22507
|
self.termbase_batch_stop_event.clear()
|
|
22277
|
-
|
|
22508
|
+
|
|
22278
22509
|
# Start new background worker thread
|
|
22279
22510
|
segment_count = len(self.current_project.segments)
|
|
22280
22511
|
self.log(f"🔄 Starting background termbase batch processor for {segment_count} segments...")
|
|
@@ -22290,96 +22521,60 @@ class SupervertalerQt(QMainWindow):
|
|
|
22290
22521
|
"""
|
|
22291
22522
|
Background worker thread: process all segments and populate termbase cache.
|
|
22292
22523
|
Runs in separate thread to not block UI.
|
|
22293
|
-
|
|
22294
|
-
|
|
22524
|
+
|
|
22525
|
+
v1.9.182: Now uses in-memory termbase index for 1000x faster lookups.
|
|
22526
|
+
Old approach: 365 seconds for 349 segments (1 second/segment)
|
|
22527
|
+
New approach: <1 second for 349 segments (<3ms/segment)
|
|
22295
22528
|
"""
|
|
22296
22529
|
if not segments:
|
|
22297
22530
|
return
|
|
22298
|
-
|
|
22299
|
-
# Create a separate database connection for this thread
|
|
22300
|
-
# SQLite connections are thread-local and cannot be shared across threads
|
|
22301
|
-
import sqlite3
|
|
22302
|
-
try:
|
|
22303
|
-
thread_db_connection = sqlite3.connect(self.db_manager.db_path)
|
|
22304
|
-
thread_db_connection.row_factory = sqlite3.Row
|
|
22305
|
-
thread_db_cursor = thread_db_connection.cursor()
|
|
22306
|
-
except Exception as e:
|
|
22307
|
-
self.log(f"❌ Failed to create database connection in batch worker: {e}")
|
|
22308
|
-
return
|
|
22309
|
-
|
|
22531
|
+
|
|
22310
22532
|
try:
|
|
22311
22533
|
processed = 0
|
|
22312
22534
|
cached = 0
|
|
22535
|
+
with_matches = 0
|
|
22313
22536
|
start_time = time.time()
|
|
22314
|
-
|
|
22537
|
+
|
|
22315
22538
|
for segment in segments:
|
|
22316
22539
|
# Check if stop event was signaled (user closed project or started new one)
|
|
22317
22540
|
if self.termbase_batch_stop_event.is_set():
|
|
22318
22541
|
self.log(f"⏹️ Termbase batch worker stopped by user (processed {processed} segments)")
|
|
22319
22542
|
break
|
|
22320
|
-
|
|
22543
|
+
|
|
22321
22544
|
segment_id = segment.id
|
|
22322
|
-
|
|
22545
|
+
|
|
22323
22546
|
# Skip if already in cache (thread-safe check)
|
|
22324
22547
|
with self.termbase_cache_lock:
|
|
22325
22548
|
if segment_id in self.termbase_cache:
|
|
22326
22549
|
cached += 1
|
|
22327
22550
|
continue
|
|
22328
|
-
|
|
22329
|
-
#
|
|
22551
|
+
|
|
22552
|
+
# v1.9.182: Use in-memory index for instant lookup (no database queries!)
|
|
22330
22553
|
try:
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
target_lang=self.current_project.target_lang if self.current_project else None,
|
|
22339
|
-
project_id=current_project_id
|
|
22340
|
-
)
|
|
22341
|
-
|
|
22554
|
+
matches = self._search_termbase_in_memory(segment.source)
|
|
22555
|
+
|
|
22556
|
+
# Store in cache (thread-safe) - even empty results to avoid re-lookup
|
|
22557
|
+
with self.termbase_cache_lock:
|
|
22558
|
+
self.termbase_cache[segment_id] = matches
|
|
22559
|
+
|
|
22560
|
+
processed += 1
|
|
22342
22561
|
if matches:
|
|
22343
|
-
|
|
22344
|
-
|
|
22345
|
-
self.termbase_cache[segment_id] = matches
|
|
22346
|
-
|
|
22347
|
-
processed += 1
|
|
22348
|
-
|
|
22349
|
-
# Log progress every 100 segments
|
|
22350
|
-
if processed % 100 == 0:
|
|
22351
|
-
elapsed = time.time() - start_time
|
|
22352
|
-
rate = processed / elapsed if elapsed > 0 else 0
|
|
22353
|
-
remaining = len(segments) - processed
|
|
22354
|
-
eta_seconds = remaining / rate if rate > 0 else 0
|
|
22355
|
-
self.log(f"📊 Batch progress: {processed}/{len(segments)} cached " +
|
|
22356
|
-
f"({rate:.1f} seg/sec, ETA: {int(eta_seconds)}s)")
|
|
22357
|
-
|
|
22562
|
+
with_matches += 1
|
|
22563
|
+
|
|
22358
22564
|
except Exception as e:
|
|
22359
22565
|
self.log(f"❌ Error processing segment {segment_id} in batch worker: {e}")
|
|
22360
22566
|
continue
|
|
22361
|
-
|
|
22362
|
-
# Small delay to prevent CPU saturation (let UI thread work)
|
|
22363
|
-
time.sleep(0.001) # 1ms delay between segments
|
|
22364
|
-
|
|
22567
|
+
|
|
22365
22568
|
elapsed = time.time() - start_time
|
|
22366
22569
|
total_cached = len(self.termbase_cache)
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22570
|
+
rate = processed / elapsed if elapsed > 0 else 0
|
|
22571
|
+
self.log(f"✅ Termbase batch worker complete: {processed} segments in {elapsed:.2f}s " +
|
|
22572
|
+
f"({rate:.0f} seg/sec, {with_matches} with matches)")
|
|
22573
|
+
|
|
22370
22574
|
except Exception as e:
|
|
22371
22575
|
self.log(f"❌ Termbase batch worker error: {e}")
|
|
22372
22576
|
import traceback
|
|
22373
22577
|
self.log(traceback.format_exc())
|
|
22374
|
-
|
|
22375
|
-
finally:
|
|
22376
|
-
# Close thread-local database connection
|
|
22377
|
-
try:
|
|
22378
|
-
thread_db_cursor.close()
|
|
22379
|
-
thread_db_connection.close()
|
|
22380
|
-
self.log("✓ Closed thread-local database connection in batch worker")
|
|
22381
|
-
except:
|
|
22382
|
-
pass
|
|
22383
22578
|
|
|
22384
22579
|
def _search_termbases_thread_safe(self, source_text: str, cursor, source_lang: str = None, target_lang: str = None, project_id: int = None) -> Dict[str, str]:
|
|
22385
22580
|
"""
|
|
@@ -22579,11 +22774,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
22579
22774
|
Also triggers PROACTIVE HIGHLIGHTING for upcoming segments with glossary matches.
|
|
22580
22775
|
"""
|
|
22581
22776
|
import json
|
|
22582
|
-
|
|
22583
|
-
print(f"[PROACTIVE DEBUG] _trigger_idle_prefetch called for row {current_row}")
|
|
22584
|
-
|
|
22777
|
+
|
|
22585
22778
|
if not self.current_project or current_row < 0:
|
|
22586
|
-
print(f"[PROACTIVE DEBUG] Early exit: no project or invalid row")
|
|
22587
22779
|
return
|
|
22588
22780
|
|
|
22589
22781
|
try:
|
|
@@ -22592,9 +22784,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
22592
22784
|
already_cached_ids = []
|
|
22593
22785
|
start_idx = current_row + 1
|
|
22594
22786
|
end_idx = min(start_idx + 5, len(self.current_project.segments))
|
|
22595
|
-
|
|
22596
|
-
print(f"[PROACTIVE DEBUG] Checking segments {start_idx} to {end_idx}")
|
|
22597
|
-
|
|
22787
|
+
|
|
22598
22788
|
for seg in self.current_project.segments[start_idx:end_idx]:
|
|
22599
22789
|
# Check if already cached
|
|
22600
22790
|
with self.translation_matches_cache_lock:
|
|
@@ -22602,23 +22792,19 @@ class SupervertalerQt(QMainWindow):
|
|
|
22602
22792
|
next_segment_ids.append(seg.id)
|
|
22603
22793
|
else:
|
|
22604
22794
|
already_cached_ids.append(seg.id)
|
|
22605
|
-
|
|
22606
|
-
print(f"[PROACTIVE DEBUG] Already cached IDs: {already_cached_ids}, Need prefetch: {next_segment_ids}")
|
|
22607
|
-
|
|
22795
|
+
|
|
22608
22796
|
# For already-cached segments, trigger proactive highlighting immediately
|
|
22609
22797
|
# This handles the case where segments were cached earlier but not highlighted
|
|
22610
22798
|
for seg_id in already_cached_ids:
|
|
22611
22799
|
try:
|
|
22612
22800
|
with self.termbase_cache_lock:
|
|
22613
22801
|
termbase_raw = self.termbase_cache.get(seg_id, {})
|
|
22614
|
-
print(f"[PROACTIVE DEBUG] Segment {seg_id} termbase cache: {len(termbase_raw) if termbase_raw else 0} matches")
|
|
22615
22802
|
if termbase_raw:
|
|
22616
22803
|
termbase_json = json.dumps(termbase_raw)
|
|
22617
22804
|
# Apply highlighting on main thread (we're already on main thread here)
|
|
22618
|
-
print(f"[PROACTIVE DEBUG] Calling _apply_proactive_highlighting for seg {seg_id}")
|
|
22619
22805
|
self._apply_proactive_highlighting(seg_id, termbase_json)
|
|
22620
|
-
except Exception
|
|
22621
|
-
|
|
22806
|
+
except Exception:
|
|
22807
|
+
pass # Silent failure for proactive highlighting
|
|
22622
22808
|
|
|
22623
22809
|
if next_segment_ids:
|
|
22624
22810
|
# Start prefetch in background (silent, no logging)
|
|
@@ -22700,43 +22886,35 @@ class SupervertalerQt(QMainWindow):
|
|
|
22700
22886
|
|
|
22701
22887
|
# Fetch TM/termbase matches (pass cursor for thread-safe termbase lookups)
|
|
22702
22888
|
matches = self._fetch_all_matches_for_segment(segment, thread_db_cursor)
|
|
22703
|
-
|
|
22704
|
-
#
|
|
22705
|
-
# This prevents "empty cache hits" when TM database is still empty
|
|
22889
|
+
|
|
22890
|
+
# Count matches for logging and proactive highlighting
|
|
22706
22891
|
tm_count = len(matches.get("TM", []))
|
|
22707
22892
|
tb_count = len(matches.get("Termbases", []))
|
|
22708
22893
|
mt_count = len(matches.get("MT", []))
|
|
22709
22894
|
llm_count = len(matches.get("LLM", []))
|
|
22710
22895
|
total_matches = tm_count + tb_count + mt_count + llm_count
|
|
22711
22896
|
|
|
22712
|
-
|
|
22713
|
-
|
|
22897
|
+
# Only cache results if we found something
|
|
22898
|
+
# Don't cache empty results - let main thread do fresh lookup
|
|
22714
22899
|
if total_matches > 0:
|
|
22715
|
-
# Store in cache only if we have results
|
|
22716
22900
|
with self.translation_matches_cache_lock:
|
|
22717
22901
|
self.translation_matches_cache[segment_id] = matches
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
self._proactive_highlight_signal.emit(segment_id, termbase_json)
|
|
22735
|
-
else:
|
|
22736
|
-
print(f"[PREFETCH DEBUG] WARNING: tb_count={tb_count} but termbase_raw is empty!")
|
|
22737
|
-
except Exception as e:
|
|
22738
|
-
print(f"[PREFETCH DEBUG] ERROR emitting signal: {e}")
|
|
22739
|
-
# else: Don't cache empty results - let it fall through to slow lookup next time
|
|
22902
|
+
|
|
22903
|
+
# PROACTIVE HIGHLIGHTING: Emit signal to apply highlighting on main thread
|
|
22904
|
+
# This makes upcoming segments show their glossary matches immediately
|
|
22905
|
+
if tb_count > 0:
|
|
22906
|
+
try:
|
|
22907
|
+
# Extract raw termbase matches from cache for highlighting
|
|
22908
|
+
with self.termbase_cache_lock:
|
|
22909
|
+
termbase_raw = self.termbase_cache.get(segment_id, {})
|
|
22910
|
+
|
|
22911
|
+
if termbase_raw:
|
|
22912
|
+
# Convert to JSON for thread-safe signal transfer
|
|
22913
|
+
termbase_json = json.dumps(termbase_raw)
|
|
22914
|
+
# Emit signal - will be handled on main thread
|
|
22915
|
+
self._proactive_highlight_signal.emit(segment_id, termbase_json)
|
|
22916
|
+
except Exception:
|
|
22917
|
+
pass # Silent fail for proactive highlighting
|
|
22740
22918
|
|
|
22741
22919
|
except Exception as e:
|
|
22742
22920
|
self.log(f"Error in prefetch worker: {e}")
|
|
@@ -22786,31 +22964,9 @@ class SupervertalerQt(QMainWindow):
|
|
|
22786
22964
|
source_lang_code = self._convert_language_to_code(source_lang)
|
|
22787
22965
|
target_lang_code = self._convert_language_to_code(target_lang)
|
|
22788
22966
|
|
|
22789
|
-
# 1. TM matches (
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
try:
|
|
22793
|
-
tm_results = self.db_manager.search_translation_memory(
|
|
22794
|
-
segment.source,
|
|
22795
|
-
source_lang,
|
|
22796
|
-
target_lang,
|
|
22797
|
-
limit=5
|
|
22798
|
-
)
|
|
22799
|
-
|
|
22800
|
-
if tm_results: # Only add if we got results
|
|
22801
|
-
for tm_match in tm_results:
|
|
22802
|
-
match_obj = TranslationMatch(
|
|
22803
|
-
source=tm_match.get('source', ''),
|
|
22804
|
-
target=tm_match.get('target', ''),
|
|
22805
|
-
relevance=tm_match.get('similarity', 0),
|
|
22806
|
-
metadata={'tm_name': tm_match.get('tm_id', 'project')},
|
|
22807
|
-
match_type='TM',
|
|
22808
|
-
compare_source=tm_match.get('source', ''),
|
|
22809
|
-
provider_code='TM'
|
|
22810
|
-
)
|
|
22811
|
-
matches_dict["TM"].append(match_obj)
|
|
22812
|
-
except Exception as e:
|
|
22813
|
-
pass # Silently continue
|
|
22967
|
+
# 1. TM matches - SKIP in prefetch worker (TM search not thread-safe)
|
|
22968
|
+
# TM will be fetched on-demand when user navigates to segment
|
|
22969
|
+
pass
|
|
22814
22970
|
|
|
22815
22971
|
# 2. MT matches (if enabled)
|
|
22816
22972
|
if self.enable_mt_matching:
|
|
@@ -22985,8 +23141,9 @@ class SupervertalerQt(QMainWindow):
|
|
|
22985
23141
|
mode_note = " (overwrite)" if overwrite_mode else ""
|
|
22986
23142
|
msg = f"💾 Saved segment to {saved_count} TM(s){mode_note}"
|
|
22987
23143
|
self._queue_tm_save_log(msg)
|
|
22988
|
-
#
|
|
22989
|
-
|
|
23144
|
+
# NOTE: Removed cache invalidation here - it was destroying batch worker's cache
|
|
23145
|
+
# on every Ctrl+Enter, making navigation extremely slow. The small chance of
|
|
23146
|
+
# seeing stale TM matches is far less important than responsive navigation.
|
|
22990
23147
|
|
|
22991
23148
|
def invalidate_translation_cache(self, smart_invalidation=True):
|
|
22992
23149
|
"""
|
|
@@ -28959,6 +29116,9 @@ class SupervertalerQt(QMainWindow):
|
|
|
28959
29116
|
segment_num_color = "black" if theme.name in ["Light (Default)", "Soft Gray", "Warm Cream", "Sepia", "High Contrast"] else theme.text
|
|
28960
29117
|
id_item.setForeground(QColor(segment_num_color))
|
|
28961
29118
|
id_item.setBackground(QColor()) # Default background from theme
|
|
29119
|
+
# Smaller font for segment numbers
|
|
29120
|
+
seg_num_font = QFont(self.default_font_family, max(8, self.default_font_size - 2))
|
|
29121
|
+
id_item.setFont(seg_num_font)
|
|
28962
29122
|
self.table.setItem(row, 0, id_item)
|
|
28963
29123
|
|
|
28964
29124
|
# Type - show segment type based on style and content
|
|
@@ -29041,13 +29201,17 @@ class SupervertalerQt(QMainWindow):
|
|
|
29041
29201
|
type_item = QTableWidgetItem(type_display)
|
|
29042
29202
|
type_item.setFlags(type_item.flags() & ~Qt.ItemFlag.ItemIsEditable) # Read-only
|
|
29043
29203
|
type_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
29044
|
-
|
|
29204
|
+
|
|
29045
29205
|
# Color-code by type for better visibility
|
|
29046
29206
|
if type_display in ("H1", "H2", "H3", "H4", "Title"):
|
|
29047
29207
|
type_item.setForeground(QColor("#1976D2")) # Blue for headings (works in both themes)
|
|
29048
29208
|
elif type_display.startswith("#") or type_display in ("•", "li"):
|
|
29049
29209
|
type_item.setForeground(QColor("#388E3C")) # Green for list items (works in both themes)
|
|
29050
|
-
|
|
29210
|
+
|
|
29211
|
+
# Smaller font for type symbols
|
|
29212
|
+
type_font = QFont(self.default_font_family, max(8, self.default_font_size - 2))
|
|
29213
|
+
type_item.setFont(type_font)
|
|
29214
|
+
|
|
29051
29215
|
self.table.setItem(row, 1, type_item)
|
|
29052
29216
|
|
|
29053
29217
|
# Source - Use read-only QTextEdit widget for easy text selection
|
|
@@ -29324,9 +29488,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
29324
29488
|
termview_layout.setContentsMargins(4, 4, 4, 0)
|
|
29325
29489
|
termview_layout.setSpacing(2)
|
|
29326
29490
|
|
|
29327
|
-
# Termview header label
|
|
29491
|
+
# Termview header label (theme-aware)
|
|
29328
29492
|
termview_header = QLabel("📖 Termview")
|
|
29329
|
-
|
|
29493
|
+
if hasattr(self, 'theme_manager') and self.theme_manager:
|
|
29494
|
+
header_color = self.theme_manager.current_theme.text_disabled
|
|
29495
|
+
else:
|
|
29496
|
+
header_color = "#666"
|
|
29497
|
+
termview_header.setStyleSheet(f"font-weight: bold; font-size: 9px; color: {header_color};")
|
|
29330
29498
|
termview_layout.addWidget(termview_header)
|
|
29331
29499
|
|
|
29332
29500
|
# Third Termview instance for Match Panel
|
|
@@ -29350,11 +29518,19 @@ class SupervertalerQt(QMainWindow):
|
|
|
29350
29518
|
tm_layout = QHBoxLayout(tm_container)
|
|
29351
29519
|
tm_layout.setContentsMargins(0, 0, 0, 0)
|
|
29352
29520
|
tm_layout.setSpacing(0)
|
|
29353
|
-
|
|
29354
|
-
#
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
|
|
29521
|
+
|
|
29522
|
+
# Get theme-aware colors for TM boxes (same as Compare Panel)
|
|
29523
|
+
if hasattr(self, 'theme_manager') and self.theme_manager:
|
|
29524
|
+
theme = self.theme_manager.current_theme
|
|
29525
|
+
is_dark = getattr(theme, 'is_dark', 'dark' in theme.name.lower())
|
|
29526
|
+
border_color = theme.border
|
|
29527
|
+
text_color = theme.text
|
|
29528
|
+
# Green background - theme-appropriate shade
|
|
29529
|
+
tm_box_bg = theme.panel_success if hasattr(theme, 'panel_success') else ("#1e3a2f" if is_dark else "#d4edda")
|
|
29530
|
+
else:
|
|
29531
|
+
tm_box_bg = "#d4edda" # Green (light mode)
|
|
29532
|
+
text_color = "#333"
|
|
29533
|
+
border_color = "#ddd"
|
|
29358
29534
|
|
|
29359
29535
|
# TM Source box (GREEN, with navigation)
|
|
29360
29536
|
self.match_panel_tm_matches = [] # Separate match list
|
|
@@ -29496,42 +29672,58 @@ class SupervertalerQt(QMainWindow):
|
|
|
29496
29672
|
header_layout.addWidget(nav_label)
|
|
29497
29673
|
|
|
29498
29674
|
if has_navigation:
|
|
29499
|
-
|
|
29500
|
-
|
|
29501
|
-
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
29507
|
-
|
|
29508
|
-
|
|
29509
|
-
|
|
29510
|
-
|
|
29511
|
-
|
|
29512
|
-
|
|
29513
|
-
|
|
29514
|
-
|
|
29515
|
-
|
|
29675
|
+
# Detect theme for arrow color
|
|
29676
|
+
is_dark_theme = hasattr(self, 'theme_manager') and self.theme_manager and 'dark' in self.theme_manager.current_theme.name.lower()
|
|
29677
|
+
arrow_color = "#FFFFFF" if is_dark_theme else "#333333"
|
|
29678
|
+
|
|
29679
|
+
# Create clickable label class with theme update capability
|
|
29680
|
+
from PyQt6.QtCore import pyqtSignal
|
|
29681
|
+
|
|
29682
|
+
class ClickableArrow(QLabel):
|
|
29683
|
+
clicked = pyqtSignal()
|
|
29684
|
+
|
|
29685
|
+
def __init__(self, arrow_symbol, parent=None):
|
|
29686
|
+
"""arrow_symbol: '◀' or '▶'"""
|
|
29687
|
+
self.arrow_symbol = arrow_symbol
|
|
29688
|
+
super().__init__("", parent)
|
|
29689
|
+
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
29690
|
+
|
|
29691
|
+
def set_color(self, color):
|
|
29692
|
+
"""Update arrow color for current theme"""
|
|
29693
|
+
self.setStyleSheet(f"""
|
|
29694
|
+
QLabel {{
|
|
29695
|
+
color: {color};
|
|
29696
|
+
background: transparent;
|
|
29697
|
+
border: none;
|
|
29698
|
+
font-size: 11px;
|
|
29699
|
+
font-weight: bold;
|
|
29700
|
+
}}
|
|
29701
|
+
""")
|
|
29702
|
+
self.setText(self.arrow_symbol)
|
|
29703
|
+
|
|
29704
|
+
def mousePressEvent(self, event):
|
|
29705
|
+
self.clicked.emit()
|
|
29706
|
+
super().mousePressEvent(event)
|
|
29707
|
+
|
|
29708
|
+
# Prev arrow - using ◀
|
|
29709
|
+
prev_btn = ClickableArrow("◀")
|
|
29710
|
+
prev_btn.set_color(arrow_color)
|
|
29711
|
+
prev_btn.setFixedSize(16, 16)
|
|
29712
|
+
prev_btn.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
29516
29713
|
header_layout.addWidget(prev_btn)
|
|
29517
|
-
|
|
29518
|
-
# Next
|
|
29519
|
-
next_btn =
|
|
29520
|
-
next_btn.
|
|
29521
|
-
next_btn.
|
|
29522
|
-
|
|
29523
|
-
font-size: 9px;
|
|
29524
|
-
padding: 0px;
|
|
29525
|
-
background: transparent;
|
|
29526
|
-
border: 1px solid {border_color};
|
|
29527
|
-
border-radius: 2px;
|
|
29528
|
-
color: {text_color};
|
|
29529
|
-
}}
|
|
29530
|
-
QPushButton:hover {{
|
|
29531
|
-
background: rgba(128,128,128,0.2);
|
|
29532
|
-
}}
|
|
29533
|
-
""")
|
|
29714
|
+
|
|
29715
|
+
# Next arrow - using ▶
|
|
29716
|
+
next_btn = ClickableArrow("▶")
|
|
29717
|
+
next_btn.set_color(arrow_color)
|
|
29718
|
+
next_btn.setFixedSize(16, 16)
|
|
29719
|
+
next_btn.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
29534
29720
|
header_layout.addWidget(next_btn)
|
|
29721
|
+
|
|
29722
|
+
# Store reference for theme updates
|
|
29723
|
+
if not hasattr(self, 'theme_aware_arrows'):
|
|
29724
|
+
self.theme_aware_arrows = []
|
|
29725
|
+
self.theme_aware_arrows.extend([prev_btn, next_btn])
|
|
29726
|
+
|
|
29535
29727
|
nav_buttons = [prev_btn, next_btn]
|
|
29536
29728
|
|
|
29537
29729
|
header_layout.addStretch()
|
|
@@ -30460,8 +30652,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
30460
30652
|
status_label = QLabel(status_def.icon)
|
|
30461
30653
|
status_label.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft)
|
|
30462
30654
|
status_label.setToolTip(status_def.label)
|
|
30463
|
-
#
|
|
30464
|
-
font_size = "
|
|
30655
|
+
# Smaller red X for "not_started" to match green checkmark visual size
|
|
30656
|
+
font_size = "8px" if segment.status == "not_started" else "13px"
|
|
30465
30657
|
# Make confirmed checkmark green
|
|
30466
30658
|
color = "color: #2e7d32;" if segment.status == "confirmed" else ""
|
|
30467
30659
|
|
|
@@ -30638,7 +30830,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
30638
30830
|
|
|
30639
30831
|
self.table.setFont(font)
|
|
30640
30832
|
|
|
30641
|
-
# Also update header font
|
|
30833
|
+
# Also update header font - same size as grid content, just bold
|
|
30642
30834
|
header_font = QFont(self.default_font_family, self.default_font_size, QFont.Weight.Bold)
|
|
30643
30835
|
self.table.horizontalHeader().setFont(header_font)
|
|
30644
30836
|
|
|
@@ -30970,8 +31162,8 @@ class SupervertalerQt(QMainWindow):
|
|
|
30970
31162
|
self.show_translation_results_pane = settings.get('show_translation_results_pane', False)
|
|
30971
31163
|
self.show_compare_panel = settings.get('show_compare_panel', True)
|
|
30972
31164
|
|
|
30973
|
-
#
|
|
30974
|
-
self.disable_all_caches = settings.get('disable_all_caches',
|
|
31165
|
+
# Load cache kill switch setting (default: False = caches ENABLED for performance)
|
|
31166
|
+
self.disable_all_caches = settings.get('disable_all_caches', False)
|
|
30975
31167
|
|
|
30976
31168
|
# Load LLM provider settings for AI Assistant
|
|
30977
31169
|
llm_settings = self.load_llm_settings()
|
|
@@ -31384,7 +31576,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
31384
31576
|
"""Handle cell selection change"""
|
|
31385
31577
|
if self.debug_mode_enabled:
|
|
31386
31578
|
self.log(f"🎯 on_cell_selected called: row {current_row}, col {current_col}")
|
|
31387
|
-
|
|
31579
|
+
|
|
31388
31580
|
# 🚫 GUARD: Don't re-run lookups if we're staying on the same row
|
|
31389
31581
|
# This prevents lookups when user edits text (focus changes within same row)
|
|
31390
31582
|
if hasattr(self, '_last_selected_row') and self._last_selected_row == current_row:
|
|
@@ -31392,34 +31584,35 @@ class SupervertalerQt(QMainWindow):
|
|
|
31392
31584
|
self.log(f"⏭️ Skipping lookup - already on row {current_row}")
|
|
31393
31585
|
return
|
|
31394
31586
|
self._last_selected_row = current_row
|
|
31395
|
-
|
|
31396
|
-
# ⚡
|
|
31397
|
-
#
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
if is_arrow_nav or is_ctrl_enter_nav:
|
|
31402
|
-
self._arrow_key_navigation = False # Reset flags
|
|
31403
|
-
self._ctrl_enter_navigation = False
|
|
31404
|
-
|
|
31405
|
-
# Schedule deferred lookup with short delay (150ms) for rapid navigation
|
|
31406
|
-
if hasattr(self, '_deferred_lookup_timer') and self._deferred_lookup_timer:
|
|
31407
|
-
self._deferred_lookup_timer.stop()
|
|
31408
|
-
from PyQt6.QtCore import QTimer
|
|
31409
|
-
self._deferred_lookup_timer = QTimer()
|
|
31410
|
-
self._deferred_lookup_timer.setSingleShot(True)
|
|
31411
|
-
self._deferred_lookup_timer.timeout.connect(
|
|
31412
|
-
lambda r=current_row, c=current_col, pr=previous_row, pc=previous_col:
|
|
31413
|
-
self._on_cell_selected_full(r, c, pr, pc)
|
|
31414
|
-
)
|
|
31415
|
-
self._deferred_lookup_timer.start(150) # 150ms debounce
|
|
31416
|
-
|
|
31417
|
-
# Do minimal UI update immediately (orange highlight, scroll)
|
|
31587
|
+
|
|
31588
|
+
# ⚡ FILTER MODE: Skip ALL heavy lookups when text filters are active
|
|
31589
|
+
# User is quickly navigating through filtered results - don't slow them down
|
|
31590
|
+
is_filtering = getattr(self, 'filtering_active', False)
|
|
31591
|
+
if is_filtering:
|
|
31592
|
+
# Only do minimal UI update (orange highlight) - no TM/termbase lookups
|
|
31418
31593
|
self._on_cell_selected_minimal(current_row, previous_row)
|
|
31419
31594
|
return
|
|
31420
|
-
|
|
31421
|
-
#
|
|
31422
|
-
|
|
31595
|
+
|
|
31596
|
+
# ⚡ FAST PATH: Defer heavy lookups for ALL navigation (arrow keys, Ctrl+Enter, AND mouse clicks)
|
|
31597
|
+
# This makes segment navigation feel INSTANT - cursor moves first, lookups happen after
|
|
31598
|
+
# Reset any navigation flags
|
|
31599
|
+
self._arrow_key_navigation = False
|
|
31600
|
+
self._ctrl_enter_navigation = False
|
|
31601
|
+
|
|
31602
|
+
# Schedule deferred lookup with short delay - debounce prevents hammering during rapid navigation
|
|
31603
|
+
if hasattr(self, '_deferred_lookup_timer') and self._deferred_lookup_timer:
|
|
31604
|
+
self._deferred_lookup_timer.stop()
|
|
31605
|
+
from PyQt6.QtCore import QTimer
|
|
31606
|
+
self._deferred_lookup_timer = QTimer()
|
|
31607
|
+
self._deferred_lookup_timer.setSingleShot(True)
|
|
31608
|
+
self._deferred_lookup_timer.timeout.connect(
|
|
31609
|
+
lambda r=current_row, c=current_col, pr=previous_row, pc=previous_col:
|
|
31610
|
+
self._on_cell_selected_full(r, c, pr, pc)
|
|
31611
|
+
)
|
|
31612
|
+
self._deferred_lookup_timer.start(10) # 10ms - just enough to batch rapid arrow key holding
|
|
31613
|
+
|
|
31614
|
+
# Do minimal UI update immediately (orange highlight, scroll)
|
|
31615
|
+
self._on_cell_selected_minimal(current_row, previous_row)
|
|
31423
31616
|
|
|
31424
31617
|
def _center_row_in_viewport(self, row: int):
|
|
31425
31618
|
"""Center the given row vertically in the visible table viewport.
|
|
@@ -31679,9 +31872,25 @@ class SupervertalerQt(QMainWindow):
|
|
|
31679
31872
|
if has_fuzzy_match and not has_100_match:
|
|
31680
31873
|
self._play_sound_effect('tm_fuzzy_match')
|
|
31681
31874
|
|
|
31682
|
-
# Skip the slow lookup below, we already have
|
|
31683
|
-
#
|
|
31875
|
+
# Skip the slow TERMBASE lookup below, we already have termbase matches cached
|
|
31876
|
+
# But TM lookup was skipped in prefetch (not thread-safe), so schedule it now
|
|
31684
31877
|
matches_dict = cached_matches # Set for later use
|
|
31878
|
+
|
|
31879
|
+
# v1.9.182: Schedule TM lookup even on cache hit (prefetch skips TM - not thread-safe)
|
|
31880
|
+
tm_count = len(cached_matches.get("TM", []))
|
|
31881
|
+
if tm_count == 0 and self.enable_tm_matching:
|
|
31882
|
+
find_replace_active = getattr(self, 'find_replace_active', False)
|
|
31883
|
+
if not find_replace_active:
|
|
31884
|
+
# Get termbase matches for the lookup
|
|
31885
|
+
termbase_matches_for_tm = [
|
|
31886
|
+
{
|
|
31887
|
+
'source_term': match.source,
|
|
31888
|
+
'target_term': match.target,
|
|
31889
|
+
'termbase_name': match.metadata.get('termbase_name', '') if match.metadata else '',
|
|
31890
|
+
}
|
|
31891
|
+
for match in cached_matches.get("Termbases", [])
|
|
31892
|
+
]
|
|
31893
|
+
self._schedule_mt_and_llm_matches(segment, termbase_matches_for_tm)
|
|
31685
31894
|
|
|
31686
31895
|
# Check if TM/Termbase matching is enabled
|
|
31687
31896
|
if not matches_dict and (not self.enable_tm_matching and not self.enable_termbase_matching):
|
|
@@ -31902,15 +32111,19 @@ class SupervertalerQt(QMainWindow):
|
|
|
31902
32111
|
|
|
31903
32112
|
# Schedule expensive searches (TM, MT, LLM) with debouncing to prevent UI blocking
|
|
31904
32113
|
# ONLY schedule if:
|
|
31905
|
-
# 1. Cache miss
|
|
32114
|
+
# 1. Cache miss OR cache hit with no TM matches (prefetch doesn't include TM - not thread-safe)
|
|
31906
32115
|
# 2. TM matching is enabled
|
|
31907
32116
|
# 3. Find/Replace is not active (to avoid slowdowns during navigation)
|
|
32117
|
+
needs_tm_lookup = True
|
|
31908
32118
|
with self.translation_matches_cache_lock:
|
|
31909
|
-
|
|
31910
|
-
|
|
32119
|
+
if segment_id in self.translation_matches_cache:
|
|
32120
|
+
cached = self.translation_matches_cache[segment_id]
|
|
32121
|
+
# v1.9.182: Check if TM matches exist - prefetch worker skips TM lookups
|
|
32122
|
+
needs_tm_lookup = len(cached.get("TM", [])) == 0
|
|
32123
|
+
|
|
31911
32124
|
find_replace_active = getattr(self, 'find_replace_active', False)
|
|
31912
|
-
|
|
31913
|
-
if
|
|
32125
|
+
|
|
32126
|
+
if needs_tm_lookup and self.enable_tm_matching and not find_replace_active:
|
|
31914
32127
|
# Get termbase matches if they exist (could be None or empty)
|
|
31915
32128
|
termbase_matches = matches_dict.get('Termbases', []) if matches_dict else []
|
|
31916
32129
|
self._schedule_mt_and_llm_matches(segment, termbase_matches)
|
|
@@ -31922,9 +32135,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
31922
32135
|
next_segment_ids = []
|
|
31923
32136
|
start_idx = current_row + 1
|
|
31924
32137
|
end_idx = min(start_idx + 20, len(self.current_project.segments))
|
|
31925
|
-
|
|
31926
|
-
print(f"[PROACTIVE NAV DEBUG] Navigation to row {current_row}, checking segments {start_idx} to {end_idx}")
|
|
31927
|
-
|
|
32138
|
+
|
|
31928
32139
|
for seg in self.current_project.segments[start_idx:end_idx]:
|
|
31929
32140
|
# Check if already cached
|
|
31930
32141
|
with self.translation_matches_cache_lock:
|
|
@@ -31938,15 +32149,12 @@ class SupervertalerQt(QMainWindow):
|
|
|
31938
32149
|
try:
|
|
31939
32150
|
with self.termbase_cache_lock:
|
|
31940
32151
|
termbase_raw = self.termbase_cache.get(seg.id, {})
|
|
31941
|
-
print(f"[PROACTIVE NAV DEBUG] Seg {seg.id}: cached, termbase_raw has {len(termbase_raw) if termbase_raw else 0} matches")
|
|
31942
32152
|
if termbase_raw:
|
|
31943
32153
|
termbase_json = json.dumps(termbase_raw)
|
|
31944
|
-
print(f"[PROACTIVE NAV DEBUG] Calling _apply_proactive_highlighting for seg {seg.id}")
|
|
31945
32154
|
self._apply_proactive_highlighting(seg.id, termbase_json)
|
|
31946
|
-
except Exception
|
|
31947
|
-
|
|
31948
|
-
|
|
31949
|
-
print(f"[PROACTIVE NAV DEBUG] Need to prefetch: {len(next_segment_ids)} segments")
|
|
32155
|
+
except Exception:
|
|
32156
|
+
pass # Silent failure for proactive highlighting
|
|
32157
|
+
|
|
31950
32158
|
if next_segment_ids:
|
|
31951
32159
|
self._start_prefetch_worker(next_segment_ids)
|
|
31952
32160
|
|
|
@@ -34349,18 +34557,32 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
34349
34557
|
"""
|
|
34350
34558
|
Find all termbase matches in source text
|
|
34351
34559
|
Returns dict of {term: translation} for all matches found
|
|
34560
|
+
|
|
34561
|
+
v1.9.182: Uses in-memory index for instant lookup when available.
|
|
34562
|
+
Falls back to per-word database queries if index not built.
|
|
34352
34563
|
"""
|
|
34353
34564
|
if not source_text or not hasattr(self, 'db_manager') or not self.db_manager:
|
|
34354
34565
|
return {}
|
|
34355
34566
|
|
|
34356
34567
|
try:
|
|
34568
|
+
# v1.9.182: Use in-memory index for instant lookup (1000x faster)
|
|
34569
|
+
# The index is built on project load by _build_termbase_index()
|
|
34570
|
+
with self.termbase_index_lock:
|
|
34571
|
+
has_index = bool(self.termbase_index)
|
|
34572
|
+
|
|
34573
|
+
if has_index:
|
|
34574
|
+
# Fast path: use pre-built in-memory index
|
|
34575
|
+
return self._search_termbase_in_memory(source_text)
|
|
34576
|
+
|
|
34577
|
+
# Fallback: original per-word database query approach
|
|
34578
|
+
# (only used if index not yet built, e.g., during startup)
|
|
34357
34579
|
source_lang = self.current_project.source_lang if self.current_project else None
|
|
34358
34580
|
target_lang = self.current_project.target_lang if self.current_project else None
|
|
34359
|
-
|
|
34581
|
+
|
|
34360
34582
|
# Convert language names to codes for termbase search
|
|
34361
34583
|
source_lang_code = self._convert_language_to_code(source_lang) if source_lang else None
|
|
34362
34584
|
target_lang_code = self._convert_language_to_code(target_lang) if target_lang else None
|
|
34363
|
-
|
|
34585
|
+
|
|
34364
34586
|
# Strip HTML/XML/CAT tool tags from source text before word splitting
|
|
34365
34587
|
# This handles <b>, </b>, <i>, memoQ {1}, [2}, Trados <1>, Déjà Vu {00001}, etc.
|
|
34366
34588
|
import re
|
|
@@ -34370,7 +34592,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
34370
34592
|
# memoQ content tags: [uicontrol id="..."} or {uicontrol] or [tagname ...} or {tagname]
|
|
34371
34593
|
clean_source_text = re.sub(r'\[[^\[\]]*\}', '', clean_source_text) # Opening: [anything}
|
|
34372
34594
|
clean_source_text = re.sub(r'\{[^\{\}]*\]', '', clean_source_text) # Closing: {anything]
|
|
34373
|
-
|
|
34595
|
+
|
|
34374
34596
|
# Search termbases for all terms that appear in the source text
|
|
34375
34597
|
# Split source text into words and search for each one
|
|
34376
34598
|
words = clean_source_text.split()
|
|
@@ -34592,23 +34814,17 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
34592
34814
|
termbase_matches_json: JSON-encoded termbase matches dict (thread-safe transfer)
|
|
34593
34815
|
"""
|
|
34594
34816
|
import json
|
|
34595
|
-
|
|
34596
|
-
print(f"[PROACTIVE DEBUG] _apply_proactive_highlighting called for segment {segment_id}")
|
|
34597
|
-
|
|
34817
|
+
|
|
34598
34818
|
if not self.current_project or not self.table:
|
|
34599
|
-
print(f"[PROACTIVE DEBUG] Early exit: no project or table")
|
|
34600
34819
|
return
|
|
34601
|
-
|
|
34820
|
+
|
|
34602
34821
|
try:
|
|
34603
34822
|
# Decode the matches from JSON
|
|
34604
34823
|
termbase_matches = json.loads(termbase_matches_json) if termbase_matches_json else {}
|
|
34605
|
-
|
|
34606
|
-
print(f"[PROACTIVE DEBUG] Decoded {len(termbase_matches)} termbase matches")
|
|
34607
|
-
|
|
34824
|
+
|
|
34608
34825
|
if not termbase_matches:
|
|
34609
|
-
print(f"[PROACTIVE DEBUG] No matches to highlight, returning")
|
|
34610
34826
|
return # Nothing to highlight
|
|
34611
|
-
|
|
34827
|
+
|
|
34612
34828
|
# Find the row for this segment ID
|
|
34613
34829
|
row = -1
|
|
34614
34830
|
for r in range(self.table.rowCount()):
|
|
@@ -34621,44 +34837,25 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
34621
34837
|
break
|
|
34622
34838
|
except ValueError:
|
|
34623
34839
|
continue
|
|
34624
|
-
|
|
34625
|
-
print(f"[PROACTIVE DEBUG] Found row {row} for segment {segment_id}")
|
|
34626
|
-
|
|
34840
|
+
|
|
34627
34841
|
if row < 0:
|
|
34628
|
-
print(f"[PROACTIVE DEBUG] Segment not visible in current page")
|
|
34629
34842
|
return # Segment not visible in current page
|
|
34630
|
-
|
|
34843
|
+
|
|
34631
34844
|
# Get segment source text
|
|
34632
34845
|
segment = None
|
|
34633
34846
|
for seg in self.current_project.segments:
|
|
34634
34847
|
if seg.id == segment_id:
|
|
34635
34848
|
segment = seg
|
|
34636
34849
|
break
|
|
34637
|
-
|
|
34850
|
+
|
|
34638
34851
|
if not segment:
|
|
34639
|
-
print(f"[PROACTIVE DEBUG] Segment object not found")
|
|
34640
34852
|
return
|
|
34641
|
-
|
|
34642
|
-
print(f"[PROACTIVE DEBUG] Applying highlight_source_with_termbase to row {row}")
|
|
34643
|
-
print(f"[PROACTIVE DEBUG] Source text: {segment.source[:80]}...")
|
|
34644
|
-
print(f"[PROACTIVE DEBUG] Matches keys: {list(termbase_matches.keys())[:5]}")
|
|
34645
|
-
if termbase_matches:
|
|
34646
|
-
first_key = list(termbase_matches.keys())[0]
|
|
34647
|
-
print(f"[PROACTIVE DEBUG] Sample match: {first_key} => {termbase_matches[first_key]}")
|
|
34648
|
-
|
|
34649
|
-
# Check if the source widget exists and is the right type
|
|
34650
|
-
source_widget = self.table.cellWidget(row, 2)
|
|
34651
|
-
print(f"[PROACTIVE DEBUG] Source widget type: {type(source_widget).__name__ if source_widget else 'None'}")
|
|
34652
|
-
print(f"[PROACTIVE DEBUG] Has highlight method: {hasattr(source_widget, 'highlight_termbase_matches') if source_widget else 'N/A'}")
|
|
34653
|
-
|
|
34853
|
+
|
|
34654
34854
|
# Apply highlighting (this updates the source cell widget)
|
|
34655
34855
|
self.highlight_source_with_termbase(row, segment.source, termbase_matches)
|
|
34656
|
-
|
|
34657
|
-
|
|
34658
|
-
|
|
34659
|
-
print(f"[PROACTIVE DEBUG] ERROR: {e}")
|
|
34660
|
-
import traceback
|
|
34661
|
-
print(f"[PROACTIVE DEBUG] Traceback: {traceback.format_exc()}")
|
|
34856
|
+
|
|
34857
|
+
except Exception:
|
|
34858
|
+
pass # Silent failure for proactive highlighting
|
|
34662
34859
|
|
|
34663
34860
|
def insert_term_translation(self, row: int, translation: str):
|
|
34664
34861
|
"""
|
|
@@ -38828,95 +39025,32 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
38828
39025
|
|
|
38829
39026
|
self.table.clearSelection()
|
|
38830
39027
|
self.table.setCurrentCell(row, 3) # Column 3 = Target (widget column)
|
|
39028
|
+
self.table.selectRow(row) # v1.9.182: Ensure row is visually selected
|
|
39029
|
+
# Ensure the row is visible by scrolling to it
|
|
39030
|
+
self.table.scrollToItem(self.table.item(row, 0), QTableWidget.ScrollHint.PositionAtCenter)
|
|
38831
39031
|
self.log(f"⏭️ Moved to next unconfirmed segment {seg.id}")
|
|
38832
|
-
|
|
38833
|
-
#
|
|
38834
|
-
|
|
38835
|
-
|
|
38836
|
-
|
|
38837
|
-
|
|
38838
|
-
|
|
38839
|
-
|
|
38840
|
-
|
|
38841
|
-
|
|
38842
|
-
|
|
38843
|
-
|
|
38844
|
-
# Use get_exact_match for 100% matches instead of fuzzy search
|
|
38845
|
-
source_lang = self.current_project.source_lang if hasattr(self.current_project, 'source_lang') else None
|
|
38846
|
-
target_lang = self.current_project.target_lang if hasattr(self.current_project, 'target_lang') else None
|
|
38847
|
-
exact_match = self.db_manager.get_exact_match(
|
|
38848
|
-
seg.source,
|
|
38849
|
-
tm_ids=activated_tm_ids,
|
|
38850
|
-
source_lang=source_lang,
|
|
38851
|
-
target_lang=target_lang
|
|
38852
|
-
)
|
|
38853
|
-
|
|
38854
|
-
# Check if there's a 100% match and (target is empty OR overwrite is enabled)
|
|
38855
|
-
target_is_empty = not seg.target.strip()
|
|
38856
|
-
can_auto_confirm = target_is_empty or self.auto_confirm_overwrite_existing
|
|
38857
|
-
|
|
38858
|
-
if exact_match and can_auto_confirm:
|
|
38859
|
-
match_target = exact_match.get('target_text', '')
|
|
38860
|
-
overwrite_note = " (overwriting existing)" if not target_is_empty else " (empty target)"
|
|
38861
|
-
self.log(f"🎯 Auto-confirm: Found 100% TM match for segment {seg.id}{overwrite_note}")
|
|
38862
|
-
|
|
38863
|
-
# Insert the match into the target cell
|
|
38864
|
-
target_widget = self.table.cellWidget(row, 3)
|
|
38865
|
-
if target_widget and match_target:
|
|
38866
|
-
target_widget.setPlainText(match_target)
|
|
38867
|
-
seg.target = match_target
|
|
38868
|
-
seg.status = 'confirmed'
|
|
38869
|
-
self.update_status_icon(row, 'confirmed')
|
|
38870
|
-
self.project_modified = True
|
|
38871
|
-
|
|
38872
|
-
# Save to TM
|
|
38873
|
-
try:
|
|
38874
|
-
self.save_segment_to_activated_tms(seg.source, seg.target)
|
|
38875
|
-
self.log(f"💾 Auto-confirmed and saved segment {seg.id} to TM")
|
|
38876
|
-
except Exception as e:
|
|
38877
|
-
self.log(f"⚠️ Error saving auto-confirmed segment to TM: {e}")
|
|
38878
|
-
|
|
38879
|
-
# Continue to the NEXT unconfirmed segment (skip this one)
|
|
38880
|
-
for next_row in range(row + 1, self.table.rowCount()):
|
|
38881
|
-
if next_row < len(self.current_project.segments):
|
|
38882
|
-
next_seg = self.current_project.segments[next_row]
|
|
38883
|
-
if next_seg.status not in ['confirmed', 'approved']:
|
|
38884
|
-
# Check pagination
|
|
38885
|
-
if self.table.isRowHidden(next_row):
|
|
38886
|
-
if hasattr(self, 'grid_page_size') and hasattr(self, 'grid_current_page'):
|
|
38887
|
-
target_page = next_row // self.grid_page_size
|
|
38888
|
-
if target_page != self.grid_current_page:
|
|
38889
|
-
self.grid_current_page = target_page
|
|
38890
|
-
self._update_pagination_ui()
|
|
38891
|
-
self._apply_pagination_to_grid()
|
|
38892
|
-
|
|
38893
|
-
# ⚡ INSTANT NAVIGATION
|
|
38894
|
-
self._ctrl_enter_navigation = True
|
|
38895
|
-
|
|
38896
|
-
self.table.clearSelection()
|
|
38897
|
-
self.table.setCurrentCell(next_row, 3)
|
|
38898
|
-
self.log(f"⏭️ Auto-skipped to next unconfirmed segment {next_seg.id}")
|
|
38899
|
-
next_target_widget = self.table.cellWidget(next_row, 3)
|
|
38900
|
-
if next_target_widget:
|
|
38901
|
-
next_target_widget.setFocus()
|
|
38902
|
-
next_target_widget.moveCursor(QTextCursor.MoveOperation.End)
|
|
38903
|
-
|
|
38904
|
-
# Recursively check if this next segment also has a 100% match
|
|
38905
|
-
self.confirm_and_next_unconfirmed()
|
|
38906
|
-
return
|
|
38907
|
-
|
|
38908
|
-
# No more unconfirmed segments after this one
|
|
38909
|
-
self.log("✅ No more unconfirmed segments after auto-confirm")
|
|
38910
|
-
# Update status bar after auto-confirming
|
|
38911
|
-
self.update_progress_stats()
|
|
38912
|
-
return
|
|
38913
|
-
|
|
38914
|
-
# Get the target cell widget and set focus to it (normal behavior without auto-confirm)
|
|
39032
|
+
|
|
39033
|
+
# v1.9.182: Explicitly update termview (don't rely on deferred signal)
|
|
39034
|
+
self._update_termview_for_segment(seg)
|
|
39035
|
+
|
|
39036
|
+
# v1.9.182: Explicitly schedule TM lookup (don't rely on deferred signal)
|
|
39037
|
+
if self.enable_tm_matching:
|
|
39038
|
+
find_replace_active = getattr(self, 'find_replace_active', False)
|
|
39039
|
+
if not find_replace_active:
|
|
39040
|
+
self._schedule_mt_and_llm_matches(seg, [])
|
|
39041
|
+
|
|
39042
|
+
# Get the target cell widget and set focus to it IMMEDIATELY
|
|
39043
|
+
# (moved BEFORE auto-confirm check for instant responsiveness)
|
|
38915
39044
|
target_widget = self.table.cellWidget(row, 3)
|
|
38916
39045
|
if target_widget:
|
|
38917
39046
|
target_widget.setFocus()
|
|
38918
39047
|
# Move cursor to end of text
|
|
38919
39048
|
target_widget.moveCursor(QTextCursor.MoveOperation.End)
|
|
39049
|
+
|
|
39050
|
+
# v1.9.182: Defer auto-confirm check to not block navigation
|
|
39051
|
+
# The TM lookup is slow - do it asynchronously after navigation completes
|
|
39052
|
+
if self.auto_confirm_100_percent_matches:
|
|
39053
|
+
QTimer.singleShot(50, lambda r=row, s=seg: self._check_auto_confirm_100_percent(r, s))
|
|
38920
39054
|
return
|
|
38921
39055
|
|
|
38922
39056
|
# No more unconfirmed segments, just go to next
|
|
@@ -38938,14 +39072,106 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
38938
39072
|
|
|
38939
39073
|
self.table.clearSelection()
|
|
38940
39074
|
self.table.setCurrentCell(next_row, 3) # Column 3 = Target (widget column)
|
|
39075
|
+
self.table.selectRow(next_row) # v1.9.182: Ensure row is visually selected
|
|
39076
|
+
# Ensure the row is visible by scrolling to it
|
|
39077
|
+
self.table.scrollToItem(self.table.item(next_row, 0), QTableWidget.ScrollHint.PositionAtCenter)
|
|
38941
39078
|
self.log(f"⏭️ Moved to next segment (all remaining confirmed)")
|
|
39079
|
+
|
|
39080
|
+
# v1.9.182: Explicitly update termview (don't rely on deferred signal)
|
|
39081
|
+
if next_row < len(self.current_project.segments):
|
|
39082
|
+
next_seg = self.current_project.segments[next_row]
|
|
39083
|
+
self._update_termview_for_segment(next_seg)
|
|
39084
|
+
|
|
39085
|
+
# v1.9.182: Explicitly schedule TM lookup (don't rely on deferred signal)
|
|
39086
|
+
if self.enable_tm_matching:
|
|
39087
|
+
find_replace_active = getattr(self, 'find_replace_active', False)
|
|
39088
|
+
if not find_replace_active:
|
|
39089
|
+
self._schedule_mt_and_llm_matches(next_seg, [])
|
|
39090
|
+
|
|
38942
39091
|
# Get the target cell widget and set focus to it
|
|
38943
39092
|
target_widget = self.table.cellWidget(next_row, 3)
|
|
38944
39093
|
if target_widget:
|
|
38945
39094
|
target_widget.setFocus()
|
|
38946
39095
|
# Move cursor to end of text
|
|
38947
39096
|
target_widget.moveCursor(QTextCursor.MoveOperation.End)
|
|
38948
|
-
|
|
39097
|
+
|
|
39098
|
+
def _check_auto_confirm_100_percent(self, row: int, seg):
|
|
39099
|
+
"""
|
|
39100
|
+
v1.9.182: Deferred auto-confirm check for 100% TM matches.
|
|
39101
|
+
|
|
39102
|
+
This is called asynchronously after Ctrl+Enter navigation to avoid blocking
|
|
39103
|
+
the UI thread with slow TM database queries.
|
|
39104
|
+
"""
|
|
39105
|
+
try:
|
|
39106
|
+
# Verify we're still on the same segment (user may have navigated away)
|
|
39107
|
+
current_row = self.table.currentRow() if hasattr(self, 'table') and self.table else -1
|
|
39108
|
+
if current_row != row:
|
|
39109
|
+
return # User has moved - don't auto-confirm wrong segment
|
|
39110
|
+
|
|
39111
|
+
if not self.enable_tm_matching or not hasattr(self, 'db_manager') or not self.db_manager:
|
|
39112
|
+
return
|
|
39113
|
+
|
|
39114
|
+
# Get activated TM IDs from project settings
|
|
39115
|
+
activated_tm_ids = []
|
|
39116
|
+
if hasattr(self.current_project, 'tm_settings') and self.current_project.tm_settings:
|
|
39117
|
+
activated_tm_ids = self.current_project.tm_settings.get('activated_tm_ids', [])
|
|
39118
|
+
|
|
39119
|
+
if not activated_tm_ids:
|
|
39120
|
+
return
|
|
39121
|
+
|
|
39122
|
+
# Use get_exact_match for 100% matches
|
|
39123
|
+
source_lang = self.current_project.source_lang if hasattr(self.current_project, 'source_lang') else None
|
|
39124
|
+
target_lang = self.current_project.target_lang if hasattr(self.current_project, 'target_lang') else None
|
|
39125
|
+
exact_match = self.db_manager.get_exact_match(
|
|
39126
|
+
seg.source,
|
|
39127
|
+
tm_ids=activated_tm_ids,
|
|
39128
|
+
source_lang=source_lang,
|
|
39129
|
+
target_lang=target_lang
|
|
39130
|
+
)
|
|
39131
|
+
|
|
39132
|
+
if not exact_match:
|
|
39133
|
+
return
|
|
39134
|
+
|
|
39135
|
+
# Check if there's a 100% match and (target is empty OR overwrite is enabled)
|
|
39136
|
+
target_is_empty = not seg.target.strip()
|
|
39137
|
+
can_auto_confirm = target_is_empty or self.auto_confirm_overwrite_existing
|
|
39138
|
+
|
|
39139
|
+
if not can_auto_confirm:
|
|
39140
|
+
return
|
|
39141
|
+
|
|
39142
|
+
# Verify AGAIN that we're still on the same segment (TM query may have taken time)
|
|
39143
|
+
current_row = self.table.currentRow() if hasattr(self, 'table') and self.table else -1
|
|
39144
|
+
if current_row != row:
|
|
39145
|
+
return # User has moved during TM lookup
|
|
39146
|
+
|
|
39147
|
+
match_target = exact_match.get('target_text', '')
|
|
39148
|
+
if not match_target:
|
|
39149
|
+
return
|
|
39150
|
+
|
|
39151
|
+
overwrite_note = " (overwriting existing)" if not target_is_empty else " (empty target)"
|
|
39152
|
+
self.log(f"🎯 Auto-confirm: Found 100% TM match for segment {seg.id}{overwrite_note}")
|
|
39153
|
+
|
|
39154
|
+
# Insert the match into the target cell
|
|
39155
|
+
target_widget = self.table.cellWidget(row, 3)
|
|
39156
|
+
if target_widget:
|
|
39157
|
+
target_widget.setPlainText(match_target)
|
|
39158
|
+
seg.target = match_target
|
|
39159
|
+
seg.status = 'confirmed'
|
|
39160
|
+
self.update_status_icon(row, 'confirmed')
|
|
39161
|
+
self.project_modified = True
|
|
39162
|
+
|
|
39163
|
+
# Save to TM
|
|
39164
|
+
try:
|
|
39165
|
+
self.save_segment_to_activated_tms(seg.source, seg.target)
|
|
39166
|
+
self.log(f"💾 Auto-confirmed and saved segment {seg.id} to TM")
|
|
39167
|
+
except Exception as e:
|
|
39168
|
+
self.log(f"⚠️ Error saving auto-confirmed segment to TM: {e}")
|
|
39169
|
+
|
|
39170
|
+
# Continue to the NEXT unconfirmed segment (skip this one)
|
|
39171
|
+
self.confirm_and_next_unconfirmed()
|
|
39172
|
+
except Exception as e:
|
|
39173
|
+
self.log(f"⚠️ Error in auto-confirm check: {e}")
|
|
39174
|
+
|
|
38949
39175
|
def confirm_selected_or_next(self):
|
|
38950
39176
|
"""Smart confirm: if multiple segments selected, confirm all; otherwise confirm and go to next.
|
|
38951
39177
|
|
|
@@ -43763,7 +43989,15 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
43763
43989
|
# Reapply alternating row colors with new theme
|
|
43764
43990
|
if hasattr(self, 'apply_alternating_row_colors'):
|
|
43765
43991
|
self.apply_alternating_row_colors()
|
|
43766
|
-
|
|
43992
|
+
|
|
43993
|
+
# Update navigation arrow colors based on theme
|
|
43994
|
+
if hasattr(self, 'theme_aware_arrows'):
|
|
43995
|
+
is_dark = theme.name == "Dark"
|
|
43996
|
+
arrow_color = "#FFFFFF" if is_dark else "#333333"
|
|
43997
|
+
for arrow in self.theme_aware_arrows:
|
|
43998
|
+
if hasattr(arrow, 'set_color'):
|
|
43999
|
+
arrow.set_color(arrow_color)
|
|
44000
|
+
|
|
43767
44001
|
# Refresh segment numbers color
|
|
43768
44002
|
if hasattr(self, 'table') and self.table:
|
|
43769
44003
|
# Determine segment number color based on theme
|
|
@@ -43800,6 +44034,11 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
43800
44034
|
if hasattr(self.termview_widget, 'apply_theme'):
|
|
43801
44035
|
self.termview_widget.apply_theme()
|
|
43802
44036
|
|
|
44037
|
+
# Also refresh Match Panel TermView (right panel)
|
|
44038
|
+
if hasattr(self, 'termview_widget_match') and self.termview_widget_match:
|
|
44039
|
+
if hasattr(self.termview_widget_match, 'apply_theme'):
|
|
44040
|
+
self.termview_widget_match.apply_theme()
|
|
44041
|
+
|
|
43803
44042
|
def show_file_progress_dialog(self):
|
|
43804
44043
|
"""Show File Progress dialog for multi-file projects.
|
|
43805
44044
|
|
|
@@ -44096,12 +44335,12 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
44096
44335
|
self._pending_mt_llm_segment = segment
|
|
44097
44336
|
self._pending_termbase_matches = termbase_matches or []
|
|
44098
44337
|
|
|
44099
|
-
# Start debounced timer - only call APIs after user stops
|
|
44338
|
+
# Start debounced timer - only call APIs after user stops navigating
|
|
44100
44339
|
from PyQt6.QtCore import QTimer
|
|
44101
44340
|
self._mt_llm_timer = QTimer()
|
|
44102
44341
|
self._mt_llm_timer.setSingleShot(True)
|
|
44103
44342
|
self._mt_llm_timer.timeout.connect(lambda: self._execute_mt_llm_lookup())
|
|
44104
|
-
self._mt_llm_timer.start(
|
|
44343
|
+
self._mt_llm_timer.start(150) # Wait 150ms of inactivity before external API calls
|
|
44105
44344
|
|
|
44106
44345
|
except Exception as e:
|
|
44107
44346
|
self.log(f"Error scheduling MT/LLM search: {e}")
|
|
@@ -44126,7 +44365,21 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
44126
44365
|
"""Search for TM, MT and LLM matches - called only after debounce delay"""
|
|
44127
44366
|
try:
|
|
44128
44367
|
from modules.translation_results_panel import TranslationMatch
|
|
44129
|
-
|
|
44368
|
+
|
|
44369
|
+
# v1.9.182: Validate we're still on the same segment before displaying results
|
|
44370
|
+
# This prevents stale results from showing when user navigates quickly
|
|
44371
|
+
current_row = self.table.currentRow() if hasattr(self, 'table') and self.table else -1
|
|
44372
|
+
if current_row >= 0:
|
|
44373
|
+
id_item = self.table.item(current_row, 0)
|
|
44374
|
+
if id_item:
|
|
44375
|
+
try:
|
|
44376
|
+
current_segment_id = int(id_item.text())
|
|
44377
|
+
if current_segment_id != segment.id:
|
|
44378
|
+
# User has moved to a different segment - abort this lookup
|
|
44379
|
+
return
|
|
44380
|
+
except (ValueError, AttributeError):
|
|
44381
|
+
pass
|
|
44382
|
+
|
|
44130
44383
|
# Get current project languages for all translation services
|
|
44131
44384
|
source_lang = getattr(self.current_project, 'source_lang', None) if self.current_project else None
|
|
44132
44385
|
target_lang = getattr(self.current_project, 'target_lang', None) if self.current_project else None
|
|
@@ -44199,6 +44452,22 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
44199
44452
|
|
|
44200
44453
|
# Show TM matches immediately (progressive loading)
|
|
44201
44454
|
if matches_dict["TM"]:
|
|
44455
|
+
# v1.9.182: Re-validate we're still on same segment before displaying
|
|
44456
|
+
current_row = self.table.currentRow() if hasattr(self, 'table') and self.table else -1
|
|
44457
|
+
if current_row >= 0:
|
|
44458
|
+
id_item = self.table.item(current_row, 0)
|
|
44459
|
+
if id_item:
|
|
44460
|
+
try:
|
|
44461
|
+
current_segment_id = int(id_item.text())
|
|
44462
|
+
if current_segment_id != segment.id:
|
|
44463
|
+
# User moved - still cache results but don't display
|
|
44464
|
+
with self.translation_matches_cache_lock:
|
|
44465
|
+
if segment.id in self.translation_matches_cache:
|
|
44466
|
+
self.translation_matches_cache[segment.id]["TM"] = matches_dict["TM"]
|
|
44467
|
+
return # Don't display stale results
|
|
44468
|
+
except (ValueError, AttributeError):
|
|
44469
|
+
pass
|
|
44470
|
+
|
|
44202
44471
|
tm_only = {"TM": matches_dict["TM"]}
|
|
44203
44472
|
if hasattr(self, 'results_panels') and self.results_panels:
|
|
44204
44473
|
for panel in self.results_panels:
|
|
@@ -44252,6 +44521,16 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
44252
44521
|
has_fuzzy_match = any(float(tm.relevance) < 99.5 and float(tm.relevance) >= 50 for tm in matches_dict["TM"])
|
|
44253
44522
|
if has_fuzzy_match and not has_100_match:
|
|
44254
44523
|
self._play_sound_effect('tm_fuzzy_match')
|
|
44524
|
+
|
|
44525
|
+
# v1.9.182: Update cache with TM results so subsequent visits are instant
|
|
44526
|
+
if matches_dict["TM"]:
|
|
44527
|
+
with self.translation_matches_cache_lock:
|
|
44528
|
+
if segment.id in self.translation_matches_cache:
|
|
44529
|
+
# Merge TM results into existing cache entry
|
|
44530
|
+
self.translation_matches_cache[segment.id]["TM"] = matches_dict["TM"]
|
|
44531
|
+
else:
|
|
44532
|
+
# Create new cache entry with TM results
|
|
44533
|
+
self.translation_matches_cache[segment.id] = matches_dict
|
|
44255
44534
|
except Exception as e:
|
|
44256
44535
|
self.log(f"Error in delayed TM search: {e}")
|
|
44257
44536
|
|
|
@@ -46830,10 +47109,8 @@ class SuperlookupTab(QWidget):
|
|
|
46830
47109
|
for row in db_manager.cursor.fetchall():
|
|
46831
47110
|
if row[0]:
|
|
46832
47111
|
all_languages.add(row[0])
|
|
46833
|
-
except Exception
|
|
46834
|
-
|
|
46835
|
-
else:
|
|
46836
|
-
print(f"[DEBUG] No db_manager available for language population")
|
|
47112
|
+
except Exception:
|
|
47113
|
+
pass # Silent failure for language population
|
|
46837
47114
|
|
|
46838
47115
|
# Get languages from termbases
|
|
46839
47116
|
if termbase_mgr:
|
|
@@ -46844,8 +47121,8 @@ class SuperlookupTab(QWidget):
|
|
|
46844
47121
|
all_languages.add(tb['source_lang'])
|
|
46845
47122
|
if tb.get('target_lang'):
|
|
46846
47123
|
all_languages.add(tb['target_lang'])
|
|
46847
|
-
except Exception
|
|
46848
|
-
|
|
47124
|
+
except Exception:
|
|
47125
|
+
pass # Silent failure for language population
|
|
46849
47126
|
|
|
46850
47127
|
# Group languages by their base language name
|
|
46851
47128
|
# E.g., "en", "en-US", "en-GB", "English" all map to "English"
|
|
@@ -46875,8 +47152,6 @@ class SuperlookupTab(QWidget):
|
|
|
46875
47152
|
# Store variants list as the data for this item
|
|
46876
47153
|
self.lang_from_combo.addItem(base_name, variants)
|
|
46877
47154
|
self.lang_to_combo.addItem(base_name, variants)
|
|
46878
|
-
|
|
46879
|
-
print(f"[DEBUG] Populated language dropdowns with {len(sorted_base_langs)} base languages (from {len(all_languages)} variants)")
|
|
46880
47155
|
|
|
46881
47156
|
def _get_base_language_name(self, lang_code):
|
|
46882
47157
|
"""Extract the base language name from any language code or name.
|
|
@@ -47063,37 +47338,20 @@ class SuperlookupTab(QWidget):
|
|
|
47063
47338
|
selected_tm_ids = self.get_selected_tm_ids()
|
|
47064
47339
|
search_direction = self.get_search_direction()
|
|
47065
47340
|
from_lang, to_lang = self.get_language_filters()
|
|
47066
|
-
|
|
47067
|
-
# Write language info to debug file
|
|
47068
|
-
with open('superlookup_debug.txt', 'a') as f:
|
|
47069
|
-
f.write(f"Language filters: from_lang='{from_lang}', to_lang='{to_lang}'\\n")
|
|
47070
|
-
f.write(f"Search direction: {search_direction}\\n")
|
|
47071
|
-
|
|
47072
|
-
print(f"[DEBUG] Superlookup: Selected TM IDs: {selected_tm_ids}, direction: {search_direction}", flush=True)
|
|
47073
|
-
print(f"[DEBUG] Superlookup: Language filters: from={from_lang}, to={to_lang}", flush=True)
|
|
47074
|
-
print(f"[DEBUG] Superlookup: tm_database = {self.tm_database}", flush=True)
|
|
47341
|
+
|
|
47075
47342
|
if self.engine:
|
|
47076
47343
|
self.engine.set_enabled_tm_ids(selected_tm_ids if selected_tm_ids else None)
|
|
47077
47344
|
|
|
47078
47345
|
# Perform TM lookup with direction and language filters
|
|
47079
47346
|
tm_results = []
|
|
47080
47347
|
if self.tm_database:
|
|
47081
|
-
|
|
47082
|
-
tm_results = self.engine.search_tm(text, direction=search_direction,
|
|
47348
|
+
tm_results = self.engine.search_tm(text, direction=search_direction,
|
|
47083
47349
|
source_lang=from_lang, target_lang=to_lang)
|
|
47084
|
-
|
|
47085
|
-
else:
|
|
47086
|
-
print(f"[DEBUG] Superlookup: tm_database is None, skipping TM search!", flush=True)
|
|
47087
|
-
|
|
47350
|
+
|
|
47088
47351
|
# Perform termbase lookup (search Supervertaler termbases directly)
|
|
47089
|
-
print(f"[DEBUG] About to call search_termbases with from_lang='{from_lang}', to_lang='{to_lang}'", flush=True)
|
|
47090
47352
|
try:
|
|
47091
47353
|
termbase_results = self.search_termbases(text, source_lang=from_lang, target_lang=to_lang)
|
|
47092
|
-
|
|
47093
|
-
except Exception as e:
|
|
47094
|
-
print(f"[DEBUG] ERROR in search_termbases: {e}", flush=True)
|
|
47095
|
-
import traceback
|
|
47096
|
-
traceback.print_exc()
|
|
47354
|
+
except Exception:
|
|
47097
47355
|
termbase_results = []
|
|
47098
47356
|
|
|
47099
47357
|
# Perform Supermemory semantic search
|