supervertaler 1.9.172__py3-none-any.whl → 1.9.180__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 +1133 -310
- modules/database_manager.py +243 -83
- modules/database_migrations.py +54 -7
- modules/mqxliff_handler.py +71 -2
- modules/termbase_manager.py +105 -1
- modules/theme_manager.py +41 -4
- modules/tm_metadata_manager.py +23 -18
- modules/translation_memory.py +2 -2
- modules/unified_prompt_library.py +2 -2
- modules/unified_prompt_manager_qt.py +35 -18
- supervertaler-1.9.180.dist-info/METADATA +151 -0
- {supervertaler-1.9.172.dist-info → supervertaler-1.9.180.dist-info}/RECORD +16 -16
- supervertaler-1.9.172.dist-info/METADATA +0 -930
- {supervertaler-1.9.172.dist-info → supervertaler-1.9.180.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.172.dist-info → supervertaler-1.9.180.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.172.dist-info → supervertaler-1.9.180.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.172.dist-info → supervertaler-1.9.180.dist-info}/top_level.txt +0 -0
modules/termbase_manager.py
CHANGED
|
@@ -409,7 +409,111 @@ class TermbaseManager:
|
|
|
409
409
|
except Exception as e:
|
|
410
410
|
self.log(f"✗ Error setting termbase read_only: {e}")
|
|
411
411
|
return False
|
|
412
|
-
|
|
412
|
+
|
|
413
|
+
def get_termbase_ai_inject(self, termbase_id: int) -> bool:
|
|
414
|
+
"""Get whether termbase terms should be injected into LLM prompts"""
|
|
415
|
+
try:
|
|
416
|
+
cursor = self.db_manager.cursor
|
|
417
|
+
cursor.execute("SELECT ai_inject FROM termbases WHERE id = ?", (termbase_id,))
|
|
418
|
+
result = cursor.fetchone()
|
|
419
|
+
return bool(result[0]) if result and result[0] else False
|
|
420
|
+
except Exception as e:
|
|
421
|
+
self.log(f"✗ Error getting termbase ai_inject: {e}")
|
|
422
|
+
return False
|
|
423
|
+
|
|
424
|
+
def set_termbase_ai_inject(self, termbase_id: int, ai_inject: bool) -> bool:
|
|
425
|
+
"""Set whether termbase terms should be injected into LLM prompts"""
|
|
426
|
+
try:
|
|
427
|
+
cursor = self.db_manager.cursor
|
|
428
|
+
cursor.execute("""
|
|
429
|
+
UPDATE termbases SET ai_inject = ? WHERE id = ?
|
|
430
|
+
""", (1 if ai_inject else 0, termbase_id))
|
|
431
|
+
self.db_manager.connection.commit()
|
|
432
|
+
status = "enabled" if ai_inject else "disabled"
|
|
433
|
+
self.log(f"✓ AI injection {status} for termbase {termbase_id}")
|
|
434
|
+
return True
|
|
435
|
+
except Exception as e:
|
|
436
|
+
self.log(f"✗ Error setting termbase ai_inject: {e}")
|
|
437
|
+
return False
|
|
438
|
+
|
|
439
|
+
def get_ai_inject_termbases(self, project_id: Optional[int] = None) -> List[Dict]:
|
|
440
|
+
"""
|
|
441
|
+
Get all termbases with ai_inject enabled that are active for the given project.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
project_id: Project ID (0 or None for global)
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
List of termbase dictionaries with all terms
|
|
448
|
+
"""
|
|
449
|
+
try:
|
|
450
|
+
cursor = self.db_manager.cursor
|
|
451
|
+
proj_id = project_id if project_id else 0
|
|
452
|
+
|
|
453
|
+
cursor.execute("""
|
|
454
|
+
SELECT t.id, t.name, t.source_lang, t.target_lang
|
|
455
|
+
FROM termbases t
|
|
456
|
+
LEFT JOIN termbase_activation ta ON t.id = ta.termbase_id AND ta.project_id = ?
|
|
457
|
+
WHERE t.ai_inject = 1
|
|
458
|
+
AND (ta.is_active = 1 OR (t.is_global = 1 AND ta.is_active IS NULL))
|
|
459
|
+
ORDER BY ta.priority ASC, t.name ASC
|
|
460
|
+
""", (proj_id,))
|
|
461
|
+
|
|
462
|
+
termbases = []
|
|
463
|
+
for row in cursor.fetchall():
|
|
464
|
+
termbases.append({
|
|
465
|
+
'id': row[0],
|
|
466
|
+
'name': row[1],
|
|
467
|
+
'source_lang': row[2],
|
|
468
|
+
'target_lang': row[3]
|
|
469
|
+
})
|
|
470
|
+
return termbases
|
|
471
|
+
except Exception as e:
|
|
472
|
+
self.log(f"✗ Error getting AI inject termbases: {e}")
|
|
473
|
+
return []
|
|
474
|
+
|
|
475
|
+
def get_ai_inject_terms(self, project_id: Optional[int] = None) -> List[Dict]:
|
|
476
|
+
"""
|
|
477
|
+
Get all terms from AI-inject-enabled termbases for the given project.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
project_id: Project ID (0 or None for global)
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
List of term dictionaries with source_term, target_term, forbidden, termbase_name
|
|
484
|
+
"""
|
|
485
|
+
try:
|
|
486
|
+
# First get all AI-inject termbases
|
|
487
|
+
ai_termbases = self.get_ai_inject_termbases(project_id)
|
|
488
|
+
if not ai_termbases:
|
|
489
|
+
return []
|
|
490
|
+
|
|
491
|
+
all_terms = []
|
|
492
|
+
cursor = self.db_manager.cursor
|
|
493
|
+
|
|
494
|
+
for tb in ai_termbases:
|
|
495
|
+
cursor.execute("""
|
|
496
|
+
SELECT source_term, target_term, forbidden, priority
|
|
497
|
+
FROM termbase_terms
|
|
498
|
+
WHERE termbase_id = ?
|
|
499
|
+
ORDER BY priority ASC, source_term ASC
|
|
500
|
+
""", (tb['id'],))
|
|
501
|
+
|
|
502
|
+
for row in cursor.fetchall():
|
|
503
|
+
all_terms.append({
|
|
504
|
+
'source_term': row[0],
|
|
505
|
+
'target_term': row[1],
|
|
506
|
+
'forbidden': bool(row[2]) if row[2] else False,
|
|
507
|
+
'priority': row[3] or 99,
|
|
508
|
+
'termbase_name': tb['name']
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
self.log(f"📚 Retrieved {len(all_terms)} terms from {len(ai_termbases)} AI-inject glossar{'y' if len(ai_termbases) == 1 else 'ies'}")
|
|
512
|
+
return all_terms
|
|
513
|
+
except Exception as e:
|
|
514
|
+
self.log(f"✗ Error getting AI inject terms: {e}")
|
|
515
|
+
return []
|
|
516
|
+
|
|
413
517
|
def set_termbase_priority(self, termbase_id: int, project_id: int, priority: int) -> bool:
|
|
414
518
|
"""
|
|
415
519
|
Set manual priority for a termbase in a specific project.
|
modules/theme_manager.py
CHANGED
|
@@ -212,7 +212,10 @@ class ThemeManager:
|
|
|
212
212
|
self.themes_file = user_data_path / "themes.json"
|
|
213
213
|
self.current_theme: Theme = self.PREDEFINED_THEMES["Light (Default)"]
|
|
214
214
|
self.custom_themes: Dict[str, Theme] = {}
|
|
215
|
-
|
|
215
|
+
|
|
216
|
+
# Global UI font scale (50-200%, default 100%)
|
|
217
|
+
self.font_scale: int = 100
|
|
218
|
+
|
|
216
219
|
# Load custom themes
|
|
217
220
|
self.load_custom_themes()
|
|
218
221
|
|
|
@@ -289,14 +292,48 @@ class ThemeManager:
|
|
|
289
292
|
def apply_theme(self, app: QApplication):
|
|
290
293
|
"""
|
|
291
294
|
Apply current theme to application
|
|
292
|
-
|
|
295
|
+
|
|
293
296
|
Args:
|
|
294
297
|
app: QApplication instance
|
|
295
298
|
"""
|
|
296
299
|
theme = self.current_theme
|
|
297
|
-
|
|
300
|
+
|
|
301
|
+
# Calculate scaled font sizes based on font_scale (default 100%)
|
|
302
|
+
base_font_size = int(10 * self.font_scale / 100) # Base: 10pt at 100%
|
|
303
|
+
small_font_size = max(7, int(9 * self.font_scale / 100)) # Small text (status bar)
|
|
304
|
+
|
|
305
|
+
# Font scaling rules (only applied if scale != 100%)
|
|
306
|
+
font_rules = ""
|
|
307
|
+
if self.font_scale != 100:
|
|
308
|
+
font_rules = f"""
|
|
309
|
+
/* Global font scaling ({self.font_scale}%) */
|
|
310
|
+
QWidget {{ font-size: {base_font_size}pt; }}
|
|
311
|
+
QMenuBar {{ font-size: {base_font_size}pt; }}
|
|
312
|
+
QMenuBar::item {{ font-size: {base_font_size}pt; }}
|
|
313
|
+
QMenu {{ font-size: {base_font_size}pt; }}
|
|
314
|
+
QMenu::item {{ font-size: {base_font_size}pt; }}
|
|
315
|
+
QStatusBar {{ font-size: {small_font_size}pt; }}
|
|
316
|
+
QTabBar::tab {{ font-size: {base_font_size}pt; }}
|
|
317
|
+
QToolBar {{ font-size: {base_font_size}pt; }}
|
|
318
|
+
QLabel {{ font-size: {base_font_size}pt; }}
|
|
319
|
+
QCheckBox {{ font-size: {base_font_size}pt; }}
|
|
320
|
+
QRadioButton {{ font-size: {base_font_size}pt; }}
|
|
321
|
+
QComboBox {{ font-size: {base_font_size}pt; }}
|
|
322
|
+
QSpinBox {{ font-size: {base_font_size}pt; }}
|
|
323
|
+
QDoubleSpinBox {{ font-size: {base_font_size}pt; }}
|
|
324
|
+
QLineEdit {{ font-size: {base_font_size}pt; }}
|
|
325
|
+
QPushButton {{ font-size: {base_font_size}pt; }}
|
|
326
|
+
QGroupBox {{ font-size: {base_font_size}pt; }}
|
|
327
|
+
QGroupBox::title {{ font-size: {base_font_size}pt; }}
|
|
328
|
+
QTextEdit {{ font-size: {base_font_size}pt; }}
|
|
329
|
+
QPlainTextEdit {{ font-size: {base_font_size}pt; }}
|
|
330
|
+
QListWidget {{ font-size: {base_font_size}pt; }}
|
|
331
|
+
QTreeWidget {{ font-size: {base_font_size}pt; }}
|
|
332
|
+
QHeaderView::section {{ font-size: {base_font_size}pt; }}
|
|
333
|
+
"""
|
|
334
|
+
|
|
298
335
|
# Create and apply stylesheet - COLORS ONLY, preserves native sizes/spacing
|
|
299
|
-
stylesheet = f"""
|
|
336
|
+
stylesheet = font_rules + f"""
|
|
300
337
|
/* Main window background */
|
|
301
338
|
QMainWindow, QWidget {{
|
|
302
339
|
background-color: {theme.window_bg};
|
modules/tm_metadata_manager.py
CHANGED
|
@@ -344,19 +344,22 @@ class TMMetadataManager:
|
|
|
344
344
|
"""Check if a TM is active for a project (or global when project_id=0)"""
|
|
345
345
|
if project_id is None:
|
|
346
346
|
return False # If None (not 0), default to inactive
|
|
347
|
-
|
|
347
|
+
|
|
348
348
|
try:
|
|
349
349
|
cursor = self.db_manager.cursor
|
|
350
|
-
|
|
350
|
+
|
|
351
|
+
# Check if TM is active for this project OR globally (project_id=0)
|
|
351
352
|
cursor.execute("""
|
|
352
|
-
SELECT is_active FROM tm_activation
|
|
353
|
-
WHERE tm_id = ? AND project_id = ?
|
|
353
|
+
SELECT is_active FROM tm_activation
|
|
354
|
+
WHERE tm_id = ? AND (project_id = ? OR project_id = 0)
|
|
355
|
+
ORDER BY project_id DESC
|
|
354
356
|
""", (tm_db_id, project_id))
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
|
|
358
|
+
# Return True if any activation is active (project-specific takes priority due to ORDER BY)
|
|
359
|
+
for row in cursor.fetchall():
|
|
360
|
+
if bool(row[0]):
|
|
361
|
+
return True
|
|
362
|
+
|
|
360
363
|
# If no activation record exists, TM is inactive by default
|
|
361
364
|
return False
|
|
362
365
|
except Exception as e:
|
|
@@ -382,15 +385,16 @@ class TMMetadataManager:
|
|
|
382
385
|
|
|
383
386
|
try:
|
|
384
387
|
cursor = self.db_manager.cursor
|
|
385
|
-
|
|
388
|
+
|
|
386
389
|
# Only return TMs that have been explicitly activated (is_active = 1)
|
|
390
|
+
# Include both project-specific activations AND global activations (project_id=0)
|
|
387
391
|
cursor.execute("""
|
|
388
|
-
SELECT tm.tm_id
|
|
392
|
+
SELECT DISTINCT tm.tm_id
|
|
389
393
|
FROM translation_memories tm
|
|
390
394
|
INNER JOIN tm_activation ta ON tm.id = ta.tm_id
|
|
391
|
-
WHERE ta.project_id = ? AND ta.is_active = 1
|
|
395
|
+
WHERE (ta.project_id = ? OR ta.project_id = 0) AND ta.is_active = 1
|
|
392
396
|
""", (project_id,))
|
|
393
|
-
|
|
397
|
+
|
|
394
398
|
return [row[0] for row in cursor.fetchall()]
|
|
395
399
|
except Exception as e:
|
|
396
400
|
self.log(f"✗ Error fetching active tm_ids: {e}")
|
|
@@ -422,16 +426,17 @@ class TMMetadataManager:
|
|
|
422
426
|
|
|
423
427
|
try:
|
|
424
428
|
cursor = self.db_manager.cursor
|
|
425
|
-
|
|
429
|
+
|
|
426
430
|
# Return TMs where Write checkbox is enabled (read_only = 0)
|
|
427
|
-
# AND the TM has an activation record for this project
|
|
431
|
+
# AND the TM has an activation record for this project OR for global (project_id=0)
|
|
432
|
+
# This ensures TMs created when no project was loaded still work
|
|
428
433
|
cursor.execute("""
|
|
429
|
-
SELECT tm.tm_id
|
|
434
|
+
SELECT DISTINCT tm.tm_id
|
|
430
435
|
FROM translation_memories tm
|
|
431
436
|
INNER JOIN tm_activation ta ON tm.id = ta.tm_id
|
|
432
|
-
WHERE ta.project_id = ? AND tm.read_only = 0
|
|
437
|
+
WHERE (ta.project_id = ? OR ta.project_id = 0) AND tm.read_only = 0
|
|
433
438
|
""", (project_id,))
|
|
434
|
-
|
|
439
|
+
|
|
435
440
|
return [row[0] for row in cursor.fetchall()]
|
|
436
441
|
except Exception as e:
|
|
437
442
|
self.log(f"✗ Error fetching writable tm_ids: {e}")
|
modules/translation_memory.py
CHANGED
|
@@ -123,8 +123,8 @@ class TMDatabase:
|
|
|
123
123
|
if source_lang and target_lang:
|
|
124
124
|
self.set_tm_languages(source_lang, target_lang)
|
|
125
125
|
|
|
126
|
-
# Global fuzzy threshold (
|
|
127
|
-
self.fuzzy_threshold = 0.
|
|
126
|
+
# Global fuzzy threshold (75% minimum similarity for fuzzy matches)
|
|
127
|
+
self.fuzzy_threshold = 0.75
|
|
128
128
|
|
|
129
129
|
# TM metadata cache (populated from database as needed)
|
|
130
130
|
# Note: Legacy 'project' and 'big_mama' TMs are no longer used.
|
|
@@ -367,7 +367,7 @@ class UnifiedPromptLibrary:
|
|
|
367
367
|
|
|
368
368
|
self.active_primary_prompt = self.prompts[relative_path]['content']
|
|
369
369
|
self.active_primary_prompt_path = relative_path
|
|
370
|
-
self.log(f"✓ Set
|
|
370
|
+
self.log(f"✓ Set custom prompt: {self.prompts[relative_path].get('name', relative_path)}")
|
|
371
371
|
return True
|
|
372
372
|
|
|
373
373
|
def set_external_primary_prompt(self, file_path: str) -> Tuple[bool, str]:
|
|
@@ -399,7 +399,7 @@ class UnifiedPromptLibrary:
|
|
|
399
399
|
self.active_primary_prompt = content
|
|
400
400
|
self.active_primary_prompt_path = f"[EXTERNAL] {file_path}"
|
|
401
401
|
|
|
402
|
-
self.log(f"✓ Set external
|
|
402
|
+
self.log(f"✓ Set external custom prompt: {display_name}")
|
|
403
403
|
return True, display_name
|
|
404
404
|
|
|
405
405
|
def attach_prompt(self, relative_path: str) -> bool:
|
|
@@ -1566,9 +1566,9 @@ class UnifiedPromptManagerQt:
|
|
|
1566
1566
|
|
|
1567
1567
|
layout.addWidget(mode_frame)
|
|
1568
1568
|
|
|
1569
|
-
#
|
|
1569
|
+
# Custom Prompt
|
|
1570
1570
|
primary_layout = QHBoxLayout()
|
|
1571
|
-
primary_label = QLabel("
|
|
1571
|
+
primary_label = QLabel("Custom Prompt ⭐:")
|
|
1572
1572
|
primary_label.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
|
|
1573
1573
|
primary_layout.addWidget(primary_label)
|
|
1574
1574
|
|
|
@@ -2210,8 +2210,8 @@ class UnifiedPromptManagerQt:
|
|
|
2210
2210
|
if data['type'] == 'prompt':
|
|
2211
2211
|
path = data['path']
|
|
2212
2212
|
|
|
2213
|
-
# Set as
|
|
2214
|
-
action_primary = menu.addAction("⭐ Set as
|
|
2213
|
+
# Set as custom prompt
|
|
2214
|
+
action_primary = menu.addAction("⭐ Set as Custom Prompt")
|
|
2215
2215
|
action_primary.triggered.connect(lambda: self._set_primary_prompt(path))
|
|
2216
2216
|
|
|
2217
2217
|
# Attach/detach
|
|
@@ -2493,7 +2493,7 @@ class UnifiedPromptManagerQt:
|
|
|
2493
2493
|
self.library.active_primary_prompt_path = None
|
|
2494
2494
|
self.primary_prompt_label.setText("[None selected]")
|
|
2495
2495
|
self.primary_prompt_label.setStyleSheet("color: #999;")
|
|
2496
|
-
self.log_message("✓ Cleared
|
|
2496
|
+
self.log_message("✓ Cleared custom prompt")
|
|
2497
2497
|
|
|
2498
2498
|
def _load_external_primary_prompt(self):
|
|
2499
2499
|
"""Load an external prompt file (not in library) as primary"""
|
|
@@ -2787,7 +2787,7 @@ class UnifiedPromptManagerQt:
|
|
|
2787
2787
|
composition_parts.append(f"📏 Total prompt length: {len(combined):,} characters")
|
|
2788
2788
|
|
|
2789
2789
|
if self.library.active_primary_prompt:
|
|
2790
|
-
composition_parts.append(f"✓
|
|
2790
|
+
composition_parts.append(f"✓ Custom prompt attached")
|
|
2791
2791
|
|
|
2792
2792
|
if self.library.attached_prompts:
|
|
2793
2793
|
composition_parts.append(f"✓ {len(self.library.attached_prompts)} additional prompt(s) attached")
|
|
@@ -2993,49 +2993,66 @@ If the text refers to figures (e.g., 'Figure 1A'), relevant images may be provid
|
|
|
2993
2993
|
|
|
2994
2994
|
# === Prompt Composition (for translation) ===
|
|
2995
2995
|
|
|
2996
|
-
def build_final_prompt(self, source_text: str, source_lang: str, target_lang: str,
|
|
2996
|
+
def build_final_prompt(self, source_text: str, source_lang: str, target_lang: str,
|
|
2997
|
+
mode: str = None, glossary_terms: list = None) -> str:
|
|
2997
2998
|
"""
|
|
2998
2999
|
Build final prompt for translation using 2-layer architecture:
|
|
2999
3000
|
1. System Prompt (auto-selected by mode)
|
|
3000
3001
|
2. Combined prompts from library (primary + attached)
|
|
3001
|
-
|
|
3002
|
+
3. Glossary terms (optional, injected before translation delimiter)
|
|
3003
|
+
|
|
3002
3004
|
Args:
|
|
3003
3005
|
source_text: Text to translate
|
|
3004
3006
|
source_lang: Source language
|
|
3005
3007
|
target_lang: Target language
|
|
3006
3008
|
mode: Override mode (if None, uses self.current_mode)
|
|
3007
|
-
|
|
3009
|
+
glossary_terms: Optional list of term dicts with 'source_term' and 'target_term' keys
|
|
3010
|
+
|
|
3008
3011
|
Returns:
|
|
3009
3012
|
Complete prompt ready for LLM
|
|
3010
3013
|
"""
|
|
3011
3014
|
if mode is None:
|
|
3012
3015
|
mode = self.current_mode
|
|
3013
|
-
|
|
3016
|
+
|
|
3014
3017
|
# Layer 1: System Prompt
|
|
3015
3018
|
system_template = self.get_system_template(mode)
|
|
3016
|
-
|
|
3019
|
+
|
|
3017
3020
|
# Replace placeholders in system prompt
|
|
3018
3021
|
system_template = system_template.replace("{{SOURCE_LANGUAGE}}", source_lang)
|
|
3019
3022
|
system_template = system_template.replace("{{TARGET_LANGUAGE}}", target_lang)
|
|
3020
3023
|
system_template = system_template.replace("{{SOURCE_TEXT}}", source_text)
|
|
3021
|
-
|
|
3024
|
+
|
|
3022
3025
|
# Layer 2: Library prompts (primary + attached)
|
|
3023
3026
|
library_prompts = ""
|
|
3024
|
-
|
|
3027
|
+
|
|
3025
3028
|
if self.library.active_primary_prompt:
|
|
3026
|
-
library_prompts += "\n\n#
|
|
3029
|
+
library_prompts += "\n\n# CUSTOM PROMPT\n\n"
|
|
3027
3030
|
library_prompts += self.library.active_primary_prompt
|
|
3028
|
-
|
|
3031
|
+
|
|
3029
3032
|
for attached_content in self.library.attached_prompts:
|
|
3030
3033
|
library_prompts += "\n\n# ADDITIONAL INSTRUCTIONS\n\n"
|
|
3031
3034
|
library_prompts += attached_content
|
|
3032
|
-
|
|
3035
|
+
|
|
3033
3036
|
# Combine
|
|
3034
3037
|
final_prompt = system_template + library_prompts
|
|
3035
|
-
|
|
3038
|
+
|
|
3039
|
+
# Glossary injection (if terms provided)
|
|
3040
|
+
if glossary_terms:
|
|
3041
|
+
final_prompt += "\n\n# GLOSSARY\n\n"
|
|
3042
|
+
final_prompt += "Use these approved terms in your translation:\n\n"
|
|
3043
|
+
for term in glossary_terms:
|
|
3044
|
+
source_term = term.get('source_term', '')
|
|
3045
|
+
target_term = term.get('target_term', '')
|
|
3046
|
+
if source_term and target_term:
|
|
3047
|
+
# Mark forbidden terms
|
|
3048
|
+
if term.get('forbidden'):
|
|
3049
|
+
final_prompt += f"- {source_term} → ⚠️ DO NOT USE: {target_term}\n"
|
|
3050
|
+
else:
|
|
3051
|
+
final_prompt += f"- {source_term} → {target_term}\n"
|
|
3052
|
+
|
|
3036
3053
|
# Add translation delimiter
|
|
3037
3054
|
final_prompt += "\n\n**YOUR TRANSLATION (provide ONLY the translated text, no numbering or labels):**\n"
|
|
3038
|
-
|
|
3055
|
+
|
|
3039
3056
|
return final_prompt
|
|
3040
3057
|
|
|
3041
3058
|
# ============================================================================
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: supervertaler
|
|
3
|
+
Version: 1.9.180
|
|
4
|
+
Summary: Professional AI-enhanced translation workbench with multi-LLM support, glossary system, TM, spellcheck, voice commands, and PyQt6 interface. Batteries included (core).
|
|
5
|
+
Home-page: https://supervertaler.com
|
|
6
|
+
Author: Michael Beijer
|
|
7
|
+
Author-email: Michael Beijer <info@michaelbeijer.co.uk>
|
|
8
|
+
Maintainer-email: Michael Beijer <info@michaelbeijer.co.uk>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Project-URL: Homepage, https://supervertaler.com
|
|
11
|
+
Project-URL: Repository, https://github.com/michaelbeijer/Supervertaler.git
|
|
12
|
+
Project-URL: Bug Tracker, https://github.com/michaelbeijer/Supervertaler/issues
|
|
13
|
+
Project-URL: Changelog, https://github.com/michaelbeijer/Supervertaler/blob/main/CHANGELOG.md
|
|
14
|
+
Project-URL: Documentation, https://github.com/michaelbeijer/Supervertaler/blob/main/AGENTS.md
|
|
15
|
+
Project-URL: Author Website, https://michaelbeijer.co.uk
|
|
16
|
+
Keywords: translation,CAT,CAT-tool,AI,LLM,GPT,Claude,Gemini,Ollama,glossary,termbase,translation-memory,TM,PyQt6,localization,memoQ,Trados,SDLPPX,XLIFF,voice-commands,spellcheck
|
|
17
|
+
Classifier: Development Status :: 4 - Beta
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
24
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
25
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
26
|
+
Classifier: Topic :: Office/Business
|
|
27
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
28
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: PyQt6>=6.5.0
|
|
33
|
+
Requires-Dist: PyQt6-WebEngine>=6.5.0
|
|
34
|
+
Requires-Dist: python-docx>=0.8.11
|
|
35
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
36
|
+
Requires-Dist: Pillow>=10.0.0
|
|
37
|
+
Requires-Dist: lxml>=4.9.0
|
|
38
|
+
Requires-Dist: openai>=1.0.0
|
|
39
|
+
Requires-Dist: anthropic>=0.7.0
|
|
40
|
+
Requires-Dist: google-generativeai>=0.3.0
|
|
41
|
+
Requires-Dist: requests>=2.28.0
|
|
42
|
+
Requires-Dist: markitdown>=0.0.1
|
|
43
|
+
Requires-Dist: sacrebleu>=2.3.1
|
|
44
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
45
|
+
Requires-Dist: chardet>=5.0.0
|
|
46
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
47
|
+
Requires-Dist: markdown>=3.4.0
|
|
48
|
+
Requires-Dist: pyspellchecker>=0.7.0
|
|
49
|
+
Requires-Dist: sounddevice>=0.4.6
|
|
50
|
+
Requires-Dist: numpy>=1.24.0
|
|
51
|
+
Requires-Dist: PyMuPDF>=1.23.0
|
|
52
|
+
Requires-Dist: boto3>=1.28.0
|
|
53
|
+
Requires-Dist: deepl>=1.15.0
|
|
54
|
+
Requires-Dist: spylls>=0.1.7
|
|
55
|
+
Requires-Dist: keyboard>=0.13.5; platform_system == "Windows"
|
|
56
|
+
Requires-Dist: ahk>=1.0.0; platform_system == "Windows"
|
|
57
|
+
Requires-Dist: pyautogui>=0.9.54; platform_system == "Windows"
|
|
58
|
+
Requires-Dist: psutil>=5.9.0
|
|
59
|
+
Provides-Extra: local-whisper
|
|
60
|
+
Requires-Dist: openai-whisper>=20230314; extra == "local-whisper"
|
|
61
|
+
Provides-Extra: voice
|
|
62
|
+
Provides-Extra: web
|
|
63
|
+
Provides-Extra: pdf
|
|
64
|
+
Provides-Extra: mt
|
|
65
|
+
Provides-Extra: hunspell
|
|
66
|
+
Provides-Extra: windows
|
|
67
|
+
Provides-Extra: core
|
|
68
|
+
Provides-Extra: all
|
|
69
|
+
Dynamic: author
|
|
70
|
+
Dynamic: home-page
|
|
71
|
+
Dynamic: license-file
|
|
72
|
+
Dynamic: requires-python
|
|
73
|
+
|
|
74
|
+
# Supervertaler
|
|
75
|
+
|
|
76
|
+
[](https://pypi.org/project/Supervertaler/)
|
|
77
|
+
[](https://www.python.org/downloads/)
|
|
78
|
+
[](https://opensource.org/licenses/MIT)
|
|
79
|
+
|
|
80
|
+
**Professional AI-enhanced translation workbench** with multi-LLM support (GPT-4, Claude, Gemini, Ollama), translation memory, glossary management, and seamless CAT tool integration (memoQ, Trados, CafeTran, Phrase, Déjà Vu).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install supervertaler
|
|
88
|
+
supervertaler
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or run from source:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/michaelbeijer/Supervertaler.git
|
|
95
|
+
cd Supervertaler
|
|
96
|
+
pip install -r requirements.txt
|
|
97
|
+
python Supervertaler.py
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Key Features
|
|
103
|
+
|
|
104
|
+
- **Multi-LLM AI Translation** - OpenAI GPT-4/5, Anthropic Claude, Google Gemini, Local Ollama
|
|
105
|
+
- **Translation Memory** - Fuzzy matching TM with TMX import/export
|
|
106
|
+
- **Glossary System** - Priority-based term highlighting with forbidden term marking
|
|
107
|
+
- **Superlookup** - Unified concordance search across TM, glossaries, MT, and web resources
|
|
108
|
+
- **CAT Tool Integration** - memoQ XLIFF, Trados SDLPPX/SDLRPX, CafeTran, Phrase, Déjà Vu X3
|
|
109
|
+
- **Voice Commands** - Hands-free translation with OpenAI Whisper
|
|
110
|
+
- **Document Support** - DOCX, bilingual DOCX, PDF, Markdown, plain text
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Documentation
|
|
115
|
+
|
|
116
|
+
| Resource | Description |
|
|
117
|
+
|----------|-------------|
|
|
118
|
+
| [Online Manual](https://supervertaler.gitbook.io/superdocs/) | Quick start, guides, and troubleshooting |
|
|
119
|
+
| [Changelog](CHANGELOG.md) | Complete version history |
|
|
120
|
+
| [Keyboard Shortcuts](docs/guides/KEYBOARD_SHORTCUTS.md) | Shortcut reference |
|
|
121
|
+
| [FAQ](FAQ.md) | Common questions |
|
|
122
|
+
| [Website](https://supervertaler.com) | Project homepage |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.10+
|
|
129
|
+
- PyQt6
|
|
130
|
+
- Windows, macOS, or Linux
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
- [Report bugs](https://github.com/michaelbeijer/Supervertaler/issues)
|
|
137
|
+
- [Request features](https://github.com/michaelbeijer/Supervertaler/discussions)
|
|
138
|
+
- [Contributing guide](CONTRIBUTING.md)
|
|
139
|
+
- [Stargazers](https://github.com/michaelbeijer/Supervertaler/stargazers) - See who's starred the project
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## About
|
|
144
|
+
|
|
145
|
+
**Supervertaler** is maintained by [Michael Beijer](https://michaelbeijer.co.uk), a professional translator with 30 years of experience in technical and patent translation.
|
|
146
|
+
|
|
147
|
+
**License:** MIT - Free for personal and commercial use.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
**Current Version:** See [CHANGELOG.md](CHANGELOG.md) for the latest release notes.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Supervertaler.py,sha256=
|
|
1
|
+
Supervertaler.py,sha256=D49WGFvjGTFaQ2Tmgl5jInj3qIUwzHdxPCJRv4iTjvY,2324894
|
|
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
|
|
@@ -6,8 +6,8 @@ modules/ai_file_viewer_dialog.py,sha256=lKKqUUlOEVgHmmu6aRxqH7P6ds-7dRLk4ltDyjCw
|
|
|
6
6
|
modules/autofingers_engine.py,sha256=eJ7tBi7YJvTToe5hYTfnyGXB-qme_cHrOPZibaoR2Xw,17061
|
|
7
7
|
modules/cafetran_docx_handler.py,sha256=_F7Jh0WPVaDnMhdxEsVSXuD1fN9r-S_V6i0gr86Pdfc,14076
|
|
8
8
|
modules/config_manager.py,sha256=MkPY3xVFgFDkcwewLREg4BfyKueO0OJkT1cTLxehcjM,17894
|
|
9
|
-
modules/database_manager.py,sha256=
|
|
10
|
-
modules/database_migrations.py,sha256=
|
|
9
|
+
modules/database_manager.py,sha256=yNtaJNAKtICBBSc5iyhIufzDn25k7OqkOuFeojmWuM4,87319
|
|
10
|
+
modules/database_migrations.py,sha256=tndJ4wV_2JBfPggMgO1tQRwdfRFZ9zwvADllCZE9CCk,15663
|
|
11
11
|
modules/dejavurtf_handler.py,sha256=8NZPPYtHga40SZCypHjPoJPmZTvm9rD-eEUUab7mjtg,28156
|
|
12
12
|
modules/document_analyzer.py,sha256=t1rVvqLaTcpQTEja228C7zZnh8dXshK4wA9t1E9aGVk,19524
|
|
13
13
|
modules/docx_handler.py,sha256=jSlZs5tollJnsnIA80buEXLfZBunp_GQ9lCtFZPUnBs,34053
|
|
@@ -28,7 +28,7 @@ modules/llm_superbench_ui.py,sha256=lmzsL8lt0KzFw-z8De1zb49Emnv7f1dZv_DJmoQz0bQ,
|
|
|
28
28
|
modules/local_llm_setup.py,sha256=33y-D_LKzkn2w8ejyjeKaovf_An6xQ98mKISoqe-Qjc,42661
|
|
29
29
|
modules/model_update_dialog.py,sha256=kEg0FuO1N-uj6QY5ZIj-FqdiLQuPuAY48pbuwT0HUGI,13113
|
|
30
30
|
modules/model_version_checker.py,sha256=41g7gcWvyrKPYeobaOGCMZLwAHgQmFwVF8zokodKae8,12741
|
|
31
|
-
modules/mqxliff_handler.py,sha256
|
|
31
|
+
modules/mqxliff_handler.py,sha256=TVtrf7ieGoxfoLxy4v4S7by9YImKypw1EY0wFpZO3Lo,28792
|
|
32
32
|
modules/non_translatables_manager.py,sha256=izorabiX6rSQzuBIvnY67wmu5vd85SbzexXccbmwPs4,27465
|
|
33
33
|
modules/pdf_rescue_Qt.py,sha256=9W_M0Zms4miapQbrqm-viHNCpaW39GL9VaKKFCJxpnE,80479
|
|
34
34
|
modules/pdf_rescue_tkinter.py,sha256=a4R_OUnn7X5O_XMR1roybrdu1aXoGCwwO-mwYB2ZpOg,39606
|
|
@@ -58,28 +58,28 @@ modules/tag_manager.py,sha256=g66S0JSxdguN9AhWzZG3hsIz87Ul51wQ3c2wOCTZVSk,12789
|
|
|
58
58
|
modules/term_extractor.py,sha256=qPvKNCVXFTGEGwXNvvC0cfCmdb5c3WhzE38EOgKdKUI,11253
|
|
59
59
|
modules/termbase_entry_editor.py,sha256=iWO9CgLjMomGAqBXDsGAX7TFJvDOp2s_taS4gBL1rZY,35818
|
|
60
60
|
modules/termbase_import_export.py,sha256=16IAY04IS_rgt0GH5UOUzUI5NoqAli4JMfMquxmFBm0,23552
|
|
61
|
-
modules/termbase_manager.py,sha256
|
|
61
|
+
modules/termbase_manager.py,sha256=7KXEFab6y0o1EmFZwHs3ADklC95udVenxvrmN4XUoj0,48808
|
|
62
62
|
modules/termview_widget.py,sha256=O3ah7g-4Lb_iUctxl9sMyxh8V3A5I5PFxmy9iIH2Kgk,53484
|
|
63
|
-
modules/theme_manager.py,sha256=
|
|
63
|
+
modules/theme_manager.py,sha256=Qk_jfCmfm7fjdMAOyBHpD18w3MiRfWBZk0cHTw6yAAg,18639
|
|
64
64
|
modules/tm_editor_dialog.py,sha256=AzGwq4QW641uFJdF8DljLTRRp4FLoYX3Pe4rlTjQWNg,3517
|
|
65
65
|
modules/tm_manager_qt.py,sha256=h2bvXkRuboHf_RRz9-5FX35GVRlpXgRDWeXyj1QWtPs,54406
|
|
66
|
-
modules/tm_metadata_manager.py,sha256=
|
|
66
|
+
modules/tm_metadata_manager.py,sha256=NTsaI_YjQnVOpU_scAwK9uR1Tcl9pzKD1GwLVy7sx2g,23590
|
|
67
67
|
modules/tmx_editor.py,sha256=n0CtdZI8f1fPRWmCqz5Ysxbnp556Qj-6Y56a-YIz6pY,59239
|
|
68
68
|
modules/tmx_editor_qt.py,sha256=PxBIUw_06PHYTBHsd8hZzVJXW8T0A0ljfz1Wjjsa4yU,117022
|
|
69
69
|
modules/tmx_generator.py,sha256=pNkxwdMLvSRMMru0lkB1gvViIpg9BQy1EVhRbwoef3k,9426
|
|
70
70
|
modules/tracked_changes.py,sha256=S_BIEC6r7wVAwjG42aSy_RgH4KaMAC8GS5thEvqrYdE,39480
|
|
71
71
|
modules/trados_docx_handler.py,sha256=VPRAQ73cUHs_SEj6x81z1PmSxfjnwPBp9P4fXeK3KpQ,16363
|
|
72
|
-
modules/translation_memory.py,sha256=
|
|
72
|
+
modules/translation_memory.py,sha256=13PDK4_kgYrWTACWBIBypOh2DvoxY9cRT8U6ulilbh4,28739
|
|
73
73
|
modules/translation_results_panel.py,sha256=DmEe0pZRSfcZFg2cWeEREK7H9vrTcPkgeuMW54Pgrys,92505
|
|
74
74
|
modules/translation_services.py,sha256=lyVpWuZK1wtVtYZMDMdLoq1DHBoSaeAnp-Yejb0TlVQ,10530
|
|
75
|
-
modules/unified_prompt_library.py,sha256=
|
|
76
|
-
modules/unified_prompt_manager_qt.py,sha256=
|
|
75
|
+
modules/unified_prompt_library.py,sha256=96u4WlMwnmmhD4uNJHZ-qVQj8v9_8dA2AVCWpBcwTrg,26006
|
|
76
|
+
modules/unified_prompt_manager_qt.py,sha256=U89UFGG-M7BLetoaLAlma0x-n8SIyx682DhSvaRnzJs,171285
|
|
77
77
|
modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
|
|
78
78
|
modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
|
|
79
79
|
modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
|
|
80
|
-
supervertaler-1.9.
|
|
81
|
-
supervertaler-1.9.
|
|
82
|
-
supervertaler-1.9.
|
|
83
|
-
supervertaler-1.9.
|
|
84
|
-
supervertaler-1.9.
|
|
85
|
-
supervertaler-1.9.
|
|
80
|
+
supervertaler-1.9.180.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
|
|
81
|
+
supervertaler-1.9.180.dist-info/METADATA,sha256=_RNcZZvz-EN-Kay2HuiR1hNQhRMVjPHZwNAjvnZ5t20,5725
|
|
82
|
+
supervertaler-1.9.180.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
83
|
+
supervertaler-1.9.180.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
|
|
84
|
+
supervertaler-1.9.180.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
|
|
85
|
+
supervertaler-1.9.180.dist-info/RECORD,,
|