supervertaler 1.9.145__py3-none-any.whl → 1.9.147__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 CHANGED
@@ -34,7 +34,7 @@ License: MIT
34
34
  """
35
35
 
36
36
  # Version Information.
37
- __version__ = "1.9.145"
37
+ __version__ = "1.9.147"
38
38
  __phase__ = "0.9"
39
39
  __release_date__ = "2026-01-20"
40
40
  __edition__ = "Qt"
@@ -65,18 +65,158 @@ def get_user_data_path() -> Path:
65
65
  """
66
66
  Get the path to user data directory.
67
67
 
68
- In frozen builds (EXE): 'user_data' folder next to the EXE
69
- In development: 'user_data' folder next to the script
68
+ This function returns a PERSISTENT location for user data that survives
69
+ pip upgrades and reinstalls. The location varies by context:
70
70
 
71
- The build process copies user_data directly next to the EXE for easy access.
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/
74
+
75
+ 2. PIP INSTALLS: Platform-specific user data directory
76
+ - Windows: %LOCALAPPDATA%/Supervertaler/ (e.g., C:/Users/John/AppData/Local/Supervertaler/)
77
+ - macOS: ~/Library/Application Support/Supervertaler/
78
+ - Linux: ~/.local/share/Supervertaler/ (XDG_DATA_HOME)
79
+ - This location persists across pip upgrades!
80
+
81
+ 3. DEVELOPMENT MODE: 'user_data' or 'user_data_private' next to script
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.
72
86
  """
73
87
  if getattr(sys, 'frozen', False):
74
- # Frozen build: user_data is next to the EXE (copied by build script)
88
+ # =====================================================================
89
+ # FROZEN BUILD (EXE): Keep data next to executable for portability
90
+ # =====================================================================
91
+ # Users can copy the entire folder to a USB drive and it works.
92
+ # The build script copies user_data/ next to Supervertaler.exe
75
93
  return Path(sys.executable).parent / "user_data"
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"
76
118
  else:
77
- # Development: user_data next to script
119
+ # =================================================================
120
+ # DEVELOPMENT MODE: Use folder next to script
121
+ # =================================================================
78
122
  return Path(__file__).parent / "user_data"
79
123
 
124
+
125
+ def migrate_user_data_if_needed(new_path: Path) -> bool:
126
+ """
127
+ Migrate user data from old location (inside site-packages) to new location.
128
+
129
+ This handles the transition for existing pip users whose data was stored
130
+ inside the package directory (which gets wiped on upgrade).
131
+
132
+ Returns True if migration was performed, False otherwise.
133
+ """
134
+ import shutil
135
+
136
+ # Only migrate for pip installs (frozen builds keep data next to EXE)
137
+ if getattr(sys, 'frozen', False):
138
+ return False
139
+
140
+ # Check if we're in a pip install
141
+ script_path = Path(__file__).resolve()
142
+ if "site-packages" not in str(script_path):
143
+ return False # Development mode, no migration needed
144
+
145
+ # Old location: user_data folder inside the package in site-packages
146
+ old_path = script_path.parent / "user_data"
147
+
148
+ # If old location doesn't exist or is empty, no migration needed
149
+ if not old_path.exists():
150
+ return False
151
+
152
+ # Check if old location has actual content (not just empty dirs)
153
+ old_has_content = False
154
+ for item in old_path.rglob('*'):
155
+ if item.is_file():
156
+ old_has_content = True
157
+ break
158
+
159
+ if not old_has_content:
160
+ return False
161
+
162
+ # If new location already has content, don't overwrite (user may have set up fresh)
163
+ new_has_content = False
164
+ if new_path.exists():
165
+ for item in new_path.rglob('*'):
166
+ if item.is_file():
167
+ new_has_content = True
168
+ break
169
+
170
+ if new_has_content:
171
+ # Both have content - log but don't migrate (user may have set up manually)
172
+ print(f"[Migration] Both old and new data locations have content:")
173
+ print(f" Old: {old_path}")
174
+ print(f" New: {new_path}")
175
+ print(f"[Migration] Keeping new location. Old data preserved in site-packages.")
176
+ return False
177
+
178
+ # Perform migration
179
+ print(f"[Migration] Migrating user data from old location...")
180
+ print(f" From: {old_path}")
181
+ print(f" To: {new_path}")
182
+
183
+ try:
184
+ # Ensure new path parent exists
185
+ new_path.parent.mkdir(parents=True, exist_ok=True)
186
+
187
+ # Copy entire directory tree (use copy, not move, for safety)
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}")
218
+ return False
219
+
80
220
  import threading
81
221
  import time # For delays in Superlookup
82
222
  import re
@@ -5167,17 +5307,25 @@ class SupervertalerQt(QMainWindow):
5167
5307
  # ============================================================================
5168
5308
  # USER DATA PATH INITIALIZATION
5169
5309
  # ============================================================================
5170
- # Set up user data paths - handles both development and frozen (EXE) builds
5171
- # In frozen builds, user_data is copied directly next to the EXE by the build script.
5310
+ # User data is stored in a PERSISTENT location that survives pip upgrades:
5311
+ # - Windows: %LOCALAPPDATA%\Supervertaler\
5312
+ # - macOS: ~/Library/Application Support/Supervertaler/
5313
+ # - Linux: ~/.local/share/Supervertaler/
5314
+ # - EXE: user_data/ folder next to executable (portable mode)
5315
+ # - Dev: user_data_private/ or user_data/ next to script
5172
5316
  from modules.database_manager import DatabaseManager
5173
5317
 
5174
5318
  if ENABLE_PRIVATE_FEATURES:
5175
5319
  # Developer mode: use private folder (git-ignored)
5176
5320
  self.user_data_path = Path(__file__).parent / "user_data_private"
5177
5321
  else:
5178
- # Normal mode: use the helper function
5322
+ # Normal mode: use the helper function (handles pip vs EXE vs dev)
5179
5323
  self.user_data_path = get_user_data_path()
5180
5324
 
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
+
5181
5329
  # Ensure user_data directory exists (creates empty folder if missing)
5182
5330
  self.user_data_path.mkdir(parents=True, exist_ok=True)
5183
5331
 
@@ -29814,12 +29962,13 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
29814
29962
  from modules.llm_clients import LLMClient
29815
29963
 
29816
29964
  # Get appropriate API key for provider
29965
+ # Note: api_keys is normalized so both 'gemini' and 'google' exist if either is set
29817
29966
  if provider == 'openai':
29818
29967
  api_key = api_keys.get('openai', '')
29819
- elif provider == 'anthropic':
29968
+ elif provider == 'claude':
29820
29969
  api_key = api_keys.get('claude', '')
29821
- elif provider == 'google':
29822
- api_key = api_keys.get('google', '')
29970
+ elif provider == 'gemini':
29971
+ api_key = api_keys.get('gemini', '')
29823
29972
  elif provider == 'ollama':
29824
29973
  api_key = '' # Ollama doesn't need an API key
29825
29974
  else:
@@ -37596,7 +37745,9 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37596
37745
  return
37597
37746
 
37598
37747
  # Check if API key exists for selected provider
37599
- if provider not in api_keys:
37748
+ # Note: 'gemini' and 'google' are aliases for the same API key
37749
+ has_api_key = provider in api_keys or (provider == 'gemini' and 'google' in api_keys)
37750
+ if not has_api_key:
37600
37751
  reply = QMessageBox.question(
37601
37752
  self, f"{provider.title()} API Key Missing",
37602
37753
  f"{provider.title()} API key not found in api_keys.txt\n\n"
@@ -37675,8 +37826,10 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37675
37826
  # Use modular LLM client with user's settings
37676
37827
  from modules.llm_clients import LLMClient
37677
37828
 
37829
+ # Get API key (handle gemini/google alias)
37830
+ api_key = api_keys.get(provider) or (api_keys.get('google') if provider == 'gemini' else None)
37678
37831
  client = LLMClient(
37679
- api_key=api_keys[provider],
37832
+ api_key=api_key,
37680
37833
  provider=provider,
37681
37834
  model=model
37682
37835
  )
@@ -38668,7 +38821,8 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
38668
38821
  "Please configure your API keys in Settings first."
38669
38822
  )
38670
38823
  return
38671
- elif llm_provider not in api_keys:
38824
+ # Note: 'gemini' and 'google' are aliases for the same API key
38825
+ elif llm_provider not in api_keys and not (llm_provider == 'gemini' and 'google' in api_keys):
38672
38826
  QMessageBox.critical(
38673
38827
  self, f"{llm_provider.title()} API Key Missing",
38674
38828
  f"Please configure your {llm_provider.title()} API key in Settings."
@@ -38917,8 +39071,10 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
38917
39071
 
38918
39072
  if translation_provider_type == 'LLM':
38919
39073
  from modules.llm_clients import LLMClient
39074
+ # Handle gemini/google alias
39075
+ api_key = api_keys.get(translation_provider_name) or (api_keys.get('google') if translation_provider_name == 'gemini' else None)
38920
39076
  client = LLMClient(
38921
- api_key=api_keys[translation_provider_name],
39077
+ api_key=api_key,
38922
39078
  provider=translation_provider_name,
38923
39079
  model=model
38924
39080
  )
@@ -39444,7 +39600,9 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
39444
39600
  "gemini": "gemini"
39445
39601
  }
39446
39602
  api_key_name = provider_key_map.get(provider)
39447
- if not api_keys.get(api_key_name):
39603
+ # Handle gemini/google alias
39604
+ api_key_value = api_keys.get(api_key_name) or (api_keys.get('google') if provider == 'gemini' else None)
39605
+ if not api_key_value:
39448
39606
  return # No API key for selected provider
39449
39607
 
39450
39608
  # Get model based on provider
@@ -39466,7 +39624,7 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
39466
39624
  from modules.translation_results_panel import TranslationMatch
39467
39625
 
39468
39626
  client = LLMClient(
39469
- api_key=api_keys[api_key_name],
39627
+ api_key=api_key_value,
39470
39628
  provider=provider,
39471
39629
  model=model
39472
39630
  )
@@ -39956,6 +40114,13 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
39956
40114
  except Exception as e:
39957
40115
  self.log(f"⚠ Error loading API keys: {str(e)}")
39958
40116
 
40117
+ # Normalize gemini/google aliases - users can use either name
40118
+ # This allows downstream code to just use api_keys.get('gemini') directly
40119
+ if api_keys.get('google') and not api_keys.get('gemini'):
40120
+ api_keys['gemini'] = api_keys['google']
40121
+ elif api_keys.get('gemini') and not api_keys.get('google'):
40122
+ api_keys['google'] = api_keys['gemini']
40123
+
39959
40124
  return api_keys
39960
40125
 
39961
40126
  def ensure_example_api_keys(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.145
3
+ Version: 1.9.147
4
4
  Summary: Professional AI-powered 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
@@ -46,6 +46,7 @@ Requires-Dist: chardet>=5.0.0
46
46
  Requires-Dist: pyyaml>=6.0.0
47
47
  Requires-Dist: markdown>=3.4.0
48
48
  Requires-Dist: pyspellchecker>=0.7.0
49
+ Requires-Dist: platformdirs>=4.0.0
49
50
  Requires-Dist: sounddevice>=0.4.6
50
51
  Requires-Dist: numpy>=1.24.0
51
52
  Requires-Dist: PyMuPDF>=1.23.0
@@ -71,7 +72,7 @@ Dynamic: home-page
71
72
  Dynamic: license-file
72
73
  Dynamic: requires-python
73
74
 
74
- # 🚀 Supervertaler v1.9.145
75
+ # 🚀 Supervertaler v1.9.147
75
76
 
76
77
  [![PyPI version](https://badge.fury.io/py/supervertaler.svg)](https://pypi.org/project/Supervertaler/)
77
78
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
@@ -79,7 +80,24 @@ Dynamic: requires-python
79
80
 
80
81
  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
82
 
82
- **Current Version:** v1.9.145 (January 20, 2026)
83
+ **Current Version:** v1.9.147 (January 21, 2026)
84
+
85
+ ### NEW in v1.9.147 - 📁 Persistent User Data Location
86
+
87
+ **No More Data Loss on Upgrade!** User data (API keys, TMs, glossaries, prompts, settings) is now stored in a persistent location that survives `pip install --upgrade`:
88
+
89
+ | Platform | User Data Location |
90
+ |----------|-------------------|
91
+ | **Windows** | `%LOCALAPPDATA%\MichaelBeijer\Supervertaler\` |
92
+ | **macOS** | `~/Library/Application Support/Supervertaler/` |
93
+ | **Linux** | `~/.local/share/Supervertaler/` |
94
+ | **Windows EXE** | `user_data\` next to executable (portable) |
95
+
96
+ Existing data is automatically migrated on first run after upgrade.
97
+
98
+ ### FIXED in v1.9.146 - 🔑 Gemini/Google API Key Alias
99
+
100
+ **Bug Fix:** Fixed "Gemini API Key Missing" error when users had `google=...` instead of `gemini=...` in their api_keys.txt. Both names now work identically thanks to automatic normalization at load time.
83
101
 
84
102
  ### FIXED in v1.9.140 - 🐛 Glossary Add No Longer Triggers TM Search
85
103
 
@@ -1,4 +1,4 @@
1
- Supervertaler.py,sha256=B2j743pISRdPk_qIPGeSw3Atfw3Ojs-STlmw5EkmD74,2152003
1
+ Supervertaler.py,sha256=0Vkn41czCn0bkCEQ-Vpd1Ee3ifsLUjeJT5QY3wYPNkg,2159745
2
2
  modules/__init__.py,sha256=G58XleS-EJ2sX4Kehm-3N2m618_W2Es0Kg8CW_eBG7g,327
3
3
  modules/ai_actions.py,sha256=i5MJcM-7Y6CAvKUwxmxrVHeoZAVtAP7aRDdWM5KLkO0,33877
4
4
  modules/ai_attachment_manager.py,sha256=mA5ISI22qN9mH3DQFF4gOTciDyBt5xVR7sHTkgkTIlw,11361
@@ -77,9 +77,9 @@ modules/unified_prompt_manager_qt.py,sha256=kpR-Tk1xmVyJGHnZdHm6NbGQPTZ1jB8t4tI_
77
77
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
78
78
  modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
79
79
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
80
- supervertaler-1.9.145.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
- supervertaler-1.9.145.dist-info/METADATA,sha256=sJ3uoEIMo4xK3XreAPZVbdwq0a_1HKPBwV0gn24-DFU,43528
82
- supervertaler-1.9.145.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- supervertaler-1.9.145.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
- supervertaler-1.9.145.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
- supervertaler-1.9.145.dist-info/RECORD,,
80
+ supervertaler-1.9.147.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
81
+ supervertaler-1.9.147.dist-info/METADATA,sha256=cqvLJ6GY6wgzZOqBQynIOMb7CiCaS46UQJbaI0qorSY,44450
82
+ supervertaler-1.9.147.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
83
+ supervertaler-1.9.147.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
84
+ supervertaler-1.9.147.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
85
+ supervertaler-1.9.147.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5