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:
@@ -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 ""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user