feat(i18n): the server's closed English vocabularies named in the reader's language (E30 S2)
- lib/vocab + the `vocab` locale area: TMDB crew jobs and departments, auto-tags (D1 A: the data
stays English, the catalog is named at its one source, useTags), built-in download profiles,
YouTube topics; held key-for-key to the backend's own lists by backendIds.contract.
- Countries by ISO code: TMDB parse stores {code, name}; rows stored before are coded from TMDB's
own country list in one request (run_enrich); Intl names them, historic codes (YU, SU, XI…)
from the catalog.
- Player tracks named from the language tag, forced/SDH flags and codec tail, not Plex's
server-language displayTitle; no invented "Audio N" on the server.
- Download lifecycle notices (shared / expiring / expired / evicted) named from data; the share
notice carries the title.
- Scheduler: the job result rides along as data and is named per key (dashboard + run notice).
- Invite status named; a render crash's notice no longer shows the engine's English message.
- Fix: YouTube links …/wiki/Humour, the tagger looked for "Humor" — Comedy never came from a topic.
This commit is contained in:
@@ -18,7 +18,8 @@ import {
|
||||
UserPlus,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { api, type AppNotification } from "../lib/api";
|
||||
import { api, type AppNotification, type JobResult } from "../lib/api";
|
||||
import { jobResultText } from "../lib/jobResult";
|
||||
import { notifyActionError } from "../lib/actionErrors";
|
||||
import { useLiveQuery } from "../lib/useLiveQuery";
|
||||
import { focusAccessRequestsTab } from "../lib/adminUsersTab";
|
||||
@@ -438,6 +439,14 @@ export default function NotificationsPanel() {
|
||||
}
|
||||
|
||||
/** A server notification's link, from its type and typed payload (the row's button and its menu). */
|
||||
// The download-lifecycle notification types → their `inbox.<key>` copy.
|
||||
const DOWNLOAD_LIFECYCLE: Record<string, string | undefined> = {
|
||||
download_shared: "downloadShared",
|
||||
download_expiring: "downloadExpiring",
|
||||
download_expired: "downloadExpired",
|
||||
download_evicted: "downloadEvicted",
|
||||
};
|
||||
|
||||
function serverKind(n: AppNotification): "scheduler" | "download" | null {
|
||||
if (n.type === "scheduler" && typeof n.data?.job_id === "string") return "scheduler";
|
||||
if (n.type === "download_done") return "download";
|
||||
@@ -508,6 +517,8 @@ function NotificationRow({
|
||||
const isScheduler = serverKind(n) === "scheduler";
|
||||
const isDownloadDone = serverKind(n) === "download";
|
||||
const isEmailUndelivered = n.type === "email_undelivered" && typeof n.data?.reason === "string";
|
||||
// A download's life after it is done (E30): shared with you, about to expire, expired, evicted.
|
||||
const lifecycle = DOWNLOAD_LIFECYCLE[n.type];
|
||||
let title = n.title;
|
||||
let body = n.body;
|
||||
if (isMaintenance) {
|
||||
@@ -516,13 +527,14 @@ function NotificationRow({
|
||||
title = t("inbox.maintenance.title");
|
||||
body = t("inbox.maintenance.body", { count: d.count });
|
||||
} else if (isScheduler) {
|
||||
// "<Job label> finished/failed", with the raw result summary as the (technical) body.
|
||||
const d = n.data as { job_id: string; status?: string; summary?: string };
|
||||
// "<Job label> finished/failed", with the result as the body — named in the reader's language
|
||||
// when it rode along as data (E30), else the English summary of an older notice.
|
||||
const d = n.data as { job_id: string; status?: string; summary?: string; result?: JobResult };
|
||||
const job = t(`scheduler.jobs.${d.job_id}`, d.job_id);
|
||||
title = t(d.status === "error" ? "inbox.jobDone.titleError" : "inbox.jobDone.titleOk", {
|
||||
job,
|
||||
});
|
||||
body = d.summary || n.body;
|
||||
body = d.result ? jobResultText(t, d.result) : d.summary || n.body;
|
||||
} else if (isEmailUndelivered) {
|
||||
// Email is fail-soft: a drop leaves no error anywhere else, so this notice IS the trace. Name
|
||||
// the message that was lost, since that is what tells the operator what their users didn't get.
|
||||
@@ -534,6 +546,13 @@ function NotificationRow({
|
||||
: "inbox.emailUndelivered.sendFailed",
|
||||
{ subject: d.subject || n.body || "" },
|
||||
);
|
||||
} else if (lifecycle) {
|
||||
// The server stores an English title ("… was shared with you", or the bare word "download" for an
|
||||
// untitled video); the notice is named from `data`, the video's title as the body — none when it
|
||||
// has no title (or the share notice predates the title riding along).
|
||||
const d = n.data as { title?: string | null; from?: string };
|
||||
title = t(`inbox.${lifecycle}.title`, { from: d.from ?? "" });
|
||||
body = d.title || null;
|
||||
} else if (isDownloadDone) {
|
||||
// "Download ready" / "Edit ready" per the producer's kind, with the video's title as the body
|
||||
// (shipped in data.title). Same notification type; the copy differs so an edit isn't mislabelled.
|
||||
|
||||
Reference in New Issue
Block a user