Foro multiidioma: nombres/descripciones de categorías y foros (ES/EN)

forum_categories.name_en y forums.name_en/description_en (sql/add_forum_en.sql,
aplicado en prod y traducidos los existentes: Comunidad/Anuncios/Discusión general).
getForumIndex(locale) y getForum(id, locale) usan EN si existe, si no caen al
español. Las páginas del foro pasan el locale. Labels (Temas/Mensajes) ya estaban
traducidos. Verificado: /en/forum en inglés, /es/forum en español.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 19:50:20 +00:00
parent 1d2aae5df2
commit cc6c76859c
4 changed files with 31 additions and 10 deletions
@@ -23,7 +23,7 @@ export default async function ForumPage({
const t = await getTranslations('Forum') const t = await getTranslations('Forum')
const id = Number(forumId) const id = Number(forumId)
const forum = await getForum(id) const forum = await getForum(id, locale)
if (!forum) notFound() if (!forum) notFound()
const total = await countTopics(id) const total = await countTopics(id)
const totalPages = Math.max(1, Math.ceil(total / PER_PAGE)) const totalPages = Math.max(1, Math.ceil(total / PER_PAGE))
+1 -1
View File
@@ -10,7 +10,7 @@ export default async function ForumIndexPage({ params }: { params: Promise<{ loc
const { locale } = await params const { locale } = await params
setRequestLocale(locale) setRequestLocale(locale)
const t = await getTranslations('Forum') const t = await getTranslations('Forum')
const categories = await getForumIndex() const categories = await getForumIndex(locale)
return ( return (
<PageShell title={t('title')}> <PageShell title={t('title')}>
+16 -8
View File
@@ -60,13 +60,14 @@ export interface SearchResult {
} }
/** Índice del foro: categorías con sus foros visibles (+ contadores y último tema). */ /** Índice del foro: categorías con sus foros visibles (+ contadores y último tema). */
export async function getForumIndex(): Promise<Category[]> { export async function getForumIndex(locale = 'es'): Promise<Category[]> {
const en = locale === 'en'
try { try {
const [cats] = await db(DB.web).query<RowDataPacket[]>( const [cats] = await db(DB.web).query<RowDataPacket[]>(
'SELECT id, name FROM forum_categories ORDER BY `order`, id', 'SELECT id, name, name_en FROM forum_categories ORDER BY `order`, id',
) )
const [forums] = await db(DB.web).query<RowDataPacket[]>( const [forums] = await db(DB.web).query<RowDataPacket[]>(
`SELECT f.id, f.category_id, f.name, f.description, f.icon, `SELECT f.id, f.category_id, f.name, f.name_en, f.description, f.description_en, f.icon,
(SELECT COUNT(*) FROM forum_topics t WHERE t.forum_id = f.id AND t.deleted = 0) AS topic_count, (SELECT COUNT(*) FROM forum_topics t WHERE t.forum_id = f.id AND t.deleted = 0) AS topic_count,
(SELECT COUNT(*) FROM forum_posts p JOIN forum_topics t ON p.topic_id = t.id (SELECT COUNT(*) FROM forum_posts p JOIN forum_topics t ON p.topic_id = t.id
WHERE t.forum_id = f.id AND p.deleted = 0 AND t.deleted = 0) AS post_count, WHERE t.forum_id = f.id AND p.deleted = 0 AND t.deleted = 0) AS post_count,
@@ -82,12 +83,14 @@ export async function getForumIndex(): Promise<Category[]> {
) )
const byCat = new Map<number, ForumInfo[]>() const byCat = new Map<number, ForumInfo[]>()
for (const f of forums) { for (const f of forums) {
const info = f as unknown as ForumInfo const info = f as unknown as ForumInfo & { name_en?: string | null; description_en?: string | null }
info.name = en && info.name_en ? info.name_en : info.name
info.description = en && info.description_en ? info.description_en : info.description
if (!byCat.has(info.category_id)) byCat.set(info.category_id, []) if (!byCat.has(info.category_id)) byCat.set(info.category_id, [])
byCat.get(info.category_id)!.push(info) byCat.get(info.category_id)!.push(info)
} }
return cats return cats
.map((c) => ({ id: c.id, name: c.name, forums: byCat.get(c.id) ?? [] })) .map((c) => ({ id: c.id, name: en && c.name_en ? c.name_en : c.name, forums: byCat.get(c.id) ?? [] }))
.filter((c) => c.forums.length > 0) .filter((c) => c.forums.length > 0)
} catch { } catch {
return [] return []
@@ -106,13 +109,18 @@ export async function listForumsForMove(): Promise<{ id: number; name: string }[
} }
} }
export async function getForum(id: number): Promise<{ name: string; description: string } | null> { export async function getForum(id: number, locale = 'es'): Promise<{ name: string; description: string } | null> {
const en = locale === 'en'
try { try {
const [rows] = await db(DB.web).query<RowDataPacket[]>( const [rows] = await db(DB.web).query<RowDataPacket[]>(
'SELECT name, description FROM forums WHERE id = ? AND visibility = 1 AND deleted = 0', 'SELECT name, name_en, description, description_en FROM forums WHERE id = ? AND visibility = 1 AND deleted = 0',
[id], [id],
) )
return rows[0] ? { name: rows[0].name, description: rows[0].description } : null if (!rows[0]) return null
return {
name: en && rows[0].name_en ? rows[0].name_en : rows[0].name,
description: en && rows[0].description_en ? rows[0].description_en : rows[0].description,
}
} catch { } catch {
return null return null
} }
+13
View File
@@ -0,0 +1,13 @@
-- Foro multiidioma: nombre/descripción en inglés (opcional). Si están vacíos, se
-- cae a la versión en español. El texto original es ES.
ALTER TABLE forum_categories ADD COLUMN name_en VARCHAR(255) NULL AFTER name;
ALTER TABLE forums
ADD COLUMN name_en VARCHAR(45) NULL AFTER name,
ADD COLUMN description_en TEXT NULL AFTER description;
-- Traducción de las categorías/foros existentes.
UPDATE forum_categories SET name_en = 'Community' WHERE name = 'Comunidad';
UPDATE forums SET name_en = 'Announcements',
description_en = 'Official server news and announcements.' WHERE name = 'Anuncios';
UPDATE forums SET name_en = 'General Discussion',
description_en = 'Talk about everything server-related with the community.' WHERE name = 'Discusión general';