feat(translation): 8h sweep + drain button + detection probe (#1376)
Throughput: translate_posts now runs every 8h (was daily) as the steady-state cadence for newly-imported posts, and the Settings "Translate now" button runs it in drain mode (run-until-done, no reset) so one press clears the whole untranslated backlog instead of a single 300-post chunk. The interrupt/backoff re-enqueue now preserves the drain flag so a bulk drain resumes cleanly after an Interpreter restart. Misdetection groundwork: surface the detector's confidence from the Interpreter client (it was in the detectedLanguage payload but discarded) and add a read-only "Test translation" box — POST /settings/translation/ probe + TranslationCard UI — that shows detected language + confidence + engine + result for pasted text, without saving. Lets the operator see why a short/abbreviation-heavy English title gets mis-detected so the detection guard (min-length + confidence floor) can be tuned from real numbers. The guard itself follows once the mis-detected cases are probed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CgZP9v2otxVJymiYsnVuMy
This commit is contained in:
@@ -113,9 +113,10 @@ async def test_translation_test_connection(client, monkeypatch):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_translation_run_enqueues_when_configured(client, monkeypatch):
|
||||
sink = {}
|
||||
monkeypatch.setattr(
|
||||
"backend.app.tasks.translation.translate_posts.delay",
|
||||
lambda *a, **k: type("R", (), {"id": "t1"})(),
|
||||
lambda *a, **k: sink.update(k) or type("R", (), {"id": "t1"})(),
|
||||
)
|
||||
await client.patch("/api/settings/import", json={
|
||||
"translation_enabled": True, "interpreter_base_url": "http://i.lan",
|
||||
@@ -123,6 +124,46 @@ async def test_translation_run_enqueues_when_configured(client, monkeypatch):
|
||||
resp = await client.post("/api/settings/translation/run")
|
||||
assert resp.status_code == 202
|
||||
assert (await resp.get_json())["celery_task_id"] == "t1"
|
||||
assert sink["drain"] is True # "Translate now" chases the whole backlog
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_translation_probe_requires_text(client):
|
||||
resp = await client.post(
|
||||
"/api/settings/translation/probe", json={"text": " "})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_translation_probe_requires_base_url(client):
|
||||
# Text given but no Interpreter URL saved → nothing to probe against.
|
||||
resp = await client.post(
|
||||
"/api/settings/translation/probe", json={"text": "hello"})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_translation_probe_returns_detection(client, monkeypatch):
|
||||
# The diagnostic surfaces detected language + confidence + result, unsaved.
|
||||
monkeypatch.setattr(
|
||||
"backend.app.api.settings.ic.translate",
|
||||
lambda texts, **k: {
|
||||
"translations": ["Work in Progress"],
|
||||
"detected_lang": "de", "detected_confidence": 71.5,
|
||||
"engine": "llm", "engine_version": "v1",
|
||||
},
|
||||
)
|
||||
await client.patch("/api/settings/import", json={
|
||||
"translation_enabled": True, "interpreter_base_url": "http://i.lan",
|
||||
})
|
||||
resp = await client.post(
|
||||
"/api/settings/translation/probe", json={"text": "WIP"})
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert body["detected_lang"] == "de"
|
||||
assert body["detected_confidence"] == 71.5
|
||||
assert body["translated"] == "Work in Progress"
|
||||
assert body["target"] == "en"
|
||||
|
||||
|
||||
def _fake_retranslate(monkeypatch, sink):
|
||||
|
||||
Reference in New Issue
Block a user