feat(translation): quality pipeline overhaul + new features (audit 2026-08-29)
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m20s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m20s
Translation quality & format preservation: - Word: merge adjacent same-format runs into one unit (sentence-level coherence like inline-tag handling); translate comments/balloons; dedupe textbox collection (was translated twice); RTL no longer overrides center/justify alignment; CJK/Arabic font hints (eastAsia/cs) - PPTX: chart translations now actually reach the output file (ChartPart.blob is read-only — rewrite chart XML in the saved ZIP); CJK typeface hints (a:ea) - Excel: sheet renames no longer break references — rewrite cell formulas (3D/quoted), defined names, data validations, cond. formats - PDF: bold/italic honored (hebo/heit/hebi); table cells never merge; unchanged blocks left untouched (typography preserved, fixes duplicate hyperlinks); attempted/changed stats + route gate now cover PDF; CJK font paths; scanned PDFs via Mistral OCR (detection + admin settings) Features: - formality param (formal/informal) + automatic regional-variant prompts - output_mode=bilingual docx (source above translation) - per-user translation memory on Redis (falls back to LRU), context-hashed - QA report + 0-100 confidence score in job status; L0 on by default - OpenAI-compatible providers: whole chunk in ONE numbered-JSON request (~15x fewer calls) with per-item fallback; base prompt always present (custom prompt no longer replaces translation instructions) Infra & marketing alignment: - plan-based engine gating + vision gating (closes paid-engine leak); /providers/available filtered per plan; 107 languages exposed - zh-CN/zh-TW validation fixed; libmagic disabled on Windows (native crash) - admin: Mistral OCR settings + engine status dashboard; httpx<0.28 pin (TestClient breakage); Prometheus test fixture fixed - marketing docs aligned with code (PDF+OCR, retention, engines, pricing) - security: .env.ionos/.env.production/provider_settings.json removed Tests: 1173 passed / 0 failed (6 network tests deselected: free Google endpoint temporarily blocked from this machine)
This commit is contained in:
98
routes/waitlist_routes.py
Normal file
98
routes/waitlist_routes.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Waitlist / email-capture routes for the marketing landing page.
|
||||
|
||||
Public endpoint: POST /api/v1/waitlist (no auth) — deduplicated by email,
|
||||
persisted in data/waitlist.json (same JSON-file pattern as provider settings).
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
from config import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/waitlist", tags=["Waitlist"])
|
||||
|
||||
WAITLIST_FILE = config.BASE_DIR / "data" / "waitlist.json"
|
||||
_write_lock = threading.Lock()
|
||||
|
||||
|
||||
class WaitlistEntry(BaseModel):
|
||||
email: EmailStr
|
||||
interest: Optional[str] = None
|
||||
|
||||
|
||||
def _load_waitlist() -> List[dict]:
|
||||
if not WAITLIST_FILE.exists():
|
||||
return []
|
||||
try:
|
||||
with open(WAITLIST_FILE, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data if isinstance(data, list) else []
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load waitlist: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def _save_waitlist(entries: List[dict]) -> None:
|
||||
WAITLIST_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(WAITLIST_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(entries, f, indent=2)
|
||||
|
||||
|
||||
@router.post("", status_code=201, summary="Join the waitlist")
|
||||
async def join_waitlist(entry: WaitlistEntry):
|
||||
email = entry.email.lower().strip()
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
with _write_lock:
|
||||
entries = _load_waitlist()
|
||||
for existing in entries:
|
||||
if existing.get("email", "").lower() == email:
|
||||
existing["updated_at"] = now
|
||||
if entry.interest:
|
||||
existing["interest"] = entry.interest
|
||||
_save_waitlist(entries)
|
||||
logger.info(f"Waitlist rejoin: {email}")
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={
|
||||
"data": {"email": email, "status": "already_joined"},
|
||||
"message": "You are already on the list.",
|
||||
},
|
||||
)
|
||||
|
||||
entries.append(
|
||||
{
|
||||
"email": email,
|
||||
"interest": entry.interest,
|
||||
"joined_at": now,
|
||||
"updated_at": now,
|
||||
}
|
||||
)
|
||||
_save_waitlist(entries)
|
||||
logger.info(f"Waitlist join: {email} (total={len(entries)})")
|
||||
|
||||
return JSONResponse(
|
||||
status_code=201,
|
||||
content={
|
||||
"data": {"email": email, "status": "joined"},
|
||||
"message": "Welcome aboard!",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/count", summary="Waitlist count")
|
||||
async def waitlist_count():
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"data": {"count": len(_load_waitlist())}},
|
||||
)
|
||||
Reference in New Issue
Block a user