diff --git a/src/actions/admin-guilds.ts b/src/actions/admin-guilds.ts new file mode 100644 index 00000000..8b70eca2 --- /dev/null +++ b/src/actions/admin-guilds.ts @@ -0,0 +1,48 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { requirePermissionRateLimited } from "@/lib/admin/guard"; +import { PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; +import { logStaffActivity } from "@/lib/services/staff-activity"; + +/** Disband a guild and clean related membership/forum rows. */ +export async function disbandGuild(formData: FormData): Promise { + const staff = await requirePermissionRateLimited(PERMS.USERS_EDIT); + const id = Number(formData.get("id")); + if (!(id > 0)) return; + + const guild = await prisma.guilds.findUnique({ + where: { id }, + select: { id: true, name: true, userId: true }, + }); + if (!guild) return; + + await prisma.$transaction(async (tx) => { + const threads = await tx.guildsForumsThreads.findMany({ + where: { guildId: id }, + select: { id: true }, + }); + const threadIds = threads.map((t) => t.id); + if (threadIds.length > 0) { + await tx.guildsForumsComments.deleteMany({ + where: { threadId: { in: threadIds } }, + }); + await tx.guildsForumsThreads.deleteMany({ where: { guildId: id } }); + } + await tx.guildForumViews.deleteMany({ where: { guildId: id } }); + await tx.guildsMembers.deleteMany({ where: { guildId: id } }); + await tx.rooms.updateMany({ where: { guildId: id }, data: { guildId: 0 } }); + await tx.items.updateMany({ where: { guildId: id }, data: { guildId: 0 } }); + await tx.guilds.delete({ where: { id } }); + }); + + await logStaffActivity({ + staffId: staff.id, + action: "guild_disband", + description: `Disbanded guild #${id} (${guild.name}), owner #${guild.userId}`, + targetType: "guild", + targetId: id, + }); + revalidatePath("/admin/guilds"); +} diff --git a/src/app/admin/guilds/[id]/page.tsx b/src/app/admin/guilds/[id]/page.tsx new file mode 100644 index 00000000..801973ec --- /dev/null +++ b/src/app/admin/guilds/[id]/page.tsx @@ -0,0 +1,205 @@ +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { disbandGuild } from "@/actions/admin-guilds"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +function fmt(ts: number): string { + if (!ts) return "—"; + return new Date(ts * 1000).toISOString().slice(0, 16).replace("T", " "); +} + +export default async function AdminGuildDetailPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { session, permissions } = await getAdminContext(); + if (!canAccess(permissions, PERMS.USERS_VIEW, session.user.rank)) { + redirect("/admin"); + } + + const canEdit = canAccess(permissions, PERMS.USERS_EDIT, session.user.rank); + const t = await getTranslations("pages.admin.guilds"); + const { id: rawId } = await params; + const id = Number(rawId); + if (!Number.isFinite(id) || id <= 0) notFound(); + + const guild = await prisma.guilds.findUnique({ + where: { id }, + select: { + id: true, + name: true, + description: true, + userId: true, + roomId: true, + badge: true, + dateCreated: true, + state: true, + forum: true, + }, + }); + if (!guild) notFound(); + + const [owner, members, memberCount, threadCount] = await Promise.all([ + prisma.user + .findUnique({ + where: { id: guild.userId }, + select: { id: true, username: true }, + }) + .catch(() => null), + prisma.guildsMembers + .findMany({ + where: { guildId: id }, + orderBy: [{ levelId: "asc" }, { memberSince: "asc" }], + take: 100, + select: { userId: true, levelId: true, memberSince: true }, + }) + .catch(() => []), + prisma.guildsMembers.count({ where: { guildId: id } }).catch(() => 0), + prisma.guildsForumsThreads.count({ where: { guildId: id } }).catch(() => 0), + ]); + + const memberUserIds = members.map((m) => m.userId).filter((uid) => uid > 0); + const memberUsers = + memberUserIds.length > 0 + ? await prisma.user + .findMany({ + where: { id: { in: memberUserIds } }, + select: { id: true, username: true }, + }) + .catch(() => []) + : []; + const usernameById = new Map(memberUsers.map((u) => [u.id, u.username])); + + return ( +
+
+
+ {guild.badge ? ( + // eslint-disable-next-line @next/next/no-img-element + + ) : null} +
+

+ {guild.name || `#${guild.id}`} +

+

#{guild.id}

+
+
+
+ + {t("back")} + + + {t("viewPublic")} + + {canEdit ? ( +
+ + +
+ ) : null} +
+
+ + {guild.description ? ( +

+ {guild.description} +

+ ) : null} + +
+
+
{t("colOwner")}
+
+ {owner ? ( + + {owner.username} + + ) : ( + `#${guild.userId}` + )} +
+
+
+
{t("colMembers")}
+
{memberCount}
+
+
+
{t("colThreads")}
+
{threadCount}
+
+
+
{t("colCreated")}
+
{fmt(guild.dateCreated)}
+
+
+
{t("colRoom")}
+
+ {guild.roomId > 0 ? ( + + #{guild.roomId} + + ) : ( + "—" + )} +
+
+
+ +
+

+ {t("members")} ({Math.min(members.length, memberCount)}) +

+ {members.length === 0 ? ( +
{t("noMembers")}
+ ) : ( +
+ + + + + + + + + + {members.map((m) => ( + + + + + + ))} + +
{t("colUser")}{t("colLevel")}{t("colJoined")}
+ + {usernameById.get(m.userId) ?? `#${m.userId}`} + + {m.levelId}{fmt(m.memberSince)}
+
+ )} +
+
+ ); +} diff --git a/src/app/admin/guilds/page.tsx b/src/app/admin/guilds/page.tsx new file mode 100644 index 00000000..2a55a3f8 --- /dev/null +++ b/src/app/admin/guilds/page.tsx @@ -0,0 +1,207 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { AdminSimplePager } from "@/components/admin/admin-simple-pager"; +import { StatusCard } from "@/components/admin/dashboard"; +import { calcPagination, parseListParams } from "@/lib/admin-helpers"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +function fmt(ts: number): string { + if (!ts) return "—"; + return new Date(ts * 1000).toISOString().slice(0, 10); +} + +export default async function AdminGuildsPage({ + searchParams, +}: { + searchParams: Promise>; +}) { + const { session, permissions } = await getAdminContext(); + if (!canAccess(permissions, PERMS.USERS_VIEW, session.user.rank)) { + redirect("/admin"); + } + + const t = await getTranslations("pages.admin.guilds"); + const raw = await searchParams; + const parsed = parseListParams(new URLSearchParams(raw)); + const q = parsed.search.trim(); + + let total = 0; + let rows: Array<{ + id: number; + name: string; + description: string; + userId: number; + badge: string; + dateCreated: number; + roomId: number; + }> = []; + let loadError = false; + + try { + const where = q + ? { + OR: [ + { name: { contains: q } }, + { description: { contains: q } }, + ...(Number.isFinite(Number(q)) && Number(q) > 0 + ? [{ id: Number(q) }] + : []), + ], + } + : {}; + + total = await prisma.guilds.count({ where }); + const pagination = calcPagination(total, parsed.page, parsed.perPage); + rows = await prisma.guilds.findMany({ + where, + select: { + id: true, + name: true, + description: true, + userId: true, + badge: true, + dateCreated: true, + roomId: true, + }, + orderBy: { id: "desc" }, + skip: pagination.offset, + take: pagination.perPage, + }); + } catch { + loadError = true; + } + + const pagination = calcPagination(total, parsed.page, parsed.perPage); + const ownerIds = [ + ...new Set(rows.map((r) => r.userId).filter((id) => id > 0)), + ]; + const guildIds = rows.map((r) => r.id); + + const [owners, memberGroups] = await Promise.all([ + ownerIds.length + ? prisma.user + .findMany({ + where: { id: { in: ownerIds } }, + select: { id: true, username: true }, + }) + .catch(() => []) + : Promise.resolve([]), + guildIds.length + ? prisma.guildsMembers + .groupBy({ + by: ["guildId"], + where: { guildId: { in: guildIds } }, + _count: { _all: true }, + }) + .catch(() => []) + : Promise.resolve([]), + ]); + + const ownerById = new Map(owners.map((u) => [u.id, u.username])); + const membersByGuild = new Map( + memberGroups.map((g) => [g.guildId, g._count._all]), + ); + + return ( +
+ {loadError ? ( +
+ {t("loadError")} +
+ ) : null} + +
+ + +
+ +
+ +
+ +
+

+ {t("title")} ({total}) +

+ {rows.length === 0 ? ( +
{t("empty")}
+ ) : ( +
+ + + + + + + + + + + + + {rows.map((g) => ( + + + + + + + + + ))} + +
{t("colId")}{t("colBadge")}{t("colName")}{t("colOwner")}{t("colMembers")}{t("colCreated")}
{g.id} + {g.badge ? ( + // eslint-disable-next-line @next/next/no-img-element + + ) : ( + — + )} + + + {g.name || `#${g.id}`} + + {g.description ? ( +
+ {g.description} +
+ ) : null} +
+ + {ownerById.get(g.userId) ?? `#${g.userId}`} + + {membersByGuild.get(g.id) ?? 0}{fmt(g.dateCreated)}
+
+ )} + +
+
+ ); +} diff --git a/src/app/admin/users/_components/user-sanction-timeline.tsx b/src/app/admin/users/_components/user-sanction-timeline.tsx new file mode 100644 index 00000000..b191065d --- /dev/null +++ b/src/app/admin/users/_components/user-sanction-timeline.tsx @@ -0,0 +1,87 @@ +import Link from "next/link"; +import type { SanctionEvent } from "../_lib/load-user-sanctions"; + +function fmt(ts: number): string { + if (!ts) return "—"; + return new Date(ts * 1000).toISOString().slice(0, 16).replace("T", " "); +} + +const KIND_CLASS: Record = { + ban: "bg-[var(--admin-error-subtle)] text-[var(--admin-error)]", + lift: "bg-[var(--admin-success-subtle)] text-[var(--admin-success)]", + mute: "bg-[var(--admin-accent)]/20 text-[var(--admin-accent)]", + unmute: "bg-[var(--admin-success-subtle)] text-[var(--admin-success)]", + cfh: "bg-[var(--admin-accent)]/15 text-[var(--admin-text)]", + staff: "bg-[var(--admin-border)] text-[var(--admin-text-muted)]", + status: "bg-[var(--admin-accent)]/25 text-[var(--admin-accent)]", +}; + +export function UserSanctionTimeline({ + events, + muteUntil, +}: { + events: SanctionEvent[]; + muteUntil: number; +}) { + const now = Math.floor(Date.now() / 1000); + const muted = muteUntil > now; + + return ( +
+
+

Sanctions timeline

+ {muted ? ( + + Muted until {fmt(muteUntil)} + + ) : null} +
+ + {events.length === 0 ? ( +

+ No bans, mutes, CFH, or staff actions recorded for this user. +

+ ) : ( +
    + {events.map((ev) => ( +
  1. + + {ev.kind} + +
    +
    + {ev.href ? ( + + {ev.label} + + ) : ( + {ev.label} + )} + + {fmt(ev.at)} + + {ev.actor ? ( + + by {ev.actor} + + ) : null} +
    +

    + {ev.detail} +

    +
    +
  2. + ))} +
+ )} +
+ ); +} diff --git a/src/app/admin/users/_lib/load-user-sanctions.ts b/src/app/admin/users/_lib/load-user-sanctions.ts new file mode 100644 index 00000000..4da7c18b --- /dev/null +++ b/src/app/admin/users/_lib/load-user-sanctions.ts @@ -0,0 +1,224 @@ +import { isBanActive, unixNow } from "@/lib/bans"; +import { prisma } from "@/lib/prisma"; + +export type SanctionEvent = { + id: string; + at: number; + kind: "ban" | "lift" | "mute" | "unmute" | "cfh" | "staff" | "status"; + label: string; + detail: string; + actor?: string; + href?: string; +}; + +function parseAuditAt(createdAt: string): number { + const asNum = Number(createdAt); + if (Number.isFinite(asNum) && asNum > 1_000_000_000) return asNum; + const ms = Date.parse(createdAt); + return Number.isFinite(ms) ? Math.floor(ms / 1000) : 0; +} + +const AUDIT_ACTIONS = [ + "ban", + "unban", + "user_mute", + "user_unmute", + "mod_mute", + "mod_unmute", + "user_ban", + "ban_lift", +] as const; + +/** Build a merged sanction timeline for an admin user show tab. */ +export async function loadUserSanctions( + userId: number, +): Promise<{ events: SanctionEvent[]; muteUntil: number }> { + const now = unixNow(); + const events: SanctionEvent[] = []; + + const [bans, audits, staffActs, cfh, settings] = await Promise.all([ + prisma.ban + .findMany({ + where: { userId }, + orderBy: { timestamp: "desc" }, + take: 40, + select: { + id: true, + banReason: true, + type: true, + timestamp: true, + banExpire: true, + userStaffId: true, + }, + }) + .catch(() => []), + prisma.adminAuditLog + .findMany({ + where: { + target: "User", + targetId: userId, + action: { in: [...AUDIT_ACTIONS] }, + }, + orderBy: { id: "desc" }, + take: 40, + select: { + id: true, + action: true, + details: true, + createdAt: true, + userId: true, + user: { select: { username: true } }, + }, + }) + .catch(() => []), + prisma.staffActivities + .findMany({ + where: { + targetType: "user", + targetId: BigInt(userId), + }, + orderBy: { id: "desc" }, + take: 40, + select: { + id: true, + action: true, + description: true, + createdAt: true, + userId: true, + }, + }) + .catch(() => []), + prisma.supportTickets + .findMany({ + where: { + OR: [{ reportedId: userId }, { senderId: userId }], + }, + orderBy: { id: "desc" }, + take: 20, + select: { + id: true, + issue: true, + state: true, + timestamp: true, + senderId: true, + reportedId: true, + modId: true, + }, + }) + .catch(() => []), + prisma.usersSettings + .findUnique({ + where: { userId }, + select: { muteEndTimestamp: true }, + }) + .catch(() => null), + ]); + + const muteUntil = settings?.muteEndTimestamp ?? 0; + if (muteUntil > now) { + events.push({ + id: "mute-active", + at: now, + kind: "status", + label: "Mute active", + detail: `Until ${new Date(muteUntil * 1000).toISOString().slice(0, 16).replace("T", " ")}`, + }); + } + + const staffIds = [ + ...new Set([ + ...bans.map((b) => b.userStaffId).filter((id) => id > 0), + ...staffActs.map((a) => Number(a.userId)).filter((id) => id > 0), + ]), + ]; + const staffUsers = + staffIds.length > 0 + ? await prisma.user + .findMany({ + where: { id: { in: staffIds } }, + select: { id: true, username: true }, + }) + .catch(() => []) + : []; + const staffName = new Map(staffUsers.map((u) => [u.id, u.username])); + + for (const ban of bans) { + const active = isBanActive(ban.banExpire, now); + events.push({ + id: `ban-${ban.id}`, + at: ban.timestamp, + kind: "ban", + label: active ? `Ban (${ban.type}) · active` : `Ban (${ban.type})`, + detail: ban.banReason || "—", + actor: staffName.get(ban.userStaffId), + }); + } + + for (const a of audits) { + const kind: SanctionEvent["kind"] = + a.action.includes("unmute") || a.action === "unban" + ? a.action.includes("mute") + ? "unmute" + : "lift" + : a.action.includes("mute") + ? "mute" + : a.action.includes("ban") + ? "ban" + : "staff"; + events.push({ + id: `audit-${a.id}`, + at: parseAuditAt(a.createdAt), + kind, + label: a.action, + detail: a.details?.slice(0, 200) || "—", + actor: a.user?.username, + }); + } + + for (const s of staffActs) { + events.push({ + id: `staff-${String(s.id)}`, + at: Math.floor(s.createdAt.getTime() / 1000), + kind: "staff", + label: s.action, + detail: s.description.slice(0, 200), + actor: staffName.get(Number(s.userId)), + }); + } + + for (const c of cfh) { + const role = + c.reportedId === userId + ? "reported" + : c.senderId === userId + ? "reporter" + : "related"; + events.push({ + id: `cfh-${c.id}`, + at: c.timestamp, + kind: "cfh", + label: `CFH #${c.id} (${role}, state ${c.state})`, + detail: c.issue?.slice(0, 200) || "—", + href: `/admin/moderation/cfh/${c.id}`, + }); + } + + events.sort((a, b) => b.at - a.at || a.id.localeCompare(b.id)); + + // Deduplicate near-identical ban+audit pairs within 5s + const deduped: SanctionEvent[] = []; + for (const ev of events) { + const prev = deduped[deduped.length - 1]; + if ( + prev && + prev.kind === ev.kind && + Math.abs(prev.at - ev.at) <= 5 && + prev.label.split(" ")[0] === ev.label.split(" ")[0] + ) { + continue; + } + deduped.push(ev); + } + + return { events: deduped.slice(0, 80), muteUntil }; +} diff --git a/src/app/admin/users/show/[id]/page.tsx b/src/app/admin/users/show/[id]/page.tsx index 381534e8..c90cf581 100644 --- a/src/app/admin/users/show/[id]/page.tsx +++ b/src/app/admin/users/show/[id]/page.tsx @@ -5,7 +5,9 @@ import { getWatchedUserIds } from "@/lib/services/watch"; import { UserAccountInfo } from "../../_components/user-account-info"; import { UserBadgesReadonly } from "../../_components/user-badges-readonly"; import { UserHero } from "../../_components/user-hero"; +import { UserSanctionTimeline } from "../../_components/user-sanction-timeline"; import { loadUserById } from "../../_lib/load-user"; +import { loadUserSanctions } from "../../_lib/load-user-sanctions"; import { UserActivityLog } from "../../[id]/user-activity-log"; import { UserQuickActions } from "../../[id]/user-quick-actions"; import { UserRooms } from "../../[id]/user-rooms"; @@ -23,6 +25,7 @@ export default async function UserShowPage({ const canEdit = canAccess(permissions, PERMS.USERS_EDIT, session.user.rank); const data = await loadUserById(id); + const sanctions = await loadUserSanctions(data.user.id); const watched = await getWatchedUserIds(session.user.id); return ( @@ -55,6 +58,9 @@ export default async function UserShowPage({ Badges ({data.user.badges.length}) Rooms ({data.roomCount}) + + Sanctions ({sanctions.events.length}) + Activity @@ -75,6 +81,13 @@ export default async function UserShowPage({ + + + + {amount} crediti per 1,00 {currency}. I crediti compaiono nel gioco al tuo prossimo accesso." - }, - "marketplace": { - "title": "Mercatino", - "subtitle": "Sfoglia mobili e rari messi in vendita dai giocatori. Le inserzioni vengono create nel gioco e si aggiornano in tempo reale — entra nell'hotel per fare un'offerta.", - "activeOffers": "Offerte attive", - "sellers": "Venditori", - "totalValue": "Valore totale", - "activeListings": "Inserzioni attive", - "mostRecentFirst": "Prima le più recenti", - "empty": "Al momento non ci sono offerte attive nel mercatino. Riprova più tardi.", - "colOffer": "Offerta", - "colItem": "Oggetto", - "colSeller": "Venditore", - "colPrice": "Prezzo", - "colListed": "Pubblicata", - "itemLabel": "Oggetto #{id}", - "userLabel": "Utente #{id}", - "justNow": "proprio ora", - "minutesAgo": "{count} min fa", - "hoursAgo": "{count} h fa", - "daysAgo": "{count} g fa" - }, - "rares": { - "title": "Valori dei rari", - "subtitle": "Ottieni una panoramica di tutti i rari dell'hotel, raggruppati per categoria.", - "empty": "Al momento non abbiamo rari elencati qui.", - "rareCount": "{count, plural, one {# raro} other {# rari}}", - "viewRares": "Vedi i rari →" - }, - "rareCategory": { - "rareCount": "{count, plural, one {# raro in questa categoria} other {# rari in questa categoria}}", - "allRares": "← Tutti i rari", - "empty": "Ancora nessun raro elencato in questa categoria.", - "colRare": "Raro", - "colCredits": "Crediti", - "colCurrency": "Valuta", - "creditsValue": "{value} crediti", - "duckets": "Duckets", - "diamonds": "Diamanti", - "credits": "Crediti" - }, - "redeem": { - "title": "Riscatta un buono", - "subtitle": "Hai un codice buono? Inseriscilo qui sotto per aggiungere la ricompensa al tuo account.", - "enterCode": "Inserisci il tuo codice", - "oncePerAccount": "Ogni buono può essere riscattato una sola volta per account.", - "balanceTitle": "Il tuo saldo", - "signedInAs": "Hai effettuato l'accesso come {username}", - "credits": "Crediti", - "instantCredit": "Le ricompense riscattate vengono accreditate immediatamente e compaiono nel gioco al tuo prossimo accesso." - }, - "help": { - "title": "Centro assistenza", - "subtitle": "Trova guide, argomenti di supporto e le regole dell'hotel: tutto ciò che ti serve per ricevere aiuto.", - "myTickets": "I miei ticket →", - "topicsTitle": "Argomenti di supporto", - "topicsSubtitle": "Sfoglia guide e risposte alle domande più comuni", - "topicsEmpty": "Ancora nessun argomento di aiuto.", - "rulesTitle": "Regole dell'hotel", - "rulesSubtitle": "Leggile prima di trascorrere del tempo nell'hotel", - "rulesEmpty": "Ancora nessuna regola pubblicata.", - "categoryEmpty": "Nessuna regola in questa categoria." - }, - "helpCategory": { - "back": "← Centro assistenza", - "topicSubtitle": "Argomento del centro assistenza", - "moreTitle": "Altri argomenti di aiuto", - "moreSubtitle": "Passa a un altro argomento di supporto" - }, - "helpTickets": { - "title": "Ticket di assistenza", - "subtitle": "Invia un ticket allo staff e tieni traccia delle tue richieste aperte.", - "statTotal": "Ticket totali", - "statOpen": "Aperti", - "statClosed": "Chiusi", - "newTitle": "Nuovo ticket", - "newSubtitle": "Descrivi il tuo problema e lo staff ti ricontatterà", - "subjectPlaceholder": "Oggetto", - "contentPlaceholder": "Descrivi il tuo problema…", - "submit": "Invia ticket", - "listTitle": "I tuoi ticket", - "listSubtitle": "Tutte le richieste che hai inviato allo staff", - "listEmpty": "Non hai ancora nessun ticket.", - "colId": "ID", - "colSubject": "Oggetto", - "colStatus": "Stato", - "colCreated": "Creato", - "statusOpen": "Aperto", - "statusClosed": "Chiuso", - "success": { - "created": "Ticket inviato." - }, - "errors": { - "invalid": "Compila oggetto e descrizione.", - "moderated": "Quel ticket è stato bloccato dalla moderazione.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "helpTicketDetail": { - "backToList": "← I tuoi ticket", - "ticketId": "Ticket #{id}", - "statusOpen": "Aperto", - "statusClosed": "Chiuso", - "messagesTitle": "Conversazione", - "messagesSubtitle": "{count, plural, one {# messaggio} other {# messaggi}}", - "you": "Tu", - "userNumber": "Utente #{id}", - "replyTitle": "Rispondi", - "replySubtitle": "Invia un aggiornamento allo staff", - "replyPlaceholder": "Scrivi il tuo messaggio…", - "replySubmit": "Invia risposta", - "closeTitle": "Chiudi ticket", - "closeSubtitle": "Segna la richiesta come risolta se non ti serve più aiuto", - "closeSubmit": "Chiudi ticket", - "closedHint": "Questo ticket è chiuso. Apri un nuovo ticket se hai bisogno di altro aiuto.", - "success": { - "replied": "Risposta inviata.", - "closed": "Ticket chiuso." - }, - "errors": { - "invalid": "Inserisci un messaggio.", - "notFound": "Ticket non trovato.", - "closedTicket": "Questo ticket è chiuso.", - "moderated": "Quel messaggio è stato bloccato dalla moderazione.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "applyStaff": { - "title": "Candidati per lo staff di {hotel}", - "subtitle": "Apriamo le candidature per lo staff di tanto in tanto. Se trovi una posizione che senti perfetta per te, non esitare a candidarti.", - "noPositionsTitle": "Nessuna posizione aperta", - "noPositionsBody": "Al momento non ci sono posizioni aperte. Torna più tardi per verificare se nel frattempo ci sono nuove opportunità. Grazie per il tuo interesse.", - "noDeadline": "Nessuna scadenza impostata", - "deadline": "Scadenza candidatura: {date}", - "alreadyApplied": "Ti sei già candidato per {rank}", - "aboutYou": "Parlaci di te", - "aboutPlaceholder": "Raccontaci perché saresti un ottimo {rank}…", - "applyFor": "Candidati per {rank}", - "success": { - "submitted": "Candidatura inviata — lo staff la esaminerà a breve." - }, - "errors": { - "empty": "Scrivi almeno 10 caratteri su di te.", - "invalid": "Candidatura non valida.", - "duplicate": "Ti sei già candidato per questa posizione.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "applyTeam": { - "title": "Candidati per il team di {hotel}", - "subtitle": "Apriamo le candidature per il team periodicamente. Se vedi un team adatto a te, non esitare a candidarti!", - "noPositionsTitle": "Nessuna posizione nel team aperta", - "noPositionsBody": "Al momento non ci sono posizioni aperte nel team. Torna più tardi per verificare se ci sono nuove opportunità. Grazie!", - "pending": "La tua candidatura è in attesa", - "aboutYou": "Parlaci di te", - "aboutPlaceholder": "Raccontaci perché saresti perfetto per {rank}…", - "applyFor": "Candidati per {rank}", - "success": { - "submitted": "Candidatura inviata — lo staff la esaminerà a breve." - }, - "errors": { - "empty": "Scrivi almeno 10 caratteri su di te.", - "invalid": "Candidatura non valida.", - "duplicate": "Ti sei già candidato per questo team.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "radio": { - "title": "Radio", - "subtitle": "Sintonizzati sulla radio dell'hotel — set DJ in diretta, richieste e saluti.", - "nowPlaying": "In riproduzione", - "nothingPlaying": "Nessun brano in riproduzione al momento.", - "onAir": "In onda: {dj}", - "noDj": "Nessun DJ in onda.", - "audioUnsupported": "Il tuo browser non supporta l'elemento audio.", - "streamOffline": "Lo streaming è al momento offline.", - "exploreTitle": "Esplora la radio", - "exploreSubtitle": "Programmazione, saluti, richieste e altro", - "linkSchedule": "Programmazione", - "linkScheduleText": "La nostra programmazione settimanale di DJ e show.", - "linkShouts": "Saluti", - "linkShoutsText": "Saluti e richieste dalla community.", - "linkRequests": "Richieste", - "linkRequestsText": "Chiedi al DJ di mettere il tuo brano preferito.", - "linkApply": "Candidati come DJ", - "linkApplyText": "Unisciti al team e va' in onda.", - "linkLeaderboard": "Classifica", - "linkLeaderboardText": "I migliori ascoltatori in base ai punti guadagnati.", - "todayTitle": "Programmazione di oggi", - "todayTitleWithDay": "Programmazione di oggi · {day}", - "todaySubtitle": "Cosa c'è in onda oggi", - "fullWeek": "Settimana intera →", - "noShowsToday": "Nessuno show in programma per oggi.", - "colTime": "Orario", - "colShow": "Show", - "colDj": "DJ", - "untitledShow": "Show senza titolo", - "userNumber": "Utente #{id}" - }, - "radioSchedule": { - "title": "Programmazione radio", - "subtitle": "La nostra programmazione settimanale di DJ e show", - "empty": "Nessuno show ancora in programma.", - "colDay": "Giorno", - "colTime": "Orario", - "colDj": "DJ", - "colShow": "Show", - "noShows": "Nessuno show" - }, - "radioShouts": { - "title": "Saluti radio", - "subtitle": "Saluti e richieste dalla community", - "postTitle": "Pubblica un saluto", - "postSubtitle": "Invia un messaggio al DJ in onda", - "messageLabel": "Il tuo messaggio", - "messagePlaceholder": "Invia un saluto al DJ…", - "shoutButton": "Invia", - "loginToPost": "Accedi per pubblicare un saluto.", - "latestTitle": "Ultimi saluti", - "latestSubtitle": "I messaggi più recenti degli ascoltatori", - "empty": "Ancora nessun saluto. Sii il primo!", - "success": { - "posted": "Saluto pubblicato." - }, - "errors": { - "invalid": "Scrivi un messaggio prima di inviare.", - "moderated": "Quel saluto è stato bloccato dalla moderazione.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "events": { - "title": "Eventi", - "subtitle": "Eventi dell'hotel in arrivo e recenti a cui puoi partecipare", - "empty": "Ancora nessun evento.", - "back": "← Eventi", - "when": "Quando", - "status": "Stato", - "statusPublished": "Aperto", - "statusCompleted": "Completato", - "registrations": "Iscrizioni", - "registered": "{count} iscritti", - "spots": "{count} / {max} posti", - "registerTitle": "Iscriviti", - "registerSubtitle": "Prenota il tuo posto per questo evento", - "register": "Iscriviti all'evento", - "registering": "Iscrizione in corso…", - "registerSuccess": "Sei iscritto!", - "registerError": "Impossibile iscriversi a questo evento.", - "loginToRegister": "Accedi per iscriverti a questo evento.", - "alreadyRegistered": "Sei già iscritto.", - "eventEnded": "Questo evento è terminato.", - "eventFull": "Questo evento è al completo.", - "cannotRegister": "Le iscrizioni non sono disponibili.", - "prizesTitle": "Premi", - "winnersTitle": "Vincitori", - "unknownUser": "Utente #{id}" - }, - "polls": { - "title": "Sondaggi", - "subtitle": "Condividi la tua opinione nei sondaggi della community", - "empty": "Ancora nessun sondaggio.", - "back": "← Sondaggi", - "statusActive": "Aperto", - "statusClosed": "Chiuso", - "questionsCount": "{count} domande", - "ends": "Termina {date}", - "voteTitle": "Esprimi il voto", - "voteSubtitle": "Rispondi a ogni domanda qui sotto", - "submitVote": "Invia voto", - "voting": "Invio in corso…", - "voteSuccess": "Grazie per aver votato!", - "voteError": "Impossibile inviare il voto.", - "loginToVote": "Accedi per votare in questo sondaggio.", - "alreadyVoted": "Hai già votato.", - "pollClosed": "Questo sondaggio è chiuso.", - "cannotVote": "Il voto non è disponibile.", - "textPlaceholder": "La tua risposta…", - "resultsTitle": "Risultati", - "voteCount": "{count} ({pct}%)", - "totalVotes": "{count} voti totali", - "textResponses": "{count} risposte libere" - }, - "radioContests": { - "title": "Concorsi radio", - "subtitle": "Sintonizzati e partecipa ai nostri ultimi concorsi in onda", - "empty": "Ancora nessun concorso.", - "contestLabel": "Concorso #{id}", - "dateUnknown": "Data sconosciuta" - }, - "radioGiveaways": { - "title": "Omaggi radio", - "subtitle": "Resta in ascolto per avere la possibilità di vincere ai nostri omaggi radio", - "empty": "Ancora nessun omaggio.", - "giveawayLabel": "Omaggio #{id}", - "dateUnknown": "Data sconosciuta" - }, - "radioLeaderboard": { - "title": "Classifica ascoltatori radio", - "subtitle": "I 50 migliori ascoltatori per punti guadagnati sintonizzandosi sulla radio", - "empty": "Ancora nessun punto ascoltatore da mostrare.", - "colRank": "Posizione", - "colAvatar": "Avatar", - "colListener": "Ascoltatore", - "colPoints": "Punti" - }, - "radioApply": { - "title": "Candidati come DJ radiofonico", - "subtitle": "Raccontaci di te e perché saresti perfetto per andare in onda", - "latestTitle": "La tua ultima candidatura", - "statusLabel": "Stato:", - "submittedOn": "· inviata il {date}", - "submitAgain": "Puoi inviare di nuovo qui sotto se i tuoi dati sono cambiati.", - "formTitle": "Modulo di candidatura", - "formSubtitle": "Tutti i campi contrassegnati come obbligatori devono essere compilati", - "realNameLabel": "Nome reale", - "realNamePlaceholder": "Il tuo nome completo", - "ageLabel": "Età", - "availabilityLabel": "Disponibilità", - "availabilityPlaceholder": "In quali giorni e orari puoi trasmettere?", - "musicStyleLabel": "Stile musicale", - "musicStylePlaceholder": "es. House, Pop, Hip-Hop", - "experienceLabel": "Esperienza", - "optional": "(facoltativo)", - "experiencePlaceholder": "Hai esperienze precedenti come DJ o conduttore?", - "motivationLabel": "Motivazione", - "motivationPlaceholder": "Perché vuoi entrare nel team della radio?", - "submitButton": "Invia candidatura", - "success": { - "submitted": "Candidatura inviata — ti ricontatteremo presto." - }, - "errors": { - "invalid": "Compila tutti i campi obbligatori.", - "ratelimit": "Stai inviando troppo in fretta. Attendi un momento e riprova.", - "error": "Qualcosa è andato storto. Riprova." - } - }, - "settings": { - "accountSettings": "Impostazioni account", - "accountSettingsSubtitle": "Gestisci il tuo profilo, la sicurezza e gli account collegati", - "publicProfile": "Il tuo profilo pubblico", - "noMotto": "Nessun motto", - "noEmail": "Nessuna e-mail registrata", - "changeMotto": "Cambia motto", - "changeMottoSubtitle": "Si aggiorna subito in gioco se sei online", - "mottoPlaceholder": "Il tuo motto", - "saveMotto": "Salva motto", - "securityTitle": "Sicurezza", - "securitySubtitle": "Autenticazione a due fattori", - "securityDescription": "Aggiungi un ulteriore livello di protezione al tuo account.", - "twoFactorLink": "Autenticazione a due fattori", - "connectionsTitle": "Account collegati", - "connectionsSubtitle": "Accedi con un clic", - "connectionsDescription": "Collega Discord o Google al tuo account dell'hotel.", - "manageConnections": "Gestisci collegamenti" - }, - "settings2fa": { - "backToSettings": "← Impostazioni", - "title": "Autenticazione a due fattori", - "subtitle": "Aggiungi un livello di sicurezza in più con un'app di autenticazione", - "nowEnabled": "L'autenticazione a due fattori è ora attiva. 🔒", - "disabledNotice": "L'autenticazione a due fattori è stata disattivata.", - "badCode": "Codice non valido, riprova.", - "noAppKeyPrefix": "L'autenticazione a due fattori non è disponibile finché non è configurata", - "noAppKeyMiddle": "(la stessa", - "noAppKeySuffix": "Laravel del tuo AtomCMS, così i segreti esistenti restano leggibili).", - "isEnabled": "L'autenticazione a due fattori è attiva", - "onYourAccount": "sul tuo account.", - "disable": "Disattiva 2FA", - "scanOrEnter": "Scansiona o inserisci questa chiave", - "scanInstructions": "Aggiungila a Google Authenticator / Authy, poi inserisci il codice a 6 cifre per confermare.", - "manualKey": "Chiave manuale:", - "codePlaceholder": "Codice a 6 cifre", - "confirm": "Conferma", - "enableIntro": "Aggiungi un livello di sicurezza in più con un'app di autenticazione.", - "enable": "Attiva 2FA" - }, - "connections": { - "backToSettings": "← Impostazioni", - "title": "Account collegati", - "subtitle": "Collega un account social per poter accedere con un clic", - "matchByEmail": "Lo abbiniamo al tuo account dell'hotel tramite e-mail, quindi usa l'indirizzo registrato:", - "lookupFailed": "Al momento non riusciamo a leggere i tuoi account collegati. Puoi comunque collegarne uno qui sotto: lo stato verrà mostrato non appena disponibile.", - "statusUnavailable": "Non disponibile su questo hotel.", - "statusConnected": "Collegato — accedi con questo account.", - "statusNotConnected": "Non collegato.", - "connected": "Collegato", - "connectProvider": "Collega {provider}", - "unavailable": "Non disponibile", - "footerNote": "Il collegamento ti porta al provider per l'autorizzazione, poi ti riporta qui. Se l'e-mail del tuo account social non corrisponde a un account dell'hotel, verrai riportato alla schermata di accesso con una nota." - }, - "login": { - "title": "Accedi", - "subtitle": "Bentornato — accedi per entrare nell'hotel.", - "subtitle2fa": "Inserisci il codice a 6 cifre della tua app di autenticazione.", - "usernamePlaceholder": "Nome utente", - "passwordPlaceholder": "Password", - "codePlaceholder": "Codice 2FA", - "pleaseWait": "Attendi…", - "verify": "Verifica", - "signIn": "Accedi", - "or": "oppure", - "continueWithDiscord": "Continua con Discord", - "continueWithGoogle": "Continua con Google", - "noAccount": "Non hai un account?", - "createOne": "Creane uno", - "forgotPassword": "Password dimenticata?", - "errorInvalidCredentials": "Nome utente o password non validi", - "errorInvalid2fa": "Codice 2FA non valido", - "errorUnverified": "Verifica la tua email prima di accedere.", - "errorCaptcha": "Verifica captcha non riuscita. Riprova.", - "welcomeBack": "Bentornato", - "welcomeBackSub": "Accedi per continuare su {hotelName}" - }, - "register": { - "title": "Crea un account", - "subtitle": "Entra nell'hotel — bastano pochi istanti.", - "username": "Nome utente", - "email": "Email", - "password": "Password", - "confirmPassword": "Ripeti la password", - "usernamePlaceholder": "Nome utente", - "emailPlaceholder": "Email", - "passwordPlaceholder": "Password (min 6 caratteri)", - "confirmPasswordPlaceholder": "Ripeti la password", - "selectOutfit": "Scegli il tuo outfit", - "termsAccept": "Ho 18+ anni e accetto i termini e regole di {hotel}.", - "createAccount": "Crea account", - "creatingAccount": "Creazione account...", - "alreadyHaveAccount": "Hai già un account? Accedi!", - "register": "Registrati", - "termsError": "Devi accettare i termini e regole per registrarti.", - "usernameError": "Il nome utente è obbligatorio.", - "emailError": "Email valida obbligatoria.", - "passwordError": "La password deve essere di almeno 6 caratteri.", - "confirmPasswordError": "Le password non coincidono.", - "termsRequired": "Devi accettare i termini e le regole.", - "usernameRequired": "Il nome utente è obbligatorio.", - "emailRequired": "Email è obbligatoria.", - "passwordRequired": "La password è obbligatoria.", - "passwordsDontMatch": "Le password non coincidono.", - "alreadyHaveAccountPre": "Hai già un account?", - "alreadyHaveAccountLink": "Accedi", - "whoIsOnline": "Chi è online", - "usersOnline": "{count} online" - }, - "forgot": { - "title": "Reimposta password", - "subtitle": "Ti invieremo via e-mail un link sicuro per impostare una nuova password.", - "sentNotice": "Se l'indirizzo è registrato, ti abbiamo inviato un link per il reset. Controlla la posta in arrivo.", - "emailPlaceholder": "La tua email", - "sendResetLink": "Invia link di reset", - "backToLogin": "Torna all'accesso", - "errorCaptcha": "Verifica captcha non riuscita. Riprova." - }, - "reset": { - "title": "Imposta una nuova password", - "subtitle": "Scegli una password robusta che non usi altrove.", - "passwordPlaceholder": "Nuova password (min 6 caratteri)", - "resetPassword": "Reimposta password", - "backToLogin": "Torna all'accesso" - }, - "verify": { - "verifiedTitle": "È tutto pronto", - "verifiedSubtitle": "Email verificata", - "verifiedBody": "Il tuo indirizzo email è stato verificato. Ora puoi accedere e giocare.", - "goToLogin": "Vai all'accesso", - "alreadyTitle": "Già verificata", - "alreadySubtitle": "Niente da fare", - "alreadyBody": "Questo indirizzo email è già stato confermato.", - "unavailableTitle": "Riprova tra poco", - "unavailableSubtitle": "Server irraggiungibile", - "unavailableBody": "Non siamo riusciti a raggiungere il server per verificare la tua email. Riprova il link tra un momento.", - "invalidTitle": "Verifica email", - "invalidSubtitle": "Link non valido o scaduto", - "invalidBody": "Questo link di verifica non è valido. Assicurati di aver aperto il link completo della tua email, oppure richiedine uno nuovo.", - "backToLogin": "Torna all'accesso" - }, - "banned": { - "title": "Sei stato bannato", - "subtitle": "Account sospeso", - "body": "Il tuo account è stato sospeso dall'hotel.", - "reasonLabel": "Motivo:", - "permanent": "Questo ban è permanente.", - "expires": "Scade il {date}.", - "contactStaff": "Se ritieni che si tratti di un errore, contatta lo staff." - }, - "maintenance": { - "title": "{hotel} è in manutenzione", - "subtitle": "Torneremo a breve", - "staffCanLogIn": "Lo staff può comunque accedere." - }, - "admin": { - "nav": { - "overview": "Panoramica", - "dashboard": "Dashboard", - "content": "Contenuti", - "articles": "Articoli", - "tags": "Tag", - "navigator": "Navigazione", - "helpCenter": "Centro assistenza", - "advertisements": "Pubblicità", - "writeableBoxes": "Box modificabili", - "photos": "Foto", - "tickets": "Assistenza", - "helpTickets": "Ticket centro assistenza", - "events": "Eventi", - "polls": "Sondaggi", - "banners": "Banner", - "prefixes": "Prefissi", - "usersAndAccess": "Utenti e accesso", - "users": "Utenti", - "bans": "Ban", - "ipManagement": "Gestione IP", - "vpn": "VPN", - "applications": "Candidature", - "teams": "Team", - "permissions": "Permessi", - "onlineUsers": "Utenti online", - "moderation": "Moderazione", - "economy": "Economia", - "catalog": "Catalogo", - "shop": "Negozio", - "marketplace": "Marketplace", - "transactions": "Transazioni", - "vouchers": "Buoni", - "subscriptions": "Abbonamenti", - "rareValues": "Valori rari", - "badges": "Distintivi", - "achievements": "Trofei", - "calendar": "Calendario", - "radio": "Radio", - "radioHistory": "Cronologia radio", - "radioBanners": "Banner", - "radioEmbed": "Incorpora", - "radioApiKeys": "Chiavi API", - "radioModeration": "Moderazione", - "radioSettings": "Impostazioni", - "radioMonitoring": "Monitoraggio", - "radioPoints": "Punti", - "radioRanks": "Gradi", - "radioAutodj": "Auto DJ", - "system": "Sistema", - "commandocentrum": "Centro comandi", - "emulator": "Emulatore", - "alerts": "Avvisi", - "logs": "Registri", - "auditLog": "Log di audit", - "analytics": "Analitiche", - "devops": "DevOps", - "wordFilter": "Filtro parole", - "maintenance": "Manutenzione", - "housekeeping": "Housekeeping", - "rooms": "Stanze", - "emailTemplates": "Template email", - "theme": "Tema", - "media": "Media", - "favicon": "Favicon", - "sounds": "Suoni", - "translations": "Traduzioni", - "import": "Importa", - "settings": "Impostazioni", - "backToSite": "Torna al sito", - "expandAll": "Apri tutte", - "collapseAll": "Chiudi tutte", - "hotelManagementPanel": "Pannello di gestione hotel", - "rankLabel": "Grado {rank}", - "hubNewsMedia": "Notizie e media", - "hubSiteContent": "Contenuti sito", - "hubEngagement": "Community", - "hubUsers": "Users", - "hubAccess": "Access & security", - "hubCatalog": "Catalog & rares", - "hubShop": "Shop & payments", - "hubTools": "Tools", - "hubObservability": "Observability", - "community": "Community", - "monitoring": "Monitoraggio", - "staffAccess": "Gradi e permessi", - "multiAccounts": "Multi-account", - "chatLog": "Log chat", - "commandLog": "Log comandi", - "tradeLog": "Log trade", - "tools": "Strumenti" - }, - "dashboard": { - "title": "Dashboard", - "subtitle": "Panoramica hotel e stato in tempo reale", - "playersOnline": "Giocatori online", - "articles": "Articoli", - "activeBans": "Ban attivi", - "database": "Database", - "databaseConnected": "Connesso", - "databaseDown": "Giù", - "usersByRank": "Utenti per grado", - "noAccountsToBreakDown": "Ancora nessun account da analizzare", - "rankLabel": "Grado {rank}", - "rankWithCount": "Grado {rank}: {count} account", - "recentStaffActivity": "Attività recente dello staff", - "viewAll": "Vedi tutto", - "noStaffActivityLogged": "Nessuna attività dello staff ancora registrata" - }, - "articles": { - "title": "Articoli", - "subtitle": "Articoli delle news — 50 più recenti", - "newArticle": "+ Nuovo articolo", - "edit": "Modifica", - "delete": "Elimina", - "noArticles": "Ancora nessun articolo", - "confirmDelete": "Sei sicuro di voler eliminare \"{title}\"?", - "colTitle": "Titolo", - "colSlug": "Slug", - "colAuthor": "Autore", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "title": "Titolo", - "titlePlaceholder": "Titolo dell'articolo", - "slug": "Slug", - "slugPlaceholder": "slug-url-articolo", - "category": "Categoria", - "categoryPlaceholder": "Seleziona una categoria", - "body": "Corpo", - "bodyPlaceholder": "Scrivi il contenuto dell'articolo…", - "image": "Immagine", - "imagePlaceholder": "https://example.com/immagine.png", - "thumbnail": "Miniatura", - "thumbnailPlaceholder": "https://example.com/miniatura.png", - "published": "Pubblicato", - "featured": "In evidenza", - "save": "Salva articolo", - "saving": "Salvataggio…", - "deleteArticle": "Elimina articolo", - "deleteConfirm": "Questa azione è irreversibile. L'articolo verrà rimosso permanentemente.", - "cancel": "Annulla" - } - }, - "events": { - "newEvent": "+ Nuovo evento", - "createTitle": "Crea evento", - "createSubtitle": "Programma un nuovo evento dell'hotel", - "searchPlaceholder": "Cerca eventi…", - "colId": "ID", - "colTitle": "Titolo", - "colType": "Tipo", - "colStarts": "Inizio", - "colStatus": "Stato", - "colActions": "Azioni", - "statusPublished": "Pubblicato", - "statusDraft": "Bozza", - "statusCancelled": "Annullato", - "statusCompleted": "Completato", - "statRegistrations": "Iscrizioni", - "statPrizes": "Premi", - "statWinners": "Vincitori", - "statTime": "Orario", - "maxPlayers": "Max: {count}", - "timeUpcoming": "In arrivo", - "timePast": "Passato", - "endsAt": "Fine: {date}", - "userFallback": "Utente #{id}", - "registrationsTitle": "Iscrizioni ({count})", - "noRegistrations": "Nessuna iscrizione ancora.", - "noPrizes": "Nessun premio configurato.", - "noWinners": "Nessun vincitore ancora.", - "addPrize": "Aggiungi premio", - "addWinner": "Aggiungi vincitore", - "formEditTitle": "Modifica evento", - "formDetailsTitle": "Dettagli evento", - "formTitle": "Titolo", - "formEventType": "Tipo evento", - "formSelectType": "Seleziona tipo", - "formDescription": "Descrizione", - "formStartsAt": "Inizio", - "formEndsAt": "Fine", - "formSelectStart": "Seleziona data di inizio", - "formSelectEnd": "Seleziona data di fine", - "formMaxPlayers": "Max giocatori", - "formUnlimited": "Illimitato", - "formRoomId": "ID stanza", - "formOptional": "Opzionale", - "formImageUrl": "URL immagine", - "formSaving": "Salvataggio…", - "formUpdate": "Aggiorna evento", - "formCreate": "Crea evento", - "toastUpdated": "Evento aggiornato!", - "toastCreated": "Evento creato!" - }, - "polls": { - "newPoll": "+ Nuovo sondaggio", - "createTitle": "Crea sondaggio", - "createSubtitle": "Crea un nuovo sondaggio o survey della community", - "searchPlaceholder": "Cerca sondaggi…", - "colId": "ID", - "colTitle": "Titolo", - "colStatus": "Stato", - "colType": "Tipo", - "colCreated": "Creato", - "colActions": "Azioni", - "statusActive": "Attivo", - "statusDraft": "Bozza", - "statusClosed": "Chiuso", - "typeMultiple": "Scelta multipla", - "typeSingle": "Scelta singola", - "statVoters": "Votanti totali", - "statQuestions": "Domande", - "statVotes": "Voti totali", - "questionsTitle": "Domande ({count})", - "addQuestion": "Aggiungi domanda", - "noQuestions": "Nessuna domanda ancora. Aggiungi la prima sopra.", - "resultsTitle": "Risultati", - "formEditTitle": "Modifica sondaggio", - "formDetailsTitle": "Dettagli sondaggio", - "formTitle": "Titolo", - "formDescription": "Descrizione", - "formStartsAt": "Inizio", - "formEndsAt": "Fine", - "formSelectStart": "Seleziona data di inizio", - "formSelectEnd": "Seleziona data di fine", - "formShowResults": "Mostra risultati ai votanti", - "formAllowMultiple": "Consenti risposte multiple", - "formSaving": "Salvataggio…", - "formUpdate": "Aggiorna sondaggio", - "formCreate": "Crea sondaggio", - "toastUpdated": "Sondaggio aggiornato!", - "toastCreated": "Sondaggio creato!" - }, - "multiAccounts": { - "minAccounts": "Min. account per cluster", - "resultLimit": "Limite risultati", - "refresh": "Aggiorna", - "noClusters": "Nessun cluster trovato con le soglie attuali." - }, - "devops": { - "noEmulatorErrors": "Nessun errore emulator trovato", - "noRecentErrors": "Nessun errore recente" - }, - "tags": { - "title": "Tag", - "subtitle": "Tag / categorie degli articoli", - "addTag": "Aggiungi tag", - "name": "Nome", - "namePlaceholder": "Nome del tag", - "backgroundColour": "Colore di sfondo", - "createTag": "Crea tag", - "editTag": "Modifica tag", - "updateTag": "Aggiorna tag", - "deleteTag": "Elimina tag", - "confirmDelete": "Eliminare \"{name}\"?", - "noTags": "Ancora nessun tag", - "colId": "ID", - "colTag": "Tag", - "colColour": "Colore", - "colUsedBy": "Usato da", - "colCreated": "Creato", - "colActions": "Azioni" - }, - "navigation": { - "title": "Navigatore", - "flatCategories": "Categorie piatte", - "publicRooms": "Stanze pubbliche", - "addCategory": "Aggiungi categoria", - "editCategory": "Modifica categoria", - "deleteCategory": "Elimina categoria", - "categoryName": "Nome categoria", - "categoryNamePlaceholder": "es. Relax e Chat", - "categoryOrder": "Ordine", - "roomName": "Nome stanza", - "roomNamePlaceholder": "es. Sala Benvenuto", - "roomId": "ID stanza", - "roomIdPlaceholder": "ID numerico della stanza", - "save": "Salva", - "cancel": "Annulla", - "noCategories": "Ancora nessuna categoria", - "noPublicRooms": "Ancora nessuna stanza pubblica", - "colId": "ID", - "colName": "Nome", - "colOrder": "Ordine", - "colRooms": "Stanze", - "colActions": "Azioni" - }, - "helpCenter": { - "title": "Centro assistenza", - "subtitle": "Gestione voci di supporto e FAQ", - "entries": "Voci", - "newEntry": "+ Nuova voce", - "edit": "Modifica", - "delete": "Elimina", - "noEntries": "Ancora nessuna voce", - "confirmDelete": "Eliminare \"{title}\"?", - "colId": "ID", - "colTitle": "Titolo", - "colCategory": "Categoria", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "title": "Titolo", - "titlePlaceholder": "Titolo della voce", - "category": "Categoria", - "categoryPlaceholder": "Seleziona categoria", - "body": "Corpo", - "bodyPlaceholder": "Scrivi la risposta…", - "save": "Salva voce", - "saving": "Salvataggio…", - "deleteEntry": "Elimina voce", - "cancel": "Annulla" - } - }, - "ads": { - "title": "Pubblicità", - "subtitle": "Gestisci annunci dell'hotel e banner promozionali", - "newAd": "+ Nuovo annuncio", - "edit": "Modifica", - "delete": "Elimina", - "noAds": "Ancora nessun annuncio", - "confirmDelete": "Eliminare questo annuncio?", - "deleteSuccess": "Annuncio eliminato!", - "deleteError": "Eliminazione annuncio non riuscita.", - "adsCount": "Annunci totali", - "latest": "Ultimo", - "mostRecentAd": "Annuncio più recente", - "allAds": "Tutti gli annunci", - "colPreview": "Anteprima", - "colId": "ID", - "colName": "Nome", - "colImage": "Immagine", - "colUrl": "URL", - "colViews": "Visualizzazioni", - "colStatus": "Stato", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "name": "Nome", - "namePlaceholder": "Nome annuncio", - "image": "URL immagine", - "imagePlaceholder": "https://example.com/banner.png", - "url": "URL di destinazione", - "urlPlaceholder": "https://example.com", - "enabled": "Attivo", - "save": "Salva annuncio", - "saving": "Salvataggio…", - "deleteAd": "Elimina annuncio", - "cancel": "Annulla" - } - }, - "writeableBoxes": { - "title": "Box modificabili", - "subtitle": "Pannelli di contenuto mostrati nell'hotel e nel CMS", - "newBox": "Nuovo box", - "edit": "Modifica", - "delete": "Elimina", - "noBoxes": "Ancora nessun box modificabile", - "confirmDelete": "Eliminare questo box?", - "colId": "ID", - "colName": "Nome", - "colLocation": "Posizione", - "colUpdated": "Aggiornato", - "colActions": "Azioni", - "form": { - "name": "Nome", - "namePlaceholder": "Nome del box", - "location": "Posizione", - "locationPlaceholder": "es. homepage, sidebar", - "content": "Contenuto", - "contentPlaceholder": "Scrivi il contenuto del box…", - "enabled": "Attivo", - "save": "Salva box", - "saving": "Salvataggio…", - "deleteBox": "Elimina box", - "cancel": "Annulla" - } - }, - "photos": { - "title": "Moderazione foto", - "subtitle": "Foto recenti dalla fotocamera di gioco", - "delete": "Elimina", - "approve": "Approva", - "noPhotos": "Nessuna foto da moderare", - "confirmDelete": "Eliminare questa foto permanentemente?", - "colId": "ID", - "colPreview": "Anteprima", - "colUser": "Utente", - "colRoom": "Stanza", - "colTimestamp": "Data", - "colActions": "Azioni", - "photoAlt": "Foto utente #{id}", - "byUser": "di {username}" - }, - "users": { - "title": "Utenti", - "subtitle": "Sfoglia, cerca e gestisci account dell'hotel", - "search": "Cerca", - "searchPlaceholder": "Cerca per nome utente, email o ID…", - "searchResults": "{count, plural, one {# risultato} other {# risultati}} per \"{query}\"", - "noResults": "Nessun utente trovato", - "colId": "ID", - "colUsername": "Nome utente", - "colEmail": "Email", - "colRank": "Grado", - "colOnline": "Online", - "colRegistered": "Registrato", - "colLastOnline": "Ultimo accesso", - "colActions": "Azioni", - "yes": "Sì", - "no": "No", - "edit": "Modifica", - "setRank": "Imposta grado", - "disconnect": "Disconnetti", - "alert": "Avviso", - "ban": "Banna", - "detail": { - "title": "Utente: {username}", - "subtitle": "Dettagli account e gestione", - "overview": "Panoramica", - "actions": "Azioni", - "username": "Nome utente", - "email": "Email", - "motto": "Motto", - "rank": "Grado", - "credits": "Crediti", - "duckets": "Duckets", - "diamonds": "Diamanti", - "online": "Online", - "registered": "Registrato", - "lastOnline": "Ultimo accesso", - "ipAddress": "Indirizzo IP", - "machineId": "ID macchina", - "sendAlert": "Invia avviso", - "sendMessage": "Invia messaggio", - "disconnectUser": "Disconnetti utente", - "banUser": "Banna utente", - "resetPassword": "Reimposta password", - "deleteUser": "Elimina utente", - "confirmDelete": "Sei sicuro di voler eliminare {username}? Questa azione è irreversibile." - }, - "editForm": { - "title": "Modifica utente: {username}", - "subtitle": "Modifica dettagli account", - "username": "Nome utente", - "usernamePlaceholder": "Nome utente", - "email": "Email", - "emailPlaceholder": "utente@example.com", - "motto": "Motto", - "mottoPlaceholder": "Motto utente", - "rank": "Grado", - "credits": "Crediti", - "duckets": "Duckets", - "diamonds": "Diamanti", - "save": "Salva modifiche", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "bans": { - "title": "Ban", - "subtitle": "Gestisci ban di account, IP e macchina", - "newBan": "Nuovo ban", - "edit": "Modifica", - "delete": "Elimina", - "noBans": "Ancora nessun ban", - "confirmDelete": "Rimuovere questo ban?", - "colId": "ID", - "colValue": "Valore", - "colType": "Tipo", - "colReason": "Motivo", - "colBannedBy": "Bannato da", - "colExpires": "Scade", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "value": "Valore", - "valuePlaceholder": "Nome utente, IP o ID macchina", - "type": "Tipo", - "typeUser": "Utente", - "typeIp": "IP", - "typeMachine": "Macchina", - "reason": "Motivo", - "reasonPlaceholder": "Motivo del ban", - "expiresAt": "Scade il", - "permanent": "Permanente", - "save": "Salva ban", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "ip": { - "title": "Gestione IP", - "subtitle": "Gestisci whitelist e blacklist IP", - "whitelist": "Whitelist", - "blacklist": "Blacklist", - "addIp": "Aggiungi IP", - "ipAddress": "Indirizzo IP", - "ipAddressPlaceholder": "es. 127.0.0.1", - "reason": "Motivo", - "reasonPlaceholder": "Motivo dell'inserimento", - "add": "Aggiungi", - "remove": "Rimuovi", - "confirmRemove": "Rimuovere questo IP dalla {list}?", - "noWhitelistedIps": "Nessun IP in whitelist", - "noBlacklistedIps": "Nessun IP in blacklist", - "colIp": "IP", - "colReason": "Motivo", - "colAdded": "Aggiunto", - "colActions": "Azioni" - }, - "vpn": { - "title": "Rilevamento VPN / Proxy", - "subtitle": "Configura le impostazioni di rilevamento VPN e proxy", - "enabled": "Attivo", - "apiKey": "Chiave API", - "apiKeyPlaceholder": "La tua chiave API per il rilevamento VPN", - "apiKeyHint": "Lascia vuoto per disattivare", - "blockOnDetect": "Blocca al rilevamento", - "blockOnDetectHint": "Blocca automaticamente gli utenti rilevati dietro VPN o proxy", - "save": "Salva impostazioni", - "saving": "Salvataggio…", - "settingsSaved": "Impostazioni VPN salvate con successo" - }, - "tickets": { - "title": "Ticket", - "subtitle": "Ticket di supporto dai giocatori", - "noTickets": "Nessun ticket attivo", - "searchPlaceholder": "Cerca oggetto, utente, categoria o ID…", - "presetActive": "Attivi", - "statusOpen": "Aperti", - "statusInProgress": "In corso", - "statusWaiting": "In attesa", - "statusClosed": "Chiusi", - "colTicket": "Ticket", - "colStatus": "Stato", - "colPriority": "Priorità", - "colAssigned": "Assegnato", - "colDate": "Data", - "meta": "di {user} · {count} msg · {category}" - }, - "helpTickets": { - "title": "Ticket centro assistenza", - "subtitle": "Ticket dei giocatori da /help/tickets", - "noTickets": "Nessun ticket aperto nel centro assistenza", - "searchPlaceholder": "Cerca titolo, utente o ID…", - "presetOpen": "Aperti", - "presetAll": "Tutti", - "statusOpen": "Aperti", - "statusClosed": "Chiusi", - "colTicket": "Ticket", - "colStatus": "Stato", - "colUser": "Utente", - "colCreated": "Creato", - "colUpdated": "Aggiornato", - "meta": "di {user} · {count} risposte", - "backToList": "Torna all'elenco", - "threadTitle": "Conversazione", - "staffBadge": "Staff", - "replyPlaceholder": "Rispondi come staff…", - "replySubmit": "Invia risposta", - "replyHint": "Ctrl+Invio per inviare.", - "closeSubmit": "Chiudi ticket", - "reopenSubmit": "Riapri ticket", - "closedHint": "Questo ticket è chiuso.", - "actionsTitle": "Azioni", - "requesterTitle": "Richiedente", - "detailsTitle": "Dettagli", - "rankLabel": "Rank {rank}", - "messageCountLabel": "Messaggi", - "success": { - "replied": "Risposta inviata.", - "closed": "Ticket chiuso.", - "reopened": "Ticket riaperto." - } - }, - "cfh": { - "title": "Call for Help", - "subtitle": "Ticket CFH in-game", - "noTickets": "Nessun ticket CFH", - "searchPlaceholder": "Cerca problema, utente o ID…", - "noDescription": "Nessuna descrizione", - "roomMeta": "Stanza #{room} · {when}", - "stateOpen": "Aperti", - "statePicked": "Presi in carico", - "stateClosed": "Chiusi", - "colId": "ID", - "colIssue": "Problema", - "colSender": "Mittente", - "colReported": "Segnalato", - "colMod": "Mod", - "colStatus": "Stato" - }, - "applications": { - "title": "Candidature staff", - "subtitle": "Candidature allo staff in sospeso e revisionate", - "pending": "In sospeso", - "approved": "Approvate", - "rejected": "Respinte", - "noApplications": "Ancora nessuna candidatura", - "colId": "ID", - "colApplicant": "Candidato", - "colPosition": "Posizione", - "colStatus": "Stato", - "colSubmitted": "Inviata", - "colActions": "Azioni", - "review": "Revisiona", - "approve": "Approva", - "reject": "Respindi", - "confirmApprove": "Approvare {username} per {position}?", - "confirmReject": "Respindere la candidatura di {username} per {position}?", - "detail": { - "title": "Candidatura di {username}", - "subtitle": "Per {position}", - "about": "Informazioni sul candidato", - "message": "Messaggio di candidatura", - "status": "Stato", - "submitted": "Inviata", - "noMessage": "Nessun messaggio fornito" - } - }, - "teams": { - "title": "Team", - "subtitle": "Definisci nomi e descrizioni dei gradi dello staff", - "addRank": "Aggiungi grado", - "editRank": "Modifica grado", - "deleteRank": "Elimina grado", - "noTeams": "Ancora nessuna definizione di grado", - "confirmDelete": "Eliminare la definizione del grado {id}?", - "colId": "ID", - "colRank": "Grado", - "colName": "Nome", - "colBadge": "Distintivo", - "colColour": "Colore", - "colActions": "Azioni", - "form": { - "rankId": "ID grado", - "rankIdPlaceholder": "ID numerico del grado", - "name": "Nome", - "namePlaceholder": "es. Amministratore", - "badge": "Codice distintivo", - "badgePlaceholder": "es. ADM001", - "colour": "Colore", - "colourPlaceholder": "es. #ff0000", - "save": "Salva definizione team", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "permissions": { - "title": "Permessi", - "subtitle": "Gestisci permessi basati sul grado per il pannello admin", - "addPermission": "Aggiungi permesso", - "editPermission": "Modifica permesso", - "deletePermission": "Elimina permesso", - "noPermissions": "Ancora nessun permesso configurato", - "confirmDelete": "Eliminare questo permesso?", - "colId": "ID", - "colRank": "Grado", - "colPermission": "Permesso", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "rankId": "ID grado", - "rankIdPlaceholder": "Grado minimo richiesto", - "permission": "Chiave permesso", - "permissionPlaceholder": "es. admin.articles.manage", - "save": "Salva permesso", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "catalog": { - "title": "Catalogo", - "subtitle": "Sfoglia e gestisci pagine e articoli del catalogo", - "pages": "Pagine", - "items": "Articoli", - "addPage": "Aggiungi pagina", - "editPage": "Modifica pagina", - "deletePage": "Elimina pagina", - "addItem": "Aggiungi articolo", - "editItem": "Modifica articolo", - "deleteItem": "Elimina articolo", - "noPages": "Ancora nessuna pagina catalogo", - "noItems": "Nessun articolo in questa pagina", - "confirmDeletePage": "Eliminare la pagina catalogo \"{name}\"?", - "confirmDeleteItem": "Eliminare questo articolo?", - "colId": "ID", - "colName": "Nome", - "colIcon": "Icona", - "colOrder": "Ordine", - "colItems": "Articoli", - "colEnabled": "Attivo", - "colActions": "Azioni", - "colItemId": "ID articolo", - "colItemName": "Nome articolo", - "colCostCredits": "Costo (crediti)", - "colCostPoints": "Costo (punti)", - "colLimited": "Limitato", - "pageForm": { - "name": "Nome", - "namePlaceholder": "Nome pagina", - "icon": "ID icona", - "iconPlaceholder": "ID numerico icona", - "order": "Ordine", - "enabled": "Attivo", - "parent": "Pagina padre", - "parentNone": "Nessuna (radice)", - "save": "Salva pagina", - "saving": "Salvataggio…", - "cancel": "Annulla" - }, - "itemForm": { - "itemId": "ID articolo", - "itemName": "Nome articolo", - "costCredits": "Costo in crediti", - "costDuckets": "Costo in duckets", - "costDiamonds": "Costo in diamanti", - "limitedStack": "Dimensione lotto limitato", - "limitedSold": "Limitati venduti", - "save": "Salva articolo", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "shop": { - "title": "Negozio", - "subtitle": "Gestisci pacchetti e ordini", - "packages": "Pacchetti", - "orders": "Ordini", - "recentOrders": "Ordini recenti", - "paidOrders": "Ordini pagati", - "ordersHint": "Ultime transazioni PayPal (ultime 25)", - "pageOf": "Pagina {page} di {lastPage}", - "packagesTotal": "{count} pacchetti", - "newPackage": "+ Nuovo pacchetto", - "edit": "Modifica", - "delete": "Elimina", - "viewOrder": "Vedi", - "noPackages": "Ancora nessun pacchetto nel negozio", - "noOrders": "Ancora nessun ordine", - "confirmDelete": "Eliminare questo pacchetto?", - "colId": "ID", - "colName": "Nome", - "colPrice": "Prezzo", - "colCredits": "Crediti", - "colDuckets": "Duckets", - "colDiamonds": "Diamanti", - "colRank": "Grado", - "colPosition": "Posizione", - "colCurrency": "Valuta", - "colType": "Tipo", - "colSold": "Venduti", - "colEnabled": "Attivo", - "colActions": "Azioni", - "colOrderId": "ID ordine", - "colCustomer": "Cliente", - "colPackage": "Pacchetto", - "colTransaction": "Transazione", - "colUser": "Utente", - "colDescription": "Descrizione", - "colAmount": "Importo", - "colStatus": "Stato", - "colDate": "Data", - "statusPending": "In sospeso", - "statusCompleted": "Completato", - "statusRefunded": "Rimborsato", - "form": { - "name": "Nome", - "namePlaceholder": "Nome pacchetto", - "description": "Descrizione", - "descriptionPlaceholder": "Descrizione pacchetto", - "price": "Prezzo", - "currency": "Valuta", - "currencyPlaceholder": "EUR, USD, GBP…", - "type": "Tipo", - "typeCredits": "Crediti", - "typeDuckets": "Duckets", - "typeDiamonds": "Diamanti", - "typeBadge": "Distintivo", - "typeFurni": "Mobile", - "typeRank": "Grado", - "amount": "Importo", - "badgeCode": "Codice distintivo", - "badgeCodePlaceholder": "es. VIP001", - "rankId": "ID grado", - "furniItems": "Mobili", - "enabled": "Attivo", - "save": "Salva pacchetto", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "marketplace": { - "title": "Marketplace", - "subtitle": "Annunci marketplace hotel", - "activeListings": "Annunci attivi", - "soldListings": "Venduti", - "filterActive": "Attivi", - "filterSold": "Venduti", - "empty": "Nessun annuncio marketplace da mostrare.", - "loadError": "Impossibile caricare i dati marketplace (database non disponibile).", - "cancel": "Annulla annuncio", - "colId": "ID", - "colItem": "Item ID", - "colSeller": "Venditore", - "colPrice": "Prezzo", - "colListed": "Pubblicato", - "colSold": "Venduto il", - "colActions": "Azioni" - }, - "transactions": { - "title": "Transazioni", - "subtitle": "Registro pagamenti PayPal", - "noTransactions": "Ancora nessuna transazione", - "totalTransactions": "Transazioni totali", - "pagePaid": "Pagate (questa pagina)", - "pageFailed": "Fallite (questa pagina)", - "searchPlaceholder": "Cerca per utente, ID transazione o pacchetto…", - "presetPaid": "Pagate", - "presetFailed": "Fallite", - "colId": "ID", - "colUser": "Utente", - "colPayer": "Pagatore", - "colAmount": "Importo", - "colCurrency": "Valuta", - "colPackage": "Pacchetto", - "colStatus": "Stato", - "colTransaction": "ID transazione", - "colDate": "Data", - "colActions": "Azioni", - "statusCompleted": "Completato", - "statusPending": "In sospeso", - "statusFailed": "Fallito", - "statusRefunded": "Rimborsato", - "detail": { - "title": "Transazione #{id}", - "paypalId": "ID PayPal", - "payerEmail": "Email pagatore", - "grossAmount": "Importo lordo", - "fee": "Commissione", - "netAmount": "Importo netto" - } - }, - "vouchers": { - "title": "Buoni negozio", - "subtitle": "Gestisci codici buono riscattabili", - "vouchers": "Buoni", - "active": "Attivi", - "usedUp": "Esauriti", - "expired": "Scaduti", - "newVoucher": "+ Nuovo buono", - "edit": "Modifica", - "delete": "Elimina", - "noVouchers": "Ancora nessun buono", - "confirmDeleteTitle": "Elimina buono", - "confirmDelete": "Eliminare il buono \"{code}\"?", - "created": "Buono creato", - "deleted": "Buono eliminato", - "createError": "Impossibile creare il buono", - "deleteError": "Impossibile eliminare il buono", - "statusActive": "Attivo", - "statusUsedUp": "Esaurito", - "statusExpired": "Scaduto", - "colId": "ID", - "colCode": "Codice", - "colValue": "Valore", - "colAmount": "Importo", - "colType": "Tipo", - "colUses": "Usi / Max", - "colStatus": "Stato", - "colExpires": "Scade", - "colCreated": "Creato", - "colActions": "Azioni", - "form": { - "code": "Codice", - "codePlaceholder": "es. ESTATE2025", - "value": "Valore", - "amount": "Crediti", - "amountPlaceholder": "es. 100", - "type": "Tipo", - "typeCredits": "Crediti", - "typeDuckets": "Duckets", - "typeDiamonds": "Diamanti", - "typeBadge": "Distintivo", - "typeFurni": "Mobile", - "typeRank": "Grado", - "badgeCode": "Codice distintivo", - "badgeCodePlaceholder": "es. VIP001", - "rankId": "ID grado", - "maxUses": "Usi massimi", - "maxUsesPlaceholder": "0 = illimitato", - "expiresAt": "Scade il", - "enabled": "Attivo", - "create": "Crea buono", - "save": "Salva buono", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "subscriptions": { - "title": "Abbonamenti", - "subtitle": "Abbonamenti ricorrenti attivi", - "noSubscriptions": "Nessun abbonamento attivo", - "cancel": "Annulla", - "confirmCancel": "Annullare l'abbonamento di {username}?", - "colId": "ID", - "colUser": "Utente", - "colPackage": "Pacchetto", - "colAmount": "Importo", - "colInterval": "Intervallo", - "colStarted": "Iniziato", - "colNextBilling": "Prossima fatturazione", - "colStatus": "Stato", - "colActions": "Azioni", - "statusActive": "Attivo", - "statusCancelled": "Annullato", - "statusExpired": "Scaduto" - }, - "rareValues": { - "title": "Valori rari", - "subtitle": "Gestisci categorie di valori rari e prezzi articoli", - "categories": "Categorie", - "items": "Articoli", - "addCategory": "Aggiungi categoria", - "editCategory": "Modifica categoria", - "deleteCategory": "Elimina categoria", - "noCategories": "Ancora nessuna categoria di valori rari", - "confirmDeleteCategory": "Eliminare la categoria \"{name}\" e tutti i suoi articoli?", - "colId": "ID", - "colName": "Nome", - "colItems": "Articoli", - "colUpdated": "Aggiornato", - "colActions": "Azioni", - "colItemName": "Nome articolo", - "colCredits": "Crediti", - "colPoints": "Punti", - "colPointType": "Tipo punti", - "categoryForm": { - "name": "Nome", - "namePlaceholder": "Nome categoria", - "save": "Salva categoria", - "saving": "Salvataggio…", - "cancel": "Annulla" - }, - "itemForm": { - "itemName": "Nome articolo", - "itemNamePlaceholder": "es. Trono", - "credits": "Valore in crediti", - "points": "Valore in punti", - "pointType": "Tipo punti", - "pointTypeDuckets": "Duckets", - "pointTypeDiamonds": "Diamanti", - "save": "Salva articolo", - "saving": "Salvataggio…", - "cancel": "Annulla" - } - }, - "badges": { - "title": "Distintivi", - "subtitle": "Carica, assegna e gestisci il catalogo distintivi", - "upload": "Carica", - "giveBadge": "Assegna distintivo", - "catalogue": "Catalogo", - "uploadTitle": "Carica un nuovo distintivo", - "uploadSubtitle": "Carica un file immagine del distintivo", - "badgeCode": "Codice distintivo", - "badgeCodePlaceholder": "es. VIP001", - "badgeFile": "File distintivo", - "badgeFileHint": "PNG o GIF, consigliato 40x40", - "uploadButton": "Carica", - "uploading": "Caricamento…", - "giveTitle": "Assegna distintivo a utente", - "giveSubtitle": "Concedi un distintivo a un utente specifico", - "username": "Nome utente", - "usernamePlaceholder": "Nome utente destinatario", - "giveButton": "Assegna distintivo", - "giving": "Assegnazione…", - "catalogueTitle": "Catalogo distintivi", - "catalogueSubtitle": "Tutti i distintivi disponibili nell'hotel", - "noBadges": "Nessun distintivo nel catalogo", - "search": "Cerca distintivi", - "searchPlaceholder": "Cerca per codice o nome…", - "colCode": "Codice", - "colImage": "Immagine", - "colName": "Nome", - "colDescription": "Descrizione", - "colActions": "Azioni" - }, - "achievements": { - "title": "Trofei", - "subtitle": "Elenco di sola lettura dei trofei di gioco", - "noAchievements": "Nessun trofeo configurato", - "colId": "ID", - "colName": "Nome", - "colCategory": "Categoria", - "colLevels": "Livelli", - "colReward": "Ricompensa", - "colProgress": "Progresso", - "levelLabel": "Livello {level}", - "rewardCredits": "{count} crediti", - "rewardPoints": "{count} {points}" - }, - "calendar": { - "title": "Campagne calendario", - "subtitle": "Elenco di sola lettura delle campagne ed eventi del calendario", - "noCampaigns": "Ancora nessuna campagna calendario", - "colId": "ID", - "colName": "Nome", - "colStart": "Inizio", - "colEnd": "Fine", - "colType": "Tipo", - "colReward": "Ricompensa", - "colActions": "Azioni", - "detail": { - "title": "Campagna: {name}", - "subtitle": "Dettagli evento calendario", - "days": "Giorni", - "dayLabel": "Giorno {day}", - "reward": "Ricompensa", - "noDays": "Nessun giorno configurato per questa campagna" - } - }, - "radio": { - "title": "Moderazione radio", - "subtitle": "Latest shouts, DJ applications and on-air schedules (25 most recent of each).", - "moderation": "Moderation", - "shouts": "Saluti", - "applications": "Candidature", - "schedules": "Programmazioni", - "settings": "Impostazioni", - "embed": "Incorpora", - "ranks": "Gradi", - "history": "Cronologia", - "apiKeys": "Chiavi API", - "points": "Punti", - "autoDj": "AutoDJ", - "monitoring": { - "title": "Monitoraggio radio", - "subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout — no data is written. Configure URLs under Settings.", - "stream": "Stream", - "online": "Online", - "offline": "Offline", - "notConfigured": "Not configured", - "nowPlaying": "Now playing", - "nowPlayingApi": "Now-playing API", - "listenersApi": "Listeners API", - "listeners": "Ascoltatori", - "currentSong": "Brano in corso", - "bitrate": "Bitrate", - "status": "Stato", - "noNowPlayingHint": "no now-playing API set", - "noListenersHint": "no listeners API set", - "endpointDiagnostics": "Endpoint diagnostics", - "diagnosticsLegend": "OK reachable · WARN not configured · ERROR fetch failed / timed out.", - "streamUrl": "Stream URL", - "unreachable": "unreachable", - "refresh": "Aggiorna" - }, - "user": "User", - "message": "Message", - "posted": "Posted", - "realName": "Real Name", - "age": "Age", - "style": "Style", - "applied": "Applied", - "day": "Day", - "start": "Start", - "end": "End", - "show": "Show", - "status": "Stato", - "active": "active", - "inactive": "inactive", - "hidden": "hidden", - "activeSchedules": "Active schedules", - "noShouts": "Nessun saluto da moderare", - "noApplications": "No applications submitted.", - "noSchedules": "No schedules configured.", - "deleteShout": "Elimina", - "confirmDeleteShout": "Eliminare questo saluto?", - "pendingApplications": "Candidature in sospeso", - "settingsForm": { - "title": "Radio Settings", - "subtitle": "Edits write to website_settings (radio_* keys) and bust the runtime settings cache. Booleans use 0 / 1.", - "enabled": "Enabled", - "disabled": "Disabled", - "totalKeys": "Total keys", - "configured": "Configured", - "curatedHint": "curated keys with a value", - "customKeys": "Custom keys", - "saveAll": "Save all", - "singleKey": "Add / overwrite a single key", - "keyPlaceholder": "radio_custom_key", - "valuePlaceholder": "value", - "commentPlaceholder": "comment (optional)", - "saveSingle": "Save", - "otherSettings": "Other radio settings", - "dbError": "Could not load settings (database unavailable). Defaults will be created on save." - }, - "embedPage": { - "title": "Radio Embed", - "subtitle": "Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).", - "streamUrl": "Stream URL", - "configured": "Configured", - "missing": "Missing", - "radioName": "Radio name", - "nowPlayingApi": "Now-playing API", - "set": "Set", - "notSet": "Not set", - "noStreamUrl": "No stream URL configured", - "noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.", - "goToSettings": "Go to Radio Settings", - "livePreview": "Live preview", - "livePreviewText": "This is the bare