supervertaler 1.9.109__py3-none-any.whl → 1.9.112__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.
Supervertaler.py CHANGED
@@ -34,7 +34,7 @@ License: MIT
34
34
  """
35
35
 
36
36
  # Version Information.
37
- __version__ = "1.9.109"
37
+ __version__ = "1.9.112"
38
38
  __phase__ = "0.9"
39
39
  __release_date__ = "2026-01-18"
40
40
  __edition__ = "Qt"
@@ -6085,8 +6085,8 @@ class SupervertalerQt(QMainWindow):
6085
6085
  export_txt_action.triggered.connect(self.export_simple_txt)
6086
6086
  export_menu.addAction(export_txt_action)
6087
6087
 
6088
- export_ai_action = QAction("🤖 &AI-Readable Format (TXT)...", self)
6089
- export_ai_action.triggered.connect(self.export_for_ai)
6088
+ export_ai_action = QAction("📄 &AI-Readable Markdown (.md)...", self)
6089
+ export_ai_action.triggered.connect(self.export_bilingual_table_markdown)
6090
6090
  export_ai_action.setToolTip("Export segments in [SEGMENT] format for AI translation/review")
6091
6091
  export_menu.addAction(export_ai_action)
6092
6092
 
@@ -6415,11 +6415,53 @@ class SupervertalerQt(QMainWindow):
6415
6415
  # Tools Menu
6416
6416
  tools_menu = menubar.addMenu("&Tools")
6417
6417
 
6418
- autofingers_action = QAction("✋ &AutoFingers - CAT Tool Automation...", self)
6418
+ # Tools in same order as Tools tab
6419
+ autofingers_action = QAction("✋ &AutoFingers...", self)
6419
6420
  autofingers_action.setShortcut("Ctrl+Shift+A")
6420
6421
  autofingers_action.triggered.connect(self.show_autofingers)
6421
6422
  tools_menu.addAction(autofingers_action)
6422
6423
 
6424
+ superconverter_action = QAction("🔄 Super&converter...", self)
6425
+ superconverter_action.triggered.connect(lambda: self._navigate_to_tool("Superconverter"))
6426
+ tools_menu.addAction(superconverter_action)
6427
+
6428
+ pdf_rescue_action = QAction("📄 &PDF Rescue...", self)
6429
+ pdf_rescue_action.triggered.connect(lambda: self._navigate_to_tool("PDF Rescue"))
6430
+ tools_menu.addAction(pdf_rescue_action)
6431
+
6432
+ superbench_action = QAction("📊 Super&bench...", self)
6433
+ superbench_action.triggered.connect(lambda: self._navigate_to_tool("Superbench"))
6434
+ tools_menu.addAction(superbench_action)
6435
+
6436
+ superbrowser_action = QAction("🌐 Super&browser...", self)
6437
+ superbrowser_action.triggered.connect(lambda: self._navigate_to_tool("Superbrowser"))
6438
+ tools_menu.addAction(superbrowser_action)
6439
+
6440
+ supercleaner_action = QAction("🧹 Supercleaner...", self)
6441
+ supercleaner_action.triggered.connect(lambda: self._navigate_to_tool("Supercleaner"))
6442
+ tools_menu.addAction(supercleaner_action)
6443
+
6444
+ superlookup_action = QAction("🔍 Super&lookup...", self)
6445
+ superlookup_action.setShortcut("Ctrl+K")
6446
+ superlookup_action.triggered.connect(self._go_to_superlookup)
6447
+ tools_menu.addAction(superlookup_action)
6448
+
6449
+ supervoice_action = QAction("🎤 Super&voice...", self)
6450
+ supervoice_action.triggered.connect(lambda: self._navigate_to_tool("Supervoice"))
6451
+ tools_menu.addAction(supervoice_action)
6452
+
6453
+ encoding_action = QAction("🔧 &Text Encoding Repair...", self)
6454
+ encoding_action.triggered.connect(lambda: self._navigate_to_tool("Text Encoding Repair"))
6455
+ tools_menu.addAction(encoding_action)
6456
+
6457
+ tmx_editor_action = QAction("✏️ T&MX Editor...", self)
6458
+ tmx_editor_action.triggered.connect(lambda: self._navigate_to_tool("TMX Editor"))
6459
+ tools_menu.addAction(tmx_editor_action)
6460
+
6461
+ tracked_changes_action = QAction("🔄 Tracked &Changes...", self)
6462
+ tracked_changes_action.triggered.connect(lambda: self._navigate_to_tool("Tracked Changes"))
6463
+ tools_menu.addAction(tracked_changes_action)
6464
+
6423
6465
  tools_menu.addSeparator()
6424
6466
 
6425
6467
  image_extractor_action = QAction("🖼️ &Image Extractor (Superimage)...", self)
@@ -7282,6 +7324,143 @@ class SupervertalerQt(QMainWindow):
7282
7324
 
7283
7325
  return prompt_widget
7284
7326
 
7327
+
7328
+ def create_superconverter_tab(self) -> QWidget:
7329
+ """Create the Superconverter tab - Format conversion tools"""
7330
+ container = QWidget()
7331
+ main_layout = QVBoxLayout(container)
7332
+ main_layout.setContentsMargins(10, 10, 10, 10)
7333
+ main_layout.setSpacing(10)
7334
+
7335
+ # Header
7336
+ header = QLabel("🔄 Superconverter")
7337
+ header.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
7338
+ main_layout.addWidget(header)
7339
+
7340
+ # Description
7341
+ description = QLabel(
7342
+ "Convert translation data between different formats - perfect for AI workflows, CAT tool exchanges, and data processing."
7343
+ )
7344
+ description.setWordWrap(True)
7345
+ description.setStyleSheet("color: #666; padding: 5px; background-color: #E3F2FD; border-radius: 3px;")
7346
+ main_layout.addWidget(description)
7347
+
7348
+ main_layout.addSpacing(5)
7349
+
7350
+ # Create tabbed interface for different conversion types
7351
+ tabs = QTabWidget()
7352
+ tabs.setStyleSheet("QTabBar::tab { outline: 0; } QTabBar::tab:focus { outline: none; }")
7353
+
7354
+ # Tab 1: Bilingual Export (current project)
7355
+ bilingual_tab = QWidget()
7356
+ bilingual_layout = QVBoxLayout(bilingual_tab)
7357
+ bilingual_layout.setContentsMargins(15, 15, 15, 15)
7358
+
7359
+ bilingual_info = QLabel(
7360
+ "<b>Export Current Project</b><br><br>"
7361
+ "Export your translation project as a Markdown table - perfect for AI chat interfaces like ChatGPT and Claude.<br><br>"
7362
+ "The table format renders beautifully and makes it easy for AI to understand and process your segments."
7363
+ )
7364
+ bilingual_info.setWordWrap(True)
7365
+ bilingual_info.setTextFormat(Qt.TextFormat.RichText)
7366
+ bilingual_layout.addWidget(bilingual_info)
7367
+
7368
+ bilingual_layout.addSpacing(15)
7369
+
7370
+ bilingual_btn = QPushButton("📄 Export as Markdown Table")
7371
+ bilingual_btn.setMinimumHeight(40)
7372
+ bilingual_btn.setStyleSheet(
7373
+ "QPushButton { background-color: #2196F3; color: white; font-weight: bold; "
7374
+ "border: none; border-radius: 5px; padding: 10px; outline: none; }"
7375
+ "QPushButton:hover { background-color: #1976D2; }"
7376
+ )
7377
+ bilingual_btn.clicked.connect(self.export_bilingual_table_markdown)
7378
+ bilingual_layout.addWidget(bilingual_btn)
7379
+
7380
+ bilingual_layout.addStretch()
7381
+ tabs.addTab(bilingual_tab, "📊 Bilingual Table")
7382
+
7383
+ # Tab 2: Document Converter (monolingual docs to Markdown)
7384
+ doc_tab = QWidget()
7385
+ doc_layout = QVBoxLayout(doc_tab)
7386
+ doc_layout.setContentsMargins(15, 15, 15, 15)
7387
+
7388
+ doc_info = QLabel(
7389
+ "<b>Convert Documents to Markdown</b><br><br>"
7390
+ "Convert DOCX or TXT documents to Markdown format, preserving structure (headings, lists, paragraphs).<br><br>"
7391
+ "Perfect for preparing documents for AI processing or publishing on web platforms."
7392
+ )
7393
+ doc_info.setWordWrap(True)
7394
+ doc_info.setTextFormat(Qt.TextFormat.RichText)
7395
+ doc_layout.addWidget(doc_info)
7396
+
7397
+ doc_layout.addSpacing(15)
7398
+
7399
+ # Single file button
7400
+ single_doc_btn = QPushButton("📄 Convert Single Document")
7401
+ single_doc_btn.setMinimumHeight(40)
7402
+ single_doc_btn.setStyleSheet(
7403
+ "QPushButton { background-color: #4CAF50; color: white; font-weight: bold; "
7404
+ "border: none; border-radius: 5px; padding: 10px; outline: none; }"
7405
+ "QPushButton:hover { background-color: #388E3C; }"
7406
+ )
7407
+ single_doc_btn.clicked.connect(self.convert_document_to_markdown)
7408
+ doc_layout.addWidget(single_doc_btn)
7409
+
7410
+ doc_layout.addSpacing(10)
7411
+
7412
+ # Batch conversion button
7413
+ batch_doc_btn = QPushButton("📁 Batch Convert Multiple Documents")
7414
+ batch_doc_btn.setMinimumHeight(40)
7415
+ batch_doc_btn.setStyleSheet(
7416
+ "QPushButton { background-color: #FF9800; color: white; font-weight: bold; "
7417
+ "border: none; border-radius: 5px; padding: 10px; outline: none; }"
7418
+ "QPushButton:hover { background-color: #F57C00; }"
7419
+ )
7420
+ batch_doc_btn.clicked.connect(self.batch_convert_documents_to_markdown)
7421
+ doc_layout.addWidget(batch_doc_btn)
7422
+
7423
+ doc_layout.addStretch()
7424
+ tabs.addTab(doc_tab, "📝 Document → Markdown")
7425
+
7426
+ # Tab 3: TMX Tools (placeholder for future)
7427
+ tmx_tab = QWidget()
7428
+ tmx_layout = QVBoxLayout(tmx_tab)
7429
+ tmx_layout.setContentsMargins(15, 15, 15, 15)
7430
+
7431
+ tmx_info = QLabel(
7432
+ "<b>TMX Conversion Tools</b><br><br>"
7433
+ "Convert Translation Memory eXchange (TMX) files to and from tab-delimited format.<br><br>"
7434
+ "<i>Coming soon...</i>"
7435
+ )
7436
+ tmx_info.setWordWrap(True)
7437
+ tmx_info.setTextFormat(Qt.TextFormat.RichText)
7438
+ tmx_info.setStyleSheet("color: #888;")
7439
+ tmx_layout.addWidget(tmx_info)
7440
+
7441
+ tmx_layout.addSpacing(15)
7442
+
7443
+ tmx_to_tsv_btn = QPushButton("TMX → Tab-delimited")
7444
+ tmx_to_tsv_btn.setMinimumHeight(40)
7445
+ tmx_to_tsv_btn.setEnabled(False)
7446
+ tmx_to_tsv_btn.setStyleSheet("QPushButton:disabled { color: #999; background-color: #E0E0E0; }")
7447
+ tmx_layout.addWidget(tmx_to_tsv_btn)
7448
+
7449
+ tmx_layout.addSpacing(10)
7450
+
7451
+ tsv_to_tmx_btn = QPushButton("Tab-delimited → TMX")
7452
+ tsv_to_tmx_btn.setMinimumHeight(40)
7453
+ tsv_to_tmx_btn.setEnabled(False)
7454
+ tsv_to_tmx_btn.setStyleSheet("QPushButton:disabled { color: #999; background-color: #E0E0E0; }")
7455
+ tmx_layout.addWidget(tsv_to_tmx_btn)
7456
+
7457
+ tmx_layout.addStretch()
7458
+ tabs.addTab(tmx_tab, "🔄 TMX Tools")
7459
+
7460
+ main_layout.addWidget(tabs)
7461
+
7462
+ return container
7463
+
7285
7464
  def create_supercleaner_tab(self) -> QWidget:
7286
7465
  """Create the Supercleaner tab - Clean DOCX documents"""
7287
7466
  from modules.supercleaner_ui import SupercleanerUI
@@ -8412,6 +8591,10 @@ class SupervertalerQt(QMainWindow):
8412
8591
  autofingers_tab = AutoFingersWidget(self)
8413
8592
  modules_tabs.addTab(autofingers_tab, "✋ AutoFingers")
8414
8593
 
8594
+ # Superconverter - Format conversion tools
8595
+ superconverter_tab = self.create_superconverter_tab()
8596
+ modules_tabs.addTab(superconverter_tab, "🔄 Superconverter")
8597
+
8415
8598
  pdf_tab = self.create_pdf_rescue_tab()
8416
8599
  modules_tabs.addTab(pdf_tab, "📄 PDF Rescue")
8417
8600
 
@@ -9734,8 +9917,12 @@ class SupervertalerQt(QMainWindow):
9734
9917
  elif status in ('draft', 'needs_review'):
9735
9918
  run.font.color.rgb = RGBColor(200, 100, 0) # Orange
9736
9919
 
9737
- # Notes column - empty for user to fill
9738
- cells[4].text = ''
9920
+ # Notes column - populate with segment notes if available
9921
+ notes_text = seg.notes if hasattr(seg, 'notes') else ''
9922
+ cells[4].text = notes_text
9923
+ for para in cells[4].paragraphs:
9924
+ for run in para.runs:
9925
+ run.font.size = Pt(8)
9739
9926
 
9740
9927
  # Add footer with branding
9741
9928
  footer_para = doc.add_paragraph()
@@ -18502,7 +18689,7 @@ class SupervertalerQt(QMainWindow):
18502
18689
  header.setStretchLastSection(False) # Don't auto-stretch last section (we use Stretch mode for Source/Target)
18503
18690
 
18504
18691
  # Set initial column widths - give Source and Target equal space
18505
- self.table.setColumnWidth(0, 55) # ID - fits 4-digit segment numbers
18692
+ self.table.setColumnWidth(0, 40) # ID - compact, fits up to 3 digits comfortably
18506
18693
  self.table.setColumnWidth(1, 40) # Type - narrower
18507
18694
  self.table.setColumnWidth(2, 400) # Source
18508
18695
  self.table.setColumnWidth(3, 400) # Target
@@ -20922,6 +21109,9 @@ class SupervertalerQt(QMainWindow):
20922
21109
  # Initialize TM for this project
20923
21110
  self.initialize_tm_database()
20924
21111
 
21112
+ # Deactivate all resources for new project (user explicitly activates what they need)
21113
+ self._deactivate_all_resources_for_new_project()
21114
+
20925
21115
  # Auto-resize rows for better initial display
20926
21116
  self.auto_resize_rows()
20927
21117
 
@@ -21670,6 +21860,493 @@ class SupervertalerQt(QMainWindow):
21670
21860
  "Export Error",
21671
21861
  f"Failed to export AI format:\n\n{str(e)}"
21672
21862
  )
21863
+
21864
+ def export_bilingual_table_markdown(self):
21865
+ """Export current project segments as Markdown table (for AI systems)"""
21866
+ try:
21867
+ if not self.current_project or not self.current_project.segments:
21868
+ QMessageBox.warning(self, "No Project", "Please open a project with segments first")
21869
+ return
21870
+
21871
+ segments = list(self.current_project.segments)
21872
+
21873
+ # Get language names
21874
+ source_lang = self.current_project.source_lang or "Source"
21875
+ target_lang = self.current_project.target_lang or "Target"
21876
+
21877
+ # Check translation status
21878
+ translated_count = sum(1 for seg in segments if seg.target and seg.target.strip())
21879
+ total_count = len(segments)
21880
+
21881
+ # Show export options dialog
21882
+ dialog = QDialog(self)
21883
+ dialog.setWindowTitle("Export as Markdown Table")
21884
+ dialog.setMinimumWidth(500)
21885
+
21886
+ layout = QVBoxLayout(dialog)
21887
+
21888
+ # Info label
21889
+ info_label = QLabel(
21890
+ "Export segments as a <b>Markdown table</b> optimized for AI systems.\n"
21891
+ "The table format renders beautifully in ChatGPT, Claude, and all AI chat interfaces."
21892
+ )
21893
+ info_label.setWordWrap(True)
21894
+ info_label.setTextFormat(Qt.TextFormat.RichText)
21895
+ layout.addWidget(info_label)
21896
+
21897
+ layout.addSpacing(10)
21898
+
21899
+ # Status info
21900
+ status_label = QLabel(
21901
+ f"📊 <b>{translated_count} of {total_count}</b> segments have translations."
21902
+ if translated_count > 0
21903
+ else f"📊 <b>{total_count}</b> segments (none translated yet)."
21904
+ )
21905
+ status_label.setTextFormat(Qt.TextFormat.RichText)
21906
+ layout.addWidget(status_label)
21907
+
21908
+ layout.addSpacing(10)
21909
+
21910
+ # Content options
21911
+ content_group = QGroupBox("Content Options")
21912
+ content_layout = QVBoxLayout(content_group)
21913
+
21914
+ include_both_radio = CheckmarkRadioButton(f"Include both {source_lang} and {target_lang} (bilingual table)")
21915
+ include_both_radio.setChecked(True)
21916
+ source_only_radio = CheckmarkRadioButton(f"{source_lang} only (for AI translation)")
21917
+ target_only_radio = CheckmarkRadioButton(f"{target_lang} only (translated segments)")
21918
+
21919
+ content_layout.addWidget(include_both_radio)
21920
+ content_layout.addWidget(source_only_radio)
21921
+ content_layout.addWidget(target_only_radio)
21922
+ layout.addWidget(content_group)
21923
+
21924
+ # Filter options
21925
+ filter_group = QGroupBox("Segment Filter")
21926
+ filter_layout = QVBoxLayout(filter_group)
21927
+
21928
+ all_segments_radio = CheckmarkRadioButton("All segments")
21929
+ all_segments_radio.setChecked(True)
21930
+ untranslated_radio = CheckmarkRadioButton("Untranslated segments only (empty target)")
21931
+ translated_radio = CheckmarkRadioButton("Translated segments only (has target)")
21932
+
21933
+ filter_layout.addWidget(all_segments_radio)
21934
+ filter_layout.addWidget(untranslated_radio)
21935
+ filter_layout.addWidget(translated_radio)
21936
+ layout.addWidget(filter_group)
21937
+
21938
+ layout.addSpacing(10)
21939
+
21940
+ # Preview
21941
+ preview_group = QGroupBox("Preview")
21942
+ preview_layout = QVBoxLayout(preview_group)
21943
+ preview_text = QTextEdit()
21944
+ preview_text.setReadOnly(True)
21945
+ preview_text.setMaximumHeight(150)
21946
+ preview_text.setStyleSheet("font-family: Consolas, monospace; font-size: 10px;")
21947
+ preview_layout.addWidget(preview_text)
21948
+ layout.addWidget(preview_group)
21949
+
21950
+ def update_preview():
21951
+ # Show preview with sample data
21952
+ if include_both_radio.isChecked():
21953
+ preview = f"# Translation Project: {self.current_project.name or 'Untitled'}\n\n"
21954
+ preview += f"| Segment | {source_lang} | {target_lang} |\n"
21955
+ preview += "|---------|" + "-" * (len(source_lang) + 2) + "|" + "-" * (len(target_lang) + 2) + "|\n"
21956
+ preview += "| 1 | Source text example... | Target text example... |\n"
21957
+ preview += "| 2 | Another segment... | Translation here... |"
21958
+ elif source_only_radio.isChecked():
21959
+ preview = f"# Translation Project: {self.current_project.name or 'Untitled'}\n\n"
21960
+ preview += f"| Segment | {source_lang} |\n"
21961
+ preview += "|---------|" + "-" * (len(source_lang) + 2) + "|\n"
21962
+ preview += "| 1 | Source text example... |\n"
21963
+ preview += "| 2 | Another segment... |"
21964
+ else:
21965
+ preview = f"# Translation Project: {self.current_project.name or 'Untitled'}\n\n"
21966
+ preview += f"| Segment | {target_lang} |\n"
21967
+ preview += "|---------|" + "-" * (len(target_lang) + 2) + "|\n"
21968
+ preview += "| 1 | Target text example... |\n"
21969
+ preview += "| 2 | Translation here... |"
21970
+
21971
+ preview_text.setPlainText(preview)
21972
+
21973
+ # Connect signals for live preview
21974
+ include_both_radio.toggled.connect(update_preview)
21975
+ source_only_radio.toggled.connect(update_preview)
21976
+ target_only_radio.toggled.connect(update_preview)
21977
+
21978
+ update_preview() # Initial preview
21979
+
21980
+ layout.addSpacing(10)
21981
+
21982
+ # Buttons
21983
+ button_layout = QHBoxLayout()
21984
+ ok_btn = QPushButton("Export")
21985
+ ok_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; border: none; outline: none;")
21986
+ ok_btn.clicked.connect(dialog.accept)
21987
+ cancel_btn = QPushButton("Cancel")
21988
+ cancel_btn.clicked.connect(dialog.reject)
21989
+
21990
+ button_layout.addStretch()
21991
+ button_layout.addWidget(cancel_btn)
21992
+ button_layout.addWidget(ok_btn)
21993
+ layout.addLayout(button_layout)
21994
+
21995
+ if dialog.exec() != QDialog.DialogCode.Accepted:
21996
+ return
21997
+
21998
+ # Determine content mode
21999
+ if source_only_radio.isChecked():
22000
+ content_mode = "source_only"
22001
+ elif target_only_radio.isChecked():
22002
+ content_mode = "target_only"
22003
+ else:
22004
+ content_mode = "bilingual"
22005
+
22006
+ # Filter segments
22007
+ if untranslated_radio.isChecked():
22008
+ filtered_segments = [s for s in segments if not (s.target and s.target.strip())]
22009
+ elif translated_radio.isChecked():
22010
+ filtered_segments = [s for s in segments if s.target and s.target.strip()]
22011
+ else:
22012
+ filtered_segments = segments
22013
+
22014
+ if not filtered_segments:
22015
+ QMessageBox.warning(
22016
+ self, "No Segments",
22017
+ "No segments match the selected filter criteria."
22018
+ )
22019
+ return
22020
+
22021
+ # Get save path
22022
+ default_name = ""
22023
+ if self.current_project.name:
22024
+ default_name = self.current_project.name.replace(" ", "_") + "_table.md"
22025
+
22026
+ file_path, _ = QFileDialog.getSaveFileName(
22027
+ self,
22028
+ "Export Markdown Table",
22029
+ default_name,
22030
+ "Markdown Files (*.md);;All Files (*.*)"
22031
+ )
22032
+
22033
+ if not file_path:
22034
+ return
22035
+
22036
+ # Ensure .md extension
22037
+ if not file_path.lower().endswith('.md'):
22038
+ file_path += '.md'
22039
+
22040
+ # Build Markdown table
22041
+ output_lines = []
22042
+ output_lines.append(f"# Translation Project: {self.current_project.name or 'Untitled'}")
22043
+ output_lines.append("")
22044
+ output_lines.append(f"**Languages:** {source_lang} → {target_lang}")
22045
+ output_lines.append(f"**Segments:** {len(filtered_segments)}")
22046
+ output_lines.append(f"**Exported:** {datetime.now().strftime('%Y-%m-%d %H:%M')}")
22047
+ output_lines.append("")
22048
+
22049
+ # Table header
22050
+ if content_mode == "bilingual":
22051
+ output_lines.append(f"| Segment | {source_lang} | {target_lang} |")
22052
+ output_lines.append("|---------|" + "-" * (len(source_lang) + 2) + "|" + "-" * (len(target_lang) + 2) + "|")
22053
+ elif content_mode == "source_only":
22054
+ output_lines.append(f"| Segment | {source_lang} |")
22055
+ output_lines.append("|---------|" + "-" * (len(source_lang) + 2) + "|")
22056
+ else: # target_only
22057
+ output_lines.append(f"| Segment | {target_lang} |")
22058
+ output_lines.append("|---------|" + "-" * (len(target_lang) + 2) + "|")
22059
+
22060
+ # Table rows
22061
+ for i, seg in enumerate(filtered_segments, 1):
22062
+ # Escape pipe characters in text for Markdown tables
22063
+ source_text = seg.source.replace("|", "\\|").replace("\n", " ")
22064
+ target_text = (seg.target or "").replace("|", "\\|").replace("\n", " ")
22065
+
22066
+ if content_mode == "bilingual":
22067
+ output_lines.append(f"| {i} | {source_text} | {target_text} |")
22068
+ elif content_mode == "source_only":
22069
+ output_lines.append(f"| {i} | {source_text} |")
22070
+ else: # target_only
22071
+ output_lines.append(f"| {i} | {target_text} |")
22072
+
22073
+ # Write file
22074
+ with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
22075
+ f.write('\n'.join(output_lines))
22076
+
22077
+ self.log(f"✓ Exported {len(filtered_segments)} segments as Markdown table to: {os.path.basename(file_path)}")
22078
+
22079
+ QMessageBox.information(
22080
+ self,
22081
+ "Export Complete",
22082
+ f"Successfully exported {len(filtered_segments)} segments as Markdown table to:\n\n{os.path.basename(file_path)}\n\n"
22083
+ f"This format renders beautifully in ChatGPT, Claude, and other AI chat interfaces!"
22084
+ )
22085
+
22086
+ except Exception as e:
22087
+ self.log(f"✗ Markdown table export failed: {str(e)}")
22088
+ QMessageBox.critical(
22089
+ self,
22090
+ "Export Error",
22091
+ f"Failed to export Markdown table:\n\n{str(e)}"
22092
+ )
22093
+
22094
+ def convert_document_to_markdown(self):
22095
+ """Convert a single DOCX or TXT document to Markdown format, preserving structure"""
22096
+ try:
22097
+ # Select input file
22098
+ file_path, _ = QFileDialog.getOpenFileName(
22099
+ self,
22100
+ "Select Document to Convert",
22101
+ "",
22102
+ "Documents (*.docx *.txt);;All Files (*.*)"
22103
+ )
22104
+
22105
+ if not file_path:
22106
+ return
22107
+
22108
+ # Determine output path
22109
+ base_name = os.path.splitext(file_path)[0]
22110
+ output_path, _ = QFileDialog.getSaveFileName(
22111
+ self,
22112
+ "Save Markdown File",
22113
+ base_name + ".md",
22114
+ "Markdown Files (*.md);;All Files (*.*)"
22115
+ )
22116
+
22117
+ if not output_path:
22118
+ return
22119
+
22120
+ # Ensure .md extension
22121
+ if not output_path.lower().endswith('.md'):
22122
+ output_path += '.md'
22123
+
22124
+ # Convert based on file type
22125
+ if file_path.lower().endswith('.docx'):
22126
+ self._convert_docx_to_markdown(file_path, output_path)
22127
+ else: # TXT
22128
+ self._convert_txt_to_markdown(file_path, output_path)
22129
+
22130
+ self.log(f"✓ Converted {os.path.basename(file_path)} → {os.path.basename(output_path)}")
22131
+
22132
+ QMessageBox.information(
22133
+ self,
22134
+ "Conversion Complete",
22135
+ f"Successfully converted document to Markdown:\n\n{os.path.basename(output_path)}"
22136
+ )
22137
+
22138
+ except Exception as e:
22139
+ self.log(f"✗ Document conversion failed: {str(e)}")
22140
+ QMessageBox.critical(
22141
+ self,
22142
+ "Conversion Error",
22143
+ f"Failed to convert document:\n\n{str(e)}"
22144
+ )
22145
+
22146
+ def batch_convert_documents_to_markdown(self):
22147
+ """Batch convert multiple documents to Markdown format"""
22148
+ try:
22149
+ # Select multiple files
22150
+ file_paths, _ = QFileDialog.getOpenFileNames(
22151
+ self,
22152
+ "Select Documents to Convert",
22153
+ "",
22154
+ "Documents (*.docx *.txt);;All Files (*.*)"
22155
+ )
22156
+
22157
+ if not file_paths:
22158
+ return
22159
+
22160
+ # Select output folder
22161
+ output_folder = QFileDialog.getExistingDirectory(
22162
+ self,
22163
+ "Select Output Folder for Markdown Files",
22164
+ "",
22165
+ QFileDialog.Option.ShowDirsOnly
22166
+ )
22167
+
22168
+ if not output_folder:
22169
+ return
22170
+
22171
+ # Progress dialog
22172
+ progress = QProgressDialog(
22173
+ "Converting documents to Markdown...",
22174
+ "Cancel",
22175
+ 0,
22176
+ len(file_paths),
22177
+ self
22178
+ )
22179
+ progress.setWindowModality(Qt.WindowModality.WindowModal)
22180
+ progress.setMinimumDuration(0)
22181
+
22182
+ converted_count = 0
22183
+ failed_count = 0
22184
+
22185
+ for i, file_path in enumerate(file_paths):
22186
+ if progress.wasCanceled():
22187
+ break
22188
+
22189
+ progress.setValue(i)
22190
+ progress.setLabelText(f"Converting: {os.path.basename(file_path)}")
22191
+ QApplication.processEvents()
22192
+
22193
+ try:
22194
+ # Determine output path
22195
+ base_name = os.path.splitext(os.path.basename(file_path))[0]
22196
+ output_path = os.path.join(output_folder, base_name + ".md")
22197
+
22198
+ # Convert based on file type
22199
+ if file_path.lower().endswith('.docx'):
22200
+ self._convert_docx_to_markdown(file_path, output_path)
22201
+ else: # TXT
22202
+ self._convert_txt_to_markdown(file_path, output_path)
22203
+
22204
+ converted_count += 1
22205
+ self.log(f"✓ Converted: {os.path.basename(file_path)}")
22206
+
22207
+ except Exception as e:
22208
+ failed_count += 1
22209
+ self.log(f"✗ Failed: {os.path.basename(file_path)} - {str(e)}")
22210
+
22211
+ progress.setValue(len(file_paths))
22212
+
22213
+ # Show summary
22214
+ summary = f"Batch conversion complete!\n\n"
22215
+ summary += f"✓ Successfully converted: {converted_count} files\n"
22216
+ if failed_count > 0:
22217
+ summary += f"✗ Failed: {failed_count} files\n"
22218
+ summary += f"\nOutput folder: {output_folder}"
22219
+
22220
+ QMessageBox.information(
22221
+ self,
22222
+ "Batch Conversion Complete",
22223
+ summary
22224
+ )
22225
+
22226
+ except Exception as e:
22227
+ self.log(f"✗ Batch conversion failed: {str(e)}")
22228
+ QMessageBox.critical(
22229
+ self,
22230
+ "Conversion Error",
22231
+ f"Failed to perform batch conversion:\n\n{str(e)}"
22232
+ )
22233
+
22234
+ def _convert_docx_to_markdown(self, docx_path: str, output_path: str):
22235
+ """Convert DOCX document to Markdown, preserving structure"""
22236
+ from docx import Document
22237
+
22238
+ doc = Document(docx_path)
22239
+ markdown_lines = []
22240
+
22241
+ # Add document title
22242
+ doc_name = os.path.splitext(os.path.basename(docx_path))[0]
22243
+ markdown_lines.append(f"# {doc_name}")
22244
+ markdown_lines.append("")
22245
+
22246
+ for para in doc.paragraphs:
22247
+ if not para.text.strip():
22248
+ markdown_lines.append("")
22249
+ continue
22250
+
22251
+ # Check paragraph style
22252
+ style = para.style.name.lower()
22253
+ text = para.text.strip()
22254
+
22255
+ # Detect ALL CAPS headings (common in patent documents)
22256
+ words = text.split()
22257
+ is_all_caps = (
22258
+ len(words) >= 2 and
22259
+ all(word.isupper() or not word.isalpha() for word in words) and
22260
+ len(text) <= 100 # Reasonable heading length
22261
+ )
22262
+
22263
+ if 'heading 1' in style or style == 'title':
22264
+ markdown_lines.append(f"# {text}")
22265
+ elif 'heading 2' in style:
22266
+ markdown_lines.append(f"## {text}")
22267
+ elif 'heading 3' in style:
22268
+ markdown_lines.append(f"### {text}")
22269
+ elif 'heading 4' in style:
22270
+ markdown_lines.append(f"#### {text}")
22271
+ elif 'heading 5' in style:
22272
+ markdown_lines.append(f"##### {text}")
22273
+ elif 'heading 6' in style:
22274
+ markdown_lines.append(f"###### {text}")
22275
+ elif is_all_caps:
22276
+ # ALL CAPS line without heading style - treat as heading
22277
+ markdown_lines.append(f"## {text.title()}")
22278
+ elif 'list bullet' in style or 'bullet' in style:
22279
+ markdown_lines.append(f"- {text}")
22280
+ elif 'list number' in style or 'numbering' in style:
22281
+ markdown_lines.append(f"1. {text}")
22282
+ elif 'quote' in style or 'quotation' in style:
22283
+ markdown_lines.append(f"> {text}")
22284
+ else:
22285
+ # Regular paragraph - preserve bold/italic if present
22286
+ # Simple formatting detection (runs with bold/italic)
22287
+ has_bold = any(run.bold for run in para.runs if run.text.strip())
22288
+ has_italic = any(run.italic for run in para.runs if run.text.strip())
22289
+
22290
+ if has_bold and has_italic:
22291
+ markdown_lines.append(f"***{text}***")
22292
+ elif has_bold:
22293
+ markdown_lines.append(f"**{text}**")
22294
+ elif has_italic:
22295
+ markdown_lines.append(f"*{text}*")
22296
+ else:
22297
+ markdown_lines.append(text)
22298
+
22299
+ markdown_lines.append("")
22300
+
22301
+ # Write output
22302
+ with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
22303
+ f.write('\n'.join(markdown_lines))
22304
+
22305
+ def _convert_txt_to_markdown(self, txt_path: str, output_path: str):
22306
+ """Convert TXT document to Markdown with minimal formatting"""
22307
+ with open(txt_path, 'r', encoding='utf-8') as f:
22308
+ lines = f.readlines()
22309
+
22310
+ markdown_lines = []
22311
+
22312
+ # Add document title
22313
+ doc_name = os.path.splitext(os.path.basename(txt_path))[0]
22314
+ markdown_lines.append(f"# {doc_name}")
22315
+ markdown_lines.append("")
22316
+
22317
+ # Process lines - detect simple patterns
22318
+ for line in lines:
22319
+ stripped = line.strip()
22320
+
22321
+ if not stripped:
22322
+ markdown_lines.append("")
22323
+ continue
22324
+
22325
+ # Detect ALL CAPS headings (common in plain text documents)
22326
+ # Must be at least 3 words long and all uppercase
22327
+ words = stripped.split()
22328
+ is_all_caps = (
22329
+ len(words) >= 2 and
22330
+ all(word.isupper() or not word.isalpha() for word in words) and
22331
+ len(stripped) <= 100 # Reasonable heading length
22332
+ )
22333
+
22334
+ if is_all_caps:
22335
+ # Convert to title case and make it a heading
22336
+ markdown_lines.append(f"## {stripped.title()}")
22337
+ # Detect simple bullet points
22338
+ elif stripped.startswith('- ') or stripped.startswith('* '):
22339
+ markdown_lines.append(stripped)
22340
+ # Detect numbered lists
22341
+ elif len(stripped) > 2 and stripped[0].isdigit() and stripped[1] in '.):':
22342
+ markdown_lines.append(stripped)
22343
+ # Regular text
22344
+ else:
22345
+ markdown_lines.append(stripped)
22346
+
22347
+ # Write output
22348
+ with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
22349
+ f.write('\n'.join(markdown_lines))
21673
22350
 
21674
22351
  # ========================================================================
21675
22352
  # MULTI-FILE FOLDER IMPORT
@@ -22912,6 +23589,9 @@ class SupervertalerQt(QMainWindow):
22912
23589
  self.load_segments_to_grid()
22913
23590
  self.initialize_tm_database()
22914
23591
 
23592
+ # Deactivate all resources for new project (user explicitly activates what they need)
23593
+ self._deactivate_all_resources_for_new_project()
23594
+
22915
23595
  # Auto-resize rows for better initial display
22916
23596
  self.auto_resize_rows()
22917
23597
 
@@ -23406,6 +24086,9 @@ class SupervertalerQt(QMainWindow):
23406
24086
  self.load_segments_to_grid()
23407
24087
  self.initialize_tm_database()
23408
24088
 
24089
+ # Deactivate all resources for new project (user explicitly activates what they need)
24090
+ self._deactivate_all_resources_for_new_project()
24091
+
23409
24092
  # Auto-resize rows for better initial display
23410
24093
  self.auto_resize_rows()
23411
24094
 
@@ -23672,6 +24355,9 @@ class SupervertalerQt(QMainWindow):
23672
24355
  self.load_segments_to_grid()
23673
24356
  self.initialize_tm_database()
23674
24357
 
24358
+ # Deactivate all resources for new project (user explicitly activates what they need)
24359
+ self._deactivate_all_resources_for_new_project()
24360
+
23675
24361
  # Auto-resize rows for better initial display
23676
24362
  self.auto_resize_rows()
23677
24363
 
@@ -23881,6 +24567,9 @@ class SupervertalerQt(QMainWindow):
23881
24567
  self.load_segments_to_grid()
23882
24568
  self.initialize_tm_database()
23883
24569
 
24570
+ # Deactivate all resources for new project (user explicitly activates what they need)
24571
+ self._deactivate_all_resources_for_new_project()
24572
+
23884
24573
  # Auto-resize rows for better initial display
23885
24574
  self.auto_resize_rows()
23886
24575
 
@@ -29800,6 +30489,35 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
29800
30489
  except Exception as e:
29801
30490
  self.log(f"⚠ Could not initialize spellcheck: {e}")
29802
30491
 
30492
+ def _deactivate_all_resources_for_new_project(self):
30493
+ """Deactivate all TMs, termbases, and NT lists for a freshly imported project.
30494
+ This ensures new projects start with clean slate - user explicitly activates what they need."""
30495
+ if not self.current_project or not hasattr(self.current_project, 'id'):
30496
+ return
30497
+
30498
+ project_id = self.current_project.id
30499
+ if not project_id:
30500
+ return
30501
+
30502
+ # Deactivate all TMs for this project
30503
+ if hasattr(self, 'tm_metadata_mgr') and self.tm_metadata_mgr:
30504
+ all_tms = self.tm_metadata_mgr.get_all_tms()
30505
+ for tm in all_tms:
30506
+ self.tm_metadata_mgr.deactivate_tm(tm['id'], project_id)
30507
+
30508
+ # Deactivate all termbases for this project
30509
+ if hasattr(self, 'termbase_mgr') and self.termbase_mgr:
30510
+ all_termbases = self.termbase_mgr.get_all_termbases()
30511
+ for tb in all_termbases:
30512
+ self.termbase_mgr.deactivate_termbase(tb['id'], project_id)
30513
+
30514
+ # Deactivate all NT lists
30515
+ if hasattr(self, 'nt_manager') and self.nt_manager:
30516
+ for list_name in list(self.nt_manager.lists.keys()):
30517
+ self.nt_manager.set_list_active(list_name, False)
30518
+
30519
+ self.log("📋 New project: All TMs, glossaries, and NT lists deactivated (start clean)")
30520
+
29803
30521
  def search_and_display_tm_matches(self, source_text: str):
29804
30522
  """Search TM and Termbases and display matches with visual diff for fuzzy matches"""
29805
30523
  self.log(f"🚨 search_and_display_tm_matches called with source_text: '{source_text[:50]}...'")
@@ -31999,15 +32717,17 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
31999
32717
 
32000
32718
  try:
32001
32719
  visible_count = 0
32002
- row_count = self.table.rowCount()
32003
32720
  segments = self.current_project.segments
32721
+ total_segments = len(segments)
32004
32722
 
32005
32723
  # Pre-compute lowercase filter texts
32006
32724
  source_filter_lower = source_filter_text.lower() if source_filter_text else None
32007
32725
  target_filter_lower = target_filter_text.lower() if target_filter_text else None
32008
32726
 
32009
- for row in range(row_count):
32010
- if row >= len(segments):
32727
+ # IMPORTANT: Always search through ALL segments, not just visible rows
32728
+ # Pagination state should not affect which segments we search
32729
+ for row in range(total_segments):
32730
+ if row >= total_segments:
32011
32731
  break
32012
32732
 
32013
32733
  segment = segments[row]
@@ -35240,6 +35960,18 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
35240
35960
  self.modules_tabs.setCurrentIndex(i)
35241
35961
  break
35242
35962
 
35963
+ def _navigate_to_tool(self, tool_name: str):
35964
+ """Navigate to a specific tool in the Tools tab"""
35965
+ if hasattr(self, 'main_tabs'):
35966
+ # Main tabs: Grid=0, Resources=1, Prompt Manager=2, Tools=3, Settings=4
35967
+ self.main_tabs.setCurrentIndex(3) # Switch to Tools tab
35968
+ # Then switch to the specific tool sub-tab
35969
+ if hasattr(self, 'modules_tabs'):
35970
+ for i in range(self.modules_tabs.count()):
35971
+ if tool_name in self.modules_tabs.tabText(i):
35972
+ self.modules_tabs.setCurrentIndex(i)
35973
+ break
35974
+
35243
35975
  def open_api_keys_file(self):
35244
35976
  """Open API keys file in system text editor"""
35245
35977
  api_keys_file = self.user_data_path / "api_keys.txt"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.109
3
+ Version: 1.9.112
4
4
  Summary: Professional AI-powered 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
@@ -71,7 +71,7 @@ Dynamic: home-page
71
71
  Dynamic: license-file
72
72
  Dynamic: requires-python
73
73
 
74
- # 🚀 Supervertaler v1.9.109
74
+ # 🚀 Supervertaler v1.9.112
75
75
 
76
76
  [![PyPI version](https://badge.fury.io/py/supervertaler.svg)](https://pypi.org/project/Supervertaler/)
77
77
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
@@ -79,7 +79,14 @@ Dynamic: requires-python
79
79
 
80
80
  AI-enhanced CAT tool with multi-LLM support (GPT-4, Claude, Gemini, Ollama), innovative Superlookup concordance system offering access to multiple terminology sources (TMs, glossaries, web resources, etc.), and seamless CAT tool integration (memoQ, Trados, CafeTran, Phrase).
81
81
 
82
- **Current Version:** v1.9.109 (January 18, 2026)
82
+ **Current Version:** v1.9.112 (January 19, 2026)
83
+
84
+ ### FIXED in v1.9.112 - 🐛 Critical Bug Fixes
85
+
86
+ Three important bug fixes:
87
+ - **Filter Pagination**: Filtering now searches ALL segments regardless of pagination (was only searching visible page)
88
+ - **Bilingual Export**: Segment notes now properly exported to Notes column in Supervertaler Bilingual Table DOCX files
89
+ - **Grid Layout**: Segment ID column reduced from 55px to 40px for more compact display
83
90
 
84
91
  ### NEW in v1.9.108 - 📥📤 memoQ XLIFF Import/Export
85
92
 
@@ -1,4 +1,4 @@
1
- Supervertaler.py,sha256=QwDGXwdEfy0UxCNEXHGo_J6X18-cgXZm554Zd4CEArU,2091722
1
+ Supervertaler.py,sha256=qHBsSWBG_tWmEHuRCD6t9rPO-SkBBgCDPo3CfTHF-qE,2124338
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=mA5ISI22qN9mH3DQFF4gOTciDyBt5xVR7sHTkgkTIlw,11361
@@ -77,9 +77,9 @@ modules/unified_prompt_manager_qt.py,sha256=xfMLSlP-fn-l6XY7yyG8Knerplq1mUM7F1fK
77
77
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
78
78
  modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
79
79
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
80
- supervertaler-1.9.109.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
- supervertaler-1.9.109.dist-info/METADATA,sha256=FF8KXj0iVdOppNcbanxRkabc4JmeC_-yUBskEz8nw70,39078
82
- supervertaler-1.9.109.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- supervertaler-1.9.109.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
- supervertaler-1.9.109.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
- supervertaler-1.9.109.dist-info/RECORD,,
80
+ supervertaler-1.9.112.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
+ supervertaler-1.9.112.dist-info/METADATA,sha256=aDqD2NFH_WzJVbt1GPE4FPLR5DoLty6Mx-EGBsWxNnY,39490
82
+ supervertaler-1.9.112.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
+ supervertaler-1.9.112.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
+ supervertaler-1.9.112.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
+ supervertaler-1.9.112.dist-info/RECORD,,