5bcf71a37b
Los enlaces de wowhead se guardan con el locale de quien los postea (subdominio, domain y nombre), así que un ítem posteado en /es se veía en español aunque lo leyeras en /en, y al revés. Ahora siguen el idioma de QUIEN LEE. Al renderizar el tema (server component, conoce el locale del lector) se localizan los enlaces: se reescribe el subdominio y el domain del data-wowhead y se vuelve a resolver el nombre en el idioma del lector (lib/forum-wowhead + lib/wowhead-resolve, compartido con la API del editor y cacheado). Es solo de presentación: no cambia lo guardado ni añade parpadeo (el nombre llega ya resuelto del servidor). Verificado: un post guardado con nombre inglés y domain=wotlk se lee como «Bolsa de tejido de Escarcha» + es.wotlk en /es, y «Frostweave Bag» + www en /en. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
2.6 KiB
TypeScript
61 lines
2.6 KiB
TypeScript
import { wowheadUrl, wowheadData, type WowheadType } from './wowhead'
|
|
import { fetchWowheadInfo } from './wowhead-resolve'
|
|
|
|
// Localiza los enlaces de wowhead de un post al idioma de QUIEN LO LEE (no de quien
|
|
// lo posteó). Los enlaces se guardan con el locale del autor (subdominio, domain y
|
|
// nombre); aquí, al renderizar el tema, se reescriben al locale del lector: se ajusta
|
|
// el subdominio y el domain del tooltip, y se vuelve a resolver el nombre en su idioma.
|
|
// No toca lo guardado: es solo de presentación.
|
|
|
|
const ANCHOR_RE = /<a\b([^>]*)>([\s\S]*?)<\/a>/gi
|
|
const ENTITY_RE = /(item|spell|quest|npc|achievement|object)=(\d+)/i
|
|
|
|
function attr(attrs: string, name: string): string | undefined {
|
|
return new RegExp(`${name}="([^"]*)"`, 'i').exec(attrs)?.[1]
|
|
}
|
|
function escAttr(s: string): string {
|
|
return s.replace(/&/g, '&').replace(/"/g, '"')
|
|
}
|
|
function escHtml(s: string): string {
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
}
|
|
|
|
/** Extrae type=id de un enlace de wowhead (de data-wowhead o del href). */
|
|
function entityOf(attrs: string): { type: WowheadType; id: string } | null {
|
|
if (!/wowhead\.com/i.test(attrs) && !/data-wowhead=/i.test(attrs)) return null
|
|
const src = attr(attrs, 'data-wowhead') || attr(attrs, 'href') || ''
|
|
const m = ENTITY_RE.exec(src)
|
|
return m ? { type: m[1].toLowerCase() as WowheadType, id: m[2] } : null
|
|
}
|
|
|
|
export async function localizeWowheadLinks(html: string, locale: string): Promise<string> {
|
|
if (!html || (!/wowhead\.com/i.test(html) && !/data-wowhead/i.test(html))) return html
|
|
|
|
// 1) recopila las entidades únicas y resuelve su nombre en el locale del lector
|
|
const entities = new Map<string, { type: WowheadType; id: string }>()
|
|
for (const m of html.matchAll(ANCHOR_RE)) {
|
|
const e = entityOf(m[1])
|
|
if (e) entities.set(`${e.type}=${e.id}`, e)
|
|
}
|
|
if (entities.size === 0) return html
|
|
|
|
const names = new Map<string, string>()
|
|
await Promise.all(
|
|
[...entities.values()].map(async (e) => {
|
|
const info = await fetchWowheadInfo(e.type, e.id, locale)
|
|
if (info?.name) names.set(`${e.type}=${e.id}`, info.name)
|
|
}),
|
|
)
|
|
|
|
// 2) reescribe cada enlace de wowhead al locale del lector
|
|
return html.replace(ANCHOR_RE, (full, attrs: string, inner: string) => {
|
|
const e = entityOf(attrs)
|
|
if (!e) return full
|
|
const key = `${e.type}=${e.id}`
|
|
const href = wowheadUrl(e.type, e.id, locale)
|
|
const wh = wowheadData(e.type, e.id, locale)
|
|
const name = names.get(key) || inner // si no resuelve, se conserva el texto guardado
|
|
return `<a href="${href}" data-wowhead="${escAttr(wh)}" target="_blank" rel="noopener noreferrer">${escHtml(name)}</a>`
|
|
})
|
|
}
|