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 +742 -10
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/METADATA +10 -3
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/RECORD +7 -7
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.109.dist-info → supervertaler-1.9.112.dist-info}/top_level.txt +0 -0
Supervertaler.py
CHANGED
|
@@ -34,7 +34,7 @@ License: MIT
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
# Version Information.
|
|
37
|
-
__version__ = "1.9.
|
|
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("
|
|
6089
|
-
export_ai_action.triggered.connect(self.
|
|
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
|
-
|
|
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 -
|
|
9738
|
-
|
|
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,
|
|
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
|
-
|
|
32010
|
-
|
|
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.
|
|
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.
|
|
74
|
+
# 🚀 Supervertaler v1.9.112
|
|
75
75
|
|
|
76
76
|
[](https://pypi.org/project/Supervertaler/)
|
|
77
77
|
[](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.
|
|
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=
|
|
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.
|
|
81
|
-
supervertaler-1.9.
|
|
82
|
-
supervertaler-1.9.
|
|
83
|
-
supervertaler-1.9.
|
|
84
|
-
supervertaler-1.9.
|
|
85
|
-
supervertaler-1.9.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|