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:
@@ -1,7 +1,9 @@
|
||||
"""
|
||||
Minimax Provider - Cloud LLM translation via Minimax API (m2.7).
|
||||
Minimax Provider - Cloud LLM translation via the Minimax public API.
|
||||
|
||||
Minimax uses an OpenAI-compatible Chat Completions API.
|
||||
Minimax exposes an OpenAI-compatible Chat Completions API at
|
||||
``https://api.minimax.io/v1/chat/completions`` (default model ``MiniMax-M3``).
|
||||
Note: ``api.minimax.chat`` is NOT a reachable public host.
|
||||
"""
|
||||
|
||||
import threading
|
||||
@@ -39,16 +41,10 @@ Rules:
|
||||
|
||||
|
||||
def _get_language_name(code: str) -> str:
|
||||
language_names = {
|
||||
"en": "English", "fr": "French", "es": "Spanish", "de": "German",
|
||||
"it": "Italian", "pt": "Portuguese", "nl": "Dutch", "ru": "Russian",
|
||||
"zh": "Chinese", "ja": "Japanese", "ko": "Korean", "ar": "Arabic",
|
||||
"hi": "Hindi", "tr": "Turkish", "pl": "Polish", "vi": "Vietnamese",
|
||||
"th": "Thai", "uk": "Ukrainian", "cs": "Czech", "sv": "Swedish",
|
||||
"ro": "Romanian", "hu": "Hungarian", "el": "Greek", "he": "Hebrew",
|
||||
}
|
||||
return language_names.get(code.split("-")[0].lower(), code)
|
||||
"""Convert language code to full name (all supported languages)."""
|
||||
from core.languages import language_name
|
||||
|
||||
return language_name(code)
|
||||
|
||||
class MinimaxProviderError(Exception):
|
||||
def __init__(self, code: str, message: str, details: Optional[Dict[str, Any]] = None):
|
||||
@@ -62,17 +58,19 @@ class MinimaxTranslationProvider(TranslationProvider):
|
||||
"""
|
||||
Minimax translation provider using OpenAI-compatible API.
|
||||
|
||||
Default model: MiniMax-M1 (latest). Also supports m2.7 via env config.
|
||||
Default model: MiniMax-M3 (latest public OpenAI-compatible model).
|
||||
The public endpoint is https://api.minimax.io/v1 (NOT api.minimax.chat,
|
||||
which is not a reachable host on the public API).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
model: str = "MiniMax-M1",
|
||||
model: str = "MiniMax-M3",
|
||||
timeout: int = 60,
|
||||
max_retries: int = 3,
|
||||
retry_delay: float = 1.0,
|
||||
base_url: str = "https://api.minimax.chat/v1",
|
||||
base_url: str = "https://api.minimax.io/v1",
|
||||
group_id: str = "",
|
||||
):
|
||||
if not api_key or not api_key.strip():
|
||||
@@ -144,13 +142,24 @@ class MinimaxTranslationProvider(TranslationProvider):
|
||||
def get_name(self) -> str:
|
||||
return self._provider_name
|
||||
|
||||
def is_available(self) -> bool:
|
||||
def _probe_available(self) -> tuple[bool, int]:
|
||||
"""Probe the Minimax API. Returns (available, status_code).
|
||||
|
||||
Minimax does not document a public ``GET /models`` endpoint, so a 404/405
|
||||
on that path does NOT mean the provider is down — it only means the path
|
||||
is absent. We treat any non-401 response as "available": the host is
|
||||
reachable and the API key was not rejected. Only 401 (and network
|
||||
errors) mark the provider unavailable.
|
||||
"""
|
||||
try:
|
||||
headers = {"Authorization": f"Bearer {self._api_key}"}
|
||||
response = requests.get(f"{self._base_url}/models", headers=headers, timeout=5)
|
||||
return response.status_code == 200
|
||||
return response.status_code != 401, response.status_code
|
||||
except Exception:
|
||||
return False
|
||||
return False, 0
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return self._probe_available()[0]
|
||||
|
||||
def translate_text(self, request: TranslationRequest) -> TranslationResponse:
|
||||
text = request.text
|
||||
@@ -163,9 +172,19 @@ class MinimaxTranslationProvider(TranslationProvider):
|
||||
source_lang_name = _get_language_name(source_language)
|
||||
target_lang_name = _get_language_name(target_language)
|
||||
custom_prompt = request.metadata.get("custom_prompt") if request.metadata else None
|
||||
system_prompt = custom_prompt or DEFAULT_TRANSLATION_PROMPT.format(
|
||||
_base_prompt = DEFAULT_TRANSLATION_PROMPT.format(
|
||||
source_lang=source_lang_name, target_lang=target_lang_name
|
||||
)
|
||||
# Base translation instructions always present; the custom prompt
|
||||
# (glossary/tone/context) is appended, never a replacement.
|
||||
if custom_prompt and custom_prompt.strip():
|
||||
system_prompt = (
|
||||
_base_prompt
|
||||
+ "\n\nADDITIONAL CONTEXT AND INSTRUCTIONS:\n"
|
||||
+ custom_prompt.strip()
|
||||
)
|
||||
else:
|
||||
system_prompt = _base_prompt
|
||||
|
||||
last_error = None
|
||||
for attempt in range(self._max_retries + 1):
|
||||
@@ -199,20 +218,19 @@ class MinimaxTranslationProvider(TranslationProvider):
|
||||
|
||||
def health_check(self) -> ProviderHealthStatus:
|
||||
start_time = time.time()
|
||||
try:
|
||||
headers = {"Authorization": f"Bearer {self._api_key}"}
|
||||
response = requests.get(f"{self._base_url}/models", headers=headers, timeout=5)
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
available, status_code = self._probe_available()
|
||||
latency_ms = (time.time() - start_time) * 1000
|
||||
if available:
|
||||
return ProviderHealthStatus(
|
||||
name=self._provider_name, available=response.status_code == 200,
|
||||
name=self._provider_name, available=True,
|
||||
latency_ms=round(latency_ms, 2), last_check=datetime.now(timezone.utc).isoformat(),
|
||||
model=self._model)
|
||||
except Exception as e:
|
||||
return ProviderHealthStatus(
|
||||
name=self._provider_name, available=False,
|
||||
latency_ms=round((time.time() - start_time) * 1000, 2),
|
||||
error=str(e)[:100], last_check=datetime.now(timezone.utc).isoformat(),
|
||||
model=self._model)
|
||||
return ProviderHealthStatus(
|
||||
name=self._provider_name, available=False,
|
||||
latency_ms=round(latency_ms, 2),
|
||||
error=f"probe failed (status={status_code})"[:100],
|
||||
last_check=datetime.now(timezone.utc).isoformat(),
|
||||
model=self._model)
|
||||
|
||||
|
||||
_provider_instance: Optional[MinimaxTranslationProvider] = None
|
||||
|
||||
Reference in New Issue
Block a user