From 4045d7faf2c1e806cbf8e9129d2298b22a2f6ca9 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 13 Jul 2026 18:31:23 +0800 Subject: [PATCH] fix: persist translated_filename_stem and pass it to printPdf - _translate_filename_stem: add success/failure logging, include glossary terms in prompt for accurate proper noun translation - task_state: store translated_filename_stem after translation - status API: return translated_filename_stem to frontend - printPdf: accept stem param, set document.title directly instead of fragile Content-Disposition header parsing - Both printPdf call sites now pass task.translatedStem --- docutranslate/app.py | 41 +++++++++++++++++++++++---------- docutranslate/static/index.html | 23 +++++------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/docutranslate/app.py b/docutranslate/app.py index c455d33..6dbb17c 100644 --- a/docutranslate/app.py +++ b/docutranslate/app.py @@ -175,6 +175,7 @@ def _create_default_task_state() -> Dict[str, Any]: "download_ready": False, "workflow_instance": None, # 仅在处理期间使用 "original_filename_stem": None, + "translated_filename_stem": None, "task_start_time": 0, "task_end_time": 0, "current_task_ref": None, @@ -631,11 +632,31 @@ async def _translate_filename_stem( stem: str, to_lang: str, base_url: str | None, api_key: str, model_id: str | None ) -> str: """Translate a filename stem to target language for cleaner download filenames.""" + _logger = logging.getLogger(__name__) try: clean_base = (base_url or "").strip().rstrip("/") clean_model = (model_id or "").strip() if not clean_base or not clean_model: + _logger.warning(f"Filename translation skipped: LLM not configured (stem='{stem}')") return stem + # Include glossary terms so proper nouns translate correctly + try: + from docutranslate.glossary.glossary_store import get_glossary_store + rows = get_glossary_store().get_rows() + glossary_hint = ", ".join( + f"{r.get('zh', '')}={r.get('en', '')}" + for r in rows[:20] + if r.get('zh') and r.get('en') + ) + except Exception: + glossary_hint = "" + prompt = ( + f"Translate the document filename below into {to_lang}. " + "Return ONLY the translated name, no file extension, no explanation, no quotes.\n" + ) + if glossary_hint: + prompt += f"Reference glossary if relevant: {glossary_hint}\n" + prompt += f"Filename: {stem}" resp = await httpx_client.post( f"{clean_base}/chat/completions", headers={ @@ -644,16 +665,7 @@ async def _translate_filename_stem( }, json={ "model": clean_model, - "messages": [ - { - "role": "user", - "content": ( - f"Translate the document filename below into {to_lang}. " - "Return ONLY the translated name, no file extension, no explanation, no quotes.\n" - f"Filename: {stem}" - ), - } - ], + "messages": [{"role": "user", "content": prompt}], "max_tokens": 60, "temperature": 0, }, @@ -663,8 +675,11 @@ async def _translate_filename_stem( translated = resp.json()["choices"][0]["message"]["content"].strip().strip("\"'") sanitized = re.sub(r"[^\w\s\-]", "", translated).strip() sanitized = re.sub(r"\s+", "_", sanitized)[:50] - return sanitized if sanitized else stem - except Exception: + result = sanitized if sanitized else stem + _logger.info(f"Filename translation: '{stem}' -> '{result}' (lang={to_lang})") + return result + except Exception as e: + _logger.warning(f"Filename translation failed for '{stem}': {e}") return stem @@ -1178,6 +1193,7 @@ async def _perform_translation( getattr(payload, "base_url", None), getattr(payload, "api_key", ""), getattr(payload, "model_id", None), ) + task_state["translated_filename_stem"] = translated_stem # 检查CDN可用性 is_cdn_available = True @@ -1972,6 +1988,7 @@ async def service_get_status( "error_flag": task_state["error_flag"], "download_ready": task_state["download_ready"], "original_filename_stem": task_state["original_filename_stem"], + "translated_filename_stem": task_state.get("translated_filename_stem") or task_state["original_filename_stem"], "original_filename": task_state.get("original_filename"), "task_start_time": task_state["task_start_time"], "task_end_time": task_state["task_end_time"], diff --git a/docutranslate/static/index.html b/docutranslate/static/index.html index 2af301f..0e69fe4 100644 --- a/docutranslate/static/index.html +++ b/docutranslate/static/index.html @@ -807,7 +807,7 @@
  • PDF
  • @@ -933,7 +933,7 @@
  • - + PDF
  • @@ -1872,6 +1872,7 @@ if (statData.download_ready && !statData.error_flag) { task.downloads = statData.downloads; task.attachment = statData.attachment; + task.translatedStem = statData.translated_filename_stem || statData.original_filename_stem; task.statusClass = 'text-success'; } else { task.statusClass = 'text-danger'; @@ -1978,7 +1979,7 @@ localStorage.setItem('ui_sync_scroll_enabled', syncScrollEnabled.value); }; - const printPdf = (url) => { + const printPdf = (url, stem) => { const msg = t('pdf_preparing') || "正在准备打印,请稍候..."; const toastContainer = document.createElement('div'); toastContainer.className = 'toast-container position-fixed top-0 start-50 translate-middle-x p-3'; @@ -2005,25 +2006,13 @@ }, 3000); const ifr = document.getElementById('printFrame'); - fetch(url).then(r => { - const cd = r.headers.get('content-disposition') || ''; - // RFC 5987: filename*=UTF-8'' - const extMatch = cd.match(/filename\*\s*=\s*UTF-8''([^;\s]+)/i); - let printTitle = ''; - if (extMatch) { - try { printTitle = decodeURIComponent(extMatch[1]).replace(/\.[^.]+$/, ''); } catch(e) {} - } else { - const plainMatch = cd.match(/filename\s*=\s*["']?([^"'\n;]+)["']?/i); - if (plainMatch) printTitle = plainMatch[1].trim().replace(/\.[^.]+$/, ''); - } - return r.text().then(h => ({ h, printTitle })); - }).then(({ h, printTitle }) => { + fetch(url).then(r => r.text()).then(h => { ifr.srcdoc = h; ifr.onload = () => { setTimeout(() => { ifr.contentWindow.focus(); const originalTitle = document.title; - if (printTitle) document.title = printTitle; + if (stem) document.title = stem; ifr.contentWindow.print(); setTimeout(() => { document.title = originalTitle; }, 1000); }, 500);