i18n: traducir toda la app (ES + EN) — páginas y componentes con texto hardcodeado
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>
This commit is contained in:
@@ -1,23 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { RecruitRewardView } from '@/lib/recruit-claim'
|
||||
import type { CharOption } from '@/components/CharacterSelect'
|
||||
|
||||
const ICON_BASE = 'https://wotlk.ultimowow.com/static/images/wow/icons/tiny'
|
||||
|
||||
function claimError(message?: string): string {
|
||||
function claimErrorKey(message?: string): string {
|
||||
switch (message) {
|
||||
case 'requirementsNotMet':
|
||||
return 'Aún no cumples los requisitos para esta recompensa.'
|
||||
return 'recruit.errRequirements'
|
||||
case 'alreadyClaimed':
|
||||
return 'Esta recompensa ya fue reclamada.'
|
||||
return 'recruit.errAlreadyClaimed'
|
||||
case 'invalidCharacter':
|
||||
return 'Personaje no válido.'
|
||||
return 'recruit.errInvalidCharacter'
|
||||
case 'notFound':
|
||||
return 'Recompensa no encontrada.'
|
||||
return 'recruit.errNotFound'
|
||||
default:
|
||||
return 'No se pudo reclamar la recompensa. Inténtalo de nuevo.'
|
||||
return 'recruit.errGeneric'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ export function RecruitPanel({
|
||||
isRecruited: boolean
|
||||
recruitedCount: number
|
||||
}) {
|
||||
const t = useTranslations('UI')
|
||||
const [character, setCharacter] = useState(characters[0]?.name ?? '')
|
||||
const selectedCss = characters.find((c) => c.name === character)?.classCss
|
||||
const [busyId, setBusyId] = useState<number | null>(null)
|
||||
@@ -62,10 +64,10 @@ export function RecruitPanel({
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
setMsg({ ok: false, text: claimError(d.message) })
|
||||
setMsg({ ok: false, text: t(claimErrorKey(d.message)) })
|
||||
setBusyId(null)
|
||||
} catch {
|
||||
setMsg({ ok: false, text: claimError() })
|
||||
setMsg({ ok: false, text: t(claimErrorKey()) })
|
||||
setBusyId(null)
|
||||
}
|
||||
}
|
||||
@@ -90,15 +92,15 @@ export function RecruitPanel({
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="lefted">Cuenta reclutada: <span>{isRecruited ? 'Sí' : 'No'}</span></p>
|
||||
<p className="lefted">Amigos reclutados: <span>{recruitedCount}</span></p>
|
||||
<p className="lefted">Amigos reclutados al nivel 80: <span>{level80Count}</span></p>
|
||||
<p className="lefted">Recompensas reclamadas: <span>{claimedCount}/{rewards.length}</span></p>
|
||||
<p className="lefted">{t('recruit.accountRecruited')} <span>{isRecruited ? t('recruit.yes') : t('recruit.no')}</span></p>
|
||||
<p className="lefted">{t('recruit.friendsRecruited')} <span>{recruitedCount}</span></p>
|
||||
<p className="lefted">{t('recruit.friendsAtLevel80')} <span>{level80Count}</span></p>
|
||||
<p className="lefted">{t('recruit.rewardsClaimed')} <span>{claimedCount}/{rewards.length}</span></p>
|
||||
<br />
|
||||
|
||||
{characters.length > 0 && (
|
||||
<p className="lefted">
|
||||
Recibir recompensas en:{' '}
|
||||
{t('recruit.receiveRewardsOn')}{' '}
|
||||
<select value={character} onChange={(e) => setCharacter(e.target.value)} className={['account-select', selectedCss].filter(Boolean).join(' ')}>
|
||||
{characters.map((c) => (
|
||||
<option key={c.name} value={c.name} className={c.classCss ? `${c.classCss} big-font` : undefined}>{c.name}</option>
|
||||
@@ -111,25 +113,25 @@ export function RecruitPanel({
|
||||
<table className="char-center-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Amigos reclutados</th>
|
||||
<th>Recompensa</th>
|
||||
<th>Estado</th>
|
||||
<th>{t('recruit.thFriends')}</th>
|
||||
<th>{t('recruit.thReward')}</th>
|
||||
<th>{t('recruit.thStatus')}</th>
|
||||
</tr>
|
||||
{rewards.map((r) => {
|
||||
const eligible = level80Count >= r.required_friends
|
||||
return (
|
||||
<tr key={r.id}>
|
||||
<td><span>{r.required_friends} {r.required_friends === 1 ? 'amigo' : 'amigos'} al nivel 80</span></td>
|
||||
<td><span>{t('recruit.friendsAtLevel80Row', { count: r.required_friends })}</span></td>
|
||||
<td>{rewardCell(r)}</td>
|
||||
<td>
|
||||
{r.claimed ? (
|
||||
<span className="green-info"><i className="fas fa-check"></i> Reclamado</span>
|
||||
<span className="green-info"><i className="fas fa-check"></i> {t('recruit.claimed')}</span>
|
||||
) : eligible ? (
|
||||
<button type="button" className="nw-tool-btn" onClick={() => claim(r)} disabled={busyId !== null}>
|
||||
{busyId === r.id ? '…' : 'Reclamar'}
|
||||
{busyId === r.id ? '…' : t('recruit.claim')}
|
||||
</button>
|
||||
) : (
|
||||
<span>Sin reclamar</span>
|
||||
<span>{t('recruit.unclaimed')}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -140,13 +142,13 @@ export function RecruitPanel({
|
||||
|
||||
<br />
|
||||
<hr />
|
||||
<p>RECOMPENSAS DISPONIBLES</p>
|
||||
<p>{t('recruit.availableRewards')}</p>
|
||||
<hr />
|
||||
{available.length === 0 ? (
|
||||
<span>Recluta amigos para poder reclamar las recompensas</span>
|
||||
<span>{t('recruit.recruitToClaim')}</span>
|
||||
) : (
|
||||
<span className="green-info">
|
||||
Tienes {available.length} recompensa{available.length === 1 ? '' : 's'} lista{available.length === 1 ? '' : 's'} para reclamar
|
||||
{t('recruit.readyToClaim', { count: available.length })}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user