feat: production deployment - full update with providers, admin, glossaries, pricing, tests

Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sepehr Ramezani
2026-04-25 15:01:47 +02:00
parent 2ba4fedfc8
commit 26bd096a06
1178 changed files with 136435 additions and 3047 deletions

View File

@@ -19,7 +19,9 @@ import random
import logging
from collections import OrderedDict
logger = logging.getLogger(__name__)
from core.logging import get_logger
logger = get_logger(__name__)
# Map language codes to full names for LLM prompts (models understand "French" better than "fr")
_LLM_LANG_NAMES = {
@@ -195,7 +197,11 @@ class TranslationProvider(ABC):
try:
return (idx, self.translate(text, target_language, source_language))
except Exception as e:
print(f"Translation error at index {idx}: {e}")
logger.warning(
"translation_error_at_index",
index=idx,
error_type=type(e).__name__,
)
return (idx, text)
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
@@ -320,10 +326,10 @@ class GoogleTranslationProvider(TranslationProvider):
if not texts_to_translate:
return results
src_norm = self._normalize_lang(source_language)
tgt_norm = self._normalize_lang(target_language)
try:
translator = GoogleTranslator(
source=source_language, target=target_language
)
translator = GoogleTranslator(source=src_norm, target=tgt_norm)
# Process in batches
translated_texts = []
@@ -349,7 +355,10 @@ class GoogleTranslationProvider(TranslationProvider):
batch_result = batch
translated_texts.extend(batch_result)
except Exception as e:
print(f"Batch translation error, falling back to individual: {e}")
logger.warning(
"batch_translation_fallback",
error_type=type(e).__name__,
)
for text in batch:
try:
translated_texts.append(translator.translate(text))
@@ -374,17 +383,17 @@ class GoogleTranslationProvider(TranslationProvider):
return results
except Exception as e:
print(f"Batch translation failed: {e}")
logger.warning("batch_translation_failed", error_type=type(e).__name__)
# Fallback to individual translations
for idx, text in zip(indices_to_translate, texts_to_translate):
try:
results[idx] = (
GoogleTranslator(
source=source_language, target=target_language
source=src_norm, target=tgt_norm
).translate(text)
or text
)
except:
except Exception:
results[idx] = text
return results
@@ -416,7 +425,7 @@ class DeepLTranslationProvider(TranslationProvider):
translator = self._get_translator(source_language, target_language)
return translator.translate(text)
except Exception as e:
print(f"Translation error: {e}")
logger.warning("translation_error", error_type=type(e).__name__)
return text
def translate_batch(
@@ -451,7 +460,7 @@ class DeepLTranslationProvider(TranslationProvider):
return results
except Exception as e:
print(f"DeepL batch error: {e}")
logger.warning("deepl_batch_error", error_type=type(e).__name__)
return [self.translate(t, target_language, source_language) for t in texts]
@@ -484,7 +493,7 @@ class LibreTranslationProvider(TranslationProvider):
translator = self._get_translator(source_language, target_language)
return translator.translate(text)
except Exception as e:
print(f"LibreTranslate error: {e}")
logger.warning("libretranslate_error", error_type=type(e).__name__)
return text
def translate_batch(
@@ -515,7 +524,7 @@ class LibreTranslationProvider(TranslationProvider):
return results
except Exception as e:
print(f"LibreTranslate batch error: {e}")
logger.warning("libretranslate_batch_error", error_type=type(e).__name__)
return texts
@@ -582,15 +591,16 @@ ADDITIONAL CONTEXT:
translated = result.get("message", {}).get("content", "").strip()
return translated if translated else text
except requests.exceptions.ConnectionError:
print(
f"Ollama error: Cannot connect to {self.base_url}. Is Ollama running?"
logger.warning(
"ollama_connection_error",
base_url=self.base_url,
)
return text
except requests.exceptions.Timeout:
print(f"Ollama error: Request timeout after 120s")
logger.warning("ollama_timeout", timeout_s=120)
return text
except Exception as e:
print(f"Ollama translation error: {e}")
logger.warning("ollama_translation_error", error_type=type(e).__name__)
return text
def translate_batch(
@@ -645,7 +655,7 @@ ADDITIONAL CONTEXT:
return data.get("models", [])
return []
except Exception as e:
print(f"Ollama list_models error: {e}")
logger.warning("ollama_list_models_error", error_type=type(e).__name__)
return []
def translate_image(self, image_path: str, target_language: str) -> str:
@@ -677,7 +687,7 @@ ADDITIONAL CONTEXT:
result = response.json()
return result.get("message", {}).get("content", "").strip()
except Exception as e:
print(f"Ollama vision translation error: {e}")
logger.warning("ollama_vision_error", error_type=type(e).__name__)
return ""
@staticmethod
@@ -689,7 +699,7 @@ ADDITIONAL CONTEXT:
models = response.json().get("models", [])
return [model["name"] for model in models]
except Exception as e:
print(f"Error listing Ollama models: {e}")
logger.warning("ollama_list_models_error", error_type=type(e).__name__)
return []
@@ -999,7 +1009,7 @@ ADDITIONAL CONTEXT AND INSTRUCTIONS:
translated = response.choices[0].message.content.strip()
return translated if translated else text
except Exception as e:
print(f"OpenAI translation error: {e}")
logger.warning("openai_translation_error", error_type=type(e).__name__)
return text
def translate_image(self, image_path: str, target_language: str) -> str:
@@ -1047,7 +1057,7 @@ ADDITIONAL CONTEXT AND INSTRUCTIONS:
return response.choices[0].message.content.strip()
except Exception as e:
print(f"OpenAI vision translation error: {e}")
logger.warning("openai_vision_error", error_type=type(e).__name__)
return ""