diff --git a/docutranslate/app.py b/docutranslate/app.py index 3f51f6b..c455d33 100644 --- a/docutranslate/app.py +++ b/docutranslate/app.py @@ -7,6 +7,7 @@ import binascii import json import logging import os +import re import shutil import socket import tempfile @@ -626,6 +627,47 @@ class TranslateServiceRequest(BaseModel): ) +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.""" + try: + clean_base = (base_url or "").strip().rstrip("/") + clean_model = (model_id or "").strip() + if not clean_base or not clean_model: + return stem + resp = await httpx_client.post( + f"{clean_base}/chat/completions", + headers={ + "Authorization": f"Bearer {(api_key or '').strip()}", + "Content-Type": "application/json", + }, + 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}" + ), + } + ], + "max_tokens": 60, + "temperature": 0, + }, + timeout=10, + ) + resp.raise_for_status() + 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: + return stem + + # --- Background Task Logic --- async def _perform_translation( task_id: str, @@ -1131,6 +1173,11 @@ async def _perform_translation( task_state["temp_dir"] = temp_dir downloadable_files = {} filename_stem = task_state["original_filename_stem"] + translated_stem = await _translate_filename_stem( + filename_stem, payload.to_lang, + getattr(payload, "base_url", None), getattr(payload, "api_key", ""), + getattr(payload, "model_id", None), + ) # 检查CDN可用性 is_cdn_available = True @@ -1149,66 +1196,66 @@ async def _perform_translation( if isinstance(workflow, MDFormatsExportable): export_map["markdown"] = ( workflow.export_to_markdown, - f"{filename_stem}_translated.md", + f"{translated_stem}.md", True, ) export_map["markdown_zip"] = ( workflow.export_to_markdown_zip, - f"{filename_stem}_translated.zip", + f"{translated_stem}.zip", False, ) if isinstance(workflow, TXTExportable): export_map["txt"] = ( workflow.export_to_txt, - f"{filename_stem}_translated.txt", + f"{translated_stem}.txt", True, ) if isinstance(workflow, JsonExportable): export_map["json"] = ( workflow.export_to_json, - f"{filename_stem}_translated.json", + f"{translated_stem}.json", True, ) if isinstance(workflow, XlsxExportable): export_map["xlsx"] = ( workflow.export_to_xlsx, - f"{filename_stem}_translated.xlsx", + f"{translated_stem}.xlsx", False, ) if isinstance(workflow, CsvExportable): export_map["csv"] = ( workflow.export_to_csv, - f"{filename_stem}_translated.csv", + f"{translated_stem}.csv", False, ) if isinstance(workflow, DocxExportable): export_map["docx"] = ( workflow.export_to_docx, - f"{filename_stem}_translated.docx", + f"{translated_stem}.docx", False, ) if isinstance(workflow, SrtExportable): export_map["srt"] = ( workflow.export_to_srt, - f"{filename_stem}_translated.srt", + f"{translated_stem}.srt", True, ) if isinstance(workflow, EpubExportable): export_map["epub"] = ( workflow.export_to_epub, - f"{filename_stem}_translated.epub", + f"{translated_stem}.epub", False, ) if isinstance(workflow, AssExportable): export_map["ass"] = ( workflow.export_to_ass, - f"{filename_stem}_translated.ass", + f"{translated_stem}.ass", True, ) if isinstance(workflow, PPTXExportable): export_map["pptx"] = ( workflow.export_to_pptx, - f"{filename_stem}_translated.pptx", + f"{translated_stem}.pptx", False, ) @@ -1235,7 +1282,7 @@ async def _perform_translation( html_config = PPTX2HTMLExporterConfig(cdn=is_cdn_available) export_map["html"] = ( lambda: workflow.export_to_html(html_config), - f"{filename_stem}_translated.html", + f"{translated_stem}.html", True, )