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:
@@ -17,6 +17,8 @@ DOWNLOAD_URL = "/api/v1/download"
|
||||
REGISTER_URL = "/api/v1/auth/register"
|
||||
LOGIN_URL = "/api/v1/auth/login"
|
||||
|
||||
AUTH_USER_ID = None # set by the authenticated_client fixture
|
||||
|
||||
VALID_USER = {
|
||||
"email": "download@example.com",
|
||||
"password": "Password123!",
|
||||
@@ -135,7 +137,9 @@ def client(users_file: Path, monkeypatch):
|
||||
@pytest.fixture()
|
||||
def authenticated_client(client):
|
||||
"""Client avec un utilisateur enregistre et authentifie."""
|
||||
client.post(REGISTER_URL, json=VALID_USER)
|
||||
global AUTH_USER_ID
|
||||
reg = client.post(REGISTER_URL, json=VALID_USER)
|
||||
AUTH_USER_ID = reg.json()["data"]["id"]
|
||||
response = client.post(
|
||||
LOGIN_URL,
|
||||
json={
|
||||
@@ -184,6 +188,7 @@ class TestDownloadEndpoint:
|
||||
job_id = "tr_test_no_output"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "test.xlsx",
|
||||
"output_path": None,
|
||||
@@ -205,6 +210,7 @@ class TestDownloadEndpoint:
|
||||
job_id = "tr_deleted_disk"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "deleted.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
@@ -224,6 +230,7 @@ class TestDownloadEndpoint:
|
||||
job_id = "tr_test_processing"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "processing",
|
||||
"progress_percent": 50,
|
||||
"file_name": "test.xlsx",
|
||||
@@ -241,6 +248,7 @@ class TestDownloadEndpoint:
|
||||
job_id = "tr_test_queued"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "queued",
|
||||
"file_name": "test.xlsx",
|
||||
}
|
||||
@@ -257,6 +265,7 @@ class TestDownloadEndpoint:
|
||||
job_id = "tr_test_failed"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "failed",
|
||||
"error_message": "Something went wrong",
|
||||
"file_name": "test.xlsx",
|
||||
@@ -288,6 +297,7 @@ class TestContentDisposition:
|
||||
job_id = "tr_test_disposition"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "report.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
@@ -310,6 +320,7 @@ class TestContentDisposition:
|
||||
job_id = "tr_test_docx"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "document.docx",
|
||||
"file_extension": ".docx",
|
||||
@@ -331,6 +342,7 @@ class TestContentDisposition:
|
||||
job_id = "tr_test_pptx"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "presentation.pptx",
|
||||
"file_extension": ".pptx",
|
||||
@@ -364,6 +376,7 @@ class TestFileDeletion:
|
||||
job_id = "tr_test_delete"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "to_delete.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
@@ -400,6 +413,7 @@ class TestMIMETypes:
|
||||
job_id = "tr_test_mime_xlsx"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "test.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
@@ -424,6 +438,7 @@ class TestMIMETypes:
|
||||
job_id = "tr_test_mime_docx"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "test.docx",
|
||||
"file_extension": ".docx",
|
||||
@@ -448,6 +463,7 @@ class TestMIMETypes:
|
||||
job_id = "tr_test_mime_pptx"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "test.pptx",
|
||||
"file_extension": ".pptx",
|
||||
@@ -489,6 +505,7 @@ class TestFileExpired:
|
||||
job_id = "tr_test_not_ready_msg"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "processing",
|
||||
"progress_percent": 30,
|
||||
"file_name": "test.xlsx",
|
||||
@@ -520,11 +537,12 @@ class TestDownloadIntegration:
|
||||
job_id = "tr_test_binary"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "binary_test.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
"output_path": str(output_file),
|
||||
"user_id": None,
|
||||
"user_id": AUTH_USER_ID,
|
||||
}
|
||||
|
||||
response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
|
||||
@@ -557,6 +575,7 @@ class TestErrorDetails:
|
||||
job_id = "tr_test_details"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "processing",
|
||||
"progress_percent": 45,
|
||||
"file_name": "test.xlsx",
|
||||
@@ -589,6 +608,7 @@ class TestDownloadAuthorization:
|
||||
job_id = "tr_other_user123"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "other.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
@@ -622,18 +642,19 @@ class TestDownloadAuthorization:
|
||||
job_id = "tr_own_file123"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": AUTH_USER_ID,
|
||||
"status": "completed",
|
||||
"file_name": "own.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
"output_path": str(output_file),
|
||||
"user_id": None,
|
||||
"user_id": AUTH_USER_ID,
|
||||
}
|
||||
|
||||
response = authenticated_client.get(f"{DOWNLOAD_URL}/{job_id}")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_anonymous_user_can_download_public_job(self, client, tmp_path):
|
||||
"""Anonymous users can download jobs without user_id (public)"""
|
||||
def test_anonymous_job_requires_token(self, client, tmp_path):
|
||||
"""Jobs without an owner require the secret per-job token (fix 2026-08-26)"""
|
||||
from routes import translate_routes
|
||||
|
||||
output_file = tmp_path / "public_job.xlsx"
|
||||
@@ -642,12 +663,19 @@ class TestDownloadAuthorization:
|
||||
job_id = "tr_public_job99"
|
||||
translate_routes._translation_jobs[job_id] = {
|
||||
"id": job_id,
|
||||
"user_id": None,
|
||||
"access_token": "secret_job_token",
|
||||
"status": "completed",
|
||||
"file_name": "public.xlsx",
|
||||
"file_extension": ".xlsx",
|
||||
"output_path": str(output_file),
|
||||
"user_id": None,
|
||||
}
|
||||
|
||||
# Without the token: denied
|
||||
response = client.get(f"{DOWNLOAD_URL}/{job_id}")
|
||||
assert response.status_code == 403
|
||||
assert response.json()["error"] == "ACCESS_DENIED"
|
||||
|
||||
# With the correct token: allowed
|
||||
response = client.get(f"{DOWNLOAD_URL}/{job_id}?token=secret_job_token")
|
||||
assert response.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user