supervertaler 1.9.167__py3-none-any.whl → 1.9.168__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Supervertaler.py CHANGED
@@ -34,7 +34,7 @@ License: MIT
34
34
  """
35
35
 
36
36
  # Version Information.
37
- __version__ = "1.9.167"
37
+ __version__ = "1.9.168"
38
38
  __phase__ = "0.9"
39
39
  __release_date__ = "2026-01-27"
40
40
  __edition__ = "Qt"
@@ -2286,12 +2286,13 @@ class ReadOnlyGridTextEditor(QTextEdit):
2286
2286
 
2287
2287
 
2288
2288
  class TagHighlighter(QSyntaxHighlighter):
2289
- """Syntax highlighter for HTML/XML tags, CAT tool tags, CafeTran pipe symbols, and spell checking in text editors"""
2289
+ """Syntax highlighter for HTML/XML tags, CAT tool tags, CafeTran pipe symbols, Markdown syntax, and spell checking in text editors"""
2290
2290
 
2291
2291
  # Class-level reference to spellcheck manager (shared across all instances)
2292
2292
  _spellcheck_manager = None
2293
2293
  _spellcheck_enabled = False
2294
2294
  _is_cafetran_project = False # Only highlight pipe symbols for CafeTran projects
2295
+ _is_markdown_project = False # Highlight Markdown syntax for Markdown imports
2295
2296
 
2296
2297
  def __init__(self, document, tag_color='#7f0001', invisible_char_color='#999999', enable_spellcheck=False):
2297
2298
  super().__init__(document)
@@ -2330,6 +2331,36 @@ class TagHighlighter(QSyntaxHighlighter):
2330
2331
  self.spellcheck_format.setUnderlineColor(QColor('#FF0000'))
2331
2332
  self.spellcheck_format.setUnderlineStyle(QTextCharFormat.UnderlineStyle.WaveUnderline)
2332
2333
 
2334
+ # Markdown syntax formats
2335
+ # Bold/Italic markers: ** __ * _
2336
+ self.md_bold_format = QTextCharFormat()
2337
+ self.md_bold_format.setForeground(QColor('#C71585')) # Medium violet red
2338
+ self.md_bold_format.setFontWeight(700) # Bold to make it stand out
2339
+
2340
+ # Heading markers: # ## ### etc.
2341
+ self.md_heading_format = QTextCharFormat()
2342
+ self.md_heading_format.setForeground(QColor('#0066CC')) # Blue
2343
+ self.md_heading_format.setFontWeight(700)
2344
+
2345
+ # Code markers: ` ```
2346
+ self.md_code_format = QTextCharFormat()
2347
+ self.md_code_format.setForeground(QColor('#D2691E')) # Chocolate/Orange
2348
+ self.md_code_format.setFontWeight(700)
2349
+
2350
+ # Link/Image syntax: []() ![]()
2351
+ self.md_link_format = QTextCharFormat()
2352
+ self.md_link_format.setForeground(QColor('#6A5ACD')) # Slate blue/Purple
2353
+
2354
+ # Blockquote markers: >
2355
+ self.md_quote_format = QTextCharFormat()
2356
+ self.md_quote_format.setForeground(QColor('#228B22')) # Forest green
2357
+ self.md_quote_format.setFontWeight(700)
2358
+
2359
+ # List markers: - * + 1. 2.
2360
+ self.md_list_format = QTextCharFormat()
2361
+ self.md_list_format.setForeground(QColor('#FF6600')) # Orange
2362
+ self.md_list_format.setFontWeight(700)
2363
+
2333
2364
  def set_tag_color(self, color: str):
2334
2365
  """Update tag highlight color"""
2335
2366
  self.tag_color = color
@@ -2393,6 +2424,10 @@ class TagHighlighter(QSyntaxHighlighter):
2393
2424
  if char == '|':
2394
2425
  self.setFormat(i, 1, self.pipe_format)
2395
2426
 
2427
+ # Markdown syntax highlighting - ONLY for Markdown projects
2428
+ if TagHighlighter._is_markdown_project:
2429
+ self._highlight_markdown_syntax(text)
2430
+
2396
2431
  # Spell checking - only for target editors when enabled
2397
2432
  if self._local_spellcheck_enabled and TagHighlighter._spellcheck_enabled and TagHighlighter._spellcheck_manager:
2398
2433
  self._highlight_misspelled_words(text)
@@ -2455,6 +2490,96 @@ class TagHighlighter(QSyntaxHighlighter):
2455
2490
  TagHighlighter._spellcheck_manager._crash_detected = True
2456
2491
  TagHighlighter._spellcheck_manager.enabled = False
2457
2492
 
2493
+ def _highlight_markdown_syntax(self, text):
2494
+ """Highlight Markdown syntax elements to make them visually distinct"""
2495
+ import re
2496
+
2497
+ # 1. Heading markers at start of line: # ## ### #### ##### ######
2498
+ # Only match at start of line (or after only whitespace)
2499
+ heading_pattern = re.compile(r'^(#{1,6})\s', re.MULTILINE)
2500
+ for match in heading_pattern.finditer(text):
2501
+ self.setFormat(match.start(1), len(match.group(1)), self.md_heading_format)
2502
+
2503
+ # 2. Bold markers: **text** or __text__
2504
+ # Highlight just the markers, not the content
2505
+ bold_pattern = re.compile(r'(\*\*|__)(?=\S)(.+?)(?<=\S)\1')
2506
+ for match in bold_pattern.finditer(text):
2507
+ # Opening marker
2508
+ self.setFormat(match.start(), 2, self.md_bold_format)
2509
+ # Closing marker
2510
+ self.setFormat(match.end() - 2, 2, self.md_bold_format)
2511
+
2512
+ # 3. Italic markers: *text* or _text_ (but not ** or __)
2513
+ # Must not be preceded/followed by same character
2514
+ italic_pattern = re.compile(r'(?<!\*)\*(?!\*)(?=\S)(.+?)(?<=\S)\*(?!\*)|(?<!_)_(?!_)(?=\S)(.+?)(?<=\S)_(?!_)')
2515
+ for match in italic_pattern.finditer(text):
2516
+ # Opening marker (1 char)
2517
+ self.setFormat(match.start(), 1, self.md_bold_format)
2518
+ # Closing marker (1 char)
2519
+ self.setFormat(match.end() - 1, 1, self.md_bold_format)
2520
+
2521
+ # 4. Inline code: `code`
2522
+ code_inline_pattern = re.compile(r'(`+)([^`]+)\1')
2523
+ for match in code_inline_pattern.finditer(text):
2524
+ # Highlight entire code span including backticks
2525
+ self.setFormat(match.start(), len(match.group(0)), self.md_code_format)
2526
+
2527
+ # 5. Code fence markers: ``` or ~~~
2528
+ code_fence_pattern = re.compile(r'^(`{3,}|~{3,}).*$', re.MULTILINE)
2529
+ for match in code_fence_pattern.finditer(text):
2530
+ self.setFormat(match.start(), len(match.group(0)), self.md_code_format)
2531
+
2532
+ # 6. Links: [text](url) - highlight brackets and parens, not the text
2533
+ link_pattern = re.compile(r'\[([^\]]+)\]\(([^\)]+)\)')
2534
+ for match in link_pattern.finditer(text):
2535
+ full_start = match.start()
2536
+ text_part = match.group(1)
2537
+ url_part = match.group(2)
2538
+ # [ bracket
2539
+ self.setFormat(full_start, 1, self.md_link_format)
2540
+ # ] bracket
2541
+ self.setFormat(full_start + 1 + len(text_part), 1, self.md_link_format)
2542
+ # ( and url and )
2543
+ url_start = full_start + 1 + len(text_part) + 1
2544
+ self.setFormat(url_start, len(url_part) + 2, self.md_link_format)
2545
+
2546
+ # 7. Images: ![alt](url) - same as links but with !
2547
+ image_pattern = re.compile(r'!\[([^\]]*)\]\(([^\)]+)\)')
2548
+ for match in image_pattern.finditer(text):
2549
+ full_start = match.start()
2550
+ alt_part = match.group(1)
2551
+ url_part = match.group(2)
2552
+ # ! and [
2553
+ self.setFormat(full_start, 2, self.md_link_format)
2554
+ # ]
2555
+ self.setFormat(full_start + 2 + len(alt_part), 1, self.md_link_format)
2556
+ # ( and url and )
2557
+ url_start = full_start + 2 + len(alt_part) + 1
2558
+ self.setFormat(url_start, len(url_part) + 2, self.md_link_format)
2559
+
2560
+ # 8. Blockquote markers: > at start of line
2561
+ quote_pattern = re.compile(r'^(>+)\s?', re.MULTILINE)
2562
+ for match in quote_pattern.finditer(text):
2563
+ self.setFormat(match.start(1), len(match.group(1)), self.md_quote_format)
2564
+
2565
+ # 9. Unordered list markers: - * + at start of line (with space after)
2566
+ ul_pattern = re.compile(r'^(\s*)([-*+])\s', re.MULTILINE)
2567
+ for match in ul_pattern.finditer(text):
2568
+ marker_start = match.start(2)
2569
+ self.setFormat(marker_start, 1, self.md_list_format)
2570
+
2571
+ # 10. Ordered list markers: 1. 2. 3. etc. at start of line
2572
+ ol_pattern = re.compile(r'^(\s*)(\d+\.)\s', re.MULTILINE)
2573
+ for match in ol_pattern.finditer(text):
2574
+ marker_start = match.start(2)
2575
+ marker_len = len(match.group(2))
2576
+ self.setFormat(marker_start, marker_len, self.md_list_format)
2577
+
2578
+ # 11. Horizontal rules: --- or *** or ___ (3+ chars)
2579
+ hr_pattern = re.compile(r'^([-*_]{3,})\s*$', re.MULTILINE)
2580
+ for match in hr_pattern.finditer(text):
2581
+ self.setFormat(match.start(1), len(match.group(1)), self.md_heading_format)
2582
+
2458
2583
 
2459
2584
  class EditableGridTextEditor(QTextEdit):
2460
2585
  """Editable QTextEdit for target cells - allows text selection and editing"""
@@ -7040,7 +7165,7 @@ class SupervertalerQt(QMainWindow):
7040
7165
  import_docx_action.setShortcut("Ctrl+O")
7041
7166
  import_menu.addAction(import_docx_action)
7042
7167
 
7043
- import_txt_action = QAction("Simple &Text File (TXT)...", self)
7168
+ import_txt_action = QAction("&Text / Markdown File (TXT, MD)...", self)
7044
7169
  import_txt_action.triggered.connect(self.import_simple_txt)
7045
7170
  import_menu.addAction(import_txt_action)
7046
7171
 
@@ -22938,6 +23063,10 @@ class SupervertalerQt(QMainWindow):
22938
23063
  segments=segments
22939
23064
  )
22940
23065
 
23066
+ # Reset project-type specific highlighting flags
23067
+ TagHighlighter._is_cafetran_project = False
23068
+ TagHighlighter._is_markdown_project = False
23069
+
22941
23070
  # Set as current project and load into grid
22942
23071
  self.current_project = project
22943
23072
  self.current_document_path = file_path # Store document path
@@ -22990,18 +23119,19 @@ class SupervertalerQt(QMainWindow):
22990
23119
 
22991
23120
  def import_simple_txt(self):
22992
23121
  """
22993
- Import a simple text file where each line is a source segment.
23122
+ Import a simple text file or Markdown file where each line is a source segment.
22994
23123
 
22995
23124
  This is the simplest possible import format:
22996
23125
  - Each line becomes one source segment
22997
23126
  - Empty lines are preserved as empty segments (for structure)
22998
23127
  - After translation, export produces a matching file with translations
23128
+ - Markdown files are imported as-is (syntax preserved for round-trip)
22999
23129
  """
23000
23130
  file_path, _ = QFileDialog.getOpenFileName(
23001
23131
  self,
23002
- "Select Simple Text File",
23132
+ "Select Text or Markdown File",
23003
23133
  "",
23004
- "Text Files (*.txt);;All Files (*.*)"
23134
+ "Text Files (*.txt *.md);;Markdown (*.md);;Plain Text (*.txt);;All Files (*.*)"
23005
23135
  )
23006
23136
 
23007
23137
  if not file_path:
@@ -23009,18 +23139,30 @@ class SupervertalerQt(QMainWindow):
23009
23139
 
23010
23140
  # Show import options dialog
23011
23141
  dialog = QDialog(self)
23012
- dialog.setWindowTitle("Import Simple Text File")
23142
+ dialog.setWindowTitle("Import Text / Markdown File")
23013
23143
  dialog.setMinimumWidth(500)
23014
23144
 
23015
23145
  layout = QVBoxLayout(dialog)
23016
23146
 
23147
+ # Detect if it's a Markdown file
23148
+ is_markdown = file_path.lower().endswith('.md')
23149
+
23017
23150
  # Info message
23018
- info_label = QLabel(
23019
- "📄 <b>Simple Text File Import</b><br><br>"
23020
- "Each line in the file will become one source segment.<br>"
23021
- "After translation, you can export a matching file with translations.<br><br>"
23022
- "Empty lines are preserved to maintain document structure."
23023
- )
23151
+ if is_markdown:
23152
+ info_label = QLabel(
23153
+ "📝 <b>Markdown File Import</b><br><br>"
23154
+ "Each line in the file will become one source segment.<br>"
23155
+ "Markdown syntax (<b>**bold**</b>, <i>*italic*</i>, # headings, etc.) is preserved.<br>"
23156
+ "After translation, export will produce a matching Markdown file.<br><br>"
23157
+ "<b>Tip:</b> Keep Markdown syntax intact when translating!"
23158
+ )
23159
+ else:
23160
+ info_label = QLabel(
23161
+ "📄 <b>Simple Text File Import</b><br><br>"
23162
+ "Each line in the file will become one source segment.<br>"
23163
+ "After translation, you can export a matching file with translations.<br><br>"
23164
+ "Empty lines are preserved to maintain document structure."
23165
+ )
23024
23166
  info_label.setWordWrap(True)
23025
23167
  info_label.setTextFormat(Qt.TextFormat.RichText)
23026
23168
  layout.addWidget(info_label)
@@ -23146,7 +23288,7 @@ class SupervertalerQt(QMainWindow):
23146
23288
 
23147
23289
  # Create new project
23148
23290
  project = Project(
23149
- name=f"TXT Import - {os.path.basename(file_path)}",
23291
+ name=f"{'MD' if is_markdown else 'TXT'} Import - {os.path.basename(file_path)}",
23150
23292
  source_lang=source_lang,
23151
23293
  target_lang=target_lang,
23152
23294
  segments=segments
@@ -23155,6 +23297,10 @@ class SupervertalerQt(QMainWindow):
23155
23297
  # Store original file path for export
23156
23298
  project.original_txt_path = file_path
23157
23299
 
23300
+ # Reset project-type specific highlighting flags
23301
+ TagHighlighter._is_cafetran_project = False
23302
+ TagHighlighter._is_markdown_project = is_markdown
23303
+
23158
23304
  # Set as current project and load into grid
23159
23305
  self.current_project = project
23160
23306
  self.current_document_path = file_path
@@ -23168,7 +23314,10 @@ class SupervertalerQt(QMainWindow):
23168
23314
 
23169
23315
  # Update status
23170
23316
  empty_count = sum(1 for seg in segments if not seg.source.strip())
23171
- self.log(f" Loaded {len(segments)} lines from text file")
23317
+ file_type = "Markdown file" if is_markdown else "text file"
23318
+ self.log(f"✓ Loaded {len(segments)} lines from {file_type}")
23319
+ if is_markdown:
23320
+ self.log(f" 📝 Markdown syntax highlighting enabled")
23172
23321
  if empty_count > 0:
23173
23322
  self.log(f" ({empty_count} empty lines preserved for structure)")
23174
23323
  self.log(f"📍 Project language pair: {source_lang.upper()} → {target_lang.upper()}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.167
3
+ Version: 1.9.168
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
@@ -71,7 +71,7 @@ Dynamic: home-page
71
71
  Dynamic: license-file
72
72
  Dynamic: requires-python
73
73
 
74
- # 🚀 Supervertaler v1.9.167
74
+ # 🚀 Supervertaler v1.9.168
75
75
 
76
76
  [![PyPI version](https://badge.fury.io/py/supervertaler.svg)](https://pypi.org/project/Supervertaler/)
77
77
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
@@ -79,9 +79,13 @@ Dynamic: requires-python
79
79
 
80
80
  AI-enhanced CAT tool with multi-LLM support (GPT-4, Claude, Gemini, Ollama), innovative Superlookup concordance system offering access to multiple terminology sources (TMs, glossaries, web resources, etc.), and seamless CAT tool integration (memoQ, Trados, CafeTran, Phrase).
81
81
 
82
- **Current Version:** v1.9.167 (January 27, 2026)
82
+ **Current Version:** v1.9.168 (January 27, 2026)
83
83
 
84
- ### NEW in v1.9.167 - 🐛 Keyboard Shortcuts Panel Fix
84
+ ### NEW in v1.9.168 - 📝 Markdown File Import with Syntax Highlighting
85
+
86
+ Import Markdown files (`.md`) with full syntax highlighting! Headings, bold/italic markers, code blocks, links, images, blockquotes, and lists are all highlighted with distinctive colors. ([#127](https://github.com/michaelbeijer/Supervertaler/issues/127))
87
+
88
+ ### Previously in v1.9.167 - 🐛 Keyboard Shortcuts Panel Fix
85
89
 
86
90
  Fixed bug where UI text (Action, Shortcut, Status columns) would disappear after changing a shortcut. ([#125](https://github.com/michaelbeijer/Supervertaler/issues/125))
87
91
 
@@ -1,4 +1,4 @@
1
- Supervertaler.py,sha256=aha64tOrEuVJuveqpseMcne0X8CSnjZrx0DVWtg1AEk,2269768
1
+ Supervertaler.py,sha256=JdHrpxldUpOF2liVAhkUk3rUVZnYikNEP2aoZo-LnnU,2277262
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
@@ -77,9 +77,9 @@ modules/unified_prompt_manager_qt.py,sha256=fyF3_r0N8hnImT-CcWo1AuBOQ1Dn_ExeeUCk
77
77
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
78
78
  modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
79
79
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
80
- supervertaler-1.9.167.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
- supervertaler-1.9.167.dist-info/METADATA,sha256=BTNq1XZ9mVkAOEgT4UPD82yoKFFbPPm5QER_XcXxh5s,46505
82
- supervertaler-1.9.167.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
83
- supervertaler-1.9.167.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
- supervertaler-1.9.167.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
- supervertaler-1.9.167.dist-info/RECORD,,
80
+ supervertaler-1.9.168.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
+ supervertaler-1.9.168.dist-info/METADATA,sha256=af_igrggCUzPzui9KbswdpYiwbL2twwA-UOsAVRuF6o,46847
82
+ supervertaler-1.9.168.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
83
+ supervertaler-1.9.168.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
+ supervertaler-1.9.168.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
+ supervertaler-1.9.168.dist-info/RECORD,,