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
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -807,7 +807,7 @@
|
||||
</li>
|
||||
<li v-if="task.downloads.html && !task.downloads.pptx">
|
||||
<a class="dropdown-item" href="#"
|
||||
@click.prevent="printPdf(task.downloads.html)"><i
|
||||
@click.prevent="printPdf(task.downloads.html, task.translatedStem)"><i
|
||||
class="bi bi-file-earmark-pdf me-2"></i>PDF</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -933,7 +933,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="previewTask.downloads.html && !previewTask.downloads.pptx">
|
||||
<a class="dropdown-item" href="#" @click.prevent="printPdf(previewTask.downloads.html)">
|
||||
<a class="dropdown-item" href="#" @click.prevent="printPdf(previewTask.downloads.html, previewTask.translatedStem)">
|
||||
<i class="bi bi-file-earmark-pdf me-2"></i>PDF
|
||||
</a>
|
||||
</li>
|
||||
@@ -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''<percent-encoded>
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user