fix(clip): a clip refusal speaks the user's language, and a choice in the clip form no longer sends it

The backend refuses a clip with a structured reason, but the client showed every one as the generic
"couldn't make the clip": each reason now has its own message in both languages, held to the
backend's reasons in both directions by the contract test. A segmented toggle's and the priority
stepper's buttons had no type, so inside a form one of them submitted it.
This commit is contained in:
2026-10-10 12:52:12 +02:00
parent 50157cc2fd
commit e301632765
7 changed files with 59 additions and 2 deletions
+16
View File
@@ -245,6 +245,22 @@ describe("req — a structured refusal reads in the user's language", () => {
expect(err.detail).toBeUndefined();
});
it.each([
["too_long", "clip.errors.too_long"],
["bad_crop", "clip.errors.bad_crop"],
])("says why a clip was not cut (%s)", async (reason, localized) => {
// It read as the generic "couldn't carry out that action" — a 10-minute cap no retry gets past.
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ detail: { code: "clip", reason } }), {
status: 422,
headers: { "content-type": "application/json" },
}),
);
await expect(
req("/api/downloads/clip", { method: "POST" }, { quiet: true }),
).rejects.toMatchObject({ detail: localized });
});
it.each(["quota", "reconnect", "unavailable"])(
"says what to do about an action YouTube refused (%s)",
async (reason) => {
+3
View File
@@ -94,6 +94,9 @@ function localizeDetail(d: unknown): string {
}
if (o?.code === "edit")
return t(`downloads.errors.edit.${o.reason}`, t("downloads.errors.edit.generic"));
// A clip the server will not cut (downloads/clip): the range, the frame, the source. An unknown
// reason reads as the plain failure rather than a key.
if (o?.code === "clip") return t(`clip.errors.${o.reason}`, t("clip.failed"));
if (o?.code === "cookies")
return t(`downloads.errors.cookies.${o.reason}`, t("downloads.errors.cookies.generic"));
// Password sign-in refusals. The card renders the account-shaped ones as a full panel