"""A request the app refuses, said in a way the UI can put in the reader's language. Every refusal the API answers carries a structured `detail` — `{code, reason, …params}` — and the client translates it (`lib/api/refusalText.ts` `localizeDetail`). A bare English string reached the Hungarian UI verbatim: the blocking error dialog, and every inline surface that printed `HttpError.detail`, showed whatever sentence the route happened to hand-write. `refused` is the generic family (`errors.refused.`, the params interpolated into the sentence); the older specific codes (`not_found`, `youtube`, `playlist`, the admin refusals, …) stay, since each is already translated. A test (`test_http_details.py`) reads every `HTTPException` in the app and fails on a string detail, and the frontend's contract test requires every `refused(…, "")` to be named in both languages — so the rule is enforced, not described. """ from fastapi import HTTPException def refused(status: int, reason: str, **params: object) -> HTTPException: """The refusal to raise: `raise refused(409, "view_name_taken", name=name)`. `params` travel in the detail for the sentence to interpolate (`{{name}}`); keep them to values the reader may see.""" return HTTPException(status_code=status, detail={"code": "refused", "reason": reason, **params}) # `sysconfig.InvalidSetting.reason` → the refusal it answers as. A dict of literals, so the frontend's # contract test can read every reason this can produce. _SETTING_REASONS = { "not_a_number": "setting_not_a_number", "too_low": "setting_too_low", "too_high": "setting_too_high", "not_a_choice": "setting_not_a_choice", "no_encryption_key": "no_encryption_key", } def setting_refused(exc) -> HTTPException: """A value the admin entered that `sysconfig.set_value` refused (`exc`: its `InvalidSetting`).""" return refused(400, _SETTING_REASONS[exc.reason], key=exc.key, **exc.params)