supervertaler 1.9.203__py3-none-any.whl → 1.9.205__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of supervertaler might be problematic. Click here for more details.

Supervertaler.py CHANGED
@@ -32,7 +32,7 @@ License: MIT
32
32
  """
33
33
 
34
34
  # Version Information.
35
- __version__ = "1.9.203"
35
+ __version__ = "1.9.204"
36
36
  __phase__ = "0.9"
37
37
  __release_date__ = "2026-02-03"
38
38
  __edition__ = "Qt"
@@ -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
@@ -9540,6 +9544,9 @@ class SupervertalerQt(QMainWindow):
9540
9544
  QPushButton:hover {
9541
9545
  background-color: #2980b9;
9542
9546
  }
9547
+ QPushButton:focus {
9548
+ outline: none;
9549
+ }
9543
9550
  """)
9544
9551
  extract_btn.clicked.connect(self._on_extract_images)
9545
9552
  header_layout.addWidget(extract_btn)
@@ -11257,8 +11264,10 @@ class SupervertalerQt(QMainWindow):
11257
11264
  text = re.sub(r'</?li>', '', text) # <li>, </li>
11258
11265
  text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
11259
11266
  text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
11267
+ text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
11268
+ text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
11260
11269
  return text.strip()
11261
-
11270
+
11262
11271
  def clean_special_chars(text):
11263
11272
  """Remove problematic Unicode characters like object replacement char"""
11264
11273
  # Remove Unicode Object Replacement Character (U+FFFC) and similar
@@ -11272,34 +11281,48 @@ class SupervertalerQt(QMainWindow):
11272
11281
  """
11273
11282
  Replace paragraph text with tagged text, applying bold/italic/underline formatting.
11274
11283
  Parses tags like <b>, <i>, <u>, <bi> and creates appropriate runs.
11284
+ Preserves original font name and size from the first run.
11275
11285
  """
11276
11286
  # Clean special characters first
11277
11287
  text = clean_special_chars(tagged_text)
11278
-
11288
+
11279
11289
  # Strip list tags - they don't affect formatting
11280
11290
  text = re.sub(r'</?li-[bo]>', '', text)
11281
11291
  text = re.sub(r'</?li>', '', text)
11282
-
11292
+
11293
+ # Capture original font properties BEFORE clearing runs
11294
+ original_font_name = None
11295
+ original_font_size = None
11296
+ original_all_caps = None
11297
+ if para.runs:
11298
+ first_run = para.runs[0]
11299
+ if first_run.font:
11300
+ original_font_name = first_run.font.name
11301
+ original_font_size = first_run.font.size
11302
+ original_all_caps = first_run.font.all_caps
11303
+
11283
11304
  # Clear existing runs
11284
11305
  for run in para.runs:
11285
11306
  run.clear()
11286
11307
  # Remove the cleared runs
11287
11308
  for run in list(para.runs):
11288
11309
  run._element.getparent().remove(run._element)
11289
-
11310
+
11290
11311
  # Parse tags and create runs with formatting
11291
11312
  # Pattern matches tags or text between tags
11292
- tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
11313
+ tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
11293
11314
  parts = tag_pattern.split(text)
11294
-
11315
+
11295
11316
  is_bold = False
11296
11317
  is_italic = False
11297
11318
  is_underline = False
11298
-
11319
+ is_subscript = False
11320
+ is_superscript = False
11321
+
11299
11322
  for part in parts:
11300
11323
  if not part:
11301
11324
  continue
11302
-
11325
+
11303
11326
  # Check if this is a tag
11304
11327
  if part == '<b>':
11305
11328
  is_bold = True
@@ -11319,6 +11342,14 @@ class SupervertalerQt(QMainWindow):
11319
11342
  elif part == '</bi>':
11320
11343
  is_bold = False
11321
11344
  is_italic = False
11345
+ elif part == '<sub>':
11346
+ is_subscript = True
11347
+ elif part == '</sub>':
11348
+ is_subscript = False
11349
+ elif part == '<sup>':
11350
+ is_superscript = True
11351
+ elif part == '</sup>':
11352
+ is_superscript = False
11322
11353
  else:
11323
11354
  # This is text content - create a run with current formatting
11324
11355
  if part.strip() or part: # Include whitespace
@@ -11326,6 +11357,17 @@ class SupervertalerQt(QMainWindow):
11326
11357
  run.bold = is_bold
11327
11358
  run.italic = is_italic
11328
11359
  run.underline = is_underline
11360
+ if is_subscript:
11361
+ run.font.subscript = True
11362
+ if is_superscript:
11363
+ run.font.superscript = True
11364
+ # Restore original font properties
11365
+ if original_font_name:
11366
+ run.font.name = original_font_name
11367
+ if original_font_size:
11368
+ run.font.size = original_font_size
11369
+ if original_all_caps:
11370
+ run.font.all_caps = original_all_caps
11329
11371
 
11330
11372
  # Build a mapping of source text (without tags) to raw target text (with tags)
11331
11373
  text_map = {}
@@ -11338,16 +11380,14 @@ class SupervertalerQt(QMainWindow):
11338
11380
  if source_clean and target_raw:
11339
11381
  text_map[source_clean] = target_raw
11340
11382
 
11341
- def replace_segments_in_text(original_text, text_map):
11342
- """Replace all matching segments in text, handling partial matches."""
11383
+ def replace_segments_in_text_with_tags(original_text, text_map):
11384
+ """Replace all matching segments in text, preserving formatting tags."""
11343
11385
  result = original_text
11344
11386
  # Sort by length (longest first) to avoid partial replacement issues
11345
11387
  for source_clean, target_raw in sorted(text_map.items(), key=lambda x: len(x[0]), reverse=True):
11346
11388
  if source_clean in result:
11347
- # Get clean target (no tags) for text replacement
11348
- target_clean = strip_all_tags(target_raw)
11349
- target_clean = clean_special_chars(target_clean)
11350
- result = result.replace(source_clean, target_clean)
11389
+ # Keep target WITH tags for formatting preservation
11390
+ result = result.replace(source_clean, target_raw)
11351
11391
  return result
11352
11392
 
11353
11393
  replaced_count = 0
@@ -11363,17 +11403,13 @@ class SupervertalerQt(QMainWindow):
11363
11403
  replaced_count += 1
11364
11404
  else:
11365
11405
  # Try partial replacement (paragraph contains multiple segments)
11366
- new_text = replace_segments_in_text(para_text, text_map)
11406
+ new_text = replace_segments_in_text_with_tags(para_text, text_map)
11367
11407
  if new_text != para_text:
11368
- # Text was changed - update paragraph
11369
- # For partial replacements, we lose formatting tags but at least translate
11370
- for run in para.runs:
11371
- run.clear()
11372
- for run in list(para.runs):
11373
- run._element.getparent().remove(run._element)
11374
- para.add_run(new_text)
11408
+ # Text was changed - use apply_formatted_text_to_paragraph
11409
+ # to preserve inline formatting tags (bold, italic, sub, sup, etc.)
11410
+ apply_formatted_text_to_paragraph(para, new_text)
11375
11411
  replaced_count += 1
11376
-
11412
+
11377
11413
  # Replace text in tables
11378
11414
  for table in doc.tables:
11379
11415
  for row in table.rows:
@@ -11388,15 +11424,12 @@ class SupervertalerQt(QMainWindow):
11388
11424
  replaced_count += 1
11389
11425
  else:
11390
11426
  # Try partial replacement
11391
- new_text = replace_segments_in_text(para_text, text_map)
11427
+ new_text = replace_segments_in_text_with_tags(para_text, text_map)
11392
11428
  if new_text != para_text:
11393
- for run in para.runs:
11394
- run.clear()
11395
- for run in list(para.runs):
11396
- run._element.getparent().remove(run._element)
11397
- para.add_run(new_text)
11429
+ # Use apply_formatted_text_to_paragraph to preserve formatting
11430
+ apply_formatted_text_to_paragraph(para, new_text)
11398
11431
  replaced_count += 1
11399
-
11432
+
11400
11433
  doc.save(file_path)
11401
11434
  self.log(f"✓ Replaced {replaced_count} text segments in original document structure")
11402
11435
 
@@ -11412,8 +11445,10 @@ class SupervertalerQt(QMainWindow):
11412
11445
  text = re.sub(r'</?li>', '', text) # <li>, </li>
11413
11446
  text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
11414
11447
  text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
11448
+ text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
11449
+ text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
11415
11450
  return text.strip()
11416
-
11451
+
11417
11452
  def clean_special_chars(text):
11418
11453
  """Remove problematic Unicode characters"""
11419
11454
  text = text.replace('\ufffc', '') # Object Replacement Character
@@ -11432,13 +11467,15 @@ class SupervertalerQt(QMainWindow):
11432
11467
  text = re.sub(r'</?li>', '', text)
11433
11468
 
11434
11469
  # Parse and apply formatting
11435
- tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
11470
+ tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
11436
11471
  parts = tag_pattern.split(text)
11437
-
11472
+
11438
11473
  is_bold = False
11439
11474
  is_italic = False
11440
11475
  is_underline = False
11441
-
11476
+ is_subscript = False
11477
+ is_superscript = False
11478
+
11442
11479
  for part in parts:
11443
11480
  if not part:
11444
11481
  continue
@@ -11460,13 +11497,25 @@ class SupervertalerQt(QMainWindow):
11460
11497
  elif part == '</bi>':
11461
11498
  is_bold = False
11462
11499
  is_italic = False
11500
+ elif part == '<sub>':
11501
+ is_subscript = True
11502
+ elif part == '</sub>':
11503
+ is_subscript = False
11504
+ elif part == '<sup>':
11505
+ is_superscript = True
11506
+ elif part == '</sup>':
11507
+ is_superscript = False
11463
11508
  else:
11464
11509
  if part:
11465
11510
  run = para.add_run(part)
11466
11511
  run.bold = is_bold
11467
11512
  run.italic = is_italic
11468
11513
  run.underline = is_underline
11469
-
11514
+ if is_subscript:
11515
+ run.font.subscript = True
11516
+ if is_superscript:
11517
+ run.font.superscript = True
11518
+
11470
11519
  doc = Document()
11471
11520
 
11472
11521
  for seg in segments:
@@ -21144,6 +21193,9 @@ class SupervertalerQt(QMainWindow):
21144
21193
  QPushButton:hover:!checked {
21145
21194
  background-color: #858585;
21146
21195
  }
21196
+ QPushButton:focus {
21197
+ outline: none;
21198
+ }
21147
21199
  """)
21148
21200
  wysiwyg_btn.clicked.connect(lambda: self.toggle_tag_view(False, None))
21149
21201
  view_mode_group.addButton(wysiwyg_btn, 0)
@@ -21170,6 +21222,9 @@ class SupervertalerQt(QMainWindow):
21170
21222
  QPushButton:hover:!checked {
21171
21223
  background-color: #858585;
21172
21224
  }
21225
+ QPushButton:focus {
21226
+ outline: none;
21227
+ }
21173
21228
  """)
21174
21229
  tags_btn.clicked.connect(lambda: self.toggle_tag_view(True, None))
21175
21230
  view_mode_group.addButton(tags_btn, 1)
@@ -21227,6 +21282,9 @@ class SupervertalerQt(QMainWindow):
21227
21282
  QPushButton:checked {
21228
21283
  background-color: #2E7D32;
21229
21284
  }
21285
+ QPushButton:focus {
21286
+ outline: none;
21287
+ }
21230
21288
  """)
21231
21289
  alwayson_btn.setToolTip("Toggle always-on voice listening\n\nWhen ON: Listens continuously for voice commands\nNo need to press F9")
21232
21290
  alwayson_btn.clicked.connect(lambda checked: self._toggle_alwayson_from_grid_btn(checked, alwayson_btn))
@@ -21986,6 +22044,9 @@ class SupervertalerQt(QMainWindow):
21986
22044
  QPushButton:pressed {
21987
22045
  background-color: #1565C0;
21988
22046
  }
22047
+ QPushButton:focus {
22048
+ outline: none;
22049
+ }
21989
22050
  """)
21990
22051
  self.scroll_up_btn.setToolTip("Scroll up one row (Precision scroll)")
21991
22052
  self.scroll_up_btn.clicked.connect(lambda: self.precision_scroll(-1))
@@ -22012,6 +22073,9 @@ class SupervertalerQt(QMainWindow):
22012
22073
  QPushButton:pressed {
22013
22074
  background-color: #1565C0;
22014
22075
  }
22076
+ QPushButton:focus {
22077
+ outline: none;
22078
+ }
22015
22079
  """)
22016
22080
  self.scroll_down_btn.setToolTip("Scroll down one row (Precision scroll)")
22017
22081
  self.scroll_down_btn.clicked.connect(lambda: self.precision_scroll(1))
@@ -24632,6 +24696,11 @@ class SupervertalerQt(QMainWindow):
24632
24696
  # Set as current project and load into grid
24633
24697
  self.current_project = project
24634
24698
  self.current_document_path = file_path # Store document path
24699
+
24700
+ # CRITICAL: Update _original_segment_order for new import
24701
+ # This prevents old segments from being saved instead of new ones
24702
+ self._original_segment_order = self.current_project.segments.copy()
24703
+
24635
24704
  self.load_segments_to_grid()
24636
24705
 
24637
24706
  # Initialize TM for this project
@@ -24866,14 +24935,18 @@ class SupervertalerQt(QMainWindow):
24866
24935
  # Set as current project and load into grid
24867
24936
  self.current_project = project
24868
24937
  self.current_document_path = file_path
24938
+
24939
+ # CRITICAL: Update _original_segment_order for new import
24940
+ self._original_segment_order = self.current_project.segments.copy()
24941
+
24869
24942
  self.load_segments_to_grid()
24870
-
24943
+
24871
24944
  # Initialize TM for this project
24872
24945
  self.initialize_tm_database()
24873
-
24946
+
24874
24947
  # Initialize spellcheck for target language
24875
24948
  self._initialize_spellcheck_for_target_language(target_lang)
24876
-
24949
+
24877
24950
  # Update status
24878
24951
  empty_count = sum(1 for seg in segments if not seg.source.strip())
24879
24952
  file_type = "Markdown file" if is_markdown else "text file"
@@ -24933,6 +25006,8 @@ class SupervertalerQt(QMainWindow):
24933
25006
  project.original_txt_path = file_path
24934
25007
  self.current_project = project
24935
25008
  self.current_document_path = file_path
25009
+ # CRITICAL: Update _original_segment_order for new import
25010
+ self._original_segment_order = self.current_project.segments.copy()
24936
25011
  self.load_segments_to_grid()
24937
25012
  self.initialize_tm_database()
24938
25013
  self.log(f"✓ Loaded {len(segments)} lines from text file")
@@ -26261,10 +26336,13 @@ class SupervertalerQt(QMainWindow):
26261
26336
  # Set as current project
26262
26337
  self.current_project = project
26263
26338
  self.current_document_path = folder_path
26264
-
26339
+
26340
+ # CRITICAL: Update _original_segment_order for new import
26341
+ self._original_segment_order = self.current_project.segments.copy()
26342
+
26265
26343
  # Load into grid
26266
26344
  self.load_segments_to_grid()
26267
-
26345
+
26268
26346
  # Initialize TM
26269
26347
  self.initialize_tm_database()
26270
26348
 
@@ -27137,10 +27215,14 @@ class SupervertalerQt(QMainWindow):
27137
27215
  # Update UI
27138
27216
  self.project_file_path = None
27139
27217
  self.project_modified = True
27218
+
27219
+ # CRITICAL: Update _original_segment_order for new import
27220
+ self._original_segment_order = self.current_project.segments.copy()
27221
+
27140
27222
  self.update_window_title()
27141
27223
  self.load_segments_to_grid()
27142
27224
  self.initialize_tm_database()
27143
-
27225
+
27144
27226
  # Deactivate all resources for new project, then auto-activate language-matching ones
27145
27227
  self._deactivate_all_resources_for_new_project()
27146
27228
 
@@ -27153,7 +27235,7 @@ class SupervertalerQt(QMainWindow):
27153
27235
  # If smart formatting was used, auto-enable Tags view so user sees the tags
27154
27236
  if self.memoq_smart_formatting:
27155
27237
  self._enable_tag_view_after_import()
27156
-
27238
+
27157
27239
  self.log(f"✓ Imported memoQ bilingual DOCX: {len(source_segments)} segments from {Path(file_path).name}")
27158
27240
 
27159
27241
  # Store current document path for AI Assistant
@@ -27646,10 +27728,14 @@ class SupervertalerQt(QMainWindow):
27646
27728
  # Update UI
27647
27729
  self.project_file_path = None
27648
27730
  self.project_modified = True
27731
+
27732
+ # CRITICAL: Update _original_segment_order for new import
27733
+ self._original_segment_order = self.current_project.segments.copy()
27734
+
27649
27735
  self.update_window_title()
27650
27736
  self.load_segments_to_grid()
27651
27737
  self.initialize_tm_database()
27652
-
27738
+
27653
27739
  # Deactivate all resources for new project, then auto-activate language-matching ones
27654
27740
  self._deactivate_all_resources_for_new_project()
27655
27741
 
@@ -27925,10 +28011,14 @@ class SupervertalerQt(QMainWindow):
27925
28011
  # Update UI
27926
28012
  self.project_file_path = None
27927
28013
  self.project_modified = True
28014
+
28015
+ # CRITICAL: Update _original_segment_order for new import
28016
+ self._original_segment_order = self.current_project.segments.copy()
28017
+
27928
28018
  self.update_window_title()
27929
28019
  self.load_segments_to_grid()
27930
28020
  self.initialize_tm_database()
27931
-
28021
+
27932
28022
  # Deactivate all resources for new project, then auto-activate language-matching ones
27933
28023
  self._deactivate_all_resources_for_new_project()
27934
28024
 
@@ -28146,6 +28236,9 @@ class SupervertalerQt(QMainWindow):
28146
28236
  # Store Trados source path in project for persistence across saves
28147
28237
  self.current_project.trados_source_path = file_path
28148
28238
 
28239
+ # CRITICAL: Update _original_segment_order for new import
28240
+ self._original_segment_order = self.current_project.segments.copy()
28241
+
28149
28242
  # Sync global language settings with imported project languages
28150
28243
  self.source_language = source_lang
28151
28244
  self.target_language = target_lang
@@ -28510,6 +28603,9 @@ class SupervertalerQt(QMainWindow):
28510
28603
  self.sdlppx_source_file = file_path
28511
28604
  self.current_project.sdlppx_source_path = file_path
28512
28605
 
28606
+ # CRITICAL: Update _original_segment_order for new import
28607
+ self._original_segment_order = self.current_project.segments.copy()
28608
+
28513
28609
  # Sync global language settings with imported project languages
28514
28610
  self.source_language = source_lang
28515
28611
  self.target_language = target_lang
@@ -28846,6 +28942,9 @@ class SupervertalerQt(QMainWindow):
28846
28942
  # Store Phrase source path in project for persistence across saves
28847
28943
  self.current_project.phrase_source_path = file_path
28848
28944
 
28945
+ # CRITICAL: Update _original_segment_order for new import
28946
+ self._original_segment_order = self.current_project.segments.copy()
28947
+
28849
28948
  # Sync global language settings with imported project languages
28850
28949
  self.source_language = source_lang
28851
28950
  self.target_language = target_lang
@@ -29148,20 +29247,23 @@ class SupervertalerQt(QMainWindow):
29148
29247
  dejavu_row_index=seg_data.row_index,
29149
29248
  )
29150
29249
  self.current_project.segments.append(segment)
29151
-
29250
+
29251
+ # CRITICAL: Update _original_segment_order for new import
29252
+ self._original_segment_order = self.current_project.segments.copy()
29253
+
29152
29254
  # Update UI
29153
29255
  self.project_file_path = None
29154
29256
  self.project_modified = True
29155
29257
  self.update_window_title()
29156
29258
  self.load_segments_to_grid()
29157
29259
  self.initialize_tm_database()
29158
-
29260
+
29159
29261
  # Auto-resize rows for better initial display
29160
29262
  self.auto_resize_rows()
29161
-
29263
+
29162
29264
  # Initialize spellcheck for target language
29163
29265
  self._initialize_spellcheck_for_target_language(target_lang)
29164
-
29266
+
29165
29267
  self.log(f"✓ Imported Déjà Vu X3 bilingual RTF: {len(segments_data)} segments from {Path(file_path).name}")
29166
29268
 
29167
29269
  # Store current document path for AI Assistant
@@ -44559,12 +44661,25 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44559
44661
 
44560
44662
  translator = deepl.Translator(api_key)
44561
44663
 
44562
- # Convert source language code (DeepL uses uppercase, no variant needed for source)
44563
- src_code = source_lang.split('-')[0].split('_')[0].upper()
44664
+ # Map full language names to ISO codes
44665
+ lang_name_to_code = {
44666
+ 'english': 'en', 'dutch': 'nl', 'german': 'de', 'french': 'fr',
44667
+ 'spanish': 'es', 'italian': 'it', 'portuguese': 'pt', 'russian': 'ru',
44668
+ 'chinese': 'zh', 'japanese': 'ja', 'korean': 'ko', 'arabic': 'ar',
44669
+ 'polish': 'pl', 'swedish': 'sv', 'norwegian': 'no', 'danish': 'da',
44670
+ 'finnish': 'fi', 'greek': 'el', 'turkish': 'tr', 'czech': 'cs',
44671
+ 'hungarian': 'hu', 'romanian': 'ro', 'bulgarian': 'bg', 'ukrainian': 'uk',
44672
+ }
44673
+
44674
+ # Convert source language - try name mapping first, then code extraction
44675
+ src_lower = source_lang.lower().strip()
44676
+ src_base = lang_name_to_code.get(src_lower, src_lower.split('-')[0].split('_')[0])
44677
+ src_code = src_base.upper()
44564
44678
 
44565
- # Convert target language code - DeepL requires variants for some languages
44566
- # Handle full codes like "en-US", "en-GB", "pt-BR", "pt-PT"
44567
- tgt_upper = target_lang.upper().replace('_', '-')
44679
+ # Convert target language - try name mapping first, then handle DeepL variants
44680
+ tgt_lower = target_lang.lower().strip()
44681
+ tgt_base = lang_name_to_code.get(tgt_lower, tgt_lower)
44682
+ tgt_upper = tgt_base.upper().replace('_', '-')
44568
44683
 
44569
44684
  # DeepL target language mapping - some require specific variants
44570
44685
  deepl_target_map = {
@@ -44610,19 +44725,33 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44610
44725
  """Call Microsoft Azure Translator API"""
44611
44726
  try:
44612
44727
  import requests
44613
-
44728
+
44614
44729
  api_keys = self.load_api_keys()
44615
44730
  if not api_key:
44616
44731
  api_key = api_keys.get("microsoft_translate") or api_keys.get("azure_translate")
44617
44732
  if not region:
44618
44733
  region = api_keys.get("microsoft_translate_region") or api_keys.get("azure_region") or "global"
44619
-
44734
+
44620
44735
  if not api_key:
44621
44736
  return "[Microsoft Translator requires API key]"
44622
-
44623
- # Convert language codes (Azure uses standard codes)
44624
- src_code = source_lang.split('-')[0].split('_')[0].lower()
44625
- tgt_code = target_lang.split('-')[0].split('_')[0].lower()
44737
+
44738
+ # Map full language names to ISO codes
44739
+ lang_name_to_code = {
44740
+ 'english': 'en', 'dutch': 'nl', 'german': 'de', 'french': 'fr',
44741
+ 'spanish': 'es', 'italian': 'it', 'portuguese': 'pt', 'russian': 'ru',
44742
+ 'chinese': 'zh', 'japanese': 'ja', 'korean': 'ko', 'arabic': 'ar',
44743
+ 'polish': 'pl', 'swedish': 'sv', 'norwegian': 'no', 'danish': 'da',
44744
+ 'finnish': 'fi', 'greek': 'el', 'turkish': 'tr', 'czech': 'cs',
44745
+ 'hungarian': 'hu', 'romanian': 'ro', 'bulgarian': 'bg', 'ukrainian': 'uk',
44746
+ 'hebrew': 'he', 'thai': 'th', 'vietnamese': 'vi', 'indonesian': 'id',
44747
+ }
44748
+
44749
+ # Convert language - try name mapping first, then code extraction
44750
+ src_lower = source_lang.lower().strip()
44751
+ tgt_lower = target_lang.lower().strip()
44752
+
44753
+ src_code = lang_name_to_code.get(src_lower, src_lower.split('-')[0].split('_')[0])
44754
+ tgt_code = lang_name_to_code.get(tgt_lower, tgt_lower.split('-')[0].split('_')[0])
44626
44755
 
44627
44756
  # Microsoft Translator API v3.0
44628
44757
  endpoint = f"https://api.cognitive.microsofttranslator.com/translate"
@@ -44710,17 +44839,30 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44710
44839
  """Call ModernMT API"""
44711
44840
  try:
44712
44841
  import requests
44713
-
44842
+
44714
44843
  api_keys = self.load_api_keys()
44715
44844
  if not api_key:
44716
44845
  api_key = api_keys.get("modernmt")
44717
-
44846
+
44718
44847
  if not api_key:
44719
44848
  return "[ModernMT requires API key]"
44720
-
44721
- # Convert language codes
44722
- src_code = source_lang.split('-')[0].split('_')[0]
44723
- tgt_code = target_lang.split('-')[0].split('_')[0]
44849
+
44850
+ # Map full language names to ISO codes
44851
+ lang_name_to_code = {
44852
+ 'english': 'en', 'dutch': 'nl', 'german': 'de', 'french': 'fr',
44853
+ 'spanish': 'es', 'italian': 'it', 'portuguese': 'pt', 'russian': 'ru',
44854
+ 'chinese': 'zh', 'japanese': 'ja', 'korean': 'ko', 'arabic': 'ar',
44855
+ 'polish': 'pl', 'swedish': 'sv', 'norwegian': 'no', 'danish': 'da',
44856
+ 'finnish': 'fi', 'greek': 'el', 'turkish': 'tr', 'czech': 'cs',
44857
+ 'hungarian': 'hu', 'romanian': 'ro', 'bulgarian': 'bg', 'ukrainian': 'uk',
44858
+ }
44859
+
44860
+ # Convert language - try name mapping first, then code extraction
44861
+ src_lower = source_lang.lower().strip()
44862
+ tgt_lower = target_lang.lower().strip()
44863
+
44864
+ src_code = lang_name_to_code.get(src_lower, src_lower.split('-')[0].split('_')[0])
44865
+ tgt_code = lang_name_to_code.get(tgt_lower, tgt_lower.split('-')[0].split('_')[0])
44724
44866
 
44725
44867
  # ModernMT API endpoint
44726
44868
  url = "https://api.modernmt.com/translate"
@@ -44749,14 +44891,29 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44749
44891
  """Call MyMemory Translation API (free tier available, simple REST API)"""
44750
44892
  try:
44751
44893
  import requests
44752
-
44894
+
44753
44895
  # MyMemory is free, but API key provides higher limits
44754
44896
  api_keys = self.load_api_keys()
44755
44897
  api_key = api_key or api_keys.get("mymemory") # Optional, works without key
44756
-
44757
- # Convert language codes (MyMemory uses 2-letter codes)
44758
- src_code = source_lang.split('-')[0].split('_')[0].lower()
44759
- tgt_code = target_lang.split('-')[0].split('_')[0].lower()
44898
+
44899
+ # Map full language names to ISO codes
44900
+ lang_name_to_code = {
44901
+ 'english': 'en', 'dutch': 'nl', 'german': 'de', 'french': 'fr',
44902
+ 'spanish': 'es', 'italian': 'it', 'portuguese': 'pt', 'russian': 'ru',
44903
+ 'chinese': 'zh', 'japanese': 'ja', 'korean': 'ko', 'arabic': 'ar',
44904
+ 'polish': 'pl', 'swedish': 'sv', 'norwegian': 'no', 'danish': 'da',
44905
+ 'finnish': 'fi', 'greek': 'el', 'turkish': 'tr', 'czech': 'cs',
44906
+ 'hungarian': 'hu', 'romanian': 'ro', 'bulgarian': 'bg', 'ukrainian': 'uk',
44907
+ 'hebrew': 'he', 'thai': 'th', 'vietnamese': 'vi', 'indonesian': 'id',
44908
+ 'malay': 'ms', 'hindi': 'hi', 'bengali': 'bn', 'tamil': 'ta',
44909
+ }
44910
+
44911
+ # Convert language - try name mapping first, then code extraction
44912
+ src_lower = source_lang.lower().strip()
44913
+ tgt_lower = target_lang.lower().strip()
44914
+
44915
+ src_code = lang_name_to_code.get(src_lower, src_lower.split('-')[0].split('_')[0])
44916
+ tgt_code = lang_name_to_code.get(tgt_lower, tgt_lower.split('-')[0].split('_')[0])
44760
44917
 
44761
44918
  # MyMemory API endpoint
44762
44919
  url = "https://api.mymemory.translated.net/get"
modules/ai_actions.py CHANGED
@@ -168,9 +168,10 @@ class AIActionSystem:
168
168
 
169
169
  except json.JSONDecodeError as e:
170
170
  self.log(f"✗ Failed to parse action parameters: {e}")
171
+ self.log(f"[DEBUG] Raw params_str: {params_str[:500]}...") # Log what we tried to parse
171
172
  action_results.append({
172
173
  'action': action_name,
173
- 'params': params_str,
174
+ 'params': params_str[:200], # Truncate for display
174
175
  'success': False,
175
176
  'error': f"Invalid JSON parameters: {e}"
176
177
  })
@@ -325,7 +326,15 @@ class AIActionSystem:
325
326
  content = params.get('content')
326
327
 
327
328
  if not name or not content:
328
- raise ValueError("Missing required parameters: name and content")
329
+ # Log what we actually received for debugging
330
+ self.log(f"[DEBUG] create_prompt received params: {params}")
331
+ self.log(f"[DEBUG] name={repr(name)}, content={repr(content)[:100] if content else None}")
332
+ missing = []
333
+ if not name:
334
+ missing.append("name")
335
+ if not content:
336
+ missing.append("content")
337
+ raise ValueError(f"Missing required parameters: {', '.join(missing)}. Received keys: {list(params.keys())}")
329
338
 
330
339
  # Build relative path
331
340
  folder = params.get('folder', '')
@@ -960,5 +969,8 @@ PARAMS: {
960
969
  output += "\n"
961
970
  else:
962
971
  output += f"\n✗ **{action_name}**: {result['error']}\n"
972
+ # Show hint for common errors
973
+ if 'Missing required parameters' in result['error']:
974
+ output += " _Hint: The AI may not have generated the correct format. Try clicking the button again._\n"
963
975
 
964
976
  return output
modules/quicktrans.py CHANGED
@@ -289,6 +289,9 @@ class MTQuickPopup(QDialog):
289
289
  background-color: #e0e0e0;
290
290
  border-radius: 4px;
291
291
  }
292
+ QPushButton:focus {
293
+ outline: none;
294
+ }
292
295
  """)
293
296
  settings_btn.clicked.connect(self._open_settings)
294
297
  header_layout.addWidget(settings_btn)
@@ -447,6 +447,9 @@ class TermbaseEntryEditor(QDialog):
447
447
  QPushButton:hover {
448
448
  background-color: #d32f2f;
449
449
  }
450
+ QPushButton:focus {
451
+ outline: none;
452
+ }
450
453
  """)
451
454
  self.delete_btn.clicked.connect(self.delete_term)
452
455
  buttons_layout.addWidget(self.delete_btn)
@@ -465,6 +468,9 @@ class TermbaseEntryEditor(QDialog):
465
468
  QPushButton:hover {
466
469
  background-color: #e0e0e0;
467
470
  }
471
+ QPushButton:focus {
472
+ outline: none;
473
+ }
468
474
  """)
469
475
  self.cancel_btn.clicked.connect(self.reject)
470
476
  buttons_layout.addWidget(self.cancel_btn)
@@ -483,6 +489,9 @@ class TermbaseEntryEditor(QDialog):
483
489
  QPushButton:hover {
484
490
  background-color: #45a049;
485
491
  }
492
+ QPushButton:focus {
493
+ outline: none;
494
+ }
486
495
  """)
487
496
  self.save_btn.clicked.connect(self.save_term)
488
497
  buttons_layout.addWidget(self.save_btn)
modules/theme_manager.py CHANGED
@@ -370,7 +370,16 @@ class ThemeManager:
370
370
  QPushButton:disabled {{
371
371
  color: {theme.text_disabled};
372
372
  }}
373
-
373
+
374
+ /* Remove focus rectangles from buttons */
375
+ QPushButton:focus {{
376
+ outline: none;
377
+ }}
378
+
379
+ QToolButton:focus {{
380
+ outline: none;
381
+ }}
382
+
374
383
  /* Combo boxes */
375
384
  QComboBox {{
376
385
  background-color: {theme.base};
@@ -1190,6 +1190,9 @@ class TranslationResultsPanel(QWidget):
1190
1190
  QPushButton:hover {
1191
1191
  background-color: #0b7dda;
1192
1192
  }
1193
+ QPushButton:focus {
1194
+ outline: none;
1195
+ }
1193
1196
  """)
1194
1197
  self.termbase_refresh_btn.setFixedHeight(20)
1195
1198
  self.termbase_refresh_btn.setToolTip("Refresh entry from database")
@@ -1210,6 +1213,9 @@ class TranslationResultsPanel(QWidget):
1210
1213
  QPushButton:hover {
1211
1214
  background-color: #45a049;
1212
1215
  }
1216
+ QPushButton:focus {
1217
+ outline: none;
1218
+ }
1213
1219
  """)
1214
1220
  self.termbase_edit_btn.setFixedHeight(20)
1215
1221
  self.termbase_edit_btn.clicked.connect(self._on_edit_termbase_entry)
@@ -595,6 +595,10 @@ class UnifiedPromptManagerQt:
595
595
  log_callback=self.log_message
596
596
  )
597
597
 
598
+ # Context inclusion toggles for AI Assistant
599
+ self.include_tm_data = False
600
+ self.include_termbase_data = False
601
+
598
602
  self._init_llm_client()
599
603
  self._load_conversation_history()
600
604
  self._load_persisted_attachments()
@@ -824,6 +828,9 @@ class UnifiedPromptManagerQt:
824
828
  QPushButton:pressed {
825
829
  background-color: #0D47A1;
826
830
  }
831
+ QPushButton:focus {
832
+ outline: none;
833
+ }
827
834
  """)
828
835
  action_btn.clicked.connect(self._analyze_and_generate)
829
836
  layout.addWidget(action_btn, 0)
@@ -1046,14 +1053,16 @@ class UnifiedPromptManagerQt:
1046
1053
  "Click to include TM data"
1047
1054
  )
1048
1055
  self.context_tms.setCursor(Qt.CursorShape.PointingHandCursor)
1056
+ self.context_tms.mousePressEvent = lambda e: self._toggle_tm_inclusion()
1049
1057
  content_layout.addWidget(self.context_tms)
1050
-
1058
+
1051
1059
  # Termbases
1052
1060
  self.context_termbases = self._create_context_section(
1053
1061
  "📚 Termbases",
1054
1062
  "Click to include termbase data"
1055
1063
  )
1056
1064
  self.context_termbases.setCursor(Qt.CursorShape.PointingHandCursor)
1065
+ self.context_termbases.mousePressEvent = lambda e: self._toggle_termbase_inclusion()
1057
1066
  content_layout.addWidget(self.context_termbases)
1058
1067
 
1059
1068
  content_layout.addStretch()
@@ -1094,6 +1103,95 @@ class UnifiedPromptManagerQt:
1094
1103
 
1095
1104
  return frame
1096
1105
 
1106
+ def _toggle_tm_inclusion(self):
1107
+ """Toggle inclusion of TM data in AI context"""
1108
+ self.include_tm_data = not self.include_tm_data
1109
+ self._update_tm_section_display()
1110
+
1111
+ # Show feedback
1112
+ status = "enabled" if self.include_tm_data else "disabled"
1113
+ self._add_chat_message("system", f"💾 Translation Memory inclusion **{status}**")
1114
+
1115
+ def _toggle_termbase_inclusion(self):
1116
+ """Toggle inclusion of termbase data in AI context"""
1117
+ self.include_termbase_data = not self.include_termbase_data
1118
+ self._update_termbase_section_display()
1119
+
1120
+ # Show feedback
1121
+ status = "enabled" if self.include_termbase_data else "disabled"
1122
+ self._add_chat_message("system", f"📚 Termbase inclusion **{status}**")
1123
+
1124
+ def _update_tm_section_display(self):
1125
+ """Update TM section visual state"""
1126
+ if self.include_tm_data:
1127
+ self.context_tms.setStyleSheet("""
1128
+ QFrame {
1129
+ background-color: #E3F2FD;
1130
+ border: 2px solid #1976D2;
1131
+ border-radius: 5px;
1132
+ padding: 8px;
1133
+ }
1134
+ """)
1135
+ # Update description label
1136
+ for child in self.context_tms.findChildren(QLabel):
1137
+ if "Click to" in child.text() or "✓" in child.text():
1138
+ child.setText("✓ TM data will be included")
1139
+ child.setStyleSheet("color: #1976D2; font-size: 8pt; font-weight: bold;")
1140
+ break
1141
+ else:
1142
+ self.context_tms.setStyleSheet("""
1143
+ QFrame {
1144
+ background-color: #F5F5F5;
1145
+ border: 1px solid #E0E0E0;
1146
+ border-radius: 5px;
1147
+ padding: 8px;
1148
+ }
1149
+ QFrame:hover {
1150
+ background-color: #EEEEEE;
1151
+ border: 1px solid #BDBDBD;
1152
+ }
1153
+ """)
1154
+ for child in self.context_tms.findChildren(QLabel):
1155
+ if "✓" in child.text() or "Click to" in child.text():
1156
+ child.setText("Click to include TM data")
1157
+ child.setStyleSheet("color: #666; font-size: 8pt;")
1158
+ break
1159
+
1160
+ def _update_termbase_section_display(self):
1161
+ """Update termbase section visual state"""
1162
+ if self.include_termbase_data:
1163
+ self.context_termbases.setStyleSheet("""
1164
+ QFrame {
1165
+ background-color: #E8F5E9;
1166
+ border: 2px solid #4CAF50;
1167
+ border-radius: 5px;
1168
+ padding: 8px;
1169
+ }
1170
+ """)
1171
+ for child in self.context_termbases.findChildren(QLabel):
1172
+ if "Click to" in child.text() or "✓" in child.text():
1173
+ child.setText("✓ Termbase data will be included")
1174
+ child.setStyleSheet("color: #4CAF50; font-size: 8pt; font-weight: bold;")
1175
+ break
1176
+ else:
1177
+ self.context_termbases.setStyleSheet("""
1178
+ QFrame {
1179
+ background-color: #F5F5F5;
1180
+ border: 1px solid #E0E0E0;
1181
+ border-radius: 5px;
1182
+ padding: 8px;
1183
+ }
1184
+ QFrame:hover {
1185
+ background-color: #EEEEEE;
1186
+ border: 1px solid #BDBDBD;
1187
+ }
1188
+ """)
1189
+ for child in self.context_termbases.findChildren(QLabel):
1190
+ if "✓" in child.text() or "Click to" in child.text():
1191
+ child.setText("Click to include termbase data")
1192
+ child.setStyleSheet("color: #666; font-size: 8pt;")
1193
+ break
1194
+
1097
1195
  def _create_attached_files_section(self) -> QFrame:
1098
1196
  """Create expandable attached files section with view/remove buttons"""
1099
1197
  frame = QFrame()
@@ -1125,6 +1223,9 @@ class UnifiedPromptManagerQt:
1125
1223
  QPushButton:hover {
1126
1224
  background-color: #E0E0E0;
1127
1225
  }
1226
+ QPushButton:focus {
1227
+ outline: none;
1228
+ }
1128
1229
  """)
1129
1230
  self.attached_files_expand_btn.clicked.connect(self._toggle_attached_files)
1130
1231
  header_layout.addWidget(self.attached_files_expand_btn)
@@ -1149,6 +1250,9 @@ class UnifiedPromptManagerQt:
1149
1250
  QPushButton:hover {
1150
1251
  background-color: #1565C0;
1151
1252
  }
1253
+ QPushButton:focus {
1254
+ outline: none;
1255
+ }
1152
1256
  """)
1153
1257
  attach_btn.clicked.connect(self._attach_file)
1154
1258
  header_layout.addWidget(attach_btn)
@@ -1253,6 +1357,9 @@ class UnifiedPromptManagerQt:
1253
1357
  QPushButton:hover {
1254
1358
  background-color: #1565C0;
1255
1359
  }
1360
+ QPushButton:focus {
1361
+ outline: none;
1362
+ }
1256
1363
  """)
1257
1364
  view_btn.clicked.connect(lambda: self._view_file(file_data))
1258
1365
  btn_layout.addWidget(view_btn)
@@ -1270,6 +1377,9 @@ class UnifiedPromptManagerQt:
1270
1377
  QPushButton:hover {
1271
1378
  background-color: #b71c1c;
1272
1379
  }
1380
+ QPushButton:focus {
1381
+ outline: none;
1382
+ }
1273
1383
  """)
1274
1384
  remove_btn.clicked.connect(lambda: self._remove_file(file_data))
1275
1385
  btn_layout.addWidget(remove_btn)
@@ -1405,6 +1515,9 @@ class UnifiedPromptManagerQt:
1405
1515
  QPushButton:pressed {
1406
1516
  background-color: #424242;
1407
1517
  }
1518
+ QPushButton:focus {
1519
+ outline: none;
1520
+ }
1408
1521
  """)
1409
1522
  clear_btn.clicked.connect(self._clear_chat)
1410
1523
  toolbar_layout.addWidget(clear_btn)
@@ -1456,6 +1569,9 @@ class UnifiedPromptManagerQt:
1456
1569
  QPushButton:pressed {
1457
1570
  background-color: #0D47A1;
1458
1571
  }
1572
+ QPushButton:focus {
1573
+ outline: none;
1574
+ }
1459
1575
  """)
1460
1576
  send_btn.clicked.connect(self._send_chat_message)
1461
1577
  input_layout.addWidget(send_btn)
@@ -3217,9 +3333,17 @@ If the text refers to figures (e.g., 'Figure 1A'), relevant images may be provid
3217
3333
  PROJECT CONTEXT:
3218
3334
  {context}
3219
3335
 
3220
- Create a translation prompt. Output ONE complete ACTION block:
3336
+ **CRITICAL INSTRUCTIONS:**
3337
+ 1. You MUST output exactly ONE ACTION block in the following format
3338
+ 2. The JSON MUST be valid and complete
3339
+ 3. Both "name" and "content" are REQUIRED fields
3340
+
3341
+ **EXACT FORMAT TO USE:**
3221
3342
 
3222
- ACTION:create_prompt PARAMS:{{"name": "[Name]", "content": "[Prompt]", "folder": "Project Prompts", "description": "Auto-generated", "activate": true}}
3343
+ ACTION:create_prompt PARAMS:{{"name": "Your Prompt Name Here", "content": "Your full prompt content here", "folder": "Project Prompts", "description": "Auto-generated prompt", "activate": true}}
3344
+
3345
+ **EXAMPLE:**
3346
+ ACTION:create_prompt PARAMS:{{"name": "Legal Translation EN-NL", "content": "You are an expert legal translator...\\n\\n# TERMINOLOGY\\n| Source | Target |\\n...", "folder": "Project Prompts", "description": "Auto-generated", "activate": true}}
3223
3347
 
3224
3348
  Prompt must include:
3225
3349
 
@@ -3317,6 +3441,18 @@ Output complete ACTION."""
3317
3441
  preview = file['content'][:200].replace('\n', ' ')
3318
3442
  context_parts.append(f" Preview: {preview}...")
3319
3443
 
3444
+ # Translation Memory data (if enabled)
3445
+ if self.include_tm_data:
3446
+ tm_data = self._get_tm_context_data()
3447
+ if tm_data:
3448
+ context_parts.append(f"\n**Translation Memory Matches:**\n{tm_data}")
3449
+
3450
+ # Termbase data (if enabled)
3451
+ if self.include_termbase_data:
3452
+ tb_data = self._get_termbase_context_data()
3453
+ if tb_data:
3454
+ context_parts.append(f"\n**Termbase Entries:**\n{tb_data}")
3455
+
3320
3456
  return "\n".join(context_parts) if context_parts else "No context available"
3321
3457
 
3322
3458
  def _list_available_prompts(self) -> str:
@@ -3334,7 +3470,73 @@ Output complete ACTION."""
3334
3470
  lines.append(f"... and {len(self.library.prompts) - 20} more")
3335
3471
 
3336
3472
  return "\n".join(lines)
3337
-
3473
+
3474
+ def _get_tm_context_data(self) -> str:
3475
+ """Get Translation Memory data for AI context"""
3476
+ try:
3477
+ if not hasattr(self.parent_app, 'tm_databases') or not self.parent_app.tm_databases:
3478
+ return "No translation memories loaded"
3479
+
3480
+ lines = []
3481
+ total_entries = 0
3482
+
3483
+ for tm_name, tm_db in self.parent_app.tm_databases.items():
3484
+ if hasattr(tm_db, 'entries'):
3485
+ count = len(tm_db.entries)
3486
+ total_entries += count
3487
+ lines.append(f"- **{tm_name}**: {count} entries")
3488
+
3489
+ # Show sample entries (first 10)
3490
+ for i, entry in enumerate(list(tm_db.entries.values())[:10]):
3491
+ if hasattr(entry, 'source') and hasattr(entry, 'target'):
3492
+ lines.append(f" {i+1}. {entry.source[:50]}... → {entry.target[:50]}...")
3493
+
3494
+ if not lines:
3495
+ return "Translation memories are empty"
3496
+
3497
+ return f"Total: {total_entries} TM entries\n" + "\n".join(lines)
3498
+
3499
+ except Exception as e:
3500
+ return f"Error loading TM data: {e}"
3501
+
3502
+ def _get_termbase_context_data(self) -> str:
3503
+ """Get Termbase data for AI context"""
3504
+ try:
3505
+ if not hasattr(self.parent_app, 'termbases') or not self.parent_app.termbases:
3506
+ # Try to get termbase entries from the termbase manager
3507
+ if hasattr(self.parent_app, 'termbase_manager'):
3508
+ terms = self.parent_app.termbase_manager.get_all_terms()
3509
+ if terms:
3510
+ lines = [f"Total: {len(terms)} termbase entries\n"]
3511
+ for i, term in enumerate(terms[:50]): # First 50 terms
3512
+ source = term.get('source_term', term.get('source', ''))
3513
+ target = term.get('target_term', term.get('target', ''))
3514
+ if source and target:
3515
+ lines.append(f"| {source} | {target} |")
3516
+ return "\n".join(lines)
3517
+ return "No termbases loaded"
3518
+
3519
+ lines = []
3520
+ total_terms = 0
3521
+
3522
+ for tb_name, tb in self.parent_app.termbases.items():
3523
+ if hasattr(tb, 'terms'):
3524
+ count = len(tb.terms)
3525
+ total_terms += count
3526
+ lines.append(f"- **{tb_name}**: {count} terms")
3527
+
3528
+ # Show sample terms (first 20)
3529
+ for i, (source, target) in enumerate(list(tb.terms.items())[:20]):
3530
+ lines.append(f" | {source} | {target} |")
3531
+
3532
+ if not lines:
3533
+ return "Termbases are empty"
3534
+
3535
+ return f"Total: {total_terms} terms\n" + "\n".join(lines)
3536
+
3537
+ except Exception as e:
3538
+ return f"Error loading termbase data: {e}"
3539
+
3338
3540
  def _attach_file(self):
3339
3541
  """Attach a file to the conversation"""
3340
3542
  file_path, _ = QFileDialog.getOpenFileName(
@@ -240,6 +240,9 @@ class VoiceDictationWidget(QWidget):
240
240
  QPushButton:disabled {
241
241
  background-color: #BDBDBD;
242
242
  }
243
+ QPushButton:focus {
244
+ outline: none;
245
+ }
243
246
  """)
244
247
  self.record_btn.clicked.connect(self.toggle_recording)
245
248
  controls_layout.addWidget(self.record_btn)
@@ -345,6 +348,9 @@ class VoiceDictationWidget(QWidget):
345
348
  QPushButton:hover {
346
349
  background-color: #C62828;
347
350
  }
351
+ QPushButton:focus {
352
+ outline: none;
353
+ }
348
354
  """)
349
355
 
350
356
  # Disable controls
@@ -380,6 +386,9 @@ class VoiceDictationWidget(QWidget):
380
386
  QPushButton:hover {
381
387
  background-color: #1976D2;
382
388
  }
389
+ QPushButton:focus {
390
+ outline: none;
391
+ }
383
392
  """)
384
393
 
385
394
  # Start transcription
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.203
3
+ Version: 1.9.205
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,6 +1,6 @@
1
- Supervertaler.py,sha256=Mgq5ZnZJxaQ-_aSzVZoVQiVYz_kEaxeVq1nfq-6TkNE,2396397
1
+ Supervertaler.py,sha256=Q4pIpw6ZFn6DcIsX7oPk0iyZKRLg2HQFnIvhzZ98MrY,2403906
2
2
  modules/__init__.py,sha256=G58XleS-EJ2sX4Kehm-3N2m618_W2Es0Kg8CW_eBG7g,327
3
- modules/ai_actions.py,sha256=i5MJcM-7Y6CAvKUwxmxrVHeoZAVtAP7aRDdWM5KLkO0,33877
3
+ modules/ai_actions.py,sha256=rU7h1TOkhPk_UaO6sxqj7V3FFUfo5Aj03Q6kSEllXyk,34688
4
4
  modules/ai_attachment_manager.py,sha256=juZlrW3UPkIkcnj0SREgOQkQROLf0fcu3ShZcKXMxsI,11361
5
5
  modules/ai_file_viewer_dialog.py,sha256=lKKqUUlOEVgHmmu6aRxqH7P6ds-7dRLk4ltDyjCwGxs,6246
6
6
  modules/autofingers_engine.py,sha256=eJ7tBi7YJvTToe5hYTfnyGXB-qme_cHrOPZibaoR2Xw,17061
@@ -40,7 +40,7 @@ modules/prompt_assistant.py,sha256=shkZqNTvyQKNDO_9aFEu1_gN0zQq0fR5krXkWfnTR2Y,1
40
40
  modules/prompt_library.py,sha256=t5w4cqB6_Sin4BQDVNALKpfB1EN_oaDeHFwlHxILLSY,26894
41
41
  modules/prompt_library_migration.py,sha256=fv3RHhe2-EnH50XW5tyTWy0YP_KJ2EsESuTxR8klfmI,17639
42
42
  modules/quick_access_sidebar.py,sha256=RPn5ssvYXlitNMWFZN9Yxv7So8u_z5RGNpHN6N-SFDI,10151
43
- modules/quicktrans.py,sha256=jnCwaX8tSwaGlM5BxX2NEz5XqkHu9Pnxd8XBRX3OUFM,26291
43
+ modules/quicktrans.py,sha256=uoEWb3MALfSR2nn07-hpBIsaQookqzcX9vAlKLGtOrE,26371
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
@@ -59,11 +59,11 @@ modules/superlookup.py,sha256=0SnIv-L8xNhAS5JuvoFPdp4BoScgACLFUB1fVwFe36Y,8980
59
59
  modules/tag_cleaner.py,sha256=u7VOchIWzD4sAhFs3X1Vuo3hX6X72zESQ0AGZE83cYc,8703
60
60
  modules/tag_manager.py,sha256=g66S0JSxdguN9AhWzZG3hsIz87Ul51wQ3c2wOCTZVSk,12789
61
61
  modules/term_extractor.py,sha256=qPvKNCVXFTGEGwXNvvC0cfCmdb5c3WhzE38EOgKdKUI,11253
62
- modules/termbase_entry_editor.py,sha256=iWO9CgLjMomGAqBXDsGAX7TFJvDOp2s_taS4gBL1rZY,35818
62
+ modules/termbase_entry_editor.py,sha256=V2IzVar8noU_SDhqkcZLg2NOtyAIhalSa5E6bLGcGUc,36070
63
63
  modules/termbase_import_export.py,sha256=16IAY04IS_rgt0GH5UOUzUI5NoqAli4JMfMquxmFBm0,23552
64
64
  modules/termbase_manager.py,sha256=XAVrz-wt8jKcjoD6ocHoXewY5PN0A0GeqFEctsv0jS8,48697
65
65
  modules/termview_widget.py,sha256=FsNnSWh86PCnmKcC3fFJS8MJNdVvRpgE5e8-u4jAosY,55742
66
- modules/theme_manager.py,sha256=Qk_jfCmfm7fjdMAOyBHpD18w3MiRfWBZk0cHTw6yAAg,18639
66
+ modules/theme_manager.py,sha256=EkiAviQ31ewzhMM0XaP4NkDJ2LZwRg8oXCy7BLQhc0k,18842
67
67
  modules/tm_editor_dialog.py,sha256=AzGwq4QW641uFJdF8DljLTRRp4FLoYX3Pe4rlTjQWNg,3517
68
68
  modules/tm_manager_qt.py,sha256=h2bvXkRuboHf_RRz9-5FX35GVRlpXgRDWeXyj1QWtPs,54406
69
69
  modules/tm_metadata_manager.py,sha256=NTsaI_YjQnVOpU_scAwK9uR1Tcl9pzKD1GwLVy7sx2g,23590
@@ -73,16 +73,16 @@ modules/tmx_generator.py,sha256=pNkxwdMLvSRMMru0lkB1gvViIpg9BQy1EVhRbwoef3k,9426
73
73
  modules/tracked_changes.py,sha256=S_BIEC6r7wVAwjG42aSy_RgH4KaMAC8GS5thEvqrYdE,39480
74
74
  modules/trados_docx_handler.py,sha256=VPRAQ73cUHs_SEj6x81z1PmSxfjnwPBp9P4fXeK3KpQ,16363
75
75
  modules/translation_memory.py,sha256=LnG8csZNL2GTHXT4zk0uecJEtvRc-MKwv7Pt7EX3s7s,28002
76
- modules/translation_results_panel.py,sha256=OWqzV9xmJOi8NGCi3h42nq-qE7-v6WStjQWRsghCVbQ,92044
76
+ modules/translation_results_panel.py,sha256=HbYYjvRNqaJrJNLYLX-YanamMm6aRdLzLjn5rWIfk3U,92198
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=y7xAIM9X7f1afXGE1tOcAc5EY7BKFy53Rgqi3fFzKmQ,173374
79
+ modules/unified_prompt_manager_qt.py,sha256=L96tgYpSLI0ZjD-R8eyxbwNYo_Vi1fyREFYriAR-Vlo,181992
80
80
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
81
- modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
81
+ modules/voice_dictation.py,sha256=7TlTTILSW4KwpTi1rDeKfvTwd5z3_XtNSzNIo35VAWU,16617
82
82
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
83
- supervertaler-1.9.203.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
- supervertaler-1.9.203.dist-info/METADATA,sha256=Az4lNNikNxVVE-gTtXSSNrTM1F_aG4x1HhqjRtnbq0E,5725
85
- supervertaler-1.9.203.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
- supervertaler-1.9.203.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
- supervertaler-1.9.203.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
- supervertaler-1.9.203.dist-info/RECORD,,
83
+ supervertaler-1.9.205.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
+ supervertaler-1.9.205.dist-info/METADATA,sha256=hX2ysmA8ur00DXX7Ua21H9t4Ct620QB1vAH7iwqVILs,5725
85
+ supervertaler-1.9.205.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
+ supervertaler-1.9.205.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
+ supervertaler-1.9.205.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
+ supervertaler-1.9.205.dist-info/RECORD,,