feat: public events/polls, friends graph, captcha, SSE hardening, and admin UX
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 2m55s

Ship product gaps: register/vote pages, friend add/accept/decline/remove, email verify TTL, captcha on login/forgot, soft-fail user actions, SSE abort/shared client, Commando Centrum error toasts, admin delete for events/polls, and IT/NL i18n fills.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 21:08:33 +02:00
1 parent 2ff08e5127
commit ed7db6e048
76 files changed
+4834 -1376

No files matched your search

+174 -23
View File
@@ -7,6 +7,8 @@
"staff": "Staff",
"rankings": "Classifiche",
"guilds": "Gruppi",
"events": "Eventi",
"polls": "Sondaggi",
"leaderboards": "Classifiche",
"rareValues": "Valori rari",
"shop": "Negozio",
@@ -39,6 +41,15 @@
"lightMode": "Tema chiaro",
"darkMode": "Tema scuro"
},
"emails": {
"verify": {
"subject": "Verifica la tua email · {hotel}",
"heading": "Verifica la tua email",
"body": "Benvenuto su {hotel}! Conferma questo indirizzo email per completare la creazione del tuo account.",
"button": "Verifica email",
"fallback": "Se il pulsante non funziona, incolla questo link nel browser:"
}
},
"pages": {
"error": {
"code": "Errore",
@@ -88,7 +99,16 @@
"emptySubtitle": "Non hai ancora aggiunto nessun amico",
"subtitle": "{count, plural, one {Hai # amico} other {Hai # amici}}",
"emptyState": "Aggiungi amici nell'hotel e compariranno qui.",
"noMotto": "Nessun motto"
"noMotto": "Nessun motto",
"remove": "Rimuovi",
"success": {
"removed": "Amico rimosso."
},
"errors": {
"notFound": "Quell'amicizia non è stata trovata.",
"invalid": "Amico non valido.",
"error": "Qualcosa è andato storto. Riprova."
}
},
"messages": {
"title": "Messaggi",
@@ -99,11 +119,22 @@
"requestsEmptyState": "Nessuna richiesta di amicizia in sospeso.",
"wantsToBeFriend": "vuole essere tuo amico.",
"accept": "Accetta",
"decline": "Rifiuta",
"offlineTitle": "Messaggi offline",
"offlineEmptySubtitle": "La tua casella è vuota",
"offlineSubtitle": "{count, plural, one {# messaggio ricevuto} other {# messaggi ricevuti}}",
"offlineEmptyState": "Non hai messaggi offline.",
"unknownUser": "Utente #{id}"
"unknownUser": "Utente #{id}",
"success": {
"accepted": "Richiesta di amicizia accettata.",
"declined": "Richiesta di amicizia rifiutata."
},
"errors": {
"notFound": "Quella richiesta di amicizia non è stata trovata.",
"unauthorized": "Non puoi gestire questa richiesta di amicizia.",
"invalid": "Richiesta di amicizia non valida.",
"error": "Qualcosa è andato storto. Riprova."
}
},
"profile": {
"noMotto": "Nessun motto",
@@ -131,7 +162,26 @@
"roomsTitle": "Stanze",
"roomsSubtitle": "Le stanze che possiede",
"roomsEmpty": "Nessuna stanza da mostrare.",
"roomUntitled": "Stanza senza nome"
"roomUntitled": "Stanza senza nome",
"addFriend": "Aggiungi amico",
"alreadyFriends": "Siete amici",
"requestPending": "Richiesta in sospeso",
"respondInMessages": "Rispondi nei Messaggi",
"success": {
"sent": "Richiesta di amicizia inviata.",
"guestbook": "Messaggio in bacheca pubblicato."
},
"errors": {
"self": "Non puoi inviarti una richiesta di amicizia.",
"alreadyFriends": "Siete già amici.",
"alreadyPending": "Hai già inviato una richiesta a questo utente.",
"incomingPending": "Questo utente ti ha già inviato una richiesta — accettala o rifiutala nei Messaggi.",
"empty": "Scrivi un messaggio prima di pubblicare.",
"moderated": "Quel messaggio è stato bloccato dalla moderazione.",
"ratelimit": "Stai pubblicando troppo in fretta. Attendi un momento e riprova.",
"invalid": "Richiesta non valida.",
"error": "Qualcosa è andato storto. Riprova."
}
},
"rankings": {
"title": "Classifica",
@@ -185,7 +235,18 @@
"addComment": "Aggiungi un commento",
"commentPlaceholder": "Scrivi il tuo commento…",
"postComment": "Pubblica commento",
"toComment": "per lasciare un commento."
"toComment": "per lasciare un commento.",
"success": {
"posted": "Commento pubblicato."
},
"errors": {
"empty": "Scrivi un commento prima di pubblicare.",
"moderated": "Quel commento è stato bloccato dalla moderazione.",
"ratelimit": "Stai commentando troppo in fretta. Attendi un momento e riprova.",
"invalid": "Commento non valido.",
"notFound": "Articolo non trovato.",
"error": "Qualcosa è andato storto. Riprova."
}
},
"photos": {
"title": "Foto",
@@ -269,7 +330,10 @@
"pinned": "In evidenza",
"locked": "Bloccata",
"userNumber": "Utente #{id}",
"unknown": "Sconosciuto"
"unknown": "Sconosciuto",
"success": {
"posted": "Discussione pubblicata."
}
},
"guildForumNew": {
"backToForum": "← Torna al forum",
@@ -281,7 +345,13 @@
"messageLabel": "Messaggio",
"messagePlaceholder": "Scrivi il tuo primo messaggio…",
"postThread": "Pubblica discussione",
"cancel": "Annulla"
"cancel": "Annulla",
"errors": {
"invalid": "Oggetto e messaggio sono obbligatori.",
"notFound": "Gilda non trovata.",
"ratelimit": "Stai pubblicando troppo in fretta. Attendi un momento e riprova.",
"error": "Qualcosa è andato storto. Riprova."
}
},
"shop": {
"title": "Negozio",
@@ -395,7 +465,16 @@
"colStatus": "Stato",
"colCreated": "Creato",
"statusOpen": "Aperto",
"statusClosed": "Chiuso"
"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."
}
},
"applyStaff": {
"title": "Candidati per lo staff di {hotel}",
@@ -472,7 +551,68 @@
"loginToPost": "Accedi per pubblicare un saluto.",
"latestTitle": "Ultimi saluti",
"latestSubtitle": "I messaggi più recenti degli ascoltatori",
"empty": "Ancora nessun saluto. Sii il primo!"
"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",
@@ -591,7 +731,9 @@
"createOne": "Creane uno",
"forgotPassword": "Password dimenticata?",
"errorInvalidCredentials": "Nome utente o password non validi",
"errorInvalid2fa": "Codice 2FA non valido"
"errorInvalid2fa": "Codice 2FA non valido",
"errorUnverified": "Verifica la tua email prima di accedere.",
"errorCaptcha": "Verifica captcha non riuscita. Riprova."
},
"register": {
"title": "Crea un account",
@@ -627,7 +769,8 @@
"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"
"backToLogin": "Torna all'accesso",
"errorCaptcha": "Verifica captcha non riuscita. Riprova."
},
"reset": {
"title": "Imposta una nuova password",
@@ -2471,19 +2614,27 @@
},
"import": {
"title": "Import",
"subtitle": "Import assets from external sources into your hotel.",
"badges": "Badges",
"badgesDescription": "Import and manage badge assets from external sources.",
"furni": "Furni",
"furniDescription": "Import furniture definitions, SWF files, and icons.",
"clothing": "Clothing",
"clothingDescription": "Import clothing and wearables for avatars.",
"effects": "Effects",
"effectsDescription": "Import avatar effects and animations.",
"pets": "Pets",
"petsDescription": "Import pet data and icons.",
"clone": "Clone",
"cloneDescription": "Clone existing items from one source to another."
"subtitle": "Importa asset da fonti esterne nel tuo hotel.",
"badges": "Distintivi",
"badgesDescription": "Importa e gestisci asset dei distintivi da fonti esterne.",
"furni": "Mobili",
"furniDescription": "Importa definizioni mobili, file SWF e icone.",
"clothing": "Abbigliamento",
"clothingDescription": "Importa abiti e indossabili per gli avatar.",
"effects": "Effetti",
"effectsDescription": "Importa effetti e animazioni avatar.",
"pets": "Animali",
"petsDescription": "Importa dati e icone degli animali.",
"clone": "Clona",
"cloneDescription": "Clona elementi esistenti da una fonte all'altra.",
"upload": "Carica .nitro",
"uploadDescription": "Carica bundle .nitro e icone direttamente, senza conversione SWF.",
"sync": "Sincronizzazione automatica",
"syncDescription": "Importa automaticamente i mobili mancanti da tutte le fonti configurate.",
"repairIcons": "Ripara icone",
"repairIconsDescription": "Scarica le icone mancanti per i mobili già presenti nel database.",
"audit": "Audit catalogo",
"auditDescription": "Scansiona il catalogo per voci mancanti, riferimenti rotti ed elementi assenti dalle fonti clone."
},
"translations": {
"title": "Translations",