fix(i18n): every refusal the API answers is structured and said in the reader's language
~150 HTTPExceptions carried a hand-written English sentence as their detail,
shown verbatim under the Hungarian UI — in the blocking error dialog and on
every inline surface that printed HttpError.detail (sign-in, setup wizard,
change password, the passphrase gate, live search). Four also quoted an
upstream exception's text back to the user.
- app/refusal.py: `refused(status, reason, **params)` → {code: "refused",
reason, …params}; localizeDetail translates errors.refused.<reason> with the
params interpolated. "Unknown X" 404s use the existing not_found(what).
- sysconfig.set_value raises InvalidSetting (reason + bounds) instead of an
English ValueError; the three writers answer it through setting_refused.
- The Plex connection/write/image failures log what the upstream said and
answer a reason; the search scraper's failure is logged and answered as
youtube/unavailable (it crashed refusal_reason before).
- HttpError.detail now only ever holds a translated reason: a string detail
(only the framework's own can remain) is left out, so reading .detail is
safe everywhere — including destructured, the hole the raw-detail source
scan could not see. That scan and its five-file exception list are gone.
- Enforced: test_http_details.py fails on any HTTPException with a sentence
for a detail; the contract test requires every refused reason and every
not_found "what" in both languages, and no key for one that is gone.
- local/jsx-comment-text: a // inside JSX children renders as text; the rule
is ten lines in eslint.config.js rather than eslint-plugin-react's ~1800
lockfile lines.
This commit is contained in:
@@ -212,11 +212,23 @@ def _default(s: ConfigSpec):
|
||||
return getattr(settings, s.default_attr)
|
||||
|
||||
|
||||
class InvalidSetting(ValueError):
|
||||
"""A value `set_value` refuses, as a reason the UI translates (`errors.refused.setting_<reason>`)
|
||||
plus what the sentence needs — the English message is for logs only. A ValueError still, so a
|
||||
caller that only needs "refused or not" keeps working."""
|
||||
|
||||
def __init__(self, key: str, reason: str, **params: object):
|
||||
super().__init__(f"{key}: {reason} {params}")
|
||||
self.key = key
|
||||
self.reason = reason
|
||||
self.params = params
|
||||
|
||||
|
||||
def _choice(s: ConfigSpec, raw: object) -> str:
|
||||
"""The canonical spelling of `raw` among the key's choices (any letter case), or ValueError."""
|
||||
"""The canonical spelling of `raw` among the key's choices (any letter case), or InvalidSetting."""
|
||||
option = canonical_choice(s.choices, raw)
|
||||
if option is None:
|
||||
raise ValueError(f"{s.key} must be one of: {', '.join(s.choices)}")
|
||||
raise InvalidSetting(s.key, "not_a_choice", choices=", ".join(s.choices))
|
||||
return option
|
||||
|
||||
|
||||
@@ -306,11 +318,14 @@ def set_value(db: Session, key: str, value) -> None:
|
||||
(B-core-10). The staged value is visible to a same-session `get()` before the commit."""
|
||||
s = _BY_KEY[key]
|
||||
if s.type == "int":
|
||||
v = int(value)
|
||||
try:
|
||||
v = int(value)
|
||||
except (TypeError, ValueError):
|
||||
raise InvalidSetting(key, "not_a_number") from None
|
||||
if s.min is not None and v < s.min:
|
||||
raise ValueError(f"{key} must be >= {s.min}")
|
||||
raise InvalidSetting(key, "too_low", min=s.min)
|
||||
if s.max is not None and v > s.max:
|
||||
raise ValueError(f"{key} must be <= {s.max}")
|
||||
raise InvalidSetting(key, "too_high", max=s.max)
|
||||
raw = str(v)
|
||||
elif s.type == "bool":
|
||||
raw = "true" if value else "false"
|
||||
@@ -322,7 +337,7 @@ def set_value(db: Session, key: str, value) -> None:
|
||||
stored, encrypted = raw, False
|
||||
if s.secret:
|
||||
if not secrets_manageable():
|
||||
raise RuntimeError("TOKEN_ENCRYPTION_KEY is not configured")
|
||||
raise InvalidSetting(key, "no_encryption_key")
|
||||
stored, encrypted = security.encrypt(raw) or "", True
|
||||
|
||||
row = db.get(SystemConfig, key)
|
||||
|
||||
Reference in New Issue
Block a user