feat(hk): explain background updates with protected content links and retry details
CI / check (push) Failing after 1m34s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped

This commit is contained in:
Simo committed 2026-09-13 19:41:25 +02:00
1 parent dd7613850e
commit c5c9941768
42 files changed
+1542 -145

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
import { expect, test } from "@playwright/test";
test("deliveries explain outcomes without exposing unauthorized content or raw metadata", async ({
page,
}, testInfo) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/admin/deliveries-harness");
const review = page.getByRole("region", { name: "Delivery review" });
const cards = review.getByRole("article");
await expect(cards).toHaveCount(6);
const news = cards.nth(0);
await expect(news.getByRole("heading", { level: 2 })).toContainText(
"Community update: SummerFestival",
);
await expect(news.getByText("Waiting", { exact: true })).toBeVisible();
await expect(
news.getByText(
"The content is saved. This update runs in the background.",
{ exact: true },
),
).toBeVisible();
await expect(news.getByRole("status")).toContainText(
"The news cache could not be refreshed.",
);
await expect(news.getByRole("status")).toContainText(
"Next automatic attempt: Jan 1, 2030, 12:05 PM",
);
await expect(
news.getByRole("link", { name: "Open content", exact: true }),
).toHaveAttribute("href", "/admin/articles/9007199254740993");
await expect(
news.getByRole("link", { name: "Service diagnostics", exact: true }),
).toHaveAttribute("href", "/admin/devops/installation");
const failed = cards.nth(1);
await expect(
failed.getByRole("heading", { name: "9 catalog offers", exact: true }),
).toBeVisible();
await expect(
failed.getByText("Needs attention", { exact: true }),
).toBeVisible();
await expect(failed.getByRole("status")).toContainText(
"Check the emulator connection.",
);
await expect(failed.getByRole("status")).toContainText(
"Automatic attempts are exhausted.",
);
await expect(
failed.getByRole("link", { name: "Open content", exact: true }),
).toHaveAttribute("href", "/admin/catalog");
await expect(
failed.getByRole("button", { name: "Retry delivery", exact: true }),
).toBeVisible();
for (const card of [cards.nth(2), cards.nth(3)]) {
await expect(
card.getByRole("heading", {
name: "Content details unavailable",
exact: true,
}),
).toBeVisible();
await expect(
card.getByRole("link", { name: "Open content", exact: true }),
).toHaveCount(0);
await expect(
card.getByRole("button", { name: "Retry delivery", exact: true }),
).toHaveCount(0);
}
const unknown = cards.nth(4);
await expect(
unknown.getByText("Other background update", { exact: true }),
).toBeVisible();
await expect(
unknown.getByText("Unknown status", { exact: true }),
).toBeVisible();
await expect(unknown).toContainText("Time unavailable");
await expect(unknown).toContainText("System");
await expect(cards.nth(5)).toContainText(
"Check the Git export history for the final result; disabled export does not publish files.",
);
await expect(review).not.toContainText(
/RESTRICTED_NEWS_TITLE|SERIALIZED_METADATA_SECRET|INTERNAL_FILE_PATH_SECRET|UNKNOWN_OPERATION_SECRET|489 catalog offers|resultJson|articleId/,
);
await expect(review.locator("details[open]")).toHaveCount(0);
await expect(
news.getByText("Operation: operation-authorized-news-1234567890", {
exact: true,
}),
).toBeHidden();
await expect(
news.getByText("Delivery: delivery-authorized-news-1234567890", {
exact: true,
}),
).toBeHidden();
await news.locator("summary").click();
await expect(
news.getByText("Operation: operation-authorized-news-1234567890", {
exact: true,
}),
).toBeVisible();
await expect(
news.getByText("Delivery: delivery-authorized-news-1234567890", {
exact: true,
}),
).toBeVisible();
await news.locator("summary").click();
await expect(
news.getByText("Operation: operation-authorized-news-1234567890", {
exact: true,
}),
).toBeHidden();
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
),
).toBe(true);
const title = news.getByRole("heading", { level: 2 });
expect(
await title.evaluate((node) => node.scrollWidth <= node.clientWidth),
).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
await testInfo.attach("delivery-cards", {
body: await page.screenshot({ fullPage: true }),
contentType: "image/png",
});
expect(errors).toEqual([]);
});
+2
View File
@@ -15,6 +15,7 @@ const port = Number(process.env.UI_TEST_PORT || 3107);
if (!Number.isInteger(port) || port < 1024 || port > 65535) if (!Number.isInteger(port) || port < 1024 || port > 65535)
throw Error("Invalid UI_TEST_PORT"); throw Error("Invalid UI_TEST_PORT");
const actions = new Set([ const actions = new Set([
"@/actions/catalog",
"@/actions/import-furni", "@/actions/import-furni",
"@/actions/events", "@/actions/events",
"@/actions/admin-settings", "@/actions/admin-settings",
@@ -199,6 +200,7 @@ const server = createServer(async (req, res) => {
[ [
"/notifications", "/notifications",
"/admin/unsaved-harness", "/admin/unsaved-harness",
"/admin/deliveries-harness",
"/admin/attachment-harness", "/admin/attachment-harness",
"/admin/studio-harness", "/admin/studio-harness",
"/admin/jobs-harness", "/admin/jobs-harness",
+119
View File
@@ -0,0 +1,119 @@
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import {
DeliveryCard,
type DeliveryCardItem,
} from "@/features/operations/delivery-card";
import { deliveryContext } from "@/features/operations/delivery-context";
const shared: Omit<DeliveryCardItem, "id" | "operationId" | "context"> = {
topic: "news.refresh",
status: "pending",
attempts: 2,
actorId: 7,
createdAt: "2030-01-01T11:00:00Z",
availableAt: "2030-01-01T12:05:00Z",
hasError: true,
};
const records: DeliveryCardItem[] = [
{
...shared,
id: "delivery-authorized-news-1234567890",
operationId: "operation-authorized-news-1234567890",
context: deliveryContext(
"news.update",
JSON.stringify({
articleId: "9007199254740993",
articleTitle: `Community update: ${"SummerFestival".repeat(14)}`,
internalNotes: "SERIALIZED_METADATA_SECRET",
lastError: "INTERNAL_FILE_PATH_SECRET",
}),
{ news: true, catalog: false },
),
},
{
...shared,
id: "delivery-failed-catalog-1234567890",
operationId: "operation-failed-catalog-1234567890",
topic: "catalog.refresh",
status: "failed",
attempts: 5,
context: deliveryContext("catalog.bulk.apply", '{"changedCount":9}', {
news: false,
catalog: true,
}),
},
{
...shared,
id: "delivery-restricted-news",
operationId: "operation-restricted-news",
hasError: false,
context: deliveryContext(
"news.update",
'{"articleTitle":"RESTRICTED_NEWS_TITLE","articleId":"99"}',
{ news: false, catalog: true },
),
},
{
...shared,
id: "delivery-restricted-catalog",
operationId: "operation-restricted-catalog",
topic: "catalog.refresh",
hasError: false,
context: deliveryContext("catalog.bulk.apply", '{"changedCount":489}', {
news: true,
catalog: false,
}),
},
{
...shared,
id: "delivery-unknown",
operationId: "operation-unknown",
topic: "future.topic",
status: "future.status",
actorId: 0,
createdAt: "invalid",
context: deliveryContext(
"future.operation",
'{"articleTitle":"UNKNOWN_OPERATION_SECRET"}',
{ news: true, catalog: true },
),
},
{
...shared,
id: "delivery-completed-export",
operationId: "operation-completed-export",
topic: "catalog.export.request",
status: "done",
hasError: false,
context: deliveryContext("catalog.bulk.apply", '{"changedCount":4}', {
news: false,
catalog: true,
}),
},
];
export function DeliveriesHarness() {
const t = useTranslations("pages.admin.deliveries");
return (
<section aria-label="Delivery review" className="min-w-0 space-y-4">
<p>{t("scope")}</p>
<ul className="space-y-3">
{records.map((item) => (
<li key={item.id}>
<DeliveryCard
item={item}
retry={
item.status === "failed" ? (
<Button type="button" variant="outline">
{t("retry")}
</Button>
) : undefined
}
/>
</li>
))}
</ul>
</section>
);
}
+15 -6
View File
@@ -7,6 +7,7 @@ import { CmsSettingsForm } from "@/app/admin/settings/cms-settings-form";
import { TicketsTable } from "@/app/admin/tickets/tickets-table"; import { TicketsTable } from "@/app/admin/tickets/tickets-table";
import { ArticleForm } from "@/components/admin/article-form"; import { ArticleForm } from "@/components/admin/article-form";
import { FurnitureSyncQueue } from "@/components/admin/studio/furniture-sync-queue"; import { FurnitureSyncQueue } from "@/components/admin/studio/furniture-sync-queue";
import { OrganizeImportsLauncher } from "@/components/admin/studio/organize-imports-launcher";
import { StudioClient } from "@/components/admin/studio/studio-client"; import { StudioClient } from "@/components/admin/studio/studio-client";
import { NotificationCenter } from "@/features/notifications/notification-center"; import { NotificationCenter } from "@/features/notifications/notification-center";
import type { ArticleSaveResult } from "@/lib/article-input"; import type { ArticleSaveResult } from "@/lib/article-input";
@@ -15,6 +16,7 @@ import { themePaletteCss } from "@/lib/theme-css";
import { PRESETS } from "@/lib/theme-presets"; import { PRESETS } from "@/lib/theme-presets";
import messages from "@/messages/en.json"; import messages from "@/messages/en.json";
import { AttachmentHarness } from "./attachment-harness"; import { AttachmentHarness } from "./attachment-harness";
import { DeliveriesHarness } from "./deliveries-harness";
import { FurnitureJobsHarness } from "./furniture-jobs-harness"; import { FurnitureJobsHarness } from "./furniture-jobs-harness";
import { fixtureAction } from "./server-actions"; import { fixtureAction } from "./server-actions";
import { UnsavedHarness } from "./unsaved-harness"; import { UnsavedHarness } from "./unsaved-harness";
@@ -120,18 +122,25 @@ createRoot(root).render(
? "Support desk" ? "Support desk"
: route.includes("articles") : route.includes("articles")
? "News editor" ? "News editor"
: "Furniture synchronization"} : route.includes("deliveries-harness")
? "Background updates"
: "Furniture synchronization"}
</h1> </h1>
{route.includes("notifications") ? ( {route.includes("notifications") ? (
<NotificationCenter userId={7} /> <NotificationCenter userId={7} />
) : route.includes("deliveries-harness") ? (
<DeliveriesHarness />
) : route.includes("unsaved-harness") ? ( ) : route.includes("unsaved-harness") ? (
<UnsavedHarness /> <UnsavedHarness />
) : route.includes("studio-harness") ? ( ) : route.includes("studio-harness") ? (
<StudioClient <>
source={buildFurniImportSource("it")} <OrganizeImportsLauncher />
initialTree={[]} <StudioClient
defaultTranslate={false} source={buildFurniImportSource("it")}
/> initialTree={[]}
defaultTranslate={false}
/>
</>
) : route.includes("attachment-harness") ? ( ) : route.includes("attachment-harness") ? (
<AttachmentHarness /> <AttachmentHarness />
) : route.includes("events/recovery") ? ( ) : route.includes("events/recovery") ? (
+3
View File
@@ -32,3 +32,6 @@ export const deleteImportedFurni = (input: unknown) =>
fixtureAction("delete-imported-furni", input); fixtureAction("delete-imported-furni", input);
export const setFurnidataTranslateEnabled = (input: unknown) => export const setFurnidataTranslateEnabled = (input: unknown) =>
fixtureAction("furnidata-translation", input); fixtureAction("furnidata-translation", input);
export const organizeImportFurni = (input: unknown) =>
fixtureAction("organize-import-furni", input);
+99
View File
@@ -205,3 +205,102 @@ test("Studio opens and dismisses detail, Nitro editor and import review", async
expect(mutations).toEqual([]); expect(mutations).toEqual([]);
expect(errors).toEqual([]); expect(errors).toEqual([]);
}); });
test("Studio loads organize imports on demand and preserves the dialog on reopen", async ({
page,
}, testInfo) => {
const errors: string[] = [];
const mutations: string[] = [];
let organizeRequests = 0;
page.on("pageerror", (error) => errors.push(error.message));
await page.route("**/fixture/actions/**", async (route) => {
mutations.push(route.request().url());
await route.fulfill({
status: 405,
json: { error: "No mutations expected" },
});
});
await page.route("**/api/**", async (route) => {
const request = route.request();
const url = new URL(request.url());
if (request.method() !== "GET") {
mutations.push(`${request.method()} ${url.pathname}`);
return route.fulfill({
status: 405,
json: { error: "No mutations expected" },
});
}
if (url.pathname === "/api/admin/import/organize") {
organizeRequests++;
return route.fulfill({
json: {
groups: [
{
name: "Fixture imports",
icon: 1,
layout: "default_3x3",
total: 1,
moved: 0,
added: 1,
thumbs: [],
items: [{ itemId: 1, catalogItemId: null, alreadyPlaced: false }],
},
],
importRootPageId: null,
importRootCaption: null,
},
});
}
if (url.pathname === "/api/admin/catalog/tree")
return route.fulfill({ json: { pages: [] } });
if (url.pathname === "/api/admin/import/furni")
return route.fulfill({
json:
url.searchParams.get("action") === "stats"
? { totalInDb: 0, inCatalog: 0, notInCatalog: 0, missingNitro: 0 }
: {
items: [],
meta: { currentPage: 1, lastPage: 1, total: 0, perPage: 20 },
},
});
if (url.pathname === "/api/admin/import/clone")
return route.fulfill({ json: { sources: [] } });
if (url.pathname === "/api/admin/studio/import-jobs")
return route.fulfill({ json: { ok: true, jobs: [], nextCursor: null } });
return route.fulfill({
status: 404,
json: { error: "Unknown fixture endpoint" },
});
});
await page.goto("/admin/studio-harness");
const trigger = page.getByRole("button", {
name: "Organize imports",
exact: true,
});
await expect(trigger).toBeVisible();
expect(organizeRequests).toBe(0);
await trigger.click();
const dialog = page.getByRole("dialog", { name: /^Organize imports/ });
await expect(dialog).toBeVisible();
const filter = dialog.getByPlaceholder("Filter import groups...");
await expect(filter).toBeVisible();
await expect(dialog.locator('input[value="Fixture imports"]')).toBeVisible();
await filter.fill("Fixture");
expect(organizeRequests).toBe(1);
await testInfo.attach("organize-imports", {
body: await page.screenshot(),
contentType: "image/png",
});
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
await expect(dialog).toHaveCount(0);
await expect(trigger).toBeFocused();
await trigger.click();
await expect(dialog).toBeVisible();
await expect(filter).toHaveValue("Fixture");
await expect.poll(() => organizeRequests).toBe(2);
await page.keyboard.press("Escape");
await expect(dialog).toHaveCount(0);
await expect(trigger).toBeFocused();
expect(mutations).toEqual([]);
expect(errors).toEqual([]);
});
+17 -2
View File
@@ -575,6 +575,13 @@ describe("real news publication, scheduling and cache delivery", () => {
); );
expect(boundaries.notify).not.toHaveBeenCalled(); expect(boundaries.notify).not.toHaveBeenCalled();
const existing = await savedArticle(); const existing = await savedArticle();
const [creation] = await rows(
"SELECT result_json FROM cms_operations WHERE kind='news.create'",
);
expect(JSON.parse(creation.result_json)).toMatchObject({
articleId: String(existing.id),
articleTitle: existing.title,
});
expect(existing.status).toBe("draft"); expect(existing.status).toBe("draft");
expect(existing.publishedAt).toBeNull(); expect(existing.publishedAt).toBeNull();
expect(await publicNews.getPublishedArticle(existing.slug)).toBeNull(); expect(await publicNews.getPublishedArticle(existing.slug)).toBeNull();
@@ -755,12 +762,20 @@ describe("real news publication, scheduling and cache delivery", () => {
{ {
actor: 0, actor: 0,
kind: "news.schedule.publish", kind: "news.schedule.publish",
result: { articleId: "9007199254740993", published: true }, result: {
articleId: "9007199254740993",
articleTitle: "due-now",
published: true,
},
}, },
{ {
actor: 7, actor: 7,
kind: "news.schedule.publish", kind: "news.schedule.publish",
result: { articleId: "1", published: true }, result: {
articleId: "1",
articleTitle: "due-earlier",
published: true,
},
}, },
]); ]);
expect(await rows("SELECT topic,status FROM cms_outbox")).toEqual([ expect(await rows("SELECT topic,status FROM cms_outbox")).toEqual([
+14
View File
@@ -51,6 +51,8 @@ vi.mock("@/lib/db", async () => {
requestHash: String(hash), requestHash: String(hash),
resultJson: null, resultJson: null,
}); });
} else if (sql.startsWith("SELECT CAST(LAST_INSERT_ID() AS CHAR)")) {
return [[{ id: "9007199254740993" }]];
} else if (sql.startsWith("SELECT id,request_hash")) { } else if (sql.startsWith("SELECT id,request_hash")) {
return [[state.operations.get(params.join(":"))]]; return [[state.operations.get(params.join(":"))]];
} else if (sql.startsWith("UPDATE cms_operations")) { } else if (sql.startsWith("UPDATE cms_operations")) {
@@ -121,6 +123,18 @@ describe("news saves", () => {
expect(state.notify).toHaveBeenCalledOnce(); expect(state.notify).toHaveBeenCalledOnce();
expect(state.effects).toHaveLength(1); expect(state.effects).toHaveLength(1);
}); });
it("stores safe article context for background deliveries without the body", async () => {
expect((await createArticle(form())).ok).toBe(true);
const result = JSON.parse(
[...state.operations.values()][0].resultJson ?? "null",
);
expect(result).toEqual({
ok: true,
articleId: "9007199254740993",
articleTitle: "Test news",
});
expect(result).not.toHaveProperty("fullStory");
});
it("returns success when cache refresh fails after the article commits", async () => { it("returns success when cache refresh fails after the article commits", async () => {
state.invalidate.mockRejectedValue(Error("cache unavailable")); state.invalidate.mockRejectedValue(Error("cache unavailable"));
await expect(createArticle(form())).resolves.toMatchObject({ ok: true }); await expect(createArticle(form())).resolves.toMatchObject({ ok: true });
+10 -3
View File
@@ -1,7 +1,7 @@
"use server"; "use server";
import { createHash, randomUUID } from "node:crypto"; import { createHash, randomUUID } from "node:crypto";
import { and, eq, ne } from "drizzle-orm"; import { and, eq, ne, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
@@ -128,6 +128,13 @@ export async function createArticle(
updatedAt: now, updatedAt: now,
publishedAt: fields.status === "published" ? now : null, publishedAt: fields.status === "published" ? now : null,
}); });
const [createdRows] = await tx.execute(
sql`SELECT CAST(LAST_INSERT_ID() AS CHAR) AS id`,
);
const articleId = (createdRows as unknown as Array<{ id: string }>)[0]
?.id;
if (!articleId || !/^[1-9][0-9]*$/.test(articleId))
throw new Error("Article identity unavailable");
await enqueueEffect(tx, operationId, "news.refresh"); await enqueueEffect(tx, operationId, "news.refresh");
if (input.status === "published") if (input.status === "published")
notification = { notification = {
@@ -136,7 +143,7 @@ export async function createArticle(
target: input.title, target: input.title,
details: slug, details: slug,
}; };
return { ok: true }; return { ok: true, articleId, articleTitle: input.title };
}, },
); );
} catch (error) { } catch (error) {
@@ -219,7 +226,7 @@ export async function updateArticle(
.where(eq(WebsiteArticles.id, id)); .where(eq(WebsiteArticles.id, id));
await recordHistory(tx, "news", historyId, staff.id, historyBefore); await recordHistory(tx, "news", historyId, staff.id, historyBefore);
await enqueueEffect(tx, operationId, "news.refresh"); await enqueueEffect(tx, operationId, "news.refresh");
return { ok: true }; return { ok: true, articleId: String(id), articleTitle: input.title };
}, },
); );
} catch (error) { } catch (error) {
+49 -26
View File
@@ -1,6 +1,8 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import Link from "@/components/link"; import Link from "@/components/link";
import { DeliveryCard } from "@/features/operations/delivery-card";
import { deliveryContext } from "@/features/operations/delivery-context";
import { DeliveryRetry } from "@/features/operations/retry-button"; import { DeliveryRetry } from "@/features/operations/retry-button";
import { listEffects } from "@/features/operations/server"; import { listEffects } from "@/features/operations/server";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
@@ -9,41 +11,62 @@ export default async function DeliveryPage() {
if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank)) if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank))
redirect("/admin"); redirect("/admin");
const editable = canAccess(permissions, PERMS.DEVOPS_EDIT, session.user.rank); const editable = canAccess(permissions, PERMS.DEVOPS_EDIT, session.user.rank);
const access = {
news: canAccess(permissions, PERMS.NEWS_VIEW, session.user.rank),
catalog: canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank),
};
const t = await getTranslations("pages.admin.deliveries"); const t = await getTranslations("pages.admin.deliveries");
const items = await listEffects(); let items: Awaited<ReturnType<typeof listEffects>> | null = null;
try {
items = await listEffects();
} catch {
/* Preserve the distinction between unavailable and empty history. */
}
const timestamp = (value: Date | string) =>
value instanceof Date ? value.toISOString() : value;
return ( return (
<section className="min-w-0 space-y-4"> <section className="min-w-0 space-y-4">
<h1 className="text-2xl font-semibold">{t("title")}</h1> <h1 className="text-2xl font-semibold">{t("title")}</h1>
<p className="text-muted-foreground">{t("scope")}</p> <p className="text-muted-foreground">{t("scope")}</p>
<Link href="/admin/devops/deliveries" className="btn btn-outline"> <div className="flex flex-wrap gap-3">
{t("refresh")} <Link href="/admin/devops/deliveries" className="btn btn-outline">
</Link> {t("refresh")}
{!items.length ? ( </Link>
{canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank) && (
<Link href="/admin/operations" className="btn btn-outline">
{t("openImports")}
</Link>
)}
</div>
{items === null ? (
<p role="alert" className="admin-card">
{t("unavailable")}
</p>
) : !items.length ? (
<p className="admin-card">{t("empty")}</p> <p className="admin-card">{t("empty")}</p>
) : ( ) : (
<ul className="space-y-3"> <ul className="space-y-3">
{items.map((item) => ( {items.map((item) => (
<li key={item.id} className="admin-card min-w-0 space-y-2"> <li key={item.id}>
<div className="flex flex-wrap justify-between gap-3"> <DeliveryCard
<strong> item={{
{t( id: item.id,
item.topic === "catalog.refresh" operationId: item.operationId,
? "hotel" topic: item.topic,
: item.topic === "news.refresh" status: item.status,
? "news" attempts: item.attempts,
: "export", actorId: item.actorId,
)} createdAt: timestamp(item.createdAt),
</strong> availableAt: timestamp(item.availableAt),
<span>{t(`states.${item.status}`)}</span> hasError: !!item.lastError,
</div> context: deliveryContext(item.kind, item.resultJson, access),
<p className="break-all text-sm"> }}
{item.operationId} · {t("actor", { id: item.actorId })} ·{" "} retry={
{t("attempts", { count: item.attempts })} editable && item.status === "failed" ? (
</p> <DeliveryRetry id={item.id} />
{item.status === "failed" && <p>{t("retryError")}</p>} ) : undefined
{editable && item.status === "failed" && ( }
<DeliveryRetry id={item.id} /> />
)}
</li> </li>
))} ))}
</ul> </ul>
+9 -2
View File
@@ -1,9 +1,16 @@
# Durable catalog operations # Durable operations and background updates
Bulk offer apply and undo accept a request UUID. The authenticated actor, operation kind and UUID identify one mutation. A canonical payload hash rejects reuse for different changes. The result and outbox entries commit in the same database transaction as the offers and audit history; replay returns the stored result. Bulk offer apply and undo accept a request UUID. The authenticated actor, operation kind and UUID identify one mutation. A canonical payload hash rejects reuse for different changes. The result and outbox entries commit in the same database transaction as the offers and audit history; replay returns the stored result.
The jobs worker claims pending effects under a database lock with a 120-second lease. A claim token protects completion from stale workers. Failed delivery uses exponential retry, stopping after eight attempts. `/admin/devops/deliveries` shows the latest 100 effects to DEVOPS_VIEW; DEVOPS_EDIT can retry failed effects without replaying the catalog mutation. The jobs worker claims pending effects under a database lock with a 120-second lease. A claim token protects completion from stale workers. Failed delivery uses exponential retry, stopping after eight attempts. `/admin/devops/deliveries` shows the latest 100 effects to DEVOPS_VIEW; DEVOPS_EDIT can retry failed effects without replaying the catalog mutation.
Delivery is at least once: a crash after sending and before acknowledgement may repeat an effect. Catalog refresh is repeatable; export delivery means a request entered the existing export queue, not that Git publication or client refresh completed. Export disabled by configuration remains a no-op. This first adapter covers bulk offer apply/undo, not every CMS mutation. Delivery is at least once: a crash after sending and before acknowledgement may repeat an effect. Catalog refresh is repeatable; export delivery means a request entered the existing export queue, not that Git publication or client refresh completed. Export disabled by configuration remains a no-op. Adapters cover bulk offer apply/undo and manual/scheduled news cache refresh, not every CMS mutation.
Migration0031 is required before the updated worker starts. Unit tests cover payload identity, dispatch limits, retries and authorization; the separate Docker integration suite covers concurrent SQL requests, transaction rollback and claim ownership. No production database was used for local tests. Migration0031 is required before the updated worker starts. Unit tests cover payload identity, dispatch limits, retries and authorization; the separate Docker integration suite covers concurrent SQL requests, transaction rollback and claim ownership. No production database was used for local tests.
The delivery screen separates saved content from its subsequent background update. New news operations persist only the article identifier and title alongside the existing result; no article body is stored for display. Article IDs remain strings to preserve bigint precision. Older records retain readable status and technical references even when no content metadata exists. Catalog operations show affected-offer counts; furniture import progress remains in the linked import history.
Content titles and links require the corresponding NEWS_VIEW or CATALOG_VIEW grant in addition to DEVOPS_VIEW. Raw result JSON and internal error text are never forwarded to the card. Each record shows its recorded time, staff/system actor, attempts, and scheduled retry time when applicable. Failures point to service diagnostics; exhausted attempts expose the existing permission-protected retry without repeating the content mutation. Delivery success does not imply Git publication or client refresh acknowledgement.
Browser fixtures verify the real cards on desktop/mobile, including long titles, denied metadata, exact links, unknown states and collapsed technical references. The fixture retry slot does not execute a backend action; backend authorization and state changes have separate tests.
+105
View File
@@ -0,0 +1,105 @@
import { useFormatter, useTranslations } from "next-intl";
import type { ReactNode } from "react";
import Link from "@/components/link";
import { deliveryTopic } from "./delivery-context";
export interface DeliveryCardItem {
id: string;
operationId: string;
topic: string;
status: string;
attempts: number;
actorId: number;
createdAt: string;
availableAt: string;
hasError: boolean;
context: {
title: string | null;
href: string | null;
changedCount: number | null;
};
}
export function DeliveryCard({
item,
retry,
}: {
item: DeliveryCardItem;
retry?: ReactNode;
}) {
const t = useTranslations("pages.admin.deliveries");
const format = useFormatter();
const topic = deliveryTopic(item.topic);
const state = ["pending", "running", "done", "failed"].includes(item.status)
? item.status
: "unknown";
const date = (value: string) => {
const parsed = new Date(value);
return Number.isNaN(parsed.getTime())
? t("unknownTime")
: format.dateTime(parsed, { dateStyle: "medium", timeStyle: "short" });
};
return (
<article className="admin-card min-w-0 space-y-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 space-y-1">
<p className="text-sm text-[var(--admin-text-muted)]">{t(topic)}</p>
<h2 className="break-words text-lg font-semibold">
{item.context.title ??
(item.context.changedCount !== null
? t("changedOffers", { count: item.context.changedCount })
: t("contextUnavailable"))}
</h2>
</div>
<span className="rounded-full border border-[var(--admin-border)] px-3 py-1 text-sm">
{t(`states.${state}`)}
</span>
</div>
<p className="text-sm text-[var(--admin-text-muted)]">
{date(item.createdAt)} ·{" "}
{item.actorId === 0 ? t("system") : t("actor", { id: item.actorId })} ·{" "}
{t("attempts", { count: item.attempts })}
</p>
{state === "done" ? (
<p className="text-sm">{t(`completed.${topic}`)}</p>
) : (
<p className="text-sm">{t("savedAlready")}</p>
)}
{item.hasError && state !== "done" && (
<div
className="rounded-lg border border-[var(--admin-border)] p-3 text-sm"
role="status"
>
<p>{t(`issues.${topic}`)}</p>
{state === "pending" && (
<p className="mt-1">
{t("nextAttempt", { time: date(item.availableAt) })}
</p>
)}
{state === "failed" && (
<p className="mt-1">{t("attemptsExhausted")}</p>
)}
</div>
)}
<div className="flex flex-wrap items-center gap-3">
{item.context.href && (
<Link href={item.context.href} className="btn btn-outline">
{t("openContent")}
</Link>
)}
{item.hasError && state !== "done" && (
<Link href="/admin/devops/installation" className="btn btn-outline">
{t("diagnostics")}
</Link>
)}
{retry}
</div>
<details className="text-xs text-[var(--admin-text-muted)]">
<summary className="cursor-pointer">{t("technicalDetails")}</summary>
<p className="mt-2 break-all">
{t("operationReference", { id: item.operationId })}
</p>
<p className="break-all">{t("deliveryReference", { id: item.id })}</p>
</details>
</article>
);
}
@@ -0,0 +1,93 @@
import { describe, expect, it } from "vitest";
import { deliveryContext, deliveryTopic } from "./delivery-context";
describe("delivery context", () => {
it("links a news record without rounding a bigint identifier", () => {
expect(
deliveryContext(
"news.update",
JSON.stringify({
articleId: "9007199254740993",
articleTitle: "A community update",
}),
{ news: true, catalog: false },
),
).toEqual({
title: "A community update",
href: "/admin/articles/9007199254740993",
changedCount: null,
});
});
it("does not expose news metadata without news permission", () => {
expect(
deliveryContext(
"news.create",
JSON.stringify({
articleTitle: "Private draft",
articleSlug: "private",
}),
{ news: false, catalog: true },
),
).toEqual({ title: null, href: null, changedCount: null });
});
it("supports searchable new articles and legacy scheduled records", () => {
expect(
deliveryContext(
"news.create",
JSON.stringify({
articleTitle: "Hello & welcome",
articleSlug: "hello",
}),
{ news: true, catalog: false },
).href,
).toBe("/admin/articles?search=Hello+%26+welcome");
expect(
deliveryContext(
"news.schedule.publish",
'{"articleId":"42","published":true}',
{ news: true, catalog: false },
).href,
).toBe("/admin/articles/42");
});
it.each([
null,
"{broken",
"[]",
'{"articleId":"../../settings","articleTitle":42}',
'"value"',
])("handles missing or invalid historical context: %s", (result) => {
expect(
deliveryContext("news.update", result, { news: true, catalog: false }),
).toEqual({ title: null, href: "/admin/articles", changedCount: null });
});
it("exposes only known bounded catalog counts with catalog permission", () => {
expect(
deliveryContext("catalog.bulk.apply", '{"changedCount":8}', {
news: false,
catalog: true,
}),
).toEqual({ title: null, href: "/admin/catalog", changedCount: 8 });
expect(
deliveryContext("catalog.bulk.apply", '{"changedCount":8}', {
news: true,
catalog: false,
}).changedCount,
).toBeNull();
expect(
deliveryContext("catalog.bulk.undo", '{"changedCount":-1}', {
news: false,
catalog: true,
}).changedCount,
).toBeNull();
});
it("does not interpret unknown operations or topics as export", () => {
expect(
deliveryContext("future.operation", '{"articleTitle":"secret"}', {
news: true,
catalog: true,
}),
).toEqual({ title: null, href: null, changedCount: null });
expect(deliveryTopic("unknown.topic")).toBe("unknown");
expect(deliveryTopic("news.refresh")).toBe("news");
});
});
@@ -0,0 +1,56 @@
/** Public presentation data only: never forward a stored operation payload to the browser. */
export function deliveryContext(
kind: string,
serialized: string | null,
access: { news: boolean; catalog: boolean },
): { title: string | null; href: string | null; changedCount: number | null } {
const context = {
title: null as string | null,
href: null as string | null,
changedCount: null as number | null,
};
let result: Record<string, unknown> = {};
if (serialized && serialized.length <= 1_048_576) {
try {
const parsed: unknown = JSON.parse(serialized);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
result = parsed as Record<string, unknown>;
} catch {
/* Older records may not contain presentation metadata. */
}
}
if (
["news.create", "news.update", "news.schedule.publish"].includes(kind) &&
access.news
) {
context.href = "/admin/articles";
if (typeof result.articleTitle === "string" && result.articleTitle.trim()) {
context.title = result.articleTitle.trim().slice(0, 255);
context.href = `/admin/articles?${new URLSearchParams({ search: context.title.slice(0, 191) })}`;
}
if (
typeof result.articleId === "string" &&
/^[1-9][0-9]{0,19}$/.test(result.articleId)
)
context.href = `/admin/articles/${result.articleId}`;
} else if (
["catalog.bulk.apply", "catalog.bulk.undo"].includes(kind) &&
access.catalog
) {
context.href = "/admin/catalog";
if (
typeof result.changedCount === "number" &&
Number.isSafeInteger(result.changedCount) &&
result.changedCount >= 0 &&
result.changedCount <= 500
)
context.changedCount = result.changedCount;
}
return context;
}
export function deliveryTopic(topic: string) {
if (topic === "catalog.refresh") return "hotel";
if (topic === "catalog.export.request") return "export";
if (topic === "news.refresh") return "news";
return "unknown";
}
+4 -2
View File
@@ -90,7 +90,7 @@ export const effectRepository = {
}; };
export async function listEffects() { export async function listEffects() {
const [rows] = await db.execute( const [rows] = await db.execute(
sql`SELECT e.id,e.operation_id AS operationId,e.topic,e.status,e.attempts,e.last_error AS lastError,e.created_at AS createdAt,o.actor_id AS actorId,o.kind FROM cms_outbox e JOIN cms_operations o ON o.id=e.operation_id ORDER BY e.created_at DESC,e.id DESC LIMIT 100`, sql`SELECT e.id,e.operation_id AS operationId,e.topic,e.status,e.attempts,e.last_error AS lastError,e.created_at AS createdAt,e.available_at AS availableAt,o.actor_id AS actorId,o.kind,o.result_json AS resultJson FROM cms_outbox e JOIN cms_operations o ON o.id=e.operation_id ORDER BY e.created_at DESC,e.id DESC LIMIT 100`,
); );
return rows as unknown as Array<{ return rows as unknown as Array<{
id: string; id: string;
@@ -99,7 +99,9 @@ export async function listEffects() {
status: string; status: string;
attempts: number; attempts: number;
lastError: string | null; lastError: string | null;
createdAt: string; createdAt: Date | string;
availableAt: Date | string;
resultJson: string | null;
actorId: number; actorId: number;
kind: string; kind: string;
}>; }>;
+18 -2
View File
@@ -7,6 +7,7 @@ const state = vi.hoisted(() => ({
articles: [] as Array<{ articles: [] as Array<{
id: string; id: string;
userId: number | null; userId: number | null;
title?: string;
status: string; status: string;
publishAt: Date; publishAt: Date;
}>, }>,
@@ -146,8 +147,13 @@ it("uses the same UTC cutoff and publication timestamps regardless of the MySQL
); );
expect(statements).toHaveLength(2); expect(statements).toHaveLength(2);
for (const statement of statements) { for (const statement of statements) {
expect(format(statement.sql, statement.params, false, "+02:00")).toBe( const values = statement.params.map((value) => {
format(statement.sql, statement.params, false, "-07:00"), if (typeof value !== "string")
throw new Error("Expected explicit SQL string parameters");
return value;
});
expect(format(statement.sql, values, false, "+02:00")).toBe(
format(statement.sql, values, false, "-07:00"),
); );
} }
expect(statements[0].params).toEqual(["2030-06-01 08:09:10.123"]); expect(statements[0].params).toEqual(["2030-06-01 08:09:10.123"]);
@@ -158,3 +164,13 @@ it("uses the same UTC cutoff and publication timestamps regardless of the MySQL
"2030-06-01 08:09:10.123", "2030-06-01 08:09:10.123",
]); ]);
}); });
it("records the article title and exact identifier for the operation screen", async () => {
state.articles[0].title = "Scheduled community news";
await publishDueArticles(now);
expect(JSON.parse(String(state.operations[0][5]))).toEqual({
articleId: "1",
articleTitle: "Scheduled community news",
published: true,
});
});
+3 -2
View File
@@ -13,12 +13,13 @@ export async function publishDueArticles(now = new Date()): Promise<number> {
const utcNow = now.toISOString().slice(0, -1).replace("T", " "); const utcNow = now.toISOString().slice(0, -1).replace("T", " ");
const published = await db.transaction(async (tx) => { const published = await db.transaction(async (tx) => {
const [rows] = await tx.execute( const [rows] = await tx.execute(
sql`SELECT CAST(id AS CHAR) AS id,user_id AS userId,publish_at AS publishAt FROM website_articles WHERE status='scheduled' AND publish_at<=${utcNow} ORDER BY publish_at,id LIMIT 100 FOR UPDATE`, sql`SELECT CAST(id AS CHAR) AS id,user_id AS userId,title,publish_at AS publishAt FROM website_articles WHERE status='scheduled' AND publish_at<=${utcNow} ORDER BY publish_at,id LIMIT 100 FOR UPDATE`,
); );
let published = 0; let published = 0;
for (const article of rows as unknown as Array<{ for (const article of rows as unknown as Array<{
id: string; id: string;
userId: number | null; userId: number | null;
title: string | null;
publishAt: Date | string; publishAt: Date | string;
}>) { }>) {
const [result] = await tx.execute( const [result] = await tx.execute(
@@ -33,7 +34,7 @@ export async function publishDueArticles(now = new Date()): Promise<number> {
}); });
// Legacy articles without an author remain attributed to system (0). // Legacy articles without an author remain attributed to system (0).
await tx.execute( await tx.execute(
sql`INSERT INTO cms_operations (id,actor_id,kind,request_key,request_hash,result_json) VALUES (${operationId},${article.userId ?? 0},${"news.schedule.publish"},${randomUUID()},${hash},${JSON.stringify({ articleId: article.id, published: true })})`, sql`INSERT INTO cms_operations (id,actor_id,kind,request_key,request_hash,result_json) VALUES (${operationId},${article.userId ?? 0},${"news.schedule.publish"},${randomUUID()},${hash},${JSON.stringify({ articleId: article.id, articleTitle: article.title, published: true })})`,
); );
await enqueueEffect(tx, operationId, "news.refresh"); await enqueueEffect(tx, operationId, "news.refresh");
published++; published++;
+32 -4
View File
@@ -3536,8 +3536,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -3551,9 +3551,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"myDashboard": { "myDashboard": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4337,8 +4337,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4352,9 +4352,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"radioRequests": { "radioRequests": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4540,8 +4540,8 @@
"recoveryHint": "You can continue. If the original files fail, the importer will search enabled sources for a matching classname, type and revision, then validate the Nitro bundle. Unavailable files can still be attached manually." "recoveryHint": "You can continue. If the original files fail, the importer will search enabled sources for a matching classname, type and revision, then validate the Nitro bundle. Unavailable files can still be attached manually."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4555,9 +4555,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"mod": { "mod": {
+32 -4
View File
@@ -4337,8 +4337,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4352,9 +4352,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"radioRequests": { "radioRequests": {
+32 -4
View File
@@ -3536,8 +3536,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -3551,9 +3551,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"myDashboard": { "myDashboard": {
+32 -4
View File
@@ -4337,8 +4337,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4352,9 +4352,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"radioRequests": { "radioRequests": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4509,8 +4509,8 @@
"recoveryHint": "Puoi continuare. Se i file originali non sono disponibili, l’importatore cercherà nelle sorgenti attive lo stesso classname, tipo e revisione, verificando poi il bundle Nitro. Puoi comunque allegare manualmente i file mancanti." "recoveryHint": "Puoi continuare. Se i file originali non sono disponibili, l’importatore cercherà nelle sorgenti attive lo stesso classname, tipo e revisione, verificando poi il bundle Nitro. Puoi comunque allegare manualmente i file mancanti."
}, },
"deliveries": { "deliveries": {
"title": "Consegne in sospeso", "title": "Aggiornamenti in background",
"scope": "Ultime 100 consegne per modifiche massive al catalogo. La modifica al database è già salvata. Consegnata significa export accodato o richiesta inviata all’hotel, non applicazione confermata dai client. Il worker ritenta gli errori; qui puoi riavviare i tentativi esauriti.", "scope": "Ultimi 100 aggiornamenti per news e modifiche multiple al catalogo. Il salvataggio dei contenuti e gli aggiornamenti successivi sono mostrati separatamente. Gli import si trovano nella cronologia import.",
"refresh": "Aggiorna", "refresh": "Aggiorna",
"empty": "Nessuna consegna registrata.", "empty": "Nessuna consegna registrata.",
"retry": "Riprova consegna", "retry": "Riprova consegna",
@@ -4524,9 +4524,37 @@
"pending": "In attesa", "pending": "In attesa",
"running": "In consegna", "running": "In consegna",
"done": "Consegnata", "done": "Consegnata",
"failed": "Da verificare" "failed": "Da verificare",
"unknown": "Stato sconosciuto"
}, },
"news": "Aggiornamento news" "news": "Aggiornamento news",
"unknown": "Altro aggiornamento",
"contextUnavailable": "Dettagli del contenuto non disponibili",
"changedOffers": "{count} offerte del catalogo",
"system": "Sistema",
"unknownTime": "Orario non disponibile",
"savedAlready": "Il contenuto è salvato. Questo aggiornamento viene eseguito in background.",
"nextAttempt": "Prossimo tentativo automatico: {time}",
"attemptsExhausted": "I tentativi automatici sono esauriti. Dopo aver controllato il servizio, puoi riprovare da qui.",
"openContent": "Apri contenuto",
"diagnostics": "Diagnostica servizi",
"technicalDetails": "Riferimenti tecnici",
"operationReference": "Operazione: {id}",
"deliveryReference": "Aggiornamento: {id}",
"openImports": "Cronologia import",
"unavailable": "Impossibile caricare gli aggiornamenti. Prova ad aggiornare la pagina.",
"completed": {
"news": "Cache delle news aggiornata.",
"hotel": "La richiesta di aggiornamento catalogo è stata inviata all’hotel. Non conferma che ogni client si sia aggiornato.",
"export": "La richiesta di esportazione è stata elaborata. Controlla la cronologia Git per il risultato finale; se l’esportazione è disattivata, nessun file viene pubblicato.",
"unknown": "Elaborazione in background completata."
},
"issues": {
"news": "Impossibile aggiornare la cache delle news. Controlla Redis e il worker.",
"hotel": "Impossibile inviare l’aggiornamento catalogo all’hotel. Controlla la connessione con l’emulatore.",
"export": "Impossibile accodare l’esportazione Git. Controlla la configurazione e il worker.",
"unknown": "L’aggiornamento non è riuscito. Controlla la diagnostica servizi."
}
} }
}, },
"radioRequests": { "radioRequests": {
+32 -4
View File
@@ -3536,8 +3536,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -3551,9 +3551,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"myDashboard": { "myDashboard": {
+32 -4
View File
@@ -4540,8 +4540,8 @@
"unavailable": "Status unavailable — open to inspect" "unavailable": "Status unavailable — open to inspect"
}, },
"deliveries": { "deliveries": {
"title": "Openstaande leveringen", "title": "Achtergrondupdates",
"scope": "De laatste 100 leveringen voor bulkcataloguswijzigingen. De databasewijziging is al opgeslagen. Afgeleverd betekent dat export is ingepland of het hotelverzoek is verstuurd, niet dat clients het hebben toegepast. De worker probeert fouten opnieuw; uitgeputte pogingen kun je hier hervatten.", "scope": "De laatste 100 updates voor nieuws en gezamenlijke cataloguswijzigingen. Het opslaan van inhoud en de achtergrondupdates worden apart getoond. Importvoortgang staat in de importgeschiedenis.",
"refresh": "Vernieuwen", "refresh": "Vernieuwen",
"empty": "Geen leveringen geregistreerd.", "empty": "Geen leveringen geregistreerd.",
"retry": "Opnieuw proberen", "retry": "Opnieuw proberen",
@@ -4555,9 +4555,37 @@
"pending": "Wachtend", "pending": "Wachtend",
"running": "Wordt afgeleverd", "running": "Wordt afgeleverd",
"done": "Afgeleverd", "done": "Afgeleverd",
"failed": "Aandacht nodig" "failed": "Aandacht nodig",
"unknown": "Onbekende status"
}, },
"news": "Nieuws bijwerken" "news": "Nieuws bijwerken",
"unknown": "Andere achtergrondupdate",
"contextUnavailable": "Inhoudsdetails niet beschikbaar",
"changedOffers": "{count} catalogusaanbiedingen",
"system": "Systeem",
"unknownTime": "Tijd niet beschikbaar",
"savedAlready": "De inhoud is opgeslagen. Deze update wordt op de achtergrond uitgevoerd.",
"nextAttempt": "Volgende automatische poging: {time}",
"attemptsExhausted": "Alle automatische pogingen zijn gebruikt. Controleer de dienst en probeer het hier opnieuw.",
"openContent": "Inhoud openen",
"diagnostics": "Dienstdiagnostiek",
"technicalDetails": "Technische referenties",
"operationReference": "Bewerking: {id}",
"deliveryReference": "Update: {id}",
"openImports": "Importgeschiedenis",
"unavailable": "De achtergrondupdates konden niet worden geladen. Vernieuw de pagina om het opnieuw te proberen.",
"completed": {
"news": "De nieuwscache is vernieuwd.",
"hotel": "Het verzoek om de catalogus te vernieuwen is naar het hotel gestuurd. Dit bevestigt niet dat iedere client is bijgewerkt.",
"export": "Het exportverzoek is verwerkt. Bekijk de Git-exportgeschiedenis voor het eindresultaat; uitgeschakelde export publiceert geen bestanden.",
"unknown": "Achtergrondverwerking voltooid."
},
"issues": {
"news": "De nieuwscache kon niet worden vernieuwd. Controleer Redis en de achtergrondworker.",
"hotel": "De catalogusupdate kon niet naar het hotel worden gestuurd. Controleer de emulatorverbinding.",
"export": "Het Git-exportverzoek kon niet worden ingepland. Controleer de exportinstellingen en de achtergrondworker.",
"unknown": "De achtergrondupdate is mislukt. Bekijk de dienstdiagnostiek."
}
} }
}, },
"mod": { "mod": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4365,8 +4365,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4380,9 +4380,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {
+32 -4
View File
@@ -4367,8 +4367,8 @@
"previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use."
}, },
"deliveries": { "deliveries": {
"title": "Pending deliveries", "title": "Background updates",
"scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.",
"refresh": "Refresh", "refresh": "Refresh",
"empty": "No delivery records.", "empty": "No delivery records.",
"retry": "Retry delivery", "retry": "Retry delivery",
@@ -4382,9 +4382,37 @@
"pending": "Waiting", "pending": "Waiting",
"running": "Delivering", "running": "Delivering",
"done": "Delivered", "done": "Delivered",
"failed": "Needs attention" "failed": "Needs attention",
"unknown": "Unknown status"
}, },
"news": "News cache refresh" "news": "News cache refresh",
"unknown": "Other background update",
"contextUnavailable": "Content details unavailable",
"changedOffers": "{count} catalog offers",
"system": "System",
"unknownTime": "Time unavailable",
"savedAlready": "The content is saved. This update runs in the background.",
"nextAttempt": "Next automatic attempt: {time}",
"attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.",
"openContent": "Open content",
"diagnostics": "Service diagnostics",
"technicalDetails": "Technical references",
"operationReference": "Operation: {id}",
"deliveryReference": "Delivery: {id}",
"openImports": "Import history",
"unavailable": "Background updates could not be loaded. Try refreshing this page.",
"completed": {
"news": "News cache refresh completed.",
"hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.",
"export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.",
"unknown": "Background processing completed."
},
"issues": {
"news": "The news cache could not be refreshed. Check Redis and the background worker.",
"hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.",
"export": "The Git export request could not be queued. Check the export configuration and background worker.",
"unknown": "The background update failed. Check the service diagnostics."
}
} }
}, },
"error": { "error": {