From 1d2aae5df21eaab69677a2b3ef67af05b3be80fa Mon Sep 17 00:00:00 2001 From: adevopg Date: Tue, 14 Jul 2026 19:44:09 +0000 Subject: [PATCH] ban-history: alcance y estado multiidioma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getSanctions devuelve claves (scopeKey account/battlenet/character + scopeName; statusKey active/activePermanent/expired) en vez de español. La página traduce alcance ("Character: {name}") y estado; "Permanente" ya usaba clave. Motivo y autor son datos del GM (no se traducen). Nuevas claves History.ban.scope/status. Verificado con sanciones de prueba (todos los combos resuelven en es y en). Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/[locale]/ban-history/page.tsx | 12 ++++----- web-next/lib/ban-history.ts | 30 +++++++++++++--------- web-next/messages/en.json | 12 ++++++++- web-next/messages/es.json | 12 ++++++++- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/web-next/app/[locale]/ban-history/page.tsx b/web-next/app/[locale]/ban-history/page.tsx index 99ac6bf..f8f40d3 100644 --- a/web-next/app/[locale]/ban-history/page.tsx +++ b/web-next/app/[locale]/ban-history/page.tsx @@ -20,10 +20,6 @@ function fmt(d: Date): string { return `${p(d.getDate())}-${p(d.getMonth() + 1)}-${d.getFullYear()} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}` } -function StatusCell({ s }: { s: Sanction }) { - return {s.status} -} - /** Caja con la tabla de sanciones (baneos o muteos), o el vacío del diseño. */ async function SanctionBox({ title, @@ -68,7 +64,11 @@ async function SanctionBox({ {showScope && ( - {s.scope} + + {s.scopeKey === 'character' + ? t('ban.scope.character', { name: s.scopeName }) + : t(`ban.scope.${s.scopeKey}`)} + )} @@ -84,7 +84,7 @@ async function SanctionBox({ {s.expires ? fmt(s.expires) : t('ban.permanent')} - + {t(`ban.status.${s.statusKey}`)} ))} diff --git a/web-next/lib/ban-history.ts b/web-next/lib/ban-history.ts index cfdb540..1a0a4d6 100644 --- a/web-next/lib/ban-history.ts +++ b/web-next/lib/ban-history.ts @@ -1,26 +1,31 @@ import type { RowDataPacket } from 'mysql2' import { db, DB } from './db' -/** Una sanción (baneo o muteo) de la cuenta. */ +/** Una sanción (baneo o muteo) de la cuenta. Textos visibles como claves i18n. */ export interface Sanction { - scope: string // 'Cuenta' | 'Battle.net' | 'Personaje: Nombre' - reason: string - by: string + scopeKey: 'account' | 'battlenet' | 'character' + scopeName: string // nombre del personaje (solo para scopeKey='character') + reason: string // motivo escrito por el GM (dato, no se traduce) + by: string // quién sancionó (dato) date: Date expires: Date | null // null = permanente active: boolean - status: string // etiqueta: Activo / Activo (permanente) / Expirado + statusKey: 'active' | 'activePermanent' | 'expired' } /** Estado de un baneo a partir de sus timestamps UNIX (segundos) y el flag active. */ -function banState(bandate: number, unbandate: number, active?: number): { active: boolean; status: string; expires: Date | null } { +function banState( + bandate: number, + unbandate: number, + active?: number, +): { active: boolean; statusKey: Sanction['statusKey']; expires: Date | null } { const now = Math.floor(Date.now() / 1000) const permanent = unbandate === 0 || unbandate <= bandate const flagActive = active === undefined ? true : active === 1 const stillActive = flagActive && (permanent || unbandate > now) return { active: stillActive, - status: stillActive ? (permanent ? 'Activo (permanente)' : 'Activo') : 'Expirado', + statusKey: stillActive ? (permanent ? 'activePermanent' : 'active') : 'expired', expires: permanent ? null : new Date(unbandate * 1000), } } @@ -46,7 +51,7 @@ export async function getSanctions(accountId: number, bnetId?: number): Promise< ) for (const r of rows) { const s = banState(Number(r.bandate), Number(r.unbandate), Number(r.active)) - bans.push({ scope: 'Cuenta', reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) + bans.push({ scopeKey: 'account', scopeName: '', reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) } } catch { /* AC no poblado */ @@ -61,7 +66,7 @@ export async function getSanctions(accountId: number, bnetId?: number): Promise< ) for (const r of rows) { const s = banState(Number(r.bandate), Number(r.unbandate)) - bans.push({ scope: 'Battle.net', reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) + bans.push({ scopeKey: 'battlenet', scopeName: '', reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) } } catch { /* AC no poblado */ @@ -80,7 +85,7 @@ export async function getSanctions(accountId: number, bnetId?: number): Promise< ) for (const r of rows) { const s = banState(Number(r.bandate), Number(r.unbandate), Number(r.active)) - bans.push({ scope: `Personaje: ${nameByGuid.get(Number(r.guid)) ?? '?'}`, reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) + bans.push({ scopeKey: 'character', scopeName: nameByGuid.get(Number(r.guid)) ?? '?', reason: String(r.banreason || '—'), by: String(r.bannedby || '—'), date: new Date(Number(r.bandate) * 1000), ...s }) } } } catch { @@ -98,13 +103,14 @@ export async function getSanctions(accountId: number, bnetId?: number): Promise< const end = Number(r.mutetime) const active = end > now mutes.push({ - scope: 'Cuenta', + scopeKey: 'account', + scopeName: '', reason: String(r.mutereason || '—'), by: String(r.mutedby || '—'), date: new Date(Number(r.mutedate) * 1000), expires: end > 0 ? new Date(end * 1000) : null, active, - status: active ? 'Activo' : 'Expirado', + statusKey: active ? 'active' : 'expired', }) } } catch { diff --git a/web-next/messages/en.json b/web-next/messages/en.json index cba0809..eb82fa9 100644 --- a/web-next/messages/en.json +++ b/web-next/messages/en.json @@ -1040,7 +1040,17 @@ "thDate": "Date", "thExpires": "Expires", "thStatus": "Status", - "permanent": "Permanent" + "permanent": "Permanent", + "scope": { + "account": "Account", + "battlenet": "Battle.net", + "character": "Character: {name}" + }, + "status": { + "active": "Active", + "activePermanent": "Active (permanent)", + "expired": "Expired" + } }, "security": { "pageTitle": "Security history", diff --git a/web-next/messages/es.json b/web-next/messages/es.json index 88eb3a6..1569b14 100644 --- a/web-next/messages/es.json +++ b/web-next/messages/es.json @@ -1040,7 +1040,17 @@ "thDate": "Fecha", "thExpires": "Expira", "thStatus": "Estado", - "permanent": "Permanente" + "permanent": "Permanente", + "scope": { + "account": "Cuenta", + "battlenet": "Battle.net", + "character": "Personaje: {name}" + }, + "status": { + "active": "Activo", + "activePermanent": "Activo (permanente)", + "expired": "Expirado" + } }, "security": { "pageTitle": "Historial de seguridad",