fix(notifications): a notice value is no plain string, so a translated name cannot freeze in one language
A name goes in as raw(), a translated word as its own msg(): tsc now lists every value, and the ones that froze are said when shown — Watch later and an untitled channel in the pin and channel notices, a Plex season's name in its removal toast (a new seasonTitleText, which seasonTitle says).
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 { msg, notify } from "../../lib/notifications";
|
||||
import { msg, notify, raw } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import Tooltip from "../../components/ui/Tooltip";
|
||||
import ChannelLink from "./ChannelLink";
|
||||
@@ -71,7 +71,7 @@ export default function ChannelDiscovery({
|
||||
notify({
|
||||
level: "success",
|
||||
title: msg("channels.discovery.subscribedTitle"),
|
||||
message: msg("channels.discovery.subscribedBody", { name }),
|
||||
message: msg("channels.discovery.subscribedBody", { name: raw(name) }),
|
||||
meta: { kind: "channel-subscribed", channelId: c.id, channelName: name },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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 { msg, notify } from "../../lib/notifications";
|
||||
import { msg, notify, raw } from "../../lib/notifications";
|
||||
import { Badge } from "../../components/ui/Badge";
|
||||
import { Button } from "../../components/ui/Button";
|
||||
import Tooltip from "../../components/ui/Tooltip";
|
||||
@@ -168,7 +168,7 @@ export default function Channels() {
|
||||
(tagId: number, name: string) => {
|
||||
setFeedFilters({ ...feedFilters, tags: [tagId], channelIds: [] });
|
||||
setPage("feed");
|
||||
notify({ level: "info", message: msg("channels.notify.filteredByTag", { name }) });
|
||||
notify({ level: "info", message: msg("channels.notify.filteredByTag", { name: raw(name) }) });
|
||||
},
|
||||
[setFeedFilters, feedFilters, setPage],
|
||||
);
|
||||
@@ -406,7 +406,10 @@ export default function Channels() {
|
||||
((channelsQuery.data ?? []).find((c) => c.id === rowMenu.target!.id) ?? rowMenu.target);
|
||||
const menuItems = menuRow
|
||||
? channelActions.items(menuRow, {
|
||||
pin: rowPin.item(pinChannel(menuRow.id), menuRow.title ?? t("channels.row.thisChannel")),
|
||||
pin: rowPin.item(
|
||||
pinChannel(menuRow.id),
|
||||
menuRow.title == null ? msg("channels.row.thisChannel") : raw(menuRow.title),
|
||||
),
|
||||
})
|
||||
: [];
|
||||
|
||||
|
||||
@@ -33,7 +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";
|
||||
import { msg, type NotifText, raw } from "../../lib/notifications";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -199,11 +199,24 @@ describe("useChannelActions — hide and unsubscribe", () => {
|
||||
message: NotifText;
|
||||
action: { onClick: () => void };
|
||||
};
|
||||
expect(toast.message).toEqual(msg("channels.notify.hidden", { name: "Chan" }));
|
||||
expect(toast.message).toEqual(msg("channels.notify.hidden", { name: raw("Chan") }));
|
||||
act(() => toast.action.onClick());
|
||||
await waitFor(() => expect(upd).toHaveBeenCalledWith("UC1", { hidden: false }));
|
||||
});
|
||||
|
||||
it('names a channel with no title yet as "this channel" in the reader\'s language when shown', async () => {
|
||||
vi.spyOn(api, "updateChannel").mockResolvedValue({} as never);
|
||||
const { result } = mount();
|
||||
act(() =>
|
||||
result.current.items(ch({ title: null }), { pin }).find((i) => i.id === "hide")!.onSelect!(),
|
||||
);
|
||||
await waitFor(() => expect(h.notify).toHaveBeenCalled());
|
||||
const toast = h.notify.mock.calls[0]![0] as { message: NotifText };
|
||||
expect(toast.message).toEqual(
|
||||
msg("channels.notify.hidden", { name: msg("channels.row.thisChannel") }),
|
||||
);
|
||||
});
|
||||
|
||||
it("unsubscribes only after the danger confirm says yes", async () => {
|
||||
const unsub = vi.spyOn(api, "unsubscribeChannel").mockResolvedValue({} as never);
|
||||
const { result } = mount();
|
||||
|
||||
@@ -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 { msg, notify } from "../../lib/notifications";
|
||||
import { msg, notify, raw } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { useMe } from "../../lib/useMe";
|
||||
import { useConfirm } from "../../components/ui/confirmContext";
|
||||
@@ -46,7 +46,9 @@ export function useChannelActions({
|
||||
const me = useMe();
|
||||
const isAdmin = me.role === "admin";
|
||||
const copy = useCopyItems();
|
||||
const nameOf = (c: ManagedChannel) => c.title ?? t("channels.row.thisChannel");
|
||||
// The notice says it when shown: a channel with no title yet is "this channel" in the reader's language.
|
||||
const nameOf = (c: ManagedChannel) =>
|
||||
c.title == null ? msg("channels.row.thisChannel") : raw(c.title);
|
||||
|
||||
const refresh = () => {
|
||||
qc.invalidateQueries({ queryKey: qk.channels() });
|
||||
|
||||
@@ -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 { msg, notify } from "../../lib/notifications";
|
||||
import { msg, notify, raw } from "../../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../../lib/youtubeErrors";
|
||||
import { useConfirm } from "../../components/ui/confirmContext";
|
||||
|
||||
@@ -22,7 +22,7 @@ export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
notify({
|
||||
level: "success",
|
||||
title: msg("channels.discovery.subscribedTitle"),
|
||||
message: msg("channels.discovery.subscribedBody", { name: nameOf(c) }),
|
||||
message: msg("channels.discovery.subscribedBody", { name: raw(nameOf(c)) }),
|
||||
meta: { kind: "channel-subscribed", channelId: c.id, channelName: nameOf(c) },
|
||||
});
|
||||
},
|
||||
@@ -43,7 +43,7 @@ export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
void qc.invalidateQueries({ queryKey: qk.watchedChannels() });
|
||||
notify({
|
||||
level: "success",
|
||||
message: msg("channels.watched.dismissed", { name: nameOf(c) }),
|
||||
message: msg("channels.watched.dismissed", { name: raw(nameOf(c)) }),
|
||||
action: { label: t("common.undo"), onClick: () => restore.mutate(c) },
|
||||
});
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export function useWatchedChannelActions(onOpenWizard: () => void) {
|
||||
const onSubscribe = async (c: WatchedChannel): Promise<boolean> => {
|
||||
const ok = await confirm({
|
||||
title: t("channels.discovery.subscribe"),
|
||||
message: t("channels.discovery.confirmSubscribe", { name: nameOf(c) }),
|
||||
message: t("channels.discovery.confirmSubscribe", { name: raw(nameOf(c)) }),
|
||||
confirmLabel: t("channels.discovery.subscribe"),
|
||||
});
|
||||
if (ok) subscribe.mutate(c);
|
||||
|
||||
Reference in New Issue
Block a user