Files
blog/frontend/server/routes/content/articles/[slug].get.ts
T
davide 33b49c5dbb Add hand-rolled English support (/en/*) alongside the Italian site
PocketBase gains optional, manually-authored English fields on articles
and categories (same record, same slug), and the frontend serves an /en
counterpart of every dynamic route via thin page wrappers around shared
view components — no i18n library, consistent with the project's existing
minimalism stance for a two-locale site with ~20 UI strings.

An article/category with no translation 404s cleanly on its own /en
detail page and is filtered out of /en listings, and the header's
language-switch link falls back to the English blog index rather than a
dead link; resolving that requires a global route middleware, since the
layout's header renders before the page content in document order and so
can't react to state a page component sets during its own async setup.
2026-09-11 13:53:17 +02:00

75 lines
2.2 KiB
TypeScript

import type { Article, Category } from '#shared/types/blog'
interface RawCategory extends Category {
nameEn?: string | null
}
interface RawArticle {
id: string
title: string
titleEn: string | null
slug: string
publishedAt: string
content: string | null
contentEn: string | null
cover: string
coverAlt: string | null
coverAltEn: string | null
authorName: string | null
expand?: { category?: RawCategory }
}
export default defineEventHandler(async (event): Promise<Article> => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({ statusCode: 400, statusMessage: 'Missing article slug' })
}
const lang = langParam(getQuery(event).lang)
const response = await pbFetch<PbList<RawArticle>>(
`/api/collections/articles/records?fields=${ARTICLE_DETAIL_FIELDS}&expand=category&filter=${encodeURIComponent(`slug = ${quote(slug)}`)}&${pagination(1, 1)}`
)
const article = response.items[0]
if (!article) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
const hasTranslation = Boolean(article.titleEn && article.contentEn)
// An /en/ request for an article with no translation yet 404s cleanly,
// rather than silently falling back to Italian text on an English URL —
// see docs/content-model.md.
if (lang === 'en' && !hasTranslation) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
const title = lang === 'en' ? article.titleEn! : article.title
const content = lang === 'en' ? article.contentEn : article.content
const coverAlt = lang === 'en' ? article.coverAltEn || article.coverAlt : article.coverAlt
const category = article.expand?.category
? {
name:
lang === 'en'
? article.expand.category.nameEn || article.expand.category.name
: article.expand.category.name,
slug: article.expand.category.slug,
}
: null
return {
title,
slug: article.slug,
publishedAt: article.publishedAt,
cover: toMediaImage('articles', article.id, article.cover, coverAlt),
category,
html: renderMarkdown(content),
summary: summarise(content),
author: article.authorName || null,
hasTranslation,
}
})