fix: translate auth pages to Russian, fix backend error parsing, calendar timezone

- Translate forgot-password and reset-password pages to Russian
- Translate verify-email page to Russian
- Fix error message parsing: backend returns {error:{message}} not {message}
- Apply timezone fix in calendar (FullCalendar timeZone prop + fTime helper)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dev Server 2026-03-12 16:50:51 +03:00
parent f6caa7df6b
commit 75d6072309
4 changed files with 43 additions and 43 deletions

View File

@ -23,8 +23,8 @@ import { requestPasswordReset } from 'src/auth/context/jwt';
const ForgotPasswordSchema = zod.object({
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
.min(1, { message: 'Введите email!' })
.email({ message: 'Введите корректный email адрес!' }),
});
// ----------------------------------------------------------------------
@ -46,11 +46,11 @@ export function JwtForgotPasswordView() {
const onSubmit = handleSubmit(async (data) => {
try {
await requestPasswordReset({ email: data.email });
setSuccessMsg('Password reset instructions have been sent to your email.');
setSuccessMsg('Инструкции по сбросу пароля отправлены на ваш email.');
setErrorMsg('');
} catch (error) {
console.error(error);
const msg = error?.response?.data?.message || error?.response?.data?.detail || 'Error sending request. Please check your email.';
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Ошибка отправки запроса. Проверьте правильность email.';
setErrorMsg(msg);
}
});
@ -58,10 +58,10 @@ export function JwtForgotPasswordView() {
return (
<>
<Stack spacing={1.5} sx={{ mb: 5 }}>
<Typography variant="h5">Forgot your password?</Typography>
<Typography variant="h5">Забыли пароль?</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Enter your email address and we will send you a link to reset your password.
Введите ваш email и мы отправим вам ссылку для сброса пароля.
</Typography>
</Stack>
@ -80,7 +80,7 @@ export function JwtForgotPasswordView() {
{!successMsg && (
<Form methods={methods} onSubmit={onSubmit}>
<Stack spacing={3}>
<Field.Text name="email" label="Email address" InputLabelProps={{ shrink: true }} />
<Field.Text name="email" label="Email" InputLabelProps={{ shrink: true }} />
<LoadingButton
fullWidth
@ -89,9 +89,9 @@ export function JwtForgotPasswordView() {
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Sending..."
loadingIndicator="Отправка..."
>
Send reset link
Отправить ссылку
</LoadingButton>
</Stack>
</Form>
@ -104,7 +104,7 @@ export function JwtForgotPasswordView() {
color="inherit"
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
>
Back to sign in
Вернуться ко входу
</Link>
</>
);

View File

@ -29,12 +29,12 @@ const ResetPasswordSchema = zod
.object({
newPassword: zod
.string()
.min(1, { message: 'Password is required!' })
.min(6, { message: 'Password must be at least 6 characters!' }),
newPasswordConfirm: zod.string().min(1, { message: 'Please confirm your password!' }),
.min(1, { message: 'Введите пароль!' })
.min(6, { message: 'Пароль должен содержать не менее 6 символов!' }),
newPasswordConfirm: zod.string().min(1, { message: 'Подтвердите пароль!' }),
})
.refine((data) => data.newPassword === data.newPasswordConfirm, {
message: 'Passwords do not match!',
message: 'Пароли не совпадают!',
path: ['newPasswordConfirm'],
});
@ -63,7 +63,7 @@ function ResetPasswordContent() {
const onSubmit = handleSubmit(async (data) => {
if (!token) {
setErrorMsg('Reset link is missing. Please request a new password reset.');
setErrorMsg('Ссылка для сброса отсутствует. Запросите новый сброс пароля.');
return;
}
try {
@ -72,11 +72,11 @@ function ResetPasswordContent() {
newPassword: data.newPassword,
newPasswordConfirm: data.newPasswordConfirm,
});
setSuccessMsg('Password changed successfully. You can now sign in with your new password.');
setSuccessMsg('Пароль успешно изменён. Теперь вы можете войти с новым паролем.');
setErrorMsg('');
} catch (error) {
console.error(error);
const msg = error?.response?.data?.message || error?.response?.data?.detail || 'Failed to reset password. The link may have expired — please request a new one.';
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || 'Не удалось сбросить пароль. Возможно, ссылка устарела — запросите новую.';
setErrorMsg(msg);
}
});
@ -85,13 +85,13 @@ function ResetPasswordContent() {
return (
<>
<Stack spacing={1.5} sx={{ mb: 5 }}>
<Typography variant="h5">Reset password</Typography>
<Typography variant="h5">Сброс пароля</Typography>
</Stack>
<Alert severity="error" sx={{ mb: 3 }}>
Reset link is missing. Please follow the link from your email or request a new one.
Ссылка для сброса отсутствует. Перейдите по ссылке из письма или запросите новую.
</Alert>
<Link component={RouterLink} href={paths.auth.jwt.forgotPassword} variant="subtitle2">
Request new reset link
Запросить новую ссылку
</Link>
</>
);
@ -101,13 +101,13 @@ function ResetPasswordContent() {
return (
<>
<Stack spacing={1.5} sx={{ mb: 5 }}>
<Typography variant="h5">Reset password</Typography>
<Typography variant="h5">Сброс пароля</Typography>
</Stack>
<Alert severity="success" sx={{ mb: 3 }}>
{successMsg}
</Alert>
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
Sign in
Войти
</Link>
</>
);
@ -116,9 +116,9 @@ function ResetPasswordContent() {
return (
<>
<Stack spacing={1.5} sx={{ mb: 5 }}>
<Typography variant="h5">Set new password</Typography>
<Typography variant="h5">Новый пароль</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Enter your new password below.
Введите новый пароль ниже.
</Typography>
</Stack>
@ -132,8 +132,8 @@ function ResetPasswordContent() {
<Stack spacing={3}>
<Field.Text
name="newPassword"
label="New password"
placeholder="6+ characters"
label="Новый пароль"
placeholder="6+ символов"
type={newPassword.value ? 'text' : 'password'}
InputLabelProps={{ shrink: true }}
InputProps={{
@ -149,8 +149,8 @@ function ResetPasswordContent() {
<Field.Text
name="newPasswordConfirm"
label="Confirm new password"
placeholder="6+ characters"
label="Подтвердите пароль"
placeholder="6+ символов"
type={newPasswordConfirm.value ? 'text' : 'password'}
InputLabelProps={{ shrink: true }}
InputProps={{
@ -171,9 +171,9 @@ function ResetPasswordContent() {
type="submit"
variant="contained"
loading={isSubmitting}
loadingIndicator="Saving..."
loadingIndicator="Сохранение..."
>
Save new password
Сохранить пароль
</LoadingButton>
</Stack>
</Form>
@ -185,7 +185,7 @@ function ResetPasswordContent() {
color="inherit"
sx={{ display: 'block', mt: 3, textAlign: 'center' }}
>
Back to sign in
Вернуться ко входу
</Link>
</>
);
@ -195,7 +195,7 @@ function ResetPasswordContent() {
export function JwtResetPasswordView() {
return (
<Suspense fallback={<Typography variant="body2">Loading...</Typography>}>
<Suspense fallback={<Typography variant="body2">Загрузка...</Typography>}>
<ResetPasswordContent />
</Suspense>
);

View File

@ -178,7 +178,7 @@ export function JwtSignUpView() {
router.replace(paths.dashboard.root);
} catch (error) {
console.error(error);
const msg = error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.');
const msg = error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.');
setErrorMsg(msg);
}
});

View File

@ -26,7 +26,7 @@ function VerifyEmailContent() {
useEffect(() => {
if (!token) {
setStatus('error');
setMessage('Verification link is missing. Please check your email or request a new one.');
setMessage('Ссылка для подтверждения отсутствует. Проверьте письмо или запросите новое.');
return;
}
@ -37,16 +37,16 @@ function VerifyEmailContent() {
if (cancelled) return;
if (res?.success) {
setStatus('success');
setMessage('Email successfully verified. You can now sign in.');
setMessage('Email успешно подтверждён. Теперь вы можете войти.');
} else {
setStatus('error');
setMessage(res?.message || 'Failed to verify email.');
setMessage(res?.error?.message || res?.message || 'Не удалось подтвердить email.');
}
})
.catch((err) => {
if (cancelled) return;
setStatus('error');
const msg = err?.response?.data?.message || err?.response?.data?.detail || 'Invalid or expired link. Please request a new verification email.';
const msg = err?.response?.data?.error?.message || err?.response?.data?.message || err?.response?.data?.detail || 'Ссылка недействительна или устарела. Запросите новое письмо с подтверждением.';
setMessage(msg);
});
@ -58,14 +58,14 @@ function VerifyEmailContent() {
return (
<>
<Stack spacing={1.5} sx={{ mb: 5 }}>
<Typography variant="h5">Email verification</Typography>
<Typography variant="h5">Подтверждение email</Typography>
</Stack>
{status === 'loading' && (
<Stack alignItems="center" sx={{ py: 4 }}>
<CircularProgress />
<Typography variant="body2" sx={{ mt: 2, color: 'text.secondary' }}>
Verifying your email...
Подтверждение email...
</Typography>
</Stack>
)}
@ -76,7 +76,7 @@ function VerifyEmailContent() {
{message}
</Alert>
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
Sign in to your account
Войти в аккаунт
</Link>
</>
)}
@ -88,10 +88,10 @@ function VerifyEmailContent() {
</Alert>
<Stack spacing={1}>
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
Back to sign in
Вернуться ко входу
</Link>
<Link component={RouterLink} href={paths.auth.jwt.signUp} variant="body2" color="text.secondary">
Create a new account
Создать новый аккаунт
</Link>
</Stack>
</>
@ -104,7 +104,7 @@ function VerifyEmailContent() {
export function JwtVerifyEmailView() {
return (
<Suspense fallback={<Typography variant="body2">Loading...</Typography>}>
<Suspense fallback={<Typography variant="body2">Загрузка...</Typography>}>
<VerifyEmailContent />
</Suspense>
);