diff --git a/src/app/(site)/me/CopyReferralButton.tsx b/src/app/(site)/me/CopyReferralButton.tsx index 87283a57b5..4bd73512a9 100644 --- a/src/app/(site)/me/CopyReferralButton.tsx +++ b/src/app/(site)/me/CopyReferralButton.tsx @@ -1,11 +1,16 @@ "use client"; +import { useTranslations } from "next-intl"; import { useState } from "react"; export default function CopyReferralButton({ value }: { value: string }) { const [copied, setCopied] = useState(false); + const [failed, setFailed] = useState(false); + const t = useTranslations("pages.myDashboard"); async function copy() { + setFailed(false); + setCopied(false); const absolute = typeof window !== "undefined" && value.startsWith("/") ? `${window.location.origin}${value}` @@ -20,9 +25,11 @@ export default function CopyReferralButton({ value }: { value: string }) { document.body.appendChild(ta); ta.select(); try { - document.execCommand("copy"); + if (!document.execCommand("copy")) throw new Error("copy-failed"); } catch { - /* give up silently */ + setFailed(true); + document.body.removeChild(ta); + return; } document.body.removeChild(ta); } @@ -31,17 +38,21 @@ export default function CopyReferralButton({ value }: { value: string }) { } return ( - + <> + + {failed &&

{t("copyFailed")}

} + ); } diff --git a/src/app/(site)/me/dashboard.module.css b/src/app/(site)/me/dashboard.module.css new file mode 100644 index 0000000000..f0fc6bd891 --- /dev/null +++ b/src/app/(site)/me/dashboard.module.css @@ -0,0 +1,249 @@ +.dashboard { + display: grid; + gap: 20px; + padding-bottom: 24px; + min-width: 0; +} +.hero { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 24px; + padding: 24px; + background: linear-gradient( + 130deg, + color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)), + var(--color-surface) + ); +} +.avatar { + flex-shrink: 0; + object-fit: contain; + image-rendering: pixelated; + border-radius: 16px; + background: color-mix(in srgb, var(--color-primary) 10%, transparent); +} +.identity { + flex: 1; + min-width: 180px; + overflow-wrap: anywhere; +} +.identity h1 { + font-size: clamp(1.6rem, 3vw, 2rem); + margin: 4px 0; +} +.dates { + font-size: 0.8rem; + color: var(--color-text-muted); + margin-top: 12px; +} +.shortcuts { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} +.shortcut { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 14px; + border-radius: 12px; + background: var(--color-surface); + border: 1px solid var(--color-border); + font-weight: 600; + color: var(--color-text-readable); + text-decoration: none; +} +.shortcut:hover, +.friend:hover, +.room:hover { + background: color-mix(in srgb, var(--color-primary) 8%, var(--color-surface)); +} +.count { + border-radius: 999px; + padding: 2px 7px; + background: var(--color-danger); + color: white; + font-size: 0.75rem; +} +.wallet { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} +.currency { + display: flex; + align-items: center; + gap: 14px; + padding: 20px; +} +.currency > div { + display: grid; + min-width: 0; +} +.currency strong { + font-size: 1.3rem; + font-variant-numeric: tabular-nums; + overflow-wrap: anywhere; +} +.currency span { + font-size: 0.8rem; + color: var(--color-text-muted); +} +.columns { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(280px, 350px); + gap: 20px; + align-items: start; +} +.stack { + display: grid; + gap: 20px; + min-width: 0; +} +.friends, +.rooms { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); + gap: 12px; +} +.friend, +.room { + display: flex; + gap: 12px; + padding: 12px; + border: 1px solid var(--color-border); + border-radius: 12px; + color: var(--color-text-readable); + text-decoration: none; + min-width: 0; +} +.friend > div, +.room { + display: grid; + min-width: 0; +} +.friend strong, +.friend span, +.room strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.friend span, +.room span { + font-size: 0.8rem; +} +.stats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; + margin: 0; +} +.stats dt { + font-size: 0.8rem; +} +.stats dd { + margin: 4px 0 0; + font-weight: 700; +} +.feedback { + margin: 0; + border: 1px solid var(--color-border); + border-left: 4px solid var(--color-primary); + border-radius: 12px; + background: var(--color-surface); + padding: 16px; +} +.progressLabel { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 16px; + font-size: 0.85rem; +} +.progress { + width: 100%; + height: 10px; + accent-color: var(--color-primary); +} +.reward { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; +} +.referralInput { + width: 100%; + min-width: 0; + margin: 8px 0; +} +.claim { + margin-top: 16px; +} +.claim button { + width: 100%; +} +.rules { + border-top: 1px solid var(--color-border); + margin-top: 16px; + padding-top: 12px; + font-size: 0.8rem; +} +.rules summary { + cursor: pointer; +} +.badges { + display: flex; + flex-wrap: wrap; + gap: 10px; +} +.badges img { + object-fit: contain; + image-rendering: pixelated; +} +@media (max-width: 760px) { + .columns { + grid-template-columns: minmax(0, 1fr); + } + .shortcuts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero { + padding: 18px; + gap: 16px; + } + .hero > a { + width: 100%; + text-align: center; + } + .currency { + padding: 12px; + gap: 8px; + flex-wrap: wrap; + } + .currency strong { + font-size: 1rem; + } +} +.progress { + appearance: none; + border: 0; + border-radius: 999px; + overflow: hidden; + background: var(--color-border); +} +.progress::-webkit-progress-bar { + background: var(--color-border); + border-radius: 999px; +} +.progress::-webkit-progress-value { + background: var(--color-primary); + border-radius: 999px; +} +.progress::-moz-progress-bar { + background: var(--color-primary); + border-radius: 999px; +} diff --git a/src/app/(site)/me/page.tsx b/src/app/(site)/me/page.tsx index 6fe78861d2..b4e3f445ea 100644 --- a/src/app/(site)/me/page.tsx +++ b/src/app/(site)/me/page.tsx @@ -1,367 +1,233 @@ -import Image from "next/image"; import { redirect } from "next/navigation"; -import type { CSSProperties, ReactNode } from "react"; +import { getFormatter, getTranslations } from "next-intl/server"; import { claimReferral } from "@/actions/referral"; import Link from "@/components/link"; - -import { Reveal } from "@/components/motion-reveal"; +import { + CurrencyIcon, + type CurrencyKind, +} from "@/components/shared/currency-icon"; +import { ProfileImage } from "@/components/shared/profile-image"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { SurfaceCard } from "@/components/surface-card"; import { auth } from "@/lib/auth"; import { avatarImageUrl } from "@/lib/format"; import { loadUserDashboard } from "@/lib/services/user-dashboard"; import CopyReferralButton from "./CopyReferralButton"; +import styles from "./dashboard.module.css"; -export const metadata = { title: "My dashboard" }; - -type SearchParams = Promise<{ claimed?: string; error?: string }>; - -function feedbackStyle(tone: "success" | "error" | "warning"): CSSProperties { - const accent = - tone === "error" - ? "var(--color-danger)" - : tone === "warning" - ? "var(--color-accent)" - : "var(--color-primary)"; - return { - margin: 0, - padding: "0.85rem 1rem", - borderRadius: "var(--radius-md)", - border: `1px solid ${accent}`, - color: "var(--color-text-readable, var(--color-text))", - fontSize: "0.9rem", - fontWeight: 600, - background: "var(--color-surface)", - borderLeft: `4px solid ${accent}`, - }; -} - -const ERROR_MESSAGES: Record = { - not_enough: "You do not have enough referrals to claim your reward yet.", - no_referrals: - "You haven't referred anyone yet — share your link to get started.", - bad_config: - "The referral reward is not configured correctly. Please contact a staff member.", - error: "Something went wrong while claiming your reward. Please try again.", -}; - -function getErrorMessage(error: string): string { - return ERROR_MESSAGES[error] ?? ERROR_MESSAGES.error; +export async function generateMetadata() { + const t = await getTranslations("pages.myDashboard"); + return { title: t("title") }; } export default async function MePage({ searchParams, }: { - searchParams: SearchParams; + searchParams: Promise<{ claimed?: string; error?: string }>; }) { const session = await auth(); if (!session?.user?.id) redirect("/login"); - const userId = Number(session.user.id); if (!Number.isFinite(userId) || userId <= 0) redirect("/login"); - - const { claimed, error } = await searchParams; - - let content: ReactNode; + const [t, format, { claimed, error }] = await Promise.all([ + getTranslations("pages.myDashboard"), + getFormatter(), + searchParams, + ]); + let data: Awaited>; try { - const BADGE_IMG_BASE = "https://images.habbo.com/c_images/album1584"; - - const { - userRows, - hotelName, - neededRaw, - rewardAmountRaw, - currencySettings, - alertRaw, - recentRooms, - badges, - userSettingsRows, - friendCountRows, - unreadCountRows, - referralsRows, - friends, - } = await loadUserDashboard(userId); - - const user = userRows[0] ?? null; - if (!user) throw new Error("user-not-found"); - - const userSettings = userSettingsRows[0] ?? null; - const friendCount = Number(friendCountRows[0]?.value ?? 0); - const unreadCount = Number(unreadCountRows[0]?.value ?? 0); - - const needed = Number.parseInt(neededRaw ?? "5", 10) || 5; - const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0; - const rewardCurrency = ( - currencySettings.referral_reward_currency_type ?? - currencySettings.referral_reward_currency ?? - "diamonds" - ) - .trim() - .toLowerCase(); - - const referralTotal = referralsRows[0] - ? Number(referralsRows[0].referralsTotal) - : 0; - const canClaim = referralTotal >= needed; - const remaining = Math.max(0, needed - referralTotal); - - const onlineFriends = friends.filter((f) => f.online === "1"); - - const registered = new Date(user.accountCreated * 1000) - .toISOString() - .slice(0, 10); - const lastLoginDate = user.lastLogin - ? new Date(user.lastLogin * 1000).toISOString().slice(0, 10) - : "Never"; - const lastOnlineDate = user.lastOnline - ? new Date(user.lastOnline * 1000).toISOString().slice(0, 10) - : "Never"; - const referralLink = `/register?ref=${encodeURIComponent(user.username)}`; - - const avatarFull = avatarImageUrl(user.look, { - direction: 2, - }); - - content = ( -
- {claimed ? ( -
- Woah! You have successfully claimed your reward — keep up the good - work! -
- ) : null} - {error ? ( -
- {getErrorMessage(error)} -
- ) : null} - - - -
-
-
- - + +

{t("loadError")}

+ + {t("retry")} + +
+ + ); + } + const user = data.userRows[0]; + if (!user) + return ( +
+ +

{t("loadError")}

+ + {t("login")} + +
+
+ ); + const { + hotelName, + neededRaw, + rewardAmountRaw, + currencySettings, + alertRaw, + recentRooms, + badges, + userSettingsRows, + friendCountRows, + unreadCountRows, + referralsRows, + friends, + currencyRows, + } = data; + const userSettings = userSettingsRows[0]; + const friendCount = Number(friendCountRows[0]?.value ?? 0); + const unreadCount = Number(unreadCountRows[0]?.value ?? 0); + const needed = Math.max(1, Number.parseInt(neededRaw ?? "5", 10) || 5); + const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0; + const rewardCurrency = ( + currencySettings.referral_reward_currency_type ?? + currencySettings.referral_reward_currency ?? + "diamonds" + ) + .trim() + .toLowerCase(); + const rewardKind = ["credits", "duckets", "diamonds", "points"].includes( + rewardCurrency, + ) + ? (rewardCurrency as CurrencyKind) + : null; + const referralTotal = Math.max( + 0, + Number(referralsRows[0]?.referralsTotal ?? 0), + ); + const canClaim = referralTotal >= needed; + const remaining = Math.max(0, needed - referralTotal); + const onlineFriends = [ + ...new Map( + friends + .filter((friend) => friend.online === "1") + .map((friend) => [friend.id, friend]), + ).values(), + ]; + const date = (value: number) => + value + ? format.dateTime(new Date(value * 1000), { dateStyle: "medium" }) + : t("never"); + const referralLink = `/register?ref=${encodeURIComponent(user.username)}`; + const avatarFull = avatarImageUrl(user.look, { direction: 2 }); + const wallet = [ + { kind: "credits", value: user.credits }, + { + kind: "duckets", + value: + currencyRows === null + ? null + : (currencyRows.find((row) => row.type === 0)?.amount ?? 0), + }, + { + kind: "diamonds", + value: + currencyRows === null + ? null + : (currencyRows.find((row) => row.type === 5)?.amount ?? 0), + }, + ] as const; + const errorKey = + error && + ["not_enough", "no_referrals", "bad_config", "ratelimit"].includes(error) + ? error + : "error"; + return ( +
+ {claimed && ( +

+ {t("claimed")} +

+ )} + {error && ( +

+ {t(`errors.${errorKey}`)} +

+ )} + + +
+

{t("welcome", { hotel: hotelName })}

+

{user.username}

+

{user.motto || t("noMotto")}

+

+ {t("registered", { date: date(user.accountCreated) })} ·{" "} + {t("lastOnline", { date: date(user.lastOnline) })} +

+
+ + {t("enter", { hotel: hotelName })} → + +
+
-
-
- Rank {user.rank} -
-

- {user.username} -

-

- {user.motto || "No motto"} -

-
- - Registered:{" "} - - {registered} - - - - Last online:{" "} - - {lastOnlineDate} - - -
+ {link.count > 99 ? "99+" : link.count} + + )} + + ))} + + {alertRaw &&

{alertRaw}

} +
+ {wallet.map((currency) => ( + + +
+ + {currency.value === null ? "—" : format.number(currency.value)} + + {t(`currencies.${currency.kind}`)}
- - - {alertRaw ? ( - - -

- {alertRaw} -

-
-
- ) : null} - - -
- {[ - { value: user.credits.toLocaleString(), label: "Credits" }, - { value: `Rank ${user.rank}`, label: "Hotel rank" }, - { - value: (userSettings?.achievementScore ?? 0).toLocaleString(), - label: "Achievement score", - }, - { - value: (userSettings?.respectsReceived ?? 0).toLocaleString(), - label: "Respects", - }, - { value: friendCount.toLocaleString(), label: "Friends" }, - { value: lastLoginDate, label: "Last login" }, - ].map((s) => ( - -
- {s.value} -
-
- {s.label} -
-
- ))} -
-
- - - -
-
-

- Online friends -

-

- {onlineFriends.length === 0 - ? "None of your friends are online right now" - : `${onlineFriends.length} of your ${friends.length} friends online`} -

-
- - View all - -
+ ))} +
+
+
+ {onlineFriends.length === 0 ? ( -
-

- No friends online right now. Check back later! -

-
+

{t("noOnlineFriends")}

) : ( -
+
{onlineFriends.map((friend) => ( - -
-

- {friend.username} -

-

- {friend.motto || "No motto"} -

- - {" "} - Online + +
+ {friend.username} + + {friend.motto || t("noMotto")}
@@ -369,247 +235,128 @@ export default async function MePage({
)} - - - - -
-

- Refer a friend ({referralTotal}/{needed}) -

-

- Refer new users and earn in-game rewards -

-
-
-

- {rewardAmount > 0 - ? `For every ${needed} users who register through your referral link you can claim a reward of ${rewardAmount.toLocaleString()} ${rewardCurrency}!` - : `Invite ${needed} users through your referral link to claim a reward!`} -

-
- Boosting referrals by making your own accounts will lead to - removal of all progress, currency, inventory and a potential - ban. -
-
- -
- - -
-
- {canClaim ? ( -
- -
- ) : ( - - )} -
-
-
- - {recentRooms.length > 0 && ( - - -
-
-

- Recent rooms -

-

- Newest rooms in the hotel -

-
- - View all - -
-
+ +

{t("roomsHint")}

+ {recentRooms.length === 0 ? ( +

{t("noRooms")}

+ ) : ( +
{recentRooms.map((room) => ( -

- {room.name} -

-

- {room.ownerName} · {room.users}/{room.usersMax} -

+ {room.name} + + {room.ownerName} · {room.users}/{room.usersMax} + ))}
-
- - )} - - {badges.length > 0 && ( - - -
-

- Badges -

-

- {badges.length} badge{badges.length !== 1 ? "s" : ""} equipped -

-
-
+ )} + + +
+ {[ + { + label: t("achievements"), + value: format.number(userSettings?.achievementScore ?? 0), + }, + { + label: t("respects"), + value: format.number(userSettings?.respectsReceived ?? 0), + }, + { label: t("friends"), value: format.number(friendCount) }, + { label: t("lastLogin"), value: date(user.lastLogin) }, + ].map((stat) => ( +
+
{stat.label}
+
{stat.value}
+
+ ))} +
+
+
+
- ); - } catch { - content = ( - -

- We couldn't load your dashboard right now. Please try again - shortly. -

-
- ); - } - - return
{content}
; + + ); } diff --git a/src/app/(site)/u/[username]/page.tsx b/src/app/(site)/u/[username]/page.tsx index 9bf8ecafff..206b579c3b 100644 --- a/src/app/(site)/u/[username]/page.tsx +++ b/src/app/(site)/u/[username]/page.tsx @@ -8,6 +8,7 @@ import { sendFriendRequest } from "@/actions/social"; import Link from "@/components/link"; import { ContentCard, EmptyState, OnlineBadge } from "@/components/public/ui"; import { CurrencyIcon } from "@/components/shared/currency-icon"; +import { ProfileImage } from "@/components/shared/profile-image"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { auth } from "@/lib/auth"; import { @@ -25,7 +26,6 @@ import { import { avatarImageUrl } from "@/lib/format"; import { formatDate } from "@/lib/format-date"; import styles from "./profile.module.css"; -import { ProfileImage } from "./profile-image"; type SearchParams = Promise<{ friend?: string; diff --git a/src/app/(site)/u/[username]/profile-image.tsx b/src/components/shared/profile-image.tsx similarity index 100% rename from src/app/(site)/u/[username]/profile-image.tsx rename to src/components/shared/profile-image.tsx diff --git a/src/i18n/dashboard-messages.test.ts b/src/i18n/dashboard-messages.test.ts new file mode 100644 index 0000000000..6be4b37b75 --- /dev/null +++ b/src/i18n/dashboard-messages.test.ts @@ -0,0 +1,53 @@ +import { readFileSync } from "node:fs"; +import { createTranslator } from "next-intl"; +import { expect, it } from "vitest"; +import en from "@/messages/en.json"; +import italian from "@/messages/it.json"; + +it.each([ + ["en", en], + ["it", italian], +] as const)("renders all dashboard messages in %s", (locale, messages) => { + const errors: unknown[] = []; + const t = createTranslator({ + locale, + messages: messages as typeof en, + namespace: "pages.myDashboard", + onError: (error) => errors.push(error), + }); + const keys = new Set( + ["credits", "duckets", "diamonds", "points"].map( + (key) => `currencies.${key}`, + ), + ); + for (const error of [ + "not_enough", + "no_referrals", + "bad_config", + "ratelimit", + "error", + ]) + keys.add(`errors.${error}`); + for (const file of ["page.tsx", "CopyReferralButton.tsx"]) { + for (const match of readFileSync( + `src/app/(site)/me/${file}`, + "utf8", + ).matchAll(/\bt\("([^"]+)"/g)) + keys.add(match[1]); + } + for (const key of keys) { + const message = key as Parameters[0]; + expect(t.has(message), key).toBe(true); + expect( + t(message, { + count: 3, + amount: "30", + currency: "Diamanti", + hotel: "Epicnabbo", + date: "06/09/2026", + }), + key, + ).not.toBe(""); + } + expect(errors).toEqual([]); +}); diff --git a/src/lib/services/user-dashboard.ts b/src/lib/services/user-dashboard.ts index e762a68eb5..22e451fdb9 100644 --- a/src/lib/services/user-dashboard.ts +++ b/src/lib/services/user-dashboard.ts @@ -1,5 +1,5 @@ import "server-only"; -import { and, asc, count, desc, eq, gt, or } from "drizzle-orm"; +import { and, asc, count, desc, eq, gt, inArray, or } from "drizzle-orm"; import { db, MessengerFriendships, @@ -8,6 +8,7 @@ import { User, UserReferrals, UsersBadges, + UsersCurrency, UsersSettings, } from "@/lib/db"; import { resolveHotelName } from "@/lib/hotel-name"; @@ -27,6 +28,7 @@ export async function loadUserDashboard(userId: number) { unreadCountRows, referralsRows, friends, + currencyRows, ] = await Promise.all([ db .select({ @@ -34,7 +36,6 @@ export async function loadUserDashboard(userId: number) { username: User.username, look: User.look, motto: User.motto, - rank: User.rank, credits: User.credits, accountCreated: User.accountCreated, lastLogin: User.lastLogin, @@ -132,6 +133,16 @@ export async function loadUserDashboard(userId: number) { ), ) .catch(() => []), + db + .select({ type: UsersCurrency.type, amount: UsersCurrency.amount }) + .from(UsersCurrency) + .where( + and( + eq(UsersCurrency.userId, userId), + inArray(UsersCurrency.type, [0, 5]), + ), + ) + .catch(() => null), ]); return { @@ -148,5 +159,6 @@ export async function loadUserDashboard(userId: number) { unreadCountRows, referralsRows, friends, + currencyRows, }; } diff --git a/src/messages/en.json b/src/messages/en.json index bb39fe5007..8ad5935d42 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -3475,6 +3475,66 @@ "description": "Try again later or contact an administrator." } } + }, + "myDashboard": { + "title": "My dashboard", + "loadError": "We could not load your dashboard. Please try again.", + "retry": "Try again", + "login": "Sign in", + "never": "Never", + "claimed": "Reward claimed successfully!", + "welcome": "Your space on {hotel}", + "noMotto": "No motto yet", + "registered": "Registered: {date}", + "lastOnline": "Last online: {date}", + "enter": "Enter {hotel}", + "quickLinks": "Quick links", + "messages": "Messages", + "profile": "My profile", + "settings": "Settings", + "shop": "Shop", + "unread": "{count} unread messages", + "wallet": "Your wallet", + "currencies": { + "credits": "Credits", + "duckets": "Duckets", + "diamonds": "Diamonds", + "points": "Points" + }, + "onlineFriends": "Online friends", + "allFriends": "All friends", + "noOnlineFriends": "None of your friends are online right now.", + "recentRooms": "New rooms", + "community": "Community", + "roomsHint": "The newest rooms created in the hotel.", + "noRooms": "No rooms available yet.", + "activity": "Your activity", + "achievements": "Achievement score", + "respects": "Respects received", + "friends": "Friends", + "lastLogin": "Last login", + "referrals": "Invite your friends", + "referralHint": "Share your link and earn rewards when new players register.", + "referralProgress": "Progress towards your reward", + "reward": "{amount} {currency} for every {count} new players.", + "rewardHint": "Invite {count} players to unlock your reward.", + "referralLink": "Your invitation link", + "claim": "Claim reward", + "remaining": "{count, plural, one {Invite # more player to unlock your reward.} other {Invite # more players to unlock your reward.}}", + "rules": "Invitation rules", + "rulesText": "Creating extra accounts to increase invitations can lead to removal of progress, currency and inventory, and a ban.", + "badges": "Equipped badges", + "noBadges": "You are not wearing any badges yet.", + "copy": "Copy link", + "copied": "Copied!", + "copyFailed": "Copy failed. Select the link and copy it manually.", + "errors": { + "not_enough": "You need more invitations to claim the reward.", + "no_referrals": "Share your link to invite your first friend.", + "bad_config": "The reward is not configured correctly. Contact the staff.", + "ratelimit": "Too many attempts. Wait a moment and try again.", + "error": "The reward could not be claimed. Please try again." + } } }, "toolbar": { diff --git a/src/messages/it.json b/src/messages/it.json index 31237ea120..15413e62eb 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -3442,6 +3442,66 @@ "description": "Riprova più tardi o contatta un amministratore." } } + }, + "myDashboard": { + "title": "La mia pagina", + "loadError": "Impossibile caricare la tua pagina. Riprova.", + "retry": "Riprova", + "login": "Accedi", + "never": "Mai", + "claimed": "Premio riscattato!", + "welcome": "Il tuo spazio su {hotel}", + "noMotto": "Nessun motto", + "registered": "Iscritto dal {date}", + "lastOnline": "Ultima presenza: {date}", + "enter": "Entra in {hotel}", + "quickLinks": "Collegamenti rapidi", + "messages": "Messaggi", + "profile": "Il mio profilo", + "settings": "Impostazioni", + "shop": "Negozio", + "unread": "{count} messaggi non letti", + "wallet": "Il tuo portafoglio", + "currencies": { + "credits": "Crediti", + "duckets": "Duckets", + "diamonds": "Diamanti", + "points": "Punti" + }, + "onlineFriends": "Amici online", + "allFriends": "Tutti gli amici", + "noOnlineFriends": "Nessuno dei tuoi amici è online al momento.", + "recentRooms": "Nuove stanze", + "community": "Community", + "roomsHint": "Le ultime stanze create nell’hotel.", + "noRooms": "Nessuna stanza disponibile.", + "activity": "La tua attività", + "achievements": "Punti traguardo", + "respects": "Rispetti ricevuti", + "friends": "Amici", + "lastLogin": "Ultimo accesso", + "referrals": "Invita i tuoi amici", + "referralHint": "Condividi il tuo link e ricevi premi quando si registrano nuovi giocatori.", + "referralProgress": "Progressi verso il premio", + "reward": "{amount} {currency} ogni {count} nuovi giocatori.", + "rewardHint": "Invita {count} giocatori per sbloccare il premio.", + "referralLink": "Il tuo link di invito", + "claim": "Riscatta premio", + "remaining": "{count, plural, one {Invita ancora # giocatore per sbloccare il premio.} other {Invita ancora # giocatori per sbloccare il premio.}}", + "rules": "Regole degli inviti", + "rulesText": "Creare altri account per aumentare gli inviti può comportare la rimozione di progressi, valute e inventario e il ban.", + "badges": "Distintivi indossati", + "noBadges": "Non stai ancora indossando distintivi.", + "copy": "Copia link", + "copied": "Copiato!", + "copyFailed": "Copia non riuscita. Seleziona il link e copialo manualmente.", + "errors": { + "not_enough": "Servono altri inviti per riscattare il premio.", + "no_referrals": "Condividi il link per invitare il tuo primo amico.", + "bad_config": "Il premio non è configurato correttamente. Contatta lo staff.", + "ratelimit": "Troppi tentativi. Attendi un momento e riprova.", + "error": "Non è stato possibile riscattare il premio. Riprova." + } } }, "toolbar": {