diff --git a/src/actions/catalog-bulk.ts b/src/actions/catalog-bulk.ts index 89b738e6..bec539ab 100644 --- a/src/actions/catalog-bulk.ts +++ b/src/actions/catalog-bulk.ts @@ -5,6 +5,7 @@ import { applyBulkOffersCommand, listBulkOfferDestinationsCommand, previewBulkOffersCommand, + undoBulkOffersCommand, } from "@/features/catalog/server/bulk-offers"; import { catalogFailure } from "@/features/catalog/server/errors"; import { sendCatalogUpdate } from "@/features/catalog/server/sync-status"; @@ -66,3 +67,18 @@ export async function getBulkOfferDestinations() { return { ok: false as const, error: catalogFailure(error).message }; } } + +export async function undoBulkOffers(historyIds: number[]) { + const staff = await requirePermission(PERMS.CATALOG_EDIT); + try { + return await withCatalogExport(async () => { + const data = await undoBulkOffersCommand(historyIds, staff.id); + await sendCatalogUpdate(); + revalidatePath("/admin/catalog", "layout"); + revalidatePath("/admin/logs/audit"); + return { ok: true as const, data }; + }); + } catch (error) { + return { ok: false as const, error: catalogFailure(error).message }; + } +} diff --git a/src/features/catalog/components/bulk-offer-editor.tsx b/src/features/catalog/components/bulk-offer-editor.tsx index eda860d2..495c0adb 100644 --- a/src/features/catalog/components/bulk-offer-editor.tsx +++ b/src/features/catalog/components/bulk-offer-editor.tsx @@ -4,7 +4,11 @@ import { ArrowRight, Loader2, SlidersHorizontal } from "lucide-react"; import { useTranslations } from "next-intl"; import { useRef, useState } from "react"; import { toast } from "sonner"; -import { applyBulkOffers, previewBulkOffers } from "@/actions/catalog-bulk"; +import { + applyBulkOffers, + previewBulkOffers, + undoBulkOffers, +} from "@/actions/catalog-bulk"; import { MovePageCombobox } from "@/app/admin/catalog/[id]/catalog-items-table/field-helpers"; import { POINTS_TYPES } from "@/app/admin/catalog/[id]/catalog-items-table/types"; import { @@ -174,7 +178,34 @@ export function BulkOfferEditor({ setPreview(null); return; } - toast.success(t("success", { count: result.data.changedCount })); + toast.success(t("success", { count: result.data.changedCount }), { + duration: 15000, + description: t("undoHint"), + action: result.data.historyIds.length + ? { + label: t("undo"), + onClick: async () => { + if (busyRef.current || (beforeEdit && !beforeEdit())) return; + busyRef.current = true; + try { + const restored = await undoBulkOffers(result.data.historyIds); + if (!restored.ok) { + toast.error(restored.error); + return; + } + toast.success( + t("undoSuccess", { count: restored.data.changedCount }), + ); + await onApplied(); + } catch { + toast.error(t("requestFailed")); + } finally { + busyRef.current = false; + } + }, + } + : undefined, + }); setOpen(false); await onApplied(); } catch { diff --git a/src/features/catalog/server/bulk-actions.test.ts b/src/features/catalog/server/bulk-actions.test.ts index f8584f99..730d5898 100644 --- a/src/features/catalog/server/bulk-actions.test.ts +++ b/src/features/catalog/server/bulk-actions.test.ts @@ -5,6 +5,7 @@ const state = vi.hoisted(() => ({ preview: vi.fn(), destinations: vi.fn(), apply: vi.fn(), + undo: vi.fn(), export: vi.fn(), send: vi.fn(), audit: vi.fn(), @@ -18,6 +19,7 @@ vi.mock("@/features/catalog/server/bulk-offers", () => ({ previewBulkOffersCommand: state.preview, listBulkOfferDestinationsCommand: state.destinations, applyBulkOffersCommand: state.apply, + undoBulkOffersCommand: state.undo, })); vi.mock("@/lib/services/catalog-git-queue", () => ({ withCatalogExport: state.export, @@ -33,6 +35,7 @@ import { applyBulkOffers, getBulkOfferDestinations, previewBulkOffers, + undoBulkOffers, } from "@/actions/catalog-bulk"; const input = { ids: [1], changes: { pageId: 2 } }; @@ -87,3 +90,20 @@ it("loads destination choices with view permission only", async () => { expect(state.permission).toHaveBeenCalledWith("view"); expect(state.export).not.toHaveBeenCalled(); }); + +it("undo enforces edit access and exports only successful restores", async () => { + state.undo.mockResolvedValueOnce({ changedCount: 2 }); + expect((await undoBulkOffers([10, 11])).ok).toBe(true); + expect(state.permission).toHaveBeenCalledWith("edit"); + expect(state.undo).toHaveBeenCalledWith([10, 11], 1); + expect(state.export).toHaveBeenCalledTimes(1); + expect(state.send).toHaveBeenCalledTimes(1); +}); +it("undo denial stops before export and a conflict does not notify", async () => { + state.permission.mockRejectedValueOnce(Error("Denied")); + await expect(undoBulkOffers([10])).rejects.toThrow("Denied"); + expect(state.export).not.toHaveBeenCalled(); + state.undo.mockRejectedValueOnce(Error("conflict")); + expect((await undoBulkOffers([10])).ok).toBe(false); + expect(state.send).not.toHaveBeenCalled(); +}); diff --git a/src/features/catalog/server/bulk-offers.test.ts b/src/features/catalog/server/bulk-offers.test.ts index 97d3223b..ca02264f 100644 --- a/src/features/catalog/server/bulk-offers.test.ts +++ b/src/features/catalog/server/bulk-offers.test.ts @@ -27,6 +27,7 @@ const state = vi.hoisted(() => ({ ], queries: [] as string[], writes: 0, + audit: [] as Array<{ before: string; after: string; target: string }>, failAt: 0, commits: 0, rollbacks: 0, @@ -36,11 +37,35 @@ vi.mock("@/lib/db", () => ({ execute: async () => [state.pages, []], transaction: async (fn: (tx: unknown) => Promise) => { const before = state.writes; + const beforeRows = structuredClone(state.rows); + const beforeAudit = [...state.audit]; try { const result = await fn({ execute: async (query: SQL) => { - const text = new MySqlDialect().sqlToQuery(query).sql; + const { sql: text, params } = new MySqlDialect().sqlToQuery(query); state.queries.push(text); + if ( + text.startsWith("SELECT") && + !text.includes("catalog_name") && + text.includes("catalog_items") + ) { + const row = state.rows.find( + (row) => row.id === Number(params.at(-1)), + ); + return [ + row + ? [ + { + pageId: String(row.pageId), + costCredits: row.costCredits, + costPoints: row.costPoints, + pointsType: row.pointsType, + }, + ] + : [], + [], + ]; + } if (text.startsWith("SELECT")) return [ text.includes("catalog_pages") ? state.pages : state.rows, @@ -48,13 +73,41 @@ vi.mock("@/lib/db", () => ({ ]; state.writes++; if (state.writes === state.failAt) throw Error("write failed"); + const row = state.rows.find( + (row) => row.id === Number(params.at(-1)), + ); + if (row) { + const columns = { + page_id: "pageId", + cost_credits: "costCredits", + cost_points: "costPoints", + points_type: "pointsType", + } as const; + const assignments = + text.split(" SET ")[1]?.split(" WHERE ")[0] ?? ""; + let index = 0; + for (const assignment of assignments.split(", ")) { + const name = assignment.match( + /`([^`]+)`/, + )?.[1] as keyof typeof columns; + if (columns[name]) row[columns[name]] = Number(params[index++]); + } + } return [{ affectedRows: 1 }, []]; }, + insert: () => ({ + values: async (entry: (typeof state.audit)[number]) => { + state.audit.push(entry); + return [{ insertId: 1000 + state.audit.length }]; + }, + }), }); state.commits++; return result; } catch (error) { state.writes = before; + state.rows = beforeRows; + state.audit = beforeAudit; state.rollbacks++; throw error; } @@ -96,6 +149,7 @@ beforeEach(() => { { id: 9, caption: "Target" }, ]; state.queries = []; + state.audit = []; state.writes = 0; state.failAt = 0; state.commits = 0; @@ -192,3 +246,21 @@ it("does not update offers whose chosen values already match", async () => { await applyBulkOffersCommand(request, p.fingerprint); expect(state.writes).toBe(0); }); + +it("records category-only bulk edits as restorable history and returns durable IDs", async () => { + const request = { ids: [1, 2], changes: { pageId: 9 } }; + const preview = await previewBulkOffersCommand(request); + expect(await applyBulkOffersCommand(request, preview.fingerprint, 7)).toEqual( + { changedCount: 2, historyIds: [1001, 1002] }, + ); + expect(state.audit).toHaveLength(2); + expect(state.audit[0].target).toBe("catalog_offer"); + expect(JSON.parse(state.audit[0].before)).toMatchObject({ + pageId: "4", + costCredits: 3, + }); + expect(JSON.parse(state.audit[0].after)).toMatchObject({ + pageId: "9", + costCredits: 3, + }); +}); diff --git a/src/features/catalog/server/bulk-offers.ts b/src/features/catalog/server/bulk-offers.ts index 120f377f..aa565c2a 100644 --- a/src/features/catalog/server/bulk-offers.ts +++ b/src/features/catalog/server/bulk-offers.ts @@ -1,7 +1,12 @@ import "server-only"; import { createHash } from "node:crypto"; import { sql } from "drizzle-orm"; -import { recordHistory } from "@/features/history/server"; +import { + applyHistory, + lockOfferHistoryPages, + readHistory, + recordHistory, +} from "@/features/history/server"; import { db } from "@/lib/db"; import { type BulkOfferInput, @@ -132,6 +137,7 @@ export async function applyBulkOffersCommand( costPoints: "cost_points", pointsType: "points_type", }; + const historyIds: number[] = []; for (const row of result.rows) { const changes = ( Object.keys(input.changes) as Array @@ -146,14 +152,22 @@ export async function applyBulkOffersCommand( sql`, `, )} WHERE id=${row.id}`, ); - if (userId) - await recordHistory(tx, "prices", row.id, userId, { - costCredits: row.before.costCredits, - costPoints: row.before.costPoints, - pointsType: row.before.pointsType, - }); + if (userId) { + const historyId = await recordHistory( + tx, + "catalog_offer", + row.id, + userId, + { + ...row.before, + pageId: String(row.before.pageId), + }, + ); + if (!historyId) throw Error("History entry could not be recorded"); + historyIds.push(historyId); + } } - return { changedCount: result.changedCount }; + return { changedCount: result.changedCount, historyIds }; }); } @@ -170,3 +184,40 @@ export async function listBulkOfferDestinationsCommand() { ); return { pages }; } + +export async function undoBulkOffersCommand( + historyIds: number[], + userId: number, +) { + if ( + !Array.isArray(historyIds) || + !historyIds.length || + historyIds.length > 500 || + new Set(historyIds).size !== historyIds.length || + historyIds.some((id) => !Number.isSafeInteger(id) || id < 1) + ) + throw new CatalogInputError("Invalid undo selection"); + return db.transaction(async (tx) => { + const entries = []; + for (const id of historyIds) { + const entry = await readHistory(tx, id); + if (entry.kind !== "catalog_offer") + throw new CatalogInputError("Invalid undo selection"); + entries.push(entry); + } + entries.sort((a, b) => Number(a.targetId) - Number(b.targetId)); + if (new Set(entries.map((entry) => entry.targetId)).size !== entries.length) + throw new CatalogInputError("Duplicate undo target"); + await lockOfferHistoryPages(tx, entries); + try { + for (const entry of entries) await applyHistory(tx, entry, userId); + } catch (error) { + if (error instanceof Error && error.message === "conflict") + throw new CatalogConflict( + "Offers changed after this update. Undo was not applied.", + ); + throw error; + } + return { changedCount: entries.length }; + }); +} diff --git a/src/features/catalog/server/bulk-undo.test.ts b/src/features/catalog/server/bulk-undo.test.ts new file mode 100644 index 00000000..a88d441a --- /dev/null +++ b/src/features/catalog/server/bulk-undo.test.ts @@ -0,0 +1,111 @@ +import type { SQL } from "drizzle-orm"; +import { MySqlDialect } from "drizzle-orm/mysql-core"; +import { beforeEach, expect, it, vi } from "vitest"; + +const state = vi.hoisted(() => ({ + current: [ + { pageId: "9", costCredits: 20, costPoints: 0, pointsType: 0 }, + { pageId: "9", costCredits: 30, costPoints: 0, pointsType: 0 }, + ], + audit: [] as unknown[], + queries: [] as string[], +})); +vi.mock("@/lib/db", () => ({ + db: { + transaction: async (fn: (tx: unknown) => Promise) => { + const saved = structuredClone(state.current); + const savedAudit = [...state.audit]; + const tx = { + select: () => ({ + from: () => ({ + where: (query: SQL) => ({ + limit: async () => { + const id = Number( + new MySqlDialect().sqlToQuery(query).params[0], + ); + return [ + { + id, + action: "history_update", + target: "catalog_offer", + targetId: id, + before: JSON.stringify({ + pageId: "4", + costCredits: id * 10, + costPoints: 0, + pointsType: 0, + }), + after: JSON.stringify({ + pageId: "9", + costCredits: (id + 1) * 10, + costPoints: 0, + pointsType: 0, + }), + }, + ]; + }, + }), + }), + }), + execute: async (query: SQL) => { + const { sql: text, params } = new MySqlDialect().sqlToQuery(query); + state.queries.push(text); + if (text.includes("catalog_pages")) return [[{ id: 4 }, { id: 9 }]]; + const id = Number(params.at(-1)); + if (text.startsWith("SELECT")) return [[state.current[id - 1]]]; + state.current[id - 1] = { + pageId: String(params[0]), + costCredits: Number(params[1]), + costPoints: Number(params[2]), + pointsType: Number(params[3]), + }; + return [{}]; + }, + insert: () => ({ + values: async (entry: unknown) => { + state.audit.push(entry); + return [{ insertId: 100 + state.audit.length }]; + }, + }), + }; + try { + return await fn(tx); + } catch (error) { + state.current = saved; + state.audit = savedAudit; + throw error; + } + }, + }, +})); + +import { undoBulkOffersCommand } from "./bulk-offers"; + +beforeEach(() => { + state.current = [ + { pageId: "9", costCredits: 20, costPoints: 0, pointsType: 0 }, + { pageId: "9", costCredits: 30, costPoints: 0, pointsType: 0 }, + ]; + state.audit = []; + state.queries = []; +}); +it("restores the whole batch and records each restore in history", async () => { + expect(await undoBulkOffersCommand([2, 1], 7)).toEqual({ changedCount: 2 }); + expect(state.current.map((row) => row.pageId)).toEqual(["4", "4"]); + expect(state.current.map((row) => row.costCredits)).toEqual([10, 20]); + expect(state.audit).toHaveLength(2); + expect(state.queries[0]).toContain("catalog_pages"); +}); +it("rolls back earlier restores and history when a later offer changed", async () => { + state.current[1].costPoints = 99; + await expect(undoBulkOffersCommand([1, 2], 7)).rejects.toThrow(/changed/); + expect(state.current[0].costCredits).toBe(20); + expect(state.current[1].costPoints).toBe(99); + expect(state.audit).toHaveLength(0); +}); +it("rejects duplicate history IDs and repeated undo", async () => { + await expect(undoBulkOffersCommand([1, 1], 7)).rejects.toThrow(/Invalid/); + await undoBulkOffersCommand([1, 2], 7); + await expect(undoBulkOffersCommand([1, 2], 7)).rejects.toThrow(/changed/); + expect(state.audit).toHaveLength(2); +}); diff --git a/src/features/history/catalog-offer.test.ts b/src/features/history/catalog-offer.test.ts new file mode 100644 index 00000000..2bd93426 --- /dev/null +++ b/src/features/history/catalog-offer.test.ts @@ -0,0 +1,54 @@ +import type { SQL } from "drizzle-orm"; +import { MySqlDialect } from "drizzle-orm/mysql-core"; +import { expect, it, vi } from "vitest"; + +vi.mock("@/lib/db", () => ({ db: {} })); + +import { applyHistory, type HistoryTransaction } from "./server"; + +it("locks original and current categories before restoring offer prices and category", async () => { + const before = { pageId: "4", costCredits: 10, costPoints: 0, pointsType: 0 }; + const after = { ...before, pageId: "9", costCredits: 20 }; + let current = after; + const queries: string[] = []; + const tx = { + execute: async (query: SQL) => { + const text = new MySqlDialect().sqlToQuery(query).sql; + queries.push(text); + if (text.includes("catalog_pages")) return [[{ id: 4 }, { id: 9 }]]; + if (text.startsWith("SELECT")) return [[current]]; + current = before; + return [{}]; + }, + insert: () => ({ values: async () => [{ insertId: 17 }] }), + } as unknown as HistoryTransaction; + await applyHistory( + tx, + { kind: "catalog_offer", targetId: 1, before, after }, + 7, + ); + expect(queries[0]).toContain("catalog_pages"); + expect(queries[0]).toContain("FOR UPDATE"); + expect(queries.find((q) => q.startsWith("UPDATE"))).toContain("page_id"); + await expect( + applyHistory(tx, { kind: "catalog_offer", targetId: 1, before, after }, 7), + ).rejects.toThrow("conflict"); +}); +it("refuses restoration into deleted categories without writing", async () => { + const execute = vi.fn(async () => [[]]); + const tx = { execute } as unknown as HistoryTransaction; + const values = { pageId: "4", costCredits: 10, costPoints: 0, pointsType: 0 }; + await expect( + applyHistory( + tx, + { + kind: "catalog_offer", + targetId: 1, + before: values, + after: { ...values, pageId: "9" }, + }, + 7, + ), + ).rejects.toThrow("unavailable"); + expect(execute).toHaveBeenCalledTimes(1); +}); diff --git a/src/features/history/server.ts b/src/features/history/server.ts index 31478827..ad8f4aee 100644 --- a/src/features/history/server.ts +++ b/src/features/history/server.ts @@ -10,7 +10,12 @@ import { import type { db } from "@/lib/db"; import { getOperationContext } from "@/lib/foundation/request-context"; import { type HistorySnapshot, sameSnapshot } from "./model"; -export type HistoryKind = "category" | "category_bc" | "prices" | "news"; +export type HistoryKind = + | "category" + | "category_bc" + | "prices" + | "catalog_offer" + | "news"; export type HistoryTransaction = Parameters< Parameters[0] >[0]; @@ -35,6 +40,10 @@ const categoryFields = [ "includes", ]; const configs = { + catalog_offer: { + table: CatalogItems, + fields: ["pageId", "costCredits", "costPoints", "pointsType"], + }, category: { table: CatalogPages, fields: categoryFields }, category_bc: { table: CatalogPagesBc, fields: categoryFields }, prices: { @@ -113,7 +122,7 @@ export async function recordHistory( Buffer.byteLength(serializedAfter, "utf8") > 16_000_000 ) throw Error("History snapshot is too large"); - await tx.insert(AdminAuditLog).values({ + const inserted = await tx.insert(AdminAuditLog).values({ userId, action, target: kind, @@ -126,6 +135,7 @@ export async function recordHistory( after: serializedAfter, createdAt: new Date().toISOString(), }); + return inserted?.[0]?.insertId; } export async function readHistory(tx: HistoryTransaction, id: number) { const [entry] = await tx @@ -176,6 +186,7 @@ export async function applyHistory( entry: Awaited>, userId: number, ) { + if (entry.kind === "catalog_offer") await lockOfferHistoryPages(tx, [entry]); const current = await historySnapshot(tx, entry.kind, entry.targetId); if (!sameSnapshot(current, entry.after)) throw Error("conflict"); const { table } = historyConfig(entry.kind); @@ -197,3 +208,26 @@ export async function applyHistory( "history_restore", ); } + +export async function lockOfferHistoryPages( + tx: HistoryTransaction, + entries: Array>>, +) { + const ids = [ + ...new Set( + entries.flatMap((entry) => [ + Number(entry.before.pageId), + Number(entry.after.pageId), + ]), + ), + ].sort((a, b) => a - b); + if (ids.some((id) => !Number.isSafeInteger(id) || id < 1)) + throw Error("unavailable"); + const [rows] = await tx.execute( + sql`SELECT id FROM catalog_pages WHERE id IN (${sql.join(ids, sql`, `)}) ORDER BY id FOR UPDATE`, + ); + const found = new Set( + (rows as unknown as Array<{ id: number }>).map((row) => Number(row.id)), + ); + if (ids.some((id) => !found.has(id))) throw Error("unavailable"); +} diff --git a/src/messages/ar.json b/src/messages/ar.json index 71e238ff..956e8152 100644 --- a/src/messages/ar.json +++ b/src/messages/ar.json @@ -76,7 +76,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -321,6 +322,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "catalogPackages": { @@ -521,11 +527,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -540,7 +546,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -632,6 +649,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/bg.json b/src/messages/bg.json index 3d85bb13..a3decd37 100644 --- a/src/messages/bg.json +++ b/src/messages/bg.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/cs.json b/src/messages/cs.json index 95c55df7..b04855ed 100644 --- a/src/messages/cs.json +++ b/src/messages/cs.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/da.json b/src/messages/da.json index cb94ca7b..6936eb67 100644 --- a/src/messages/da.json +++ b/src/messages/da.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/de.json b/src/messages/de.json index f75ad9cd..dc4cd5e2 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -1613,6 +1613,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2362,7 +2367,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3446,11 +3452,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3465,7 +3471,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/el.json b/src/messages/el.json index 4f141c29..8b558506 100644 --- a/src/messages/el.json +++ b/src/messages/el.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/en.json b/src/messages/en.json index 88c9b31c..a0678e6e 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -2214,7 +2214,10 @@ "pageSearch": "Search categories…", "back": "Edit changes", "selectionLimit": "Select at most 500 offers for one operation.", - "refreshing": "Refreshing updated offers…" + "refreshing": "Refreshing updated offers…", + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." }, "discovery": { "searchPlaceholder": "Name, classname, furniture ID or #offer ID…", @@ -3361,7 +3364,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -4360,11 +4364,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -4379,7 +4383,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -4754,6 +4769,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } }, diff --git a/src/messages/es.json b/src/messages/es.json index e8cc4a5b..89bba150 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -1613,6 +1613,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2362,7 +2367,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3446,11 +3452,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3465,7 +3471,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/fi.json b/src/messages/fi.json index db7fe7c8..4e472aa3 100644 --- a/src/messages/fi.json +++ b/src/messages/fi.json @@ -76,7 +76,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -321,6 +322,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "catalogPackages": { @@ -521,11 +527,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -540,7 +546,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -632,6 +649,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/fr.json b/src/messages/fr.json index 3ef2e028..a5e5da2f 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -1613,6 +1613,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2362,7 +2367,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3446,11 +3452,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3465,7 +3471,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/hr.json b/src/messages/hr.json index 76871b8a..ad3e1010 100644 --- a/src/messages/hr.json +++ b/src/messages/hr.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/hu.json b/src/messages/hu.json index cc8a1ae7..fb8634b7 100644 --- a/src/messages/hu.json +++ b/src/messages/hu.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/it.json b/src/messages/it.json index 9ce382dd..111b1040 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -2133,7 +2133,10 @@ "pageSearch": "Cerca categorie…", "back": "Rivedi le modifiche", "selectionLimit": "Seleziona al massimo 500 offerte per operazione.", - "refreshing": "Aggiornamento delle offerte modificate…" + "refreshing": "Aggiornamento delle offerte modificate…", + "undo": "Annulla", + "undoHint": "Puoi ripristinare le singole offerte anche dalla cronologia delle modifiche.", + "undoSuccess": "Ripristinate {count} offerte." }, "discovery": { "searchPlaceholder": "Nome, classname, ID furni o #ID offerta…", @@ -3280,7 +3283,8 @@ "fullStory": "Contenuto articolo", "status": "Stato pubblicazione", "publishAt": "Pubblicazione programmata", - "publishedAt": "Data pubblicazione" + "publishedAt": "Data pubblicazione", + "pageId": "Categoria" } } }, @@ -4335,11 +4339,11 @@ }, "performance": { "title": "Prestazioni delle richieste", - "scope": "API autenticate dell’HK: tempo server fino alla risposta pronta. Non misura il rendering del browser o le pagine pubbliche.", + "scope": "Misure server delle API HK autenticate e di alcune pagine pubbliche. Scegli un gruppo qui sotto.", "refresh": "Aggiorna", "shared": "Diagnostica condivisa tramite Redis.", "local": "Diagnostica locale di questa istanza; si azzera al riavvio.", - "retention": "Fino a 200 richieste recenti, conservate per un’ora. La raccolta può omettere misure.", + "retention": "Fino a 200 campioni per gruppo, conservati per un’ora. La raccolta non è garantita per ogni richiesta.", "search": "Percorso o ID operazione", "sort": "Ordine", "slowest": "Prima le più lente", @@ -4354,7 +4358,18 @@ "calls": "{count} chiamate", "failures": "{count} errori", "streaming": "Risposta in streaming: la misura termina alla creazione della risposta, non alla fine dell’operazione.", - "operation": "ID operazione" + "operation": "ID operazione", + "kind": "Gruppo di misurazione", + "adminApi": "API HK", + "publicPage": "Pagine pubbliche", + "applyFilters": "Applica filtri", + "publicBoundary": "Le misure pubbliche coprono il componente server principale di /me, news, profili, eventi e ricerca. Escludono metadati, componenti figli renderizzati separatamente, rete e rendering nel browser. Gli esiti non sono codici HTTP. Le esecuzioni durante la build statica sono escluse.", + "outcomes": { + "completed": "Componente completato", + "failed": "Componente fallito", + "redirect": "Reindirizzamento", + "not-found": "Pagina non trovata" + } }, "importProgress": { "validating": "Verifica dati locali", @@ -4759,6 +4774,25 @@ }, "openAudit": "Apri Audit catalogo per risolvere i conflitti", "excludedConflicts": "{count, plural, one {# elemento richiede un controllo ed è escluso da questa importazione.} other {# elementi richiedono un controllo e sono esclusi da questa importazione.}}" + }, + "preflight": { + "filterLabel": "Filtra i controlli di importazione", + "filters": { + "all": "Tutti ({count})", + "complete": "Da completare ({count})", + "conflict": "Conflitti ({count})", + "verify": "Da verificare ({count})" + }, + "empty": "Nessun furni in questo gruppo.", + "actions": { + "complete": "Suggerimento: completa il furni esistente", + "import": "Suggerimento: importa il nuovo furni", + "refresh": "Il furni esistente è completo; aggiorna i dati dalla fonte", + "conflict": "Risolvi i conflitti nei dati locali prima di importare", + "verify": "Controlla i componenti non verificati prima di proseguire" + }, + "missing": "Mancanti: {parts}", + "unknown": "Non verificati: {parts}" } } }, diff --git a/src/messages/ja.json b/src/messages/ja.json index ab35e90d..e707806d 100644 --- a/src/messages/ja.json +++ b/src/messages/ja.json @@ -76,7 +76,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -321,6 +322,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "catalogPackages": { @@ -521,11 +527,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -540,7 +546,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -632,6 +649,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/nl.json b/src/messages/nl.json index 35a08065..074c4285 100644 --- a/src/messages/nl.json +++ b/src/messages/nl.json @@ -1982,7 +1982,10 @@ "pageSearch": "Categorieën zoeken…", "back": "Wijzigingen aanpassen", "selectionLimit": "Selecteer maximaal 500 aanbiedingen per bewerking.", - "refreshing": "Bijgewerkte aanbiedingen vernieuwen…" + "refreshing": "Bijgewerkte aanbiedingen vernieuwen…", + "undo": "Ongedaan maken", + "undoHint": "Je kunt afzonderlijke aanbiedingen ook herstellen via de auditgeschiedenis.", + "undoSuccess": "{count} aanbiedingen hersteld." }, "discovery": { "searchPlaceholder": "Naam, classname, meubel-ID of #aanbiedings-ID…", @@ -2918,7 +2921,8 @@ "fullStory": "Artikelinhoud", "status": "Publicatiestatus", "publishAt": "Geplande publicatie", - "publishedAt": "Publicatiedatum" + "publishedAt": "Publicatiedatum", + "pageId": "Categorie" } } }, @@ -3951,11 +3955,11 @@ }, "performance": { "title": "Prestaties van verzoeken", - "scope": "Aangemelde HK-API’s: servertijd totdat het antwoord klaarstaat. Dit meet geen browserweergave of openbare pagina’s.", + "scope": "Servermetingen voor geauthenticeerde HK-API’s en geselecteerde openbare paginacomponenten. Kies hieronder een groep.", "refresh": "Vernieuwen", "shared": "Gedeelde diagnostiek via Redis.", "local": "Lokale diagnostiek voor deze instantie; gewist bij herstart.", - "retention": "Maximaal 200 recente verzoeken, één uur bewaard. Niet alle metingen worden gegarandeerd opgeslagen.", + "retention": "Maximaal 200 metingen per groep, één uur bewaard. Niet elk verzoek wordt gegarandeerd vastgelegd.", "search": "Route of operatie-ID", "sort": "Volgorde", "slowest": "Langzaamste eerst", @@ -3970,7 +3974,18 @@ "calls": "{count} oproepen", "failures": "{count} fouten", "streaming": "Streamingantwoord: de meting stopt bij het aanmaken van het antwoord, niet aan het einde van de operatie.", - "operation": "Operatie-ID" + "operation": "Operatie-ID", + "kind": "Meetgroep", + "adminApi": "HK-API’s", + "publicPage": "Openbare pagina’s", + "applyFilters": "Filters toepassen", + "publicBoundary": "Openbare metingen omvatten de hoofdcomponent op de server voor /me, nieuws, profielen, evenementen en zoeken. Metadata, afzonderlijk gerenderde onderliggende componenten, netwerkoverdracht en browserweergave zijn uitgesloten. Componentresultaten zijn geen HTTP-statuscodes. Aanroepen tijdens de statische build zijn uitgesloten.", + "outcomes": { + "completed": "Component voltooid", + "failed": "Component mislukt", + "redirect": "Omleiding", + "not-found": "Pagina niet gevonden" + } }, "importProgress": { "validating": "Lokale gegevens controleren", @@ -4230,6 +4245,25 @@ }, "openAudit": "Open Cataloguscontrole om conflicten op te lossen", "excludedConflicts": "{count, plural, one {# item moet worden gecontroleerd en is uitgesloten van deze import.} other {# items moeten worden gecontroleerd en zijn uitgesloten van deze import.}}" + }, + "preflight": { + "filterLabel": "Importcontroles filteren", + "filters": { + "all": "Alles ({count})", + "complete": "Aan te vullen ({count})", + "conflict": "Conflicten ({count})", + "verify": "Te controleren ({count})" + }, + "empty": "Geen meubels in deze groep.", + "actions": { + "complete": "Voorstel: bestaand meubel aanvullen", + "import": "Voorstel: nieuw meubel importeren", + "refresh": "Bestaand meubel is compleet; brongegevens vernieuwen", + "conflict": "Los conflicten in lokale gegevens op voordat je importeert", + "verify": "Controleer de niet-geverifieerde onderdelen voordat je doorgaat" + }, + "missing": "Ontbreekt: {parts}", + "unknown": "Niet geverifieerd: {parts}" } } }, diff --git a/src/messages/no.json b/src/messages/no.json index a72ec570..e995e930 100644 --- a/src/messages/no.json +++ b/src/messages/no.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/pl.json b/src/messages/pl.json index 59c31547..835da0b3 100644 --- a/src/messages/pl.json +++ b/src/messages/pl.json @@ -1643,6 +1643,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2392,7 +2397,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3476,11 +3482,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3495,7 +3501,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/pt.json b/src/messages/pt.json index 82d75521..2053b2a5 100644 --- a/src/messages/pt.json +++ b/src/messages/pt.json @@ -1643,6 +1643,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2392,7 +2397,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3476,11 +3482,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3495,7 +3501,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/ro.json b/src/messages/ro.json index 7061fc1d..ee706133 100644 --- a/src/messages/ro.json +++ b/src/messages/ro.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/ru.json b/src/messages/ru.json index 414a15d9..765ae3c2 100644 --- a/src/messages/ru.json +++ b/src/messages/ru.json @@ -1641,6 +1641,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2390,7 +2395,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3474,11 +3480,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3493,7 +3499,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3875,6 +3892,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/sk.json b/src/messages/sk.json index 8634d70d..65725d29 100644 --- a/src/messages/sk.json +++ b/src/messages/sk.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/sr.json b/src/messages/sr.json index 6882e13d..f6d6b7a7 100644 --- a/src/messages/sr.json +++ b/src/messages/sr.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/sv.json b/src/messages/sv.json index 80abc498..f347cc9a 100644 --- a/src/messages/sv.json +++ b/src/messages/sv.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/tr.json b/src/messages/tr.json index 1b6b5d89..4ccf899f 100644 --- a/src/messages/tr.json +++ b/src/messages/tr.json @@ -1643,6 +1643,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2392,7 +2397,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3476,11 +3482,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3495,7 +3501,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3877,6 +3894,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } } diff --git a/src/messages/uk.json b/src/messages/uk.json index 8ec287be..13036970 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -1642,6 +1642,11 @@ "invalid_offer_id": "Invalid offer identifier", "duplicate_offer_id": "Repeated offer identifier" } + }, + "bulk": { + "undo": "Undo", + "undoHint": "You can also restore individual offers from audit history.", + "undoSuccess": "Restored {count} offers." } }, "shop": { @@ -2391,7 +2396,8 @@ "fullStory": "Article content", "status": "Publication status", "publishAt": "Scheduled publication", - "publishedAt": "Publication date" + "publishedAt": "Publication date", + "pageId": "Category" } } }, @@ -3475,11 +3481,11 @@ }, "performance": { "title": "Request performance", - "scope": "Authenticated HK APIs: server time until the response is ready. This does not measure browser rendering or public pages.", + "scope": "Server measurements for authenticated HK APIs and selected public page components. Choose a group below.", "refresh": "Refresh", "shared": "Shared diagnostics via Redis.", "local": "Local diagnostics for this instance only; cleared on restart.", - "retention": "Up to 200 recent requests, retained for one hour. Collection is best effort.", + "retention": "Up to 200 recent samples per group, retained for one hour. Collection is best effort.", "search": "Route or operation ID", "sort": "Order", "slowest": "Slowest first", @@ -3494,7 +3500,18 @@ "calls": "{count} calls", "failures": "{count} failures", "streaming": "Streaming response: timing stops at response creation, not at the end of the operation.", - "operation": "Operation ID" + "operation": "Operation ID", + "kind": "Measurement group", + "adminApi": "HK APIs", + "publicPage": "Public pages", + "applyFilters": "Apply filters", + "publicBoundary": "Public measurements cover the root server page component on /me, news, profiles, events and search. They exclude metadata, separately rendered children, network transfer and browser rendering. Component outcomes are not HTTP response codes. Static build invocations are excluded.", + "outcomes": { + "completed": "Component completed", + "failed": "Component failed", + "redirect": "Redirect", + "not-found": "Page not found" + } }, "importProgress": { "validating": "Checking local data", @@ -3876,6 +3893,25 @@ }, "openAudit": "Open Catalog audit to resolve conflicts", "excludedConflicts": "{count, plural, one {# item needs review and is excluded from this import.} other {# items need review and are excluded from this import.}}" + }, + "preflight": { + "filterLabel": "Filter import checks", + "filters": { + "all": "All ({count})", + "complete": "To complete ({count})", + "conflict": "Conflicts ({count})", + "verify": "To verify ({count})" + }, + "empty": "No furniture in this group.", + "actions": { + "complete": "Suggested: complete existing furniture", + "import": "Suggested: import new furniture", + "refresh": "Existing furniture is complete; refresh source data", + "conflict": "Resolve the conflicting local records before import", + "verify": "Review the unverified components before continuing" + }, + "missing": "Missing: {parts}", + "unknown": "Not verified: {parts}" } } }