Register: auto sign-in to /me and speed/cleanup improvements
- Send the verification email after the response via after() so it never blocks sign-up - Invalidate the cached login lookup right after account creation so the automatic sign-in always finds the fresh row - Auto sign in with the submitted credentials and go straight to /me, with a fallback to /login?registered=1 if sign-in is refused (e.g. email verification required) - Cache the register page's online/latest user queries to cut DB load under traffic - Fix terms checkbox label double-toggle cancelling the selection - Add pages.register.redirecting translation to all locales
This commit is contained in:
1 parent
2d09a4a92c
commit
f25a26a93e
25 files changed
+143
-53
No files matched your search
+46
-24
@@ -1,10 +1,11 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { count, eq } from "drizzle-orm";
|
import { count, eq } from "drizzle-orm";
|
||||||
import { redirect } from "next/navigation";
|
import { after } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { sendVerification } from "@/actions/email-verify";
|
import { sendVerification } from "@/actions/email-verify";
|
||||||
import { hashPassword } from "@/lib/auth/password";
|
import { hashPassword } from "@/lib/auth/password";
|
||||||
|
import { invalidateKey } from "@/lib/cached-db";
|
||||||
import { db, User } from "@/lib/db";
|
import { db, User } from "@/lib/db";
|
||||||
import { logger } from "@/lib/logger";
|
import { logger } from "@/lib/logger";
|
||||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||||
@@ -36,10 +37,16 @@ const registerSchema = z.object({
|
|||||||
// A valid starter Habbo figure so the avatar renders in-client immediately.
|
// A valid starter Habbo figure so the avatar renders in-client immediately.
|
||||||
const DEFAULT_LOOK = "hr-100-.hd-180-1.ch-255-66.lg-280-110.sh-305-62";
|
const DEFAULT_LOOK = "hr-100-.hd-180-1.ch-255-66.lg-280-110.sh-305-62";
|
||||||
|
|
||||||
|
export interface RegisterState {
|
||||||
|
error: string | null;
|
||||||
|
ok: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export async function register(
|
export async function register(
|
||||||
_prevState: string | null,
|
_prevState: RegisterState,
|
||||||
formData: FormData,
|
formData: FormData,
|
||||||
): Promise<string | null> {
|
): Promise<RegisterState> {
|
||||||
|
const fail = (error: string): RegisterState => ({ error, ok: false });
|
||||||
const raw = {
|
const raw = {
|
||||||
username: String(formData.get("username") ?? "")
|
username: String(formData.get("username") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -61,11 +68,11 @@ export async function register(
|
|||||||
|
|
||||||
const parsed = registerSchema.safeParse(raw);
|
const parsed = registerSchema.safeParse(raw);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return parsed.error.issues[0]?.message ?? "Invalid input";
|
return fail(parsed.error.issues[0]?.message ?? "Invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.data.password !== parsed.data.passwordConfirmation) {
|
if (parsed.data.password !== parsed.data.passwordConfirmation) {
|
||||||
return "Passwords do not match";
|
return fail("Passwords do not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, mail, password, look } = parsed.data;
|
const { username, mail, password, look } = parsed.data;
|
||||||
@@ -74,7 +81,9 @@ export async function register(
|
|||||||
|
|
||||||
// Throttle sign-ups per IP (5 per 10 minutes) to curb account spam.
|
// Throttle sign-ups per IP (5 per 10 minutes) to curb account spam.
|
||||||
if (!(await rateLimit(`register:${ip}`, 5, 10 * 60_000)).ok) {
|
if (!(await rateLimit(`register:${ip}`, 5, 10 * 60_000)).ok) {
|
||||||
return "Too many sign-up attempts. Please wait a few minutes and try again.";
|
return fail(
|
||||||
|
"Too many sign-up attempts. Please wait a few minutes and try again.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CAPTCHA (Turnstile / reCAPTCHA) — only enforced when configured in settings.
|
// CAPTCHA (Turnstile / reCAPTCHA) — only enforced when configured in settings.
|
||||||
@@ -82,18 +91,18 @@ export async function register(
|
|||||||
if (cfg.provider !== "none") {
|
if (cfg.provider !== "none") {
|
||||||
const token = String(formData.get(cfg.field) ?? "").normalize("NFC");
|
const token = String(formData.get(cfg.field) ?? "").normalize("NFC");
|
||||||
if (!(await verifyCaptcha(token, ip)))
|
if (!(await verifyCaptcha(token, ip)))
|
||||||
return "Captcha verification failed. Please try again.";
|
return fail("Captcha verification failed. Please try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terms acceptance check.
|
// Terms acceptance check.
|
||||||
if (!raw.termsAccepted)
|
if (!raw.termsAccepted)
|
||||||
return "You must accept the terms and conditions to register.";
|
return fail("You must accept the terms and conditions to register.");
|
||||||
|
|
||||||
// VPN/proxy block (only when enabled in /admin/vpn).
|
// VPN/proxy block (only when enabled in /admin/vpn).
|
||||||
if ((await checkVpn(ip)).blocked) {
|
if ((await checkVpn(ip)).blocked) {
|
||||||
return (
|
return fail(
|
||||||
(await siteSettings.get("vpn_block_message", "")) ||
|
(await siteSettings.get("vpn_block_message", "")) ||
|
||||||
"Registrations from VPN/proxy connections are not allowed."
|
"Registrations from VPN/proxy connections are not allowed.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +115,9 @@ export async function register(
|
|||||||
.where(eq(User.ipRegister, ip))
|
.where(eq(User.ipRegister, ip))
|
||||||
.catch(() => [{ total: 0 }]);
|
.catch(() => [{ total: 0 }]);
|
||||||
if (Number(row?.total ?? 0) >= max)
|
if (Number(row?.total ?? 0) >= max)
|
||||||
return "You have reached the maximum number of accounts for your connection.";
|
return fail(
|
||||||
|
"You have reached the maximum number of accounts for your connection.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uniqueness check.
|
// Uniqueness check.
|
||||||
@@ -116,10 +127,10 @@ export async function register(
|
|||||||
.from(User)
|
.from(User)
|
||||||
.where(eq(User.username, username))
|
.where(eq(User.username, username))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
if (existing) return "That username is already taken";
|
if (existing) return fail("That username is already taken");
|
||||||
} catch {
|
} catch {
|
||||||
logger.warn("Username uniqueness check failed during registration");
|
logger.warn("Username uniqueness check failed during registration");
|
||||||
return "Registration is temporarily unavailable";
|
return fail("Registration is temporarily unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
@@ -134,25 +145,36 @@ export async function register(
|
|||||||
look,
|
look,
|
||||||
termsAccepted: raw.termsAccepted,
|
termsAccepted: raw.termsAccepted,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasEmail) {
|
|
||||||
try {
|
|
||||||
await sendVerification(mail);
|
|
||||||
} catch {
|
|
||||||
logger.warn("Failed to send verification email after registration");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code = (err as { cause?: { code?: string } }).cause?.code;
|
const code = (err as { cause?: { code?: string } }).cause?.code;
|
||||||
if (code === "ER_DUP_ENTRY") {
|
if (code === "ER_DUP_ENTRY") {
|
||||||
return "That username is already taken";
|
return fail("That username is already taken");
|
||||||
}
|
}
|
||||||
logger.error("Account creation failed", {
|
logger.error("Account creation failed", {
|
||||||
code,
|
code,
|
||||||
message: err instanceof Error ? err.message : String(err),
|
message: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
return "Could not create the account. Please try again or contact staff.";
|
return fail(
|
||||||
|
"Could not create the account. Please try again or contact staff.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect("/login?registered=1");
|
// The login lookup is cached for 15s — drop any stale entry so the
|
||||||
|
// immediate auto sign-in sees the fresh row.
|
||||||
|
await invalidateKey(`login:user:${username}`);
|
||||||
|
|
||||||
|
// Verification email must never block the sign-up response — it is sent
|
||||||
|
// after the response is flushed (no-op when mail is unconfigured).
|
||||||
|
if (hasEmail) {
|
||||||
|
after(async () => {
|
||||||
|
try {
|
||||||
|
await sendVerification(mail);
|
||||||
|
} catch {
|
||||||
|
logger.warn("Failed to send verification email after registration");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client auto signs in with these credentials and navigates to /me.
|
||||||
|
return { error: null, ok: true };
|
||||||
}
|
}
|
||||||
@@ -33,18 +33,20 @@ export default async function RegisterPage() {
|
|||||||
.where(eq(User.online, "1"))
|
.where(eq(User.online, "1"))
|
||||||
.then((rows) => rows[0]?.total ?? 0),
|
.then((rows) => rows[0]?.total ?? 0),
|
||||||
).catch(() => 0),
|
).catch(() => 0),
|
||||||
db
|
cached("register_online_users", 10_000, () =>
|
||||||
.select({ username: User.username, look: User.look })
|
db
|
||||||
.from(User)
|
.select({ username: User.username, look: User.look })
|
||||||
.where(eq(User.online, "1"))
|
.from(User)
|
||||||
.limit(8)
|
.where(eq(User.online, "1"))
|
||||||
.catch(() => []),
|
.limit(8),
|
||||||
db
|
).catch(() => []),
|
||||||
.select({ username: User.username, look: User.look })
|
cached("register_latest_users", 30_000, () =>
|
||||||
.from(User)
|
db
|
||||||
.orderBy(desc(User.accountCreated))
|
.select({ username: User.username, look: User.look })
|
||||||
.limit(8)
|
.from(User)
|
||||||
.catch(() => []),
|
.orderBy(desc(User.accountCreated))
|
||||||
|
.limit(8),
|
||||||
|
).catch(() => []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useActionState, useState } from "react";
|
import { useActionState, useEffect, useRef, useState } from "react";
|
||||||
import { register } from "@/actions/register";
|
import { type RegisterState, register } from "@/actions/register";
|
||||||
|
import { signInWithTransientRetry } from "@/lib/auth/sign-in-retry";
|
||||||
|
|
||||||
interface RegisterFormProps {
|
interface RegisterFormProps {
|
||||||
hotelName: string;
|
hotelName: string;
|
||||||
@@ -40,13 +42,56 @@ export function RegisterForm({
|
|||||||
}: RegisterFormProps) {
|
}: RegisterFormProps) {
|
||||||
const t = useTranslations("pages.register");
|
const t = useTranslations("pages.register");
|
||||||
const showCaptcha = captcha.provider !== "none" && !!captcha.siteKey;
|
const showCaptcha = captcha.provider !== "none" && !!captcha.siteKey;
|
||||||
const [serverError, formAction, isPending] = useActionState(register, null);
|
const [state, formAction, isPending] = useActionState<
|
||||||
|
RegisterState,
|
||||||
|
FormData
|
||||||
|
>(register, { error: null, ok: false });
|
||||||
const [termsAccepted, setTermsAccepted] = useState(false);
|
const [termsAccepted, setTermsAccepted] = useState(false);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
|
const [signingIn, setSigningIn] = useState(false);
|
||||||
|
const credentialsRef = useRef<{ username: string; password: string } | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const autoLoginStartedRef = useRef(false);
|
||||||
const strength = passwordStrength(password);
|
const strength = passwordStrength(password);
|
||||||
|
|
||||||
|
// Successful sign-up: sign in with the submitted credentials and go
|
||||||
|
// straight to /me. If the automatic sign-in is refused (e.g. email
|
||||||
|
// verification required), fall back to the regular login page.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!state.ok || autoLoginStartedRef.current) return;
|
||||||
|
autoLoginStartedRef.current = true;
|
||||||
|
|
||||||
|
const creds = credentialsRef.current;
|
||||||
|
if (!creds) {
|
||||||
|
window.location.href = "/login?registered=1";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
setSigningIn(true);
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await signInWithTransientRetry(() =>
|
||||||
|
signIn("credentials", { ...creds, code: "", redirect: false }),
|
||||||
|
);
|
||||||
|
if (!cancelled && res && !res.error) {
|
||||||
|
window.location.href = "/me";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* fall through to fallback below */
|
||||||
|
}
|
||||||
|
if (!cancelled) window.location.href = "/login?registered=1";
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [state.ok]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-[800px]">
|
<div className="mx-auto w-full max-w-[800px]">
|
||||||
{showCaptcha && captcha.provider === "turnstile" ? (
|
{showCaptcha && captcha.provider === "turnstile" ? (
|
||||||
@@ -112,7 +157,13 @@ export function RegisterForm({
|
|||||||
|
|
||||||
{/* Form */}
|
{/* Form */}
|
||||||
<form
|
<form
|
||||||
action={formAction}
|
action={(fd) => {
|
||||||
|
credentialsRef.current = {
|
||||||
|
username: String(fd.get("username") ?? "").trim(),
|
||||||
|
password: String(fd.get("password") ?? ""),
|
||||||
|
};
|
||||||
|
formAction(fd);
|
||||||
|
}}
|
||||||
className="p-6 flex flex-col gap-6"
|
className="p-6 flex flex-col gap-6"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
@@ -120,12 +171,12 @@ export function RegisterForm({
|
|||||||
borderRadius: "0 0 12px 12px",
|
borderRadius: "0 0 12px 12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(error || serverError) && (
|
{(error || state.error) && (
|
||||||
<div
|
<div
|
||||||
className="animate-fade-in-up p-3 rounded-lg text-sm font-semibold text-white"
|
className="animate-fade-in-up p-3 rounded-lg text-sm font-semibold text-white"
|
||||||
style={{ backgroundColor: "var(--color-danger)" }}
|
style={{ backgroundColor: "var(--color-danger)" }}
|
||||||
>
|
>
|
||||||
{error || serverError}
|
{error || state.error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -301,14 +352,7 @@ export function RegisterForm({
|
|||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="terms"
|
htmlFor="terms"
|
||||||
className="font-medium text-sm text-[var(--color-text-readable)]"
|
className="font-medium text-sm text-[var(--color-text-readable)] cursor-pointer"
|
||||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault();
|
|
||||||
setTermsAccepted(!termsAccepted);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t("termsAccept", { hotel: hotelName })}
|
{t("termsAccept", { hotel: hotelName })}
|
||||||
</label>
|
</label>
|
||||||
@@ -332,7 +376,7 @@ export function RegisterForm({
|
|||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isPending}
|
disabled={isPending || signingIn}
|
||||||
className="btn-shine w-full rounded-xl font-extrabold text-sm py-3.5 transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg disabled:opacity-60"
|
className="btn-shine w-full rounded-xl font-extrabold text-sm py-3.5 transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg disabled:opacity-60"
|
||||||
style={{
|
style={{
|
||||||
background: "var(--color-primary)",
|
background: "var(--color-primary)",
|
||||||
@@ -341,7 +385,7 @@ export function RegisterForm({
|
|||||||
"0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
|
"0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending || signingIn ? (
|
||||||
<span className="inline-flex items-center justify-center gap-2">
|
<span className="inline-flex items-center justify-center gap-2">
|
||||||
<svg
|
<svg
|
||||||
className="animate-spin h-4 w-4"
|
className="animate-spin h-4 w-4"
|
||||||
@@ -364,7 +408,7 @@ export function RegisterForm({
|
|||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{t("creatingAccount")}
|
{signingIn ? t("redirecting") : t("creatingAccount")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
t("createAccount")
|
t("createAccount")
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Аз съм на 18+ и приемам условията и правилата на {hotel}.",
|
"termsAccept": "Аз съм на 18+ и приемам условията и правилата на {hotel}.",
|
||||||
"createAccount": "Създаване на акаунт",
|
"createAccount": "Създаване на акаунт",
|
||||||
"creatingAccount": "Създаване на акаунт...",
|
"creatingAccount": "Създаване на акаунт...",
|
||||||
|
"redirecting": "Прехвърляне към вашия акаунт...",
|
||||||
"alreadyHaveAccount": "Вече имате акаунт? Вход!",
|
"alreadyHaveAccount": "Вече имате акаунт? Вход!",
|
||||||
"register": "Регистрирайте се",
|
"register": "Регистрирайте се",
|
||||||
"termsError": "Трябва да приемете условията и правилата, за да се регистрирате.",
|
"termsError": "Трябва да приемете условията и правилата, за да се регистрирате.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Je mi 18+ a souhlasím s podmínkami a pravidly {hotel}.",
|
"termsAccept": "Je mi 18+ a souhlasím s podmínkami a pravidly {hotel}.",
|
||||||
"createAccount": "Vytvořit účet",
|
"createAccount": "Vytvořit účet",
|
||||||
"creatingAccount": "Vytváření účtu...",
|
"creatingAccount": "Vytváření účtu...",
|
||||||
|
"redirecting": "Přesměrovávám na váš účet...",
|
||||||
"alreadyHaveAccount": "Už máte účet? Přihlášení!",
|
"alreadyHaveAccount": "Už máte účet? Přihlášení!",
|
||||||
"register": "Zaregistrujte se",
|
"register": "Zaregistrujte se",
|
||||||
"termsError": "Chcete-li se zaregistrovat, musíte přijmout podmínky a pravidla.",
|
"termsError": "Chcete-li se zaregistrovat, musíte přijmout podmínky a pravidla.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Jeg er 18+ og accepterer vilkårene og reglerne for {hotel}.",
|
"termsAccept": "Jeg er 18+ og accepterer vilkårene og reglerne for {hotel}.",
|
||||||
"createAccount": "Opret konto",
|
"createAccount": "Opret konto",
|
||||||
"creatingAccount": "Opretter konto...",
|
"creatingAccount": "Opretter konto...",
|
||||||
|
"redirecting": "Omdirigerer til din konto...",
|
||||||
"alreadyHaveAccount": "Har du allerede en konto? Log ind!",
|
"alreadyHaveAccount": "Har du allerede en konto? Log ind!",
|
||||||
"register": "Tilmeld dig",
|
"register": "Tilmeld dig",
|
||||||
"termsError": "Du skal acceptere vilkårene og reglerne for at registrere dig.",
|
"termsError": "Du skal acceptere vilkårene og reglerne for at registrere dig.",
|
||||||
|
|||||||
@@ -724,6 +724,7 @@
|
|||||||
"termsAccept": "Ich bin 18+ und akzeptiere die {hotel} AGB & Regeln.",
|
"termsAccept": "Ich bin 18+ und akzeptiere die {hotel} AGB & Regeln.",
|
||||||
"createAccount": "Konto erstellen",
|
"createAccount": "Konto erstellen",
|
||||||
"creatingAccount": "Konto wird erstellt...",
|
"creatingAccount": "Konto wird erstellt...",
|
||||||
|
"redirecting": "Weiterleitung zu deinem Account...",
|
||||||
"alreadyHaveAccount": "Bereits ein Konto? Anmelden!",
|
"alreadyHaveAccount": "Bereits ein Konto? Anmelden!",
|
||||||
"register": "Registrieren",
|
"register": "Registrieren",
|
||||||
"termsError": "Du musst die AGB & Regeln akzeptieren, um dich zu registrieren.",
|
"termsError": "Du musst die AGB & Regeln akzeptieren, um dich zu registrieren.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Είμαι 18+ και αποδέχομαι τους όρους και τους κανόνες του {hotel}.",
|
"termsAccept": "Είμαι 18+ και αποδέχομαι τους όρους και τους κανόνες του {hotel}.",
|
||||||
"createAccount": "Δημιουργία λογαριασμού",
|
"createAccount": "Δημιουργία λογαριασμού",
|
||||||
"creatingAccount": "Δημιουργία λογαριασμού...",
|
"creatingAccount": "Δημιουργία λογαριασμού...",
|
||||||
|
"redirecting": "Ανακατεύθυνση στον λογαριασμό σας...",
|
||||||
"alreadyHaveAccount": "Έχετε ήδη λογαριασμό; Σύνδεση!",
|
"alreadyHaveAccount": "Έχετε ήδη λογαριασμό; Σύνδεση!",
|
||||||
"register": "Εγγραφή",
|
"register": "Εγγραφή",
|
||||||
"termsError": "Πρέπει να αποδεχτείτε τους όρους και τους κανόνες για να εγγραφείτε.",
|
"termsError": "Πρέπει να αποδεχτείτε τους όρους και τους κανόνες για να εγγραφείτε.",
|
||||||
|
|||||||
@@ -903,6 +903,7 @@
|
|||||||
"termsAccept": "I am 18+ and accept the {hotel} terms & rules.",
|
"termsAccept": "I am 18+ and accept the {hotel} terms & rules.",
|
||||||
"createAccount": "Create account",
|
"createAccount": "Create account",
|
||||||
"creatingAccount": "Creating account...",
|
"creatingAccount": "Creating account...",
|
||||||
|
"redirecting": "Redirecting to your account...",
|
||||||
"alreadyHaveAccount": "Already have an account? Login!",
|
"alreadyHaveAccount": "Already have an account? Login!",
|
||||||
"alreadyHaveAccountPre": "Already have an account?",
|
"alreadyHaveAccountPre": "Already have an account?",
|
||||||
"alreadyHaveAccountLink": "Sign in",
|
"alreadyHaveAccountLink": "Sign in",
|
||||||
|
|||||||
@@ -724,6 +724,7 @@
|
|||||||
"termsAccept": "Soy mayor de 18 años y acepto los términos y reglas de {hotel}.",
|
"termsAccept": "Soy mayor de 18 años y acepto los términos y reglas de {hotel}.",
|
||||||
"createAccount": "Crear cuenta",
|
"createAccount": "Crear cuenta",
|
||||||
"creatingAccount": "Creando cuenta...",
|
"creatingAccount": "Creando cuenta...",
|
||||||
|
"redirecting": "Redirigiendo a tu cuenta...",
|
||||||
"alreadyHaveAccount": "¿Ya tienes una cuenta? ¡Inicia sesión!",
|
"alreadyHaveAccount": "¿Ya tienes una cuenta? ¡Inicia sesión!",
|
||||||
"register": "Registrarse",
|
"register": "Registrarse",
|
||||||
"termsError": "Debes aceptar los términos y reglas para registrarte.",
|
"termsError": "Debes aceptar los términos y reglas para registrarte.",
|
||||||
|
|||||||
@@ -724,6 +724,7 @@
|
|||||||
"termsAccept": "J'ai 18 ans ou plus et j'accepte les conditions et règles de {hotel}.",
|
"termsAccept": "J'ai 18 ans ou plus et j'accepte les conditions et règles de {hotel}.",
|
||||||
"createAccount": "Créer un compte",
|
"createAccount": "Créer un compte",
|
||||||
"creatingAccount": "Création du compte...",
|
"creatingAccount": "Création du compte...",
|
||||||
|
"redirecting": "Redirection vers votre compte...",
|
||||||
"alreadyHaveAccount": "Vous avez déjà un compte ? Connectez-vous !",
|
"alreadyHaveAccount": "Vous avez déjà un compte ? Connectez-vous !",
|
||||||
"register": "S'inscrire",
|
"register": "S'inscrire",
|
||||||
"termsError": "Vous devez accepter les conditions et règles pour vous inscrire.",
|
"termsError": "Vous devez accepter les conditions et règles pour vous inscrire.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Imam 18+ godina i prihvaćam odredbe i pravila hotela {hotel}.",
|
"termsAccept": "Imam 18+ godina i prihvaćam odredbe i pravila hotela {hotel}.",
|
||||||
"createAccount": "Napravi račun",
|
"createAccount": "Napravi račun",
|
||||||
"creatingAccount": "Izrada računa...",
|
"creatingAccount": "Izrada računa...",
|
||||||
|
"redirecting": "Preusmjeravanje na vaš račun...",
|
||||||
"alreadyHaveAccount": "Već imate račun? Prijava!",
|
"alreadyHaveAccount": "Već imate račun? Prijava!",
|
||||||
"register": "Registrirajte se",
|
"register": "Registrirajte se",
|
||||||
"termsError": "Za registraciju morate prihvatiti uvjete i pravila.",
|
"termsError": "Za registraciju morate prihvatiti uvjete i pravila.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "18 évesnél idősebb vagyok, és elfogadom a {hotel} feltételeit és szabályait.",
|
"termsAccept": "18 évesnél idősebb vagyok, és elfogadom a {hotel} feltételeit és szabályait.",
|
||||||
"createAccount": "Hozzon létre fiókot",
|
"createAccount": "Hozzon létre fiókot",
|
||||||
"creatingAccount": "Fiók létrehozása...",
|
"creatingAccount": "Fiók létrehozása...",
|
||||||
|
"redirecting": "Átirányítás a fiókjához...",
|
||||||
"alreadyHaveAccount": "Már van fiókja? Bejelentkezés!",
|
"alreadyHaveAccount": "Már van fiókja? Bejelentkezés!",
|
||||||
"register": "Regisztráció",
|
"register": "Regisztráció",
|
||||||
"termsError": "A regisztrációhoz el kell fogadnia a feltételeket és szabályokat.",
|
"termsError": "A regisztrációhoz el kell fogadnia a feltételeket és szabályokat.",
|
||||||
|
|||||||
@@ -877,6 +877,7 @@
|
|||||||
"termsAccept": "Ho 18+ anni e accetto i termini e regole di {hotel}.",
|
"termsAccept": "Ho 18+ anni e accetto i termini e regole di {hotel}.",
|
||||||
"createAccount": "Crea account",
|
"createAccount": "Crea account",
|
||||||
"creatingAccount": "Creazione account...",
|
"creatingAccount": "Creazione account...",
|
||||||
|
"redirecting": "Reindirizzamento al tuo account...",
|
||||||
"alreadyHaveAccount": "Hai già un account? Accedi!",
|
"alreadyHaveAccount": "Hai già un account? Accedi!",
|
||||||
"register": "Registrati",
|
"register": "Registrati",
|
||||||
"termsError": "Devi accettare i termini e regole per registrarti.",
|
"termsError": "Devi accettare i termini e regole per registrarti.",
|
||||||
|
|||||||
@@ -907,6 +907,7 @@
|
|||||||
"termsAccept": "Ik ben 18+ en accepteer de {hotel} voorwaarden & regels.",
|
"termsAccept": "Ik ben 18+ en accepteer de {hotel} voorwaarden & regels.",
|
||||||
"createAccount": "Account aanmaken",
|
"createAccount": "Account aanmaken",
|
||||||
"creatingAccount": "Account wordt aangemaakt...",
|
"creatingAccount": "Account wordt aangemaakt...",
|
||||||
|
"redirecting": "Doorgaan naar je account...",
|
||||||
"alreadyHaveAccount": "Al een account? Inloggen!",
|
"alreadyHaveAccount": "Al een account? Inloggen!",
|
||||||
"register": "Registreren",
|
"register": "Registreren",
|
||||||
"alreadyHaveAccountPre": "Al een account?",
|
"alreadyHaveAccountPre": "Al een account?",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Jeg er 18+ og godtar vilkårene og reglene for {hotel}.",
|
"termsAccept": "Jeg er 18+ og godtar vilkårene og reglene for {hotel}.",
|
||||||
"createAccount": "Opprett konto",
|
"createAccount": "Opprett konto",
|
||||||
"creatingAccount": "Oppretter konto ...",
|
"creatingAccount": "Oppretter konto ...",
|
||||||
|
"redirecting": "Videre til kontoen din...",
|
||||||
"alreadyHaveAccount": "Har du allerede en konto? Logg inn!",
|
"alreadyHaveAccount": "Har du allerede en konto? Logg inn!",
|
||||||
"register": "Registrer deg",
|
"register": "Registrer deg",
|
||||||
"termsError": "Du må godta vilkårene og reglene for å registrere deg.",
|
"termsError": "Du må godta vilkårene og reglene for å registrere deg.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Mam ukończone 18 lat i akceptuję warunki i zasady hotelu {hotel}.",
|
"termsAccept": "Mam ukończone 18 lat i akceptuję warunki i zasady hotelu {hotel}.",
|
||||||
"createAccount": "Utwórz konto",
|
"createAccount": "Utwórz konto",
|
||||||
"creatingAccount": "Tworzenie konta...",
|
"creatingAccount": "Tworzenie konta...",
|
||||||
|
"redirecting": "Przekierowywanie na twoje konto...",
|
||||||
"alreadyHaveAccount": "Masz już konto? Login!",
|
"alreadyHaveAccount": "Masz już konto? Login!",
|
||||||
"register": "Zarejestruj się",
|
"register": "Zarejestruj się",
|
||||||
"termsError": "Aby się zarejestrować, musisz zaakceptować regulamin.",
|
"termsError": "Aby się zarejestrować, musisz zaakceptować regulamin.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Tenho mais de 18 anos e aceito os termos e regras do {hotel}.",
|
"termsAccept": "Tenho mais de 18 anos e aceito os termos e regras do {hotel}.",
|
||||||
"createAccount": "Criar conta",
|
"createAccount": "Criar conta",
|
||||||
"creatingAccount": "Criando conta...",
|
"creatingAccount": "Criando conta...",
|
||||||
|
"redirecting": "A redirecionar para a sua conta...",
|
||||||
"alreadyHaveAccount": "Já tem uma conta? Conecte-se!",
|
"alreadyHaveAccount": "Já tem uma conta? Conecte-se!",
|
||||||
"register": "Cadastre-se",
|
"register": "Cadastre-se",
|
||||||
"termsError": "Você deve aceitar os termos e regras para se registrar.",
|
"termsError": "Você deve aceitar os termos e regras para se registrar.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Am peste 18 ani și accept termenii și regulile {hotel}.",
|
"termsAccept": "Am peste 18 ani și accept termenii și regulile {hotel}.",
|
||||||
"createAccount": "Creați cont",
|
"createAccount": "Creați cont",
|
||||||
"creatingAccount": "Se creează cont...",
|
"creatingAccount": "Se creează cont...",
|
||||||
|
"redirecting": "Se redirecționează către contul tău...",
|
||||||
"alreadyHaveAccount": "Aveți deja un cont? Log in!",
|
"alreadyHaveAccount": "Aveți deja un cont? Log in!",
|
||||||
"register": "Înregistrează-te",
|
"register": "Înregistrează-te",
|
||||||
"termsError": "Trebuie să acceptați termenii și regulile pentru a vă înregistra.",
|
"termsError": "Trebuie să acceptați termenii și regulile pentru a vă înregistra.",
|
||||||
|
|||||||
@@ -752,6 +752,7 @@
|
|||||||
"termsAccept": "Мне 18+ и я принимаю условия и правила {hotel}.",
|
"termsAccept": "Мне 18+ и я принимаю условия и правила {hotel}.",
|
||||||
"createAccount": "Создать аккаунт",
|
"createAccount": "Создать аккаунт",
|
||||||
"creatingAccount": "Создание учетной записи...",
|
"creatingAccount": "Создание учетной записи...",
|
||||||
|
"redirecting": "Переход в ваш аккаунт...",
|
||||||
"alreadyHaveAccount": "Уже есть аккаунт? Войти!",
|
"alreadyHaveAccount": "Уже есть аккаунт? Войти!",
|
||||||
"register": "Регистрация",
|
"register": "Регистрация",
|
||||||
"termsError": "Чтобы зарегистрироваться, вы должны принять условия и правила.",
|
"termsError": "Чтобы зарегистрироваться, вы должны принять условия и правила.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Mám 18+ a súhlasím so zmluvnými podmienkami a pravidlami {hotel}.",
|
"termsAccept": "Mám 18+ a súhlasím so zmluvnými podmienkami a pravidlami {hotel}.",
|
||||||
"createAccount": "Vytvoriť účet",
|
"createAccount": "Vytvoriť účet",
|
||||||
"creatingAccount": "Vytvára sa účet...",
|
"creatingAccount": "Vytvára sa účet...",
|
||||||
|
"redirecting": "Presmerovávam na váš účet...",
|
||||||
"alreadyHaveAccount": "Už máte účet? Prihláste sa!",
|
"alreadyHaveAccount": "Už máte účet? Prihláste sa!",
|
||||||
"register": "Zaregistrujte sa",
|
"register": "Zaregistrujte sa",
|
||||||
"termsError": "Ak sa chcete zaregistrovať, musíte prijať podmienky a pravidlá.",
|
"termsError": "Ak sa chcete zaregistrovať, musíte prijať podmienky a pravidlá.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "I am 18+ and accept the {hotel} terms & rules.",
|
"termsAccept": "I am 18+ and accept the {hotel} terms & rules.",
|
||||||
"createAccount": "Креирајте налог",
|
"createAccount": "Креирајте налог",
|
||||||
"creatingAccount": "Прављење налога...",
|
"creatingAccount": "Прављење налога...",
|
||||||
|
"redirecting": "Преусмеравање на ваш налог...",
|
||||||
"alreadyHaveAccount": "Већ имате налог? Пријавите се!",
|
"alreadyHaveAccount": "Већ имате налог? Пријавите се!",
|
||||||
"register": "Региструјте се",
|
"register": "Региструјте се",
|
||||||
"termsError": "Морате прихватити услове и правила да бисте се регистровали.",
|
"termsError": "Морате прихватити услове и правила да бисте се регистровали.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Jag är 18+ och accepterar villkoren och reglerna för {hotel}.",
|
"termsAccept": "Jag är 18+ och accepterar villkoren och reglerna för {hotel}.",
|
||||||
"createAccount": "Skapa konto",
|
"createAccount": "Skapa konto",
|
||||||
"creatingAccount": "Skapar konto...",
|
"creatingAccount": "Skapar konto...",
|
||||||
|
"redirecting": "Vidare till ditt konto...",
|
||||||
"alreadyHaveAccount": "Har du redan ett konto? Inloggning!",
|
"alreadyHaveAccount": "Har du redan ett konto? Inloggning!",
|
||||||
"register": "Registrera dig",
|
"register": "Registrera dig",
|
||||||
"termsError": "Du måste acceptera villkoren och reglerna för att registrera dig.",
|
"termsError": "Du måste acceptera villkoren och reglerna för att registrera dig.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "18 yaşındayım ve {hotel} şartlarını ve kurallarını kabul ediyorum.",
|
"termsAccept": "18 yaşındayım ve {hotel} şartlarını ve kurallarını kabul ediyorum.",
|
||||||
"createAccount": "Hesap oluştur",
|
"createAccount": "Hesap oluştur",
|
||||||
"creatingAccount": "Hesap oluşturuluyor...",
|
"creatingAccount": "Hesap oluşturuluyor...",
|
||||||
|
"redirecting": "Hesabınıza yönlendiriliyorsunuz...",
|
||||||
"alreadyHaveAccount": "Zaten bir hesabınız var mı? Giriş yapmak!",
|
"alreadyHaveAccount": "Zaten bir hesabınız var mı? Giriş yapmak!",
|
||||||
"register": "Kayıt ol",
|
"register": "Kayıt ol",
|
||||||
"termsError": "Kayıt olmak için şartları ve kuralları kabul etmelisiniz.",
|
"termsError": "Kayıt olmak için şartları ve kuralları kabul etmelisiniz.",
|
||||||
|
|||||||
@@ -754,6 +754,7 @@
|
|||||||
"termsAccept": "Мені 18+ і я приймаю умови та правила {hotel}.",
|
"termsAccept": "Мені 18+ і я приймаю умови та правила {hotel}.",
|
||||||
"createAccount": "Створити акаунт",
|
"createAccount": "Створити акаунт",
|
||||||
"creatingAccount": "Створення облікового запису...",
|
"creatingAccount": "Створення облікового запису...",
|
||||||
|
"redirecting": "Перехід до вашого акаунту...",
|
||||||
"alreadyHaveAccount": "Вже маєте акаунт? Вхід!",
|
"alreadyHaveAccount": "Вже маєте акаунт? Вхід!",
|
||||||
"register": "зареєструватися",
|
"register": "зареєструватися",
|
||||||
"termsError": "Ви повинні прийняти умови та правила, щоб зареєструватися.",
|
"termsError": "Ви повинні прийняти умови та правила, щоб зареєструватися.",
|
||||||
|
|||||||
Reference in new issue
Block a user