From e38555b74dfea7707bacb5eb12526ca97fd606e9 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 13 Jul 2026 17:44:08 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D1=8B=20=D0=B8=20=D1=82=D0=B5=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - «Winrate» -> «Винрейт» в карточке статистики; в справке добавлена расшифровка «Винрейт (WR)» — WR остаётся в компактных таблицах; - кнопки сохранения: «ОК» -> «Сохранить» (аккаунт, фракции, аккаунты в админке); отмена в модалках/формах — везде «Отмена», не «Назад»; - пустые состояния приведены к шаблону «X пока нет.»; - роли участников группы переведены: owner/member -> владелец/участник, бейдж admin -> «админ»; «Имя обновлено» -> «Ник обновлён»; - «N игр.» в значении числа игроков -> «N игроков» со склонением (новый хелпер plural в domain/format.ts); - «минимум 2 участника» -> «минимум 2 игрока» (в партии — игроки, в группе — участники). Co-Authored-By: Claude Fable 5 --- frontend/src/components/InProgressMatches.tsx | 5 +++-- frontend/src/components/Leaderboard.tsx | 2 +- frontend/src/components/NotificationPanel.tsx | 2 +- frontend/src/components/ProfileStatsCard.tsx | 2 +- frontend/src/domain/format.ts | 9 +++++++++ frontend/src/pages/AccountPage.tsx | 2 +- frontend/src/pages/CreateMatchPage.tsx | 2 +- frontend/src/pages/GroupSettingsPage.tsx | 3 ++- frontend/src/pages/HelpPage.tsx | 2 +- frontend/src/pages/LoginPage.tsx | 2 +- frontend/src/pages/MatchDetailPage.tsx | 1 - frontend/src/pages/admin/AdminAccountsPage.tsx | 6 +++--- frontend/src/pages/admin/AdminFactionsPage.tsx | 2 +- frontend/src/pages/admin/AdminLogsPage.tsx | 2 +- frontend/src/pages/admin/AdminRecordsPage.tsx | 7 ++++--- 15 files changed, 30 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/InProgressMatches.tsx b/frontend/src/components/InProgressMatches.tsx index e6044dc..750232e 100644 --- a/frontend/src/components/InProgressMatches.tsx +++ b/frontend/src/components/InProgressMatches.tsx @@ -1,7 +1,7 @@ import { ChevronRight } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { formatTime } from "../domain/format"; +import { formatTime, plural } from "../domain/format"; import type { HomeInProgressMatch } from "../domain/types"; import { PlayerLink } from "./PlayerLink"; @@ -37,7 +37,8 @@ export function InProgressMatches({ items }: { items: HomeInProgressMatch[] }) { идёт
- {formatTime(m.started_at)} · {m.player_count} игр. + {formatTime(m.started_at)} · {m.player_count}{" "} + {plural(m.player_count, "игрок", "игрока", "игроков")}
Пока нет сыгранных партий.
; + return
Сыгранных партий пока нет.
; } return (
diff --git a/frontend/src/components/NotificationPanel.tsx b/frontend/src/components/NotificationPanel.tsx index 2cf981d..53f1c6f 100644 --- a/frontend/src/components/NotificationPanel.tsx +++ b/frontend/src/components/NotificationPanel.tsx @@ -22,7 +22,7 @@ export function NotificationPanel({
Уведомления
{items.length === 0 ? ( -
Уведомлений нет
+
Уведомлений пока нет.
) : (
{items.map((n) => ( diff --git a/frontend/src/components/ProfileStatsCard.tsx b/frontend/src/components/ProfileStatsCard.tsx index 63f7a05..8651d9b 100644 --- a/frontend/src/components/ProfileStatsCard.tsx +++ b/frontend/src/components/ProfileStatsCard.tsx @@ -24,7 +24,7 @@ export function ProfileStatsCard({ {o.wins}
-
Winrate
+
Винрейт
{pct(o.win_rate)}
diff --git a/frontend/src/domain/format.ts b/frontend/src/domain/format.ts index 8a4b6cd..5885c61 100644 --- a/frontend/src/domain/format.ts +++ b/frontend/src/domain/format.ts @@ -9,6 +9,15 @@ function shifted(iso: string): Date { return new Date(new Date(iso).getTime() + APP_TZ_OFFSET_MIN * 60_000); } +// Склонение существительного при числе: plural(3, "игрок", "игрока", "игроков"). +export function plural(n: number, one: string, few: string, many: string): string { + const m10 = n % 10; + const m100 = n % 100; + if (m10 === 1 && m100 !== 11) return one; + if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return few; + return many; +} + export function formatDuration(minutes: number | null | undefined): string { if (minutes == null) return "—"; const h = Math.floor(minutes / 60); diff --git a/frontend/src/pages/AccountPage.tsx b/frontend/src/pages/AccountPage.tsx index 7c8a336..004fbee 100644 --- a/frontend/src/pages/AccountPage.tsx +++ b/frontend/src/pages/AccountPage.tsx @@ -145,7 +145,7 @@ export function AccountPage() {
setNickname(e.target.value)} placeholder={me.nickname} />
diff --git a/frontend/src/pages/CreateMatchPage.tsx b/frontend/src/pages/CreateMatchPage.tsx index 5e005c6..0d443ae 100644 --- a/frontend/src/pages/CreateMatchPage.tsx +++ b/frontend/src/pages/CreateMatchPage.tsx @@ -134,7 +134,7 @@ export function CreateMatchPage() { setError(null); const filled = rows.filter((r) => r.user_id !== "" && r.faction_id !== ""); if (filled.length < 2) { - setError("Нужно минимум 2 участника с выбранным игроком и фракцией."); + setError("Нужно минимум 2 игрока с выбранной фракцией."); return; } if (filled.length > MAX_MATCH_PLAYERS) { diff --git a/frontend/src/pages/GroupSettingsPage.tsx b/frontend/src/pages/GroupSettingsPage.tsx index bca3365..be2b233 100644 --- a/frontend/src/pages/GroupSettingsPage.tsx +++ b/frontend/src/pages/GroupSettingsPage.tsx @@ -120,7 +120,8 @@ export function GroupSettingsPage() {
{/* Кликабельна вся левая часть строки, не только текст ника. */} - {m.nickname} · {m.role} + {m.nickname}{" "} + · {m.role === "owner" ? "владелец" : "участник"} {isOwner && m.role !== "owner" && (
{error &&

{error}

}
); diff --git a/frontend/src/pages/MatchDetailPage.tsx b/frontend/src/pages/MatchDetailPage.tsx index d8ebc67..9858497 100644 --- a/frontend/src/pages/MatchDetailPage.tsx +++ b/frontend/src/pages/MatchDetailPage.tsx @@ -293,7 +293,6 @@ export function MatchDetailPage() { : "Сыгранная партия и её результаты будут удалены. Действие необратимо." } confirmLabel={inProgress ? "Отменить партию" : "Удалить"} - cancelLabel="Назад" onCancel={() => setConfirmRemove(false)} onConfirm={() => { setConfirmRemove(false); diff --git a/frontend/src/pages/admin/AdminAccountsPage.tsx b/frontend/src/pages/admin/AdminAccountsPage.tsx index 3876bda..e481452 100644 --- a/frontend/src/pages/admin/AdminAccountsPage.tsx +++ b/frontend/src/pages/admin/AdminAccountsPage.tsx @@ -28,7 +28,7 @@ export function AdminAccountsPage() { if (editId == null || !name) return; try { await update.mutateAsync({ userId: editId, nickname: name }); - toast.show("Имя обновлено"); + toast.show("Ник обновлён"); setEditId(null); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Не удалось переименовать"); @@ -63,7 +63,7 @@ export function AdminAccountsPage() { disabled={update.isPending || !editName.trim() || editName.trim() === u.nickname} onClick={saveName} > - ОК + Сохранить
diff --git a/frontend/src/pages/admin/AdminLogsPage.tsx b/frontend/src/pages/admin/AdminLogsPage.tsx index 3569f01..8bb7a95 100644 --- a/frontend/src/pages/admin/AdminLogsPage.tsx +++ b/frontend/src/pages/admin/AdminLogsPage.tsx @@ -59,7 +59,7 @@ export function AdminLogsPage() { )} ))} - {(data?.items ?? []).length === 0 &&
Событий нет.
} + {(data?.items ?? []).length === 0 &&
Событий пока нет.
} )} diff --git a/frontend/src/pages/admin/AdminRecordsPage.tsx b/frontend/src/pages/admin/AdminRecordsPage.tsx index e4de2f4..6f762d4 100644 --- a/frontend/src/pages/admin/AdminRecordsPage.tsx +++ b/frontend/src/pages/admin/AdminRecordsPage.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import { ApiError } from "../../api/client"; import { ConfirmDialog } from "../../components/ConfirmDialog"; -import { formatDate } from "../../domain/format"; +import { formatDate, plural } from "../../domain/format"; import { Spinner } from "../../components/Spinner"; import { Switch } from "../../components/Switch"; import { useToast } from "../../context/ToastContext"; @@ -58,7 +58,8 @@ export function AdminRecordsPage() { #{m.id} · {formatDate(m.played_at)} {!grouped && · {m.group_name}}
- {m.player_count} игр. · автор {m.created_by} + {m.player_count} {plural(m.player_count, "игрок", "игрока", "игроков")} · автор{" "} + {m.created_by}
@@ -120,7 +121,7 @@ export function AdminRecordsPage() { {isLoading ? ( ) : (matches ?? []).length === 0 ? ( -
Записей нет.
+
Записей пока нет.
) : grouped ? ( byGroup.map(([gid, { name, items }]) => (