supervertaler 1.9.147__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 +609 -236
- 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.147.dist-info → supervertaler-1.9.149.dist-info}/METADATA +23 -17
- {supervertaler-1.9.147.dist-info → supervertaler-1.9.149.dist-info}/RECORD +19 -19
- {supervertaler-1.9.147.dist-info → supervertaler-1.9.149.dist-info}/WHEEL +0 -0
- {supervertaler-1.9.147.dist-info → supervertaler-1.9.149.dist-info}/entry_points.txt +0 -0
- {supervertaler-1.9.147.dist-info → supervertaler-1.9.149.dist-info}/licenses/LICENSE +0 -0
- {supervertaler-1.9.147.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,161 +61,148 @@ def get_resource_path(relative_path: str) -> Path:
|
|
|
61
61
|
return base_path / relative_path
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def
|
|
64
|
+
def get_config_pointer_path() -> Path:
|
|
65
65
|
"""
|
|
66
|
-
Get
|
|
67
|
-
|
|
68
|
-
This function returns a PERSISTENT location for user data that survives
|
|
69
|
-
pip upgrades and reinstalls. The location varies by context:
|
|
70
|
-
|
|
71
|
-
1. FROZEN BUILDS (Windows EXE): 'user_data' folder next to the EXE
|
|
72
|
-
- This allows the EXE to be portable (copy folder = copy everything)
|
|
73
|
-
- Path: C:/SomeFolder/Supervertaler/user_data/
|
|
66
|
+
Get path to the config pointer file that stores the user's chosen data location.
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- This location persists across pip upgrades!
|
|
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
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
- Developers can use ENABLE_PRIVATE_FEATURES to use user_data_private/
|
|
83
|
-
|
|
84
|
-
The key insight: pip installs wipe site-packages on upgrade, so we MUST
|
|
85
|
-
store user data outside the package directory for pip users.
|
|
73
|
+
This small file just contains a pointer to where the real data lives.
|
|
86
74
|
"""
|
|
87
|
-
if
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
return Path(
|
|
94
|
-
|
|
95
|
-
# Check if we're running from site-packages (pip install)
|
|
96
|
-
script_path = Path(__file__).resolve()
|
|
97
|
-
is_pip_install = "site-packages" in str(script_path)
|
|
98
|
-
|
|
99
|
-
if is_pip_install:
|
|
100
|
-
# =================================================================
|
|
101
|
-
# PIP INSTALL: Use platform-specific persistent location
|
|
102
|
-
# =================================================================
|
|
103
|
-
# This survives pip upgrades because it's OUTSIDE site-packages!
|
|
104
|
-
try:
|
|
105
|
-
from platformdirs import user_data_dir
|
|
106
|
-
return Path(user_data_dir("Supervertaler", "MichaelBeijer"))
|
|
107
|
-
except ImportError:
|
|
108
|
-
# Fallback if platformdirs not available (shouldn't happen)
|
|
109
|
-
if sys.platform == 'win32':
|
|
110
|
-
base = Path(os.environ.get('LOCALAPPDATA', os.path.expanduser('~')))
|
|
111
|
-
return base / "Supervertaler"
|
|
112
|
-
elif sys.platform == 'darwin':
|
|
113
|
-
return Path.home() / "Library" / "Application Support" / "Supervertaler"
|
|
114
|
-
else:
|
|
115
|
-
# Linux/BSD - follow XDG spec
|
|
116
|
-
xdg_data = os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
|
|
117
|
-
return Path(xdg_data) / "Supervertaler"
|
|
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"
|
|
118
82
|
else:
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return Path(__file__).parent / "user_data"
|
|
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"
|
|
123
86
|
|
|
124
87
|
|
|
125
|
-
def
|
|
88
|
+
def get_default_user_data_path() -> Path:
|
|
126
89
|
"""
|
|
127
|
-
|
|
90
|
+
Get the DEFAULT user data path (visible in home folder).
|
|
128
91
|
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
- Windows: C:/Users/Username/Supervertaler/
|
|
93
|
+
- macOS: ~/Supervertaler/
|
|
94
|
+
- Linux: ~/Supervertaler/
|
|
131
95
|
|
|
132
|
-
|
|
96
|
+
This is intentionally a visible, easily-accessible location.
|
|
133
97
|
"""
|
|
134
|
-
|
|
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)
|
|
135
105
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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()
|
|
139
117
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if "site-packages" not in str(script_path):
|
|
143
|
-
return False # Development mode, no migration needed
|
|
118
|
+
if not pointer_path.exists():
|
|
119
|
+
return None
|
|
144
120
|
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
130
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_user_data_path() -> Path:
|
|
135
|
+
"""
|
|
136
|
+
Get the path to user data directory.
|
|
151
137
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
old_has_content = True
|
|
157
|
-
break
|
|
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)
|
|
158
142
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
147
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
148
|
+
This ensures data survives pip upgrades, EXE updates, and is easy to find/backup.
|
|
149
|
+
"""
|
|
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
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
177
182
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
print(f" To: {new_path}")
|
|
183
|
+
Returns True if:
|
|
184
|
+
- No config pointer file exists, AND
|
|
185
|
+
- Default data location doesn't have existing content
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if new_path.exists():
|
|
189
|
-
# Merge into existing empty directory
|
|
190
|
-
for item in old_path.iterdir():
|
|
191
|
-
dest = new_path / item.name
|
|
192
|
-
if item.is_dir():
|
|
193
|
-
shutil.copytree(item, dest, dirs_exist_ok=True)
|
|
194
|
-
else:
|
|
195
|
-
shutil.copy2(item, dest)
|
|
196
|
-
else:
|
|
197
|
-
shutil.copytree(old_path, new_path)
|
|
198
|
-
|
|
199
|
-
print(f"[Migration] ✅ Successfully migrated user data!")
|
|
200
|
-
print(f"[Migration] Your API keys, TMs, glossaries, and prompts are now in:")
|
|
201
|
-
print(f" {new_path}")
|
|
202
|
-
|
|
203
|
-
# Leave a marker file in the old location so users know what happened
|
|
204
|
-
marker = old_path / "_MIGRATED_TO_NEW_LOCATION.txt"
|
|
205
|
-
marker.write_text(
|
|
206
|
-
f"Your Supervertaler data has been migrated to a persistent location:\n"
|
|
207
|
-
f"{new_path}\n\n"
|
|
208
|
-
f"This location survives pip upgrades.\n"
|
|
209
|
-
f"You can safely delete this old folder.\n"
|
|
210
|
-
f"Migration date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
return True
|
|
214
|
-
|
|
215
|
-
except Exception as e:
|
|
216
|
-
print(f"[Migration] ⚠️ Error during migration: {e}")
|
|
217
|
-
print(f"[Migration] Your data is still in: {old_path}")
|
|
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():
|
|
218
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
|
|
219
206
|
|
|
220
207
|
import threading
|
|
221
208
|
import time # For delays in Superlookup
|
|
@@ -5307,43 +5294,48 @@ class SupervertalerQt(QMainWindow):
|
|
|
5307
5294
|
# ============================================================================
|
|
5308
5295
|
# USER DATA PATH INITIALIZATION
|
|
5309
5296
|
# ============================================================================
|
|
5310
|
-
#
|
|
5311
|
-
# -
|
|
5312
|
-
# -
|
|
5313
|
-
# -
|
|
5314
|
-
# -
|
|
5315
|
-
# - Dev: user_data_private/ or user_data/ next to script
|
|
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
|
|
5316
5302
|
from modules.database_manager import DatabaseManager
|
|
5317
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
|
+
|
|
5318
5308
|
if ENABLE_PRIVATE_FEATURES:
|
|
5319
5309
|
# Developer mode: use private folder (git-ignored)
|
|
5320
5310
|
self.user_data_path = Path(__file__).parent / "user_data_private"
|
|
5311
|
+
self._needs_data_location_dialog = False # Dev mode doesn't need dialog
|
|
5321
5312
|
else:
|
|
5322
|
-
# Normal mode: use
|
|
5313
|
+
# Normal mode: use unified system (same for pip, EXE, etc.)
|
|
5323
5314
|
self.user_data_path = get_user_data_path()
|
|
5324
5315
|
|
|
5325
|
-
# Migrate old data from site-packages to new persistent location (pip users)
|
|
5326
|
-
# This is a one-time migration for users upgrading from older versions
|
|
5327
|
-
migrate_user_data_if_needed(self.user_data_path)
|
|
5328
|
-
|
|
5329
5316
|
# Ensure user_data directory exists (creates empty folder if missing)
|
|
5330
|
-
|
|
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)
|
|
5331
5320
|
|
|
5332
5321
|
print(f"[Data Paths] User data: {self.user_data_path}")
|
|
5333
5322
|
|
|
5334
5323
|
# Database Manager for Termbases
|
|
5335
5324
|
self.db_manager = DatabaseManager(
|
|
5336
|
-
db_path=str(self.user_data_path / "
|
|
5325
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5337
5326
|
log_callback=self.log
|
|
5338
5327
|
)
|
|
5339
|
-
|
|
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()
|
|
5340
5332
|
|
|
5341
5333
|
# TM Database - Initialize early so Superlookup works without a project loaded
|
|
5342
5334
|
from modules.translation_memory import TMDatabase
|
|
5343
5335
|
self.tm_database = TMDatabase(
|
|
5344
5336
|
source_lang=None, # Will be set when project is loaded
|
|
5345
5337
|
target_lang=None, # Will be set when project is loaded
|
|
5346
|
-
db_path=str(self.user_data_path / "
|
|
5338
|
+
db_path=str(self.user_data_path / "resources" / "supervertaler.db"),
|
|
5347
5339
|
log_callback=self.log
|
|
5348
5340
|
)
|
|
5349
5341
|
|
|
@@ -5460,11 +5452,184 @@ class SupervertalerQt(QMainWindow):
|
|
|
5460
5452
|
from PyQt6.QtCore import QTimer
|
|
5461
5453
|
QTimer.singleShot(2000, lambda: self._check_for_new_models(force=False)) # 2 second delay
|
|
5462
5454
|
|
|
5463
|
-
# First-run check - show
|
|
5464
|
-
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):
|
|
5465
5460
|
from PyQt6.QtCore import QTimer
|
|
5466
5461
|
QTimer.singleShot(500, self._show_first_run_welcome)
|
|
5467
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
|
+
|
|
5468
5633
|
def _show_first_run_welcome(self):
|
|
5469
5634
|
"""Show welcome message and Features tab on first run."""
|
|
5470
5635
|
try:
|
|
@@ -5886,7 +6051,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
5886
6051
|
quickmenu_items = lib.get_quickmenu_grid_prompts() or []
|
|
5887
6052
|
|
|
5888
6053
|
if not quickmenu_items:
|
|
5889
|
-
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.")
|
|
5890
6055
|
return
|
|
5891
6056
|
|
|
5892
6057
|
# Find the currently focused widget (source or target cell)
|
|
@@ -6567,7 +6732,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
6567
6732
|
go_resources_action.triggered.connect(lambda: self.main_tabs.setCurrentIndex(1) if hasattr(self, 'main_tabs') else None)
|
|
6568
6733
|
nav_menu.addAction(go_resources_action)
|
|
6569
6734
|
|
|
6570
|
-
go_prompt_manager_action = QAction("
|
|
6735
|
+
go_prompt_manager_action = QAction("⚡ &QuickMenu", self)
|
|
6571
6736
|
go_prompt_manager_action.triggered.connect(lambda: self.main_tabs.setCurrentIndex(2) if hasattr(self, 'main_tabs') else None)
|
|
6572
6737
|
nav_menu.addAction(go_prompt_manager_action)
|
|
6573
6738
|
|
|
@@ -7046,13 +7211,13 @@ class SupervertalerQt(QMainWindow):
|
|
|
7046
7211
|
resources_tab = self.create_resources_tab()
|
|
7047
7212
|
self.main_tabs.addTab(resources_tab, "🗂️ Resources")
|
|
7048
7213
|
|
|
7049
|
-
# ===== 3.
|
|
7050
|
-
# Unified
|
|
7214
|
+
# ===== 3. QUICKMENU TAB =====
|
|
7215
|
+
# Unified QuickMenu system: folder structure = menu hierarchy for in-app + global access
|
|
7051
7216
|
from modules.unified_prompt_manager_qt import UnifiedPromptManagerQt
|
|
7052
7217
|
prompt_widget = QWidget()
|
|
7053
7218
|
self.prompt_manager_qt = UnifiedPromptManagerQt(self, standalone=False)
|
|
7054
7219
|
self.prompt_manager_qt.create_tab(prompt_widget)
|
|
7055
|
-
self.main_tabs.addTab(prompt_widget, "
|
|
7220
|
+
self.main_tabs.addTab(prompt_widget, "⚡ QuickMenu")
|
|
7056
7221
|
|
|
7057
7222
|
# Keep backward compatibility reference
|
|
7058
7223
|
self.document_views_widget = self.main_tabs
|
|
@@ -7112,7 +7277,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
7112
7277
|
layout.addWidget(desc)
|
|
7113
7278
|
|
|
7114
7279
|
# Initialize NT manager
|
|
7115
|
-
nt_path = self.user_data_path / "
|
|
7280
|
+
nt_path = self.user_data_path / "resources" / "non_translatables"
|
|
7116
7281
|
nt_path.mkdir(parents=True, exist_ok=True)
|
|
7117
7282
|
self.nt_manager = NonTranslatablesManager(str(nt_path), self.log)
|
|
7118
7283
|
|
|
@@ -8456,7 +8621,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8456
8621
|
from modules.superbrowser import SuperbrowserWidget
|
|
8457
8622
|
|
|
8458
8623
|
# Create and return the Superbrowser widget
|
|
8459
|
-
superbrowser_widget = SuperbrowserWidget(parent=self)
|
|
8624
|
+
superbrowser_widget = SuperbrowserWidget(parent=self, user_data_path=self.user_data_path)
|
|
8460
8625
|
|
|
8461
8626
|
return superbrowser_widget
|
|
8462
8627
|
|
|
@@ -8743,7 +8908,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8743
8908
|
|
|
8744
8909
|
# Create new Superlookup instance for detached window
|
|
8745
8910
|
# Or move the existing one - better to create new to avoid widget parenting issues
|
|
8746
|
-
detached_lookup = SuperlookupTab(self.lookup_detached_window)
|
|
8911
|
+
detached_lookup = SuperlookupTab(self.lookup_detached_window, user_data_path=self.user_data_path)
|
|
8747
8912
|
|
|
8748
8913
|
# Explicitly copy theme_manager reference
|
|
8749
8914
|
detached_lookup.theme_manager = self.theme_manager
|
|
@@ -8880,7 +9045,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
8880
9045
|
# Superdocs removed (online GitBook will be used instead)
|
|
8881
9046
|
|
|
8882
9047
|
print("[DEBUG] About to create SuperlookupTab...")
|
|
8883
|
-
lookup_tab = SuperlookupTab(self)
|
|
9048
|
+
lookup_tab = SuperlookupTab(self, user_data_path=self.user_data_path)
|
|
8884
9049
|
print("[DEBUG] SuperlookupTab created successfully")
|
|
8885
9050
|
self.lookup_tab = lookup_tab # Store reference for later use
|
|
8886
9051
|
modules_tabs.addTab(lookup_tab, "🔍 Superlookup")
|
|
@@ -9324,58 +9489,139 @@ class SupervertalerQt(QMainWindow):
|
|
|
9324
9489
|
# Show progress dialog
|
|
9325
9490
|
from PyQt6.QtWidgets import QProgressDialog
|
|
9326
9491
|
from PyQt6.QtCore import Qt
|
|
9492
|
+
import time
|
|
9327
9493
|
|
|
9328
|
-
|
|
9494
|
+
# Create progress dialog with cancel button
|
|
9495
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
9329
9496
|
progress.setWindowTitle("TMX Import")
|
|
9330
9497
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
9331
9498
|
progress.setMinimumDuration(0)
|
|
9499
|
+
progress.setAutoClose(False)
|
|
9500
|
+
progress.setAutoReset(False)
|
|
9332
9501
|
progress.setValue(0)
|
|
9333
9502
|
progress.show()
|
|
9334
9503
|
QApplication.processEvents()
|
|
9335
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
|
+
|
|
9336
9535
|
if choice == 'import_strip':
|
|
9337
9536
|
# Import with variant stripping
|
|
9338
9537
|
tm_id, count = self.tm_database.load_tmx_file(
|
|
9339
9538
|
file_path, source_lang, target_lang,
|
|
9340
|
-
tm_name=None, read_only=False
|
|
9539
|
+
tm_name=None, read_only=False, progress_callback=update_progress
|
|
9341
9540
|
)
|
|
9342
9541
|
progress.close()
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
self,
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
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:,}")
|
|
9349
9553
|
elif choice == 'create_new':
|
|
9350
9554
|
# Create new TM with variant languages
|
|
9351
9555
|
tm_id, count = self.tm_database.load_tmx_file(
|
|
9352
9556
|
file_path, compat['tmx_source'], compat['tmx_target'],
|
|
9353
|
-
tm_name=None, read_only=False
|
|
9557
|
+
tm_name=None, read_only=False, progress_callback=update_progress
|
|
9354
9558
|
)
|
|
9355
9559
|
progress.close()
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
self
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
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:,}")
|
|
9362
9571
|
else:
|
|
9363
|
-
# Exact match - proceed normally with progress
|
|
9572
|
+
# Exact match - proceed normally with real-time progress
|
|
9364
9573
|
from PyQt6.QtWidgets import QProgressDialog
|
|
9365
9574
|
from PyQt6.QtCore import Qt
|
|
9575
|
+
import time
|
|
9366
9576
|
|
|
9367
|
-
progress = QProgressDialog("
|
|
9577
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
9368
9578
|
progress.setWindowTitle("TMX Import")
|
|
9369
9579
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
9370
9580
|
progress.setMinimumDuration(0)
|
|
9581
|
+
progress.setAutoClose(False)
|
|
9582
|
+
progress.setAutoReset(False)
|
|
9371
9583
|
progress.setValue(0)
|
|
9372
9584
|
progress.show()
|
|
9373
9585
|
QApplication.processEvents()
|
|
9374
9586
|
|
|
9375
|
-
|
|
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)
|
|
9376
9617
|
progress.close()
|
|
9377
|
-
|
|
9378
|
-
|
|
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:,}")
|
|
9379
9625
|
|
|
9380
9626
|
except Exception as e:
|
|
9381
9627
|
self.log(f"Error importing TMX file: {e}")
|
|
@@ -10507,7 +10753,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
10507
10753
|
if reply2 == QMessageBox.StandardButton.Yes and self.tm_database:
|
|
10508
10754
|
# Clear TM entries
|
|
10509
10755
|
import sqlite3
|
|
10510
|
-
db_path = self.user_data_path / "
|
|
10756
|
+
db_path = self.user_data_path / "resources" / "supervertaler.db"
|
|
10511
10757
|
conn = sqlite3.connect(str(db_path))
|
|
10512
10758
|
cursor = conn.cursor()
|
|
10513
10759
|
cursor.execute("DELETE FROM translation_units")
|
|
@@ -13606,29 +13852,66 @@ class SupervertalerQt(QMainWindow):
|
|
|
13606
13852
|
if reply != QMessageBox.StandardButton.Yes:
|
|
13607
13853
|
return
|
|
13608
13854
|
|
|
13609
|
-
# Import into existing TM with progress dialog
|
|
13855
|
+
# Import into existing TM with real-time progress dialog
|
|
13610
13856
|
from PyQt6.QtWidgets import QProgressDialog
|
|
13611
13857
|
from PyQt6.QtCore import Qt
|
|
13858
|
+
import time
|
|
13612
13859
|
|
|
13613
|
-
progress = QProgressDialog("
|
|
13860
|
+
progress = QProgressDialog("Counting translation units...", "Cancel", 0, 100, self)
|
|
13614
13861
|
progress.setWindowTitle("TMX Import")
|
|
13615
13862
|
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
13616
13863
|
progress.setMinimumDuration(0)
|
|
13864
|
+
progress.setAutoClose(False)
|
|
13865
|
+
progress.setAutoReset(False)
|
|
13617
13866
|
progress.setValue(0)
|
|
13618
13867
|
progress.show()
|
|
13619
13868
|
QApplication.processEvents()
|
|
13620
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
|
+
|
|
13621
13899
|
count = self.tm_database._load_tmx_into_db(
|
|
13622
|
-
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
|
|
13623
13902
|
)
|
|
13624
13903
|
|
|
13625
13904
|
progress.close()
|
|
13626
13905
|
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
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:,}")
|
|
13632
13915
|
refresh_callback()
|
|
13633
13916
|
else:
|
|
13634
13917
|
QMessageBox.critical(self, "Error", "TM database not available")
|
|
@@ -14408,7 +14691,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
14408
14691
|
auto_markdown_cb.setChecked(general_settings.get('auto_generate_markdown', False))
|
|
14409
14692
|
auto_markdown_cb.setToolTip(
|
|
14410
14693
|
"Automatically convert imported documents to markdown format\n"
|
|
14411
|
-
"for AI Assistant access in user_data_private/
|
|
14694
|
+
"for AI Assistant access in user_data_private/ai_assistant/current_document/"
|
|
14412
14695
|
)
|
|
14413
14696
|
behavior_layout.addWidget(auto_markdown_cb)
|
|
14414
14697
|
|
|
@@ -14620,7 +14903,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
14620
14903
|
|
|
14621
14904
|
def _create_general_settings_tab(self):
|
|
14622
14905
|
"""Create General Settings tab content"""
|
|
14623
|
-
from PyQt6.QtWidgets import QCheckBox, QGroupBox, QPushButton
|
|
14906
|
+
from PyQt6.QtWidgets import QCheckBox, QGroupBox, QPushButton, QLineEdit, QFileDialog
|
|
14624
14907
|
|
|
14625
14908
|
tab = QWidget()
|
|
14626
14909
|
layout = QVBoxLayout(tab)
|
|
@@ -14650,6 +14933,119 @@ class SupervertalerQt(QMainWindow):
|
|
|
14650
14933
|
startup_group.setLayout(startup_layout)
|
|
14651
14934
|
layout.addWidget(startup_group)
|
|
14652
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
|
+
|
|
14653
15049
|
# Sound Effects group
|
|
14654
15050
|
sound_group = QGroupBox("🔊 Sound Effects")
|
|
14655
15051
|
sound_layout = QVBoxLayout()
|
|
@@ -16685,7 +17081,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
16685
17081
|
prompt_text = self.prompt_manager_qt.get_system_template(mode_key)
|
|
16686
17082
|
else:
|
|
16687
17083
|
# Fallback: load from JSON file
|
|
16688
|
-
system_prompts_file = self.user_data_path / "
|
|
17084
|
+
system_prompts_file = self.user_data_path / "prompt_library" / "system_prompts_layer1.json"
|
|
16689
17085
|
if system_prompts_file.exists():
|
|
16690
17086
|
import json
|
|
16691
17087
|
with open(system_prompts_file, 'r', encoding='utf-8') as f:
|
|
@@ -16714,7 +17110,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
16714
17110
|
|
|
16715
17111
|
# Always save to JSON file
|
|
16716
17112
|
import json
|
|
16717
|
-
system_prompts_file = self.user_data_path / "
|
|
17113
|
+
system_prompts_file = self.user_data_path / "prompt_library" / "system_prompts_layer1.json"
|
|
16718
17114
|
system_prompts_file.parent.mkdir(parents=True, exist_ok=True)
|
|
16719
17115
|
|
|
16720
17116
|
# Load existing prompts
|
|
@@ -29775,7 +30171,7 @@ class SupervertalerQt(QMainWindow):
|
|
|
29775
30171
|
layout = QVBoxLayout(dialog)
|
|
29776
30172
|
|
|
29777
30173
|
# Header
|
|
29778
|
-
header = QLabel("<h3>✅ AI-
|
|
30174
|
+
header = QLabel("<h3>✅ AI-Enhanced Translation Proofreading</h3>")
|
|
29779
30175
|
layout.addWidget(header)
|
|
29780
30176
|
|
|
29781
30177
|
info_label = QLabel(
|
|
@@ -36493,7 +36889,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36493
36889
|
subtab_name: Name of the sub-tab to navigate to (e.g., "AI Settings")
|
|
36494
36890
|
"""
|
|
36495
36891
|
if hasattr(self, 'main_tabs'):
|
|
36496
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36892
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36497
36893
|
self.main_tabs.setCurrentIndex(4)
|
|
36498
36894
|
|
|
36499
36895
|
# Navigate to specific sub-tab if requested
|
|
@@ -36514,7 +36910,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36514
36910
|
def _go_to_superlookup(self):
|
|
36515
36911
|
"""Navigate to Superlookup in Tools tab"""
|
|
36516
36912
|
if hasattr(self, 'main_tabs'):
|
|
36517
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36913
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36518
36914
|
self.main_tabs.setCurrentIndex(3) # Switch to Tools tab
|
|
36519
36915
|
# Then switch to Superlookup sub-tab
|
|
36520
36916
|
if hasattr(self, 'modules_tabs'):
|
|
@@ -36527,7 +36923,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36527
36923
|
def _navigate_to_tool(self, tool_name: str):
|
|
36528
36924
|
"""Navigate to a specific tool in the Tools tab"""
|
|
36529
36925
|
if hasattr(self, 'main_tabs'):
|
|
36530
|
-
# Main tabs: Grid=0, Resources=1,
|
|
36926
|
+
# Main tabs: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
36531
36927
|
self.main_tabs.setCurrentIndex(3) # Switch to Tools tab
|
|
36532
36928
|
# Then switch to the specific tool sub-tab
|
|
36533
36929
|
if hasattr(self, 'modules_tabs'):
|
|
@@ -36760,7 +37156,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
36760
37156
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
36761
37157
|
dialog,
|
|
36762
37158
|
"Select TMX File",
|
|
36763
|
-
str(self.user_data_path / "
|
|
37159
|
+
str(self.user_data_path / "resources"),
|
|
36764
37160
|
"TMX Files (*.tmx);;All Files (*.*)"
|
|
36765
37161
|
)
|
|
36766
37162
|
if file_path:
|
|
@@ -37546,7 +37942,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
|
|
|
37546
37942
|
layout.addWidget(title)
|
|
37547
37943
|
|
|
37548
37944
|
# Description
|
|
37549
|
-
desc = QLabel("<p><b>AI-
|
|
37945
|
+
desc = QLabel("<p><b>AI-enhanced tool for translators & writers</b></p>")
|
|
37550
37946
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
37551
37947
|
layout.addWidget(desc)
|
|
37552
37948
|
|
|
@@ -41333,9 +41729,10 @@ class SuperlookupTab(QWidget):
|
|
|
41333
41729
|
Works anywhere on your computer: in CAT tools, browsers, Word, any text box
|
|
41334
41730
|
"""
|
|
41335
41731
|
|
|
41336
|
-
def __init__(self, parent=None):
|
|
41732
|
+
def __init__(self, parent=None, user_data_path=None):
|
|
41337
41733
|
super().__init__(parent)
|
|
41338
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
|
|
41339
41736
|
|
|
41340
41737
|
print("[Superlookup] SuperlookupTab.__init__ called")
|
|
41341
41738
|
|
|
@@ -41507,31 +41904,10 @@ class SuperlookupTab(QWidget):
|
|
|
41507
41904
|
|
|
41508
41905
|
layout.addLayout(search_row)
|
|
41509
41906
|
|
|
41510
|
-
# Options row (
|
|
41907
|
+
# Options row (language filters only) - compact single line
|
|
41511
41908
|
options_layout = QHBoxLayout()
|
|
41512
41909
|
options_layout.setSpacing(8)
|
|
41513
41910
|
|
|
41514
|
-
# Search direction selector
|
|
41515
|
-
direction_label = QLabel("Direction:")
|
|
41516
|
-
direction_label.setStyleSheet("font-weight: bold; font-size: 9pt;")
|
|
41517
|
-
options_layout.addWidget(direction_label)
|
|
41518
|
-
|
|
41519
|
-
self.direction_both = CheckmarkRadioButton("Both")
|
|
41520
|
-
self.direction_both.setChecked(True) # Default
|
|
41521
|
-
self.direction_both.setToolTip("Search in both source and target text (bidirectional)")
|
|
41522
|
-
options_layout.addWidget(self.direction_both)
|
|
41523
|
-
|
|
41524
|
-
self.direction_source = CheckmarkRadioButton("Source")
|
|
41525
|
-
self.direction_source.setToolTip("Search only in source language text")
|
|
41526
|
-
options_layout.addWidget(self.direction_source)
|
|
41527
|
-
|
|
41528
|
-
self.direction_target = CheckmarkRadioButton("Target")
|
|
41529
|
-
self.direction_target.setToolTip("Search only in target language text")
|
|
41530
|
-
options_layout.addWidget(self.direction_target)
|
|
41531
|
-
|
|
41532
|
-
# Spacer
|
|
41533
|
-
options_layout.addSpacing(15)
|
|
41534
|
-
|
|
41535
41911
|
# Language filter dropdowns
|
|
41536
41912
|
from_label = QLabel("From:")
|
|
41537
41913
|
from_label.setStyleSheet("font-weight: bold; font-size: 9pt;")
|
|
@@ -42043,7 +42419,11 @@ class SuperlookupTab(QWidget):
|
|
|
42043
42419
|
self.web_engine_available = True
|
|
42044
42420
|
|
|
42045
42421
|
# Create persistent profile for login/cookie storage
|
|
42046
|
-
|
|
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')
|
|
42047
42427
|
os.makedirs(storage_path, exist_ok=True)
|
|
42048
42428
|
self.web_profile = QWebEngineProfile("SuperlookupProfile", self)
|
|
42049
42429
|
self.web_profile.setPersistentStoragePath(storage_path)
|
|
@@ -43307,17 +43687,10 @@ class SuperlookupTab(QWidget):
|
|
|
43307
43687
|
def get_search_direction(self):
|
|
43308
43688
|
"""Get the current search direction setting.
|
|
43309
43689
|
|
|
43310
|
-
|
|
43311
|
-
|
|
43312
|
-
'source' - search source text only
|
|
43313
|
-
'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.
|
|
43314
43692
|
"""
|
|
43315
|
-
|
|
43316
|
-
return 'source'
|
|
43317
|
-
elif hasattr(self, 'direction_target') and self.direction_target.isChecked():
|
|
43318
|
-
return 'target'
|
|
43319
|
-
else:
|
|
43320
|
-
return 'both'
|
|
43693
|
+
return 'both'
|
|
43321
43694
|
|
|
43322
43695
|
def _on_language_changed(self):
|
|
43323
43696
|
"""Handle language dropdown change - update Web Resources info"""
|
|
@@ -45074,7 +45447,7 @@ class SuperlookupTab(QWidget):
|
|
|
45074
45447
|
print(f"[Superlookup] Has main_tabs: {hasattr(main_window, 'main_tabs')}")
|
|
45075
45448
|
|
|
45076
45449
|
# Switch to Tools tab (main_tabs index 3)
|
|
45077
|
-
# Tab structure: Grid=0, Resources=1,
|
|
45450
|
+
# Tab structure: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
|
|
45078
45451
|
if hasattr(main_window, 'main_tabs'):
|
|
45079
45452
|
print(f"[Superlookup] Current main_tab index: {main_window.main_tabs.currentIndex()}")
|
|
45080
45453
|
main_window.main_tabs.setCurrentIndex(3) # Tools tab
|