fix(notifications): a notice keeps what to say and says it in the reader's language when shown

This commit is contained in:
2026-10-29 22:37:14 +01:00
parent 6986eb1937
commit 4ef772d37b
125 changed files with 973 additions and 674 deletions
+11 -9
View File
@@ -34,10 +34,10 @@ import {
type Video,
type VideoStatus,
} from "../../lib/api";
import { notify } from "../../lib/notifications";
import { msg, notify } from "../../lib/notifications";
import { notifyActionError } from "../../lib/actionErrors";
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
import { playlistName } from "../../lib/playlistName";
import { playlistName, playlistNameText } from "../../lib/playlistName";
import { reinsertAt } from "../../lib/reinsert";
import { useUndoable } from "../../lib/useUndoable";
import { cycleSort, type SortState } from "../../lib/columnSort";
@@ -231,7 +231,7 @@ function PlaylistDetail() {
// Was a bare .then(): a failed reorder left the UI showing an order the server had
// rejected, and produced an unhandled rejection. Say so, and put back the order the server
// still has — or the rejected one becomes the base for the next drag.
notifyYouTubeActionError(e, t("playlists.reorderFailed"));
notifyYouTubeActionError(e, msg("playlists.reorderFailed"));
restoreFromCache(listId);
qc.invalidateQueries({ queryKey: qk.playlist(listId) });
});
@@ -535,7 +535,7 @@ function PlaylistDetail() {
// Said here, as moveItemTo does, or a removal fails in silence (inside the full-screen player
// there is not even a row snapping back to show it) — unless `req` already put it in its error
// dialog, which `notifyActionError` checks.
notifyActionError(e, t("playlists.removeFailed"));
notifyActionError(e, msg("playlists.removeFailed"));
// …and the row goes back NOW, not when the resync lands: a removal that failed because the
// server was unreachable has a resync that fails too, and the list kept showing the row as
// removed while it was still in the playlist.
@@ -545,7 +545,7 @@ function PlaylistDetail() {
}
notify({
level: "success",
message: t("playlists.removedToast", { title: video.title }),
message: msg("playlists.removedToast", { title: video.title ?? "" }),
action: {
label: t("common.undo"),
// ONE request that puts it back at its index (the server renumbers around it): an append
@@ -567,7 +567,7 @@ function PlaylistDetail() {
if (back.keys.length)
return addPlaylistItem(qc, listRef(listId), video.id, back.positions[0]);
})
.catch((e: unknown) => notifyActionError(e, t("playlists.undoRemoveFailed")))
.catch((e: unknown) => notifyActionError(e, msg("playlists.undoRemoveFailed")))
.finally(() => {
refreshAll(listId);
qc.invalidateQueries({ queryKey: qk.playlistMembership(video.id) });
@@ -619,6 +619,8 @@ function PlaylistDetail() {
const next = items.filter((v) => v.id !== video.id);
order.rebase((list) => list.filter((v) => v.id !== video.id));
lastSetRef.current = `${selectedId}:${idsKey(next)}`;
// Outside the `try`: a `??` inside it makes the React Compiler skip the component.
const title = video.title ?? "";
try {
// ONE request. The two-call version (add there, remove here) leaves the video in BOTH lists
// whenever the second call fails — see the endpoint's docstring.
@@ -628,7 +630,7 @@ function PlaylistDetail() {
setLastMoveTargetId(target.id);
notify({
level: "success",
message: t("playlists.movedToast", { title: video.title, name: plName(target) }),
message: msg("playlists.movedToast", { title, name: playlistNameText(target) }),
});
} catch (e) {
// NOT a silent revert. `req` deliberately leaves 401/403/404 to the caller as control flow, and
@@ -637,7 +639,7 @@ function PlaylistDetail() {
// explanation and the shipped moveErrors strings were unreachable.
// A fault `req` already put in its error dialog is not toasted again — `notifyActionError`
// checks (a review found it reported twice).
notifyActionError(e, t("playlists.moveFailed"));
notifyActionError(e, msg("playlists.moveFailed"));
restoreFromCache(selectedId); // the row is still in this list
}
// BOTH sides changed, and so did this video's membership. `qk.playlist(selectedId)` is an exact
@@ -677,7 +679,7 @@ function PlaylistDetail() {
qc.invalidateQueries({ queryKey: qk.feed() });
})
// No catch used to mean an unhandled rejection and nothing said.
.catch((e: unknown) => notifyActionError(e, t("errors.writeFailed.videoState")));
.catch((e: unknown) => notifyActionError(e, msg("errors.writeFailed.videoState")));
return write;
}