supervertaler 1.9.186__py3-none-any.whl → 1.9.188__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 CHANGED
@@ -32,7 +32,7 @@ License: MIT
32
32
  """
33
33
 
34
34
  # Version Information.
35
- __version__ = "1.9.186"
35
+ __version__ = "1.9.188"
36
36
  __phase__ = "0.9"
37
37
  __release_date__ = "2026-02-01"
38
38
  __edition__ = "Qt"
@@ -20348,7 +20348,68 @@ class SupervertalerQt(QMainWindow):
20348
20348
  advanced_filter_btn.clicked.connect(self.show_advanced_filters_dialog)
20349
20349
  advanced_filter_btn.setMaximumWidth(160)
20350
20350
  advanced_filter_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
20351
-
20351
+
20352
+ # Sort dropdown button (similar to memoQ)
20353
+ sort_btn = QPushButton("⇅ Sort")
20354
+ sort_btn.setMaximumWidth(100)
20355
+ sort_btn.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
20356
+ sort_menu = QMenu(self)
20357
+
20358
+ # Initialize sort state if not exists
20359
+ if not hasattr(self, 'current_sort'):
20360
+ self.current_sort = None # None = document order
20361
+
20362
+ # Sort by source text
20363
+ sort_menu.addAction("📝 Source A → Z", lambda: self.apply_sort('source_asc'))
20364
+ sort_menu.addAction("📝 Source Z → A", lambda: self.apply_sort('source_desc'))
20365
+
20366
+ sort_menu.addSeparator()
20367
+
20368
+ # Sort by target text
20369
+ sort_menu.addAction("📄 Target A → Z", lambda: self.apply_sort('target_asc'))
20370
+ sort_menu.addAction("📄 Target Z → A", lambda: self.apply_sort('target_desc'))
20371
+
20372
+ sort_menu.addSeparator()
20373
+
20374
+ # Sort by text length
20375
+ sort_menu.addAction("📏 Source (longer first)", lambda: self.apply_sort('source_length_desc'))
20376
+ sort_menu.addAction("📏 Source (shorter first)", lambda: self.apply_sort('source_length_asc'))
20377
+ sort_menu.addAction("📏 Target (longer first)", lambda: self.apply_sort('target_length_desc'))
20378
+ sort_menu.addAction("📏 Target (shorter first)", lambda: self.apply_sort('target_length_asc'))
20379
+
20380
+ sort_menu.addSeparator()
20381
+
20382
+ # Sort by match rate
20383
+ sort_menu.addAction("🎯 Match Rate (higher first)", lambda: self.apply_sort('match_desc'))
20384
+ sort_menu.addAction("🎯 Match Rate (lower first)", lambda: self.apply_sort('match_asc'))
20385
+
20386
+ sort_menu.addSeparator()
20387
+
20388
+ # Sort by frequency
20389
+ sort_menu.addAction("📊 Source Frequency (higher first)", lambda: self.apply_sort('source_freq_desc'))
20390
+ sort_menu.addAction("📊 Source Frequency (lower first)", lambda: self.apply_sort('source_freq_asc'))
20391
+ sort_menu.addAction("📊 Target Frequency (higher first)", lambda: self.apply_sort('target_freq_desc'))
20392
+ sort_menu.addAction("📊 Target Frequency (lower first)", lambda: self.apply_sort('target_freq_asc'))
20393
+
20394
+ sort_menu.addSeparator()
20395
+
20396
+ # Sort by last changed
20397
+ sort_menu.addAction("🕒 Last Changed (newest first)", lambda: self.apply_sort('modified_desc'))
20398
+ sort_menu.addAction("🕒 Last Changed (oldest first)", lambda: self.apply_sort('modified_asc'))
20399
+
20400
+ sort_menu.addSeparator()
20401
+
20402
+ # Sort by row status
20403
+ sort_menu.addAction("🚦 Row Status", lambda: self.apply_sort('status'))
20404
+
20405
+ sort_menu.addSeparator()
20406
+
20407
+ # Reset to document order
20408
+ sort_menu.addAction("↩️ Document Order (default)", lambda: self.apply_sort(None))
20409
+
20410
+ sort_btn.setMenu(sort_menu)
20411
+ sort_btn.setToolTip("Sort segments by various criteria")
20412
+
20352
20413
  # File filter dropdown (for multi-file projects)
20353
20414
  self.file_filter_combo = QComboBox()
20354
20415
  self.file_filter_combo.setMinimumWidth(150)
@@ -20442,6 +20503,7 @@ class SupervertalerQt(QMainWindow):
20442
20503
  filter_layout.addWidget(clear_filters_btn)
20443
20504
  filter_layout.addWidget(quick_filter_btn)
20444
20505
  filter_layout.addWidget(advanced_filter_btn)
20506
+ filter_layout.addWidget(sort_btn) # Sort dropdown
20445
20507
  filter_layout.addWidget(self.file_filter_combo) # File filter for multi-file projects
20446
20508
  filter_layout.addWidget(show_invisibles_btn_home)
20447
20509
  filter_layout.addWidget(self.spellcheck_btn)
@@ -37616,7 +37678,135 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37616
37678
  self.table.setUpdatesEnabled(True)
37617
37679
 
37618
37680
  self.log(f"🔍 Advanced filters: showing {visible_count} of {len(self.current_project.segments)} segments")
37619
-
37681
+
37682
+ def apply_sort(self, sort_type: str = None):
37683
+ """Sort segments by various criteria (similar to memoQ)"""
37684
+ if not self.current_project or not hasattr(self, 'table') or self.table is None:
37685
+ return
37686
+
37687
+ if not self.current_project.segments:
37688
+ return
37689
+
37690
+ # Store original document order if not already stored
37691
+ if not hasattr(self, '_original_segment_order'):
37692
+ self._original_segment_order = self.current_project.segments.copy()
37693
+
37694
+ # Update current sort state
37695
+ self.current_sort = sort_type
37696
+
37697
+ # If sort_type is None, restore document order
37698
+ if sort_type is None:
37699
+ self.current_project.segments = self._original_segment_order.copy()
37700
+
37701
+ # Set pagination to "All" to show all segments
37702
+ if hasattr(self, 'page_size_combo') and self._widget_is_alive(self.page_size_combo):
37703
+ self.page_size_combo.blockSignals(True)
37704
+ self.page_size_combo.setCurrentText("All")
37705
+ self.page_size_combo.blockSignals(False)
37706
+ # Update the internal page size variable
37707
+ if hasattr(self, 'grid_page_size'):
37708
+ self.grid_page_size = 999999
37709
+
37710
+ self.load_segments_to_grid()
37711
+ self.log("↩️ Restored document order (showing all segments)")
37712
+ return
37713
+
37714
+ # Helper function to get text without tags for more accurate sorting
37715
+ def strip_tags(text: str) -> str:
37716
+ """Remove HTML/XML tags from text for sorting"""
37717
+ import re
37718
+ return re.sub(r'<[^>]+>', '', text).strip()
37719
+
37720
+ # Calculate frequency maps if needed
37721
+ frequency_cache = {}
37722
+ if 'freq' in sort_type:
37723
+ from collections import Counter
37724
+ if 'source' in sort_type:
37725
+ counter = Counter(strip_tags(seg.source).lower() for seg in self.current_project.segments)
37726
+ frequency_cache = {strip_tags(seg.source).lower(): counter[strip_tags(seg.source).lower()]
37727
+ for seg in self.current_project.segments}
37728
+ else: # target frequency
37729
+ counter = Counter(strip_tags(seg.target).lower() for seg in self.current_project.segments if seg.target)
37730
+ frequency_cache = {strip_tags(seg.target).lower(): counter[strip_tags(seg.target).lower()]
37731
+ for seg in self.current_project.segments if seg.target}
37732
+
37733
+ # Sort based on selected criterion
37734
+ try:
37735
+ if sort_type == 'source_asc':
37736
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.source).lower())
37737
+ sort_name = "Source A → Z"
37738
+ elif sort_type == 'source_desc':
37739
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.source).lower(), reverse=True)
37740
+ sort_name = "Source Z → A"
37741
+ elif sort_type == 'target_asc':
37742
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.target).lower() if s.target else "")
37743
+ sort_name = "Target A → Z"
37744
+ elif sort_type == 'target_desc':
37745
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.target).lower() if s.target else "", reverse=True)
37746
+ sort_name = "Target Z → A"
37747
+ elif sort_type == 'source_length_asc':
37748
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.source)))
37749
+ sort_name = "Source (shorter first)"
37750
+ elif sort_type == 'source_length_desc':
37751
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.source)), reverse=True)
37752
+ sort_name = "Source (longer first)"
37753
+ elif sort_type == 'target_length_asc':
37754
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.target)) if s.target else 0)
37755
+ sort_name = "Target (shorter first)"
37756
+ elif sort_type == 'target_length_desc':
37757
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.target)) if s.target else 0, reverse=True)
37758
+ sort_name = "Target (longer first)"
37759
+ elif sort_type == 'match_asc':
37760
+ self.current_project.segments.sort(key=lambda s: getattr(s, 'match_percent', 0) or 0)
37761
+ sort_name = "Match Rate (lower first)"
37762
+ elif sort_type == 'match_desc':
37763
+ self.current_project.segments.sort(key=lambda s: getattr(s, 'match_percent', 0) or 0, reverse=True)
37764
+ sort_name = "Match Rate (higher first)"
37765
+ elif sort_type == 'source_freq_asc':
37766
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.source).lower(), 0))
37767
+ sort_name = "Source Frequency (lower first)"
37768
+ elif sort_type == 'source_freq_desc':
37769
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.source).lower(), 0), reverse=True)
37770
+ sort_name = "Source Frequency (higher first)"
37771
+ elif sort_type == 'target_freq_asc':
37772
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.target).lower(), 0) if s.target else 0)
37773
+ sort_name = "Target Frequency (lower first)"
37774
+ elif sort_type == 'target_freq_desc':
37775
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.target).lower(), 0) if s.target else 0, reverse=True)
37776
+ sort_name = "Target Frequency (higher first)"
37777
+ elif sort_type == 'modified_asc':
37778
+ self.current_project.segments.sort(key=lambda s: s.modified_at if s.modified_at else "")
37779
+ sort_name = "Last Changed (oldest first)"
37780
+ elif sort_type == 'modified_desc':
37781
+ self.current_project.segments.sort(key=lambda s: s.modified_at if s.modified_at else "", reverse=True)
37782
+ sort_name = "Last Changed (newest first)"
37783
+ elif sort_type == 'status':
37784
+ # Sort by status in a logical order: not_started, draft, translated, confirmed
37785
+ status_order = {'not_started': 0, 'draft': 1, 'translated': 2, 'confirmed': 3}
37786
+ self.current_project.segments.sort(key=lambda s: status_order.get(s.status, 99))
37787
+ sort_name = "Row Status"
37788
+ else:
37789
+ self.log(f"⚠️ Unknown sort type: {sort_type}")
37790
+ return
37791
+
37792
+ # Set pagination to "All" to show all sorted segments
37793
+ if hasattr(self, 'page_size_combo') and self._widget_is_alive(self.page_size_combo):
37794
+ self.page_size_combo.blockSignals(True)
37795
+ self.page_size_combo.setCurrentText("All")
37796
+ self.page_size_combo.blockSignals(False)
37797
+ # Update the internal page size variable
37798
+ if hasattr(self, 'grid_page_size'):
37799
+ self.grid_page_size = 999999
37800
+
37801
+ # Reload grid to reflect new order
37802
+ self.load_segments_to_grid()
37803
+ self.log(f"⇅ Sorted by: {sort_name} (showing all segments)")
37804
+
37805
+ except Exception as e:
37806
+ self.log(f"❌ Error sorting segments: {e}")
37807
+ import traceback
37808
+ traceback.print_exc()
37809
+
37620
37810
  # ========================================================================
37621
37811
  # TABBED SEGMENT EDITOR METHODS (for Grid view)
37622
37812
  # ========================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.186
3
+ Version: 1.9.188
4
4
  Summary: Professional AI-enhanced translation workbench with multi-LLM support, glossary system, TM, spellcheck, voice commands, and PyQt6 interface. Batteries included (core).
5
5
  Home-page: https://supervertaler.com
6
6
  Author: Michael Beijer
@@ -1,4 +1,4 @@
1
- Supervertaler.py,sha256=WC3nqwiY5d9QqlcSazFqSZd-S9DAPFgAsPhppK2QQaU,2342425
1
+ Supervertaler.py,sha256=A1mrclFgUCGhQmplbECjOl66vWosWT0npDn-dgzmiPA,2352573
2
2
  modules/__init__.py,sha256=G58XleS-EJ2sX4Kehm-3N2m618_W2Es0Kg8CW_eBG7g,327
3
3
  modules/ai_actions.py,sha256=i5MJcM-7Y6CAvKUwxmxrVHeoZAVtAP7aRDdWM5KLkO0,33877
4
4
  modules/ai_attachment_manager.py,sha256=juZlrW3UPkIkcnj0SREgOQkQROLf0fcu3ShZcKXMxsI,11361
@@ -79,9 +79,9 @@ modules/unified_prompt_manager_qt.py,sha256=HkGUnH0wlfxt-hVe-nKCeWLyProYdefuuq2s
79
79
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
80
80
  modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
81
81
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
82
- supervertaler-1.9.186.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
83
- supervertaler-1.9.186.dist-info/METADATA,sha256=PkpuWKzW5oAa0mDFBFMvaq0Vmo6houXf0taGVT3LEaM,5725
84
- supervertaler-1.9.186.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
85
- supervertaler-1.9.186.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
86
- supervertaler-1.9.186.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
87
- supervertaler-1.9.186.dist-info/RECORD,,
82
+ supervertaler-1.9.188.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
83
+ supervertaler-1.9.188.dist-info/METADATA,sha256=yQEbyUHJJ3bownTFzAotI23O-tVUEvW61TK6j1jJplQ,5725
84
+ supervertaler-1.9.188.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
85
+ supervertaler-1.9.188.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
86
+ supervertaler-1.9.188.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
87
+ supervertaler-1.9.188.dist-info/RECORD,,