supervertaler 1.9.146__py3-none-any.whl → 1.9.149__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 +625 -104
- modules/ai_attachment_manager.py +3 -3
- modules/config_manager.py +10 -10
- modules/database_manager.py +107 -10
- modules/non_translatables_manager.py +1 -1
- modules/prompt_library_migration.py +1 -1
- modules/setup_wizard.py +8 -8
- modules/superbrowser.py +16 -12
- modules/superlookup.py +6 -2
- modules/tmx_editor_qt.py +1 -1
- modules/translation_memory.py +52 -7
- modules/unified_prompt_library.py +1 -1
- modules/unified_prompt_manager_qt.py +11 -30
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/METADATA +27 -7
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/RECORD +19 -19
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.146.dist-info → supervertaler-1.9.149.dist-info}/top_level.txt +0 -0
Supervertaler.py
CHANGED
|
@@ -13,7 +13,7 @@ For the classic tkinter edition, see Supervertaler_tkinter.py
|
|
|
13
13
|
Key Features:
|
|
14
14
|
- Complete Translation Matching: Termbase + TM + MT + Multi-LLM
|
|
15
15
|
- Project Termbases: Dedicated terminology per project with automatic extraction
|
|
16
|
-
- Supervoice: AI-
|
|
16
|
+
- Supervoice: AI-enhanced voice dictation (100+ languages via OpenAI Whisper)
|
|
17
17
|
- Superimage: Extract images from DOCX files with preview
|
|
18
18
|
- Google Cloud Translation API integration
|
|
19
19
|
- Multi-LLM Support: OpenAI GPT, Claude, Google Gemini
|
|
@@ -34,9 +34,9 @@ License: MIT
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
# Version Information.
|
|
37
|
-
__version__ = "1.9.
|
|
37
|
+
__version__ = "1.9.149-beta"
|
|
38
38
|
__phase__ = "0.9"
|
|
39
|
-
__release_date__ = "2026-01-
|
|
39
|
+
__release_date__ = "2026-01-21"
|
|
40
40
|
__edition__ = "Qt"
|
|
41
41
|
|
|
42
42
|
import sys
|
|
@@ -61,21 +61,148 @@ def get_resource_path(relative_path: str) -> Path:
|
|
|
61
61
|
return base_path / relative_path
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def get_config_pointer_path() -> Path:
|
|
65
|
+
"""
|
|
66
|
+
Get path to the config pointer file that stores the user's chosen data location.
|
|
67
|
+
|
|
68
|
+
This is stored in a standard config location:
|
|
69
|
+
- Windows: %APPDATA%/Supervertaler/config.json
|
|
70
|
+
- macOS: ~/Library/Application Support/Supervertaler/config.json
|
|
71
|
+
- Linux: ~/.config/Supervertaler/config.json
|
|
72
|
+
|
|
73
|
+
This small file just contains a pointer to where the real data lives.
|
|
74
|
+
"""
|
|
75
|
+
if sys.platform == 'win32':
|
|
76
|
+
# Windows: %APPDATA% (Roaming, syncs across machines on domain)
|
|
77
|
+
appdata = os.environ.get('APPDATA', os.path.expanduser('~'))
|
|
78
|
+
return Path(appdata) / "Supervertaler" / "config.json"
|
|
79
|
+
elif sys.platform == 'darwin':
|
|
80
|
+
# macOS: ~/Library/Application Support/
|
|
81
|
+
return Path.home() / "Library" / "Application Support" / "Supervertaler" / "config.json"
|
|
82
|
+
else:
|
|
83
|
+
# Linux/BSD: ~/.config/ (XDG_CONFIG_HOME)
|
|
84
|
+
xdg_config = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
|
|
85
|
+
return Path(xdg_config) / "Supervertaler" / "config.json"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_default_user_data_path() -> Path:
|
|
89
|
+
"""
|
|
90
|
+
Get the DEFAULT user data path (visible in home folder).
|
|
91
|
+
|
|
92
|
+
- Windows: C:/Users/Username/Supervertaler/
|
|
93
|
+
- macOS: ~/Supervertaler/
|
|
94
|
+
- Linux: ~/Supervertaler/
|
|
95
|
+
|
|
96
|
+
This is intentionally a visible, easily-accessible location.
|
|
97
|
+
"""
|
|
98
|
+
return Path.home() / "Supervertaler"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def save_user_data_path(path: Path) -> None:
|
|
102
|
+
"""Save the user's chosen data path to the config pointer file."""
|
|
103
|
+
pointer_path = get_config_pointer_path()
|
|
104
|
+
pointer_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
config = {"user_data_path": str(path)}
|
|
107
|
+
with open(pointer_path, 'w', encoding='utf-8') as f:
|
|
108
|
+
json.dump(config, f, indent=2)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def load_user_data_path_from_config() -> Optional[Path]:
|
|
112
|
+
"""
|
|
113
|
+
Load the user's chosen data path from config pointer file.
|
|
114
|
+
Returns None if no config exists or path is invalid.
|
|
115
|
+
"""
|
|
116
|
+
pointer_path = get_config_pointer_path()
|
|
117
|
+
|
|
118
|
+
if not pointer_path.exists():
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
with open(pointer_path, 'r', encoding='utf-8') as f:
|
|
123
|
+
config = json.load(f)
|
|
124
|
+
|
|
125
|
+
path_str = config.get('user_data_path')
|
|
126
|
+
if path_str:
|
|
127
|
+
return Path(path_str)
|
|
128
|
+
except (json.JSONDecodeError, OSError):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
64
134
|
def get_user_data_path() -> Path:
|
|
65
135
|
"""
|
|
66
136
|
Get the path to user data directory.
|
|
67
137
|
|
|
68
|
-
|
|
69
|
-
|
|
138
|
+
Resolution order:
|
|
139
|
+
1. Check config pointer file for user's saved preference
|
|
140
|
+
2. If pointer missing, check if default location has data (auto-recover)
|
|
141
|
+
3. If nothing found, return default (first-run dialog will be shown later)
|
|
142
|
+
|
|
143
|
+
All users (EXE, pip, dev) use the same unified system:
|
|
144
|
+
- Default: ~/Supervertaler/ (visible in home folder)
|
|
145
|
+
- User can choose custom location on first run
|
|
146
|
+
- Config pointer remembers their choice
|
|
70
147
|
|
|
71
|
-
|
|
148
|
+
This ensures data survives pip upgrades, EXE updates, and is easy to find/backup.
|
|
72
149
|
"""
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
150
|
+
# 1. Check if user has a saved preference
|
|
151
|
+
saved_path = load_user_data_path_from_config()
|
|
152
|
+
if saved_path and saved_path.exists():
|
|
153
|
+
return saved_path
|
|
154
|
+
|
|
155
|
+
# 2. Check if default location has data (auto-recovery if pointer deleted)
|
|
156
|
+
default_path = get_default_user_data_path()
|
|
157
|
+
if default_path.exists():
|
|
158
|
+
# Check if it has actual content
|
|
159
|
+
has_content = False
|
|
160
|
+
try:
|
|
161
|
+
for item in default_path.iterdir():
|
|
162
|
+
if item.is_file() or (item.is_dir() and any(item.iterdir())):
|
|
163
|
+
has_content = True
|
|
164
|
+
break
|
|
165
|
+
except OSError:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
if has_content:
|
|
169
|
+
# Auto-recover: recreate the pointer file
|
|
170
|
+
save_user_data_path(default_path)
|
|
171
|
+
print(f"[Data Paths] Auto-recovered config pointer to: {default_path}")
|
|
172
|
+
return default_path
|
|
173
|
+
|
|
174
|
+
# 3. Return default path (first-run dialog will be shown by MainWindow)
|
|
175
|
+
# Don't create folder yet - let the dialog handle that
|
|
176
|
+
return default_path
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def needs_first_run_data_dialog() -> bool:
|
|
180
|
+
"""
|
|
181
|
+
Check if we need to show the first-run data location dialog.
|
|
182
|
+
|
|
183
|
+
Returns True if:
|
|
184
|
+
- No config pointer file exists, AND
|
|
185
|
+
- Default data location doesn't have existing content
|
|
186
|
+
|
|
187
|
+
This means it's truly a fresh install.
|
|
188
|
+
"""
|
|
189
|
+
# If config pointer exists, user has already chosen a location
|
|
190
|
+
pointer_path = get_config_pointer_path()
|
|
191
|
+
if pointer_path.exists():
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
# If default location has content, we auto-recovered (no dialog needed)
|
|
195
|
+
default_path = get_default_user_data_path()
|
|
196
|
+
if default_path.exists():
|
|
197
|
+
try:
|
|
198
|
+
for item in default_path.iterdir():
|
|
199
|
+
if item.is_file() or (item.is_dir() and any(item.iterdir())):
|
|
200
|
+
return False # Has content, no dialog needed
|
|
201
|
+
except OSError:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
# Truly a fresh install - need to show dialog
|
|
205
|
+
return True
|
|
79
206
|
|
|
80
207
|
import threading
|
|
81
208
|
import time # For delays in Superlookup
|
|
@@ -5167,35 +5294,48 @@ class SupervertalerQt(QMainWindow):
|
|
|
5167
5294
|
# ============================================================================
|
|
5168
5295
|
# USER DATA PATH INITIALIZATION
|
|
5169
5296
|
# ============================================================================
|
|
5170
|
-
#
|
|
5171
|
-
#
|
|
5297
|
+
# ALL users (EXE, pip, dev) now use a unified system:
|
|
5298
|
+
# - Default location: ~/Supervertaler/ (visible in home folder)
|
|
5299
|
+
# - User can choose custom location on first run
|
|
5300
|
+
# - Config pointer stores their choice at standard config location
|
|
5301
|
+
# - Auto-recovery if pointer is deleted but data exists at default location
|
|
5172
5302
|
from modules.database_manager import DatabaseManager
|
|
5173
5303
|
|
|
5304
|
+
# Check if this is first run BEFORE getting the path
|
|
5305
|
+
# (so we know whether to show the data location dialog later)
|
|
5306
|
+
self._needs_data_location_dialog = needs_first_run_data_dialog()
|
|
5307
|
+
|
|
5174
5308
|
if ENABLE_PRIVATE_FEATURES:
|
|
5175
5309
|
# Developer mode: use private folder (git-ignored)
|
|
5176
5310
|
self.user_data_path = Path(__file__).parent / "user_data_private"
|
|
5311
|
+
self._needs_data_location_dialog = False # Dev mode doesn't need dialog
|
|
5177
5312
|
else:
|
|
5178
|
-
# Normal mode: use
|
|
5313
|
+
# Normal mode: use unified system (same for pip, EXE, etc.)
|
|
5179
5314
|
self.user_data_path = get_user_data_path()
|
|
5180
5315
|
|
|
5181
5316
|
# Ensure user_data directory exists (creates empty folder if missing)
|
|
5182
|
-
|
|
5317
|
+
# BUT only if we're not going to show the dialog (which will create the chosen folder)
|
|
5318
|
+
if not self._needs_data_location_dialog:
|
|
5319
|
+
self.user_data_path.mkdir(parents=True, exist_ok=True)
|
|
5183
5320
|
|
|
5184
5321
|
print(f"[Data Paths] User data: {self.user_data_path}")
|
|
5185
5322
|
|
|
5186
5323
|
# Database Manager for Termbases
|
|
5187
5324
|
self.db_manager = DatabaseManager(
|
|
5188
|
-
db_path=str(self.user_data_path / "
|
|
5325
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5189
5326
|
log_callback=self.log
|
|
5190
5327
|
)
|
|
5191
|
-
|
|
5328
|
+
# Only connect if we're not showing the dialog (which will create the folder)
|
|
5329
|
+
# If dialog is needed, we'll connect after user chooses location
|
|
5330
|
+
if not self._needs_data_location_dialog:
|
|
5331
|
+
self.db_manager.connect()
|
|
5192
5332
|
|
|
5193
5333
|
# TM Database - Initialize early so Superlookup works without a project loaded
|
|
5194
5334
|
from modules.translation_memory import TMDatabase
|
|
5195
5335
|
self.tm_database = TMDatabase(
|
|
5196
5336
|
source_lang=None, # Will be set when project is loaded
|
|
5197
5337
|
target_lang=None, # Will be set when project is loaded
|
|
5198
|
-
db_path=str(self.user_data_path / "
|
|
5338
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5199
5339
|
log_callback=self.log
|
|
5200
5340
|
)
|
|
5201
5341
|
|
|
@@ -5312,11 +5452,184 @@ class SupervertalerQt(QMainWindow):
|
|
|
5312
5452
|
from PyQt6.QtCore import QTimer
|
|
5313
5453
|
QTimer.singleShot(2000, lambda: self._check_for_new_models(force=False)) # 2 second delay
|
|
5314
5454
|
|
|
5315
|
-
# First-run check - show
|
|
5316
|
-
if
|
|
5455
|
+
# First-run check - show data location dialog, then Features tab
|
|
5456
|
+
if self._needs_data_location_dialog:
|
|
5457
|
+
from PyQt6.QtCore import QTimer
|
|
5458
|
+
QTimer.singleShot(300, self._show_data_location_dialog)
|
|
5459
|
+
elif not general_settings.get('first_run_completed', False):
|
|
5317
5460
|
from PyQt6.QtCore import QTimer
|
|
5318
5461
|
QTimer.singleShot(500, self._show_first_run_welcome)
|
|
5319
5462
|
|
|
5463
|
+
def _show_data_location_dialog(self):
|
|
5464
|
+
"""Show dialog to let user choose their data folder location on first run."""
|
|
5465
|
+
try:
|
|
5466
|
+
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
|
5467
|
+
QPushButton, QLineEdit, QFileDialog, QDialogButtonBox)
|
|
5468
|
+
|
|
5469
|
+
dialog = QDialog(self)
|
|
5470
|
+
dialog.setWindowTitle("Choose Data Folder Location")
|
|
5471
|
+
dialog.setMinimumWidth(550)
|
|
5472
|
+
dialog.setModal(True)
|
|
5473
|
+
|
|
5474
|
+
layout = QVBoxLayout(dialog)
|
|
5475
|
+
layout.setSpacing(15)
|
|
5476
|
+
|
|
5477
|
+
# Title
|
|
5478
|
+
title_label = QLabel("<h2>📁 Choose Your Data Folder</h2>")
|
|
5479
|
+
layout.addWidget(title_label)
|
|
5480
|
+
|
|
5481
|
+
# Explanation
|
|
5482
|
+
msg_label = QLabel(
|
|
5483
|
+
"Supervertaler stores your data in a folder of your choice, which contains things like:<br><br>"
|
|
5484
|
+
"• API keys<br>"
|
|
5485
|
+
"• Translation memories<br>"
|
|
5486
|
+
"• Glossaries<br>"
|
|
5487
|
+
"• Prompts<br>"
|
|
5488
|
+
"• Settings<br><br>"
|
|
5489
|
+
"Choose a location that's easy to find and backup.<br>"
|
|
5490
|
+
"You can change this later in Settings → General."
|
|
5491
|
+
)
|
|
5492
|
+
msg_label.setWordWrap(True)
|
|
5493
|
+
layout.addWidget(msg_label)
|
|
5494
|
+
|
|
5495
|
+
# Path input with browse button
|
|
5496
|
+
path_layout = QHBoxLayout()
|
|
5497
|
+
|
|
5498
|
+
path_edit = QLineEdit()
|
|
5499
|
+
default_path = get_default_user_data_path()
|
|
5500
|
+
path_edit.setText(str(default_path))
|
|
5501
|
+
path_edit.setMinimumWidth(350)
|
|
5502
|
+
path_layout.addWidget(path_edit)
|
|
5503
|
+
|
|
5504
|
+
browse_btn = QPushButton("Browse...")
|
|
5505
|
+
def browse_folder():
|
|
5506
|
+
folder = QFileDialog.getExistingDirectory(
|
|
5507
|
+
dialog,
|
|
5508
|
+
"Choose Data Folder",
|
|
5509
|
+
str(Path.home())
|
|
5510
|
+
)
|
|
5511
|
+
if folder:
|
|
5512
|
+
# Append "Supervertaler" if user didn't include it
|
|
5513
|
+
folder_path = Path(folder)
|
|
5514
|
+
if folder_path.name != "Supervertaler":
|
|
5515
|
+
folder_path = folder_path / "Supervertaler"
|
|
5516
|
+
path_edit.setText(str(folder_path))
|
|
5517
|
+
|
|
5518
|
+
browse_btn.clicked.connect(browse_folder)
|
|
5519
|
+
path_layout.addWidget(browse_btn)
|
|
5520
|
+
|
|
5521
|
+
layout.addLayout(path_layout)
|
|
5522
|
+
|
|
5523
|
+
# Tip
|
|
5524
|
+
tip_label = QLabel(
|
|
5525
|
+
"💡 <b>Tip:</b> The default location is in your home folder, "
|
|
5526
|
+
"making it easy to find and backup."
|
|
5527
|
+
)
|
|
5528
|
+
tip_label.setWordWrap(True)
|
|
5529
|
+
tip_label.setStyleSheet("color: #666;")
|
|
5530
|
+
layout.addWidget(tip_label)
|
|
5531
|
+
|
|
5532
|
+
# Buttons
|
|
5533
|
+
button_layout = QHBoxLayout()
|
|
5534
|
+
|
|
5535
|
+
default_btn = QPushButton("Use Default")
|
|
5536
|
+
default_btn.clicked.connect(lambda: path_edit.setText(str(default_path)))
|
|
5537
|
+
button_layout.addWidget(default_btn)
|
|
5538
|
+
|
|
5539
|
+
button_layout.addStretch()
|
|
5540
|
+
|
|
5541
|
+
ok_btn = QPushButton("OK")
|
|
5542
|
+
ok_btn.setDefault(True)
|
|
5543
|
+
ok_btn.clicked.connect(dialog.accept)
|
|
5544
|
+
button_layout.addWidget(ok_btn)
|
|
5545
|
+
|
|
5546
|
+
layout.addLayout(button_layout)
|
|
5547
|
+
|
|
5548
|
+
# Show dialog
|
|
5549
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
5550
|
+
chosen_path = Path(path_edit.text())
|
|
5551
|
+
|
|
5552
|
+
# Create the folder
|
|
5553
|
+
chosen_path.mkdir(parents=True, exist_ok=True)
|
|
5554
|
+
|
|
5555
|
+
# Save the choice to config pointer
|
|
5556
|
+
save_user_data_path(chosen_path)
|
|
5557
|
+
|
|
5558
|
+
# Update our path if different from what we initialized with
|
|
5559
|
+
if chosen_path != self.user_data_path:
|
|
5560
|
+
self.user_data_path = chosen_path
|
|
5561
|
+
# Re-initialize managers with new path
|
|
5562
|
+
self._reinitialize_with_new_data_path()
|
|
5563
|
+
else:
|
|
5564
|
+
# Same path, but we still need to connect database (it was deferred)
|
|
5565
|
+
if hasattr(self, 'db_manager') and self.db_manager and not self.db_manager.connection:
|
|
5566
|
+
self.db_manager.connect()
|
|
5567
|
+
|
|
5568
|
+
self.log(f"📁 Data folder set to: {chosen_path}")
|
|
5569
|
+
|
|
5570
|
+
# Now show the features welcome dialog
|
|
5571
|
+
from PyQt6.QtCore import QTimer
|
|
5572
|
+
QTimer.singleShot(300, self._show_first_run_welcome)
|
|
5573
|
+
else:
|
|
5574
|
+
# User cancelled - use default anyway
|
|
5575
|
+
default_path.mkdir(parents=True, exist_ok=True)
|
|
5576
|
+
save_user_data_path(default_path)
|
|
5577
|
+
self.log(f"📁 Using default data folder: {default_path}")
|
|
5578
|
+
|
|
5579
|
+
except Exception as e:
|
|
5580
|
+
self.log(f"⚠️ Data location dialog error: {e}")
|
|
5581
|
+
import traceback
|
|
5582
|
+
traceback.print_exc()
|
|
5583
|
+
|
|
5584
|
+
def _reinitialize_with_new_data_path(self):
|
|
5585
|
+
"""Re-initialize managers after user changes data path."""
|
|
5586
|
+
try:
|
|
5587
|
+
# Close existing database connection
|
|
5588
|
+
if hasattr(self, 'db_manager') and self.db_manager:
|
|
5589
|
+
self.db_manager.close()
|
|
5590
|
+
|
|
5591
|
+
# Re-initialize database manager
|
|
5592
|
+
from modules.database_manager import DatabaseManager
|
|
5593
|
+
self.db_manager = DatabaseManager(
|
|
5594
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5595
|
+
log_callback=self.log
|
|
5596
|
+
)
|
|
5597
|
+
self.db_manager.connect()
|
|
5598
|
+
|
|
5599
|
+
# Re-initialize TM database
|
|
5600
|
+
from modules.translation_memory import TMDatabase
|
|
5601
|
+
self.tm_database = TMDatabase(
|
|
5602
|
+
source_lang=None,
|
|
5603
|
+
target_lang=None,
|
|
5604
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5605
|
+
log_callback=self.log
|
|
5606
|
+
)
|
|
5607
|
+
|
|
5608
|
+
# Re-initialize TM metadata manager
|
|
5609
|
+
from modules.tm_metadata_manager import TMMetadataManager
|
|
5610
|
+
self.tm_metadata_mgr = TMMetadataManager(self.db_manager, self.log)
|
|
5611
|
+
|
|
5612
|
+
# Update other managers
|
|
5613
|
+
self.spellcheck_manager = get_spellcheck_manager(str(self.user_data_path))
|
|
5614
|
+
self.fr_history = FindReplaceHistory(str(self.user_data_path))
|
|
5615
|
+
self.shortcut_manager = ShortcutManager(Path(self.user_data_path) / "shortcuts.json")
|
|
5616
|
+
self.voice_command_manager = VoiceCommandManager(self.user_data_path, main_window=self)
|
|
5617
|
+
|
|
5618
|
+
# Update theme manager
|
|
5619
|
+
from modules.theme_manager import ThemeManager
|
|
5620
|
+
self.theme_manager = ThemeManager(self.user_data_path)
|
|
5621
|
+
self.theme_manager.apply_theme(QApplication.instance())
|
|
5622
|
+
|
|
5623
|
+
# Update recent projects file path
|
|
5624
|
+
self.recent_projects_file = self.user_data_path / "recent_projects.json"
|
|
5625
|
+
|
|
5626
|
+
self.log(f"✅ Re-initialized all managers with new data path")
|
|
5627
|
+
|
|
5628
|
+
except Exception as e:
|
|
5629
|
+
self.log(f"⚠️ Error re-initializing managers: {e}")
|
|
5630
|
+
import traceback
|
|
5631
|
+
traceback.print_exc()
|
|
5632
|
+
|
|
5320
5633
|
def _show_first_run_welcome(self):
|
|
5321
5634
|
"""Show welcome message and Features tab on first run."""
|
|
5322
5635
|
try:
|
|
@@ -5738,7 +6051,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
5738
6051
|
quickmenu_items = lib.get_quickmenu_grid_prompts() or []
|
|
5739
6052
|
|
|
5740
6053
|
if not quickmenu_items:
|
|
5741
|
-
self.log("⚠️ No QuickMenu prompts available. Add prompts with 'Show in
|
|
6054
|
+
self.log("⚠️ No QuickMenu prompts available. Add prompts with 'Show in QuickMenu' enabled.")
|
|
5742
6055
|
return
|
|
5743
6056
|
|
|
5744
6057
|
# Find the currently focused widget (source or target cell)
|
|
@@ -6419,7 +6732,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
6419
6732
|
go_resources_action.triggered.connect(lambda: self.main_tabs.setCurrentIndex(1) if hasattr(self, 'main_tabs') else None)
|
|
6420
6733
|
nav_menu.addAction(go_resources_action)
|
|
6421
6734
|
|
|
6422
|
-
go_prompt_manager_action = QAction("
|
|
6735
|
+
go_prompt_manager_action = QAction("⚡ &QuickMenu", self)
|
|
6423
6736
|
go_prompt_manager_action.triggered.connect(lambda: self.main_tabs.setCurrentIndex(2) if hasattr(self, 'main_tabs') else None)
|
|
6424
6737
|
nav_menu.addAction(go_prompt_manager_action)
|
|
6425
6738
|
|
|
@@ -6898,13 +7211,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
6898
7211
|
resources_tab = self.create_resources_tab()
|
|
6899
7212
|
self.main_tabs.addTab(resources_tab, "🗂️ Resources")
|
|
6900
7213
|
|
|
6901
|
-
# ===== 3.
|
|
6902
|
-
# Unified
|
|
7214
|
+
# ===== 3. QUICKMENU TAB =====
|
|
7215
|
+
# Unified QuickMenu system: folder structure = menu hierarchy for in-app + global access
|
|
6903
7216
|
from modules.unified_prompt_manager_qt import UnifiedPromptManagerQt
|
|
6904
7217
|
prompt_widget = QWidget()
|
|
6905
7218
|
self.prompt_manager_qt = UnifiedPromptManagerQt(self, standalone=False)
|
|
6906
7219
|
self.prompt_manager_qt.create_tab(prompt_widget)
|
|
6907
|
-
self.main_tabs.addTab(prompt_widget, "
|
|
7220
|
+
self.main_tabs.addTab(prompt_widget, "⚡ QuickMenu")
|
|
6908
7221
|
|
|
6909
7222
|
# Keep backward compatibility reference
|
|
6910
7223
|
self.document_views_widget = self.main_tabs
|
|
@@ -6964,7 +7277,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
6964
7277
|
layout.addWidget(desc)
|
|
6965
7278
|
|
|
6966
7279
|
# Initialize NT manager
|
|
6967
|
-
nt_path = self.user_data_path / "
|
|
7280
|
+
nt_path = self.user_data_path / "resources" / "non_translatables"
|
|
6968
7281
|
nt_path.mkdir(parents=True, exist_ok=True)
|
|
6969
7282
|
self.nt_manager = NonTranslatablesManager(str(nt_path), self.log)
|
|
6970
7283
|
|
|
@@ -8308,7 +8621,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8308
8621
|
from modules.superbrowser import SuperbrowserWidget
|
|
8309
8622
|
|
|
8310
8623
|
# Create and return the Superbrowser widget
|
|
8311
|
-
superbrowser_widget = SuperbrowserWidget(parent=self)
|
|
8624
|
+
superbrowser_widget = SuperbrowserWidget(parent=self, user_data_path=self.user_data_path)
|
|
8312
8625
|
|
|
8313
8626
|
return superbrowser_widget
|
|
8314
8627
|
|
|
@@ -8595,7 +8908,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8595
8908
|
|
|
8596
8909
|
# Create new Superlookup instance for detached window
|
|
8597
8910
|
# Or move the existing one - better to create new to avoid widget parenting issues
|
|
8598
|
-
detached_lookup = SuperlookupTab(self.lookup_detached_window)
|
|
8911
|
+
detached_lookup = SuperlookupTab(self.lookup_detached_window, user_data_path=self.user_data_path)
|
|
8599
8912
|
|
|
8600
8913
|
# Explicitly copy theme_manager reference
|
|
8601
8914
|
detached_lookup.theme_manager = self.theme_manager
|
|
@@ -8732,7 +9045,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8732
9045
|
# Superdocs removed (online GitBook will be used instead)
|
|
8733
9046
|
|
|
8734
9047
|
print("[DEBUG] About to create SuperlookupTab...")
|
|
8735
|
-
lookup_tab = SuperlookupTab(self)
|
|
9048
|
+
lookup_tab = SuperlookupTab(self, user_data_path=self.user_data_path)
|
|
8736
9049
|
print("[DEBUG] SuperlookupTab created successfully")
|
|
8737
9050
|
self.lookup_tab = lookup_tab # Store reference for later use
|
|
8738
9051
|
modules_tabs.addTab(lookup_tab, "🔍 Superlookup")
|
|
@@ -9176,58 +9489,139 @@ class SupervertalerQt(QMainWindow):
|
|
|
9176
9489
|
# Show progress dialog
|
|
9177
9490
|
from PyQt6.QtWidgets import QProgressDialog
|
|
9178
9491
|
from PyQt6.QtCore import Qt
|
|
9492
|
+
import time
|
|
9179
9493
|
|
|
9180
|
-
|
|
9494
|
+
# Create progress dialog with cancel button
|
|
9495
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
9181
9496
|
progress.setWindowTitle("TMX Import")
|
|
9182
9497
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
9183
9498
|
progress.setMinimumDuration(0)
|
|
9499
|
+
progress.setAutoClose(False)
|
|
9500
|
+
progress.setAutoReset(False)
|
|
9184
9501
|
progress.setValue(0)
|
|
9185
9502
|
progress.show()
|
|
9186
9503
|
QApplication.processEvents()
|
|
9187
9504
|
|
|
9505
|
+
# Track if user cancelled
|
|
9506
|
+
cancelled = False
|
|
9507
|
+
start_time = time.time()
|
|
9508
|
+
|
|
9509
|
+
def update_progress(current, total, message):
|
|
9510
|
+
nonlocal cancelled
|
|
9511
|
+
if progress.wasCanceled():
|
|
9512
|
+
cancelled = True
|
|
9513
|
+
return
|
|
9514
|
+
|
|
9515
|
+
if total > 0:
|
|
9516
|
+
percentage = int((current / total) * 100)
|
|
9517
|
+
progress.setValue(percentage)
|
|
9518
|
+
|
|
9519
|
+
# Calculate time stats
|
|
9520
|
+
elapsed = time.time() - start_time
|
|
9521
|
+
if current > 0:
|
|
9522
|
+
rate = current / elapsed
|
|
9523
|
+
remaining = (total - current) / rate if rate > 0 else 0
|
|
9524
|
+
|
|
9525
|
+
# Format message with stats
|
|
9526
|
+
time_info = f" (~{int(remaining/60)}m {int(remaining%60)}s remaining)" if remaining > 10 else ""
|
|
9527
|
+
progress.setLabelText(f"{message}\nRate: {int(rate):,} entries/sec{time_info}")
|
|
9528
|
+
else:
|
|
9529
|
+
progress.setLabelText(message)
|
|
9530
|
+
else:
|
|
9531
|
+
progress.setLabelText(message)
|
|
9532
|
+
|
|
9533
|
+
QApplication.processEvents()
|
|
9534
|
+
|
|
9188
9535
|
if choice == 'import_strip':
|
|
9189
9536
|
# Import with variant stripping
|
|
9190
9537
|
tm_id, count = self.tm_database.load_tmx_file(
|
|
9191
9538
|
file_path, source_lang, target_lang,
|
|
9192
|
-
tm_name=None, read_only=False
|
|
9539
|
+
tm_name=None, read_only=False, progress_callback=update_progress
|
|
9193
9540
|
)
|
|
9194
9541
|
progress.close()
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
self,
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9542
|
+
|
|
9543
|
+
if not cancelled:
|
|
9544
|
+
self.log(f"Imported {count} entries (variants stripped: {compat['tmx_source']}, {compat['tmx_target']} → {source_lang}, {target_lang})")
|
|
9545
|
+
QMessageBox.information(
|
|
9546
|
+
self, "Import Complete",
|
|
9547
|
+
f"TMX imported successfully!\n\nEntries: {count:,}\n"
|
|
9548
|
+
f"Mapped: {compat['tmx_source']}, {compat['tmx_target']} → {source_lang}, {target_lang}"
|
|
9549
|
+
)
|
|
9550
|
+
else:
|
|
9551
|
+
self.log("TMX import cancelled by user")
|
|
9552
|
+
QMessageBox.information(self, "Import Cancelled", f"Import stopped.\n\nPartial entries may have been imported: {count:,}")
|
|
9201
9553
|
elif choice == 'create_new':
|
|
9202
9554
|
# Create new TM with variant languages
|
|
9203
9555
|
tm_id, count = self.tm_database.load_tmx_file(
|
|
9204
9556
|
file_path, compat['tmx_source'], compat['tmx_target'],
|
|
9205
|
-
tm_name=None, read_only=False
|
|
9557
|
+
tm_name=None, read_only=False, progress_callback=update_progress
|
|
9206
9558
|
)
|
|
9207
9559
|
progress.close()
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
self
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9560
|
+
|
|
9561
|
+
if not cancelled:
|
|
9562
|
+
self.log(f"Created new TM with variants: {compat['tmx_source']}, {compat['tmx_target']}")
|
|
9563
|
+
QMessageBox.information(
|
|
9564
|
+
self, "Import Complete",
|
|
9565
|
+
f"New TM created!\n\nTM ID: {tm_id}\nEntries: {count:,}\n"
|
|
9566
|
+
f"Languages: {compat['tmx_source']}, {compat['tmx_target']}"
|
|
9567
|
+
)
|
|
9568
|
+
else:
|
|
9569
|
+
self.log("TMX import cancelled by user")
|
|
9570
|
+
QMessageBox.information(self, "Import Cancelled", f"Import stopped.\n\nPartial entries may have been imported: {count:,}")
|
|
9214
9571
|
else:
|
|
9215
|
-
# Exact match - proceed normally with progress
|
|
9572
|
+
# Exact match - proceed normally with real-time progress
|
|
9216
9573
|
from PyQt6.QtWidgets import QProgressDialog
|
|
9217
9574
|
from PyQt6.QtCore import Qt
|
|
9575
|
+
import time
|
|
9218
9576
|
|
|
9219
|
-
progress = QProgressDialog("
|
|
9577
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
9220
9578
|
progress.setWindowTitle("TMX Import")
|
|
9221
9579
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
9222
9580
|
progress.setMinimumDuration(0)
|
|
9581
|
+
progress.setAutoClose(False)
|
|
9582
|
+
progress.setAutoReset(False)
|
|
9223
9583
|
progress.setValue(0)
|
|
9224
9584
|
progress.show()
|
|
9225
9585
|
QApplication.processEvents()
|
|
9226
9586
|
|
|
9227
|
-
|
|
9587
|
+
cancelled = False
|
|
9588
|
+
start_time = time.time()
|
|
9589
|
+
|
|
9590
|
+
def update_progress(current, total, message):
|
|
9591
|
+
nonlocal cancelled
|
|
9592
|
+
if progress.wasCanceled():
|
|
9593
|
+
cancelled = True
|
|
9594
|
+
return
|
|
9595
|
+
|
|
9596
|
+
if total > 0:
|
|
9597
|
+
percentage = int((current / total) * 100)
|
|
9598
|
+
progress.setValue(percentage)
|
|
9599
|
+
|
|
9600
|
+
# Calculate time stats
|
|
9601
|
+
elapsed = time.time() - start_time
|
|
9602
|
+
if current > 0:
|
|
9603
|
+
rate = current / elapsed
|
|
9604
|
+
remaining = (total - current) / rate if rate > 0 else 0
|
|
9605
|
+
|
|
9606
|
+
# Format message with stats
|
|
9607
|
+
time_info = f" (~{int(remaining/60)}m {int(remaining%60)}s remaining)" if remaining > 10 else ""
|
|
9608
|
+
progress.setLabelText(f"{message}\nRate: {int(rate):,} entries/sec{time_info}")
|
|
9609
|
+
else:
|
|
9610
|
+
progress.setLabelText(message)
|
|
9611
|
+
else:
|
|
9612
|
+
progress.setLabelText(message)
|
|
9613
|
+
|
|
9614
|
+
QApplication.processEvents()
|
|
9615
|
+
|
|
9616
|
+
tm_id, count = self.tm_database.load_tmx_file(file_path, source_lang, target_lang, progress_callback=update_progress)
|
|
9228
9617
|
progress.close()
|
|
9229
|
-
|
|
9230
|
-
|
|
9618
|
+
|
|
9619
|
+
if not cancelled:
|
|
9620
|
+
self.log(f"Successfully imported {count} entries from TMX file")
|
|
9621
|
+
QMessageBox.information(self, "Import Complete", f"TMX imported!\n\nEntries: {count:,}\nTM ID: {tm_id}")
|
|
9622
|
+
else:
|
|
9623
|
+
self.log("TMX import cancelled by user")
|
|
9624
|
+
QMessageBox.information(self, "Import Cancelled", f"Import stopped.\n\nPartial entries may have been imported: {count:,}")
|
|
9231
9625
|
|
|
9232
9626
|
except Exception as e:
|
|
9233
9627
|
self.log(f"Error importing TMX file: {e}")
|
|
@@ -10359,7 +10753,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
10359
10753
|
if reply2 == QMessageBox.StandardButton.Yes and self.tm_database:
|
|
10360
10754
|
# Clear TM entries
|
|
10361
10755
|
import sqlite3
|
|
10362
|
-
db_path = self.user_data_path / "
|
|
10756
|
+
db_path = self.user_data_path / "resources" / "supervertaler.db"
|
|
10363
10757
|
conn = sqlite3.connect(str(db_path))
|
|
10364
10758
|
cursor = conn.cursor()
|
|
10365
10759
|
cursor.execute("DELETE FROM translation_units")
|
|
@@ -13458,29 +13852,66 @@ class SupervertalerQt(QMainWindow):
|
|
|
13458
13852
|
if reply != QMessageBox.StandardButton.Yes:
|
|
13459
13853
|
return
|
|
13460
13854
|
|
|
13461
|
-
# Import into existing TM with progress dialog
|
|
13855
|
+
# Import into existing TM with real-time progress dialog
|
|
13462
13856
|
from PyQt6.QtWidgets import QProgressDialog
|
|
13463
13857
|
from PyQt6.QtCore import Qt
|
|
13858
|
+
import time
|
|
13464
13859
|
|
|
13465
|
-
progress = QProgressDialog("
|
|
13860
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
13466
13861
|
progress.setWindowTitle("TMX Import")
|
|
13467
13862
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
13468
13863
|
progress.setMinimumDuration(0)
|
|
13864
|
+
progress.setAutoClose(False)
|
|
13865
|
+
progress.setAutoReset(False)
|
|
13469
13866
|
progress.setValue(0)
|
|
13470
13867
|
progress.show()
|
|
13471
13868
|
QApplication.processEvents()
|
|
13472
13869
|
|
|
13870
|
+
cancelled = False
|
|
13871
|
+
start_time = time.time()
|
|
13872
|
+
|
|
13873
|
+
def update_progress(current, total, message):
|
|
13874
|
+
nonlocal cancelled
|
|
13875
|
+
if progress.wasCanceled():
|
|
13876
|
+
cancelled = True
|
|
13877
|
+
return
|
|
13878
|
+
|
|
13879
|
+
if total > 0:
|
|
13880
|
+
percentage = int((current / total) * 100)
|
|
13881
|
+
progress.setValue(percentage)
|
|
13882
|
+
|
|
13883
|
+
# Calculate time stats
|
|
13884
|
+
elapsed = time.time() - start_time
|
|
13885
|
+
if current > 0:
|
|
13886
|
+
rate = current / elapsed
|
|
13887
|
+
remaining = (total - current) / rate if rate > 0 else 0
|
|
13888
|
+
|
|
13889
|
+
# Format message with stats
|
|
13890
|
+
time_info = f" (~{int(remaining/60)}m {int(remaining%60)}s remaining)" if remaining > 10 else ""
|
|
13891
|
+
progress.setLabelText(f"{message}\nRate: {int(rate):,} entries/sec{time_info}")
|
|
13892
|
+
else:
|
|
13893
|
+
progress.setLabelText(message)
|
|
13894
|
+
else:
|
|
13895
|
+
progress.setLabelText(message)
|
|
13896
|
+
|
|
13897
|
+
QApplication.processEvents()
|
|
13898
|
+
|
|
13473
13899
|
count = self.tm_database._load_tmx_into_db(
|
|
13474
|
-
filepath, tm_src_lang, tm_tgt_lang, target_tm_id,
|
|
13900
|
+
filepath, tm_src_lang, tm_tgt_lang, target_tm_id,
|
|
13901
|
+
strip_variants=strip_variants, progress_callback=update_progress
|
|
13475
13902
|
)
|
|
13476
13903
|
|
|
13477
13904
|
progress.close()
|
|
13478
13905
|
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13906
|
+
if not cancelled:
|
|
13907
|
+
# Update entry count
|
|
13908
|
+
tm_metadata_mgr.update_entry_count(target_tm_id)
|
|
13909
|
+
self.log(f"Imported {count} entries into TM '{target_tm_id}'")
|
|
13910
|
+
|
|
13911
|
+
QMessageBox.information(self, "Success", f"TMX imported successfully into TM '{target_tm_id}'!\n\nEntries imported: {count:,}")
|
|
13912
|
+
else:
|
|
13913
|
+
self.log(f"TMX import cancelled by user. Partial import: {count} entries")
|
|
13914
|
+
QMessageBox.information(self, "Import Cancelled", f"Import stopped.\n\nPartial entries imported: {count:,}")
|
|
13484
13915
|
refresh_callback()
|
|
13485
13916
|
else:
|
|
13486
13917
|
QMessageBox.critical(self, "Error", "TM database not available")
|
|
@@ -14260,7 +14691,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
14260
14691
|
auto_markdown_cb.setChecked(general_settings.get('auto_generate_markdown', False))
|
|
14261
14692
|
auto_markdown_cb.setToolTip(
|
|
14262
14693
|
"Automatically convert imported documents to markdown format\n"
|
|
14263
|
-
"for AI Assistant access in user_data_private/
|
|
14694
|
+
"for AI Assistant access in user_data_private/ai_assistant/current_document/"
|
|
14264
14695
|
)
|
|
14265
14696
|
behavior_layout.addWidget(auto_markdown_cb)
|
|
14266
14697
|
|
|
@@ -14472,7 +14903,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
14472
14903
|
|
|
14473
14904
|
def _create_general_settings_tab(self):
|
|
14474
14905
|
"""Create General Settings tab content"""
|
|
14475
|
-
from PyQt6.QtWidgets import QCheckBox, QGroupBox, QPushButton
|
|
14906
|
+
from PyQt6.QtWidgets import QCheckBox, QGroupBox, QPushButton, QLineEdit, QFileDialog
|
|
14476
14907
|
|
|
14477
14908
|
tab = QWidget()
|
|
14478
14909
|
layout = QVBoxLayout(tab)
|
|
@@ -14502,6 +14933,119 @@ class SupervertalerQt(QMainWindow):
|
|
|
14502
14933
|
startup_group.setLayout(startup_layout)
|
|
14503
14934
|
layout.addWidget(startup_group)
|
|
14504
14935
|
|
|
14936
|
+
# Data Folder Location group
|
|
14937
|
+
data_folder_group = QGroupBox("📁 Data Folder Location")
|
|
14938
|
+
data_folder_layout = QVBoxLayout()
|
|
14939
|
+
|
|
14940
|
+
data_folder_info = QLabel(
|
|
14941
|
+
"Your translation memories, glossaries, prompts, and settings are stored here:"
|
|
14942
|
+
)
|
|
14943
|
+
data_folder_info.setWordWrap(True)
|
|
14944
|
+
data_folder_layout.addWidget(data_folder_info)
|
|
14945
|
+
|
|
14946
|
+
# Path display with browse button
|
|
14947
|
+
path_row = QHBoxLayout()
|
|
14948
|
+
|
|
14949
|
+
data_path_edit = QLineEdit()
|
|
14950
|
+
data_path_edit.setText(str(self.user_data_path))
|
|
14951
|
+
data_path_edit.setReadOnly(True)
|
|
14952
|
+
data_path_edit.setToolTip("Current data folder location")
|
|
14953
|
+
path_row.addWidget(data_path_edit)
|
|
14954
|
+
|
|
14955
|
+
change_path_btn = QPushButton("Change...")
|
|
14956
|
+
change_path_btn.setToolTip("Choose a different location for your data folder")
|
|
14957
|
+
|
|
14958
|
+
def change_data_path():
|
|
14959
|
+
"""Let user change their data folder location."""
|
|
14960
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
14961
|
+
|
|
14962
|
+
folder = QFileDialog.getExistingDirectory(
|
|
14963
|
+
self,
|
|
14964
|
+
"Choose New Data Folder Location",
|
|
14965
|
+
str(Path.home())
|
|
14966
|
+
)
|
|
14967
|
+
if folder:
|
|
14968
|
+
folder_path = Path(folder)
|
|
14969
|
+
# Append "Supervertaler" if user didn't include it
|
|
14970
|
+
if folder_path.name != "Supervertaler":
|
|
14971
|
+
folder_path = folder_path / "Supervertaler"
|
|
14972
|
+
|
|
14973
|
+
# Warn if switching to a folder with existing data
|
|
14974
|
+
if folder_path.exists() and folder_path != self.user_data_path:
|
|
14975
|
+
has_content = any(folder_path.iterdir())
|
|
14976
|
+
if has_content:
|
|
14977
|
+
reply = QMessageBox.question(
|
|
14978
|
+
self,
|
|
14979
|
+
"Existing Data Found",
|
|
14980
|
+
f"The folder already contains data:\n{folder_path}\n\n"
|
|
14981
|
+
"Do you want to use this existing data?",
|
|
14982
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel
|
|
14983
|
+
)
|
|
14984
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
14985
|
+
return
|
|
14986
|
+
|
|
14987
|
+
# Create the folder
|
|
14988
|
+
folder_path.mkdir(parents=True, exist_ok=True)
|
|
14989
|
+
|
|
14990
|
+
# Ask if they want to copy existing data
|
|
14991
|
+
if self.user_data_path.exists() and self.user_data_path != folder_path:
|
|
14992
|
+
reply = QMessageBox.question(
|
|
14993
|
+
self,
|
|
14994
|
+
"Copy Existing Data?",
|
|
14995
|
+
f"Would you like to copy your existing data to the new location?\n\n"
|
|
14996
|
+
f"From: {self.user_data_path}\n"
|
|
14997
|
+
f"To: {folder_path}\n\n"
|
|
14998
|
+
"This will NOT delete the original data.",
|
|
14999
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
15000
|
+
)
|
|
15001
|
+
if reply == QMessageBox.StandardButton.Yes:
|
|
15002
|
+
import shutil
|
|
15003
|
+
try:
|
|
15004
|
+
for item in self.user_data_path.iterdir():
|
|
15005
|
+
dest = folder_path / item.name
|
|
15006
|
+
if item.is_dir():
|
|
15007
|
+
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
15008
|
+
else:
|
|
15009
|
+
shutil.copy2(item, dest)
|
|
15010
|
+
self.log(f"✅ Copied data to new location")
|
|
15011
|
+
except Exception as e:
|
|
15012
|
+
QMessageBox.warning(self, "Copy Error", f"Failed to copy data: {e}")
|
|
15013
|
+
|
|
15014
|
+
# Save the new path
|
|
15015
|
+
save_user_data_path(folder_path)
|
|
15016
|
+
data_path_edit.setText(str(folder_path))
|
|
15017
|
+
|
|
15018
|
+
# Update and reinitialize
|
|
15019
|
+
self.user_data_path = folder_path
|
|
15020
|
+
self._reinitialize_with_new_data_path()
|
|
15021
|
+
|
|
15022
|
+
QMessageBox.information(
|
|
15023
|
+
self,
|
|
15024
|
+
"Data Folder Changed",
|
|
15025
|
+
f"Data folder changed to:\n{folder_path}\n\n"
|
|
15026
|
+
"All managers have been reinitialized."
|
|
15027
|
+
)
|
|
15028
|
+
|
|
15029
|
+
change_path_btn.clicked.connect(change_data_path)
|
|
15030
|
+
path_row.addWidget(change_path_btn)
|
|
15031
|
+
|
|
15032
|
+
open_folder_btn = QPushButton("Open")
|
|
15033
|
+
open_folder_btn.setToolTip("Open this folder in your file manager")
|
|
15034
|
+
open_folder_btn.clicked.connect(lambda: os.startfile(str(self.user_data_path)) if sys.platform == 'win32'
|
|
15035
|
+
else subprocess.run(['xdg-open' if sys.platform == 'linux' else 'open', str(self.user_data_path)]))
|
|
15036
|
+
path_row.addWidget(open_folder_btn)
|
|
15037
|
+
|
|
15038
|
+
data_folder_layout.addLayout(path_row)
|
|
15039
|
+
|
|
15040
|
+
data_folder_tip = QLabel(
|
|
15041
|
+
"💡 This folder persists across updates. Back it up regularly!"
|
|
15042
|
+
)
|
|
15043
|
+
data_folder_tip.setStyleSheet("color: #666; font-size: 9pt;")
|
|
15044
|
+
data_folder_layout.addWidget(data_folder_tip)
|
|
15045
|
+
|
|
15046
|
+
data_folder_group.setLayout(data_folder_layout)
|
|
15047
|
+
layout.addWidget(data_folder_group)
|
|
15048
|
+
|
|
14505
15049
|
# Sound Effects group
|
|
14506
15050
|
sound_group = QGroupBox("🔊 Sound Effects")
|
|
14507
15051
|
sound_layout = QVBoxLayout()
|
|
@@ -16537,7 +17081,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
16537
17081
|
prompt_text = self.prompt_manager_qt.get_system_template(mode_key)
|
|
16538
17082
|
else:
|
|
16539
17083
|
# Fallback: load from JSON file
|
|
16540
|
-
system_prompts_file = self.user_data_path / "
|
|
17084
|
+
system_prompts_file = self.user_data_path / "prompt_library" / "system_prompts_layer1.json"
|
|
16541
17085
|
if system_prompts_file.exists():
|
|
16542
17086
|
import json
|
|
16543
17087
|
with open(system_prompts_file, 'r', encoding='utf-8') as f:
|
|
@@ -16566,7 +17110,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
16566
17110
|
|
|
16567
17111
|
# Always save to JSON file
|
|
16568
17112
|
import json
|
|
16569
|
-
system_prompts_file = self.user_data_path / "
|
|
17113
|
+
system_prompts_file = self.user_data_path / "prompt_library" / "system_prompts_layer1.json"
|
|
16570
17114
|
system_prompts_file.parent.mkdir(parents=True, exist_ok=True)
|
|
16571
17115
|
|
|
16572
17116
|
# Load existing prompts
|
|
@@ -29627,7 +30171,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
29627
30171
|
layout = QVBoxLayout(dialog)
|
|
29628
30172
|
|
|
29629
30173
|
# Header
|
|
29630
|
-
header = QLabel("<h3>✅ AI-
|
|
30174
|
+
header = QLabel("<h3>✅ AI-Enhanced Translation Proofreading</h3>")
|
|
29631
30175
|
layout.addWidget(header)
|
|
29632
30176
|
|
|
29633
30177
|
info_label = QLabel(
|
|
@@ -36345,7 +36889,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36345
36889
|
subtab_name: Name of the sub-tab to navigate to (e.g., "AI Settings")
|
|
36346
36890
|
"""
|
|
36347
36891
|
if hasattr(self, 'main_tabs'):
|
|
36348
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36892
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36349
36893
|
self.main_tabs.setCurrentIndex(4)
|
|
36350
36894
|
|
|
36351
36895
|
# Navigate to specific sub-tab if requested
|
|
@@ -36366,7 +36910,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36366
36910
|
def _go_to_superlookup(self):
|
|
36367
36911
|
"""Navigate to Superlookup in Tools tab"""
|
|
36368
36912
|
if hasattr(self, 'main_tabs'):
|
|
36369
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36913
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36370
36914
|
self.main_tabs.setCurrentIndex(3) # Switch to Tools tab
|
|
36371
36915
|
# Then switch to Superlookup sub-tab
|
|
36372
36916
|
if hasattr(self, 'modules_tabs'):
|
|
@@ -36379,7 +36923,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36379
36923
|
def _navigate_to_tool(self, tool_name: str):
|
|
36380
36924
|
"""Navigate to a specific tool in the Tools tab"""
|
|
36381
36925
|
if hasattr(self, 'main_tabs'):
|
|
36382
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36926
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36383
36927
|
self.main_tabs.setCurrentIndex(3) # Switch to Tools tab
|
|
36384
36928
|
# Then switch to the specific tool sub-tab
|
|
36385
36929
|
if hasattr(self, 'modules_tabs'):
|
|
@@ -36612,7 +37156,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36612
37156
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
36613
37157
|
dialog,
|
|
36614
37158
|
"Select TMX File",
|
|
36615
|
-
str(self.user_data_path / "
|
|
37159
|
+
str(self.user_data_path / "resources"),
|
|
36616
37160
|
"TMX Files (*.tmx);;All Files (*.*)"
|
|
36617
37161
|
)
|
|
36618
37162
|
if file_path:
|
|
@@ -37398,7 +37942,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
37398
37942
|
layout.addWidget(title)
|
|
37399
37943
|
|
|
37400
37944
|
# Description
|
|
37401
|
-
desc = QLabel("<p><b>AI-
|
|
37945
|
+
desc = QLabel("<p><b>AI-enhanced tool for translators & writers</b></p>")
|
|
37402
37946
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
37403
37947
|
layout.addWidget(desc)
|
|
37404
37948
|
|
|
@@ -41185,9 +41729,10 @@ class SuperlookupTab(QWidget):
|
|
|
41185
41729
|
Works anywhere on your computer: in CAT tools, browsers, Word, any text box
|
|
41186
41730
|
"""
|
|
41187
41731
|
|
|
41188
|
-
def __init__(self, parent=None):
|
|
41732
|
+
def __init__(self, parent=None, user_data_path=None):
|
|
41189
41733
|
super().__init__(parent)
|
|
41190
41734
|
self.main_window = parent # Store reference to main window for database access
|
|
41735
|
+
self.user_data_path = user_data_path # Store user data path for web cache
|
|
41191
41736
|
|
|
41192
41737
|
print("[Superlookup] SuperlookupTab.__init__ called")
|
|
41193
41738
|
|
|
@@ -41359,31 +41904,10 @@ class SuperlookupTab(QWidget):
|
|
|
41359
41904
|
|
|
41360
41905
|
layout.addLayout(search_row)
|
|
41361
41906
|
|
|
41362
|
-
# Options row (
|
|
41907
|
+
# Options row (language filters only) - compact single line
|
|
41363
41908
|
options_layout = QHBoxLayout()
|
|
41364
41909
|
options_layout.setSpacing(8)
|
|
41365
41910
|
|
|
41366
|
-
# Search direction selector
|
|
41367
|
-
direction_label = QLabel("Direction:")
|
|
41368
|
-
direction_label.setStyleSheet("font-weight: bold; font-size: 9pt;")
|
|
41369
|
-
options_layout.addWidget(direction_label)
|
|
41370
|
-
|
|
41371
|
-
self.direction_both = CheckmarkRadioButton("Both")
|
|
41372
|
-
self.direction_both.setChecked(True) # Default
|
|
41373
|
-
self.direction_both.setToolTip("Search in both source and target text (bidirectional)")
|
|
41374
|
-
options_layout.addWidget(self.direction_both)
|
|
41375
|
-
|
|
41376
|
-
self.direction_source = CheckmarkRadioButton("Source")
|
|
41377
|
-
self.direction_source.setToolTip("Search only in source language text")
|
|
41378
|
-
options_layout.addWidget(self.direction_source)
|
|
41379
|
-
|
|
41380
|
-
self.direction_target = CheckmarkRadioButton("Target")
|
|
41381
|
-
self.direction_target.setToolTip("Search only in target language text")
|
|
41382
|
-
options_layout.addWidget(self.direction_target)
|
|
41383
|
-
|
|
41384
|
-
# Spacer
|
|
41385
|
-
options_layout.addSpacing(15)
|
|
41386
|
-
|
|
41387
41911
|
# Language filter dropdowns
|
|
41388
41912
|
from_label = QLabel("From:")
|
|
41389
41913
|
from_label.setStyleSheet("font-weight: bold; font-size: 9pt;")
|
|
@@ -41895,7 +42419,11 @@ class SuperlookupTab(QWidget):
|
|
|
41895
42419
|
self.web_engine_available = True
|
|
41896
42420
|
|
|
41897
42421
|
# Create persistent profile for login/cookie storage
|
|
41898
|
-
|
|
42422
|
+
if self.user_data_path:
|
|
42423
|
+
storage_path = os.path.join(str(self.user_data_path), 'web_cache')
|
|
42424
|
+
else:
|
|
42425
|
+
# Fallback to script directory if user_data_path not provided
|
|
42426
|
+
storage_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_data', 'web_cache')
|
|
41899
42427
|
os.makedirs(storage_path, exist_ok=True)
|
|
41900
42428
|
self.web_profile = QWebEngineProfile("SuperlookupProfile", self)
|
|
41901
42429
|
self.web_profile.setPersistentStoragePath(storage_path)
|
|
@@ -43159,17 +43687,10 @@ class SuperlookupTab(QWidget):
|
|
|
43159
43687
|
def get_search_direction(self):
|
|
43160
43688
|
"""Get the current search direction setting.
|
|
43161
43689
|
|
|
43162
|
-
|
|
43163
|
-
|
|
43164
|
-
'source' - search source text only
|
|
43165
|
-
'target' - search target text only
|
|
43690
|
+
Always returns 'both' for maximum search coverage.
|
|
43691
|
+
Smart language swapping ensures results are always shown in correct order.
|
|
43166
43692
|
"""
|
|
43167
|
-
|
|
43168
|
-
return 'source'
|
|
43169
|
-
elif hasattr(self, 'direction_target') and self.direction_target.isChecked():
|
|
43170
|
-
return 'target'
|
|
43171
|
-
else:
|
|
43172
|
-
return 'both'
|
|
43693
|
+
return 'both'
|
|
43173
43694
|
|
|
43174
43695
|
def _on_language_changed(self):
|
|
43175
43696
|
"""Handle language dropdown change - update Web Resources info"""
|
|
@@ -44926,7 +45447,7 @@ class SuperlookupTab(QWidget):
|
|
|
44926
45447
|
print(f"[Superlookup] Has main_tabs: {hasattr(main_window, 'main_tabs')}")
|
|
44927
45448
|
|
|
44928
45449
|
# Switch to Tools tab (main_tabs index 3)
|
|
44929
|
-
# Tab structure: Grid=0, Resources=1,
|
|
45450
|
+
# Tab structure: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
44930
45451
|
if hasattr(main_window, 'main_tabs'):
|
|
44931
45452
|
print(f"[Superlookup] Current main_tab index: {main_window.main_tabs.currentIndex()}")
|
|
44932
45453
|
main_window.main_tabs.setCurrentIndex(3) # Tools tab
|