supervertaler 1.9.202__py3-none-any.whl → 1.9.204__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 +213 -141
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/METADATA +1 -1
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/RECORD +7 -7
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.202.dist-info → supervertaler-1.9.204.dist-info}/top_level.txt +0 -0
Supervertaler.py
CHANGED
|
@@ -32,9 +32,9 @@ License: MIT
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
# Version Information.
|
|
35
|
-
__version__ = "1.9.
|
|
35
|
+
__version__ = "1.9.204"
|
|
36
36
|
__phase__ = "0.9"
|
|
37
|
-
__release_date__ = "2026-02-
|
|
37
|
+
__release_date__ = "2026-02-03"
|
|
38
38
|
__edition__ = "Qt"
|
|
39
39
|
|
|
40
40
|
import sys
|
|
@@ -7945,19 +7945,36 @@ class SupervertalerQt(QMainWindow):
|
|
|
7945
7945
|
import_menu.addAction(import_review_table_action)
|
|
7946
7946
|
|
|
7947
7947
|
export_menu = file_menu.addMenu("&Export")
|
|
7948
|
-
|
|
7948
|
+
|
|
7949
|
+
# --- Monolingual (target-only) exports at top ---
|
|
7950
|
+
export_target_docx_action = QAction("&Target Only (DOCX)...", self)
|
|
7951
|
+
export_target_docx_action.triggered.connect(self.export_target_only_docx)
|
|
7952
|
+
export_menu.addAction(export_target_docx_action)
|
|
7953
|
+
|
|
7954
|
+
export_txt_action = QAction("Simple &Text File - Translated (TXT)...", self)
|
|
7955
|
+
export_txt_action.triggered.connect(self.export_simple_txt)
|
|
7956
|
+
export_menu.addAction(export_txt_action)
|
|
7957
|
+
|
|
7958
|
+
export_ai_action = QAction("📄 &AI-Readable Markdown (.md)...", self)
|
|
7959
|
+
export_ai_action.triggered.connect(self.export_bilingual_table_markdown)
|
|
7960
|
+
export_ai_action.setToolTip("Export segments in [SEGMENT] format for AI translation/review")
|
|
7961
|
+
export_menu.addAction(export_ai_action)
|
|
7962
|
+
|
|
7963
|
+
export_menu.addSeparator()
|
|
7964
|
+
|
|
7965
|
+
# --- Bilingual CAT tool exports ---
|
|
7949
7966
|
export_memoq_action = QAction("memoQ &Bilingual Table - Translated (DOCX)...", self)
|
|
7950
7967
|
export_memoq_action.triggered.connect(self.export_memoq_bilingual)
|
|
7951
7968
|
export_menu.addAction(export_memoq_action)
|
|
7952
|
-
|
|
7969
|
+
|
|
7953
7970
|
export_memoq_xliff_action = QAction("memoQ &XLIFF - Translated (.mqxliff)...", self)
|
|
7954
7971
|
export_memoq_xliff_action.triggered.connect(self.export_memoq_xliff)
|
|
7955
7972
|
export_menu.addAction(export_memoq_xliff_action)
|
|
7956
|
-
|
|
7973
|
+
|
|
7957
7974
|
export_cafetran_action = QAction("&CafeTran Bilingual Table - Translated (DOCX)...", self)
|
|
7958
7975
|
export_cafetran_action.triggered.connect(self.export_cafetran_bilingual)
|
|
7959
7976
|
export_menu.addAction(export_cafetran_action)
|
|
7960
|
-
|
|
7977
|
+
|
|
7961
7978
|
# Trados submenu - group all Trados exports together
|
|
7962
7979
|
trados_export_submenu = export_menu.addMenu("&Trados Studio")
|
|
7963
7980
|
|
|
@@ -7978,20 +7995,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
7978
7995
|
export_dejavu_action = QAction("&Déjà Vu X3 Bilingual - Translated (RTF)...", self)
|
|
7979
7996
|
export_dejavu_action.triggered.connect(self.export_dejavu_bilingual)
|
|
7980
7997
|
export_menu.addAction(export_dejavu_action)
|
|
7981
|
-
|
|
7982
|
-
export_target_docx_action = QAction("&Target Only (DOCX)...", self)
|
|
7983
|
-
export_target_docx_action.triggered.connect(self.export_target_only_docx)
|
|
7984
|
-
export_menu.addAction(export_target_docx_action)
|
|
7985
|
-
|
|
7986
|
-
export_txt_action = QAction("Simple &Text File - Translated (TXT)...", self)
|
|
7987
|
-
export_txt_action.triggered.connect(self.export_simple_txt)
|
|
7988
|
-
export_menu.addAction(export_txt_action)
|
|
7989
|
-
|
|
7990
|
-
export_ai_action = QAction("📄 &AI-Readable Markdown (.md)...", self)
|
|
7991
|
-
export_ai_action.triggered.connect(self.export_bilingual_table_markdown)
|
|
7992
|
-
export_ai_action.setToolTip("Export segments in [SEGMENT] format for AI translation/review")
|
|
7993
|
-
export_menu.addAction(export_ai_action)
|
|
7994
|
-
|
|
7998
|
+
|
|
7995
7999
|
export_menu.addSeparator()
|
|
7996
8000
|
|
|
7997
8001
|
# Multi-file folder export
|
|
@@ -8267,30 +8271,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8267
8271
|
grid_font_family_menu.addAction(font_action)
|
|
8268
8272
|
|
|
8269
8273
|
view_menu.addSeparator()
|
|
8270
|
-
|
|
8271
|
-
# Translation Results Pane section
|
|
8272
|
-
results_zoom_menu = view_menu.addMenu("📋 Translation &Results Pane")
|
|
8273
|
-
|
|
8274
|
-
results_zoom_in_action = QAction("Results Zoom &In", self)
|
|
8275
|
-
results_zoom_in_action.setShortcut("Ctrl+Shift+=")
|
|
8276
|
-
results_zoom_in_action.triggered.connect(self.results_pane_zoom_in)
|
|
8277
|
-
results_zoom_menu.addAction(results_zoom_in_action)
|
|
8278
|
-
|
|
8279
|
-
results_zoom_out_action = QAction("Results Zoom &Out", self)
|
|
8280
|
-
results_zoom_out_action.setShortcut("Ctrl+Shift+-")
|
|
8281
|
-
results_zoom_out_action.triggered.connect(self.results_pane_zoom_out)
|
|
8282
|
-
results_zoom_menu.addAction(results_zoom_out_action)
|
|
8283
|
-
|
|
8284
|
-
results_zoom_reset_action = QAction("Results Zoom &Reset", self)
|
|
8285
|
-
results_zoom_reset_action.triggered.connect(self.results_pane_zoom_reset)
|
|
8286
|
-
results_zoom_menu.addAction(results_zoom_reset_action)
|
|
8287
|
-
|
|
8288
|
-
results_zoom_menu.addSeparator()
|
|
8289
|
-
|
|
8290
|
-
results_note = QAction("(Includes match list + compare boxes)", self)
|
|
8291
|
-
results_note.setEnabled(False)
|
|
8292
|
-
results_zoom_menu.addAction(results_note)
|
|
8293
|
-
|
|
8274
|
+
|
|
8294
8275
|
# Match Panel zoom section
|
|
8295
8276
|
match_panel_zoom_menu = view_menu.addMenu("🔍 &Match Panel")
|
|
8296
8277
|
|
|
@@ -11280,8 +11261,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
11280
11261
|
text = re.sub(r'</?li>', '', text) # <li>, </li>
|
|
11281
11262
|
text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
|
|
11282
11263
|
text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
|
|
11264
|
+
text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
|
|
11265
|
+
text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
|
|
11283
11266
|
return text.strip()
|
|
11284
|
-
|
|
11267
|
+
|
|
11285
11268
|
def clean_special_chars(text):
|
|
11286
11269
|
"""Remove problematic Unicode characters like object replacement char"""
|
|
11287
11270
|
# Remove Unicode Object Replacement Character (U+FFFC) and similar
|
|
@@ -11295,34 +11278,48 @@ class SupervertalerQt(QMainWindow):
|
|
|
11295
11278
|
"""
|
|
11296
11279
|
Replace paragraph text with tagged text, applying bold/italic/underline formatting.
|
|
11297
11280
|
Parses tags like <b>, <i>, <u>, <bi> and creates appropriate runs.
|
|
11281
|
+
Preserves original font name and size from the first run.
|
|
11298
11282
|
"""
|
|
11299
11283
|
# Clean special characters first
|
|
11300
11284
|
text = clean_special_chars(tagged_text)
|
|
11301
|
-
|
|
11285
|
+
|
|
11302
11286
|
# Strip list tags - they don't affect formatting
|
|
11303
11287
|
text = re.sub(r'</?li-[bo]>', '', text)
|
|
11304
11288
|
text = re.sub(r'</?li>', '', text)
|
|
11305
|
-
|
|
11289
|
+
|
|
11290
|
+
# Capture original font properties BEFORE clearing runs
|
|
11291
|
+
original_font_name = None
|
|
11292
|
+
original_font_size = None
|
|
11293
|
+
original_all_caps = None
|
|
11294
|
+
if para.runs:
|
|
11295
|
+
first_run = para.runs[0]
|
|
11296
|
+
if first_run.font:
|
|
11297
|
+
original_font_name = first_run.font.name
|
|
11298
|
+
original_font_size = first_run.font.size
|
|
11299
|
+
original_all_caps = first_run.font.all_caps
|
|
11300
|
+
|
|
11306
11301
|
# Clear existing runs
|
|
11307
11302
|
for run in para.runs:
|
|
11308
11303
|
run.clear()
|
|
11309
11304
|
# Remove the cleared runs
|
|
11310
11305
|
for run in list(para.runs):
|
|
11311
11306
|
run._element.getparent().remove(run._element)
|
|
11312
|
-
|
|
11307
|
+
|
|
11313
11308
|
# Parse tags and create runs with formatting
|
|
11314
11309
|
# Pattern matches tags or text between tags
|
|
11315
|
-
tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
|
|
11310
|
+
tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
|
|
11316
11311
|
parts = tag_pattern.split(text)
|
|
11317
|
-
|
|
11312
|
+
|
|
11318
11313
|
is_bold = False
|
|
11319
11314
|
is_italic = False
|
|
11320
11315
|
is_underline = False
|
|
11321
|
-
|
|
11316
|
+
is_subscript = False
|
|
11317
|
+
is_superscript = False
|
|
11318
|
+
|
|
11322
11319
|
for part in parts:
|
|
11323
11320
|
if not part:
|
|
11324
11321
|
continue
|
|
11325
|
-
|
|
11322
|
+
|
|
11326
11323
|
# Check if this is a tag
|
|
11327
11324
|
if part == '<b>':
|
|
11328
11325
|
is_bold = True
|
|
@@ -11342,6 +11339,14 @@ class SupervertalerQt(QMainWindow):
|
|
|
11342
11339
|
elif part == '</bi>':
|
|
11343
11340
|
is_bold = False
|
|
11344
11341
|
is_italic = False
|
|
11342
|
+
elif part == '<sub>':
|
|
11343
|
+
is_subscript = True
|
|
11344
|
+
elif part == '</sub>':
|
|
11345
|
+
is_subscript = False
|
|
11346
|
+
elif part == '<sup>':
|
|
11347
|
+
is_superscript = True
|
|
11348
|
+
elif part == '</sup>':
|
|
11349
|
+
is_superscript = False
|
|
11345
11350
|
else:
|
|
11346
11351
|
# This is text content - create a run with current formatting
|
|
11347
11352
|
if part.strip() or part: # Include whitespace
|
|
@@ -11349,6 +11354,17 @@ class SupervertalerQt(QMainWindow):
|
|
|
11349
11354
|
run.bold = is_bold
|
|
11350
11355
|
run.italic = is_italic
|
|
11351
11356
|
run.underline = is_underline
|
|
11357
|
+
if is_subscript:
|
|
11358
|
+
run.font.subscript = True
|
|
11359
|
+
if is_superscript:
|
|
11360
|
+
run.font.superscript = True
|
|
11361
|
+
# Restore original font properties
|
|
11362
|
+
if original_font_name:
|
|
11363
|
+
run.font.name = original_font_name
|
|
11364
|
+
if original_font_size:
|
|
11365
|
+
run.font.size = original_font_size
|
|
11366
|
+
if original_all_caps:
|
|
11367
|
+
run.font.all_caps = original_all_caps
|
|
11352
11368
|
|
|
11353
11369
|
# Build a mapping of source text (without tags) to raw target text (with tags)
|
|
11354
11370
|
text_map = {}
|
|
@@ -11361,16 +11377,14 @@ class SupervertalerQt(QMainWindow):
|
|
|
11361
11377
|
if source_clean and target_raw:
|
|
11362
11378
|
text_map[source_clean] = target_raw
|
|
11363
11379
|
|
|
11364
|
-
def
|
|
11365
|
-
"""Replace all matching segments in text,
|
|
11380
|
+
def replace_segments_in_text_with_tags(original_text, text_map):
|
|
11381
|
+
"""Replace all matching segments in text, preserving formatting tags."""
|
|
11366
11382
|
result = original_text
|
|
11367
11383
|
# Sort by length (longest first) to avoid partial replacement issues
|
|
11368
11384
|
for source_clean, target_raw in sorted(text_map.items(), key=lambda x: len(x[0]), reverse=True):
|
|
11369
11385
|
if source_clean in result:
|
|
11370
|
-
#
|
|
11371
|
-
|
|
11372
|
-
target_clean = clean_special_chars(target_clean)
|
|
11373
|
-
result = result.replace(source_clean, target_clean)
|
|
11386
|
+
# Keep target WITH tags for formatting preservation
|
|
11387
|
+
result = result.replace(source_clean, target_raw)
|
|
11374
11388
|
return result
|
|
11375
11389
|
|
|
11376
11390
|
replaced_count = 0
|
|
@@ -11386,17 +11400,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
11386
11400
|
replaced_count += 1
|
|
11387
11401
|
else:
|
|
11388
11402
|
# Try partial replacement (paragraph contains multiple segments)
|
|
11389
|
-
new_text =
|
|
11403
|
+
new_text = replace_segments_in_text_with_tags(para_text, text_map)
|
|
11390
11404
|
if new_text != para_text:
|
|
11391
|
-
# Text was changed -
|
|
11392
|
-
#
|
|
11393
|
-
|
|
11394
|
-
run.clear()
|
|
11395
|
-
for run in list(para.runs):
|
|
11396
|
-
run._element.getparent().remove(run._element)
|
|
11397
|
-
para.add_run(new_text)
|
|
11405
|
+
# Text was changed - use apply_formatted_text_to_paragraph
|
|
11406
|
+
# to preserve inline formatting tags (bold, italic, sub, sup, etc.)
|
|
11407
|
+
apply_formatted_text_to_paragraph(para, new_text)
|
|
11398
11408
|
replaced_count += 1
|
|
11399
|
-
|
|
11409
|
+
|
|
11400
11410
|
# Replace text in tables
|
|
11401
11411
|
for table in doc.tables:
|
|
11402
11412
|
for row in table.rows:
|
|
@@ -11411,15 +11421,12 @@ class SupervertalerQt(QMainWindow):
|
|
|
11411
11421
|
replaced_count += 1
|
|
11412
11422
|
else:
|
|
11413
11423
|
# Try partial replacement
|
|
11414
|
-
new_text =
|
|
11424
|
+
new_text = replace_segments_in_text_with_tags(para_text, text_map)
|
|
11415
11425
|
if new_text != para_text:
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
for run in list(para.runs):
|
|
11419
|
-
run._element.getparent().remove(run._element)
|
|
11420
|
-
para.add_run(new_text)
|
|
11426
|
+
# Use apply_formatted_text_to_paragraph to preserve formatting
|
|
11427
|
+
apply_formatted_text_to_paragraph(para, new_text)
|
|
11421
11428
|
replaced_count += 1
|
|
11422
|
-
|
|
11429
|
+
|
|
11423
11430
|
doc.save(file_path)
|
|
11424
11431
|
self.log(f"✓ Replaced {replaced_count} text segments in original document structure")
|
|
11425
11432
|
|
|
@@ -11435,8 +11442,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
11435
11442
|
text = re.sub(r'</?li>', '', text) # <li>, </li>
|
|
11436
11443
|
text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
|
|
11437
11444
|
text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
|
|
11445
|
+
text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
|
|
11446
|
+
text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
|
|
11438
11447
|
return text.strip()
|
|
11439
|
-
|
|
11448
|
+
|
|
11440
11449
|
def clean_special_chars(text):
|
|
11441
11450
|
"""Remove problematic Unicode characters"""
|
|
11442
11451
|
text = text.replace('\ufffc', '') # Object Replacement Character
|
|
@@ -11455,13 +11464,15 @@ class SupervertalerQt(QMainWindow):
|
|
|
11455
11464
|
text = re.sub(r'</?li>', '', text)
|
|
11456
11465
|
|
|
11457
11466
|
# Parse and apply formatting
|
|
11458
|
-
tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
|
|
11467
|
+
tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
|
|
11459
11468
|
parts = tag_pattern.split(text)
|
|
11460
|
-
|
|
11469
|
+
|
|
11461
11470
|
is_bold = False
|
|
11462
11471
|
is_italic = False
|
|
11463
11472
|
is_underline = False
|
|
11464
|
-
|
|
11473
|
+
is_subscript = False
|
|
11474
|
+
is_superscript = False
|
|
11475
|
+
|
|
11465
11476
|
for part in parts:
|
|
11466
11477
|
if not part:
|
|
11467
11478
|
continue
|
|
@@ -11483,13 +11494,25 @@ class SupervertalerQt(QMainWindow):
|
|
|
11483
11494
|
elif part == '</bi>':
|
|
11484
11495
|
is_bold = False
|
|
11485
11496
|
is_italic = False
|
|
11497
|
+
elif part == '<sub>':
|
|
11498
|
+
is_subscript = True
|
|
11499
|
+
elif part == '</sub>':
|
|
11500
|
+
is_subscript = False
|
|
11501
|
+
elif part == '<sup>':
|
|
11502
|
+
is_superscript = True
|
|
11503
|
+
elif part == '</sup>':
|
|
11504
|
+
is_superscript = False
|
|
11486
11505
|
else:
|
|
11487
11506
|
if part:
|
|
11488
11507
|
run = para.add_run(part)
|
|
11489
11508
|
run.bold = is_bold
|
|
11490
11509
|
run.italic = is_italic
|
|
11491
11510
|
run.underline = is_underline
|
|
11492
|
-
|
|
11511
|
+
if is_subscript:
|
|
11512
|
+
run.font.subscript = True
|
|
11513
|
+
if is_superscript:
|
|
11514
|
+
run.font.superscript = True
|
|
11515
|
+
|
|
11493
11516
|
doc = Document()
|
|
11494
11517
|
|
|
11495
11518
|
for seg in segments:
|
|
@@ -21387,70 +21410,46 @@ class SupervertalerQt(QMainWindow):
|
|
|
21387
21410
|
|
|
21388
21411
|
# Add left side to main horizontal splitter
|
|
21389
21412
|
main_horizontal_splitter.addWidget(left_container)
|
|
21390
|
-
|
|
21391
|
-
# Right side: Translation Results panel with Preview tab
|
|
21392
|
-
from modules.translation_results_panel import TranslationResultsPanel
|
|
21393
|
-
|
|
21413
|
+
|
|
21394
21414
|
# Create tabbed container for right panel
|
|
21395
21415
|
right_tabs = QTabWidget()
|
|
21396
21416
|
right_tabs.tabBar().setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
21397
21417
|
right_tabs.tabBar().setDrawBase(False)
|
|
21398
21418
|
right_tabs.setStyleSheet("QTabBar::tab { outline: 0; } QTabBar::tab:focus { outline: none; } QTabBar::tab:selected { border-bottom: 1px solid #2196F3; background-color: rgba(33, 150, 243, 0.08); }")
|
|
21399
|
-
|
|
21400
|
-
#
|
|
21401
|
-
|
|
21402
|
-
|
|
21403
|
-
# Connect signals for match selection/insertion
|
|
21404
|
-
self.translation_results_panel.match_selected.connect(self.on_match_selected)
|
|
21405
|
-
self.translation_results_panel.match_inserted.connect(self.on_match_inserted)
|
|
21406
|
-
|
|
21407
|
-
# Connect notes editing to save segment notes
|
|
21408
|
-
if hasattr(self.translation_results_panel, 'notes_edit') and self.translation_results_panel.notes_edit:
|
|
21409
|
-
self.translation_results_panel.notes_edit.textChanged.connect(self._on_results_panel_notes_changed)
|
|
21410
|
-
|
|
21411
|
-
# Register this panel so it receives updates when segments are selected
|
|
21419
|
+
|
|
21420
|
+
# NOTE: Translation Results panel is deprecated - MT/LLM is now only via QuickTrans (Ctrl+M)
|
|
21421
|
+
# Panel is no longer created to save resources. Set to None for compatibility checks.
|
|
21422
|
+
self.translation_results_panel = None
|
|
21412
21423
|
if not hasattr(self, 'results_panels'):
|
|
21413
21424
|
self.results_panels = []
|
|
21414
|
-
|
|
21415
|
-
|
|
21425
|
+
|
|
21416
21426
|
# Track tab indices for visibility-aware default selection
|
|
21417
21427
|
tab_index = 0
|
|
21418
|
-
results_tab_index = -1
|
|
21419
|
-
compare_tab_index = -1
|
|
21420
21428
|
preview_tab_index = -1
|
|
21421
|
-
|
|
21422
|
-
# Tab 1:
|
|
21423
|
-
if self.show_translation_results_pane:
|
|
21424
|
-
right_tabs.addTab(self.translation_results_panel, "🔍 Translation Results")
|
|
21425
|
-
results_tab_index = tab_index
|
|
21426
|
-
tab_index += 1
|
|
21427
|
-
else:
|
|
21428
|
-
# Hide the panel if not added as tab (it still exists as child widget)
|
|
21429
|
-
self.translation_results_panel.hide()
|
|
21430
|
-
|
|
21431
|
-
# Tab 2: Match Panel (Termview + TM Source/Target) - shown first by default
|
|
21429
|
+
|
|
21430
|
+
# Tab 1: Match Panel (Termview + TM Source/Target) - primary TM display
|
|
21432
21431
|
match_panel_widget = self._create_match_panel()
|
|
21433
21432
|
self.match_panel_widget = match_panel_widget # Store reference for mode detection
|
|
21434
21433
|
right_tabs.addTab(match_panel_widget, "🎯 Match Panel")
|
|
21435
21434
|
match_panel_tab_index = tab_index
|
|
21436
21435
|
tab_index += 1
|
|
21437
21436
|
|
|
21438
|
-
# Tab
|
|
21437
|
+
# Tab 2: Document Preview
|
|
21439
21438
|
preview_widget = self._create_preview_tab()
|
|
21440
21439
|
right_tabs.addTab(preview_widget, "📄 Preview")
|
|
21441
21440
|
preview_tab_index = tab_index
|
|
21442
21441
|
self._preview_tab_index = preview_tab_index # Store for visibility checks
|
|
21443
21442
|
tab_index += 1
|
|
21444
|
-
|
|
21445
|
-
# Tab
|
|
21443
|
+
|
|
21444
|
+
# Tab 3: Segment Note
|
|
21446
21445
|
right_tabs.addTab(self._notes_widget_for_right_panel, "📝 Segment note")
|
|
21447
21446
|
tab_index += 1
|
|
21448
|
-
|
|
21449
|
-
# Tab
|
|
21447
|
+
|
|
21448
|
+
# Tab 4: Session Log
|
|
21450
21449
|
right_tabs.addTab(self._session_log_widget_for_right_panel, "📋 Session Log")
|
|
21451
21450
|
tab_index += 1
|
|
21452
|
-
|
|
21453
|
-
# Tab
|
|
21451
|
+
|
|
21452
|
+
# Tab 5: Scratchpad (private translator notes for the whole project)
|
|
21454
21453
|
right_tabs.addTab(self._scratchpad_widget_for_right_panel, "📝 Scratchpad")
|
|
21455
21454
|
tab_index += 1
|
|
21456
21455
|
|
|
@@ -21597,7 +21596,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
21597
21596
|
self.table.setRowHidden(row, not in_page)
|
|
21598
21597
|
finally:
|
|
21599
21598
|
self.table.setUpdatesEnabled(True)
|
|
21600
|
-
|
|
21599
|
+
|
|
21600
|
+
# Recalculate heights for visible rows to prevent layout corruption.
|
|
21601
|
+
# Call synchronously first, then schedule a deferred resize to catch any
|
|
21602
|
+
# layout issues that Qt processes asynchronously.
|
|
21603
|
+
self._resize_visible_rows()
|
|
21604
|
+
QTimer.singleShot(50, self._resize_visible_rows)
|
|
21605
|
+
|
|
21601
21606
|
# Update pagination UI
|
|
21602
21607
|
self._update_pagination_ui()
|
|
21603
21608
|
|
|
@@ -21889,7 +21894,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
21889
21894
|
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) # Target - stretch to fill space
|
|
21890
21895
|
header.setSectionResizeMode(4, QHeaderView.ResizeMode.Interactive) # Status - allow resizing
|
|
21891
21896
|
header.setStretchLastSection(False) # Don't auto-stretch last section (we use Stretch mode for Source/Target)
|
|
21892
|
-
|
|
21897
|
+
|
|
21898
|
+
# Recalculate row heights when columns are resized (text reflows to new width)
|
|
21899
|
+
header.sectionResized.connect(self._on_column_resized)
|
|
21900
|
+
|
|
21893
21901
|
# Set initial column widths - give Source and Target equal space
|
|
21894
21902
|
# ID column width will be auto-adjusted by _update_segment_column_width() after segments load
|
|
21895
21903
|
self.table.setColumnWidth(0, 35) # ID - temporary, auto-adjusts to fit content
|
|
@@ -30118,10 +30126,15 @@ class SupervertalerQt(QMainWindow):
|
|
|
30118
30126
|
|
|
30119
30127
|
# Update progress stats in status bar
|
|
30120
30128
|
self.update_progress_stats()
|
|
30121
|
-
|
|
30129
|
+
|
|
30122
30130
|
# Refresh document preview
|
|
30123
30131
|
self.refresh_preview()
|
|
30124
30132
|
|
|
30133
|
+
# Deferred row resize: columns use Stretch mode, so their final widths
|
|
30134
|
+
# are only known after Qt processes the layout. Schedule a resize for
|
|
30135
|
+
# after all pending events are processed.
|
|
30136
|
+
QTimer.singleShot(0, self._resize_visible_rows)
|
|
30137
|
+
|
|
30125
30138
|
# =========================================================================
|
|
30126
30139
|
# COMPARE PANEL TAB
|
|
30127
30140
|
# =========================================================================
|
|
@@ -31481,9 +31494,11 @@ class SupervertalerQt(QMainWindow):
|
|
|
31481
31494
|
def _refresh_segment_status(self, segment: Segment):
|
|
31482
31495
|
if not self.current_project:
|
|
31483
31496
|
return
|
|
31497
|
+
updated_row = None
|
|
31484
31498
|
for row, seg in enumerate(self.current_project.segments):
|
|
31485
31499
|
if seg.id == segment.id:
|
|
31486
31500
|
self._update_status_cell(row, seg)
|
|
31501
|
+
updated_row = row
|
|
31487
31502
|
break
|
|
31488
31503
|
|
|
31489
31504
|
# Update list view entry in place (if visible)
|
|
@@ -31505,7 +31520,10 @@ class SupervertalerQt(QMainWindow):
|
|
|
31505
31520
|
status_tooltip += f"\nComment: {segment.notes.strip()}"
|
|
31506
31521
|
item.setToolTip(2, status_tooltip)
|
|
31507
31522
|
item.setBackground(2, QColor(status_def.color))
|
|
31508
|
-
|
|
31523
|
+
|
|
31524
|
+
# Recalculate row height after status cell update to prevent layout corruption
|
|
31525
|
+
if updated_row is not None:
|
|
31526
|
+
self._auto_resize_single_row(updated_row)
|
|
31509
31527
|
|
|
31510
31528
|
def _refresh_segment_status_by_id(self, segment_id: int):
|
|
31511
31529
|
"""Refresh the status display for a segment by its ID."""
|
|
@@ -31568,40 +31586,79 @@ class SupervertalerQt(QMainWindow):
|
|
|
31568
31586
|
return
|
|
31569
31587
|
if row < 0 or row >= self.table.rowCount():
|
|
31570
31588
|
return
|
|
31571
|
-
|
|
31589
|
+
|
|
31590
|
+
# Minimum column width threshold - if columns are narrower than this,
|
|
31591
|
+
# layout isn't ready yet and we should skip resizing
|
|
31592
|
+
MIN_COL_WIDTH = 100
|
|
31593
|
+
|
|
31572
31594
|
max_height = 1
|
|
31573
|
-
|
|
31595
|
+
|
|
31574
31596
|
# Check source cell (column 2)
|
|
31575
31597
|
source_widget = self.table.cellWidget(row, 2)
|
|
31576
31598
|
if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
|
|
31577
31599
|
doc = source_widget.document()
|
|
31578
31600
|
if doc:
|
|
31579
31601
|
col_width = self.table.columnWidth(2) - width_reduction
|
|
31580
|
-
if col_width
|
|
31602
|
+
if col_width >= MIN_COL_WIDTH:
|
|
31581
31603
|
doc.setTextWidth(col_width)
|
|
31582
|
-
|
|
31583
|
-
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31604
|
+
# Use documentLayout().documentSize() to force fresh calculation
|
|
31605
|
+
layout = doc.documentLayout()
|
|
31606
|
+
if layout:
|
|
31607
|
+
size = layout.documentSize()
|
|
31608
|
+
if size.isValid():
|
|
31609
|
+
height = int(size.height())
|
|
31610
|
+
max_height = max(max_height, height)
|
|
31611
|
+
|
|
31587
31612
|
# Check target cell (column 3)
|
|
31588
31613
|
target_widget = self.table.cellWidget(row, 3)
|
|
31589
31614
|
if target_widget and isinstance(target_widget, EditableGridTextEditor):
|
|
31590
31615
|
doc = target_widget.document()
|
|
31591
31616
|
if doc:
|
|
31592
31617
|
col_width = self.table.columnWidth(3) - width_reduction
|
|
31593
|
-
if col_width
|
|
31618
|
+
if col_width >= MIN_COL_WIDTH:
|
|
31594
31619
|
doc.setTextWidth(col_width)
|
|
31595
|
-
|
|
31596
|
-
|
|
31597
|
-
|
|
31598
|
-
|
|
31620
|
+
# Use documentLayout().documentSize() to force fresh calculation
|
|
31621
|
+
layout = doc.documentLayout()
|
|
31622
|
+
if layout:
|
|
31623
|
+
size = layout.documentSize()
|
|
31624
|
+
if size.isValid():
|
|
31625
|
+
height = int(size.height())
|
|
31626
|
+
max_height = max(max_height, height)
|
|
31599
31627
|
|
|
31600
31628
|
# Set row height with minimal padding
|
|
31601
31629
|
# Minimum 32px to accommodate status icons (16px) + match text + padding without any cutoff
|
|
31602
31630
|
compact_height = max(max_height + 2, 32)
|
|
31603
31631
|
self.table.setRowHeight(row, compact_height)
|
|
31604
|
-
|
|
31632
|
+
|
|
31633
|
+
def _resize_visible_rows(self):
|
|
31634
|
+
"""Resize only visible (non-hidden) rows for efficiency after pagination/filter changes."""
|
|
31635
|
+
if not hasattr(self, 'table') or not self.table:
|
|
31636
|
+
return
|
|
31637
|
+
row_count = self.table.rowCount()
|
|
31638
|
+
for row in range(row_count):
|
|
31639
|
+
if not self.table.isRowHidden(row):
|
|
31640
|
+
self._auto_resize_single_row(row)
|
|
31641
|
+
|
|
31642
|
+
def _on_column_resized(self, logical_index: int, old_size: int, new_size: int):
|
|
31643
|
+
"""Handle column resize - recalculate row heights for text reflow.
|
|
31644
|
+
|
|
31645
|
+
Only reacts to Source (2) or Target (3) column changes since those
|
|
31646
|
+
contain wrapped text. Uses debouncing to avoid excessive recalculations
|
|
31647
|
+
during continuous dragging.
|
|
31648
|
+
"""
|
|
31649
|
+
# Only care about Source or Target columns (they contain wrapped text)
|
|
31650
|
+
if logical_index not in (2, 3):
|
|
31651
|
+
return
|
|
31652
|
+
|
|
31653
|
+
# Debounce: cancel previous timer and start a new one
|
|
31654
|
+
if hasattr(self, '_column_resize_timer') and self._column_resize_timer is not None:
|
|
31655
|
+
self._column_resize_timer.stop()
|
|
31656
|
+
|
|
31657
|
+
self._column_resize_timer = QTimer()
|
|
31658
|
+
self._column_resize_timer.setSingleShot(True)
|
|
31659
|
+
self._column_resize_timer.timeout.connect(self._resize_visible_rows)
|
|
31660
|
+
self._column_resize_timer.start(150) # 150ms debounce
|
|
31661
|
+
|
|
31605
31662
|
def apply_font_to_grid(self):
|
|
31606
31663
|
"""Apply selected font to all grid cells"""
|
|
31607
31664
|
font = QFont(self.default_font_family, self.default_font_size)
|
|
@@ -37438,10 +37495,10 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
37438
37495
|
# Clear filtering flag to re-enable auto-center
|
|
37439
37496
|
self.filtering_active = False
|
|
37440
37497
|
|
|
37441
|
-
# Re-apply pagination after clearing filters
|
|
37498
|
+
# Re-apply pagination after clearing filters (also resizes visible rows)
|
|
37442
37499
|
if hasattr(self, '_apply_pagination_to_grid'):
|
|
37443
37500
|
self._apply_pagination_to_grid()
|
|
37444
|
-
|
|
37501
|
+
|
|
37445
37502
|
# Restore selection to the previously selected segment
|
|
37446
37503
|
if selected_segment_id is not None:
|
|
37447
37504
|
for row, segment in enumerate(self.current_project.segments):
|
|
@@ -38709,7 +38766,12 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
38709
38766
|
break
|
|
38710
38767
|
|
|
38711
38768
|
def _on_results_panel_notes_changed(self):
|
|
38712
|
-
"""Handle notes change in Translation Results panel - saves to current segment
|
|
38769
|
+
"""Handle notes change in Translation Results panel - saves to current segment.
|
|
38770
|
+
NOTE: This function is deprecated - Translation Results panel has been removed.
|
|
38771
|
+
"""
|
|
38772
|
+
# Translation Results panel is deprecated - this should never be called
|
|
38773
|
+
if not self.translation_results_panel:
|
|
38774
|
+
return
|
|
38713
38775
|
if not self.current_project:
|
|
38714
38776
|
return
|
|
38715
38777
|
|
|
@@ -45647,9 +45709,19 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
45647
45709
|
self.log(f"⚠ Error auto-inserting TM match: {e}")
|
|
45648
45710
|
|
|
45649
45711
|
def _add_mt_and_llm_matches_progressive(self, segment, source_lang, target_lang, source_lang_code, target_lang_code):
|
|
45650
|
-
"""
|
|
45712
|
+
"""DEPRECATED: Translation Results panel has been removed.
|
|
45713
|
+
|
|
45714
|
+
MT/LLM translations are now only available via QuickTrans (Ctrl+M).
|
|
45715
|
+
This function is kept as a stub for backwards compatibility.
|
|
45716
|
+
"""
|
|
45717
|
+
pass # Translation Results panel removed - use QuickTrans (Ctrl+M) for MT/LLM
|
|
45718
|
+
|
|
45719
|
+
def _REMOVED_add_mt_and_llm_matches_progressive(self, segment, source_lang, target_lang, source_lang_code, target_lang_code):
|
|
45720
|
+
"""REMOVED - kept for reference only, will be deleted in future cleanup."""
|
|
45721
|
+
return # Dead code below - keeping temporarily for reference
|
|
45722
|
+
|
|
45651
45723
|
from modules.translation_results_panel import TranslationMatch
|
|
45652
|
-
|
|
45724
|
+
|
|
45653
45725
|
# MT matches (usually fast ~0.5s each)
|
|
45654
45726
|
if self.enable_mt_matching:
|
|
45655
45727
|
api_keys = self.load_api_keys()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supervertaler
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.204
|
|
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=
|
|
1
|
+
Supervertaler.py,sha256=ZCV8QknOZhHpUGJ_t4ONW_wq7zS1L_0nlHAuw8x0ybo,2398568
|
|
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
|
|
@@ -80,9 +80,9 @@ modules/unified_prompt_manager_qt.py,sha256=y7xAIM9X7f1afXGE1tOcAc5EY7BKFy53Rgqi
|
|
|
80
80
|
modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
|
|
81
81
|
modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
|
|
82
82
|
modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
|
|
83
|
-
supervertaler-1.9.
|
|
84
|
-
supervertaler-1.9.
|
|
85
|
-
supervertaler-1.9.
|
|
86
|
-
supervertaler-1.9.
|
|
87
|
-
supervertaler-1.9.
|
|
88
|
-
supervertaler-1.9.
|
|
83
|
+
supervertaler-1.9.204.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
|
|
84
|
+
supervertaler-1.9.204.dist-info/METADATA,sha256=zcv2fcgWU60pLclQn-I23GvrLuXf-z9vVFzElk6hIXw,5725
|
|
85
|
+
supervertaler-1.9.204.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
86
|
+
supervertaler-1.9.204.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
|
|
87
|
+
supervertaler-1.9.204.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
|
|
88
|
+
supervertaler-1.9.204.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|