supervertaler 1.9.163__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 +48473 -0
- modules/__init__.py +10 -0
- modules/ai_actions.py +964 -0
- modules/ai_attachment_manager.py +343 -0
- modules/ai_file_viewer_dialog.py +210 -0
- modules/autofingers_engine.py +466 -0
- modules/cafetran_docx_handler.py +379 -0
- modules/config_manager.py +469 -0
- modules/database_manager.py +1911 -0
- modules/database_migrations.py +417 -0
- modules/dejavurtf_handler.py +779 -0
- modules/document_analyzer.py +427 -0
- modules/docx_handler.py +689 -0
- modules/encoding_repair.py +319 -0
- modules/encoding_repair_Qt.py +393 -0
- modules/encoding_repair_ui.py +481 -0
- modules/feature_manager.py +350 -0
- modules/figure_context_manager.py +340 -0
- modules/file_dialog_helper.py +148 -0
- modules/find_replace.py +164 -0
- modules/find_replace_qt.py +457 -0
- modules/glossary_manager.py +433 -0
- modules/image_extractor.py +188 -0
- modules/keyboard_shortcuts_widget.py +571 -0
- modules/llm_clients.py +1211 -0
- modules/llm_leaderboard.py +737 -0
- modules/llm_superbench_ui.py +1401 -0
- modules/local_llm_setup.py +1104 -0
- modules/model_update_dialog.py +381 -0
- modules/model_version_checker.py +373 -0
- modules/mqxliff_handler.py +638 -0
- modules/non_translatables_manager.py +743 -0
- modules/pdf_rescue_Qt.py +1822 -0
- modules/pdf_rescue_tkinter.py +909 -0
- modules/phrase_docx_handler.py +516 -0
- modules/project_home_panel.py +209 -0
- modules/prompt_assistant.py +357 -0
- modules/prompt_library.py +689 -0
- modules/prompt_library_migration.py +447 -0
- modules/quick_access_sidebar.py +282 -0
- modules/ribbon_widget.py +597 -0
- modules/sdlppx_handler.py +874 -0
- modules/setup_wizard.py +353 -0
- modules/shortcut_manager.py +932 -0
- modules/simple_segmenter.py +128 -0
- modules/spellcheck_manager.py +727 -0
- modules/statuses.py +207 -0
- modules/style_guide_manager.py +315 -0
- modules/superbench_ui.py +1319 -0
- modules/superbrowser.py +329 -0
- modules/supercleaner.py +600 -0
- modules/supercleaner_ui.py +444 -0
- modules/superdocs.py +19 -0
- modules/superdocs_viewer_qt.py +382 -0
- modules/superlookup.py +252 -0
- modules/tag_cleaner.py +260 -0
- modules/tag_manager.py +351 -0
- modules/term_extractor.py +270 -0
- modules/termbase_entry_editor.py +842 -0
- modules/termbase_import_export.py +488 -0
- modules/termbase_manager.py +1060 -0
- modules/termview_widget.py +1176 -0
- modules/theme_manager.py +499 -0
- modules/tm_editor_dialog.py +99 -0
- modules/tm_manager_qt.py +1280 -0
- modules/tm_metadata_manager.py +545 -0
- modules/tmx_editor.py +1461 -0
- modules/tmx_editor_qt.py +2784 -0
- modules/tmx_generator.py +284 -0
- modules/tracked_changes.py +900 -0
- modules/trados_docx_handler.py +430 -0
- modules/translation_memory.py +715 -0
- modules/translation_results_panel.py +2134 -0
- modules/translation_services.py +282 -0
- modules/unified_prompt_library.py +659 -0
- modules/unified_prompt_manager_qt.py +3951 -0
- modules/voice_commands.py +920 -0
- modules/voice_dictation.py +477 -0
- modules/voice_dictation_lite.py +249 -0
- supervertaler-1.9.163.dist-info/METADATA +906 -0
- supervertaler-1.9.163.dist-info/RECORD +85 -0
- supervertaler-1.9.163.dist-info/WHEEL +5 -0
- supervertaler-1.9.163.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.163.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.163.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Termbase Manager Module
|
|
3
|
+
|
|
4
|
+
Handles all termbase operations: creation, activation, term management, searching.
|
|
5
|
+
Uses 'termbase' terminology throughout (never 'glossary').
|
|
6
|
+
|
|
7
|
+
Termbases can be:
|
|
8
|
+
- Global (available to all projects)
|
|
9
|
+
- Project-specific (linked to particular project)
|
|
10
|
+
|
|
11
|
+
Activation system: termbases can be activated/deactivated per project.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sqlite3
|
|
15
|
+
import json
|
|
16
|
+
from typing import List, Dict, Optional, Tuple
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TermbaseManager:
|
|
21
|
+
"""Manages termbase operations and term storage"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, db_manager, log_callback=None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize termbase manager
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
db_manager: DatabaseManager instance
|
|
29
|
+
log_callback: Optional logging function
|
|
30
|
+
"""
|
|
31
|
+
self.db_manager = db_manager
|
|
32
|
+
self.log = log_callback if log_callback else print
|
|
33
|
+
|
|
34
|
+
# ========================================================================
|
|
35
|
+
# TERMBASE MANAGEMENT
|
|
36
|
+
# ========================================================================
|
|
37
|
+
|
|
38
|
+
def create_termbase(self, name: str, source_lang: Optional[str] = None,
|
|
39
|
+
target_lang: Optional[str] = None, project_id: Optional[int] = None,
|
|
40
|
+
description: str = "", is_global: bool = True, is_project_termbase: bool = False) -> Optional[int]:
|
|
41
|
+
"""
|
|
42
|
+
Create a new termbase
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
name: Termbase name
|
|
46
|
+
source_lang: Source language code (e.g., 'en', 'nl')
|
|
47
|
+
target_lang: Target language code
|
|
48
|
+
project_id: If set, termbase is project-specific; if None, it's global
|
|
49
|
+
description: Optional description
|
|
50
|
+
is_global: Whether this is a global termbase (available to all projects)
|
|
51
|
+
is_project_termbase: Whether this is the special project termbase (only one allowed per project)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Termbase ID or None if failed
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
cursor = self.db_manager.cursor
|
|
58
|
+
now = datetime.now().isoformat()
|
|
59
|
+
|
|
60
|
+
# If this is a project termbase, check if one already exists for this project
|
|
61
|
+
if is_project_termbase and project_id:
|
|
62
|
+
cursor.execute("""
|
|
63
|
+
SELECT id, name FROM termbases
|
|
64
|
+
WHERE project_id = ? AND is_project_termbase = 1
|
|
65
|
+
""", (project_id,))
|
|
66
|
+
existing = cursor.fetchone()
|
|
67
|
+
if existing:
|
|
68
|
+
self.log(f"✗ Project {project_id} already has a project termbase: {existing[1]}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
cursor.execute("""
|
|
72
|
+
INSERT INTO termbases (name, source_lang, target_lang, project_id,
|
|
73
|
+
description, is_global, is_project_termbase, created_date, modified_date)
|
|
74
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
75
|
+
""", (name, source_lang, target_lang, project_id, description, is_global, is_project_termbase, now, now))
|
|
76
|
+
|
|
77
|
+
self.db_manager.connection.commit()
|
|
78
|
+
termbase_id = cursor.lastrowid
|
|
79
|
+
tb_type = "project termbase" if is_project_termbase else "termbase"
|
|
80
|
+
self.log(f"✓ Created {tb_type}: {name} (ID: {termbase_id})")
|
|
81
|
+
return termbase_id
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.log(f"✗ Error creating termbase: {e}")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def get_all_termbases(self) -> List[Dict]:
|
|
87
|
+
"""
|
|
88
|
+
Get all termbases (global and project-specific)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of termbase dictionaries with fields: id, name, source_lang, target_lang,
|
|
92
|
+
project_id, description, is_global, is_active, term_count, created_date, modified_date
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
cursor = self.db_manager.cursor
|
|
96
|
+
|
|
97
|
+
cursor.execute("""
|
|
98
|
+
SELECT
|
|
99
|
+
t.id, t.name, t.source_lang, t.target_lang, t.project_id,
|
|
100
|
+
t.description, t.is_global, t.priority, t.is_project_termbase,
|
|
101
|
+
t.ranking, t.read_only, t.created_date, t.modified_date,
|
|
102
|
+
COUNT(gt.id) as term_count
|
|
103
|
+
FROM termbases t
|
|
104
|
+
LEFT JOIN termbase_terms gt ON CAST(t.id AS TEXT) = gt.termbase_id
|
|
105
|
+
GROUP BY t.id
|
|
106
|
+
ORDER BY t.is_project_termbase DESC, t.is_global DESC, t.name ASC
|
|
107
|
+
""")
|
|
108
|
+
|
|
109
|
+
termbases = []
|
|
110
|
+
for row in cursor.fetchall():
|
|
111
|
+
termbases.append({
|
|
112
|
+
'id': row[0],
|
|
113
|
+
'name': row[1],
|
|
114
|
+
'source_lang': row[2],
|
|
115
|
+
'target_lang': row[3],
|
|
116
|
+
'project_id': row[4],
|
|
117
|
+
'description': row[5],
|
|
118
|
+
'is_global': row[6],
|
|
119
|
+
'priority': row[7] or 50, # Default to 50 if NULL (legacy)
|
|
120
|
+
'is_project_termbase': bool(row[8]),
|
|
121
|
+
'ranking': row[9], # Termbase ranking
|
|
122
|
+
'read_only': bool(row[10]) if row[10] is not None else True, # Default to read-only if NULL
|
|
123
|
+
'created_date': row[11],
|
|
124
|
+
'modified_date': row[12],
|
|
125
|
+
'term_count': row[13] or 0
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return termbases
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.log(f"✗ Error fetching termbases: {e}")
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
def get_termbase(self, termbase_id: int) -> Optional[Dict]:
|
|
134
|
+
"""Get single termbase by ID"""
|
|
135
|
+
try:
|
|
136
|
+
cursor = self.db_manager.cursor
|
|
137
|
+
|
|
138
|
+
cursor.execute("""
|
|
139
|
+
SELECT
|
|
140
|
+
t.id, t.name, t.source_lang, t.target_lang, t.project_id,
|
|
141
|
+
t.description, t.is_global, t.created_date, t.modified_date,
|
|
142
|
+
COUNT(gt.id) as term_count
|
|
143
|
+
FROM termbases t
|
|
144
|
+
LEFT JOIN termbase_terms gt ON t.id = gt.termbase_id
|
|
145
|
+
WHERE t.id = ?
|
|
146
|
+
GROUP BY t.id
|
|
147
|
+
""", (termbase_id,))
|
|
148
|
+
|
|
149
|
+
row = cursor.fetchone()
|
|
150
|
+
if row:
|
|
151
|
+
return {
|
|
152
|
+
'id': row[0],
|
|
153
|
+
'name': row[1],
|
|
154
|
+
'source_lang': row[2],
|
|
155
|
+
'target_lang': row[3],
|
|
156
|
+
'project_id': row[4],
|
|
157
|
+
'description': row[5],
|
|
158
|
+
'is_global': row[6],
|
|
159
|
+
'created_date': row[7],
|
|
160
|
+
'modified_date': row[8],
|
|
161
|
+
'term_count': row[9] or 0
|
|
162
|
+
}
|
|
163
|
+
return None
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.log(f"✗ Error fetching termbase: {e}")
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def delete_termbase(self, termbase_id: int) -> bool:
|
|
169
|
+
"""
|
|
170
|
+
Delete termbase and all its terms
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
termbase_id: Termbase ID
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if successful
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
cursor = self.db_manager.cursor
|
|
180
|
+
|
|
181
|
+
# Delete terms first
|
|
182
|
+
cursor.execute("DELETE FROM termbase_terms WHERE termbase_id = ?", (termbase_id,))
|
|
183
|
+
|
|
184
|
+
# Delete termbase
|
|
185
|
+
cursor.execute("DELETE FROM termbases WHERE id = ?", (termbase_id,))
|
|
186
|
+
|
|
187
|
+
self.db_manager.connection.commit()
|
|
188
|
+
self.log(f"✓ Deleted termbase ID: {termbase_id}")
|
|
189
|
+
return True
|
|
190
|
+
except Exception as e:
|
|
191
|
+
self.log(f"✗ Error deleting termbase: {e}")
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
def rename_termbase(self, termbase_id: int, new_name: str) -> bool:
|
|
195
|
+
"""
|
|
196
|
+
Rename a termbase
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
termbase_id: Termbase ID
|
|
200
|
+
new_name: New name for the termbase
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
True if successful
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
if not new_name or not new_name.strip():
|
|
207
|
+
self.log(f"✗ Cannot rename termbase: empty name provided")
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
new_name = new_name.strip()
|
|
211
|
+
cursor = self.db_manager.cursor
|
|
212
|
+
now = datetime.now().isoformat()
|
|
213
|
+
|
|
214
|
+
cursor.execute("""
|
|
215
|
+
UPDATE termbases
|
|
216
|
+
SET name = ?, modified_date = ?
|
|
217
|
+
WHERE id = ?
|
|
218
|
+
""", (new_name, now, termbase_id))
|
|
219
|
+
|
|
220
|
+
self.db_manager.connection.commit()
|
|
221
|
+
self.log(f"✓ Renamed termbase ID {termbase_id} to '{new_name}'")
|
|
222
|
+
return True
|
|
223
|
+
except Exception as e:
|
|
224
|
+
self.log(f"✗ Error renaming termbase: {e}")
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
def get_active_termbases_for_project(self, project_id: int) -> List[Dict]:
|
|
228
|
+
"""
|
|
229
|
+
Get all active termbases for a specific project
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
project_id: Project ID
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
List of active termbase dictionaries
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
cursor = self.db_manager.cursor
|
|
239
|
+
|
|
240
|
+
cursor.execute("""
|
|
241
|
+
SELECT
|
|
242
|
+
t.id, t.name, t.source_lang, t.target_lang, t.project_id,
|
|
243
|
+
t.description, t.is_global, t.created_date, t.modified_date,
|
|
244
|
+
t.ranking, t.is_project_termbase,
|
|
245
|
+
COUNT(gt.id) as term_count
|
|
246
|
+
FROM termbases t
|
|
247
|
+
LEFT JOIN termbase_terms gt ON t.id = gt.termbase_id
|
|
248
|
+
LEFT JOIN termbase_activation ta ON t.id = ta.termbase_id AND ta.project_id = ?
|
|
249
|
+
WHERE (t.is_global = 1 OR t.project_id = ?)
|
|
250
|
+
AND (ta.is_active = 1 OR ta.is_active IS NULL)
|
|
251
|
+
GROUP BY t.id
|
|
252
|
+
ORDER BY t.name ASC
|
|
253
|
+
""", (project_id, project_id))
|
|
254
|
+
|
|
255
|
+
termbases = []
|
|
256
|
+
for row in cursor.fetchall():
|
|
257
|
+
termbases.append({
|
|
258
|
+
'id': row[0],
|
|
259
|
+
'name': row[1],
|
|
260
|
+
'source_lang': row[2],
|
|
261
|
+
'target_lang': row[3],
|
|
262
|
+
'project_id': row[4],
|
|
263
|
+
'description': row[5],
|
|
264
|
+
'is_global': row[6],
|
|
265
|
+
'created_date': row[7],
|
|
266
|
+
'modified_date': row[8],
|
|
267
|
+
'ranking': row[9],
|
|
268
|
+
'is_project_termbase': row[10],
|
|
269
|
+
'term_count': row[11] or 0
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return termbases
|
|
273
|
+
except Exception as e:
|
|
274
|
+
self.log(f"✗ Error fetching active termbases: {e}")
|
|
275
|
+
return []
|
|
276
|
+
|
|
277
|
+
# ========================================================================
|
|
278
|
+
# TERMBASE ACTIVATION
|
|
279
|
+
# ========================================================================
|
|
280
|
+
|
|
281
|
+
def is_termbase_active(self, termbase_id: int, project_id: int) -> bool:
|
|
282
|
+
"""Check if termbase is active for a project"""
|
|
283
|
+
try:
|
|
284
|
+
cursor = self.db_manager.cursor
|
|
285
|
+
|
|
286
|
+
cursor.execute("""
|
|
287
|
+
SELECT is_active FROM termbase_activation
|
|
288
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
289
|
+
""", (termbase_id, project_id))
|
|
290
|
+
|
|
291
|
+
result = cursor.fetchone()
|
|
292
|
+
if result:
|
|
293
|
+
return result[0] == 1
|
|
294
|
+
|
|
295
|
+
# If no record exists, termbases are active by default
|
|
296
|
+
return True
|
|
297
|
+
except Exception as e:
|
|
298
|
+
self.log(f"✗ Error checking termbase activation: {e}")
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
def activate_termbase(self, termbase_id: int, project_id: int) -> bool:
|
|
302
|
+
"""Activate termbase for project and assign ranking"""
|
|
303
|
+
try:
|
|
304
|
+
cursor = self.db_manager.cursor
|
|
305
|
+
|
|
306
|
+
self.log(f"🔵 ACTIVATE: termbase_id={termbase_id}, project_id={project_id}")
|
|
307
|
+
|
|
308
|
+
# Check if activation record already exists
|
|
309
|
+
cursor.execute("""
|
|
310
|
+
SELECT activated_date FROM termbase_activation
|
|
311
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
312
|
+
""", (termbase_id, project_id))
|
|
313
|
+
existing = cursor.fetchone()
|
|
314
|
+
|
|
315
|
+
if existing:
|
|
316
|
+
# Preserve original activated_date when re-activating
|
|
317
|
+
# Check if priority is NULL and set default if needed
|
|
318
|
+
cursor.execute("""
|
|
319
|
+
SELECT priority FROM termbase_activation
|
|
320
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
321
|
+
""", (termbase_id, project_id))
|
|
322
|
+
existing_priority = cursor.fetchone()[0]
|
|
323
|
+
|
|
324
|
+
if existing_priority is None:
|
|
325
|
+
# Priority is NULL - assign default priority
|
|
326
|
+
cursor.execute("""
|
|
327
|
+
SELECT COALESCE(MAX(priority), 0) FROM termbase_activation
|
|
328
|
+
WHERE project_id = ? AND is_active = 1
|
|
329
|
+
""", (project_id,))
|
|
330
|
+
max_priority = cursor.fetchone()[0]
|
|
331
|
+
default_priority = max_priority + 1
|
|
332
|
+
|
|
333
|
+
cursor.execute("""
|
|
334
|
+
UPDATE termbase_activation
|
|
335
|
+
SET is_active = 1, priority = ?
|
|
336
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
337
|
+
""", (default_priority, termbase_id, project_id))
|
|
338
|
+
self.log(f" ✓ Updated activation record (preserved timestamp, set priority #{default_priority})")
|
|
339
|
+
else:
|
|
340
|
+
# Priority already exists - just update is_active
|
|
341
|
+
cursor.execute("""
|
|
342
|
+
UPDATE termbase_activation
|
|
343
|
+
SET is_active = 1
|
|
344
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
345
|
+
""", (termbase_id, project_id))
|
|
346
|
+
self.log(f" ✓ Updated activation record (preserved timestamp and priority #{existing_priority})")
|
|
347
|
+
else:
|
|
348
|
+
# Create new activation record with default priority
|
|
349
|
+
# Default priority: Find highest existing priority and add 1
|
|
350
|
+
cursor.execute("""
|
|
351
|
+
SELECT COALESCE(MAX(priority), 0) FROM termbase_activation
|
|
352
|
+
WHERE project_id = ? AND is_active = 1
|
|
353
|
+
""", (project_id,))
|
|
354
|
+
max_priority = cursor.fetchone()[0]
|
|
355
|
+
default_priority = max_priority + 1
|
|
356
|
+
|
|
357
|
+
cursor.execute("""
|
|
358
|
+
INSERT INTO termbase_activation (termbase_id, project_id, is_active, priority)
|
|
359
|
+
VALUES (?, ?, 1, ?)
|
|
360
|
+
""", (termbase_id, project_id, default_priority))
|
|
361
|
+
self.log(f" ✓ Created new activation record with default priority #{default_priority}")
|
|
362
|
+
|
|
363
|
+
self.db_manager.connection.commit()
|
|
364
|
+
self.log(f"✓ Activated termbase {termbase_id} for project {project_id}")
|
|
365
|
+
return True
|
|
366
|
+
except Exception as e:
|
|
367
|
+
self.log(f"✗ Error activating termbase: {e}")
|
|
368
|
+
import traceback
|
|
369
|
+
self.log(f"Traceback: {traceback.format_exc()}")
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
def deactivate_termbase(self, termbase_id: int, project_id: int) -> bool:
|
|
373
|
+
"""Deactivate termbase for project and reassign rankings"""
|
|
374
|
+
try:
|
|
375
|
+
cursor = self.db_manager.cursor
|
|
376
|
+
|
|
377
|
+
self.log(f"🔴 DEACTIVATE: termbase_id={termbase_id}, project_id={project_id}")
|
|
378
|
+
|
|
379
|
+
cursor.execute("""
|
|
380
|
+
INSERT OR REPLACE INTO termbase_activation (termbase_id, project_id, is_active)
|
|
381
|
+
VALUES (?, ?, 0)
|
|
382
|
+
""", (termbase_id, project_id))
|
|
383
|
+
|
|
384
|
+
self.log(f" ✓ Inserted deactivation record")
|
|
385
|
+
|
|
386
|
+
# Note: Priority is preserved in termbase_activation table even when deactivated
|
|
387
|
+
# This way if user re-activates, the priority is remembered
|
|
388
|
+
|
|
389
|
+
self.db_manager.connection.commit()
|
|
390
|
+
self.log(f"✓ Deactivated termbase {termbase_id} for project {project_id}")
|
|
391
|
+
return True
|
|
392
|
+
except Exception as e:
|
|
393
|
+
self.log(f"✗ Error deactivating termbase: {e}")
|
|
394
|
+
import traceback
|
|
395
|
+
self.log(f"Traceback: {traceback.format_exc()}")
|
|
396
|
+
return False
|
|
397
|
+
|
|
398
|
+
def set_termbase_read_only(self, termbase_id: int, read_only: bool) -> bool:
|
|
399
|
+
"""Set termbase read-only status (True = read-only, False = writable)"""
|
|
400
|
+
try:
|
|
401
|
+
cursor = self.db_manager.cursor
|
|
402
|
+
cursor.execute("""
|
|
403
|
+
UPDATE termbases SET read_only = ? WHERE id = ?
|
|
404
|
+
""", (1 if read_only else 0, termbase_id))
|
|
405
|
+
self.db_manager.connection.commit()
|
|
406
|
+
status = "read-only" if read_only else "writable"
|
|
407
|
+
self.log(f"✓ Set termbase {termbase_id} to {status}")
|
|
408
|
+
return True
|
|
409
|
+
except Exception as e:
|
|
410
|
+
self.log(f"✗ Error setting termbase read_only: {e}")
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
def set_termbase_priority(self, termbase_id: int, project_id: int, priority: int) -> bool:
|
|
414
|
+
"""
|
|
415
|
+
Set manual priority for a termbase in a specific project.
|
|
416
|
+
Multiple termbases can have the same priority.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
termbase_id: Termbase ID
|
|
420
|
+
project_id: Project ID
|
|
421
|
+
priority: Priority level (1=highest, 2=second, etc.)
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
True if successful
|
|
425
|
+
"""
|
|
426
|
+
try:
|
|
427
|
+
cursor = self.db_manager.cursor
|
|
428
|
+
|
|
429
|
+
# Update priority in termbase_activation table
|
|
430
|
+
cursor.execute("""
|
|
431
|
+
UPDATE termbase_activation
|
|
432
|
+
SET priority = ?
|
|
433
|
+
WHERE termbase_id = ? AND project_id = ?
|
|
434
|
+
""", (priority, termbase_id, project_id))
|
|
435
|
+
|
|
436
|
+
if cursor.rowcount == 0:
|
|
437
|
+
self.log(f"⚠️ No activation record found for termbase {termbase_id}, project {project_id}")
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
self.db_manager.connection.commit()
|
|
441
|
+
self.log(f"✓ Set termbase {termbase_id} priority to #{priority} for project {project_id}")
|
|
442
|
+
return True
|
|
443
|
+
except Exception as e:
|
|
444
|
+
self.log(f"✗ Error setting termbase priority: {e}")
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
def get_termbase_priority(self, termbase_id: int, project_id: int) -> Optional[int]:
|
|
448
|
+
"""Get priority for a termbase in a specific project"""
|
|
449
|
+
try:
|
|
450
|
+
cursor = self.db_manager.cursor
|
|
451
|
+
cursor.execute("""
|
|
452
|
+
SELECT priority FROM termbase_activation
|
|
453
|
+
WHERE termbase_id = ? AND project_id = ? AND is_active = 1
|
|
454
|
+
""", (termbase_id, project_id))
|
|
455
|
+
result = cursor.fetchone()
|
|
456
|
+
return result[0] if result else None
|
|
457
|
+
except Exception as e:
|
|
458
|
+
self.log(f"✗ Error getting termbase priority: {e}")
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
def set_as_project_termbase(self, termbase_id: int, project_id: int) -> bool:
|
|
462
|
+
"""
|
|
463
|
+
Set a termbase as the project termbase for a project.
|
|
464
|
+
Only one project termbase allowed per project - this will unset any existing one.
|
|
465
|
+
"""
|
|
466
|
+
try:
|
|
467
|
+
cursor = self.db_manager.cursor
|
|
468
|
+
|
|
469
|
+
# First, unset any existing project termbase for this project
|
|
470
|
+
cursor.execute("""
|
|
471
|
+
UPDATE termbases
|
|
472
|
+
SET is_project_termbase = 0
|
|
473
|
+
WHERE project_id = ? AND is_project_termbase = 1
|
|
474
|
+
""", (project_id,))
|
|
475
|
+
|
|
476
|
+
# Then set the new one
|
|
477
|
+
cursor.execute("""
|
|
478
|
+
UPDATE termbases
|
|
479
|
+
SET is_project_termbase = 1
|
|
480
|
+
WHERE id = ?
|
|
481
|
+
""", (termbase_id,))
|
|
482
|
+
|
|
483
|
+
self.db_manager.connection.commit()
|
|
484
|
+
self.log(f"✓ Set termbase {termbase_id} as project termbase for project {project_id}")
|
|
485
|
+
return True
|
|
486
|
+
except Exception as e:
|
|
487
|
+
self.log(f"✗ Error setting project termbase: {e}")
|
|
488
|
+
return False
|
|
489
|
+
|
|
490
|
+
def get_active_termbase_ids(self, project_id: int) -> List[int]:
|
|
491
|
+
"""
|
|
492
|
+
Get list of active termbase IDs for a project (for saving to project file)
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
List of termbase IDs (not database IDs)
|
|
496
|
+
"""
|
|
497
|
+
try:
|
|
498
|
+
cursor = self.db_manager.cursor
|
|
499
|
+
cursor.execute("""
|
|
500
|
+
SELECT t.id
|
|
501
|
+
FROM termbases t
|
|
502
|
+
INNER JOIN termbase_activation ta ON t.id = ta.termbase_id
|
|
503
|
+
WHERE ta.project_id = ? AND ta.is_active = 1
|
|
504
|
+
ORDER BY ta.activated_date ASC
|
|
505
|
+
""", (project_id,))
|
|
506
|
+
|
|
507
|
+
active_ids = [row[0] for row in cursor.fetchall()]
|
|
508
|
+
self.log(f"📋 Found {len(active_ids)} active termbases for project {project_id}: {active_ids}")
|
|
509
|
+
return active_ids
|
|
510
|
+
except Exception as e:
|
|
511
|
+
self.log(f"✗ Error getting active termbase IDs: {e}")
|
|
512
|
+
return []
|
|
513
|
+
|
|
514
|
+
def _reassign_rankings_for_project(self, project_id: int):
|
|
515
|
+
"""
|
|
516
|
+
Reassign rankings to all activated termbases for a project.
|
|
517
|
+
Rankings are assigned sequentially (1, 2, 3, ...) based on termbase ID order.
|
|
518
|
+
Project termbases don't get rankings (they're always highlighted pink).
|
|
519
|
+
"""
|
|
520
|
+
try:
|
|
521
|
+
cursor = self.db_manager.cursor
|
|
522
|
+
|
|
523
|
+
# Get all activated termbases for this project (excluding project termbases)
|
|
524
|
+
# Order by activation timestamp so first activated gets #1, second gets #2, etc.
|
|
525
|
+
cursor.execute("""
|
|
526
|
+
SELECT t.id
|
|
527
|
+
FROM termbases t
|
|
528
|
+
INNER JOIN termbase_activation ta ON t.id = ta.termbase_id
|
|
529
|
+
WHERE ta.project_id = ? AND ta.is_active = 1
|
|
530
|
+
AND (t.is_project_termbase = 0 OR t.is_project_termbase IS NULL)
|
|
531
|
+
ORDER BY ta.activated_date ASC
|
|
532
|
+
""", (project_id,))
|
|
533
|
+
|
|
534
|
+
activated_termbase_ids = [row[0] for row in cursor.fetchall()]
|
|
535
|
+
|
|
536
|
+
# Assign rankings sequentially
|
|
537
|
+
for rank, termbase_id in enumerate(activated_termbase_ids, start=1):
|
|
538
|
+
cursor.execute("""
|
|
539
|
+
UPDATE termbases SET ranking = ? WHERE id = ?
|
|
540
|
+
""", (rank, termbase_id))
|
|
541
|
+
self.log(f" ✓ Assigned ranking #{rank} to termbase ID {termbase_id}")
|
|
542
|
+
|
|
543
|
+
# Clear rankings for non-activated termbases
|
|
544
|
+
if activated_termbase_ids:
|
|
545
|
+
placeholders = ','.join('?' * len(activated_termbase_ids))
|
|
546
|
+
cursor.execute(f"""
|
|
547
|
+
UPDATE termbases SET ranking = NULL
|
|
548
|
+
WHERE id NOT IN ({placeholders})
|
|
549
|
+
""", activated_termbase_ids)
|
|
550
|
+
else:
|
|
551
|
+
cursor.execute("UPDATE termbases SET ranking = NULL")
|
|
552
|
+
|
|
553
|
+
# Commit the changes
|
|
554
|
+
self.db_manager.connection.commit()
|
|
555
|
+
self.log(f"✓ Assigned rankings to {len(activated_termbase_ids)} activated termbase(s) for project {project_id}")
|
|
556
|
+
|
|
557
|
+
except Exception as e:
|
|
558
|
+
self.log(f"✗ Error reassigning rankings: {e}")
|
|
559
|
+
|
|
560
|
+
def unset_project_termbase(self, termbase_id: int) -> bool:
|
|
561
|
+
"""Remove project termbase designation from a termbase"""
|
|
562
|
+
try:
|
|
563
|
+
cursor = self.db_manager.cursor
|
|
564
|
+
|
|
565
|
+
cursor.execute("""
|
|
566
|
+
UPDATE termbases
|
|
567
|
+
SET is_project_termbase = 0
|
|
568
|
+
WHERE id = ?
|
|
569
|
+
""", (termbase_id,))
|
|
570
|
+
|
|
571
|
+
self.db_manager.connection.commit()
|
|
572
|
+
self.log(f"✓ Removed project termbase designation from termbase {termbase_id}")
|
|
573
|
+
return True
|
|
574
|
+
except Exception as e:
|
|
575
|
+
self.log(f"✗ Error unsetting project termbase: {e}")
|
|
576
|
+
return False
|
|
577
|
+
|
|
578
|
+
def get_project_termbase(self, project_id: int) -> Optional[Dict]:
|
|
579
|
+
"""Get the project termbase for a specific project"""
|
|
580
|
+
try:
|
|
581
|
+
cursor = self.db_manager.cursor
|
|
582
|
+
|
|
583
|
+
cursor.execute("""
|
|
584
|
+
SELECT
|
|
585
|
+
t.id, t.name, t.source_lang, t.target_lang, t.project_id,
|
|
586
|
+
t.description, t.is_global, t.priority, t.is_project_termbase,
|
|
587
|
+
t.created_date, t.modified_date,
|
|
588
|
+
COUNT(gt.id) as term_count
|
|
589
|
+
FROM termbases t
|
|
590
|
+
LEFT JOIN termbase_terms gt ON CAST(t.id AS TEXT) = gt.termbase_id
|
|
591
|
+
WHERE t.project_id = ? AND t.is_project_termbase = 1
|
|
592
|
+
GROUP BY t.id
|
|
593
|
+
""", (project_id,))
|
|
594
|
+
|
|
595
|
+
row = cursor.fetchone()
|
|
596
|
+
if row:
|
|
597
|
+
return {
|
|
598
|
+
'id': row[0],
|
|
599
|
+
'name': row[1],
|
|
600
|
+
'source_lang': row[2],
|
|
601
|
+
'target_lang': row[3],
|
|
602
|
+
'project_id': row[4],
|
|
603
|
+
'description': row[5],
|
|
604
|
+
'is_global': row[6],
|
|
605
|
+
'priority': row[7] or 50,
|
|
606
|
+
'is_project_termbase': bool(row[8]),
|
|
607
|
+
'created_date': row[9],
|
|
608
|
+
'modified_date': row[10],
|
|
609
|
+
'term_count': row[11] or 0
|
|
610
|
+
}
|
|
611
|
+
return None
|
|
612
|
+
except Exception as e:
|
|
613
|
+
self.log(f"✗ Error getting project termbase: {e}")
|
|
614
|
+
return None
|
|
615
|
+
|
|
616
|
+
# ========================================================================
|
|
617
|
+
# TERM MANAGEMENT
|
|
618
|
+
# ========================================================================
|
|
619
|
+
|
|
620
|
+
def add_term(self, termbase_id: int, source_term: str, target_term: str,
|
|
621
|
+
priority: int = 99, domain: str = "", notes: str = "",
|
|
622
|
+
project: str = "", client: str = "",
|
|
623
|
+
forbidden: bool = False, source_lang: Optional[str] = None,
|
|
624
|
+
target_lang: Optional[str] = None, term_uuid: Optional[str] = None) -> Optional[int]:
|
|
625
|
+
"""
|
|
626
|
+
Add a term to termbase
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
termbase_id: Termbase ID
|
|
630
|
+
source_term: Source language term
|
|
631
|
+
target_term: Target language term
|
|
632
|
+
priority: Priority (1=highest, 99=default)
|
|
633
|
+
domain: Domain/category
|
|
634
|
+
notes: Optional notes/definition
|
|
635
|
+
project: Optional project name
|
|
636
|
+
client: Optional client name
|
|
637
|
+
forbidden: Whether this is a forbidden term
|
|
638
|
+
source_lang: Source language code
|
|
639
|
+
target_lang: Target language code
|
|
640
|
+
term_uuid: Optional UUID for tracking term across imports/exports
|
|
641
|
+
|
|
642
|
+
Returns:
|
|
643
|
+
Term ID or None if failed (returns None if duplicate found)
|
|
644
|
+
"""
|
|
645
|
+
try:
|
|
646
|
+
import uuid
|
|
647
|
+
cursor = self.db_manager.cursor
|
|
648
|
+
|
|
649
|
+
# Check for duplicate (case-insensitive check)
|
|
650
|
+
cursor.execute("""
|
|
651
|
+
SELECT id FROM termbase_terms
|
|
652
|
+
WHERE termbase_id = ?
|
|
653
|
+
AND LOWER(source_term) = LOWER(?)
|
|
654
|
+
AND LOWER(target_term) = LOWER(?)
|
|
655
|
+
""", (termbase_id, source_term, target_term))
|
|
656
|
+
|
|
657
|
+
existing = cursor.fetchone()
|
|
658
|
+
if existing:
|
|
659
|
+
self.log(f"⚠️ Duplicate term not added: {source_term} → {target_term} (already exists in termbase {termbase_id})")
|
|
660
|
+
return None
|
|
661
|
+
|
|
662
|
+
# Generate UUID if not provided
|
|
663
|
+
if not term_uuid:
|
|
664
|
+
term_uuid = str(uuid.uuid4())
|
|
665
|
+
|
|
666
|
+
cursor.execute("""
|
|
667
|
+
INSERT INTO termbase_terms
|
|
668
|
+
(termbase_id, source_term, target_term, priority, domain, notes,
|
|
669
|
+
project, client, forbidden, source_lang, target_lang, term_uuid)
|
|
670
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
671
|
+
""", (termbase_id, source_term, target_term, priority, domain, notes,
|
|
672
|
+
project, client, forbidden, source_lang, target_lang, term_uuid))
|
|
673
|
+
|
|
674
|
+
self.db_manager.connection.commit()
|
|
675
|
+
term_id = cursor.lastrowid
|
|
676
|
+
self.log(f"✓ Added term to termbase {termbase_id}: {source_term} → {target_term}")
|
|
677
|
+
return term_id
|
|
678
|
+
except Exception as e:
|
|
679
|
+
self.log(f"✗ Error adding term: {e}")
|
|
680
|
+
return None
|
|
681
|
+
|
|
682
|
+
def get_terms(self, termbase_id: int) -> List[Dict]:
|
|
683
|
+
"""Get all terms in a termbase"""
|
|
684
|
+
try:
|
|
685
|
+
cursor = self.db_manager.cursor
|
|
686
|
+
|
|
687
|
+
cursor.execute("""
|
|
688
|
+
SELECT id, source_term, target_term, priority, domain, notes,
|
|
689
|
+
project, client, forbidden, term_uuid
|
|
690
|
+
FROM termbase_terms
|
|
691
|
+
WHERE termbase_id = ?
|
|
692
|
+
ORDER BY priority ASC, source_term ASC
|
|
693
|
+
""", (termbase_id,))
|
|
694
|
+
|
|
695
|
+
terms = []
|
|
696
|
+
for row in cursor.fetchall():
|
|
697
|
+
terms.append({
|
|
698
|
+
'id': row[0],
|
|
699
|
+
'source_term': row[1],
|
|
700
|
+
'target_term': row[2],
|
|
701
|
+
'priority': row[3],
|
|
702
|
+
'domain': row[4],
|
|
703
|
+
'notes': row[5],
|
|
704
|
+
'project': row[6],
|
|
705
|
+
'client': row[7],
|
|
706
|
+
'forbidden': row[8],
|
|
707
|
+
'term_uuid': row[9]
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
return terms
|
|
711
|
+
except Exception as e:
|
|
712
|
+
self.log(f"✗ Error fetching terms: {e}")
|
|
713
|
+
return []
|
|
714
|
+
|
|
715
|
+
def update_term(self, term_id: int, source_term: Optional[str] = None,
|
|
716
|
+
target_term: Optional[str] = None, priority: Optional[int] = None,
|
|
717
|
+
domain: Optional[str] = None, notes: Optional[str] = None,
|
|
718
|
+
project: Optional[str] = None, client: Optional[str] = None,
|
|
719
|
+
forbidden: Optional[bool] = None) -> bool:
|
|
720
|
+
"""Update a term"""
|
|
721
|
+
try:
|
|
722
|
+
cursor = self.db_manager.cursor
|
|
723
|
+
updates = []
|
|
724
|
+
params = []
|
|
725
|
+
|
|
726
|
+
if source_term is not None:
|
|
727
|
+
updates.append("source_term = ?")
|
|
728
|
+
params.append(source_term)
|
|
729
|
+
if target_term is not None:
|
|
730
|
+
updates.append("target_term = ?")
|
|
731
|
+
params.append(target_term)
|
|
732
|
+
if priority is not None:
|
|
733
|
+
updates.append("priority = ?")
|
|
734
|
+
params.append(priority)
|
|
735
|
+
if domain is not None:
|
|
736
|
+
updates.append("domain = ?")
|
|
737
|
+
params.append(domain)
|
|
738
|
+
if notes is not None:
|
|
739
|
+
updates.append("notes = ?")
|
|
740
|
+
params.append(notes)
|
|
741
|
+
if project is not None:
|
|
742
|
+
updates.append("project = ?")
|
|
743
|
+
params.append(project)
|
|
744
|
+
if client is not None:
|
|
745
|
+
updates.append("client = ?")
|
|
746
|
+
params.append(client)
|
|
747
|
+
if forbidden is not None:
|
|
748
|
+
updates.append("forbidden = ?")
|
|
749
|
+
params.append(forbidden)
|
|
750
|
+
|
|
751
|
+
if not updates:
|
|
752
|
+
return False
|
|
753
|
+
|
|
754
|
+
params.append(term_id)
|
|
755
|
+
sql = f"UPDATE termbase_terms SET {', '.join(updates)} WHERE id = ?"
|
|
756
|
+
cursor.execute(sql, params)
|
|
757
|
+
self.db_manager.connection.commit()
|
|
758
|
+
|
|
759
|
+
self.log(f"✓ Updated term {term_id}")
|
|
760
|
+
return True
|
|
761
|
+
except Exception as e:
|
|
762
|
+
self.log(f"✗ Error updating term: {e}")
|
|
763
|
+
return False
|
|
764
|
+
|
|
765
|
+
def delete_term(self, term_id: int) -> bool:
|
|
766
|
+
"""Delete a term"""
|
|
767
|
+
try:
|
|
768
|
+
cursor = self.db_manager.cursor
|
|
769
|
+
cursor.execute("DELETE FROM termbase_terms WHERE id = ?", (term_id,))
|
|
770
|
+
self.db_manager.connection.commit()
|
|
771
|
+
self.log(f"✓ Deleted term {term_id}")
|
|
772
|
+
return True
|
|
773
|
+
except Exception as e:
|
|
774
|
+
self.log(f"✗ Error deleting term: {e}")
|
|
775
|
+
return False
|
|
776
|
+
|
|
777
|
+
# ========================================================================
|
|
778
|
+
# SEARCH
|
|
779
|
+
# ========================================================================
|
|
780
|
+
|
|
781
|
+
def search_termbase(self, termbase_id: int, search_term: str,
|
|
782
|
+
search_source: bool = True, search_target: bool = True) -> List[Dict]:
|
|
783
|
+
"""
|
|
784
|
+
Search within a termbase (searches main terms AND synonyms)
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
termbase_id: Termbase ID to search in
|
|
788
|
+
search_term: Term to search for
|
|
789
|
+
search_source: Search in source terms and source synonyms
|
|
790
|
+
search_target: Search in target terms and target synonyms
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
List of matching terms (includes main term + synonyms as separate entries)
|
|
794
|
+
"""
|
|
795
|
+
try:
|
|
796
|
+
cursor = self.db_manager.cursor
|
|
797
|
+
|
|
798
|
+
# Find matching term IDs (from main terms OR synonyms)
|
|
799
|
+
matching_term_ids = set()
|
|
800
|
+
|
|
801
|
+
if search_source:
|
|
802
|
+
# Search main source terms
|
|
803
|
+
cursor.execute("""
|
|
804
|
+
SELECT id FROM termbase_terms
|
|
805
|
+
WHERE termbase_id = ? AND source_term LIKE ?
|
|
806
|
+
""", (termbase_id, f"%{search_term}%"))
|
|
807
|
+
matching_term_ids.update(row[0] for row in cursor.fetchall())
|
|
808
|
+
|
|
809
|
+
# Search source synonyms
|
|
810
|
+
cursor.execute("""
|
|
811
|
+
SELECT term_id FROM termbase_synonyms
|
|
812
|
+
WHERE term_id IN (SELECT id FROM termbase_terms WHERE termbase_id = ?)
|
|
813
|
+
AND language = 'source' AND synonym_text LIKE ?
|
|
814
|
+
""", (termbase_id, f"%{search_term}%"))
|
|
815
|
+
matching_term_ids.update(row[0] for row in cursor.fetchall())
|
|
816
|
+
|
|
817
|
+
if search_target:
|
|
818
|
+
# Search main target terms
|
|
819
|
+
cursor.execute("""
|
|
820
|
+
SELECT id FROM termbase_terms
|
|
821
|
+
WHERE termbase_id = ? AND target_term LIKE ?
|
|
822
|
+
""", (termbase_id, f"%{search_term}%"))
|
|
823
|
+
matching_term_ids.update(row[0] for row in cursor.fetchall())
|
|
824
|
+
|
|
825
|
+
# Search target synonyms
|
|
826
|
+
cursor.execute("""
|
|
827
|
+
SELECT term_id FROM termbase_synonyms
|
|
828
|
+
WHERE term_id IN (SELECT id FROM termbase_terms WHERE termbase_id = ?)
|
|
829
|
+
AND language = 'target' AND synonym_text LIKE ?
|
|
830
|
+
""", (termbase_id, f"%{search_term}%"))
|
|
831
|
+
matching_term_ids.update(row[0] for row in cursor.fetchall())
|
|
832
|
+
|
|
833
|
+
if not matching_term_ids:
|
|
834
|
+
return []
|
|
835
|
+
|
|
836
|
+
# Get full details for matching terms
|
|
837
|
+
placeholders = ','.join('?' * len(matching_term_ids))
|
|
838
|
+
sql = f"""
|
|
839
|
+
SELECT id, source_term, target_term, priority, domain, definition, forbidden
|
|
840
|
+
FROM termbase_terms
|
|
841
|
+
WHERE id IN ({placeholders})
|
|
842
|
+
ORDER BY priority ASC, source_term ASC
|
|
843
|
+
"""
|
|
844
|
+
|
|
845
|
+
cursor.execute(sql, list(matching_term_ids))
|
|
846
|
+
|
|
847
|
+
results = []
|
|
848
|
+
for row in cursor.fetchall():
|
|
849
|
+
term_id = row[0]
|
|
850
|
+
|
|
851
|
+
# Add main term
|
|
852
|
+
results.append({
|
|
853
|
+
'id': term_id,
|
|
854
|
+
'source_term': row[1],
|
|
855
|
+
'target_term': row[2],
|
|
856
|
+
'priority': row[3],
|
|
857
|
+
'domain': row[4],
|
|
858
|
+
'definition': row[5],
|
|
859
|
+
'forbidden': row[6]
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
# Add target synonyms as separate entries (memoQ style)
|
|
863
|
+
# Synonyms are ordered by display_order (position 0 = main/preferred)
|
|
864
|
+
target_synonyms = self.get_synonyms(term_id, language='target')
|
|
865
|
+
for syn in target_synonyms:
|
|
866
|
+
results.append({
|
|
867
|
+
'id': term_id, # Same term ID
|
|
868
|
+
'source_term': row[1], # Same source
|
|
869
|
+
'target_term': syn['synonym_text'], # Synonym as target
|
|
870
|
+
'priority': row[3],
|
|
871
|
+
'domain': row[4],
|
|
872
|
+
'definition': row[5],
|
|
873
|
+
'forbidden': syn['forbidden'] # Use synonym's forbidden flag
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
return results
|
|
877
|
+
except Exception as e:
|
|
878
|
+
self.log(f"✗ Error searching termbase: {e}")
|
|
879
|
+
return []
|
|
880
|
+
|
|
881
|
+
# ========================================================================
|
|
882
|
+
# SYNONYM MANAGEMENT
|
|
883
|
+
# ========================================================================
|
|
884
|
+
|
|
885
|
+
def add_synonym(self, term_id: int, synonym_text: str, language: str = 'target',
|
|
886
|
+
display_order: int = 0, forbidden: bool = False) -> bool:
|
|
887
|
+
"""
|
|
888
|
+
Add a synonym to a term
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
term_id: Term ID to add synonym to
|
|
892
|
+
synonym_text: The synonym text
|
|
893
|
+
language: 'source' or 'target' (default: 'target')
|
|
894
|
+
display_order: Position in list (0 = main/top, higher = lower priority)
|
|
895
|
+
forbidden: Whether this synonym is forbidden
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
True if successful, False otherwise
|
|
899
|
+
"""
|
|
900
|
+
try:
|
|
901
|
+
cursor = self.db_manager.cursor
|
|
902
|
+
now = datetime.now().isoformat()
|
|
903
|
+
|
|
904
|
+
# Check if synonym already exists
|
|
905
|
+
cursor.execute("""
|
|
906
|
+
SELECT id FROM termbase_synonyms
|
|
907
|
+
WHERE term_id = ? AND synonym_text = ? AND language = ?
|
|
908
|
+
""", (term_id, synonym_text, language))
|
|
909
|
+
|
|
910
|
+
if cursor.fetchone():
|
|
911
|
+
self.log(f"✗ Synonym already exists: {synonym_text}")
|
|
912
|
+
return False
|
|
913
|
+
|
|
914
|
+
cursor.execute("""
|
|
915
|
+
INSERT INTO termbase_synonyms (term_id, synonym_text, language, display_order, forbidden, created_date, modified_date)
|
|
916
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
917
|
+
""", (term_id, synonym_text, language, display_order, 1 if forbidden else 0, now, now))
|
|
918
|
+
|
|
919
|
+
self.db_manager.connection.commit()
|
|
920
|
+
self.log(f"✓ Added synonym: {synonym_text}")
|
|
921
|
+
return True
|
|
922
|
+
except Exception as e:
|
|
923
|
+
self.log(f"✗ Error adding synonym: {e}")
|
|
924
|
+
return False
|
|
925
|
+
|
|
926
|
+
def get_synonyms(self, term_id: int, language: Optional[str] = None) -> List[Dict]:
|
|
927
|
+
"""
|
|
928
|
+
Get synonyms for a term, ordered by display_order (position)
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
term_id: Term ID to get synonyms for
|
|
932
|
+
language: Optional filter - 'source', 'target', or None for both
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
List of synonym dictionaries with fields: id, synonym_text, language, display_order, forbidden
|
|
936
|
+
"""
|
|
937
|
+
try:
|
|
938
|
+
cursor = self.db_manager.cursor
|
|
939
|
+
|
|
940
|
+
if language:
|
|
941
|
+
cursor.execute("""
|
|
942
|
+
SELECT id, synonym_text, language, display_order, forbidden, created_date, modified_date
|
|
943
|
+
FROM termbase_synonyms
|
|
944
|
+
WHERE term_id = ? AND language = ?
|
|
945
|
+
ORDER BY display_order ASC, created_date ASC
|
|
946
|
+
""", (term_id, language))
|
|
947
|
+
else:
|
|
948
|
+
cursor.execute("""
|
|
949
|
+
SELECT id, synonym_text, language, display_order, forbidden, created_date, modified_date
|
|
950
|
+
FROM termbase_synonyms
|
|
951
|
+
WHERE term_id = ?
|
|
952
|
+
ORDER BY language DESC, display_order ASC, created_date ASC
|
|
953
|
+
""", (term_id,))
|
|
954
|
+
|
|
955
|
+
results = []
|
|
956
|
+
for row in cursor.fetchall():
|
|
957
|
+
results.append({
|
|
958
|
+
'id': row[0],
|
|
959
|
+
'synonym_text': row[1],
|
|
960
|
+
'language': row[2],
|
|
961
|
+
'display_order': row[3],
|
|
962
|
+
'forbidden': bool(row[4]),
|
|
963
|
+
'created_date': row[5],
|
|
964
|
+
'modified_date': row[6]
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
return results
|
|
968
|
+
except Exception as e:
|
|
969
|
+
self.log(f"✗ Error getting synonyms: {e}")
|
|
970
|
+
return []
|
|
971
|
+
|
|
972
|
+
def update_synonym_order(self, synonym_id: int, new_order: int) -> bool:
|
|
973
|
+
"""
|
|
974
|
+
Update the display order of a synonym
|
|
975
|
+
|
|
976
|
+
Args:
|
|
977
|
+
synonym_id: Synonym ID to update
|
|
978
|
+
new_order: New display order (0 = top/main)
|
|
979
|
+
|
|
980
|
+
Returns:
|
|
981
|
+
True if successful, False otherwise
|
|
982
|
+
"""
|
|
983
|
+
try:
|
|
984
|
+
cursor = self.db_manager.cursor
|
|
985
|
+
now = datetime.now().isoformat()
|
|
986
|
+
cursor.execute("""
|
|
987
|
+
UPDATE termbase_synonyms
|
|
988
|
+
SET display_order = ?, modified_date = ?
|
|
989
|
+
WHERE id = ?
|
|
990
|
+
""", (new_order, now, synonym_id))
|
|
991
|
+
self.db_manager.connection.commit()
|
|
992
|
+
return True
|
|
993
|
+
except Exception as e:
|
|
994
|
+
self.log(f"✗ Error updating synonym order: {e}")
|
|
995
|
+
return False
|
|
996
|
+
|
|
997
|
+
def update_synonym_forbidden(self, synonym_id: int, forbidden: bool) -> bool:
|
|
998
|
+
"""
|
|
999
|
+
Update the forbidden flag of a synonym
|
|
1000
|
+
|
|
1001
|
+
Args:
|
|
1002
|
+
synonym_id: Synonym ID to update
|
|
1003
|
+
forbidden: New forbidden status
|
|
1004
|
+
|
|
1005
|
+
Returns:
|
|
1006
|
+
True if successful, False otherwise
|
|
1007
|
+
"""
|
|
1008
|
+
try:
|
|
1009
|
+
cursor = self.db_manager.cursor
|
|
1010
|
+
now = datetime.now().isoformat()
|
|
1011
|
+
cursor.execute("""
|
|
1012
|
+
UPDATE termbase_synonyms
|
|
1013
|
+
SET forbidden = ?, modified_date = ?
|
|
1014
|
+
WHERE id = ?
|
|
1015
|
+
""", (1 if forbidden else 0, now, synonym_id))
|
|
1016
|
+
self.db_manager.connection.commit()
|
|
1017
|
+
return True
|
|
1018
|
+
except Exception as e:
|
|
1019
|
+
self.log(f"✗ Error updating synonym forbidden status: {e}")
|
|
1020
|
+
return False
|
|
1021
|
+
|
|
1022
|
+
def reorder_synonyms(self, term_id: int, language: str, synonym_ids_in_order: List[int]) -> bool:
|
|
1023
|
+
"""
|
|
1024
|
+
Reorder synonyms for a term
|
|
1025
|
+
|
|
1026
|
+
Args:
|
|
1027
|
+
term_id: Term ID
|
|
1028
|
+
language: 'source' or 'target'
|
|
1029
|
+
synonym_ids_in_order: List of synonym IDs in desired order
|
|
1030
|
+
|
|
1031
|
+
Returns:
|
|
1032
|
+
True if successful, False otherwise
|
|
1033
|
+
"""
|
|
1034
|
+
try:
|
|
1035
|
+
for order, syn_id in enumerate(synonym_ids_in_order):
|
|
1036
|
+
self.update_synonym_order(syn_id, order)
|
|
1037
|
+
return True
|
|
1038
|
+
except Exception as e:
|
|
1039
|
+
self.log(f"✗ Error reordering synonyms: {e}")
|
|
1040
|
+
return False
|
|
1041
|
+
|
|
1042
|
+
def delete_synonym(self, synonym_id: int) -> bool:
|
|
1043
|
+
"""
|
|
1044
|
+
Delete a synonym
|
|
1045
|
+
|
|
1046
|
+
Args:
|
|
1047
|
+
synonym_id: Synonym ID to delete
|
|
1048
|
+
|
|
1049
|
+
Returns:
|
|
1050
|
+
True if successful, False otherwise
|
|
1051
|
+
"""
|
|
1052
|
+
try:
|
|
1053
|
+
cursor = self.db_manager.cursor
|
|
1054
|
+
cursor.execute("DELETE FROM termbase_synonyms WHERE id = ?", (synonym_id,))
|
|
1055
|
+
self.db_manager.connection.commit()
|
|
1056
|
+
self.log(f"✓ Deleted synonym {synonym_id}")
|
|
1057
|
+
return True
|
|
1058
|
+
except Exception as e:
|
|
1059
|
+
self.log(f"✗ Error deleting synonym: {e}")
|
|
1060
|
+
return False
|