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,6 +1,7 @@
import {useCallback, useEffect, useState} from "react";
import CloseIcon from "./icons/CloseIcon";
import { EMPTY_FORM, CATEGORIES } from "../config/feedConstants";
import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants";
import { useTranslation } from 'react-i18next';
import { usePostMutation } from "../hooks/usePostMutation";
import { useImageUpload, ACCEPT } from "../hooks/useImageUpload";
import { validatePostForm } from "../utils/validatePostForm";
@@ -19,6 +20,7 @@ const MAX_SIZE_MB = 5;
export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
// Режим: редактирование (true) или создание нового (false)
const isEdit = !!initialData;
const { t } = useTranslation();
// Поля формы; при редактировании заполняем из initialData
const [form, setForm] = useState(
@@ -88,7 +90,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
}
if (coords && !addressRef.current) {
setErrors(prev => ({ ...prev, map: "Подождите, адрес загружается..." }));
setErrors(prev => ({ ...prev, map: t('post.mapLoading') }));
return;
}
@@ -136,7 +138,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Шапка модалки */}
<div className="f-modal-head">
<span className="f-modal-title">
{isEdit ? "Редактировать объявление" : "Новое объявление"}
{isEdit ? t('post.editTitle') : t('post.newTitle')}
</span>
<button className="f-close-btn" onClick={onClose}>
<CloseIcon/>
@@ -145,31 +147,31 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Тип: Потеря / Находка */}
<div className="f-m-group">
<label className="f-m-label">Тип объявления</label>
<label className="f-m-label">{t('post.typeLabel')}</label>
<div className="f-type-toggle">
<button
className={`f-type-opt${form.type === "loss" ? " loss" : ""}`}
onClick={() => set("type", "loss")}
>
Потеря
{t('post.loss')}
</button>
<button
className={`f-type-opt${form.type === "found" ? " found"
: ""}`}
onClick={() => set("type", "found")}
>
Находка
{t('post.found')}
</button>
</div>
</div>
{/* Название */}
<div className="f-m-group">
<label className="f-m-label">Название</label>
<label className="f-m-label">{t('post.titleLabel')}</label>
<input
className={`f-m-field${errors.title ? " err" : ""}`}
type="text"
placeholder="Например: Кошелёк чёрный кожаный"
placeholder={t('post.titlePlaceholder')}
value={form.title}
onChange={e => set("title", e.target.value)}
/>
@@ -178,11 +180,11 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Описание */}
<div className="f-m-group">
<label className="f-m-label">Описание</label>
<label className="f-m-label">{t('post.descriptionLabel')}</label>
<textarea
className={`f-m-field${errors.description ? " err" : ""}`}
rows={3}
placeholder="Подробно опишите предмет — цвет, размер, особые приметы..."
placeholder={t('post.descriptionPlaceholder')}
value={form.description}
onChange={e => set("description", e.target.value)}
/>
@@ -192,7 +194,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Фотографии: сетка превью + кнопка добавления */}
<div className="f-m-group">
<label className="f-m-label">Фотографии</label>
<label className="f-m-label">{t('post.photosLabel')}</label>
{uploadedImages.length > 0 && (
<div className="f-img-grid">
@@ -203,7 +205,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
className="f-img-remove"
onClick={() => removeImage(img.id)}
type="button"
aria-label="Удалить фото"
aria-label={t('post.deletePhoto')}
>
<CloseIcon/>
</button>
@@ -226,27 +228,27 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
disabled={compressing}
/>
{compressing ? (
<span>Обработка...</span>
<span>{t('post.processing')}</span>
) : (
<span>{uploadedImages.length > 0 ? "➕ Добавить ещё" : "📷 Добавить фото"}</span>
<span>{uploadedImages.length > 0 ? t('post.addMore') : t('post.addPhoto')}</span>
)}
</label>
{imgError && <p className="f-err-msg">{imgError}</p>}
<p className="f-hint">JPEG / PNG / WebP · до {MAX_SIZE_MB} МБ · можно несколько</p>
<p className="f-hint">{t('post.photoHint', { maxSize: MAX_SIZE_MB })}</p>
</div>
{/* Категория */}
<div className="f-m-row">
<div className="f-m-group">
<label className="f-m-label">Категория</label>
<label className="f-m-label">{t('post.categoryLabel')}</label>
<select
className={`f-m-field${errors.category ? " err" : ""}`}
value={form.category}
onChange={e => set("category", e.target.value)}
>
<option value="">Выбрать...</option>
{CATEGORIES.map(c => <option key={c} value={c}>{c}</option>)}
<option value="">{t('categories.select')}</option>
{CATEGORY_KEYS.map(k => <option key={k} value={t(k)}>{t(k)}</option>)}
</select>
{errors.category && <p
className="f-err-msg">{errors.category}</p>}
@@ -255,7 +257,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Карта */}
<div className="f-m-group">
<label className="f-m-label">Место на карте</label>
<label className="f-m-label">{t('post.mapLabel')}</label>
<div style={{
width: "100%",
height: 260,
@@ -265,17 +267,16 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
<div ref={mapRef} style={{width: "100%", height: "100%"}}/>
</div>
{!coords && <p className="f-hint">Нажмите на карту, чтобы отметить
место</p>}
{!coords && <p className="f-hint">{t('post.mapHint')}</p>}
{errors.map && <p className="f-err-msg">{errors.map}</p>}
</div>
<div className="f-m-group">
<label className="f-m-label">Адрес</label>
<label className="f-m-label">{t('post.addressLabel')}</label>
<input
className={`f-m-field${errors.address ? " err" : ""}`}
type="text"
placeholder="Например: Москва, ул. Ленина, 5"
placeholder={t('post.addressPlaceholder')}
value={form.address}
onChange={e => set("address", e.target.value)}
/>
@@ -284,11 +285,11 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{/* Телефон с автоматической маской */}
<div className="f-m-group">
<label className="f-m-label">Номер телефона</label>
<label className="f-m-label">{t('post.phoneLabel')}</label>
<input
className={`f-m-field${errors.phone ? " err" : ""}`}
type="tel"
placeholder="+7 (___) ___-__-__"
placeholder={t('post.phonePlaceholder')}
value={form.phone}
onChange={e => set("phone", formatPhone(e.target.value))}
/>
@@ -302,7 +303,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
disabled={loading}>
<div className="f-m-submit-row">
{loading && <div className="f-m-spinner"/>}
{isEdit ? "Сохранить изменения" : "Разместить объявление"}
{isEdit ? t('post.saveChanges') : t('post.publish')}
</div>
</button>