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
modules/ai_attachment_manager.py
CHANGED
|
@@ -29,15 +29,15 @@ class AttachmentManager:
|
|
|
29
29
|
Initialize the AttachmentManager.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
|
-
base_dir: Base directory for attachments (default: user_data_private/
|
|
32
|
+
base_dir: Base directory for attachments (default: user_data_private/ai_assistant)
|
|
33
33
|
log_callback: Function to call for logging messages
|
|
34
34
|
"""
|
|
35
35
|
self.log = log_callback if log_callback else print
|
|
36
36
|
|
|
37
37
|
# Set base directory
|
|
38
38
|
if base_dir is None:
|
|
39
|
-
# Default to user_data_private/
|
|
40
|
-
base_dir = Path("user_data_private") / "
|
|
39
|
+
# Default to user_data_private/ai_assistant
|
|
40
|
+
base_dir = Path("user_data_private") / "ai_assistant"
|
|
41
41
|
|
|
42
42
|
self.base_dir = Path(base_dir)
|
|
43
43
|
self.attachments_dir = self.base_dir / "attachments"
|
modules/config_manager.py
CHANGED
|
@@ -35,14 +35,14 @@ class ConfigManager:
|
|
|
35
35
|
REQUIRED_FOLDERS = [
|
|
36
36
|
# Note: Old numbered folders (1_System_Prompts, 2_Domain_Prompts, etc.) are deprecated
|
|
37
37
|
# Migration moves them to unified Library structure
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
38
|
+
"prompt_library/domain_expertise",
|
|
39
|
+
"prompt_library/project_prompts",
|
|
40
|
+
"prompt_library/style_guides",
|
|
41
|
+
"resources/termbases",
|
|
42
|
+
"resources/tms",
|
|
43
|
+
"resources/non_translatables",
|
|
44
|
+
"resources/segmentation_rules",
|
|
45
|
+
"projects",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
def __init__(self):
|
|
@@ -268,8 +268,8 @@ class ConfigManager:
|
|
|
268
268
|
Get the full path to a subfolder in user_data.
|
|
269
269
|
|
|
270
270
|
Example:
|
|
271
|
-
config.get_subfolder_path('
|
|
272
|
-
-> '/home/user/
|
|
271
|
+
config.get_subfolder_path('resources/tms')
|
|
272
|
+
-> '/home/user/Supervertaler/resources/tms'
|
|
273
273
|
"""
|
|
274
274
|
user_data_path = self.get_user_data_path()
|
|
275
275
|
full_path = os.path.join(user_data_path, subfolder)
|
modules/database_manager.py
CHANGED
|
@@ -1124,6 +1124,12 @@ class DatabaseManager:
|
|
|
1124
1124
|
Uses FTS5 full-text search for fast matching on millions of segments.
|
|
1125
1125
|
Falls back to LIKE queries if FTS5 fails.
|
|
1126
1126
|
|
|
1127
|
+
Language filters define what you're searching FOR and what translation you want:
|
|
1128
|
+
- "From: Dutch, To: English" = Search for Dutch text, show English translations
|
|
1129
|
+
- Searches ALL TMs (regardless of their stored language pair direction)
|
|
1130
|
+
- Automatically swaps columns when needed (e.g., finds Dutch in target column of EN→NL TM)
|
|
1131
|
+
- This is MORE intuitive than traditional CAT tools that only search specific TM directions
|
|
1132
|
+
|
|
1127
1133
|
Args:
|
|
1128
1134
|
query: Text to search for
|
|
1129
1135
|
tm_ids: List of TM IDs to search (None = all)
|
|
@@ -1141,6 +1147,12 @@ class DatabaseManager:
|
|
|
1141
1147
|
# Wrap in quotes for phrase search
|
|
1142
1148
|
fts_query = f'"{fts_query}"'
|
|
1143
1149
|
|
|
1150
|
+
# When language filters specified, we need to search intelligently:
|
|
1151
|
+
# - Don't filter by TM language pair (search ALL TMs)
|
|
1152
|
+
# - Search in BOTH columns to find text
|
|
1153
|
+
# - Swap columns if needed to show correct language order
|
|
1154
|
+
use_smart_search = (source_langs or target_langs)
|
|
1155
|
+
|
|
1144
1156
|
try:
|
|
1145
1157
|
# Use FTS5 for fast full-text search
|
|
1146
1158
|
if direction == 'source':
|
|
@@ -1171,20 +1183,105 @@ class DatabaseManager:
|
|
|
1171
1183
|
fts_sql += f" AND tu.tm_id IN ({placeholders})"
|
|
1172
1184
|
params.extend(tm_ids)
|
|
1173
1185
|
|
|
1174
|
-
#
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1186
|
+
# DON'T filter by language when smart search active
|
|
1187
|
+
# (we need to search all TMs and figure out which column has our language)
|
|
1188
|
+
if not use_smart_search:
|
|
1189
|
+
# Traditional filtering when no language filters
|
|
1190
|
+
if source_langs:
|
|
1191
|
+
placeholders = ','.join('?' * len(source_langs))
|
|
1192
|
+
fts_sql += f" AND tu.source_lang IN ({placeholders})"
|
|
1193
|
+
params.extend(source_langs)
|
|
1194
|
+
if target_langs:
|
|
1195
|
+
placeholders = ','.join('?' * len(target_langs))
|
|
1196
|
+
fts_sql += f" AND tu.target_lang IN ({placeholders})"
|
|
1197
|
+
params.extend(target_langs)
|
|
1183
1198
|
|
|
1184
1199
|
fts_sql += " ORDER BY tu.modified_date DESC LIMIT 100"
|
|
1185
1200
|
|
|
1186
1201
|
self.cursor.execute(fts_sql, params)
|
|
1187
|
-
|
|
1202
|
+
raw_results = [dict(row) for row in self.cursor.fetchall()]
|
|
1203
|
+
|
|
1204
|
+
# Smart search: Filter and swap based on language metadata
|
|
1205
|
+
if use_smart_search:
|
|
1206
|
+
processed_results = []
|
|
1207
|
+
for row in raw_results:
|
|
1208
|
+
row_src_lang = row.get('source_lang', '')
|
|
1209
|
+
row_tgt_lang = row.get('target_lang', '')
|
|
1210
|
+
|
|
1211
|
+
# Check if this row matches our language requirements
|
|
1212
|
+
# If "From: Dutch, To: English":
|
|
1213
|
+
# - Accept if source=nl and target=en (normal)
|
|
1214
|
+
# - Accept if source=en and target=nl (swap needed)
|
|
1215
|
+
|
|
1216
|
+
matches = False
|
|
1217
|
+
needs_swap = False
|
|
1218
|
+
|
|
1219
|
+
if source_langs and target_langs:
|
|
1220
|
+
# Both filters specified
|
|
1221
|
+
if row_src_lang in source_langs and row_tgt_lang in target_langs:
|
|
1222
|
+
# Perfect match - no swap
|
|
1223
|
+
matches = True
|
|
1224
|
+
needs_swap = False
|
|
1225
|
+
elif row_src_lang in target_langs and row_tgt_lang in source_langs:
|
|
1226
|
+
# Reversed - needs swap
|
|
1227
|
+
matches = True
|
|
1228
|
+
needs_swap = True
|
|
1229
|
+
elif source_langs:
|
|
1230
|
+
# Only "From" specified - just check if Dutch is in EITHER column
|
|
1231
|
+
if row_src_lang in source_langs:
|
|
1232
|
+
matches = True
|
|
1233
|
+
needs_swap = False
|
|
1234
|
+
elif row_tgt_lang in source_langs:
|
|
1235
|
+
matches = True
|
|
1236
|
+
needs_swap = True
|
|
1237
|
+
elif target_langs:
|
|
1238
|
+
# Only "To" specified - just check if English is in EITHER column
|
|
1239
|
+
if row_tgt_lang in target_langs:
|
|
1240
|
+
matches = True
|
|
1241
|
+
needs_swap = False
|
|
1242
|
+
elif row_src_lang in target_langs:
|
|
1243
|
+
matches = True
|
|
1244
|
+
needs_swap = True
|
|
1245
|
+
|
|
1246
|
+
if matches:
|
|
1247
|
+
# CRITICAL CHECK: Verify the search text is actually in the correct column
|
|
1248
|
+
# If user searches for Dutch with "From: Dutch", the text must be in the source column (after any swap)
|
|
1249
|
+
# This prevents finding Dutch text when user asks to search FOR English
|
|
1250
|
+
|
|
1251
|
+
if needs_swap:
|
|
1252
|
+
# After swap, check if query is in the NEW source column (was target)
|
|
1253
|
+
text_to_check = row['target_text'].lower()
|
|
1254
|
+
else:
|
|
1255
|
+
# No swap, check if query is in source column
|
|
1256
|
+
text_to_check = row['source_text'].lower()
|
|
1257
|
+
|
|
1258
|
+
# Only include if query text is actually in the source column
|
|
1259
|
+
if query.lower() in text_to_check:
|
|
1260
|
+
if needs_swap:
|
|
1261
|
+
# Swap columns to show correct language order
|
|
1262
|
+
swapped_row = row.copy()
|
|
1263
|
+
swapped_row['source'] = row['target_text']
|
|
1264
|
+
swapped_row['target'] = row['source_text']
|
|
1265
|
+
swapped_row['source_lang'] = row['target_lang']
|
|
1266
|
+
swapped_row['target_lang'] = row['source_lang']
|
|
1267
|
+
processed_results.append(swapped_row)
|
|
1268
|
+
else:
|
|
1269
|
+
# No swap needed - just rename columns
|
|
1270
|
+
processed_row = row.copy()
|
|
1271
|
+
processed_row['source'] = row['source_text']
|
|
1272
|
+
processed_row['target'] = row['target_text']
|
|
1273
|
+
processed_results.append(processed_row)
|
|
1274
|
+
|
|
1275
|
+
return processed_results
|
|
1276
|
+
else:
|
|
1277
|
+
# No language filters - just rename columns
|
|
1278
|
+
processed_results = []
|
|
1279
|
+
for row in raw_results:
|
|
1280
|
+
processed_row = row.copy()
|
|
1281
|
+
processed_row['source'] = row['source_text']
|
|
1282
|
+
processed_row['target'] = row['target_text']
|
|
1283
|
+
processed_results.append(processed_row)
|
|
1284
|
+
return processed_results
|
|
1188
1285
|
|
|
1189
1286
|
except Exception as e:
|
|
1190
1287
|
# Fallback to LIKE query if FTS5 fails (e.g., index not built)
|
|
@@ -172,7 +172,7 @@ class NonTranslatablesManager:
|
|
|
172
172
|
Initialize manager.
|
|
173
173
|
|
|
174
174
|
Args:
|
|
175
|
-
base_path: Base path for NT files (typically user_data/
|
|
175
|
+
base_path: Base path for NT files (typically user_data/resources/non_translatables)
|
|
176
176
|
log_callback: Optional logging function
|
|
177
177
|
"""
|
|
178
178
|
self.base_path = Path(base_path)
|
|
@@ -29,7 +29,7 @@ class PromptLibraryMigration:
|
|
|
29
29
|
def __init__(self, prompt_library_dir: str, log_callback=None):
|
|
30
30
|
"""
|
|
31
31
|
Args:
|
|
32
|
-
prompt_library_dir: Path to user_data/
|
|
32
|
+
prompt_library_dir: Path to user_data/prompt_library
|
|
33
33
|
log_callback: Function for logging
|
|
34
34
|
"""
|
|
35
35
|
self.prompt_library_dir = Path(prompt_library_dir)
|
modules/setup_wizard.py
CHANGED
|
@@ -80,17 +80,17 @@ class SetupWizard:
|
|
|
80
80
|
"Supervertaler will create the following structure:\n\n"
|
|
81
81
|
f"{self.selected_path}\n"
|
|
82
82
|
f" ├── api_keys.txt\n"
|
|
83
|
-
f" ├──
|
|
83
|
+
f" ├── prompt_library/\n"
|
|
84
84
|
f" │ ├── 1_System_Prompts/\n"
|
|
85
85
|
f" │ ├── 2_Domain_Prompts/\n"
|
|
86
86
|
f" │ ├── 3_Project_Prompts/\n"
|
|
87
87
|
f" │ └── 4_Style_Guides/\n"
|
|
88
|
-
f" ├──
|
|
88
|
+
f" ├── resources/\n"
|
|
89
89
|
f" │ ├── TMs/\n"
|
|
90
90
|
f" │ ├── Glossaries/\n"
|
|
91
|
-
f" │ ├──
|
|
92
|
-
f" │ └──
|
|
93
|
-
f" └──
|
|
91
|
+
f" │ ├── non_translatables/\n"
|
|
92
|
+
f" │ └── segmentation_rules/\n"
|
|
93
|
+
f" └── projects/\n\n"
|
|
94
94
|
"Is this correct?"
|
|
95
95
|
)
|
|
96
96
|
|
|
@@ -140,9 +140,9 @@ class SetupWizard:
|
|
|
140
140
|
f"Your data folder: {self.selected_path}\n\n"
|
|
141
141
|
f"Created:\n"
|
|
142
142
|
f" • api_keys.txt (add your API keys here)\n"
|
|
143
|
-
f" •
|
|
144
|
-
f" •
|
|
145
|
-
f" •
|
|
143
|
+
f" • prompt_library/ (your prompts)\n"
|
|
144
|
+
f" • resources/ (TMs, glossaries)\n"
|
|
145
|
+
f" • projects/ (your work)\n\n"
|
|
146
146
|
f"All your translation memories, prompts, and projects\n"
|
|
147
147
|
f"will be stored in this location."
|
|
148
148
|
)
|
modules/superbrowser.py
CHANGED
|
@@ -45,11 +45,12 @@ def _clear_corrupted_cache(storage_path: str):
|
|
|
45
45
|
class ChatColumn(QWidget):
|
|
46
46
|
"""A column containing a chat interface with web browser"""
|
|
47
47
|
|
|
48
|
-
def __init__(self, title, url, header_color, parent=None):
|
|
48
|
+
def __init__(self, title, url, header_color, parent=None, user_data_path=None):
|
|
49
49
|
super().__init__(parent)
|
|
50
50
|
self.title = title
|
|
51
51
|
self.url = url
|
|
52
52
|
self.header_color = header_color
|
|
53
|
+
self.user_data_path = user_data_path # Store user data path
|
|
53
54
|
self.init_ui()
|
|
54
55
|
|
|
55
56
|
def init_ui(self):
|
|
@@ -102,12 +103,14 @@ class ChatColumn(QWidget):
|
|
|
102
103
|
profile_name = f"superbrowser_{self.title.lower()}"
|
|
103
104
|
self.profile = QWebEngineProfile(profile_name, self)
|
|
104
105
|
|
|
105
|
-
# Set persistent storage path
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
# Set persistent storage path using user_data_path from parent
|
|
107
|
+
if self.user_data_path:
|
|
108
|
+
storage_path = os.path.join(str(self.user_data_path), "superbrowser_profiles", profile_name)
|
|
109
|
+
else:
|
|
110
|
+
# Fallback to script directory if user_data_path not provided
|
|
111
|
+
dev_mode_marker = os.path.join(os.path.dirname(__file__), "..", ".supervertaler.local")
|
|
112
|
+
base_folder = "user_data_private" if os.path.exists(dev_mode_marker) else "user_data"
|
|
113
|
+
storage_path = os.path.join(os.path.dirname(__file__), "..", base_folder, "superbrowser_profiles", profile_name)
|
|
111
114
|
os.makedirs(storage_path, exist_ok=True)
|
|
112
115
|
|
|
113
116
|
# Clear potentially corrupted cache to prevent Chromium errors
|
|
@@ -166,9 +169,10 @@ class SuperbrowserWidget(QWidget):
|
|
|
166
169
|
and concurrent interaction with different AI models.
|
|
167
170
|
"""
|
|
168
171
|
|
|
169
|
-
def __init__(self, parent=None):
|
|
172
|
+
def __init__(self, parent=None, user_data_path=None):
|
|
170
173
|
super().__init__(parent)
|
|
171
174
|
self.parent_window = parent
|
|
175
|
+
self.user_data_path = user_data_path # Store user data path for profiles
|
|
172
176
|
|
|
173
177
|
# Default URLs for AI chat interfaces
|
|
174
178
|
self.chatgpt_url = "https://chatgpt.com/"
|
|
@@ -257,10 +261,10 @@ class SuperbrowserWidget(QWidget):
|
|
|
257
261
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
258
262
|
splitter.setHandleWidth(3)
|
|
259
263
|
|
|
260
|
-
# Create chat columns
|
|
261
|
-
self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self)
|
|
262
|
-
self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self)
|
|
263
|
-
self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self)
|
|
264
|
+
# Create chat columns - pass user_data_path for profile storage
|
|
265
|
+
self.chatgpt_column = ChatColumn("ChatGPT", self.chatgpt_url, "#10a37f", self, user_data_path=self.user_data_path)
|
|
266
|
+
self.claude_column = ChatColumn("Claude", self.claude_url, "#c17c4f", self, user_data_path=self.user_data_path)
|
|
267
|
+
self.gemini_column = ChatColumn("Gemini", self.gemini_url, "#4285f4", self, user_data_path=self.user_data_path)
|
|
264
268
|
|
|
265
269
|
# Add columns to splitter
|
|
266
270
|
splitter.addWidget(self.chatgpt_column)
|
modules/superlookup.py
CHANGED
|
@@ -157,9 +157,13 @@ class SuperlookupEngine:
|
|
|
157
157
|
|
|
158
158
|
# Convert to LookupResult format (limit results)
|
|
159
159
|
for match in matches[:max_results]:
|
|
160
|
+
# Use 'source' and 'target' keys (matches database column names)
|
|
161
|
+
source_text = match.get('source', '')
|
|
162
|
+
target_text = match.get('target', '')
|
|
163
|
+
print(f"[Superlookup] Extracted: source='{source_text[:50]}...', target='{target_text[:50]}...'")
|
|
160
164
|
results.append(LookupResult(
|
|
161
|
-
source=
|
|
162
|
-
target=
|
|
165
|
+
source=source_text,
|
|
166
|
+
target=target_text,
|
|
163
167
|
match_percent=100, # Concordance = contains the text
|
|
164
168
|
source_type='tm',
|
|
165
169
|
metadata={
|
modules/tmx_editor_qt.py
CHANGED
|
@@ -2655,7 +2655,7 @@ if __name__ == "__main__":
|
|
|
2655
2655
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".supervertaler.local")
|
|
2656
2656
|
)
|
|
2657
2657
|
user_data_path = Path("user_data_private" if ENABLE_PRIVATE_FEATURES else "user_data")
|
|
2658
|
-
db_path = user_data_path / "
|
|
2658
|
+
db_path = user_data_path / "resources" / "supervertaler.db"
|
|
2659
2659
|
|
|
2660
2660
|
# Ensure database directory exists
|
|
2661
2661
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
modules/translation_memory.py
CHANGED
|
@@ -401,7 +401,7 @@ class TMDatabase:
|
|
|
401
401
|
|
|
402
402
|
def load_tmx_file(self, filepath: str, src_lang: str, tgt_lang: str,
|
|
403
403
|
tm_name: str = None, read_only: bool = False,
|
|
404
|
-
strip_variants: bool = True) -> tuple[str, int]:
|
|
404
|
+
strip_variants: bool = True, progress_callback=None) -> tuple[str, int]:
|
|
405
405
|
"""
|
|
406
406
|
Load TMX file into a new custom TM
|
|
407
407
|
|
|
@@ -412,6 +412,7 @@ class TMDatabase:
|
|
|
412
412
|
tm_name: Custom name for TM (default: filename)
|
|
413
413
|
read_only: Make TM read-only
|
|
414
414
|
strip_variants: Match base languages ignoring regional variants (default: True)
|
|
415
|
+
progress_callback: Optional callback function(current, total, message) for progress updates
|
|
415
416
|
|
|
416
417
|
Returns: (tm_id, entry_count)
|
|
417
418
|
"""
|
|
@@ -423,16 +424,18 @@ class TMDatabase:
|
|
|
423
424
|
self.add_custom_tm(tm_name, tm_id, read_only=read_only)
|
|
424
425
|
|
|
425
426
|
# Load TMX content
|
|
426
|
-
loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id,
|
|
427
|
+
loaded_count = self._load_tmx_into_db(filepath, src_lang, tgt_lang, tm_id,
|
|
428
|
+
strip_variants=strip_variants,
|
|
429
|
+
progress_callback=progress_callback)
|
|
427
430
|
|
|
428
431
|
self.log(f"✓ Loaded {loaded_count} entries from {os.path.basename(filepath)}")
|
|
429
432
|
|
|
430
433
|
return tm_id, loaded_count
|
|
431
434
|
|
|
432
435
|
def _load_tmx_into_db(self, filepath: str, src_lang: str, tgt_lang: str, tm_id: str,
|
|
433
|
-
strip_variants: bool = False) -> int:
|
|
436
|
+
strip_variants: bool = False, progress_callback=None) -> int:
|
|
434
437
|
"""
|
|
435
|
-
Internal: Load TMX content into database
|
|
438
|
+
Internal: Load TMX content into database with chunked processing
|
|
436
439
|
|
|
437
440
|
Args:
|
|
438
441
|
filepath: Path to TMX file
|
|
@@ -440,12 +443,24 @@ class TMDatabase:
|
|
|
440
443
|
tgt_lang: Target target language code
|
|
441
444
|
tm_id: TM identifier
|
|
442
445
|
strip_variants: If True, match base languages ignoring regional variants
|
|
446
|
+
progress_callback: Optional callback function(current, total, message) for progress updates
|
|
443
447
|
"""
|
|
444
448
|
loaded_count = 0
|
|
449
|
+
chunk_size = 1000 # Process in chunks for responsiveness
|
|
450
|
+
chunk_buffer = []
|
|
445
451
|
|
|
446
452
|
try:
|
|
453
|
+
# First pass: count total TUs for progress bar
|
|
454
|
+
if progress_callback:
|
|
455
|
+
progress_callback(0, 0, "Counting translation units...")
|
|
456
|
+
|
|
447
457
|
tree = ET.parse(filepath)
|
|
448
458
|
root = tree.getroot()
|
|
459
|
+
total_tus = len(root.findall('.//tu'))
|
|
460
|
+
|
|
461
|
+
if progress_callback:
|
|
462
|
+
progress_callback(0, total_tus, f"Processing 0 / {total_tus:,} entries...")
|
|
463
|
+
|
|
449
464
|
xml_ns = "http://www.w3.org/XML/1998/namespace"
|
|
450
465
|
|
|
451
466
|
# Normalize language codes
|
|
@@ -458,6 +473,7 @@ class TMDatabase:
|
|
|
458
473
|
src_base = get_base_lang_code(src_lang_normalized)
|
|
459
474
|
tgt_base = get_base_lang_code(tgt_lang_normalized)
|
|
460
475
|
|
|
476
|
+
processed = 0
|
|
461
477
|
for tu in root.findall('.//tu'):
|
|
462
478
|
src_text, tgt_text = None, None
|
|
463
479
|
|
|
@@ -488,14 +504,43 @@ class TMDatabase:
|
|
|
488
504
|
tgt_text = text
|
|
489
505
|
|
|
490
506
|
if src_text and tgt_text:
|
|
507
|
+
chunk_buffer.append((src_text, tgt_text))
|
|
508
|
+
loaded_count += 1
|
|
509
|
+
|
|
510
|
+
# Process chunk when buffer is full
|
|
511
|
+
if len(chunk_buffer) >= chunk_size:
|
|
512
|
+
for src, tgt in chunk_buffer:
|
|
513
|
+
self.db.add_translation_unit(
|
|
514
|
+
source=src,
|
|
515
|
+
target=tgt,
|
|
516
|
+
source_lang=src_lang_normalized,
|
|
517
|
+
target_lang=tgt_lang_normalized,
|
|
518
|
+
tm_id=tm_id
|
|
519
|
+
)
|
|
520
|
+
chunk_buffer.clear()
|
|
521
|
+
|
|
522
|
+
# Update progress
|
|
523
|
+
if progress_callback:
|
|
524
|
+
progress_callback(processed + 1, total_tus,
|
|
525
|
+
f"Processing {loaded_count:,} / {total_tus:,} entries...")
|
|
526
|
+
|
|
527
|
+
processed += 1
|
|
528
|
+
|
|
529
|
+
# Process remaining entries in buffer
|
|
530
|
+
if chunk_buffer:
|
|
531
|
+
for src, tgt in chunk_buffer:
|
|
491
532
|
self.db.add_translation_unit(
|
|
492
|
-
source=
|
|
493
|
-
target=
|
|
533
|
+
source=src,
|
|
534
|
+
target=tgt,
|
|
494
535
|
source_lang=src_lang_normalized,
|
|
495
536
|
target_lang=tgt_lang_normalized,
|
|
496
537
|
tm_id=tm_id
|
|
497
538
|
)
|
|
498
|
-
|
|
539
|
+
chunk_buffer.clear()
|
|
540
|
+
|
|
541
|
+
# Final progress update
|
|
542
|
+
if progress_callback:
|
|
543
|
+
progress_callback(total_tus, total_tus, f"Completed: {loaded_count:,} entries imported")
|
|
499
544
|
|
|
500
545
|
return loaded_count
|
|
501
546
|
except Exception as e:
|
|
@@ -30,7 +30,7 @@ class UnifiedPromptLibrary:
|
|
|
30
30
|
Initialize the Unified Prompt Library.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
|
-
library_dir: Path to unified library directory (user_data/
|
|
33
|
+
library_dir: Path to unified library directory (user_data/prompt_library)
|
|
34
34
|
log_callback: Function to call for logging messages
|
|
35
35
|
"""
|
|
36
36
|
self.library_dir = Path(library_dir) if library_dir else None
|
|
@@ -544,8 +544,8 @@ class UnifiedPromptManagerQt:
|
|
|
544
544
|
self.log = parent_app.log if hasattr(parent_app, 'log') else print
|
|
545
545
|
|
|
546
546
|
# Paths
|
|
547
|
-
self.prompt_library_dir = self.user_data_path / "
|
|
548
|
-
# Use
|
|
547
|
+
self.prompt_library_dir = self.user_data_path / "prompt_library"
|
|
548
|
+
# Use prompt_library directly, not prompt_library/Library
|
|
549
549
|
self.unified_library_dir = self.prompt_library_dir
|
|
550
550
|
|
|
551
551
|
# Run migration if needed
|
|
@@ -579,7 +579,7 @@ class UnifiedPromptManagerQt:
|
|
|
579
579
|
self._cached_document_markdown: Optional[str] = None # Cached markdown conversion of current document
|
|
580
580
|
|
|
581
581
|
# Initialize Attachment Manager
|
|
582
|
-
ai_assistant_dir = self.user_data_path / "
|
|
582
|
+
ai_assistant_dir = self.user_data_path / "ai_assistant"
|
|
583
583
|
self.attachment_manager = AttachmentManager(
|
|
584
584
|
base_dir=str(ai_assistant_dir),
|
|
585
585
|
log_callback=self.log_message
|
|
@@ -640,7 +640,7 @@ class UnifiedPromptManagerQt:
|
|
|
640
640
|
|
|
641
641
|
# Tab 1: Prompt Library
|
|
642
642
|
library_tab = self._create_prompt_library_tab()
|
|
643
|
-
self.sub_tabs.addTab(library_tab, "📚
|
|
643
|
+
self.sub_tabs.addTab(library_tab, "📚 Library")
|
|
644
644
|
|
|
645
645
|
# Tab 2: AI Assistant (placeholder for now)
|
|
646
646
|
assistant_tab = self._create_ai_assistant_tab()
|
|
@@ -694,7 +694,7 @@ class UnifiedPromptManagerQt:
|
|
|
694
694
|
layout.setSpacing(5)
|
|
695
695
|
|
|
696
696
|
# Title
|
|
697
|
-
title = QLabel("
|
|
697
|
+
title = QLabel("⚡ QuickMenu")
|
|
698
698
|
title.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
|
|
699
699
|
layout.addWidget(title, 0)
|
|
700
700
|
|
|
@@ -1546,7 +1546,7 @@ class UnifiedPromptManagerQt:
|
|
|
1546
1546
|
|
|
1547
1547
|
def _create_active_config_panel(self) -> QGroupBox:
|
|
1548
1548
|
"""Create active prompt configuration panel"""
|
|
1549
|
-
group = QGroupBox("Active
|
|
1549
|
+
group = QGroupBox("Active Prompt")
|
|
1550
1550
|
layout = QVBoxLayout()
|
|
1551
1551
|
|
|
1552
1552
|
# Mode info (read-only, auto-selected)
|
|
@@ -1704,10 +1704,10 @@ class UnifiedPromptManagerQt:
|
|
|
1704
1704
|
self.editor_quickmenu_label_input.setPlaceholderText("Label shown in QuickMenu")
|
|
1705
1705
|
quickmenu_layout.addWidget(self.editor_quickmenu_label_input, 2)
|
|
1706
1706
|
|
|
1707
|
-
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in
|
|
1707
|
+
self.editor_quickmenu_in_grid_cb = CheckmarkCheckBox("Show in QuickMenu (in-app)")
|
|
1708
1708
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_grid_cb, 2)
|
|
1709
1709
|
|
|
1710
|
-
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in
|
|
1710
|
+
self.editor_quickmenu_in_quickmenu_cb = CheckmarkCheckBox("Show in QuickMenu (global)")
|
|
1711
1711
|
quickmenu_layout.addWidget(self.editor_quickmenu_in_quickmenu_cb, 1)
|
|
1712
1712
|
|
|
1713
1713
|
layout.addLayout(quickmenu_layout)
|
|
@@ -1760,26 +1760,7 @@ class UnifiedPromptManagerQt:
|
|
|
1760
1760
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
|
|
1761
1761
|
favorites_root.addChild(item)
|
|
1762
1762
|
|
|
1763
|
-
#
|
|
1764
|
-
quick_run_root = QTreeWidgetItem(["⚡ QuickMenu"])
|
|
1765
|
-
# Special node: not draggable/droppable
|
|
1766
|
-
quick_run_root.setData(0, Qt.ItemDataRole.UserRole, {'type': 'special', 'kind': 'quick_run'})
|
|
1767
|
-
quick_run_root.setExpanded(False)
|
|
1768
|
-
font = quick_run_root.font(0)
|
|
1769
|
-
font.setBold(True)
|
|
1770
|
-
quick_run_root.setFont(0, font)
|
|
1771
|
-
self.tree_widget.addTopLevelItem(quick_run_root)
|
|
1772
|
-
|
|
1773
|
-
quickmenu_items = self.library.get_quickmenu_prompts() if hasattr(self.library, 'get_quickmenu_prompts') else self.library.get_quick_run_prompts()
|
|
1774
|
-
self.log_message(f"🔍 DEBUG: QuickMenu count: {len(quickmenu_items)}")
|
|
1775
|
-
for path, label in quickmenu_items:
|
|
1776
|
-
item = QTreeWidgetItem([label])
|
|
1777
|
-
item.setData(0, Qt.ItemDataRole.UserRole, {'type': 'prompt', 'path': path})
|
|
1778
|
-
# Quick Run entries are shortcuts, but allow dragging to move the actual prompt file.
|
|
1779
|
-
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsDragEnabled)
|
|
1780
|
-
quick_run_root.addChild(item)
|
|
1781
|
-
|
|
1782
|
-
# Library folders
|
|
1763
|
+
# Library folders (QuickMenu parent folder removed - folder hierarchy now defines menu structure)
|
|
1783
1764
|
self.log_message(f"🔍 DEBUG: Building tree from {self.unified_library_dir}")
|
|
1784
1765
|
self._build_tree_recursive(None, self.unified_library_dir, "")
|
|
1785
1766
|
|
|
@@ -3548,7 +3529,7 @@ Output complete ACTION."""
|
|
|
3548
3529
|
"""
|
|
3549
3530
|
Save the markdown conversion of the current document.
|
|
3550
3531
|
|
|
3551
|
-
Saves to: user_data_private/
|
|
3532
|
+
Saves to: user_data_private/ai_assistant/current_document/
|
|
3552
3533
|
|
|
3553
3534
|
Args:
|
|
3554
3535
|
original_path: Original document file path
|
|
@@ -3556,7 +3537,7 @@ Output complete ACTION."""
|
|
|
3556
3537
|
"""
|
|
3557
3538
|
try:
|
|
3558
3539
|
# Create directory for current document markdown
|
|
3559
|
-
doc_dir = self.user_data_path / "
|
|
3540
|
+
doc_dir = self.user_data_path / "ai_assistant" / "current_document"
|
|
3560
3541
|
doc_dir.mkdir(parents=True, exist_ok=True)
|
|
3561
3542
|
|
|
3562
3543
|
# Create filename based on original
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supervertaler
|
|
3
|
-
Version: 1.9.
|
|
4
|
-
Summary: Professional AI-
|
|
3
|
+
Version: 1.9.149
|
|
4
|
+
Summary: Professional AI-enhanced translation workbench with multi-LLM support, glossary system, TM, spellcheck, voice commands, and PyQt6 interface. Batteries included (core).
|
|
5
5
|
Home-page: https://supervertaler.com
|
|
6
6
|
Author: Michael Beijer
|
|
7
7
|
Author-email: Michael Beijer <info@michaelbeijer.co.uk>
|
|
@@ -71,7 +71,7 @@ Dynamic: home-page
|
|
|
71
71
|
Dynamic: license-file
|
|
72
72
|
Dynamic: requires-python
|
|
73
73
|
|
|
74
|
-
# 🚀 Supervertaler v1.9.
|
|
74
|
+
# 🚀 Supervertaler v1.9.148-beta
|
|
75
75
|
|
|
76
76
|
[](https://pypi.org/project/Supervertaler/)
|
|
77
77
|
[](https://www.python.org/downloads/)
|
|
@@ -79,7 +79,27 @@ Dynamic: requires-python
|
|
|
79
79
|
|
|
80
80
|
AI-enhanced CAT tool with multi-LLM support (GPT-4, Claude, Gemini, Ollama), innovative Superlookup concordance system offering access to multiple terminology sources (TMs, glossaries, web resources, etc.), and seamless CAT tool integration (memoQ, Trados, CafeTran, Phrase).
|
|
81
81
|
|
|
82
|
-
**Current Version:** v1.9.
|
|
82
|
+
**Current Version:** v1.9.148-beta (January 21, 2026)
|
|
83
|
+
|
|
84
|
+
### NEW in v1.9.148-beta - 📁 User-Choosable Data Folder
|
|
85
|
+
|
|
86
|
+
**Your Data, Your Location!** On first run, you choose where to store your data (API keys, TMs, glossaries, prompts). Default is a visible folder in your home directory:
|
|
87
|
+
|
|
88
|
+
| Platform | Default Location |
|
|
89
|
+
|----------|-----------------|
|
|
90
|
+
| **Windows** | `C:\Users\Username\Supervertaler\` |
|
|
91
|
+
| **macOS** | `~/Supervertaler/` |
|
|
92
|
+
| **Linux** | `~/Supervertaler/` |
|
|
93
|
+
|
|
94
|
+
**Features:**
|
|
95
|
+
- First-run dialog lets you choose your data folder
|
|
96
|
+
- Change location anytime in Settings → General
|
|
97
|
+
- Auto-recovery if config pointer is deleted
|
|
98
|
+
- Easy to backup - just copy the folder!
|
|
99
|
+
|
|
100
|
+
### v1.9.147 - 📁 Persistent User Data Location
|
|
101
|
+
|
|
102
|
+
**No More Data Loss on Upgrade!** User data now stored outside the pip package directory, surviving `pip install --upgrade`.
|
|
83
103
|
|
|
84
104
|
### FIXED in v1.9.146 - 🔑 Gemini/Google API Key Alias
|
|
85
105
|
|
|
@@ -429,7 +449,7 @@ python Supervertaler.py
|
|
|
429
449
|
- 📊 **Smart Status** - Manual edits reset status requiring confirmation
|
|
430
450
|
|
|
431
451
|
**v1.4.0 - Supervoice Voice Dictation + Detachable Log:**
|
|
432
|
-
- 🎤 **Supervoice Voice Dictation** - AI-
|
|
452
|
+
- 🎤 **Supervoice Voice Dictation** - AI-enhanced hands-free translation input
|
|
433
453
|
- 🌍 **100+ Languages** - OpenAI Whisper supports virtually any language
|
|
434
454
|
- ⌨️ **F9 Global Hotkey** - Press-to-start, press-to-stop recording anywhere
|
|
435
455
|
- 🎚️ **5 Model Sizes** - Tiny to Large (balance speed vs accuracy)
|
|
@@ -479,7 +499,7 @@ python Supervertaler.py
|
|
|
479
499
|
- 🔍 **Superlookup** - System-wide search with global hotkey (Ctrl+Alt+L)
|
|
480
500
|
- 📝 **TMX Editor** - Professional translation memory editor with database support
|
|
481
501
|
- 🧹 **AutoFingers** - Automated translation pasting for memoQ with tag cleaning
|
|
482
|
-
- 🔧 **PDF Rescue** - AI-
|
|
502
|
+
- 🔧 **PDF Rescue** - AI-enhanced OCR for poorly formatted PDFs
|
|
483
503
|
- 🔧 **Encoding Repair Tool** - Detect and fix text encoding corruption (mojibake)
|
|
484
504
|
- 💾 **Translation Memory** - Fuzzy matching with TMX import/export
|
|
485
505
|
- 📚 **Multiple Termbases** - Glossary support per project
|
|
@@ -540,7 +560,7 @@ For comprehensive project information, see [PROJECT_CONTEXT.md](PROJECT_CONTEXT.
|
|
|
540
560
|
- 🤖 **LLM Integration** - OpenAI GPT-4/5, Anthropic Claude, Google Gemini
|
|
541
561
|
- 🎯 **Context-aware Translation** - Full document understanding
|
|
542
562
|
- 📚 **Unified Prompt Library** - System Prompts + Custom Instructions
|
|
543
|
-
- 🆘 **PDF Rescue** - AI-
|
|
563
|
+
- 🆘 **PDF Rescue** - AI-enhanced OCR for badly-formatted PDFs
|
|
544
564
|
- ✅ **CAT Features** - Segment editing, grid pagination, dual selection
|
|
545
565
|
- 📝 **TMX Editor** - Professional translation memory editor
|
|
546
566
|
- 🔗 **CAT Tool Integration** - memoQ, CafeTran, Trados Studio
|