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:
@@ -20,12 +20,9 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeou
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
try:
|
||||
import structlog
|
||||
logger = structlog.get_logger(__name__)
|
||||
except ImportError:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
from .base import TranslationProvider
|
||||
from .schemas import (
|
||||
@@ -42,6 +39,23 @@ GOOGLE_NETWORK_ERROR = "GOOGLE_NETWORK_ERROR"
|
||||
GOOGLE_UNSUPPORTED_LANGUAGE = "GOOGLE_UNSUPPORTED_LANGUAGE"
|
||||
GOOGLE_TEXT_TOO_LONG = "GOOGLE_TEXT_TOO_LONG"
|
||||
|
||||
# Align with services.translation_service.GoogleTranslationProvider (deep_translator codes)
|
||||
_GOOGLE_LANG_MAP: dict[str, str] = {
|
||||
"zh": "zh-CN",
|
||||
"zh-cn": "zh-CN",
|
||||
"zh-tw": "zh-TW",
|
||||
"iw": "he",
|
||||
"he": "iw",
|
||||
"jv": "jw",
|
||||
"nb": "no",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_lang_for_google(code: str) -> str:
|
||||
if not code or code == "auto":
|
||||
return "auto"
|
||||
return _GOOGLE_LANG_MAP.get(code, _GOOGLE_LANG_MAP.get(code.lower(), code))
|
||||
|
||||
_RETRYABLE_ERRORS = {GOOGLE_NETWORK_ERROR, GOOGLE_QUOTA_EXCEEDED}
|
||||
|
||||
|
||||
@@ -121,13 +135,13 @@ class GoogleTranslationProvider(TranslationProvider):
|
||||
"""Get or create a translator instance for the current thread."""
|
||||
from deep_translator import GoogleTranslator
|
||||
|
||||
key = f"{source_language}_{target_language}"
|
||||
src = _normalize_lang_for_google(source_language)
|
||||
tgt = _normalize_lang_for_google(target_language)
|
||||
key = f"{src}_{tgt}"
|
||||
if not hasattr(self._local, "translators"):
|
||||
self._local.translators = {}
|
||||
if key not in self._local.translators:
|
||||
self._local.translators[key] = GoogleTranslator(
|
||||
source=source_language, target=target_language
|
||||
)
|
||||
self._local.translators[key] = GoogleTranslator(source=src, target=tgt)
|
||||
return self._local.translators[key]
|
||||
|
||||
def _make_api_request(
|
||||
@@ -377,15 +391,42 @@ class GoogleTranslationProvider(TranslationProvider):
|
||||
"""
|
||||
Translate multiple texts with optimized batch processing.
|
||||
|
||||
Args:
|
||||
requests: List of TranslationRequest objects
|
||||
|
||||
Returns:
|
||||
List of TranslationResponse objects
|
||||
When all requests share the same source/target languages, delegates to
|
||||
services.translation_service.GoogleTranslationProvider.translate_batch
|
||||
(batched deep_translator calls). Otherwise falls back to per-item
|
||||
translate_text.
|
||||
"""
|
||||
if not requests:
|
||||
return []
|
||||
|
||||
tgt0 = requests[0].target_language
|
||||
src0 = requests[0].source_language or "auto"
|
||||
uniform = all(
|
||||
r.target_language == tgt0 and (r.source_language or "auto") == src0
|
||||
for r in requests
|
||||
)
|
||||
if uniform:
|
||||
try:
|
||||
from services.translation_service import (
|
||||
GoogleTranslationProvider as LegacyGoogle,
|
||||
)
|
||||
|
||||
texts = [r.text for r in requests]
|
||||
outs = LegacyGoogle().translate_batch(texts, tgt0, src0)
|
||||
return [
|
||||
TranslationResponse(
|
||||
translated_text=out,
|
||||
provider_name=self._provider_name,
|
||||
from_cache=False,
|
||||
)
|
||||
for out in outs
|
||||
]
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"google_batch_legacy_fallback",
|
||||
error_type=type(e).__name__,
|
||||
)
|
||||
|
||||
return [self.translate_text(req) for req in requests]
|
||||
|
||||
def health_check(self) -> ProviderHealthStatus:
|
||||
|
||||
Reference in New Issue
Block a user