You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
knowledge upload: rejection reasons reach the user in English only — the API composes prose where it should emit a code #17577
Split from #17528's review, where it was raised as a blocker and measured as not one.
The gap
api/knowledge.py raises four distinguishable upload rejections, each as an English sentence:
Condition
Sentence
oversize
File too large. Maximum size is 10MB
disallowed extension
File type not allowed. Allowed: …
extraction timeout
Could not read '<name>' within <N>s…
no text layer
No text layer found in any of the N page(s)… needs OCR. (#13884)
#17529 makes these reach the user, which they previously did not. They arrive in English in all eleven locales, because the backend composes prose and the frontend has nothing to map.
What this is NOT
It was put as "a regression for ten locales in exchange for an improvement in one." Measured on origin/main before #17529:
err.message was `HTTP ${status}: ${statusText}` — always truthy
So the translated fallback never rendered, and every locale already saw HTTP 400: Bad Request in English. #17529 changes untranslated generic to untranslated specific, equally everywhere. No locale loses a translation. The gap here predates it.
Why the obvious fixes are wrong
Translate the sentences in the frontend. Requires matching backend prose by string. The API may reword at any time and the match fails silently, degrading to the English it was meant to replace — a mapping whose failure mode is invisible.
Move the strings into the frontend. The backend needs them too: they appear in logs and in API responses to non-browser callers.
The shape that works
Have the endpoint emit a stable code beside the prose:
{ "detail": "File too large. Maximum size is 10MB",
"detail_code": "upload.too_large",
"detail_params": { "max_mb": 10 } }
The frontend maps detail_code to a translated key with parameters, and keeps detail as the fallback for any code it does not know. English prose stays the backend's, translation stays the frontend's, and an unmapped code degrades to today's behaviour rather than to nothing.
Acceptance criteria
POST /knowledge_base/upload emits detail_code for all four rejections, alongside the existing detail
detail_code values are stable identifiers, asserted by a backend test against the four raise sites
The frontend maps each to an i18n key present in all 11 locales
A guard fails if a raise site gains a rejection without a detail_code
Not claimed
Whether other endpoints share this shape is unmeasured — I looked only at the upload path. If they do, the mapping belongs somewhere shared rather than in the upload UI, and that decision should be made before the first one is written.
So the parent was not inferred from the file path — it is where this issue's own pair already sits, on the same line and the same defect chain.
How it was found. A closed-state duplicate sweep on a new axis: file:line citations appearing in both an open and a closed issue body. This pair was one of 30 such collisions. None of the 30 was a duplicate — the axis turns out to measure shared provenance rather than duplication, because siblings from one audit cite the same lines by construction. Three missing links fell out of it, and this was the best-evidenced.
Activity
mrveiss commentedon Oct 3, 2026
Linked as a native sub-issue of #17527.
What established it, stated so it can be revisited:
KnowledgeUpload.vue:302, and they are the two halves of one concern — knowledge upload: the backend explains every rejection and the frontend shows 'HTTP 400: Bad Request' #17528 was "the backend explains every rejection and the frontend showsHTTP 400: Bad Request", and this issue is the next layer down: the reason now reaches the user, in English only, because the API composes prose where it should emit a code.So the parent was not inferred from the file path — it is where this issue's own pair already sits, on the same line and the same defect chain.
How it was found. A closed-state duplicate sweep on a new axis:
file:linecitations appearing in both an open and a closed issue body. This pair was one of 30 such collisions. None of the 30 was a duplicate — the axis turns out to measure shared provenance rather than duplication, because siblings from one audit cite the same lines by construction. Three missing links fell out of it, and this was the best-evidenced.No scope, milestone, state or title changed.