Files
NightSpire/web-next/app/[locale]/page.tsx
T
Inna 86c9b01c76 Noticias multiidioma (ES/EN) con fallback
home_noticia gana columnas titulo_en/contenido_en (sql/add_news_en.sql). getNoticias(locale)
usa la versión EN si existe, si no cae al español (título y contenido independientes); la fecha
también se formatea por idioma. El panel de admin permite escribir la traducción al crear y EDITAR
noticias existentes (nuevo PUT /api/admin/news/[id], updateNews). Claves Admin nuevas (título/
contenido EN, editar/guardar). Probado: /en/ muestra EN, /es/ ES; PUT guardado (403 sin sesión).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 19:14:07 +00:00

159 lines
6.2 KiB
TypeScript

import { getTranslations, setRequestLocale } from 'next-intl/server'
import { Link } from '@/i18n/navigation'
import { ServerClock } from '@/components/ServerClock'
import { getNoticias, getServerStatus, type Noticia, type ServerStatus } from '@/lib/home'
import { getRealmName } from '@/lib/realm'
// SSR por petición: los datos se leen DIRECTAMENTE de MySQL desde el servidor Next.
export const dynamic = 'force-dynamic'
async function getData(locale: string): Promise<{ noticias: Noticia[]; status: ServerStatus | null }> {
const [news, status] = await Promise.allSettled([getNoticias(locale), getServerStatus()])
return {
noticias: news.status === 'fulfilled' ? news.value : [],
status: status.status === 'fulfilled' ? status.value : null,
}
}
// Sustituye la marca «UltimoWoW»/«Nova WoW» por el nombre del reino SOLO en el
// texto visible (fuera de las etiquetas < >), para no romper las URLs de los
// enlaces; respeta MAYÚSCULAS y no toca dominios «...com».
function brandify(html: string, realm: string): string {
const re = /(?:ultimo\s?wow|nova\s?wow)(?!\.com)/gi
const repl = (m: string) => (m === m.toUpperCase() ? realm.toUpperCase() : realm)
return html.replace(/<[^>]*>|[^<]+/g, (seg) => (seg.startsWith('<') ? seg : seg.replace(re, repl)))
}
function formatFecha(iso: string | Date | null, locale: string): string {
if (!iso) return ''
const d = new Date(iso)
if (isNaN(d.getTime())) return String(iso)
return d.toLocaleDateString(locale === 'en' ? 'en-US' : 'es-ES', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
}
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Misc')
const { noticias, status } = await getData(locale)
const realmName = await getRealmName()
const worldStatus = status ? status.world_status : 'Offline'
const worldClass = worldStatus === 'Online' ? 'green-info' : 'red-info'
const loginStatus = status ? status.status : 'Offline'
const loginClass = loginStatus === 'Online' ? 'green-info' : 'red-info'
return (
<div className="main-page">
<div className="middle-content middle-content-index">
<div className="body-content">
<div className="title-content">
<h1>{realmName} WOTLK 3.4.3</h1>
</div>
<div className="box-content">
<div className="raf-index-holder">
<div className="right-content" id="right-content-min">
<div className="right-body">
{/* Estado del servidor + reloj en vivo (réplica de la isla React de Django) */}
<table className="max-center-table">
<tbody>
<tr>
<td className="yellow-info">
{realmName}{' '}
<span className="blue-info small-font">{status?.expansion ?? ''}</span>
</td>
<td className={`real-info-box ${worldClass}`}>{worldStatus}</td>
</tr>
<tr>
<td>Login</td>
<td className={`real-info-box ${loginClass}`}>{loginStatus}</td>
</tr>
<tr>
<td>{t('home.labelTime')}</td>
<td className="real-info-box">
<ServerClock />
</td>
</tr>
<tr>
<td colSpan={2}>
<hr />
</td>
</tr>
<tr className="centered">
<td colSpan={2}>
<p>
<span>{status?.address ?? ''}</span>
</p>
</td>
</tr>
</tbody>
</table>
</div>
<div className="raf-index-responsive">
<Link href="/recruit-a-friend">{t('home.recruitFriend')}</Link>
</div>
</div>
<div className="raf-index">
<Link href="/recruit-a-friend">{t('home.recruitFriend')}</Link>
</div>
</div>
<div className="body-box-content">
<div className="body-box-content">
<p>{t('home.introP1', { realm: realmName })}</p>
<p>{t('home.introP2')}</p>
<br />
<p>{t('home.introP3')}</p>
<p>{t('home.introP4')}</p>
</div>
</div>
</div>
<br />
<div className="title-content-h3">
<h3 className="big-font">{t('home.newsHeading')}</h3>
</div>
<div className="box-content">
<div className="body-box-content">
{noticias.length === 0 ? (
<p>{t('home.noNews')}</p>
) : (
<>
{noticias.map((n) => (
<div key={n.id} className="noticia-item">
<h3>{brandify(n.titulo, realmName)}</h3>
<span className="date">{formatFecha(n.fecha, locale)}</span>
<p
className="noticia-contenido"
dangerouslySetInnerHTML={{ __html: brandify(n.contenido, realmName) }}
/>
{n.enlace && (
<p>
{t('home.newsLinkLabel')}{' '}
<a href={n.enlace} target="_blank" rel="noopener noreferrer">
{t('home.here')}
</a>
</p>
)}
<br />
<hr />
<br />
</div>
))}
<p className="centered third-brown">
<i>{t('home.showingLatest')}</i>
</p>
<br />
</>
)}
</div>
</div>
</div>
</div>
</div>
)
}