added english language

This commit is contained in:
SlimusMinus
2026-07-09 00:09:45 +03:00
parent 3b503623f6
commit fffcdaa526
33 changed files with 454 additions and 687 deletions

View File

@@ -1,4 +1,5 @@
import { useState, useCallback } from "react";
import { useTranslation } from 'react-i18next';
import MagnifierLogo from "./MagnifierLogo";
import { useAuth } from "../hooks/useAuth";
import { useRegister } from "../hooks/useRegister";
@@ -9,14 +10,14 @@ import likeSmile from '../assets/success.png';
import { useCheckLogin } from "../hooks/useRegister";
// ─── helpers ──────────────────────────────────────────────────────────────────
function getStrength(val) {
function getStrength(val, t) {
if (!val) return { level: 0, label: "", color: "transparent", cls: "" };
let s = 0;
if (val.length >= 6) s++;
if (val.length >= 10) s++;
if (/[A-Z]/.test(val) || /[0-9]/.test(val)) s++;
if (/[^A-Za-z0-9]/.test(val)) s++;
const labels = ["", "Слабый", "Средний", "Хороший", "Отличный"];
const labels = ["", t('auth.strengthWeak'), t('auth.strengthMedium'), t('auth.strengthGood'), t('auth.strengthExcellent')];
const colors = ["", "#ff6b6b", "#ffd166", "#2de0c8", "#2de0c8"];
const cls = ["", "sw", "sm", "ss", "ss"];
return { level: s, label: labels[s], color: colors[s], cls: cls[s] };
@@ -49,6 +50,7 @@ function StepDots({ current, total }) {
// LOGIN FORM
// ═══════════════════════════════════════════════════════════════════════════════
function LoginForm({ onSwitchToRegister }) {
const { t } = useTranslation();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPass, setShowPass] = useState(false);
@@ -56,14 +58,14 @@ function LoginForm({ onSwitchToRegister }) {
return (
<div className="form-body">
<p className="form-subtitle">С возвращением</p>
<p className="form-subtitle">{t('auth.welcomeBack')}</p>
<form onSubmit={(e) => { e.preventDefault(); login(email, password); }} noValidate>
<div className="field-wrap">
<input
className="field"
type="email"
placeholder="Введите email"
placeholder={t('auth.enterEmail')}
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="off"
@@ -76,7 +78,7 @@ function LoginForm({ onSwitchToRegister }) {
<input
className="field"
type={showPass ? "text" : "password"}
placeholder="Введите пароль"
placeholder={t('auth.enterPassword')}
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ paddingRight: "44px" }}
@@ -90,16 +92,16 @@ function LoginForm({ onSwitchToRegister }) {
{error && <p className="err-msg">{error}</p>}
<button type="submit" className="btn-primary" disabled={loading}>
{loading ? "Входим..." : "Войти"}
{loading ? t('auth.loggingIn') : t('auth.login')}
</button>
</form>
<button className="ghost-btn">Забыли пароль?</button>
<button className="ghost-btn">{t('auth.forgotPassword')}</button>
<p className="switch-row">
Нет аккаунта?{" "}
{t('auth.noAccount')}{" "}
<button className="link-btn" onClick={onSwitchToRegister}>
Зарегистрироваться
{t('auth.register')}
</button>
</p>
</div>
@@ -108,21 +110,22 @@ function LoginForm({ onSwitchToRegister }) {
// ─── step 0+1: name + login + passwords ───────────────────────────────────────
function RegStep01({ data, onChange, onNext }) {
const { t } = useTranslation();
const [show1, setShow1] = useState(false);
const [show2, setShow2] = useState(false);
const [errors, setErrors] = useState({});
const strength = getStrength(data.password);
const strength = getStrength(data.password, t);
const matched = data.password && data.password === data.password2;
const { checking: checkingLogin, exists: loginExists } = useCheckLogin(data.login);
const validate = () => {
const e = {};
if (!data.name.trim()) e.name = "Введите имя";
if (!data.login.trim()) e.login = "Введите логин";
else if (data.login.length < 3) e.login = "Минимум 3 символа";
if (data.password.length < 6) e.password = "Минимум 6 символов";
else if (data.password !== data.password2) e.password2 = "Пароли не совпадают";
if (!data.name.trim()) e.name = t('auth.validateNameRequired');
if (!data.login.trim()) e.login = t('auth.validateLoginRequired');
else if (data.login.length < 3) e.login = t('auth.loginMinChars');
if (data.password.length < 6) e.password = t('auth.validatePasswordMin');
else if (data.password !== data.password2) e.password2 = t('auth.validatePasswordMatch');
setErrors(e);
return !Object.keys(e).length;
@@ -131,11 +134,11 @@ function RegStep01({ data, onChange, onNext }) {
return (
<>
<div className="field-wrap">
<label className="field-label">Имя пользователя</label>
<label className="field-label">{t('auth.username')}</label>
<input
className={`field ${errors.name ? "field-err" : ""}`}
type="text"
placeholder="Как вас зовут?"
placeholder={t('auth.namePlaceholder')}
value={data.name}
onChange={(e) => {
onChange("name", e.target.value);
@@ -147,12 +150,12 @@ function RegStep01({ data, onChange, onNext }) {
</div>
<div className="field-wrap">
<label className="field-label">Логин</label>
<label className="field-label">{t('auth.loginLabel')}</label>
<input
className={`field ${errors.login || loginExists ? "field-err"
: ""}`}
type="text"
placeholder="Придумайте логин"
placeholder={t('auth.loginPlaceholder')}
value={data.login}
onChange={(e) => {
onChange("login", e.target.value);
@@ -161,12 +164,12 @@ function RegStep01({ data, onChange, onNext }) {
autoComplete="off"
/>
{checkingLogin && <p className="hint-msg">Проверяем...</p>}
{checkingLogin && <p className="hint-msg">{t('auth.checking')}</p>}
{!checkingLogin && data.login.length >= 3 && (
<p className="err-msg"
style={{color: loginExists ? "#ff6b6b" : "#2de0c8"}}>
{loginExists ? "✗ Логин занят" : "✓ Логин свободен"}
{loginExists ? t('auth.loginTaken') : t('auth.loginFree')}
</p>
)}
@@ -174,13 +177,13 @@ function RegStep01({ data, onChange, onNext }) {
</div>
<div className="field-wrap">
<label className="field-label">Пароль</label>
<label className="field-label">{t('auth.passwordLabel')}</label>
<div className="input-wrap">
<input
className={`field ${errors.password ? "field-err" : ""}`}
type={show1 ? "text" : "password"}
placeholder="Минимум 6 символов"
placeholder={t('auth.passwordMin')}
value={data.password}
onChange={(e) => {
onChange("password", e.target.value);
@@ -211,14 +214,14 @@ function RegStep01({ data, onChange, onNext }) {
<div className="field-wrap">
<label className="field-label">Повторите пароль</label>
<label className="field-label">{t('auth.repeatPassword')}</label>
<div className="input-wrap">
<input
className={`field ${data.password2 ? (matched ? "field-ok"
: "field-err") : ""}`}
type={show2 ? "text" : "password"}
placeholder="Введите ещё раз"
placeholder={t('auth.repeatPasswordPlaceholder')}
value={data.password2}
onChange={(e) => {
onChange("password2", e.target.value);
@@ -232,7 +235,7 @@ function RegStep01({ data, onChange, onNext }) {
{data.password2 && (
<p className="err-msg"
style={{color: matched ? "#2de0c8" : "#ff6b6b"}}>
{matched ? "✓ Совпадают" : "✗ Не совпадают"}
{matched ? t('auth.passwordsMatch') : t('auth.passwordsNoMatch')}
</p>
)}
@@ -240,7 +243,7 @@ function RegStep01({ data, onChange, onNext }) {
</div>
<button className="btn-primary" onClick={() => validate() && onNext()}>
Далее →
{t('auth.next')}
</button>
</>
);
@@ -248,16 +251,17 @@ function RegStep01({ data, onChange, onNext }) {
// ─── step 2: confirm ──────────────────────────────────────────────────────────
function RegStep2({data, onSubmit, onBack, loading, error}) {
const { t } = useTranslation();
return (
<>
<div className="confirm-box">
<div className="confirm-row">
<span className="confirm-label">Имя</span>
<span className="confirm-label">{t('auth.nameLabel')}</span>
<span className="confirm-val">{data.name}</span>
</div>
<div className="confirm-divider"/>
<div className="confirm-row">
<span className="confirm-label">Логин</span>
<span className="confirm-label">{t('auth.loginLabel')}</span>
<span className="confirm-val accent">@{data.login}</span>
</div>
</div>
@@ -266,30 +270,32 @@ function RegStep2({data, onSubmit, onBack, loading, error}) {
marginBottom: "10px"
}}>{error}</p>}
<button className="btn-primary" onClick={onSubmit} disabled={loading}>
{loading ? "Создаём..." : "Создать аккаунт ✓"}
{loading ? t('auth.creating') : t('auth.createAccountBtn')}
</button>
<button className="back-btn" onClick={onBack}>← Назад</button>
<button className="back-btn" onClick={onBack}>{t('auth.back')}</button>
</>
);
}
// ─── success ──────────────────────────────────────────────────────────────────
function RegSuccess({name, onLogin}) {
const { t } = useTranslation();
return (
<div className="success-wrap">
<img src={likeSmile} alt="success" className="success-icon"/>
<p
className="success-title">Добро пожаловать, {name}!</p>
<p className="success-sub">Аккаунт создан успешно.</p>
className="success-title">{t('auth.welcome', { name })}</p>
<p className="success-sub">{t('auth.accountCreated')}</p>
<button className="btn-primary" style={{marginTop: "20px"}}
onClick={onLogin}>
Войти →
{t('auth.loginRedirect')}
</button>
</div>
);
}
function RegisterForm({onSwitchToLogin}) {
const { t } = useTranslation();
const [step, setStep] = useState(0);
const [form, setForm] = useState(
{name: "", login: "", password: "", password2: ""});
@@ -307,7 +313,7 @@ function RegisterForm({onSwitchToLogin}) {
return (
<div className="form-body">
<p className="form-subtitle">Создайте аккаунт</p>
<p className="form-subtitle">{t('auth.createAccount')}</p>
<StepDots current={step} total={2} />
{step === 0 && (
@@ -325,8 +331,8 @@ function RegisterForm({onSwitchToLogin}) {
{step === 0 && (
<p className="switch-row">
Уже есть аккаунт?{" "}
<button className="link-btn" onClick={onSwitchToLogin}>Войти</button>
{t('auth.hasAccount')}{" "}
<button className="link-btn" onClick={onSwitchToLogin}>{t('auth.loginLink')}</button>
</p>
)}
</div>
@@ -337,6 +343,7 @@ function RegisterForm({onSwitchToLogin}) {
// AUTH PAGE — единственная страница, переключает login ↔ register
// ═══════════════════════════════════════════════════════════════════════════════
export default function Authentication() {
const { t } = useTranslation();
const [mode, setMode] = useState("login"); // "login" | "register"
const [animating, setAnimating] = useState(false);
@@ -367,7 +374,7 @@ export default function Authentication() {
<div className="logo-wrap">
<MagnifierLogo />
</div>
<h1 className="auth-title">Находка</h1>
<h1 className="auth-title">{t('app.name')}</h1>
{mode === "login"
? <LoginForm onSwitchToRegister={() => switchTo("register")} />