feat: add housekeeping domain registry
This commit is contained in:
1 parent
7edca410fa
commit
2d5f03048a
14 files changed
+694
No files matched your search
@@ -0,0 +1,22 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const contentManifest = {
|
||||
id: "content",
|
||||
labelKey: "pages.housekeeping.domains.content.title",
|
||||
descriptionKey: "pages.housekeeping.domains.content.description",
|
||||
iconId: "file-text",
|
||||
previewHref: "/admin-next/content",
|
||||
capability: anyCapability(
|
||||
PERMS.NEWS_VIEW,
|
||||
PERMS.PAGES_VIEW,
|
||||
PERMS.BANNERS_VIEW,
|
||||
PERMS.EVENTS_VIEW,
|
||||
PERMS.POLLS_VIEW,
|
||||
PERMS.PREFIXES_VIEW,
|
||||
),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const economyManifest = {
|
||||
id: "economy",
|
||||
labelKey: "pages.housekeeping.domains.economy.title",
|
||||
descriptionKey: "pages.housekeeping.domains.economy.description",
|
||||
iconId: "gem",
|
||||
previewHref: "/admin-next/economy",
|
||||
capability: anyCapability(PERMS.CATALOG_VIEW, PERMS.SHOP_VIEW),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const hotelManifest = {
|
||||
id: "hotel",
|
||||
labelKey: "pages.housekeeping.domains.hotel.title",
|
||||
descriptionKey: "pages.housekeeping.domains.hotel.description",
|
||||
iconId: "hotel",
|
||||
previewHref: "/admin-next/hotel",
|
||||
capability: anyCapability(
|
||||
PERMS.ROOMS_VIEW,
|
||||
PERMS.RADIO_VIEW,
|
||||
PERMS.ASSETS_IMPORT,
|
||||
),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const operationsManifest = {
|
||||
id: "operations",
|
||||
labelKey: "pages.housekeeping.domains.operations.title",
|
||||
descriptionKey: "pages.housekeeping.domains.operations.description",
|
||||
iconId: "inbox",
|
||||
previewHref: "/admin-next/operations",
|
||||
capability: anyCapability(PERMS.ADMIN_DASHBOARD),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const peopleManifest = {
|
||||
id: "people",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
descriptionKey: "pages.housekeeping.domains.people.description",
|
||||
iconId: "users",
|
||||
previewHref: "/admin-next/people",
|
||||
capability: anyCapability(
|
||||
PERMS.USERS_VIEW,
|
||||
PERMS.MODERATION_VIEW,
|
||||
PERMS.TICKETS_VIEW,
|
||||
PERMS.BANS_VIEW,
|
||||
PERMS.MOD_DASHBOARD,
|
||||
PERMS.MOD_CFH_VIEW,
|
||||
PERMS.MOD_TEAM_VIEW,
|
||||
PERMS.MOD_TICKETS_VIEW,
|
||||
PERMS.MOD_USERS_VIEW,
|
||||
PERMS.MOD_BANS_VIEW,
|
||||
),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import {
|
||||
anyCapability,
|
||||
type HousekeepingDomainManifest,
|
||||
} from "../../foundation/contracts";
|
||||
|
||||
export const systemManifest = {
|
||||
id: "system",
|
||||
labelKey: "pages.housekeeping.domains.system.title",
|
||||
descriptionKey: "pages.housekeeping.domains.system.description",
|
||||
iconId: "settings",
|
||||
previewHref: "/admin-next/system",
|
||||
capability: anyCapability(
|
||||
PERMS.SETTINGS_VIEW,
|
||||
PERMS.LOGS_VIEW,
|
||||
PERMS.ANALYTICS_VIEW,
|
||||
PERMS.DEVOPS_VIEW,
|
||||
PERMS.NOTIFICATIONS_VIEW,
|
||||
PERMS.PERMISSIONS_MANAGE,
|
||||
PERMS.RCON_EXECUTE,
|
||||
),
|
||||
routes: [],
|
||||
} satisfies HousekeepingDomainManifest;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import en from "@/messages/en.json";
|
||||
import itMessages from "@/messages/it.json";
|
||||
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
|
||||
|
||||
const requiredKeys = [
|
||||
"pages.housekeeping.preview.badge",
|
||||
"pages.housekeeping.preview.commandDisabled",
|
||||
"pages.housekeeping.preview.backToSite",
|
||||
"pages.housekeeping.navigation.skipToContent",
|
||||
"pages.housekeeping.navigation.primary",
|
||||
"pages.housekeeping.navigation.contextual",
|
||||
"pages.housekeeping.states.loading.title",
|
||||
"pages.housekeeping.states.loading.description",
|
||||
"pages.housekeeping.states.empty.title",
|
||||
"pages.housekeeping.states.empty.description",
|
||||
"pages.housekeeping.states.partial.label",
|
||||
"pages.housekeeping.states.partial.title",
|
||||
"pages.housekeeping.states.partial.description",
|
||||
"pages.housekeeping.states.error.title",
|
||||
"pages.housekeeping.states.error.description",
|
||||
];
|
||||
|
||||
function resolveMessage(messages: unknown, key: string): unknown {
|
||||
return key.split(".").reduce<unknown>((value, segment) => {
|
||||
if (value === null || typeof value !== "object") return undefined;
|
||||
|
||||
return (value as Record<string, unknown>)[segment];
|
||||
}, messages);
|
||||
}
|
||||
|
||||
describe("housekeeping localization contract", () => {
|
||||
it.each([
|
||||
["English", en],
|
||||
["Italian", itMessages],
|
||||
])("provides the planned housekeeping subtree in %s", (_locale, messages) => {
|
||||
const housekeeping = resolveMessage(messages, "pages.housekeeping");
|
||||
|
||||
expect(housekeeping).toEqual(expect.any(Object));
|
||||
expect(Object.keys(housekeeping as object)).toEqual([
|
||||
"preview",
|
||||
"navigation",
|
||||
"domains",
|
||||
"states",
|
||||
]);
|
||||
|
||||
for (const key of [
|
||||
...HOUSEKEEPING_MANIFESTS.flatMap((manifest) => [
|
||||
manifest.labelKey,
|
||||
manifest.descriptionKey,
|
||||
]),
|
||||
...requiredKeys,
|
||||
]) {
|
||||
expect(resolveMessage(messages, key), key).toEqual(expect.any(String));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { anyCapability, type HousekeepingCapabilityContext } from "./contracts";
|
||||
import { buildHousekeepingNavigation } from "./navigation";
|
||||
import { createHousekeepingRegistry } from "./registry";
|
||||
|
||||
const context = (
|
||||
granted: readonly string[],
|
||||
): HousekeepingCapabilityContext => ({
|
||||
actor: { id: 42, username: "moderator", rank: 3 },
|
||||
isSuperAdmin: false,
|
||||
has: (slug) => granted.includes(slug),
|
||||
hasAny: (...slugs) => slugs.some((slug) => granted.includes(slug)),
|
||||
hasAll: (...slugs) => slugs.every((slug) => granted.includes(slug)),
|
||||
});
|
||||
|
||||
describe("housekeeping navigation", () => {
|
||||
it("shows People to a moderator with a mod view capability while hiding Economy", () => {
|
||||
const registry = createHousekeepingRegistry([
|
||||
{
|
||||
id: "people",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
descriptionKey: "pages.housekeeping.domains.people.description",
|
||||
iconId: "users",
|
||||
previewHref: "/admin-next/people",
|
||||
capability: anyCapability(PERMS.MOD_CFH_VIEW),
|
||||
routes: [],
|
||||
},
|
||||
{
|
||||
id: "economy",
|
||||
labelKey: "pages.housekeeping.domains.economy.title",
|
||||
descriptionKey: "pages.housekeeping.domains.economy.description",
|
||||
iconId: "gem",
|
||||
previewHref: "/admin-next/economy",
|
||||
capability: anyCapability(PERMS.CATALOG_VIEW),
|
||||
routes: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const navigation = buildHousekeepingNavigation(
|
||||
registry,
|
||||
context([PERMS.MOD_CFH_VIEW]),
|
||||
(key) => key,
|
||||
);
|
||||
|
||||
expect(navigation).toEqual([
|
||||
{
|
||||
id: "people",
|
||||
href: "/admin-next/people",
|
||||
iconId: "users",
|
||||
label: "pages.housekeeping.domains.people.title",
|
||||
description: "pages.housekeeping.domains.people.description",
|
||||
items: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters unauthorized domains and routes before translation", () => {
|
||||
const registry = createHousekeepingRegistry([
|
||||
{
|
||||
id: "people",
|
||||
labelKey: "people.title",
|
||||
descriptionKey: "people.description",
|
||||
iconId: "users",
|
||||
previewHref: "/admin-next/people",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
routes: [
|
||||
{
|
||||
id: "users",
|
||||
labelKey: "people.users",
|
||||
href: "/admin-next/people/users",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
},
|
||||
{
|
||||
id: "bans",
|
||||
labelKey: "people.bans",
|
||||
href: "/admin-next/people/bans",
|
||||
capability: anyCapability(PERMS.BANS_VIEW),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "system",
|
||||
labelKey: "system.title",
|
||||
descriptionKey: "system.description",
|
||||
iconId: "settings",
|
||||
previewHref: "/admin-next/system",
|
||||
capability: anyCapability(PERMS.SETTINGS_VIEW),
|
||||
routes: [],
|
||||
},
|
||||
]);
|
||||
const translate = vi.fn((key: string) => `translated:${key}`);
|
||||
|
||||
const navigation = buildHousekeepingNavigation(
|
||||
registry,
|
||||
context([PERMS.USERS_VIEW]),
|
||||
translate,
|
||||
);
|
||||
|
||||
expect(navigation).toEqual([
|
||||
{
|
||||
id: "people",
|
||||
href: "/admin-next/people",
|
||||
iconId: "users",
|
||||
label: "translated:people.title",
|
||||
description: "translated:people.description",
|
||||
items: [
|
||||
{
|
||||
id: "users",
|
||||
href: "/admin-next/people/users",
|
||||
label: "translated:people.users",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(translate).not.toHaveBeenCalledWith("people.bans");
|
||||
expect(translate).not.toHaveBeenCalledWith("system.title");
|
||||
expect(translate).not.toHaveBeenCalledWith("system.description");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { HousekeepingDomainId } from "../migration/types";
|
||||
import { satisfiesCapability } from "./capability-context";
|
||||
import type {
|
||||
HousekeepingCapabilityContext,
|
||||
HousekeepingDomainManifest,
|
||||
} from "./contracts";
|
||||
import type { HousekeepingRegistry } from "./registry";
|
||||
|
||||
export interface HousekeepingNavigationDomain {
|
||||
id: HousekeepingDomainId;
|
||||
href: string;
|
||||
iconId: HousekeepingDomainManifest["iconId"];
|
||||
label: string;
|
||||
description: string;
|
||||
items: readonly { id: string; href: string; label: string }[];
|
||||
}
|
||||
|
||||
export function buildHousekeepingNavigation(
|
||||
registry: HousekeepingRegistry,
|
||||
context: HousekeepingCapabilityContext,
|
||||
translate: (key: string) => string,
|
||||
): readonly HousekeepingNavigationDomain[] {
|
||||
return registry.domains
|
||||
.filter((domain) => satisfiesCapability(context, domain.capability))
|
||||
.map((domain) => ({
|
||||
id: domain.id,
|
||||
href: domain.previewHref,
|
||||
iconId: domain.iconId,
|
||||
label: translate(domain.labelKey),
|
||||
description: translate(domain.descriptionKey),
|
||||
items: domain.routes
|
||||
.filter((route) => satisfiesCapability(context, route.capability))
|
||||
.map((route) => ({
|
||||
id: route.id,
|
||||
href: route.href,
|
||||
label: translate(route.labelKey),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
|
||||
import {
|
||||
HOUSEKEEPING_DOMAIN_IDS,
|
||||
type HousekeepingDomainId,
|
||||
} from "../migration/types";
|
||||
import { anyCapability, type HousekeepingDomainManifest } from "./contracts";
|
||||
import { createHousekeepingRegistry } from "./registry";
|
||||
|
||||
const manifest = (
|
||||
id: HousekeepingDomainId,
|
||||
capabilitySlug = PERMS.ADMIN_DASHBOARD,
|
||||
): HousekeepingDomainManifest => ({
|
||||
id,
|
||||
labelKey: `pages.housekeeping.domains.${id}.title`,
|
||||
descriptionKey: `pages.housekeeping.domains.${id}.description`,
|
||||
iconId: "settings",
|
||||
previewHref: `/admin-next/${id}`,
|
||||
capability: anyCapability(capabilitySlug),
|
||||
routes: [],
|
||||
});
|
||||
|
||||
describe("housekeeping registry", () => {
|
||||
it("registers the six approved domains in their locked order", () => {
|
||||
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
|
||||
|
||||
expect(registry.domains.map((domain) => domain.id)).toEqual(
|
||||
HOUSEKEEPING_DOMAIN_IDS,
|
||||
);
|
||||
expect(Object.isFrozen(registry.domains)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects duplicate and unknown domain identifiers", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
manifest("operations"),
|
||||
manifest("operations"),
|
||||
]),
|
||||
).toThrow("duplicate domain id");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("operations"), id: "future" as HousekeepingDomainId },
|
||||
]),
|
||||
).toThrow("unknown domain id");
|
||||
});
|
||||
|
||||
it("rejects invalid preview and empty translation keys", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), previewHref: "/admin-next/operations" },
|
||||
]),
|
||||
).toThrow("invalid preview href");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([{ ...manifest("people"), labelKey: " " }]),
|
||||
).toThrow("empty label key");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), descriptionKey: "\t" },
|
||||
]),
|
||||
).toThrow("empty description key");
|
||||
});
|
||||
|
||||
it("rejects duplicate route identities and hrefs", () => {
|
||||
const base = manifest("people");
|
||||
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...base,
|
||||
routes: [
|
||||
{
|
||||
id: "users",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
href: "/admin-next/people/users",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
},
|
||||
{
|
||||
id: "users",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
href: "/admin-next/people/staff",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate route id");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...base,
|
||||
routes: [
|
||||
{
|
||||
id: "users",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
href: "/admin-next/people/users",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
},
|
||||
{
|
||||
id: "staff",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
href: "/admin-next/people/users",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate route href");
|
||||
});
|
||||
|
||||
it("rejects empty route fields and unknown capability slugs", () => {
|
||||
const route = {
|
||||
id: "users",
|
||||
labelKey: "pages.housekeeping.domains.people.title",
|
||||
href: "/admin-next/people/users",
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), routes: [{ ...route, id: " " }] },
|
||||
]),
|
||||
).toThrow("empty route id");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), routes: [{ ...route, href: " " }] },
|
||||
]),
|
||||
).toThrow("empty route href");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), routes: [{ ...route, labelKey: " " }] },
|
||||
]),
|
||||
).toThrow("empty route label key");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("system"),
|
||||
capability: anyCapability("admin.ghost.view"),
|
||||
},
|
||||
]),
|
||||
).toThrow("unknown capability slug: admin.ghost.view");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
routes: [
|
||||
{ ...route, capability: anyCapability("admin.ghost.route") },
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("unknown capability slug: admin.ghost.route");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { HOUSEKEEPING_DOMAIN_IDS } from "../migration/types";
|
||||
import type {
|
||||
CapabilityRequirement,
|
||||
HousekeepingDomainManifest,
|
||||
} from "./contracts";
|
||||
|
||||
const approvedDomainIds = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
|
||||
const knownCapabilitySlugs = new Set<string>(Object.values(PERMS));
|
||||
|
||||
export interface HousekeepingRegistry {
|
||||
domains: readonly HousekeepingDomainManifest[];
|
||||
}
|
||||
|
||||
export function createHousekeepingRegistry(
|
||||
manifests: readonly HousekeepingDomainManifest[],
|
||||
): HousekeepingRegistry {
|
||||
const domainIds = new Set<string>();
|
||||
const routeIds = new Set<string>();
|
||||
const routeHrefs = new Set<string>();
|
||||
|
||||
for (const manifest of manifests) {
|
||||
if (!approvedDomainIds.has(manifest.id)) {
|
||||
throw new Error(`unknown domain id: ${manifest.id}`);
|
||||
}
|
||||
if (domainIds.has(manifest.id)) {
|
||||
throw new Error(`duplicate domain id: ${manifest.id}`);
|
||||
}
|
||||
domainIds.add(manifest.id);
|
||||
|
||||
if (manifest.previewHref !== `/admin-next/${manifest.id}`) {
|
||||
throw new Error(`invalid preview href: ${manifest.previewHref}`);
|
||||
}
|
||||
validateNonEmpty(manifest.labelKey, "label key");
|
||||
validateNonEmpty(manifest.descriptionKey, "description key");
|
||||
validateCapability(manifest.capability);
|
||||
|
||||
for (const route of manifest.routes) {
|
||||
validateNonEmpty(route.id, "route id");
|
||||
if (routeIds.has(route.id)) {
|
||||
throw new Error(`duplicate route id: ${route.id}`);
|
||||
}
|
||||
routeIds.add(route.id);
|
||||
|
||||
validateNonEmpty(route.href, "route href");
|
||||
if (routeHrefs.has(route.href)) {
|
||||
throw new Error(`duplicate route href: ${route.href}`);
|
||||
}
|
||||
routeHrefs.add(route.href);
|
||||
|
||||
validateNonEmpty(route.labelKey, "route label key");
|
||||
validateCapability(route.capability);
|
||||
}
|
||||
}
|
||||
|
||||
return { domains: Object.freeze([...manifests]) };
|
||||
}
|
||||
|
||||
function validateNonEmpty(value: string, name: string): void {
|
||||
if (!value.trim()) {
|
||||
throw new Error(`empty ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateCapability(requirement: CapabilityRequirement): void {
|
||||
if (requirement.slugs.length === 0) {
|
||||
throw new Error("empty capability requirement");
|
||||
}
|
||||
|
||||
for (const slug of requirement.slugs) {
|
||||
if (!knownCapabilitySlugs.has(slug)) {
|
||||
throw new Error(`unknown capability slug: ${slug}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { contentManifest } from "./domains/content/manifest";
|
||||
import { economyManifest } from "./domains/economy/manifest";
|
||||
import { hotelManifest } from "./domains/hotel/manifest";
|
||||
import { operationsManifest } from "./domains/operations/manifest";
|
||||
import { peopleManifest } from "./domains/people/manifest";
|
||||
import { systemManifest } from "./domains/system/manifest";
|
||||
import type { HousekeepingDomainManifest } from "./foundation/contracts";
|
||||
|
||||
export const HOUSEKEEPING_MANIFESTS = [
|
||||
operationsManifest,
|
||||
peopleManifest,
|
||||
contentManifest,
|
||||
economyManifest,
|
||||
hotelManifest,
|
||||
systemManifest,
|
||||
] as const satisfies readonly HousekeepingDomainManifest[];
|
||||
@@ -3255,6 +3255,63 @@
|
||||
"failed": "Failed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"housekeeping": {
|
||||
"preview": {
|
||||
"badge": "Foundation preview",
|
||||
"commandDisabled": "Search and commands are enabled in a later subproject.",
|
||||
"backToSite": "Back to site"
|
||||
},
|
||||
"navigation": {
|
||||
"skipToContent": "Skip to content",
|
||||
"primary": "Housekeeping domains",
|
||||
"contextual": "Domain navigation"
|
||||
},
|
||||
"domains": {
|
||||
"operations": {
|
||||
"title": "Operations",
|
||||
"description": "Operational overview and daily queues"
|
||||
},
|
||||
"people": {
|
||||
"title": "People",
|
||||
"description": "Users, moderation, and support"
|
||||
},
|
||||
"content": {
|
||||
"title": "Content",
|
||||
"description": "Editorial content and community engagement"
|
||||
},
|
||||
"economy": {
|
||||
"title": "Economy",
|
||||
"description": "Catalog and shop operations"
|
||||
},
|
||||
"hotel": {
|
||||
"title": "Hotel",
|
||||
"description": "Rooms, radio, and asset tools"
|
||||
},
|
||||
"system": {
|
||||
"title": "System",
|
||||
"description": "Configuration, observability, and access"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"loading": {
|
||||
"title": "Loading housekeeping",
|
||||
"description": "Preparing the available housekeeping tools."
|
||||
},
|
||||
"empty": {
|
||||
"title": "Nothing available",
|
||||
"description": "No housekeeping content is available for this domain."
|
||||
},
|
||||
"partial": {
|
||||
"label": "Partial data",
|
||||
"title": "Some information is unavailable",
|
||||
"description": "Review the available information and try again later."
|
||||
},
|
||||
"error": {
|
||||
"title": "Unable to load housekeeping",
|
||||
"description": "Try again later or contact an administrator."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3260,6 +3260,63 @@
|
||||
"failed": "Failed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"housekeeping": {
|
||||
"preview": {
|
||||
"badge": "Anteprima della base",
|
||||
"commandDisabled": "Ricerca e comandi saranno abilitati in un sottoprogetto successivo.",
|
||||
"backToSite": "Torna al sito"
|
||||
},
|
||||
"navigation": {
|
||||
"skipToContent": "Vai al contenuto",
|
||||
"primary": "Domini housekeeping",
|
||||
"contextual": "Navigazione del dominio"
|
||||
},
|
||||
"domains": {
|
||||
"operations": {
|
||||
"title": "Operazioni",
|
||||
"description": "Panoramica operativa e code giornaliere"
|
||||
},
|
||||
"people": {
|
||||
"title": "Persone",
|
||||
"description": "Utenti, moderazione e supporto"
|
||||
},
|
||||
"content": {
|
||||
"title": "Contenuti",
|
||||
"description": "Contenuti editoriali e coinvolgimento della community"
|
||||
},
|
||||
"economy": {
|
||||
"title": "Economia",
|
||||
"description": "Operazioni di catalogo e shop"
|
||||
},
|
||||
"hotel": {
|
||||
"title": "Hotel",
|
||||
"description": "Stanze, radio e strumenti per gli asset"
|
||||
},
|
||||
"system": {
|
||||
"title": "Sistema",
|
||||
"description": "Configurazione, osservabilità e accesso"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"loading": {
|
||||
"title": "Caricamento housekeeping",
|
||||
"description": "Preparazione degli strumenti housekeeping disponibili."
|
||||
},
|
||||
"empty": {
|
||||
"title": "Nessun contenuto disponibile",
|
||||
"description": "Non ci sono contenuti housekeeping disponibili per questo dominio."
|
||||
},
|
||||
"partial": {
|
||||
"label": "Dati parziali",
|
||||
"title": "Alcune informazioni non sono disponibili",
|
||||
"description": "Controlla le informazioni disponibili e riprova più tardi."
|
||||
},
|
||||
"error": {
|
||||
"title": "Impossibile caricare housekeeping",
|
||||
"description": "Riprova più tardi o contatta un amministratore."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user