fix(notifications): a notice keeps what to say and says it in the reader's language when shown
This commit is contained in:
@@ -11,7 +11,7 @@ import { hydrateSort, sortRows, type SortState } from "../../lib/columnSort";
|
||||
import { usePref } from "../../lib/usePref";
|
||||
import { useCardPager } from "../../lib/useCardPager";
|
||||
import { subsColumn } from "./channelColumns";
|
||||
import { notify } from "../../lib/notifications";
|
||||
import { msg, notify } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import Tooltip from "../../components/ui/Tooltip";
|
||||
import ChannelLink from "./ChannelLink";
|
||||
@@ -70,13 +70,13 @@ export default function ChannelDiscovery({
|
||||
// callbacks are gone (see ChannelSubscribedMeta).
|
||||
notify({
|
||||
level: "success",
|
||||
title: t("channels.discovery.subscribedTitle"),
|
||||
message: t("channels.discovery.subscribedBody", { name }),
|
||||
title: msg("channels.discovery.subscribedTitle"),
|
||||
message: msg("channels.discovery.subscribedBody", { name }),
|
||||
meta: { kind: "channel-subscribed", channelId: c.id, channelName: name },
|
||||
});
|
||||
},
|
||||
onError: (err: unknown) =>
|
||||
notifyYouTubeActionError(err, t("channels.discovery.subscribeFailed"), onOpenWizard),
|
||||
notifyYouTubeActionError(err, msg("channels.discovery.subscribeFailed"), onOpenWizard),
|
||||
});
|
||||
|
||||
// Subscribing changes the user's real YouTube account and spends a little quota — confirm
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Sparkles } from "lucide-react";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { LS, ownKey } from "../../lib/storage";
|
||||
import { buildChannelColumns, applySyncFilter, tagFilterChips } from "./channelColumns";
|
||||
import { notify } from "../../lib/notifications";
|
||||
import { msg, notify } from "../../lib/notifications";
|
||||
import { Badge } from "../../components/ui/Badge";
|
||||
import { Button } from "../../components/ui/Button";
|
||||
import Tooltip from "../../components/ui/Tooltip";
|
||||
@@ -168,9 +168,9 @@ export default function Channels() {
|
||||
(tagId: number, name: string) => {
|
||||
setFeedFilters({ ...feedFilters, tags: [tagId], channelIds: [] });
|
||||
setPage("feed");
|
||||
notify({ level: "info", message: t("channels.notify.filteredByTag", { name }) });
|
||||
notify({ level: "info", message: msg("channels.notify.filteredByTag", { name }) });
|
||||
},
|
||||
[setFeedFilters, feedFilters, setPage, t],
|
||||
[setFeedFilters, feedFilters, setPage],
|
||||
);
|
||||
|
||||
const channelsQuery = useQuery({ queryKey: qk.channels(), queryFn: api.channels });
|
||||
@@ -294,10 +294,10 @@ export default function Channels() {
|
||||
invalidate();
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("channels.notify.synced", { count: r.subscriptions ?? 0 }),
|
||||
message: msg("channels.notify.synced", { count: r.subscriptions ?? 0 }),
|
||||
});
|
||||
},
|
||||
onError: (e) => notifyYouTubeActionError(e, t("channels.notify.syncFailed"), onOpenWizard),
|
||||
onError: (e) => notifyYouTubeActionError(e, msg("channels.notify.syncFailed"), onOpenWizard),
|
||||
});
|
||||
const deepAll = useMutation({
|
||||
mutationFn: () => api.deepAll(true),
|
||||
@@ -306,11 +306,11 @@ export default function Channels() {
|
||||
qc.invalidateQueries({ queryKey: qk.myStatus() });
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("channels.notify.fullHistoryRequested", { count: r.updated ?? 0 }),
|
||||
message: msg("channels.notify.fullHistoryRequested", { count: r.updated ?? 0 }),
|
||||
});
|
||||
},
|
||||
onError: (e) =>
|
||||
notifyYouTubeActionError(e, t("channels.notify.fullHistoryFailed"), onOpenWizard),
|
||||
notifyYouTubeActionError(e, msg("channels.notify.fullHistoryFailed"), onOpenWizard),
|
||||
});
|
||||
|
||||
// The tag-chip row is a controlled view over the Tags COLUMN filter (`filters.tags`, a string[] of
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Button } from "../../components/ui/Button";
|
||||
import { IconButton } from "../../components/ui/IconButton";
|
||||
import { Pressable } from "../../components/ui/Pressable";
|
||||
import { SURFACE } from "../../components/ui/surfaces";
|
||||
import { msg } from "../../lib/notifications";
|
||||
|
||||
// Per-user status (not the global catalog): shows the number of videos available to *this*
|
||||
// user, how many of *their* channels are still being fetched, and how many lack full
|
||||
@@ -64,7 +65,7 @@ export default function SyncStatus({
|
||||
onError: (e) =>
|
||||
notifyActionError(
|
||||
e,
|
||||
t(data?.paused ? "errors.writeFailed.syncResume" : "errors.writeFailed.syncPause"),
|
||||
msg(data?.paused ? "errors.writeFailed.syncResume" : "errors.writeFailed.syncPause"),
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Pressable } from "../../components/ui/Pressable";
|
||||
import RemoteImage from "../../components/ui/RemoteImage";
|
||||
import Tooltip from "../../components/ui/Tooltip";
|
||||
import type { WatchedChannelActions } from "./useWatchedChannelActions";
|
||||
import { msg } from "../../lib/notifications";
|
||||
|
||||
const PlayerModal = lazy(() => import("../../features/player/PlayerModal"));
|
||||
|
||||
@@ -67,7 +68,7 @@ export default function WatchedVideosDialog({
|
||||
);
|
||||
void qc.invalidateQueries({ queryKey: qk.feed() });
|
||||
})
|
||||
.catch((e: unknown) => notifyActionError(e, t("errors.writeFailed.videoState")));
|
||||
.catch((e: unknown) => notifyActionError(e, msg("errors.writeFailed.videoState")));
|
||||
return write;
|
||||
});
|
||||
const closePlayer = useStableCallback(() => setPlaying(null));
|
||||
|
||||
@@ -33,6 +33,7 @@ vi.mock("../../lib/useMe", () => ({ useMe: () => ({ role: h.role, can_write: h.c
|
||||
import { useChannelActions } from "./useChannelActions";
|
||||
import { api, type ManagedChannel, type Tag } from "../../lib/api";
|
||||
import type { ActionMenuItem } from "../../components/ui/ActionMenu";
|
||||
import { msg, type NotifText } from "../../lib/notifications";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -195,10 +196,10 @@ describe("useChannelActions — hide and unsubscribe", () => {
|
||||
expect(h.confirm).not.toHaveBeenCalled();
|
||||
expect(upd).toHaveBeenCalledWith("UC1", { hidden: true });
|
||||
const toast = h.notify.mock.calls[0]![0] as {
|
||||
message: string;
|
||||
message: NotifText;
|
||||
action: { onClick: () => void };
|
||||
};
|
||||
expect(toast.message).toBe("channels.notify.hidden");
|
||||
expect(toast.message).toEqual(msg("channels.notify.hidden", { name: "Chan" }));
|
||||
act(() => toast.action.onClick());
|
||||
await waitFor(() => expect(upd).toHaveBeenCalledWith("UC1", { hidden: false }));
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { api, type ManagedChannel, type Tag } from "../../lib/api";
|
||||
import { priorityChoices } from "../../lib/channelOrder";
|
||||
import { compareText } from "../../lib/collate";
|
||||
import { channelYouTubeUrl } from "../../lib/format";
|
||||
import { notify } from "../../lib/notifications";
|
||||
import { msg, notify } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { useMe } from "../../lib/useMe";
|
||||
import { useConfirm } from "../../components/ui/confirmContext";
|
||||
@@ -70,7 +70,7 @@ export function useChannelActions({
|
||||
refresh();
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("channels.notify.hidden", { name: nameOf(c) }),
|
||||
message: msg("channels.notify.hidden", { name: nameOf(c) }),
|
||||
action: {
|
||||
label: t("common.undo"),
|
||||
onClick: () => patch.mutate({ id: c.id, body: { hidden: false } }),
|
||||
@@ -82,19 +82,22 @@ export function useChannelActions({
|
||||
mutationFn: (c: ManagedChannel) => api.unsubscribeChannel(c.id),
|
||||
onSuccess: (_d, c) => {
|
||||
invalidateSubscription(qc, c.id);
|
||||
notify({ level: "success", message: t("channels.notify.unsubscribed", { name: nameOf(c) }) });
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("channels.notify.unsubscribed", { name: nameOf(c) }),
|
||||
});
|
||||
},
|
||||
onError: (e) =>
|
||||
notifyYouTubeActionError(e, t("channels.notify.unsubscribeFailed"), onOpenWizard),
|
||||
notifyYouTubeActionError(e, msg("channels.notify.unsubscribeFailed"), onOpenWizard),
|
||||
});
|
||||
const resetBackfill = useMutation({
|
||||
mutationFn: (c: ManagedChannel) => api.resetChannelBackfill(c.id),
|
||||
onSuccess: (_d, c) => {
|
||||
qc.invalidateQueries({ queryKey: qk.channels() });
|
||||
qc.invalidateQueries({ queryKey: qk.myStatus() });
|
||||
notify({ level: "success", message: t("channels.notify.resetDone", { name: nameOf(c) }) });
|
||||
notify({ level: "success", message: msg("channels.notify.resetDone", { name: nameOf(c) }) });
|
||||
},
|
||||
onError: (e) => notifyYouTubeActionError(e, t("channels.notify.resetFailed"), onOpenWizard),
|
||||
onError: (e) => notifyYouTubeActionError(e, msg("channels.notify.resetFailed"), onOpenWizard),
|
||||
});
|
||||
|
||||
async function onUnsubscribe(c: ManagedChannel) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { qk } from "../../lib/queryKeys";
|
||||
import { invalidateSubscription } from "../../lib/subscriptionCache";
|
||||
import { api, type WatchedChannel } from "../../lib/api";
|
||||
import { notify } from "../../lib/notifications";
|
||||
import { msg, notify } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { useConfirm } from "../../components/ui/confirmContext";
|
||||
|
||||
@@ -21,13 +21,13 @@ export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
invalidateSubscription(qc, c.id);
|
||||
notify({
|
||||
level: "success",
|
||||
title: t("channels.discovery.subscribedTitle"),
|
||||
message: t("channels.discovery.subscribedBody", { name: nameOf(c) }),
|
||||
title: msg("channels.discovery.subscribedTitle"),
|
||||
message: msg("channels.discovery.subscribedBody", { name: nameOf(c) }),
|
||||
meta: { kind: "channel-subscribed", channelId: c.id, channelName: nameOf(c) },
|
||||
});
|
||||
},
|
||||
onError: (err: unknown) =>
|
||||
notifyYouTubeActionError(err, t("channels.discovery.subscribeFailed"), onOpenWizard),
|
||||
notifyYouTubeActionError(err, msg("channels.discovery.subscribeFailed"), onOpenWizard),
|
||||
});
|
||||
const restore = useMutation({
|
||||
mutationFn: (c: WatchedChannel) => api.restoreWatchedChannel(c.id),
|
||||
@@ -43,7 +43,7 @@ export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
void qc.invalidateQueries({ queryKey: qk.watchedChannels() });
|
||||
notify({
|
||||
level: "success",
|
||||
message: t("channels.watched.dismissed", { name: nameOf(c) }),
|
||||
message: msg("channels.watched.dismissed", { name: nameOf(c) }),
|
||||
action: { label: t("common.undo"), onClick: () => restore.mutate(c) },
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user