supervertaler 1.9.194__py3-none-any.whl → 1.9.198__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
@@ -32,9 +32,9 @@ License: MIT
32
32
  """
33
33
 
34
34
  # Version Information.
35
- __version__ = "1.9.194"
35
+ __version__ = "1.9.198"
36
36
  __phase__ = "0.9"
37
- __release_date__ = "2026-02-01"
37
+ __release_date__ = "2026-02-02"
38
38
  __edition__ = "Qt"
39
39
 
40
40
  import sys
@@ -391,10 +391,10 @@ def runs_to_tagged_text(paragraphs) -> str:
391
391
  def strip_formatting_tags(text: str) -> str:
392
392
  """
393
393
  Remove HTML formatting tags from text, leaving plain text.
394
-
394
+
395
395
  Args:
396
396
  text: Text with HTML tags like <b>, </b>, <i>, </i>, <u>, </u>
397
-
397
+
398
398
  Returns:
399
399
  Plain text without tags
400
400
  """
@@ -403,6 +403,104 @@ def strip_formatting_tags(text: str) -> str:
403
403
  return re.sub(r'</?[biu]>', '', text)
404
404
 
405
405
 
406
+ def strip_outer_wrapping_tags(text: str) -> tuple:
407
+ """
408
+ Strip outer wrapping tags from text if the entire segment is wrapped in a single tag pair.
409
+
410
+ This handles structural tags like <li-o>...</li-o>, <p>...</p>, <td>...</td>, etc.
411
+ Inner formatting tags like <b>...</b> are preserved.
412
+
413
+ Args:
414
+ text: Text that may be wrapped in outer structural tags
415
+
416
+ Returns:
417
+ Tuple of (stripped_text, tag_name) where tag_name is the outer tag that was stripped,
418
+ or (original_text, None) if no outer wrapping tag was found
419
+
420
+ Examples:
421
+ "<li-o>Hello <b>world</b></li-o>" -> ("Hello <b>world</b>", "li-o")
422
+ "<p>Simple text</p>" -> ("Simple text", "p")
423
+ "No tags here" -> ("No tags here", None)
424
+ "<b>Bold text</b>" -> ("<b>Bold text</b>", None) # <b> is formatting, not structural
425
+ """
426
+ import re
427
+
428
+ if not text or not text.strip():
429
+ return (text, None)
430
+
431
+ text = text.strip()
432
+
433
+ # Structural tags that wrap entire segments (not inline formatting)
434
+ structural_tags = {
435
+ 'li-o', 'li-b', 'li', 'p', 'td', 'th', 'tr', 'div', 'span',
436
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'caption',
437
+ 'blockquote', 'pre', 'code', 'dt', 'dd', 'header', 'footer',
438
+ 'article', 'section', 'aside', 'nav', 'main', 'figure', 'figcaption'
439
+ }
440
+
441
+ # Pattern to match opening tag at start: <tag> or <tag attr="...">
442
+ opening_pattern = r'^<([a-zA-Z][a-zA-Z0-9-]*)(?:\s+[^>]*)?>(.*)$'
443
+ opening_match = re.match(opening_pattern, text, re.DOTALL)
444
+
445
+ if not opening_match:
446
+ return (text, None)
447
+
448
+ tag_name = opening_match.group(1).lower()
449
+ rest = opening_match.group(2)
450
+
451
+ # Only strip structural tags, not inline formatting like <b>, <i>, <u>, <em>, <strong>
452
+ if tag_name not in structural_tags:
453
+ return (text, None)
454
+
455
+ # Check if text ends with matching closing tag
456
+ closing_pattern = rf'^(.*)</{re.escape(tag_name)}>$'
457
+ closing_match = re.match(closing_pattern, rest, re.DOTALL | re.IGNORECASE)
458
+
459
+ if not closing_match:
460
+ return (text, None)
461
+
462
+ inner_content = closing_match.group(1)
463
+
464
+ # Verify this is truly a wrapping pair (no other occurrences of this tag inside)
465
+ # Count opening and closing tags of this type in the inner content
466
+ inner_opening_count = len(re.findall(rf'<{re.escape(tag_name)}(?:\s+[^>]*)?>',
467
+ inner_content, re.IGNORECASE))
468
+ inner_closing_count = len(re.findall(rf'</{re.escape(tag_name)}>', inner_content, re.IGNORECASE))
469
+
470
+ # If there are nested tags of the same type, don't strip
471
+ if inner_opening_count > 0 or inner_closing_count > 0:
472
+ return (text, None)
473
+
474
+ return (inner_content, tag_name)
475
+
476
+
477
+ def get_list_prefix_for_tag(tag_name: str, list_position: int = 1) -> str:
478
+ """
479
+ Get visual prefix for list items in WYSIWYG mode.
480
+
481
+ Args:
482
+ tag_name: The outer wrapping tag name (e.g., 'li-o', 'li-b', 'li')
483
+ list_position: Position within the list (1-indexed) for ordered lists
484
+
485
+ Returns:
486
+ Visual prefix string: "1. ", "2. ", "• ", etc., or "" if not a list tag
487
+ """
488
+ if not tag_name:
489
+ return ""
490
+
491
+ tag_lower = tag_name.lower()
492
+
493
+ # Ordered list items
494
+ if tag_lower == 'li-o':
495
+ return f"{list_position}. "
496
+
497
+ # Unordered/bullet list items
498
+ if tag_lower in ('li-b', 'li'):
499
+ return "• "
500
+
501
+ return ""
502
+
503
+
406
504
  def has_formatting_tags(text: str) -> bool:
407
505
  """
408
506
  Check if text contains any formatting tags.
@@ -6222,7 +6320,10 @@ class SupervertalerQt(QMainWindow):
6222
6320
  self.enable_alternating_row_colors = True # Enable alternating row colors by default
6223
6321
  self.even_row_color = '#FFFFFF' # White for even rows
6224
6322
  self.odd_row_color = '#F0F0F0' # Light gray for odd rows
6225
-
6323
+
6324
+ # Hide outer wrapping tags in grid display (e.g. <li-o>...</li-o>)
6325
+ self.hide_outer_wrapping_tags = False # Disabled by default
6326
+
6226
6327
  # Termbase highlight style settings
6227
6328
  self.termbase_highlight_style = 'semibold' # 'background', 'dotted', or 'semibold'
6228
6329
  self.termbase_dotted_color = '#808080' # Medium gray for dotted underline (more visible)
@@ -7447,7 +7548,7 @@ class SupervertalerQt(QMainWindow):
7447
7548
  return
7448
7549
 
7449
7550
  # Import and create the popup
7450
- from modules.mt_quick_popup import MTQuickPopup
7551
+ from modules.quicktrans import MTQuickPopup
7451
7552
 
7452
7553
  # Create popup
7453
7554
  popup = MTQuickPopup(
@@ -17690,14 +17791,41 @@ class SupervertalerQt(QMainWindow):
17690
17791
 
17691
17792
  grid_group.setLayout(grid_layout)
17692
17793
  layout.addWidget(grid_group)
17693
-
17694
- # Translation Results Pane & Tag Coloring section
17695
- results_group = QGroupBox("📋 Translation Results Pane && Tag Colors")
17794
+
17795
+ # Grid Display Options section
17796
+ grid_display_group = QGroupBox("📊 Grid Display Options")
17797
+ grid_display_layout = QVBoxLayout()
17798
+
17799
+ grid_display_info = QLabel(
17800
+ "Configure how content is displayed in the translation grid."
17801
+ )
17802
+ grid_display_info.setStyleSheet("font-size: 8pt; padding: 8px; border-radius: 2px;")
17803
+ grid_display_info.setWordWrap(True)
17804
+ grid_display_layout.addWidget(grid_display_info)
17805
+
17806
+ # Hide wrapping tags checkbox
17807
+ hide_wrapping_tags_layout = QHBoxLayout()
17808
+ hide_wrapping_tags_check = CheckmarkCheckBox("Hide outer wrapping tags in grid (e.g. <li-o>...</li-o>)")
17809
+ hide_wrapping_tags_check.setChecked(font_settings.get('hide_outer_wrapping_tags', False))
17810
+ hide_wrapping_tags_check.setToolTip(
17811
+ "When enabled, structural tags that wrap the entire segment (like <li-o>, <p>, <td>) are hidden in the grid.\n"
17812
+ "The segment type is already shown in the Type column, so this reduces visual clutter.\n"
17813
+ "Inner formatting tags like <b>bold</b> are still shown.\n"
17814
+ "This affects the Source column display only - the Target column keeps tags for editing."
17815
+ )
17816
+ hide_wrapping_tags_layout.addWidget(hide_wrapping_tags_check)
17817
+ hide_wrapping_tags_layout.addStretch()
17818
+ grid_display_layout.addLayout(hide_wrapping_tags_layout)
17819
+
17820
+ grid_display_group.setLayout(grid_display_layout)
17821
+ layout.addWidget(grid_display_group)
17822
+
17823
+ # Match Panel & Tag Colors section
17824
+ results_group = QGroupBox("📋 Match Panel && Tag Colors")
17696
17825
  results_layout = QVBoxLayout()
17697
17826
 
17698
17827
  results_size_info = QLabel(
17699
- "Set the default font sizes for the translation results pane.\n"
17700
- "You can also adjust these using View menu → Translation Results Pane."
17828
+ "Set font sizes for the Match Panel (TM/termbase matches) and tag colors."
17701
17829
  )
17702
17830
  results_size_info.setStyleSheet("font-size: 8pt; padding: 8px; border-radius: 2px;")
17703
17831
  results_size_info.setWordWrap(True)
@@ -17737,7 +17865,7 @@ class SupervertalerQt(QMainWindow):
17737
17865
  show_tags_layout.addWidget(show_tags_check)
17738
17866
  show_tags_layout.addStretch()
17739
17867
  results_layout.addLayout(show_tags_layout)
17740
-
17868
+
17741
17869
  # Tag highlight color picker
17742
17870
  tag_color_layout = QHBoxLayout()
17743
17871
  tag_color_layout.addWidget(QLabel("Tag Highlight Color:"))
@@ -18295,7 +18423,8 @@ class SupervertalerQt(QMainWindow):
18295
18423
  grid_font_spin, match_font_spin, compare_font_spin, show_tags_check, tag_color_btn,
18296
18424
  alt_colors_check, even_color_btn, odd_color_btn, invisible_char_color_btn, grid_font_family_combo,
18297
18425
  termview_font_family_combo, termview_font_spin, termview_bold_check,
18298
- border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check
18426
+ border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check,
18427
+ hide_wrapping_tags_check
18299
18428
  )
18300
18429
 
18301
18430
  save_btn.clicked.connect(save_view_settings_with_scale)
@@ -18307,14 +18436,15 @@ class SupervertalerQt(QMainWindow):
18307
18436
 
18308
18437
  def _create_voice_dictation_settings_tab(self):
18309
18438
  """Create Supervoice Settings tab content with Voice Commands"""
18310
- from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
18439
+ from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
18311
18440
  QTableWidget, QTableWidgetItem, QHeaderView,
18312
- QAbstractItemView, QCheckBox)
18441
+ QAbstractItemView, QCheckBox, QSplitter)
18442
+ from modules.keyboard_shortcuts_widget import CheckmarkCheckBox
18313
18443
 
18314
18444
  tab = QWidget()
18315
- layout = QVBoxLayout(tab)
18316
- layout.setContentsMargins(20, 20, 20, 20)
18317
- layout.setSpacing(15)
18445
+ main_layout = QVBoxLayout(tab)
18446
+ main_layout.setContentsMargins(20, 20, 20, 20)
18447
+ main_layout.setSpacing(15)
18318
18448
 
18319
18449
  # Load current dictation settings
18320
18450
  dictation_settings = self.load_dictation_settings()
@@ -18327,19 +18457,33 @@ class SupervertalerQt(QMainWindow):
18327
18457
  header_info.setTextFormat(Qt.TextFormat.RichText)
18328
18458
  header_info.setStyleSheet("font-size: 9pt; color: #444; padding: 10px; background-color: #E3F2FD; border-radius: 4px;")
18329
18459
  header_info.setWordWrap(True)
18330
- layout.addWidget(header_info)
18460
+ main_layout.addWidget(header_info)
18331
18461
 
18332
- # ===== Voice Commands Section =====
18333
- commands_group = QGroupBox("🗣️ Voice Commands (Talon-style)")
18334
- commands_layout = QVBoxLayout()
18462
+ # ===== Two-column layout: Left = Settings, Right = Voice Commands Table =====
18463
+ columns_layout = QHBoxLayout()
18464
+ columns_layout.setSpacing(15)
18465
+
18466
+ # --- LEFT COLUMN: Settings ---
18467
+ left_column = QVBoxLayout()
18468
+ left_column.setSpacing(15)
18335
18469
 
18336
- # Enable voice commands checkbox
18337
- voice_cmd_enabled = QCheckBox("Enable voice commands (spoken phrases trigger actions)")
18470
+ # Enable voice commands checkbox (green checkmark style)
18471
+ voice_cmd_enabled = CheckmarkCheckBox("Enable voice commands (spoken phrases trigger actions)")
18338
18472
  voice_cmd_enabled.setChecked(dictation_settings.get('voice_commands_enabled', True))
18339
18473
  voice_cmd_enabled.setToolTip("When enabled, spoken phrases like 'confirm' or 'next segment' will execute commands instead of being inserted as text")
18340
- commands_layout.addWidget(voice_cmd_enabled)
18474
+ left_column.addWidget(voice_cmd_enabled)
18341
18475
  self.voice_commands_enabled_checkbox = voice_cmd_enabled
18342
18476
 
18477
+ # Create a layout variable for settings sections to be added below
18478
+ layout = left_column
18479
+
18480
+ # --- RIGHT COLUMN: Voice Commands Table ---
18481
+ right_column = QVBoxLayout()
18482
+ right_column.setSpacing(10)
18483
+
18484
+ commands_group = QGroupBox("🗣️ Voice Commands (Talon-style)")
18485
+ commands_layout = QVBoxLayout()
18486
+
18343
18487
  commands_info = QLabel(
18344
18488
  "Voice commands let you control Supervertaler by voice. Say a phrase to execute an action.\n"
18345
18489
  "If no command matches, the spoken text is inserted as dictation."
@@ -18358,38 +18502,47 @@ class SupervertalerQt(QMainWindow):
18358
18502
  self.voice_commands_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
18359
18503
  self.voice_commands_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
18360
18504
  self.voice_commands_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
18361
- self.voice_commands_table.setMinimumHeight(200)
18362
- self.voice_commands_table.setMaximumHeight(300)
18363
-
18505
+ self.voice_commands_table.setMinimumHeight(350)
18506
+
18364
18507
  # Populate table with current commands
18365
18508
  self._populate_voice_commands_table()
18366
18509
  commands_layout.addWidget(self.voice_commands_table)
18367
18510
 
18368
18511
  # Command buttons
18369
18512
  cmd_btn_layout = QHBoxLayout()
18370
-
18371
- add_cmd_btn = QPushButton("➕ Add Command")
18513
+
18514
+ add_cmd_btn = QPushButton("➕ Add")
18372
18515
  add_cmd_btn.clicked.connect(self._add_voice_command)
18373
18516
  cmd_btn_layout.addWidget(add_cmd_btn)
18374
-
18375
- edit_cmd_btn = QPushButton("✏️ Edit Command")
18517
+
18518
+ edit_cmd_btn = QPushButton("✏️ Edit")
18376
18519
  edit_cmd_btn.clicked.connect(self._edit_voice_command)
18377
18520
  cmd_btn_layout.addWidget(edit_cmd_btn)
18378
-
18379
- remove_cmd_btn = QPushButton("🗑️ Remove Command")
18521
+
18522
+ remove_cmd_btn = QPushButton("🗑️ Remove")
18380
18523
  remove_cmd_btn.clicked.connect(self._remove_voice_command)
18381
18524
  cmd_btn_layout.addWidget(remove_cmd_btn)
18382
-
18525
+
18383
18526
  cmd_btn_layout.addStretch()
18384
-
18385
- reset_cmd_btn = QPushButton("🔄 Reset to Defaults")
18527
+
18528
+ reset_cmd_btn = QPushButton("🔄 Reset")
18386
18529
  reset_cmd_btn.clicked.connect(self._reset_voice_commands)
18387
18530
  cmd_btn_layout.addWidget(reset_cmd_btn)
18388
-
18531
+
18389
18532
  commands_layout.addLayout(cmd_btn_layout)
18390
18533
 
18391
18534
  commands_group.setLayout(commands_layout)
18392
- layout.addWidget(commands_group)
18535
+ right_column.addWidget(commands_group)
18536
+ right_column.addStretch()
18537
+
18538
+ # Add columns to the two-column layout
18539
+ left_widget = QWidget()
18540
+ left_widget.setLayout(left_column)
18541
+ right_widget = QWidget()
18542
+ right_widget.setLayout(right_column)
18543
+
18544
+ columns_layout.addWidget(left_widget, stretch=1)
18545
+ columns_layout.addWidget(right_widget, stretch=1)
18393
18546
 
18394
18547
  # ===== Always-On Mode Section =====
18395
18548
  alwayson_group = QGroupBox("🎧 Always-On Listening Mode")
@@ -18552,7 +18705,13 @@ class SupervertalerQt(QMainWindow):
18552
18705
  ahk_group.setLayout(ahk_layout)
18553
18706
  layout.addWidget(ahk_group)
18554
18707
 
18555
- # Save button
18708
+ # Add stretch to left column to push content up
18709
+ layout.addStretch()
18710
+
18711
+ # Add two-column layout to main layout
18712
+ main_layout.addLayout(columns_layout, stretch=1)
18713
+
18714
+ # Save button (full width, below the two columns)
18556
18715
  save_btn = QPushButton("💾 Save Supervoice Settings")
18557
18716
  save_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; border: none; outline: none;")
18558
18717
  save_btn.clicked.connect(lambda: self._save_voice_settings(
@@ -18561,15 +18720,13 @@ class SupervertalerQt(QMainWindow):
18561
18720
  lang_combo.currentText(),
18562
18721
  voice_cmd_enabled.isChecked()
18563
18722
  ))
18564
- layout.addWidget(save_btn)
18723
+ main_layout.addWidget(save_btn)
18565
18724
 
18566
18725
  # Store references
18567
18726
  self.dictation_model_combo = model_combo
18568
18727
  self.dictation_duration_spin = duration_spin
18569
18728
  self.dictation_lang_combo = lang_combo
18570
18729
 
18571
- layout.addStretch()
18572
-
18573
18730
  return tab
18574
18731
 
18575
18732
  def _populate_voice_commands_table(self):
@@ -20198,7 +20355,8 @@ class SupervertalerQt(QMainWindow):
20198
20355
  def _save_view_settings_from_ui(self, grid_spin, match_spin, compare_spin, show_tags_check=None, tag_color_btn=None,
20199
20356
  alt_colors_check=None, even_color_btn=None, odd_color_btn=None, invisible_char_color_btn=None,
20200
20357
  grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
20201
- border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None):
20358
+ border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None,
20359
+ hide_wrapping_tags_check=None):
20202
20360
  """Save view settings from UI"""
20203
20361
  # CRITICAL: Suppress TM saves during view settings update
20204
20362
  # Grid operations (setStyleSheet, rehighlight, etc.) can trigger textChanged events
@@ -20211,7 +20369,8 @@ class SupervertalerQt(QMainWindow):
20211
20369
  grid_spin, match_spin, compare_spin, show_tags_check, tag_color_btn,
20212
20370
  alt_colors_check, even_color_btn, odd_color_btn, invisible_char_color_btn,
20213
20371
  grid_font_family_combo, termview_font_family_combo, termview_font_spin, termview_bold_check,
20214
- border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check
20372
+ border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check,
20373
+ hide_wrapping_tags_check
20215
20374
  )
20216
20375
  finally:
20217
20376
  self._suppress_target_change_handlers = previous_suppression
@@ -20219,16 +20378,18 @@ class SupervertalerQt(QMainWindow):
20219
20378
  def _save_view_settings_from_ui_impl(self, grid_spin, match_spin, compare_spin, show_tags_check=None, tag_color_btn=None,
20220
20379
  alt_colors_check=None, even_color_btn=None, odd_color_btn=None, invisible_char_color_btn=None,
20221
20380
  grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
20222
- border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None):
20381
+ border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None,
20382
+ hide_wrapping_tags_check=None):
20223
20383
  """Implementation of save view settings (called with TM saves suppressed)"""
20224
- general_settings = {
20225
- 'restore_last_project': self.load_general_settings().get('restore_last_project', False),
20226
- 'auto_propagate_exact_matches': self.auto_propagate_exact_matches, # Keep existing value
20384
+ # Load existing settings first to preserve all values, then update with new ones
20385
+ general_settings = self.load_general_settings()
20386
+ general_settings.update({
20387
+ 'auto_propagate_exact_matches': self.auto_propagate_exact_matches,
20227
20388
  'grid_font_size': grid_spin.value(),
20228
20389
  'results_match_font_size': match_spin.value(),
20229
20390
  'results_compare_font_size': compare_spin.value(),
20230
- 'enable_tm_termbase_matching': self.enable_tm_matching # Save TM/termbase matching state
20231
- }
20391
+ 'enable_tm_termbase_matching': self.enable_tm_matching
20392
+ })
20232
20393
 
20233
20394
  # Add tabs above grid setting if provided
20234
20395
  if tabs_above_check is not None:
@@ -20268,7 +20429,12 @@ class SupervertalerQt(QMainWindow):
20268
20429
  if invisible_char_color:
20269
20430
  general_settings['invisible_char_color'] = invisible_char_color
20270
20431
  self.invisible_char_color = invisible_char_color
20271
-
20432
+
20433
+ # Add hide outer wrapping tags setting if provided
20434
+ if hide_wrapping_tags_check is not None:
20435
+ general_settings['hide_outer_wrapping_tags'] = hide_wrapping_tags_check.isChecked()
20436
+ self.hide_outer_wrapping_tags = hide_wrapping_tags_check.isChecked()
20437
+
20272
20438
  # Add focus border settings if provided
20273
20439
  if border_color_btn is not None:
20274
20440
  border_color = border_color_btn.property('selected_color')
@@ -20432,7 +20598,11 @@ class SupervertalerQt(QMainWindow):
20432
20598
  self.refresh_grid_tag_colors()
20433
20599
  # Also refresh row colors
20434
20600
  self.apply_alternating_row_colors()
20435
-
20601
+
20602
+ # Refresh source column if hide_outer_wrapping_tags setting changed
20603
+ if hide_wrapping_tags_check is not None and hasattr(self, 'table') and self.table is not None:
20604
+ self._refresh_source_column_display()
20605
+
20436
20606
  self.log("✓ View settings saved and applied")
20437
20607
  # Use explicit QMessageBox instance to ensure proper dialog closing
20438
20608
  msg = QMessageBox(self)
@@ -29773,30 +29943,62 @@ class SupervertalerQt(QMainWindow):
29773
29943
  self.table.setItem(row, 1, type_item)
29774
29944
 
29775
29945
  # Source - Use read-only QTextEdit widget for easy text selection
29946
+ # Strip outer wrapping tags if setting is enabled (display only, data unchanged)
29947
+ source_for_display = segment.source
29948
+ stripped_source_tag = None
29949
+ list_prefix = ""
29950
+ if self.hide_outer_wrapping_tags:
29951
+ stripped, stripped_source_tag = strip_outer_wrapping_tags(segment.source)
29952
+ source_for_display = stripped
29953
+ # Add WYSIWYG list prefix (e.g., "1. " or "• ") for list items
29954
+ if stripped_source_tag:
29955
+ list_pos = list_numbers.get(row, 1)
29956
+ list_prefix = get_list_prefix_for_tag(stripped_source_tag, list_pos)
29957
+ if list_prefix:
29958
+ source_for_display = list_prefix + source_for_display
29776
29959
  # Apply invisible character replacements for display only
29777
- source_display_text = self.apply_invisible_replacements(segment.source)
29960
+ source_display_text = self.apply_invisible_replacements(source_for_display)
29778
29961
  source_editor = ReadOnlyGridTextEditor(source_display_text, self.table, row)
29779
-
29962
+
29780
29963
  # Initialize empty termbase matches (will be populated lazily on segment selection or by background worker)
29781
29964
  source_editor.termbase_matches = {}
29782
-
29965
+
29783
29966
  # Set font to match grid
29784
29967
  font = QFont(self.default_font_family, self.default_font_size)
29785
29968
  source_editor.setFont(font)
29786
-
29969
+
29787
29970
  # Set as cell widget (allows easy text selection)
29788
29971
  self.table.setCellWidget(row, 2, source_editor)
29789
-
29972
+
29790
29973
  # Also set a placeholder item for row height calculation
29791
29974
  source_item = QTableWidgetItem()
29792
29975
  source_item.setFlags(Qt.ItemFlag.NoItemFlags) # No interaction
29793
29976
  self.table.setItem(row, 2, source_item)
29794
-
29977
+
29795
29978
  # Target - Use editable QTextEdit widget for easy text selection and editing
29979
+ # Strip outer wrapping tags if enabled (will be auto-restored when saving)
29980
+ target_for_display = segment.target
29981
+ stripped_target_tag = None
29982
+ target_list_prefix = ""
29983
+ if self.hide_outer_wrapping_tags:
29984
+ stripped, stripped_target_tag = strip_outer_wrapping_tags(segment.target)
29985
+ target_for_display = stripped
29986
+ # Store stripped tag on segment for restore during save
29987
+ # Use source tag as reference (target should match source structure)
29988
+ segment._stripped_outer_tag = stripped_source_tag or stripped_target_tag
29989
+ # Add WYSIWYG list prefix for display (same as source)
29990
+ target_list_prefix = list_prefix # Reuse the prefix calculated for source
29991
+
29796
29992
  # Apply invisible character replacements for display (will be reversed when saving)
29797
- target_display_text = self.apply_invisible_replacements(segment.target)
29993
+ target_display_text = self.apply_invisible_replacements(target_for_display)
29994
+ # Add list prefix for display (stored separately so we can strip it on save)
29995
+ if target_list_prefix:
29996
+ target_display_text = target_list_prefix + target_display_text
29798
29997
  target_editor = EditableGridTextEditor(target_display_text, self.table, row, self.table)
29799
29998
  target_editor.setFont(font)
29999
+ # Store stripped tag and list prefix on editor for auto-restore
30000
+ target_editor._stripped_outer_tag = stripped_source_tag or stripped_target_tag
30001
+ target_editor._list_prefix = target_list_prefix
29800
30002
 
29801
30003
  # Connect text changes to update segment
29802
30004
  # Use a factory function to create a proper closure that captures the segment ID
@@ -29808,9 +30010,21 @@ class SupervertalerQt(QMainWindow):
29808
30010
  nonlocal debounce_timer
29809
30011
  new_text = editor_widget.toPlainText()
29810
30012
 
30013
+ # Strip WYSIWYG list prefix if present (display-only, not saved)
30014
+ list_prefix = getattr(editor_widget, '_list_prefix', '')
30015
+ if list_prefix and new_text.startswith(list_prefix):
30016
+ new_text = new_text[len(list_prefix):]
30017
+
29811
30018
  # Reverse invisible character replacements before saving
29812
30019
  new_text = self.reverse_invisible_replacements(new_text)
29813
30020
 
30021
+ # Auto-restore stripped outer wrapping tags if they were hidden
30022
+ stripped_tag = getattr(editor_widget, '_stripped_outer_tag', None)
30023
+ if stripped_tag and self.hide_outer_wrapping_tags:
30024
+ # Only re-add if the text doesn't already have the tag
30025
+ if not new_text.strip().startswith(f'<{stripped_tag}'):
30026
+ new_text = f'<{stripped_tag}>{new_text}</{stripped_tag}>'
30027
+
29814
30028
  # DEBUG: Log EVERY call to catch the culprit (only in debug mode)
29815
30029
  if self.debug_mode_enabled:
29816
30030
  self.log(f"🔔 textChanged FIRED: segment_id={segment_id}, new_text='{new_text[:20] if new_text else 'EMPTY'}...'")
@@ -31730,7 +31944,10 @@ class SupervertalerQt(QMainWindow):
31730
31944
  self.enable_alternating_row_colors = settings.get('enable_alternating_row_colors', True)
31731
31945
  self.even_row_color = settings.get('even_row_color', '#FFFFFF')
31732
31946
  self.odd_row_color = settings.get('odd_row_color', '#F0F0F0')
31733
-
31947
+
31948
+ # Load hide outer wrapping tags setting
31949
+ self.hide_outer_wrapping_tags = settings.get('hide_outer_wrapping_tags', False)
31950
+
31734
31951
  # Load termbase highlight style settings
31735
31952
  self.termbase_highlight_style = settings.get('termbase_highlight_style', 'semibold')
31736
31953
  self.termbase_dotted_color = settings.get('termbase_dotted_color', '#808080')
@@ -37377,6 +37594,22 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37377
37594
  # during load_segments_to_grid when creating cell widgets
37378
37595
  self.load_segments_to_grid()
37379
37596
 
37597
+ def _refresh_source_column_display(self):
37598
+ """Refresh grid to reflect hide_outer_wrapping_tags setting.
37599
+
37600
+ Reloads the entire grid to properly handle:
37601
+ - Tag stripping on both Source and Target columns
37602
+ - WYSIWYG list numbering/bullet prefixes
37603
+ - Proper list position calculation
37604
+ """
37605
+ if not hasattr(self, 'table') or not self.table:
37606
+ return
37607
+ if not hasattr(self, 'current_project') or not self.current_project:
37608
+ return
37609
+
37610
+ # Reload grid to apply changes with proper list numbering calculation
37611
+ self.load_segments_to_grid()
37612
+
37380
37613
  def apply_invisible_replacements(self, text):
37381
37614
  """Apply invisible character replacements to text based on settings"""
37382
37615
  if not hasattr(self, 'invisible_display_settings'):
@@ -49563,7 +49796,7 @@ class SuperlookupTab(QWidget):
49563
49796
  target_lang = getattr(main_window, 'target_language', 'Dutch')
49564
49797
 
49565
49798
  # Import and show MT Quick Lookup popup
49566
- from modules.mt_quick_popup import MTQuickPopup
49799
+ from modules.quicktrans import MTQuickPopup
49567
49800
 
49568
49801
  # Create popup without Qt parent so it can appear independently over any app
49569
49802
  # Pass main_window as parent_app for API access
modules/llm_clients.py CHANGED
@@ -594,18 +594,20 @@ class LLMClient:
594
594
  context: Optional[str] = None,
595
595
  custom_prompt: Optional[str] = None,
596
596
  max_tokens: Optional[int] = None,
597
- images: Optional[List] = None
597
+ images: Optional[List] = None,
598
+ system_prompt: Optional[str] = None
598
599
  ) -> str:
599
600
  """
600
601
  Translate text using configured LLM
601
-
602
+
602
603
  Args:
603
604
  text: Text to translate
604
605
  source_lang: Source language code
605
606
  target_lang: Target language code
606
607
  context: Optional context for translation
607
608
  custom_prompt: Optional custom prompt (overrides default simple prompt)
608
-
609
+ system_prompt: Optional system prompt for AI behavior context
610
+
609
611
  Returns:
610
612
  Translated text
611
613
  """
@@ -615,30 +617,30 @@ class LLMClient:
615
617
  else:
616
618
  # Build prompt
617
619
  prompt = f"Translate the following text from {source_lang} to {target_lang}:\n\n{text}"
618
-
620
+
619
621
  if context:
620
622
  prompt = f"Context: {context}\n\n{prompt}"
621
-
623
+
622
624
  # Log warning if images provided but model doesn't support vision
623
625
  if images and not self.model_supports_vision(self.provider, self.model):
624
626
  print(f"⚠️ Warning: Model {self.model} doesn't support vision. Images will be ignored.")
625
627
  images = None # Don't pass to API
626
-
628
+
627
629
  # Call appropriate provider
628
630
  if self.provider == "openai":
629
- return self._call_openai(prompt, max_tokens=max_tokens, images=images)
631
+ return self._call_openai(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
630
632
  elif self.provider == "claude":
631
- return self._call_claude(prompt, max_tokens=max_tokens, images=images)
633
+ return self._call_claude(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
632
634
  elif self.provider == "gemini":
633
- return self._call_gemini(prompt, max_tokens=max_tokens, images=images)
635
+ return self._call_gemini(prompt, max_tokens=max_tokens, images=images, system_prompt=system_prompt)
634
636
  elif self.provider == "ollama":
635
- return self._call_ollama(prompt, max_tokens=max_tokens)
637
+ return self._call_ollama(prompt, max_tokens=max_tokens, system_prompt=system_prompt)
636
638
  else:
637
639
  raise ValueError(f"Unsupported provider: {self.provider}")
638
640
 
639
- def _call_openai(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
641
+ def _call_openai(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
640
642
  """Call OpenAI API with GPT-5/o1/o3 reasoning model support and vision capability"""
641
- print(f"🔵 _call_openai START: model={self.model}, prompt_len={len(prompt)}, max_tokens={max_tokens}, images={len(images) if images else 0}")
643
+ print(f"🔵 _call_openai START: model={self.model}, prompt_len={len(prompt)}, max_tokens={max_tokens}, images={len(images) if images else 0}, has_system={bool(system_prompt)}")
642
644
 
643
645
  try:
644
646
  from openai import OpenAI
@@ -686,10 +688,16 @@ class LLMClient:
686
688
  # Standard text-only format
687
689
  content = prompt
688
690
 
691
+ # Build messages list
692
+ messages = []
693
+ if system_prompt:
694
+ messages.append({"role": "system", "content": system_prompt})
695
+ messages.append({"role": "user", "content": content})
696
+
689
697
  # Build API call parameters
690
698
  api_params = {
691
699
  "model": self.model,
692
- "messages": [{"role": "user", "content": content}],
700
+ "messages": messages,
693
701
  "timeout": timeout_seconds
694
702
  }
695
703
 
@@ -742,7 +750,7 @@ class LLMClient:
742
750
  print(f" Response: {e.response}")
743
751
  raise # Re-raise to be caught by calling code
744
752
 
745
- def _call_claude(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
753
+ def _call_claude(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
746
754
  """Call Anthropic Claude API with vision support"""
747
755
  try:
748
756
  import anthropic
@@ -786,12 +794,19 @@ class LLMClient:
786
794
  # Standard text-only format
787
795
  content = prompt
788
796
 
789
- response = client.messages.create(
790
- model=self.model,
791
- max_tokens=tokens_to_use,
792
- messages=[{"role": "user", "content": content}],
793
- timeout=timeout_seconds # Explicit timeout
794
- )
797
+ # Build API call parameters
798
+ api_params = {
799
+ "model": self.model,
800
+ "max_tokens": tokens_to_use,
801
+ "messages": [{"role": "user", "content": content}],
802
+ "timeout": timeout_seconds # Explicit timeout
803
+ }
804
+
805
+ # Add system prompt if provided (Claude uses 'system' parameter, not a message)
806
+ if system_prompt:
807
+ api_params["system"] = system_prompt
808
+
809
+ response = client.messages.create(**api_params)
795
810
 
796
811
  translation = response.content[0].text.strip()
797
812
 
@@ -800,7 +815,7 @@ class LLMClient:
800
815
 
801
816
  return translation
802
817
 
803
- def _call_gemini(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None) -> str:
818
+ def _call_gemini(self, prompt: str, max_tokens: Optional[int] = None, images: Optional[List] = None, system_prompt: Optional[str] = None) -> str:
804
819
  """Call Google Gemini API with vision support"""
805
820
  try:
806
821
  import google.generativeai as genai
@@ -809,10 +824,15 @@ class LLMClient:
809
824
  raise ImportError(
810
825
  "Google AI library not installed. Install with: pip install google-generativeai pillow"
811
826
  )
812
-
827
+
813
828
  genai.configure(api_key=self.api_key)
814
- model = genai.GenerativeModel(self.model)
815
-
829
+
830
+ # Gemini supports system instructions via GenerativeModel parameter
831
+ if system_prompt:
832
+ model = genai.GenerativeModel(self.model, system_instruction=system_prompt)
833
+ else:
834
+ model = genai.GenerativeModel(self.model)
835
+
816
836
  # Build content (text + optional images)
817
837
  if images:
818
838
  # Gemini format: list with prompt text followed by PIL Image objects
@@ -823,7 +843,7 @@ class LLMClient:
823
843
  else:
824
844
  # Standard text-only
825
845
  content = prompt
826
-
846
+
827
847
  response = model.generate_content(content)
828
848
  translation = response.text.strip()
829
849
 
@@ -832,20 +852,21 @@ class LLMClient:
832
852
 
833
853
  return translation
834
854
 
835
- def _call_ollama(self, prompt: str, max_tokens: Optional[int] = None) -> str:
855
+ def _call_ollama(self, prompt: str, max_tokens: Optional[int] = None, system_prompt: Optional[str] = None) -> str:
836
856
  """
837
857
  Call local Ollama server for translation.
838
-
858
+
839
859
  Ollama provides a simple REST API compatible with local LLM inference.
840
860
  Models run entirely on the user's computer - no API keys, no internet required.
841
-
861
+
842
862
  Args:
843
863
  prompt: The full prompt to send
844
864
  max_tokens: Maximum tokens to generate (default: 4096)
845
-
865
+ system_prompt: Optional system prompt for AI behavior context
866
+
846
867
  Returns:
847
868
  Translated text
848
-
869
+
849
870
  Raises:
850
871
  ConnectionError: If Ollama is not running
851
872
  ValueError: If model is not available
@@ -866,13 +887,17 @@ class LLMClient:
866
887
  print(f"🟠 _call_ollama START: model={self.model}, prompt_len={len(prompt)}, max_tokens={tokens_to_use}")
867
888
  print(f"🟠 Ollama endpoint: {endpoint}")
868
889
 
890
+ # Build messages list
891
+ messages = []
892
+ if system_prompt:
893
+ messages.append({"role": "system", "content": system_prompt})
894
+ messages.append({"role": "user", "content": prompt})
895
+
869
896
  # Build request payload
870
897
  # Using /api/chat for chat-style interaction (better for translation prompts)
871
898
  payload = {
872
899
  "model": self.model,
873
- "messages": [
874
- {"role": "user", "content": prompt}
875
- ],
900
+ "messages": messages,
876
901
  "stream": False, # Get complete response at once
877
902
  "options": {
878
903
  "temperature": 0.3, # Low temperature for consistent translations
@@ -3795,6 +3795,26 @@ Output complete ACTION."""
3795
3795
  from PyQt6.QtWidgets import QApplication
3796
3796
  QApplication.processEvents()
3797
3797
 
3798
+ # System prompt that explains the ACTION format to the AI
3799
+ ai_system_prompt = """You are an AI assistant for Supervertaler, a professional translation workbench.
3800
+
3801
+ You can execute actions using a special format. When you need to create, modify, or manage prompts, output ACTION blocks in this EXACT format:
3802
+
3803
+ ACTION:function_name PARAMS:{"param1": "value1", "param2": "value2"}
3804
+
3805
+ Available actions:
3806
+ - create_prompt: Create a new prompt. Required params: name, content. Optional: folder, description, activate
3807
+ - update_prompt: Update an existing prompt. Required params: name. Optional: content, folder, description
3808
+ - delete_prompt: Delete a prompt. Required params: name
3809
+ - list_prompts: List all prompts. Optional params: folder
3810
+ - activate_prompt: Set a prompt as active. Required params: name
3811
+
3812
+ IMPORTANT:
3813
+ 1. Output ONLY the ACTION block when asked to create/modify prompts - no explanatory text
3814
+ 2. The ACTION must be on a single line (PARAMS JSON can be multiline if needed)
3815
+ 3. Use valid JSON for PARAMS (double quotes for strings, escape special characters)
3816
+ 4. Do not wrap in code fences or add any markdown formatting"""
3817
+
3798
3818
  # Call LLM using translate method with custom prompt
3799
3819
  # The translate method accepts a custom_prompt parameter that we can use for any text generation
3800
3820
  self.log_message("[AI Assistant] Calling LLM translate method...")
@@ -3802,7 +3822,8 @@ Output complete ACTION."""
3802
3822
  text="", # Empty text since we're using custom_prompt
3803
3823
  source_lang="en",
3804
3824
  target_lang="en",
3805
- custom_prompt=prompt
3825
+ custom_prompt=prompt,
3826
+ system_prompt=ai_system_prompt
3806
3827
  )
3807
3828
 
3808
3829
  # Log the response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.194
3
+ Version: 1.9.198
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=k2rhthldnENDar6i9p1WikBXKg9d13jaWpRjtHzcX_g,2380045
1
+ Supervertaler.py,sha256=YY7nUEoaCqP0SZucFNKDqBuGhJDOgcUNb6lPbf9xsvo,2390297
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
@@ -23,14 +23,13 @@ modules/find_replace_qt.py,sha256=z06yyjOCmpYBrCzZcCv68VK-2o6pjfFCPtbr9o95XH8,18
23
23
  modules/glossary_manager.py,sha256=JDxY9RAGcv-l6Nms4FH7CNDucZdY1TI4WTzyylAuj24,16437
24
24
  modules/image_extractor.py,sha256=HI6QHnpkjO35GHzTXbzazYdjoHZPFD44WAa4JGa9bAw,8332
25
25
  modules/keyboard_shortcuts_widget.py,sha256=H489eknMDJkYusri1hQYr5MT-NhkVoHWdx9k_NMTr68,26411
26
- modules/llm_clients.py,sha256=sdlc6CMFhPbAM5OEJow7LFsHCY8HOnU1jLXHVh5cJ50,48831
26
+ modules/llm_clients.py,sha256=q-E9PnmtAuN8LWs_EKlMV4sXN5Jx2L57kfKCTk9X-24,50012
27
27
  modules/llm_leaderboard.py,sha256=MQ-RbjlE10-CdgVHwoVXzXlCuUfulZ839FaF6dKlt1M,29139
28
28
  modules/llm_superbench_ui.py,sha256=lmzsL8lt0KzFw-z8De1zb49Emnv7f1dZv_DJmoQz0bQ,60212
29
29
  modules/local_llm_setup.py,sha256=33y-D_LKzkn2w8ejyjeKaovf_An6xQ98mKISoqe-Qjc,42661
30
30
  modules/model_update_dialog.py,sha256=kEg0FuO1N-uj6QY5ZIj-FqdiLQuPuAY48pbuwT0HUGI,13113
31
31
  modules/model_version_checker.py,sha256=41g7gcWvyrKPYeobaOGCMZLwAHgQmFwVF8zokodKae8,12741
32
32
  modules/mqxliff_handler.py,sha256=TVtrf7ieGoxfoLxy4v4S7by9YImKypw1EY0wFpZO3Lo,28792
33
- modules/mt_quick_popup.py,sha256=jnCwaX8tSwaGlM5BxX2NEz5XqkHu9Pnxd8XBRX3OUFM,26291
34
33
  modules/non_translatables_manager.py,sha256=izorabiX6rSQzuBIvnY67wmu5vd85SbzexXccbmwPs4,27465
35
34
  modules/pdf_rescue_Qt.py,sha256=9W_M0Zms4miapQbrqm-viHNCpaW39GL9VaKKFCJxpnE,80479
36
35
  modules/pdf_rescue_tkinter.py,sha256=a4R_OUnn7X5O_XMR1roybrdu1aXoGCwwO-mwYB2ZpOg,39606
@@ -41,6 +40,7 @@ modules/prompt_assistant.py,sha256=shkZqNTvyQKNDO_9aFEu1_gN0zQq0fR5krXkWfnTR2Y,1
41
40
  modules/prompt_library.py,sha256=t5w4cqB6_Sin4BQDVNALKpfB1EN_oaDeHFwlHxILLSY,26894
42
41
  modules/prompt_library_migration.py,sha256=fv3RHhe2-EnH50XW5tyTWy0YP_KJ2EsESuTxR8klfmI,17639
43
42
  modules/quick_access_sidebar.py,sha256=RPn5ssvYXlitNMWFZN9Yxv7So8u_z5RGNpHN6N-SFDI,10151
43
+ modules/quicktrans.py,sha256=jnCwaX8tSwaGlM5BxX2NEz5XqkHu9Pnxd8XBRX3OUFM,26291
44
44
  modules/ribbon_widget.py,sha256=QNGKxmit_oM5C5nJViadYYEzeRlIdIsla8Bzu_RNGO0,21990
45
45
  modules/sdlppx_handler.py,sha256=o6Rj_T0B94toiYlvDDwMYLSz4q6kANgegFaDK5i3yhs,33538
46
46
  modules/setup_wizard.py,sha256=7apNkeTcmMyw8pzJOWAmTOncxFvt3klOtmg-kKjQNgQ,13091
@@ -76,13 +76,13 @@ modules/translation_memory.py,sha256=LnG8csZNL2GTHXT4zk0uecJEtvRc-MKwv7Pt7EX3s7s
76
76
  modules/translation_results_panel.py,sha256=OWqzV9xmJOi8NGCi3h42nq-qE7-v6WStjQWRsghCVbQ,92044
77
77
  modules/translation_services.py,sha256=lyVpWuZK1wtVtYZMDMdLoq1DHBoSaeAnp-Yejb0TlVQ,10530
78
78
  modules/unified_prompt_library.py,sha256=96u4WlMwnmmhD4uNJHZ-qVQj8v9_8dA2AVCWpBcwTrg,26006
79
- modules/unified_prompt_manager_qt.py,sha256=HkGUnH0wlfxt-hVe-nKCeWLyProYdefuuq2slqv8hI4,172160
79
+ modules/unified_prompt_manager_qt.py,sha256=y7xAIM9X7f1afXGE1tOcAc5EY7BKFy53Rgqi3fFzKmQ,173374
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.194.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
- supervertaler-1.9.194.dist-info/METADATA,sha256=due75gEd18_LV7HXO677PBvuMnD_RTjghSF7Jq275IM,5725
85
- supervertaler-1.9.194.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
- supervertaler-1.9.194.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
- supervertaler-1.9.194.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
- supervertaler-1.9.194.dist-info/RECORD,,
83
+ supervertaler-1.9.198.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
+ supervertaler-1.9.198.dist-info/METADATA,sha256=XD0vrT3puNdRtUEj8oHeAnOkSJ7CohlYNWQo5_VkM8M,5725
85
+ supervertaler-1.9.198.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
+ supervertaler-1.9.198.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
+ supervertaler-1.9.198.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
+ supervertaler-1.9.198.dist-info/RECORD,,
File without changes