🆙 Small update to the login texts

This commit is contained in:
duckietm
2026-05-06 08:32:36 +02:00
parent 606afa7792
commit 793c260fc5
+75 -64
View File
@@ -15,6 +15,7 @@ import flagTr from '../../assets/images/flag_icon/flag_icon_tr.png';
import { applyTextTranslationLocale } from '../../hooks/translation/useTranslation';
import { NewsWindow } from './components/NewsWindow';
import { TurnstileWidget } from './TurnstileWidget';
import { t } from './utils/i18n';
type DialogMode = 'login' | 'register' | 'forgot';
type LoginLocale = { code: string; file: string; label: string; flag: string };
@@ -194,8 +195,16 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
const [ localeApplying, setLocaleApplying ] = useState(false);
const [ localeError, setLocaleError ] = useState('');
const [ loginViewConfig, setLoginViewConfig ] = useState<Record<string, unknown>>(() => GetConfigurationValue<Record<string, unknown>>('loginview', {}));
const [ , setLocalizationVersion ] = useState(0);
const submitTimeRef = useRef(0);
useEffect(() =>
{
const refreshLocalization = () => setLocalizationVersion(value => (value + 1));
window.addEventListener('nitro-localization-updated', refreshLocalization);
return () => window.removeEventListener('nitro-localization-updated', refreshLocalization);
}, []);
const loginImages = useMemo<Record<string, string>>(() =>
{
const configured = (loginViewConfig?.['images'] as Record<string, string>) ?? {};
@@ -413,19 +422,19 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
if(state.lockedUntil > nowTs)
{
const remaining = Math.ceil((state.lockedUntil - nowTs) / 1000);
setError(`Too many attempts. Try again in ${ remaining }s.`);
setError(t('nitro.login.error.too_many_attempts', 'Too many attempts. Try again in %seconds%s.', [ 'seconds' ], [ String(remaining) ]));
return;
}
if(!username.trim() || !password)
{
setError('Please enter both your Habbo name and password.');
setError(t('nitro.login.error.missing_credentials', 'Please enter both your Habbo name and password.'));
return;
}
if(turnstileEnabled && !loginTurnstileToken)
{
setError('Please complete the security check.');
setError(t('nitro.login.error.turnstile', 'Please complete the security check.'));
return;
}
@@ -453,14 +462,14 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
}
recordFailure();
const message = typeof payload.error === 'string' ? payload.error : 'Invalid Habbo name or password.';
const message = typeof payload.error === 'string' ? payload.error : t('nitro.login.error.invalid_credentials', 'Invalid Habbo name or password.');
setError(message);
resetLoginTurnstile();
}
catch(err)
{
recordFailure();
setError('Unable to reach the login service. Please try again.');
setError(t('nitro.login.error.login_unreachable', 'Unable to reach the login service. Please try again.'));
resetLoginTurnstile();
}
finally
@@ -497,7 +506,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
const { ok, status, payload } = await postJson(checkEmailUrl, { email });
const result = interpretAvailability(ok, status, payload);
if(result.available) return { available: true };
return { available: false, error: result.error || 'This email is already in use.' };
return { available: false, error: result.error || t('nitro.login.error.email_taken', 'This email is already in use.') };
}
catch
{
@@ -512,7 +521,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
const { ok, status, payload } = await postJson(checkUsernameUrl, { username });
const result = interpretAvailability(ok, status, payload);
if(result.available) return { available: true };
return { available: false, error: result.error || 'This Habbo name is already taken.' };
return { available: false, error: result.error || t('nitro.login.error.username_taken', 'This Habbo name is already taken.') };
}
catch
{
@@ -524,7 +533,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
{
if(turnstileEnabled && !body.turnstileToken)
{
setError('Please complete the security check.');
setError(t('nitro.login.error.turnstile', 'Please complete the security check.'));
return;
}
@@ -545,7 +554,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
if(ok)
{
const friendly = `Welcome aboard, ${ body.username }! Your account is ready — log in below with the password you just chose.`;
const friendly = t('nitro.login.register.success', 'Welcome aboard, %username%! Your account is ready — log in below with the password you just chose.', [ 'username' ], [ body.username ]);
setInfo(typeof payload.message === 'string' ? payload.message : friendly);
setMode('login');
setUsername(body.username);
@@ -553,12 +562,12 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
return;
}
setError(typeof payload.error === 'string' ? payload.error : 'Unable to create your account.');
setError(typeof payload.error === 'string' ? payload.error : t('nitro.login.error.register_failed', 'Unable to create your account.'));
onDialogReset();
}
catch
{
setError('Unable to reach the registration service.');
setError(t('nitro.login.error.register_unreachable', 'Unable to reach the registration service.'));
onDialogReset();
}
finally
@@ -571,7 +580,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
{
if(turnstileEnabled && !body.turnstileToken)
{
setError('Please complete the security check.');
setError(t('nitro.login.error.turnstile', 'Please complete the security check.'));
return;
}
@@ -588,18 +597,18 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
if(ok)
{
const friendly = 'Email sent! If an account matches that address you\'ll find a reset link in your inbox shortly (check spam if it doesn\'t show up within a minute).';
const friendly = t('nitro.login.forgot.success', 'Email sent! If an account matches that address you\'ll find a reset link in your inbox shortly (check spam if it doesn\'t show up within a minute).');
setInfo(typeof payload.message === 'string' ? payload.message : friendly);
setMode('login');
return;
}
setError(typeof payload.error === 'string' ? payload.error : 'Unable to send a reset email right now.');
setError(typeof payload.error === 'string' ? payload.error : t('nitro.login.error.forgot_failed', 'Unable to send a reset email right now.'));
onDialogReset();
}
catch
{
setError('Unable to reach the password reset service.');
setError(t('nitro.login.error.forgot_unreachable', 'Unable to reach the password reset service.'));
onDialogReset();
}
finally
@@ -655,8 +664,8 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
<div className="login-stack">
<div className="nitro-login-card login-language-card">
<div className="card-title">Choose your language</div>
<div className="login-language-grid" role="list" aria-label="Language selection">
<div className="card-title">{ t('nitro.login.language.title', 'Choose your language') }</div>
<div className="login-language-grid" role="list" aria-label={ t('nitro.login.language.aria', 'Language selection') }>
{ LOGIN_LOCALES.map(locale =>
<button
key={ locale.code }
@@ -673,23 +682,23 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
</div>
{ localeError.length > 0 && <div className="language-error">{ localeError }</div> }
<button type="button" className="ok-button login-language-confirm" disabled={ localeApplying } onClick={ confirmLocaleSelection }>
{ localeApplying ? 'Loading...' : 'OK' }
{ localeApplying ? t('nitro.login.language.loading', 'Loading...') : t('nitro.login.language.ok', 'OK') }
</button>
</div>
<div className="nitro-login-card">
<div className="card-title">First time here?</div>
<div className="card-title">{ t('nitro.login.firsttime.title', 'First time here?') }</div>
<div className="card-body register-card-body">
<span>Don't have a Habbo yet?</span>
<a onClick={ () => setMode('register') }>You can create one here</a>
<span>{ t('nitro.login.firsttime.text', 'Don\'t have a Habbo yet?') }</span>
<a onClick={ () => setMode('register') }>{ t('nitro.login.firsttime.link', 'You can create one here') }</a>
</div>
</div>
<div className="nitro-login-card">
<div className="card-title">What's your Habbo called?</div>
<div className="card-title">{ t('nitro.login.card.title', 'What\'s your Habbo called?') }</div>
<form className="card-body" onSubmit={ handleLoginSubmit } autoComplete="on">
<div className="field">
<label htmlFor="login-username">Name of your Habbo</label>
<label htmlFor="login-username">{ t('login.username', 'Name of your Habbo') }</label>
<input
id="login-username"
name="username"
@@ -701,7 +710,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
/>
</div>
<div className="field">
<label htmlFor="login-password">Password</label>
<label htmlFor="login-password">{ t('generic.password', 'Password') }</label>
<input
id="login-password"
name="password"
@@ -718,7 +727,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
checked={ rememberMe }
onChange={ e => setRememberMe(e.target.checked) }
/>
<span>Ricordami</span>
<span>{ t('login.remember_me', 'Remember me') }</span>
</label>
{ turnstileEnabled && mode === 'login' &&
<TurnstileWidget
@@ -731,9 +740,9 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
/> }
{ loginServerReachable === false &&
<div className="error-line server-offline">
The gameserver isn't running right now. Please try again in a moment.
{ t('nitro.login.server.offline.short', 'The gameserver isn\'t running right now. Please try again in a moment.') }
<button type="button" className="retry-link" onClick={ pingLoginServer } disabled={ loginPingingServer }>
{ loginPingingServer ? 'Checking' : 'Retry' }
{ loginPingingServer ? t('nitro.login.server.checking', 'Checking…') : t('nitro.login.server.retry', 'Retry') }
</button>
</div>
}
@@ -744,9 +753,9 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
type="submit"
className="ok-button"
disabled={ submitting || isEntering || isLocked }
>{ isEntering ? 'Entrando' : loginPingingServer ? 'Checking' : 'OK' }</button>
>{ isEntering ? t('nitro.login.entering', 'Entering…') : loginPingingServer ? t('nitro.login.server.checking', 'Checking…') : t('login.title', 'Log in') }</button>
</div>
<a className="forgot" onClick={ () => setMode('forgot') }>Forgotten your password?</a>
<a className="forgot" onClick={ () => setMode('forgot') }>{ t('login.forgot_password', 'Forgotten your password?') }</a>
</form>
</div>
@@ -1097,22 +1106,22 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
if(!email.trim() || !password || !confirm)
{
setLocalError('Please fill in every field.');
setLocalError(t('nitro.login.register.error.missing_fields', 'Please fill in every field.'));
return;
}
if(!EMAIL_REGEX.test(email.trim()))
{
setLocalError('Please enter a valid email address.');
setLocalError(t('nitro.login.register.error.invalid_email', 'Please enter a valid email address.'));
return;
}
if(password.length < 8)
{
setLocalError('Your password must be at least 8 characters.');
setLocalError(t('nitro.login.register.error.password_too_short', 'Your password must be at least 8 characters.'));
return;
}
if(password !== confirm)
{
setLocalError('Passwords do not match.');
setLocalError(t('nitro.login.register.error.password_mismatch', 'Passwords do not match.'));
return;
}
@@ -1122,13 +1131,13 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
const serverOk = await pingServer();
if(!serverOk)
{
setLocalError('The gameserver is not running. Please try again later.');
setLocalError(t('nitro.login.error.server_offline', 'The gameserver is not running. Please try again later.'));
return;
}
const result = await onCheckEmail(email.trim());
if(!result.available)
{
setLocalError(result.error || 'This email is already in use.');
setLocalError(result.error || t('nitro.login.error.email_taken', 'This email is already in use.'));
return;
}
setStep('avatar');
@@ -1207,18 +1216,18 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
const trimmed = username.trim();
if(!trimmed)
{
setLocalError('Please choose a Habbo name.');
setLocalError(t('nitro.login.register.error.username_required', 'Please choose a Habbo name.'));
return;
}
if(trimmed.length < 3 || trimmed.length > 16)
{
setLocalError('Habbo name must be 316 characters.');
setLocalError(t('nitro.login.register.error.username_length', 'Habbo name must be 316 characters.'));
return;
}
if(turnstileEnabled && !turnstileToken)
{
setLocalError('Please complete the security check.');
setLocalError(t('nitro.login.error.turnstile', 'Please complete the security check.'));
return;
}
@@ -1228,13 +1237,13 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
const serverOk = await pingServer();
if(!serverOk)
{
setLocalError('The gameserver is not running. Please try again later.');
setLocalError(t('nitro.login.error.server_offline', 'The gameserver is not running. Please try again later.'));
return;
}
const result = await onCheckUsername(trimmed);
if(!result.available)
{
setLocalError(result.error || 'This Habbo name is already taken.');
setLocalError(result.error || t('nitro.login.error.username_taken', 'This Habbo name is already taken.'));
return;
}
}
@@ -1261,35 +1270,35 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
<div className={ `dialog ${ step === 'avatar' ? 'dialog-avatar' : '' }` }>
<div className="nitro-login-card">
<div className="card-title">
<span>Habbo Details</span>
<span className="nitro-card-close-button" role="button" aria-label="Close" onClick={ onCancel } />
<span>{ t('nitro.login.register.title', 'Habbo Details') }</span>
<span className="nitro-card-close-button" role="button" aria-label={ t('generic.close', 'Close') } onClick={ onCancel } />
</div>
{ step === 'credentials' &&
<form className="card-body" onSubmit={ handleCredentialsNext } autoComplete="on">
<div className="register-intro">
Let's create your account. Enter your email and pick a password we'll check that email isn't already in use.
{ t('nitro.login.register.intro.credentials', 'Let\'s create your account. Enter your email and pick a password — we\'ll check that email isn\'t already in use.') }
</div>
{ serverOffline &&
<div className="error-line server-offline">
The gameserver isn't running right now, so new accounts can't be created. Please try again in a moment.
{ t('nitro.login.register.server.offline', 'The gameserver isn\'t running right now, so new accounts can\'t be created. Please try again in a moment.') }
<button type="button" className="retry-link" onClick={ pingServer } disabled={ pingingServer }>
{ pingingServer ? 'Checking…' : 'Retry' }
{ pingingServer ? t('nitro.login.server.checking', 'Checking…') : t('nitro.login.server.retry', 'Retry') }
</button>
</div>
}
<div className="field">
<label htmlFor="register-email">Email</label>
<label htmlFor="register-email">{ t('nitro.login.register.email', 'Email') }</label>
<input id="register-email" type="email" maxLength={ 120 } autoComplete="email"
value={ email } onChange={ e => setEmail(e.target.value) } />
</div>
<div className="field">
<label htmlFor="register-password">Password</label>
<label htmlFor="register-password">{ t('generic.password', 'Password') }</label>
<input id="register-password" type="password" maxLength={ 128 } autoComplete="new-password"
value={ password } onChange={ e => setPassword(e.target.value) } />
</div>
<div className="field">
<label htmlFor="register-confirm">Confirm password</label>
<label htmlFor="register-confirm">{ t('nitro.login.register.confirm_password', 'Confirm password') }</label>
<input id="register-confirm" type="password" maxLength={ 128 } autoComplete="new-password"
value={ confirm } onChange={ e => setConfirm(e.target.value) } />
</div>
@@ -1298,7 +1307,7 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
<div className="step-footer">
<span className="step-indicator">1/2</span>
<button type="submit" className="ok-button" disabled={ !credentialsValid || busy || serverOffline }>
{ checking || pingingServer ? 'Checking…' : 'Next' }
{ checking || pingingServer ? t('nitro.login.server.checking', 'Checking…') : t('nitro.login.register.next', 'Next') }
</button>
</div>
</form>
@@ -1307,29 +1316,29 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
{ step === 'avatar' &&
<form className="card-body" onSubmit={ handleAvatarSubmit } autoComplete="on">
<div className="register-intro">
Now it's time to make your own Habbo character! To make your own Habbo, please start by choosing your Habbo Name.
{ t('nitro.login.register.intro.avatar', 'Now it\'s time to make your own Habbo character! To make your own Habbo, please start by choosing your Habbo Name.') }
</div>
{ serverOffline &&
<div className="error-line server-offline">
The gameserver isn't running right now, so new accounts can't be created. Please try again in a moment.
{ t('nitro.login.register.server.offline', 'The gameserver isn\'t running right now, so new accounts can\'t be created. Please try again in a moment.') }
<button type="button" className="retry-link" onClick={ pingServer } disabled={ pingingServer }>
{ pingingServer ? 'Checking' : 'Retry' }
{ pingingServer ? t('nitro.login.server.checking', 'Checking…') : t('nitro.login.server.retry', 'Retry') }
</button>
</div>
}
<div className="field">
<input id="register-username" type="text" maxLength={ 16 } autoComplete="username" placeholder="HabboName"
<input id="register-username" type="text" maxLength={ 16 } autoComplete="username" placeholder={ t('nitro.login.register.username_placeholder', 'HabboName') }
value={ username } onChange={ e => setUsername(e.target.value) } />
</div>
<div className="gender-row">
<label>
<input type="radio" name="register-gender" checked={ gender === 'F' } onChange={ () => applyGender('F') } />
<span>Girl</span>
<span>{ t('nitro.login.register.gender.girl', 'Girl') }</span>
</label>
<label>
<input type="radio" name="register-gender" checked={ gender === 'M' } onChange={ () => applyGender('M') } />
<span>Boy</span>
<span>{ t('nitro.login.register.gender.boy', 'Boy') }</span>
</label>
</div>
@@ -1377,8 +1386,10 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
<button type="button" className="ok-button hot-looks-button"
onClick={ cycleHotLook }
disabled={ !hotLooks.length || busy }
title={ hotLooks.length ? `${ hotLooks.length } looks available` : 'No hot looks loaded' }>
Hot Looks{ hotLookIndex >= 0 && hotLooks.length ? ` (${ hotLookIndex + 1 }/${ hotLooks.length })` : '' }
title={ hotLooks.length
? t('nitro.login.register.hotlooks.available', '%count% looks available', [ 'count' ], [ String(hotLooks.length) ])
: t('nitro.login.register.hotlooks.none', 'No hot looks loaded') }>
{ t('nitro.login.register.hotlooks', 'Hot Looks') }{ hotLookIndex >= 0 && hotLooks.length ? ` (${ hotLookIndex + 1 }/${ hotLooks.length })` : '' }
</button>
</div>
@@ -1395,10 +1406,10 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
{ info && <div className="info-line">{ info }</div> }
<div className="step-footer step-footer-split">
<button type="button" className="ok-button back-button" onClick={ () => setStep('credentials') } disabled={ busy }>Back</button>
<button type="button" className="ok-button back-button" onClick={ () => setStep('credentials') } disabled={ busy }>{ t('nitro.login.register.back', 'Back') }</button>
<span className="step-indicator">2/2</span>
<button type="submit" className="ok-button" disabled={ !username.trim() || busy || serverOffline }>
{ submitting ? 'Creating' : (checking || pingingServer) ? 'Checking' : 'Next' }
{ submitting ? t('nitro.login.register.creating', 'Creating…') : (checking || pingingServer) ? t('nitro.login.server.checking', 'Checking…') : t('nitro.login.register.next', 'Next') }
</button>
</div>
</form>
@@ -1436,7 +1447,7 @@ const ForgotDialog: FC<ForgotDialogProps> = props =>
if(!email.trim())
{
setLocalError('Please enter your email address.');
setLocalError(t('nitro.login.forgot.error.email_required', 'Please enter your email address.'));
return;
}
@@ -1448,12 +1459,12 @@ const ForgotDialog: FC<ForgotDialogProps> = props =>
<div className="dialog">
<div className="nitro-login-card">
<div className="card-title">
<span>Reset password</span>
<span className="nitro-card-close-button" role="button" aria-label="Close" onClick={ onCancel } />
<span>{ t('nitro.login.forgot.title', 'Reset password') }</span>
<span className="nitro-card-close-button" role="button" aria-label={ t('generic.close', 'Close') } onClick={ onCancel } />
</div>
<form className="card-body" onSubmit={ handle } autoComplete="on">
<div className="field">
<label htmlFor="forgot-email">Email address</label>
<label htmlFor="forgot-email">{ t('nitro.login.forgot.email_label', 'Email address') }</label>
<input id="forgot-email" type="email" maxLength={ 120 } autoComplete="email"
value={ email } onChange={ e => setEmail(e.target.value) } />
</div>
@@ -1469,7 +1480,7 @@ const ForgotDialog: FC<ForgotDialogProps> = props =>
{ (localError || error) && <div className="error-line">{ localError || error }</div> }
{ info && <div className="info-line">{ info }</div> }
<div className="submit-row">
<button type="submit" className="ok-button" disabled={ submitting }>Send email</button>
<button type="submit" className="ok-button" disabled={ submitting }>{ t('nitro.login.forgot.send_email', 'Send email') }</button>
</div>
</form>
</div>