feat(ui,api): wave 3 — editorial pricing, real cancel, server history, DeepL purge
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m36s

Pricing: full editorial redesign — serif card headers with accent pills
replace the colored font-black blocks, tone sweep across toggle/metrics/
features/CTAs, PLAN_COLORS removed; one design system app-wide.

Translate: decorative titles one step down (CTA hierarchy restored);
glossary and image-translation blocks hidden entirely for free users
(progressive disclosure — three controls for free).

Reviews: XLIFF hint line explains the exchange format; backend errors
routed through a friendly mapper (session/not-found/rate-limit/server).

Landing: fabricated hero UI cards (fake 'Context Engine' overlay)
removed — the photo no longer promises screens that don't exist.

Nav: single DashboardNavLinks component shared by sidebar and mobile
drawer (was duplicated markup).

API: GET /api/v1/translations (user job history, paginated; completed
jobs retained 24h) and POST /api/v1/translations/{id}/cancel —
cooperative cancellation with worker checkpoints before dispatch and
before finalisation, reserved quota released immediately. Translate
monitor now offers a real 'Cancel translation' next to 'Back to start';
recent-jobs list reads server history first, localStorage fallback.

DeepL purge (backend): provider module, registry registration, config
attrs/defaults, dispatch branch, admin settings schema + test branch,
legacy availability block, validation rules, plan provider lists,
error-code mappings, MCP enums, translator prompt mention, related
tests updated/removed. Fallback resolver skips unknown providers, so
stale chains containing 'deepl' degrade gracefully.

Verified: backend 110 tests passed; frontend build exit 0, vitest 9/9,
0 missing i18n keys, eslint 63 errors (vs 64 at HEAD).
This commit is contained in:
2026-08-30 22:42:29 +02:00
parent 52748ee653
commit 67365918ae
35 changed files with 398 additions and 1572 deletions

View File

@@ -7,7 +7,7 @@ Optimized for high performance with parallel processing and caching
from abc import ABC, abstractmethod
from typing import Optional, List, Dict, Tuple
import requests
from deep_translator import GoogleTranslator, DeeplTranslator, LibreTranslator
from deep_translator import GoogleTranslator, LibreTranslator
from config import config
import concurrent.futures
import threading
@@ -386,72 +386,6 @@ class GoogleTranslationProvider(TranslationProvider):
return results
class DeepLTranslationProvider(TranslationProvider):
"""DeepL Translate implementation with batch support"""
def __init__(self, api_key: str):
self.api_key = api_key
self._translator_cache = {}
def _get_translator(
self, source_language: str, target_language: str
) -> DeeplTranslator:
key = f"{source_language}_{target_language}"
if key not in self._translator_cache:
self._translator_cache[key] = DeeplTranslator(
api_key=self.api_key, source=source_language, target=target_language
)
return self._translator_cache[key]
def translate(
self, text: str, target_language: str, source_language: str = "auto"
) -> str:
if not text or not text.strip():
return text
try:
translator = self._get_translator(source_language, target_language)
return translator.translate(text)
except Exception as e:
logger.warning("translation_error", error_type=type(e).__name__)
return text
def translate_batch(
self, texts: List[str], target_language: str, source_language: str = "auto"
) -> List[str]:
"""Batch translate using DeepL"""
if not texts:
return []
results = [""] * len(texts)
non_empty = [(i, t) for i, t in enumerate(texts) if t and t.strip()]
if not non_empty:
return [t if t else "" for t in texts]
try:
translator = self._get_translator(source_language, target_language)
non_empty_texts = [t for _, t in non_empty]
if hasattr(translator, "translate_batch"):
translated = translator.translate_batch(non_empty_texts)
else:
translated = [translator.translate(t) for t in non_empty_texts]
for (idx, _), trans in zip(non_empty, translated):
results[idx] = trans if trans else texts[idx]
# Fill empty positions
for i, text in enumerate(texts):
if not text or not text.strip():
results[i] = text if text else ""
return results
except Exception as e:
logger.warning("deepl_batch_error", error_type=type(e).__name__)
return [self.translate(t, target_language, source_language) for t in texts]
class LibreTranslationProvider(TranslationProvider):
"""LibreTranslate implementation with batch support"""