diff --git a/src/actions/translations.ts b/src/actions/translations.ts
index 3a32e91000..53c7ce51f7 100644
--- a/src/actions/translations.ts
+++ b/src/actions/translations.ts
@@ -10,7 +10,7 @@ import { adminAction } from "@/lib/safe-action";
import { ActionError, actionOk } from "@/lib/safe-action-shared";
const saveTranslationsSchema = z.object({
- locale: z.enum(["en", "it", "nl", "de", "fr", "es"]),
+ locale: z.enum(["en", "it", "nl", "de", "fr", "es", "pt", "pl", "sv"]),
data: z.record(z.string(), z.unknown()),
});
diff --git a/src/app/admin/translations/cms/page.tsx b/src/app/admin/translations/cms/page.tsx
index 101d30fbf0..f24d86ba3f 100644
--- a/src/app/admin/translations/cms/page.tsx
+++ b/src/app/admin/translations/cms/page.tsx
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { TranslationsClient } from "../translations-client";
-const LOCALES = ["en", "it", "nl", "de", "fr", "es"] as const;
+const LOCALES = ["en", "it", "nl", "de", "fr", "es", "pt", "pl", "sv"] as const;
export default async function CmsTranslationsPage() {
const messagesDir = path.join(process.cwd(), "messages");
diff --git a/src/app/admin/translations/translations-client.tsx b/src/app/admin/translations/translations-client.tsx
index fe36a5c993..cadd5777f0 100644
--- a/src/app/admin/translations/translations-client.tsx
+++ b/src/app/admin/translations/translations-client.tsx
@@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input";
import { useServerAction } from "@/hooks/use-server-action";
import { flattenObject, unflattenObject } from "@/lib/translations-utils";
-type Locale = "en" | "it" | "nl" | "de" | "fr" | "es";
+type Locale = "en" | "it" | "nl" | "de" | "fr" | "es" | "pt" | "pl" | "sv";
const LOCALES: { code: Locale; label: string }[] = [
{ code: "en", label: "EN" },
@@ -17,6 +17,9 @@ const LOCALES: { code: Locale; label: string }[] = [
{ code: "de", label: "DE" },
{ code: "fr", label: "FR" },
{ code: "es", label: "ES" },
+ { code: "pt", label: "PT" },
+ { code: "pl", label: "PL" },
+ { code: "sv", label: "SV" },
];
interface Props {
diff --git a/src/components/language-switcher.tsx b/src/components/language-switcher.tsx
index a5c67f8f21..0eb2a8615c 100644
--- a/src/components/language-switcher.tsx
+++ b/src/components/language-switcher.tsx
@@ -12,6 +12,9 @@ const LOCALES: { code: string; label: string; lang: string }[] = [
{ code: "fr", label: "Français", lang: "fr" },
{ code: "es", label: "Español", lang: "es" },
{ code: "it", label: "Italiano", lang: "it" },
+ { code: "pt", label: "Português", lang: "pt" },
+ { code: "pl", label: "Polski", lang: "pl" },
+ { code: "sv", label: "Svenska", lang: "sv" },
];
export function LanguageSwitcher() {
diff --git a/src/i18n/request.ts b/src/i18n/request.ts
index 66ff478354..9fb2309cba 100644
--- a/src/i18n/request.ts
+++ b/src/i18n/request.ts
@@ -4,7 +4,7 @@ import { getRequestConfig } from "next-intl/server";
// i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie (set by
// the language switcher) rather than a URL segment, so every existing route and
// the access-guard middleware keep working unchanged. English is the fallback.
-export const SUPPORTED_LOCALES = ["en", "it", "nl", "de", "fr", "es"] as const;
+export const SUPPORTED_LOCALES = ["en", "it", "nl", "de", "fr", "es", "pt", "pl", "sv"] as const;
export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
export const DEFAULT_LOCALE: AppLocale = "en";
diff --git a/src/messages/pl.json b/src/messages/pl.json
new file mode 100644
index 0000000000..7448d6040c
--- /dev/null
+++ b/src/messages/pl.json
@@ -0,0 +1,2034 @@
+{
+ "nav": {
+ "home": "Home",
+ "community": "Community",
+ "articles": "Articles",
+ "photos": "Photos",
+ "staff": "Staff",
+ "rankings": "Rankings",
+ "guilds": "Guilds",
+ "leaderboards": "Leaderboards",
+ "rareValues": "Rare values",
+ "shop": "Shop",
+ "market": "Market",
+ "assistance": "Assistance",
+ "helpCenter": "Help center",
+ "myTickets": "My tickets",
+ "badges": "Badges",
+ "radio": "Radio",
+ "friends": "Friends",
+ "messages": "Messages",
+ "admin": "Admin",
+ "openMenu": "Open menu",
+ "closeMenu": "Close menu",
+ "drawBadge": "Draw badge"
+ },
+ "header": {
+ "online": "{count} {hotel} online",
+ "nitroClient": "Nitro client",
+ "tagline": "An online virtual world where you can create your own avatar, make friends, chat, create rooms and much more!",
+ "login": "Login",
+ "or": "Or",
+ "createAccount": "Create an account"
+ },
+ "footer": {
+ "copyright": "© {year} - {hotel} is a not for profit educational project"
+ },
+ "common": {
+ "language": "Language",
+ "lightMode": "Light mode",
+ "darkMode": "Dark mode"
+ },
+ "pages": {
+ "staff": {
+ "title": "Staff",
+ "subtitle": "The team behind the hotel",
+ "ranksTitle": "Ranks",
+ "ranksSubtitle": "What each part of the team takes care of",
+ "membersTitle": "Team members",
+ "membersEmptySubtitle": "Nobody to show right now",
+ "membersSubtitle": "{count, plural, one {# member keeping things running} other {# members keeping things running}}",
+ "emptyState": "No staff members to show.",
+ "colUsername": "Username",
+ "colRank": "Rank",
+ "colMotto": "Motto"
+ },
+ "leaderboard": {
+ "title": "Leaderboard",
+ "subtitle": "Top 20 players by {currency}",
+ "tab_credits": "Credits",
+ "tab_diamonds": "Diamonds",
+ "tab_duckets": "Duckets",
+ "tab_achievement": "Achievements",
+ "tab_respects": "Respects",
+ "tab_onlinetime": "Online Time",
+ "emptyState": "No rankings to show yet.",
+ "colAvatar": "Avatar",
+ "colPlayer": "Player"
+ },
+ "friends": {
+ "title": "Friends",
+ "emptySubtitle": "You have no friends added yet",
+ "subtitle": "{count, plural, one {You have # friend} other {You have # friends}}",
+ "emptyState": "Add friends in the hotel and they will show up here.",
+ "noMotto": "No motto"
+ },
+ "messages": {
+ "title": "Messages",
+ "subtitle": "Friend requests and offline messages waiting for you",
+ "requestsTitle": "Friend requests",
+ "requestsEmptySubtitle": "Nobody's asked to be your friend yet",
+ "requestsSubtitle": "{count, plural, one {# pending request} other {# pending requests}}",
+ "requestsEmptyState": "No pending friend requests.",
+ "wantsToBeFriend": "wants to be your friend.",
+ "accept": "Accept",
+ "offlineTitle": "Offline messages",
+ "offlineEmptySubtitle": "Your inbox is empty",
+ "offlineSubtitle": "{count, plural, one {# message received} other {# messages received}}",
+ "offlineEmptyState": "You have no offline messages.",
+ "unknownUser": "User #{id}"
+ },
+ "profile": {
+ "noMotto": "No motto",
+ "statRank": "Rank",
+ "statCredits": "Credits",
+ "statRegistered": "Registered",
+ "badgesTitle": "Badges",
+ "badgesEmpty": "No badges yet.",
+ "photosTitle": "Photos",
+ "photosEmpty": "No photos yet.",
+ "guestbookTitle": "Guestbook",
+ "guestbookSubtitle": "Messages on {username}'s wall",
+ "leaveMessage": "Leave a message",
+ "guestbookPlaceholder": "Write something on {username}'s guestbook…",
+ "post": "Post",
+ "loginToPost": "Log in to leave a guestbook message.",
+ "guestbookEmpty": "No guestbook entries yet.",
+ "unknownUser": "User #{id}",
+ "statDuckets": "Duckets",
+ "statDiamonds": "Diamonds",
+ "walletTitle": "Wallet",
+ "friendsTitle": "Friends",
+ "friendsSubtitle": "People in the hotel they hang out with",
+ "friendsEmpty": "No friends to show.",
+ "roomsTitle": "Rooms",
+ "roomsSubtitle": "Rooms they own",
+ "roomsEmpty": "No rooms to show.",
+ "roomUntitled": "Untitled room"
+ },
+ "rankings": {
+ "title": "Rankings",
+ "subtitle": "The top 12 players ranked by credits",
+ "emptyState": "No players to rank yet."
+ },
+ "home": {
+ "welcome": "Welcome to {hotelName}",
+ "welcomeSubtitle": "Your retro hotel, now powered by Next.js",
+ "welcomeBody": "Hang out, collect furni, and meet friends.",
+ "enterHotel": "Enter {hotelName}",
+ "onlineFriends": "Online Friends",
+ "noFriendsOnline": "No friends online",
+ "register": "REGISTER NOW",
+ "joinNow": "Join {hotelName} today!",
+ "recentUsers": "Recent Users",
+ "recentPhotos": "Recent Photos",
+ "latestNews": "Latest news",
+ "newsTitle": "Latest news",
+ "newsSubtitle": "Fresh from the hotel",
+ "allNews": "All news →",
+ "noArticles": "No articles yet.",
+ "noPhotos": "No photos yet.",
+ "noUsers": "No users online",
+ "latestArticle": "Latest Article"
+ },
+ "news": {
+ "title": "News",
+ "subtitle": "The latest announcements, events and updates from the hotel",
+ "noArticles": "No articles yet."
+ },
+ "article": {
+ "backToNews": "← News",
+ "reactionsTitle": "Reactions",
+ "reactionsSubtitle": "Tell us what you think",
+ "reaction": {
+ "like": "Like",
+ "love": "Love",
+ "wow": "Wow"
+ },
+ "removeReaction": "Click to remove your reaction",
+ "reactWith": "React with {reaction}",
+ "noReactions": "No reactions yet.",
+ "logIn": "Log in",
+ "toReact": "to react.",
+ "commentsTitle": "Comments",
+ "commentsSubtitle": "Join the conversation",
+ "commentsCount": "{count} so far",
+ "noComments": "No comments yet. Be the first to comment.",
+ "userNumber": "User #{id}",
+ "addComment": "Add a comment",
+ "commentPlaceholder": "Write your comment…",
+ "postComment": "Post comment",
+ "toComment": "to leave a comment."
+ },
+ "photos": {
+ "title": "Photos",
+ "subtitle": "The latest snapshots taken in the hotel",
+ "noPhotos": "No photos yet.",
+ "photoAlt": "Photo by user #{id}",
+ "byUser": "by user #{id}"
+ },
+ "badges": {
+ "title": "Badges",
+ "subtitle": "The badges you can earn and show off around the hotel",
+ "noBadges": "No badges available yet."
+ },
+ "community": {
+ "title": "Community",
+ "subtitle": "Everything happening around the hotel in one place",
+ "open": "Open →",
+ "links": {
+ "rankings": {
+ "title": "Rankings",
+ "text": "See the wealthiest players in the hotel, ranked by credits."
+ },
+ "staff": {
+ "title": "Staff",
+ "text": "Meet the team that keeps the hotel running every day."
+ },
+ "news": {
+ "title": "News",
+ "text": "The latest announcements, events and updates."
+ },
+ "photos": {
+ "title": "Photos",
+ "text": "Snapshots shared by the community from around the hotel."
+ },
+ "guilds": {
+ "title": "Guilds",
+ "text": "Browse the groups players have created and joined."
+ },
+ "leaderboards": {
+ "title": "Leaderboards",
+ "text": "Who's on top this season across the hotel's stats."
+ }
+ }
+ },
+ "guilds": {
+ "title": "Guilds",
+ "subtitle": "Browse the groups players have created and joined",
+ "empty": "No guilds yet.",
+ "unnamedGuild": "Unnamed guild",
+ "noDescription": "No description"
+ },
+ "guild": {
+ "allGuilds": "← All guilds",
+ "unnamedGuild": "Unnamed guild",
+ "noDescription": "No description",
+ "memberSingular": "Member",
+ "memberPlural": "Members",
+ "founded": "Founded",
+ "forumLink": "Forum →",
+ "discussion": "Discussion",
+ "membersTitle": "Members",
+ "memberLowerSingular": "member",
+ "memberLowerPlural": "members",
+ "noMembers": "No members.",
+ "owner": "Owner",
+ "noMotto": "No motto"
+ },
+ "guildForum": {
+ "backToGuild": "← Back to guild",
+ "unnamedGuild": "Unnamed guild",
+ "forumTitle": "{guild} — Forum",
+ "threadSingular": "thread",
+ "threadPlural": "threads",
+ "newThread": "+ New thread",
+ "empty": "No threads in this forum yet.",
+ "colSubject": "Subject",
+ "colAuthor": "Author",
+ "colPosts": "Posts",
+ "colDate": "Date",
+ "noSubject": "(no subject)",
+ "pinned": "Pinned",
+ "locked": "Locked",
+ "userNumber": "User #{id}",
+ "unknown": "Unknown"
+ },
+ "guildForumNew": {
+ "backToForum": "← Back to forum",
+ "unnamedGuild": "Unnamed guild",
+ "title": "New thread",
+ "subtitle": "in {guild} forum",
+ "subjectLabel": "Subject",
+ "subjectPlaceholder": "Thread subject",
+ "messageLabel": "Message",
+ "messagePlaceholder": "Write your opening post…",
+ "postThread": "Post thread",
+ "cancel": "Cancel"
+ },
+ "shop": {
+ "title": "Shop",
+ "subtitle": "Top up your account and grab exclusive packages, currency and badges.",
+ "browsingCategory": "Browsing the {category} packages.",
+ "all": "All",
+ "allPackages": "All packages",
+ "packagesAvailable": "{count, plural, one {# package available} other {# packages available}}",
+ "empty": "No packages available right now.",
+ "buy": "Buy"
+ },
+ "topup": {
+ "title": "Top up with PayPal",
+ "subtitle": "Pay securely with PayPal and your credits are added the moment the payment clears.",
+ "cancelled": "Checkout was cancelled — no payment was taken.",
+ "chooseAmount": "Choose an amount",
+ "notConfiguredPre": "PayPal isn't configured yet. An administrator needs to set",
+ "notConfiguredAnd": "and",
+ "notConfiguredOptional": "(and optionally",
+ "notConfiguredPost": "for sandbox vs. live).",
+ "balanceTitle": "Your balance",
+ "signedInAs": "Signed in as {username}",
+ "credits": "Credits",
+ "creditsPerUnit": "Credits per 1.00 {currency}",
+ "rateLine": "You receive {amount} credits per 1.00 {currency}. Credits appear in-game the next time you log in."
+ },
+ "marketplace": {
+ "title": "Marketplace",
+ "subtitle": "Browse furniture and rares listed by players. Listings are placed in-game and update live — head into the hotel to make an offer.",
+ "activeOffers": "Active offers",
+ "sellers": "Sellers",
+ "totalValue": "Total value",
+ "activeListings": "Active listings",
+ "mostRecentFirst": "Most recently listed first",
+ "empty": "There are no active marketplace offers right now. Check back later.",
+ "colOffer": "Offer",
+ "colItem": "Item",
+ "colSeller": "Seller",
+ "colPrice": "Price",
+ "colListed": "Listed",
+ "itemLabel": "Item #{id}",
+ "userLabel": "User #{id}",
+ "justNow": "just now",
+ "minutesAgo": "{count}m ago",
+ "hoursAgo": "{count}h ago",
+ "daysAgo": "{count}d ago"
+ },
+ "rares": {
+ "title": "Rare values",
+ "subtitle": "Get an overview of all of the rares on the hotel, grouped by category.",
+ "empty": "We currently have no rares listed here.",
+ "rareCount": "{count, plural, one {# rare} other {# rares}}",
+ "viewRares": "View rares →"
+ },
+ "rareCategory": {
+ "rareCount": "{count, plural, one {# rare in this category} other {# rares in this category}}",
+ "allRares": "← All rares",
+ "empty": "No rares listed in this category yet.",
+ "colRare": "Rare",
+ "colCredits": "Credits",
+ "colCurrency": "Currency",
+ "creditsValue": "{value} credits",
+ "duckets": "Duckets",
+ "diamonds": "Diamonds",
+ "credits": "Credits"
+ },
+ "redeem": {
+ "title": "Redeem a voucher",
+ "subtitle": "Got a voucher code? Enter it below to add the reward to your account.",
+ "enterCode": "Enter your code",
+ "oncePerAccount": "Each voucher can only be redeemed once per account.",
+ "balanceTitle": "Your balance",
+ "signedInAs": "Signed in as {username}",
+ "credits": "Credits",
+ "instantCredit": "Redeemed rewards are credited instantly and appear in-game the next time you log in."
+ },
+ "help": {
+ "title": "Help Center",
+ "subtitle": "Find guides, support topics, and the hotel rules — everything you need to get help.",
+ "myTickets": "My tickets →",
+ "topicsTitle": "Support topics",
+ "topicsSubtitle": "Browse guides and answers to common questions",
+ "topicsEmpty": "No help topics yet.",
+ "rulesTitle": "Hotel rules",
+ "rulesSubtitle": "Please read these before hanging out in the hotel",
+ "rulesEmpty": "No rules published yet.",
+ "categoryEmpty": "No rules in this category."
+ },
+ "helpCategory": {
+ "back": "← Help Center",
+ "topicSubtitle": "Help Center topic",
+ "moreTitle": "More help topics",
+ "moreSubtitle": "Jump to another support topic"
+ },
+ "helpTickets": {
+ "title": "Help Tickets",
+ "subtitle": "Submit a ticket to staff and track your open requests.",
+ "statTotal": "Total tickets",
+ "statOpen": "Open",
+ "statClosed": "Closed",
+ "newTitle": "New ticket",
+ "newSubtitle": "Describe your issue and staff will follow up",
+ "subjectPlaceholder": "Subject",
+ "contentPlaceholder": "Describe your issue…",
+ "submit": "Submit ticket",
+ "listTitle": "Your tickets",
+ "listSubtitle": "Every request you have sent to staff",
+ "listEmpty": "You have no tickets yet.",
+ "colId": "ID",
+ "colSubject": "Subject",
+ "colStatus": "Status",
+ "colCreated": "Created",
+ "statusOpen": "Open",
+ "statusClosed": "Closed"
+ },
+ "applyStaff": {
+ "title": "Apply for {hotel} staff",
+ "subtitle": "We open staff applications every now and then. If you come across a position you feel you would fit perfectly into, do not hesitate to apply for it.",
+ "noPositionsTitle": "No positions open",
+ "noPositionsBody": "There are currently no positions open. Please come back at a later time to check if we have any openings by then. Thank you for your interest.",
+ "noDeadline": "No deadline set",
+ "deadline": "Application deadline: {date}",
+ "alreadyApplied": "You have already applied for {rank}",
+ "aboutYou": "About you",
+ "aboutPlaceholder": "Tell us why you'd be a great {rank}…",
+ "applyFor": "Apply for {rank}"
+ },
+ "applyTeam": {
+ "title": "Apply for the {hotel} team",
+ "subtitle": "We open team applications periodically. If you see a team you fit, do not hesitate to apply!",
+ "noPositionsTitle": "No team positions open",
+ "noPositionsBody": "There are currently no open team positions. Please come back later to check for new openings. Thank you!",
+ "pending": "Your application is pending",
+ "aboutYou": "About you",
+ "aboutPlaceholder": "Tell us why you'd be a great fit for {rank}…",
+ "applyFor": "Apply for {rank}"
+ },
+ "radio": {
+ "title": "Radio",
+ "subtitle": "Tune in to the hotel radio — live DJ sets, requests and shoutouts.",
+ "nowPlaying": "Now playing",
+ "nothingPlaying": "Nothing playing right now.",
+ "onAir": "On air: {dj}",
+ "noDj": "No DJ on air.",
+ "audioUnsupported": "Your browser does not support the audio element.",
+ "streamOffline": "The stream is currently offline.",
+ "exploreTitle": "Explore the radio",
+ "exploreSubtitle": "Schedule, shouts, requests and more",
+ "linkSchedule": "Schedule",
+ "linkScheduleText": "Our weekly line-up of DJs and shows.",
+ "linkShouts": "Shouts",
+ "linkShoutsText": "Shout-outs and requests from the community.",
+ "linkRequests": "Requests",
+ "linkRequestsText": "Ask the DJ to spin your favourite track.",
+ "linkApply": "Apply as DJ",
+ "linkApplyText": "Join the team and take to the airwaves.",
+ "linkLeaderboard": "Leaderboard",
+ "linkLeaderboardText": "Top listeners ranked by points earned.",
+ "todayTitle": "Today's schedule",
+ "todayTitleWithDay": "Today's schedule · {day}",
+ "todaySubtitle": "What's on the airwaves today",
+ "fullWeek": "Full week →",
+ "noShowsToday": "No shows scheduled for today.",
+ "colTime": "Time",
+ "colShow": "Show",
+ "colDj": "DJ",
+ "untitledShow": "Untitled show",
+ "userNumber": "User #{id}"
+ },
+ "radioSchedule": {
+ "title": "Radio Schedule",
+ "subtitle": "Our weekly line-up of DJs and shows",
+ "empty": "No shows scheduled yet.",
+ "colDay": "Day",
+ "colTime": "Time",
+ "colDj": "DJ",
+ "colShow": "Show",
+ "noShows": "No shows"
+ },
+ "radioShouts": {
+ "title": "Radio Shouts",
+ "subtitle": "Shout-outs and requests from the community",
+ "postTitle": "Post a shout",
+ "postSubtitle": "Send a message to the DJ on air",
+ "messageLabel": "Your message",
+ "messagePlaceholder": "Send a shout to the DJ…",
+ "shoutButton": "Shout",
+ "loginToPost": "Log in to post a shout.",
+ "latestTitle": "Latest shouts",
+ "latestSubtitle": "The most recent messages from listeners",
+ "empty": "No shouts yet. Be the first!"
+ },
+ "radioContests": {
+ "title": "Radio Contests",
+ "subtitle": "Tune in and take part in our latest on-air contests",
+ "empty": "No contests yet.",
+ "contestLabel": "Contest #{id}",
+ "dateUnknown": "Date unknown"
+ },
+ "radioGiveaways": {
+ "title": "Radio Giveaways",
+ "subtitle": "Listen in for your chance to win in our radio giveaways",
+ "empty": "No giveaways yet.",
+ "giveawayLabel": "Giveaway #{id}",
+ "dateUnknown": "Date unknown"
+ },
+ "radioLeaderboard": {
+ "title": "Radio Listener Leaderboard",
+ "subtitle": "Top 50 listeners by points earned tuning in to the radio",
+ "empty": "No listener points to show yet.",
+ "colRank": "Rank",
+ "colAvatar": "Avatar",
+ "colListener": "Listener",
+ "colPoints": "Points"
+ },
+ "radioRequests": {
+ "formTitle": "Submit a request",
+ "formSubtitle": "Ask the DJ to play your favourite track",
+ "songLabel": "Song",
+ "songPlaceholder": "Title of the song",
+ "artistLabel": "Artist",
+ "artistPlaceholder": "Name of the artist (optional)",
+ "submitButton": "Submit request",
+ "loginToRequest": "Log in to submit a request.",
+ "latestTitle": "Recent requests",
+ "latestSubtitle": "The most recently requested songs",
+ "empty": "No requests yet.",
+ "colSong": "Song",
+ "colArtist": "Artist",
+ "colRequester": "Requester",
+ "colDate": "Date",
+ "colStatus": "Status",
+ "statusPending": "Pending",
+ "statusApproved": "Approved",
+ "statusPlayed": "Played"
+ },
+ "client": {
+ "title": "Nitro client",
+ "loading": "Loading client…",
+ "generatingTicket": "Generating SSO ticket…",
+ "connected": "Connected",
+ "welcome": "Welcome to {hotel}",
+ "openingClient": "Opening the client in a new tab…",
+ "fallbackLink": "If it doesn't open,",
+ "fallbackClick": "click here",
+ "errors": {
+ "unauthorized": "You need to be logged in to open the client.",
+ "generic": "Could not generate SSO ticket",
+ "loginRedirect": "Log in"
+ }
+ },
+ "radioApply": {
+ "title": "Apply to be a Radio DJ",
+ "subtitle": "Tell us about yourself and why you'd be a great fit for the airwaves",
+ "latestTitle": "Your latest application",
+ "statusLabel": "Status:",
+ "submittedOn": "· submitted {date}",
+ "submitAgain": "You can submit again below if your details have changed.",
+ "formTitle": "Application form",
+ "formSubtitle": "All fields marked required must be filled in",
+ "realNameLabel": "Real name",
+ "realNamePlaceholder": "Your full name",
+ "ageLabel": "Age",
+ "availabilityLabel": "Availability",
+ "availabilityPlaceholder": "Which days and times can you broadcast?",
+ "musicStyleLabel": "Music style",
+ "musicStylePlaceholder": "e.g. House, Pop, Hip-Hop",
+ "experienceLabel": "Experience",
+ "optional": "(optional)",
+ "experiencePlaceholder": "Any prior DJ or hosting experience?",
+ "motivationLabel": "Motivation",
+ "motivationPlaceholder": "Why do you want to join the radio team?",
+ "submitButton": "Submit application"
+ },
+ "settings": {
+ "accountSettings": "Account settings",
+ "accountSettingsSubtitle": "Manage your profile, security and connected accounts",
+ "publicProfile": "Your public profile",
+ "noMotto": "No motto",
+ "noEmail": "No e-mail on file",
+ "changeMotto": "Change motto",
+ "changeMottoSubtitle": "Updates instantly in-game if you are online",
+ "mottoPlaceholder": "Your motto",
+ "saveMotto": "Save motto",
+ "securityTitle": "Security",
+ "securitySubtitle": "Two-factor authentication",
+ "securityDescription": "Add a second layer of protection to your account.",
+ "twoFactorLink": "Two-factor authentication",
+ "connectionsTitle": "Connected accounts",
+ "connectionsSubtitle": "Sign in with one click",
+ "connectionsDescription": "Link Discord or Google to your hotel account.",
+ "manageConnections": "Manage connections"
+ },
+ "settings2fa": {
+ "backToSettings": "← Settings",
+ "title": "Two-factor authentication",
+ "subtitle": "Add a second layer of security with an authenticator app",
+ "nowEnabled": "2FA is now enabled. 🔒",
+ "disabledNotice": "2FA has been disabled.",
+ "badCode": "That code wasn't valid — try again.",
+ "noAppKeyPrefix": "2FA is unavailable until",
+ "noAppKeyMiddle": "is configured (the same Laravel",
+ "noAppKeySuffix": "as your AtomCMS install, so existing secrets stay readable).",
+ "isEnabled": "2FA is enabled",
+ "onYourAccount": "on your account.",
+ "disable": "Disable 2FA",
+ "scanOrEnter": "Scan or enter this key",
+ "scanInstructions": "Add this to Google Authenticator / Authy, then enter the 6-digit code to confirm.",
+ "manualKey": "Manual key:",
+ "codePlaceholder": "6-digit code",
+ "confirm": "Confirm",
+ "enableIntro": "Add a second layer of security with an authenticator app.",
+ "enable": "Enable 2FA"
+ },
+ "connections": {
+ "backToSettings": "← Settings",
+ "title": "Connected accounts",
+ "subtitle": "Link a social account so you can sign in with one click",
+ "matchByEmail": "We match it to your hotel account by e-mail, so use the address on file:",
+ "lookupFailed": "We couldn't read your linked accounts right now. You can still connect below — your status will show once it's available.",
+ "statusUnavailable": "Not available on this hotel.",
+ "statusConnected": "Connected — sign in with this account.",
+ "statusNotConnected": "Not connected.",
+ "connected": "Connected",
+ "connectProvider": "Connect {provider}",
+ "unavailable": "Unavailable",
+ "footerNote": "Connecting takes you to the provider to authorise, then back here. If your social account's e-mail doesn't match a hotel account, you'll be returned to the login screen with a note."
+ },
+ "login": {
+ "title": "Sign in",
+ "subtitle": "Welcome back — log in to enter the hotel.",
+ "subtitle2fa": "Enter the 6-digit code from your authenticator.",
+ "usernamePlaceholder": "Username",
+ "passwordPlaceholder": "Password",
+ "codePlaceholder": "2FA code",
+ "pleaseWait": "Please wait…",
+ "verify": "Verify",
+ "signIn": "Sign in",
+ "or": "or",
+ "continueWithDiscord": "Continue with Discord",
+ "continueWithGoogle": "Continue with Google",
+ "noAccount": "No account?",
+ "createOne": "Create one",
+ "forgotPassword": "Forgot password?",
+ "errorInvalidCredentials": "Invalid username or password",
+ "errorInvalid2fa": "Invalid 2FA code"
+ },
+ "register": {
+ "title": "Create account",
+ "subtitle": "Join the hotel — it only takes a moment.",
+ "username": "Username",
+ "email": "Email",
+ "password": "Password",
+ "confirmPassword": "Repeat password",
+ "usernamePlaceholder": "Username",
+ "emailPlaceholder": "Email",
+ "passwordPlaceholder": "Password (min 6 chars)",
+ "confirmPasswordPlaceholder": "Repeat password",
+ "selectOutfit": "Select your outfit",
+ "termsAccept": "I am 18+ and accept the {hotel} terms & rules.",
+ "createAccount": "Create account",
+ "creatingAccount": "Creating account...",
+ "alreadyHaveAccount": "Already have an account? Login!",
+ "register": "Register",
+ "termsError": "You must accept the terms & rules to register.",
+ "usernameError": "Username is required.",
+ "emailError": "Valid email is required.",
+ "passwordError": "Password must be at least 6 characters.",
+ "confirmPasswordError": "Passwords do not match.",
+ "termsRequired": "You must accept the terms & rules.",
+ "usernameRequired": "Username is required.",
+ "emailRequired": "Email is required.",
+ "passwordRequired": "Password is required.",
+ "passwordsDontMatch": "Passwords do not match."
+ },
+ "forgot": {
+ "title": "Reset password",
+ "subtitle": "We'll email you a secure link to set a new password.",
+ "sentNotice": "If that email is registered, a reset link has been sent. Check your inbox.",
+ "emailPlaceholder": "Your email",
+ "sendResetLink": "Send reset link",
+ "backToLogin": "Back to login"
+ },
+ "reset": {
+ "title": "Set a new password",
+ "subtitle": "Choose a strong password you don't use elsewhere.",
+ "passwordPlaceholder": "New password (min 6 chars)",
+ "resetPassword": "Reset password",
+ "backToLogin": "Back to login"
+ },
+ "verify": {
+ "verifiedTitle": "You're all set",
+ "verifiedSubtitle": "Email verified",
+ "verifiedBody": "Your email address has been verified. You can now log in and play.",
+ "goToLogin": "Go to login",
+ "alreadyTitle": "Already verified",
+ "alreadySubtitle": "Nothing left to do",
+ "alreadyBody": "This email address has already been confirmed.",
+ "unavailableTitle": "Try again shortly",
+ "unavailableSubtitle": "Server unreachable",
+ "unavailableBody": "We couldn't reach the server to verify your email. Please retry the link in a moment.",
+ "invalidTitle": "Email verification",
+ "invalidSubtitle": "Invalid or expired link",
+ "invalidBody": "This verification link is not valid. Make sure you opened the full link from your email, or request a new one.",
+ "backToLogin": "Back to login"
+ },
+ "banned": {
+ "title": "You are banned",
+ "subtitle": "Account suspended",
+ "body": "Your account has been suspended from the hotel.",
+ "reasonLabel": "Reason:",
+ "permanent": "This ban is permanent.",
+ "expires": "Expires {date}.",
+ "contactStaff": "If you believe this is a mistake, contact the staff team."
+ },
+ "maintenance": {
+ "title": "{hotel} is under maintenance",
+ "subtitle": "We'll be back shortly",
+ "staffCanLogIn": "Staff can still log in."
+ },
+ "admin": {
+ "nav": {
+ "overview": "Overview",
+ "dashboard": "Dashboard",
+ "content": "Content",
+ "articles": "Articles",
+ "tags": "Tags",
+ "navigator": "Navigator",
+ "helpCenter": "Help center",
+ "advertisements": "Advertisements",
+ "writeableBoxes": "Writeable boxes",
+ "photos": "Photos",
+ "tickets": "Tickets",
+ "usersAndAccess": "Users & access",
+ "users": "Users",
+ "bans": "Bans",
+ "ipManagement": "IP management",
+ "vpn": "VPN",
+ "applications": "Applications",
+ "teams": "Teams",
+ "permissions": "Permissions",
+ "economy": "Economy",
+ "catalog": "Catalog",
+ "shop": "Shop",
+ "transactions": "Transactions",
+ "vouchers": "Vouchers",
+ "subscriptions": "Subscriptions",
+ "rareValues": "Rare values",
+ "badges": "Badges",
+ "achievements": "Achievements",
+ "calendar": "Calendar",
+ "radio": "Radio",
+ "radioHistory": "Radio history",
+ "radioBanners": "Banners",
+ "radioEmbed": "Embed",
+ "radioApiKeys": "API keys",
+ "radioModeration": "Moderation",
+ "radioSettings": "Settings",
+ "radioMonitoring": "Monitoring",
+ "radioPoints": "Points",
+ "radioRanks": "Ranks",
+ "radioAutodj": "Auto DJ",
+ "system": "System",
+ "commandocentrum": "Commandocentrum",
+ "emulator": "Emulator",
+ "alerts": "Alerts",
+ "logs": "Logs",
+ "wordFilter": "Word filter",
+ "maintenance": "Maintenance",
+ "rooms": "Rooms",
+ "emailTemplates": "Email templates",
+ "theme": "Theme",
+ "media": "Media",
+ "favicon": "Favicon",
+ "sounds": "Sounds",
+ "translations": "Translations",
+ "import": "Import",
+ "settings": "Settings",
+ "backToSite": "Back to site",
+ "housekeeping": "Housekeeping",
+ "hotelManagementPanel": "Hotel management panel",
+ "rankLabel": "Rank {rank}"
+ },
+ "dashboard": {
+ "title": "Dashboard",
+ "subtitle": "Hotel overview & live status",
+ "playersOnline": "Players online",
+ "articles": "Articles",
+ "activeBans": "Active bans",
+ "database": "Database",
+ "databaseConnected": "Connected",
+ "databaseDown": "Down",
+ "usersByRank": "Users by rank",
+ "noAccountsToBreakDown": "No accounts to break down yet",
+ "rankLabel": "Rank {rank}",
+ "rankWithCount": "Rank {rank}: {count} accounts",
+ "recentStaffActivity": "Recent staff activity",
+ "viewAll": "View all",
+ "noStaffActivityLogged": "No staff activity logged yet"
+ },
+ "articles": {
+ "title": "Articles",
+ "subtitle": "News articles — 50 most recent",
+ "newArticle": "+ New article",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noArticles": "No articles yet",
+ "confirmDelete": "Are you sure you want to delete \"{title}\"?",
+ "colTitle": "Title",
+ "colSlug": "Slug",
+ "colAuthor": "Author",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "title": "Title",
+ "titlePlaceholder": "Article title",
+ "slug": "Slug",
+ "slugPlaceholder": "article-url-slug",
+ "category": "Category",
+ "categoryPlaceholder": "Select a category",
+ "body": "Body",
+ "bodyPlaceholder": "Write your article content…",
+ "image": "Image",
+ "imagePlaceholder": "https://example.com/image.png",
+ "thumbnail": "Thumbnail",
+ "thumbnailPlaceholder": "https://example.com/thumb.png",
+ "published": "Published",
+ "featured": "Featured",
+ "save": "Save article",
+ "saving": "Saving…",
+ "deleteArticle": "Delete article",
+ "deleteConfirm": "This action cannot be undone. The article will be permanently removed.",
+ "cancel": "Cancel"
+ }
+ },
+ "tags": {
+ "title": "Tags",
+ "subtitle": "Article tags / categories",
+ "addTag": "Add tag",
+ "name": "Name",
+ "namePlaceholder": "Tag name",
+ "backgroundColour": "Background colour",
+ "createTag": "Create tag",
+ "editTag": "Edit tag",
+ "updateTag": "Update tag",
+ "deleteTag": "Delete tag",
+ "confirmDelete": "Delete \"{name}\"?",
+ "noTags": "No tags yet",
+ "colId": "ID",
+ "colTag": "Tag",
+ "colColour": "Colour",
+ "colUsedBy": "Used by",
+ "colCreated": "Created",
+ "colActions": "Actions"
+ },
+ "navigation": {
+ "title": "Navigator",
+ "flatCategories": "Flat categories",
+ "publicRooms": "Public rooms",
+ "addCategory": "Add category",
+ "editCategory": "Edit category",
+ "deleteCategory": "Delete category",
+ "categoryName": "Category name",
+ "categoryNamePlaceholder": "e.g. Chill & Chat",
+ "categoryOrder": "Order",
+ "roomName": "Room name",
+ "roomNamePlaceholder": "e.g. Welcome Lounge",
+ "roomId": "Room ID",
+ "roomIdPlaceholder": "Numeric room ID",
+ "save": "Save",
+ "cancel": "Cancel",
+ "noCategories": "No categories yet",
+ "noPublicRooms": "No public rooms yet",
+ "colId": "ID",
+ "colName": "Name",
+ "colOrder": "Order",
+ "colRooms": "Rooms",
+ "colActions": "Actions"
+ },
+ "helpCenter": {
+ "title": "Help Center",
+ "subtitle": "Support entries and FAQ management",
+ "entries": "Entries",
+ "newEntry": "+ New entry",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noEntries": "No entries yet",
+ "confirmDelete": "Delete \"{title}\"?",
+ "colId": "ID",
+ "colTitle": "Title",
+ "colCategory": "Category",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "title": "Title",
+ "titlePlaceholder": "Entry title",
+ "category": "Category",
+ "categoryPlaceholder": "Select category",
+ "body": "Body",
+ "bodyPlaceholder": "Write the answer…",
+ "save": "Save entry",
+ "saving": "Saving…",
+ "deleteEntry": "Delete entry",
+ "cancel": "Cancel"
+ }
+ },
+ "ads": {
+ "title": "Advertisements",
+ "subtitle": "Manage hotel advertisements and promotional banners",
+ "newAd": "+ New ad",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noAds": "No advertisements yet",
+ "confirmDelete": "Delete this ad?",
+ "colId": "ID",
+ "colName": "Name",
+ "colImage": "Image",
+ "colUrl": "URL",
+ "colViews": "Views",
+ "colStatus": "Status",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "name": "Name",
+ "namePlaceholder": "Ad name",
+ "image": "Image URL",
+ "imagePlaceholder": "https://example.com/banner.png",
+ "url": "Target URL",
+ "urlPlaceholder": "https://example.com",
+ "enabled": "Enabled",
+ "save": "Save ad",
+ "saving": "Saving…",
+ "deleteAd": "Delete ad",
+ "cancel": "Cancel"
+ }
+ },
+ "writeableBoxes": {
+ "title": "Writeable Boxes",
+ "subtitle": "Content panels shown across the hotel and CMS",
+ "newBox": "New box",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noBoxes": "No writeable boxes yet",
+ "confirmDelete": "Delete this box?",
+ "colId": "ID",
+ "colName": "Name",
+ "colLocation": "Location",
+ "colUpdated": "Updated",
+ "colActions": "Actions",
+ "form": {
+ "name": "Name",
+ "namePlaceholder": "Box name",
+ "location": "Location",
+ "locationPlaceholder": "e.g. homepage, sidebar",
+ "content": "Content",
+ "contentPlaceholder": "Write the box content…",
+ "enabled": "Enabled",
+ "save": "Save box",
+ "saving": "Saving…",
+ "deleteBox": "Delete box",
+ "cancel": "Cancel"
+ }
+ },
+ "photos": {
+ "title": "Photo Moderation",
+ "subtitle": "Recent in-game camera photos",
+ "delete": "Delete",
+ "approve": "Approve",
+ "noPhotos": "No photos to moderate",
+ "confirmDelete": "Delete this photo permanently?",
+ "colId": "ID",
+ "colPreview": "Preview",
+ "colUser": "User",
+ "colRoom": "Room",
+ "colTimestamp": "Timestamp",
+ "colActions": "Actions",
+ "photoAlt": "User photo #{id}",
+ "byUser": "by {username}"
+ },
+ "users": {
+ "title": "Users",
+ "subtitle": "Browse, search and manage hotel accounts",
+ "search": "Search",
+ "searchPlaceholder": "Search by username, email or ID…",
+ "searchResults": "{count, plural, one {# result} other {# results}} for \"{query}\"",
+ "noResults": "No users found",
+ "colId": "ID",
+ "colUsername": "Username",
+ "colEmail": "Email",
+ "colRank": "Rank",
+ "colOnline": "Online",
+ "colRegistered": "Registered",
+ "colLastOnline": "Last online",
+ "colActions": "Actions",
+ "yes": "Yes",
+ "no": "No",
+ "edit": "Edit",
+ "setRank": "Set rank",
+ "disconnect": "Disconnect",
+ "alert": "Alert",
+ "ban": "Ban",
+ "detail": {
+ "title": "User: {username}",
+ "subtitle": "Account details and management",
+ "overview": "Overview",
+ "actions": "Actions",
+ "username": "Username",
+ "email": "Email",
+ "motto": "Motto",
+ "rank": "Rank",
+ "credits": "Credits",
+ "duckets": "Duckets",
+ "diamonds": "Diamonds",
+ "online": "Online",
+ "registered": "Registered",
+ "lastOnline": "Last online",
+ "ipAddress": "IP address",
+ "machineId": "Machine ID",
+ "sendAlert": "Send alert",
+ "sendMessage": "Send message",
+ "disconnectUser": "Disconnect user",
+ "banUser": "Ban user",
+ "resetPassword": "Reset password",
+ "deleteUser": "Delete user",
+ "confirmDelete": "Are you sure you want to delete {username}? This cannot be undone."
+ },
+ "editForm": {
+ "title": "Edit user: {username}",
+ "subtitle": "Modify account details",
+ "username": "Username",
+ "usernamePlaceholder": "Username",
+ "email": "Email",
+ "emailPlaceholder": "user@example.com",
+ "motto": "Motto",
+ "mottoPlaceholder": "User motto",
+ "rank": "Rank",
+ "credits": "Credits",
+ "duckets": "Duckets",
+ "diamonds": "Diamonds",
+ "save": "Save changes",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "bans": {
+ "title": "Bans",
+ "subtitle": "Manage account, IP and machine bans",
+ "newBan": "New ban",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noBans": "No bans yet",
+ "confirmDelete": "Remove this ban?",
+ "colId": "ID",
+ "colValue": "Value",
+ "colType": "Type",
+ "colReason": "Reason",
+ "colBannedBy": "Banned by",
+ "colExpires": "Expires",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "value": "Value",
+ "valuePlaceholder": "Username, IP, or machine ID",
+ "type": "Type",
+ "typeUser": "User",
+ "typeIp": "IP",
+ "typeMachine": "Machine",
+ "reason": "Reason",
+ "reasonPlaceholder": "Ban reason",
+ "expiresAt": "Expires at",
+ "permanent": "Permanent",
+ "save": "Save ban",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "ip": {
+ "title": "IP Management",
+ "subtitle": "Manage IP whitelist and blacklist",
+ "whitelist": "Whitelist",
+ "blacklist": "Blacklist",
+ "addIp": "Add IP",
+ "ipAddress": "IP address",
+ "ipAddressPlaceholder": "e.g. 127.0.0.1",
+ "reason": "Reason",
+ "reasonPlaceholder": "Reason for listing",
+ "add": "Add",
+ "remove": "Remove",
+ "confirmRemove": "Remove this IP from the {list}?",
+ "noWhitelistedIps": "No whitelisted IPs",
+ "noBlacklistedIps": "No blacklisted IPs",
+ "colIp": "IP",
+ "colReason": "Reason",
+ "colAdded": "Added",
+ "colActions": "Actions"
+ },
+ "vpn": {
+ "title": "VPN / Proxy Detection",
+ "subtitle": "Configure VPN and proxy detection settings",
+ "enabled": "Enabled",
+ "apiKey": "API key",
+ "apiKeyPlaceholder": "Your VPN detection API key",
+ "apiKeyHint": "Leave empty to disable",
+ "blockOnDetect": "Block on detection",
+ "blockOnDetectHint": "Automatically block users detected behind a VPN or proxy",
+ "save": "Save settings",
+ "saving": "Saving…",
+ "settingsSaved": "VPN settings saved successfully"
+ },
+ "applications": {
+ "title": "Staff Applications",
+ "subtitle": "Pending and reviewed staff applications",
+ "pending": "Pending",
+ "approved": "Approved",
+ "rejected": "Rejected",
+ "noApplications": "No applications yet",
+ "colId": "ID",
+ "colApplicant": "Applicant",
+ "colPosition": "Position",
+ "colStatus": "Status",
+ "colSubmitted": "Submitted",
+ "colActions": "Actions",
+ "review": "Review",
+ "approve": "Approve",
+ "reject": "Reject",
+ "confirmApprove": "Approve {username} for {position}?",
+ "confirmReject": "Reject {username}'s application for {position}?",
+ "detail": {
+ "title": "Application by {username}",
+ "subtitle": "For {position}",
+ "about": "About the applicant",
+ "message": "Application message",
+ "status": "Status",
+ "submitted": "Submitted",
+ "noMessage": "No message provided"
+ }
+ },
+ "teams": {
+ "title": "Teams",
+ "subtitle": "Define staff rank names and descriptions",
+ "addRank": "Add rank",
+ "editRank": "Edit rank",
+ "deleteRank": "Delete rank",
+ "noTeams": "No rank definitions yet",
+ "confirmDelete": "Delete rank {id} definition?",
+ "colId": "ID",
+ "colRank": "Rank",
+ "colName": "Name",
+ "colBadge": "Badge",
+ "colColour": "Colour",
+ "colActions": "Actions",
+ "form": {
+ "rankId": "Rank ID",
+ "rankIdPlaceholder": "Numeric rank ID",
+ "name": "Name",
+ "namePlaceholder": "e.g. Administrator",
+ "badge": "Badge code",
+ "badgePlaceholder": "e.g. ADM001",
+ "colour": "Colour",
+ "colourPlaceholder": "e.g. #ff0000",
+ "save": "Save team definition",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "permissions": {
+ "title": "Permissions",
+ "subtitle": "Manage rank-based permissions for the admin panel",
+ "addPermission": "Add permission",
+ "editPermission": "Edit permission",
+ "deletePermission": "Delete permission",
+ "noPermissions": "No permissions configured yet",
+ "confirmDelete": "Delete this permission?",
+ "colId": "ID",
+ "colRank": "Rank",
+ "colPermission": "Permission",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "rankId": "Rank ID",
+ "rankIdPlaceholder": "Minimum rank required",
+ "permission": "Permission key",
+ "permissionPlaceholder": "e.g. admin.articles.manage",
+ "save": "Save permission",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "catalog": {
+ "title": "Catalog",
+ "subtitle": "Browse and manage catalog pages and items",
+ "pages": "Pages",
+ "items": "Items",
+ "addPage": "Add page",
+ "editPage": "Edit page",
+ "deletePage": "Delete page",
+ "addItem": "Add item",
+ "editItem": "Edit item",
+ "deleteItem": "Delete item",
+ "noPages": "No catalog pages yet",
+ "noItems": "No items in this page",
+ "confirmDeletePage": "Delete catalog page \"{name}\"?",
+ "confirmDeleteItem": "Delete this item?",
+ "colId": "ID",
+ "colName": "Name",
+ "colIcon": "Icon",
+ "colOrder": "Order",
+ "colItems": "Items",
+ "colEnabled": "Enabled",
+ "colActions": "Actions",
+ "colItemId": "Item ID",
+ "colItemName": "Item name",
+ "colCostCredits": "Cost (credits)",
+ "colCostPoints": "Cost (points)",
+ "colLimited": "Limited",
+ "pageForm": {
+ "name": "Name",
+ "namePlaceholder": "Page name",
+ "icon": "Icon ID",
+ "iconPlaceholder": "Numeric icon ID",
+ "order": "Order",
+ "enabled": "Enabled",
+ "parent": "Parent page",
+ "parentNone": "None (root)",
+ "save": "Save page",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ },
+ "itemForm": {
+ "itemId": "Item ID",
+ "itemName": "Item name",
+ "costCredits": "Cost in credits",
+ "costDuckets": "Cost in duckets",
+ "costDiamonds": "Cost in diamonds",
+ "limitedStack": "Limited stack size",
+ "limitedSold": "Limited sold",
+ "save": "Save item",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "shop": {
+ "title": "Shop",
+ "subtitle": "Manage packages and orders",
+ "packages": "Packages",
+ "orders": "Orders",
+ "newPackage": "+ New package",
+ "edit": "Edit",
+ "delete": "Delete",
+ "viewOrder": "View",
+ "noPackages": "No shop packages yet",
+ "noOrders": "No orders yet",
+ "confirmDelete": "Delete this package?",
+ "colId": "ID",
+ "colName": "Name",
+ "colPrice": "Price",
+ "colCurrency": "Currency",
+ "colType": "Type",
+ "colSold": "Sold",
+ "colEnabled": "Enabled",
+ "colActions": "Actions",
+ "colOrderId": "Order ID",
+ "colCustomer": "Customer",
+ "colPackage": "Package",
+ "colAmount": "Amount",
+ "colStatus": "Status",
+ "colDate": "Date",
+ "statusPending": "Pending",
+ "statusCompleted": "Completed",
+ "statusRefunded": "Refunded",
+ "form": {
+ "name": "Name",
+ "namePlaceholder": "Package name",
+ "description": "Description",
+ "descriptionPlaceholder": "Package description",
+ "price": "Price",
+ "currency": "Currency",
+ "currencyPlaceholder": "EUR, USD, GBP…",
+ "type": "Type",
+ "typeCredits": "Credits",
+ "typeDuckets": "Duckets",
+ "typeDiamonds": "Diamonds",
+ "typeBadge": "Badge",
+ "typeFurni": "Furniture",
+ "typeRank": "Rank",
+ "amount": "Amount",
+ "badgeCode": "Badge code",
+ "badgeCodePlaceholder": "e.g. VIP001",
+ "rankId": "Rank ID",
+ "furniItems": "Furniture items",
+ "enabled": "Enabled",
+ "save": "Save package",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "transactions": {
+ "title": "Transactions",
+ "subtitle": "PayPal payment log",
+ "noTransactions": "No transactions yet",
+ "colId": "ID",
+ "colUser": "User",
+ "colAmount": "Amount",
+ "colCurrency": "Currency",
+ "colPackage": "Package",
+ "colStatus": "Status",
+ "colDate": "Date",
+ "colActions": "Actions",
+ "statusCompleted": "Completed",
+ "statusPending": "Pending",
+ "statusFailed": "Failed",
+ "statusRefunded": "Refunded",
+ "detail": {
+ "title": "Transaction #{id}",
+ "paypalId": "PayPal ID",
+ "payerEmail": "Payer email",
+ "grossAmount": "Gross amount",
+ "fee": "Fee",
+ "netAmount": "Net amount"
+ }
+ },
+ "vouchers": {
+ "title": "Shop Vouchers",
+ "subtitle": "Manage redeemable voucher codes",
+ "newVoucher": "+ New voucher",
+ "edit": "Edit",
+ "delete": "Delete",
+ "noVouchers": "No vouchers yet",
+ "confirmDelete": "Delete voucher \"{code}\"?",
+ "colId": "ID",
+ "colCode": "Code",
+ "colValue": "Value",
+ "colType": "Type",
+ "colUses": "Uses / Max",
+ "colExpires": "Expires",
+ "colCreated": "Created",
+ "colActions": "Actions",
+ "form": {
+ "code": "Code",
+ "codePlaceholder": "e.g. SUMMER2025",
+ "value": "Value",
+ "type": "Type",
+ "typeCredits": "Credits",
+ "typeDuckets": "Duckets",
+ "typeDiamonds": "Diamonds",
+ "typeBadge": "Badge",
+ "typeFurni": "Furniture",
+ "typeRank": "Rank",
+ "badgeCode": "Badge code",
+ "badgeCodePlaceholder": "e.g. VIP001",
+ "rankId": "Rank ID",
+ "maxUses": "Max uses",
+ "maxUsesPlaceholder": "0 = unlimited",
+ "expiresAt": "Expires at",
+ "enabled": "Enabled",
+ "save": "Save voucher",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "subscriptions": {
+ "title": "Subscriptions",
+ "subtitle": "Active recurring subscriptions",
+ "noSubscriptions": "No active subscriptions",
+ "cancel": "Cancel",
+ "confirmCancel": "Cancel subscription for {username}?",
+ "colId": "ID",
+ "colUser": "User",
+ "colPackage": "Package",
+ "colAmount": "Amount",
+ "colInterval": "Interval",
+ "colStarted": "Started",
+ "colNextBilling": "Next billing",
+ "colStatus": "Status",
+ "colActions": "Actions",
+ "statusActive": "Active",
+ "statusCancelled": "Cancelled",
+ "statusExpired": "Expired"
+ },
+ "rareValues": {
+ "title": "Rare Values",
+ "subtitle": "Manage rare value categories and item prices",
+ "categories": "Categories",
+ "items": "Items",
+ "addCategory": "Add category",
+ "editCategory": "Edit category",
+ "deleteCategory": "Delete category",
+ "noCategories": "No rare value categories yet",
+ "confirmDeleteCategory": "Delete category \"{name}\" and all its items?",
+ "colId": "ID",
+ "colName": "Name",
+ "colItems": "Items",
+ "colUpdated": "Updated",
+ "colActions": "Actions",
+ "colItemName": "Item name",
+ "colCredits": "Credits",
+ "colPoints": "Points",
+ "colPointType": "Point type",
+ "categoryForm": {
+ "name": "Name",
+ "namePlaceholder": "Category name",
+ "save": "Save category",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ },
+ "itemForm": {
+ "itemName": "Item name",
+ "itemNamePlaceholder": "e.g. Throne",
+ "credits": "Credits value",
+ "points": "Points value",
+ "pointType": "Point type",
+ "pointTypeDuckets": "Duckets",
+ "pointTypeDiamonds": "Diamonds",
+ "save": "Save item",
+ "saving": "Saving…",
+ "cancel": "Cancel"
+ }
+ },
+ "badges": {
+ "title": "Badges",
+ "subtitle": "Upload, give and manage badge catalogue",
+ "upload": "Upload",
+ "giveBadge": "Give badge",
+ "catalogue": "Catalogue",
+ "uploadTitle": "Upload a new badge",
+ "uploadSubtitle": "Upload a badge image file",
+ "badgeCode": "Badge code",
+ "badgeCodePlaceholder": "e.g. VIP001",
+ "badgeFile": "Badge file",
+ "badgeFileHint": "PNG or GIF, recommended 40x40",
+ "uploadButton": "Upload",
+ "uploading": "Uploading…",
+ "giveTitle": "Give badge to user",
+ "giveSubtitle": "Grant a badge to a specific user",
+ "username": "Username",
+ "usernamePlaceholder": "Target username",
+ "giveButton": "Give badge",
+ "giving": "Giving…",
+ "catalogueTitle": "Badge catalogue",
+ "catalogueSubtitle": "All available badges in the hotel",
+ "noBadges": "No badges in the catalogue",
+ "search": "Search badges",
+ "searchPlaceholder": "Search by code or name…",
+ "colCode": "Code",
+ "colImage": "Image",
+ "colName": "Name",
+ "colDescription": "Description",
+ "colActions": "Actions"
+ },
+ "achievements": {
+ "title": "Achievements",
+ "subtitle": "Read-only list of in-game achievements",
+ "noAchievements": "No achievements configured",
+ "colId": "ID",
+ "colName": "Name",
+ "colCategory": "Category",
+ "colLevels": "Levels",
+ "colReward": "Reward",
+ "colProgress": "Progress",
+ "levelLabel": "Level {level}",
+ "rewardCredits": "{count} credits",
+ "rewardPoints": "{count} {points}"
+ },
+ "calendar": {
+ "title": "Calendar campaigns",
+ "subtitle": "Read-only list of calendar campaigns and events",
+ "noCampaigns": "No calendar campaigns yet",
+ "colId": "ID",
+ "colName": "Name",
+ "colStart": "Start",
+ "colEnd": "End",
+ "colType": "Type",
+ "colReward": "Reward",
+ "colActions": "Actions",
+ "detail": {
+ "title": "Campaign: {name}",
+ "subtitle": "Calendar event details",
+ "days": "Days",
+ "dayLabel": "Day {day}",
+ "reward": "Reward",
+ "noDays": "No days configured for this campaign"
+ }
+ },
+ "radio": {
+ "title": "Radio Moderation",
+ "subtitle": "Latest shouts, DJ applications and on-air schedules (25 most recent of each).",
+ "moderation": "Moderation",
+ "shouts": "Shouts",
+ "applications": "Applications",
+ "schedules": "Schedules",
+ "settings": "Settings",
+ "embed": "Embed",
+ "ranks": "Ranks",
+ "history": "History",
+ "apiKeys": "API Keys",
+ "points": "Points",
+ "autoDj": "AutoDJ",
+ "monitoring": "Monitoring",
+ "user": "User",
+ "message": "Message",
+ "posted": "Posted",
+ "realName": "Real Name",
+ "age": "Age",
+ "style": "Style",
+ "applied": "Applied",
+ "day": "Day",
+ "start": "Start",
+ "end": "End",
+ "show": "Show",
+ "status": "Status",
+ "active": "active",
+ "inactive": "inactive",
+ "hidden": "hidden",
+ "activeSchedules": "Active schedules",
+ "noShouts": "No shouts to moderate",
+ "noApplications": "No applications submitted.",
+ "noSchedules": "No schedules configured.",
+ "deleteShout": "Delete",
+ "confirmDeleteShout": "Delete this shout?",
+ "pendingApplications": "Pending applications",
+ "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 \u2014 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