feat: translate output filename to target language

Add _translate_filename_stem() which calls the configured LLM to
translate the filename stem before building the export map. Result:
磨粉.pdf translated to English → grinding.pdf (instead of 磨粉_translated.pdf).
Falls back to original stem on any API error with zero impact on translation flow.
This commit is contained in:
2026-07-13 16:13:45 +08:00
parent d86d5c1c64
commit 424d5e01a8

View File

@@ -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,
)