651d8deafc
Se externaliza el texto español hardcodeado de ~55 archivos a next-intl y se añaden traducciones al inglés, con paridad de claves es/en. Nuevos namespaces: History, CharService, CharServiceB, Points, Legal, UI, Misc (+ altas en Common/Admin). - Historiales (PD/PV, transacciones, sanciones, seguridad), servicios de personaje (transfer, send-gift, quest, restore-*, change-*, customize, level-up, gold, rename), PD/pagos (d-points, trade, promo, transfer-dp, rename-guild, DPointsTabs), páginas legales (cookies, privacidad, términos, reembolsos, aviso legal, contacto), layout (cabecera, footer, cookies, 2FA, descargas, jugadores, recluta) y páginas varias (home, reino, ayuda, addons, 2falogin). - Textos con markup inline via t.rich; interpolación con ICU. - Componente <NoteLegend/> para la leyenda NOTA/NOTE compartida. - payLabel/confirmText de los servicios de pago traducidos. - Verificado: tsc OK, next build OK, todas las claves t() resuelven en es y en, todos los t.rich casan etiquetas, páginas 200 en /es/ y /en/ sin claves crudas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
164 lines
7.4 KiB
TypeScript
164 lines
7.4 KiB
TypeScript
'use client'
|
||
|
||
import { useRef, useState } from 'react'
|
||
import { useTranslations } from 'next-intl'
|
||
|
||
// Lista de trackers común a todos los magnet (idéntica en el original).
|
||
const TRACKERS =
|
||
'&tr=http%3A%2F%2F184.105.151.164%3A6969%2Fannounce&tr=http%3A%2F%2F184.105.151.164%3A6969%2Fannounce&tr=http%3A%2F%2F182.150.53.61%3A8080%2Fannounce&tr=http%3A%2F%2F62.210.202.61%3A80%2Fannounce&tr=http%3A%2F%2F78.30.254.12%3A2710%2Fannounce&tr=http%3A%2F%2F91.207.136.85%3A80%2Fannounce&tr=http%3A%2F%2F176.113.71.19%3A6961%2Fannounce&tr=http%3A%2F%2F34.92.10.197%3A6789%2Fannounce&tr=http%3A%2F%2F80.209.252.132%3A1337%2Fannounce&tr=udp%3A%2F%2F62.138.0.158%3A6969%2Fannounce&tr=udp%3A%2F%2F188.241.58.209%3A6969%2Fannounce&tr=udp%3A%2F%2F93.158.213.92%3A1337%2Fannounce&tr=udp%3A%2F%2F62.210.97.59%3A1337%2Fannounce&tr=udp%3A%2F%2F151.80.120.112%3A2710%2Fannounce&tr=udp%3A%2F%2F80.209.252.132%3A1337%2Fannounce'
|
||
|
||
// Hash btih por idioma (Windows y Mac OS Catalina comparten el mismo torrent).
|
||
const BTIH: Record<string, string> = {
|
||
wotlkeses: 'AIV765TUMAIZSGLSVGN7MRYASNJW6UPD',
|
||
wotlkesmx: 'EL64Q3QUXM42PBGJC4TUHNAH2UUTAJXB',
|
||
wotlkenus: 'XAJ4DBISLXFEV2N74IIUZLA5XXVGVRG7',
|
||
wotlkengb: '7U3ATWR7YN2IR63L53EFPPHZ4SI4RZKX',
|
||
}
|
||
function magnetFor(lang: string): string {
|
||
return `magnet:?xt=urn:btih:${BTIH[lang]}${TRACKERS}`
|
||
}
|
||
|
||
const LANG_INFO_KEY: Record<string, string> = {
|
||
wotlkeses: 'clientDownload.langEses',
|
||
wotlkesmx: 'clientDownload.langEsmx',
|
||
wotlkenus: 'clientDownload.langEnus',
|
||
wotlkengb: 'clientDownload.langEngb',
|
||
}
|
||
|
||
export function ClientDownload() {
|
||
const t = useTranslations('UI')
|
||
const [lang, setLang] = useState('')
|
||
const [os, setOs] = useState('')
|
||
const [error, setError] = useState(false)
|
||
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||
|
||
function handleSubmit(e: React.FormEvent) {
|
||
e.preventDefault()
|
||
if (!lang || !os) {
|
||
setError(true)
|
||
if (timer.current) clearTimeout(timer.current)
|
||
timer.current = setTimeout(() => setError(false), 5000)
|
||
return
|
||
}
|
||
window.location.href = magnetFor(lang)
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<div className="box-content">
|
||
<div className="title-box-content" id="torrent">
|
||
<h2>{t('clientDownload.torrentTitle')}</h2>
|
||
</div>
|
||
<div className="body-box-content justified">
|
||
<p><span>{t('clientDownload.infoLabel')}</span></p>
|
||
<p>{t('clientDownload.info1')}</p>
|
||
<p>{t('clientDownload.info2')}</p>
|
||
<p>{t('clientDownload.info3')}</p>
|
||
<br />
|
||
<p><span>{t('clientDownload.requirementsLabel')}</span></p>
|
||
<p>{t('clientDownload.requirementsText')}</p>
|
||
<ul>
|
||
<li><a href="https://www.qbittorrent.org/download.php" target="_blank" rel="noopener noreferrer">qBittorrent</a></li>
|
||
<li><a href="https://www.winrar.es/descargas" target="_blank" rel="noopener noreferrer">WinRAR</a></li>
|
||
</ul>
|
||
<p>{t('clientDownload.popupNote')}</p>
|
||
<br />
|
||
<p><span>{t('clientDownload.stepByStep')}</span></p>
|
||
<p>{t.rich('clientDownload.stepOptional', { winrar: (c) => <a href="https://www.winrar.es/descargas" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
|
||
<p>{t.rich('clientDownload.step1', { qbit: (c) => <a href="https://www.qbittorrent.org/download.php" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
|
||
<p>{t('clientDownload.step2')}</p>
|
||
<p>{t('clientDownload.step3')}</p>
|
||
<p>{t('clientDownload.step4')}</p>
|
||
<p>{t('clientDownload.step5')}</p>
|
||
<p>{t('clientDownload.step6')}</p>
|
||
<p>{t('clientDownload.step7')}</p>
|
||
<p>{t('clientDownload.step8')}</p>
|
||
<p>{t('clientDownload.step9')}</p>
|
||
<p>{t('clientDownload.step10')}</p>
|
||
<p>{t('clientDownload.step11')}</p>
|
||
<p>{t('clientDownload.step12')}</p>
|
||
<p>{t('clientDownload.step13')}</p>
|
||
<p>{t('clientDownload.step14')}</p>
|
||
<p>{t('clientDownload.step15')}</p>
|
||
<br />
|
||
<hr />
|
||
<br />
|
||
<div className="centered">
|
||
<form name="uw-client-dowload" id="uw-client-download" autoComplete="off" onSubmit={handleSubmit}>
|
||
<p><span>{t('clientDownload.languageLabel')}</span></p>
|
||
<br />
|
||
<select
|
||
name="game-language"
|
||
id="game-language"
|
||
className="game-download-select"
|
||
value={lang}
|
||
onChange={(e) => { setLang(e.target.value); setOs(''); }}
|
||
>
|
||
<option value="" disabled>{t('clientDownload.chooseLanguage')}</option>
|
||
<option value="wotlkeses">{t('clientDownload.clientEses')}</option>
|
||
<option value="wotlkesmx">{t('clientDownload.clientEsmx')}</option>
|
||
<option value="wotlkenus">{t('clientDownload.clientEnus')}</option>
|
||
<option value="wotlkengb">{t('clientDownload.clientEngb')}</option>
|
||
</select>
|
||
|
||
{lang && (
|
||
<div id="select-op">
|
||
<br />
|
||
<p id="game-info">{lang ? t(LANG_INFO_KEY[lang]) :' '}</p>
|
||
<br />
|
||
<p><span>{t('clientDownload.osLabel')}</span></p>
|
||
<br />
|
||
<select
|
||
name="operating-system"
|
||
id="operating-system"
|
||
className="game-download-select"
|
||
value={os}
|
||
onChange={(e) => setOs(e.target.value)}
|
||
>
|
||
<option value="" disabled>{t('clientDownload.chooseOs')}</option>
|
||
<option value="windows">{t('clientDownload.windows')}</option>
|
||
<option value="macosc">{t('clientDownload.macCatalina')}</option>
|
||
</select>
|
||
<br />
|
||
<button type="submit" className="g-download-button" id="download-c-button">{t('clientDownload.downloadBtn')}</button>
|
||
</div>
|
||
)}
|
||
</form>
|
||
<br />
|
||
<hr />
|
||
<div id="error-message" className="red-form-response">
|
||
{error ? t('clientDownload.errorChoose') : ''}
|
||
</div>
|
||
<br />
|
||
</div>
|
||
</div>
|
||
|
||
{os === 'macosc' && (
|
||
<div id="op-info" className="box-content">
|
||
<div className="title-box-content">
|
||
<h2>{t('clientDownload.macGuideTitle')}</h2>
|
||
</div>
|
||
<div className="body-box-content">
|
||
<p>{t('clientDownload.mac1')}</p>
|
||
<p>{t('clientDownload.mac2')}</p>
|
||
<br />
|
||
<span>{t('clientDownload.macInstructions')}</span>
|
||
<p>{t.rich('clientDownload.macStep1', { crossover: (c) => <a href="https://insmac.org/macosx/3728-crossover-19.html" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
|
||
<p>{t('clientDownload.macStep2')}</p>
|
||
<p>{t('clientDownload.macStep3')}</p>
|
||
<p>{t('clientDownload.macStep4')}</p>
|
||
<p>{t('clientDownload.macStep5')}</p>
|
||
<p>{t('clientDownload.macStep6')}</p>
|
||
<p>{t('clientDownload.macStep7')}</p>
|
||
<p>{t('clientDownload.macStep8')}</p>
|
||
<p>{t('clientDownload.macStep9')}</p>
|
||
<p>{t('clientDownload.macStep10')}</p>
|
||
<p>{t('clientDownload.macStep11')}</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
)
|
||
}
|