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.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import type { Article, Category } from '#shared/types/blog'
|
||||
|
||||
/**
|
||||
* Resolves the language-switch target *before* the layout (and its header,
|
||||
* rendered ahead of the page in document order) mounts — setting this from
|
||||
* inside the page component itself is too late: a parent renders its
|
||||
* children in document order, so the header's LanguageSwitch would already
|
||||
* be resolved before the page's own setup ever runs, translated content or
|
||||
* not. Running as global route middleware guarantees this resolves first.
|
||||
*/
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
const langSwitch = useLangSwitchState()
|
||||
const locale = to.path === '/en' || to.path.startsWith('/en/') ? 'en' : 'it'
|
||||
const bare = locale === 'en' ? to.path.slice(3) || '/' : to.path
|
||||
|
||||
const articleMatch = bare.match(/^\/blog\/([^/]+)$/)
|
||||
const categoryMatch = bare.match(/^\/category\/([^/]+)$/)
|
||||
|
||||
if (articleMatch) {
|
||||
const slug = articleMatch[1]
|
||||
const { data } = await useFetch<Article>(`/content/articles/${slug}`, {
|
||||
key: `article-${slug}-${locale}`,
|
||||
query: { lang: locale },
|
||||
})
|
||||
langSwitch.value = { available: Boolean(data.value?.hasTranslation), fallback: '/en/blog' }
|
||||
return
|
||||
}
|
||||
|
||||
if (categoryMatch) {
|
||||
// A category's own translation only gates that specific label — the
|
||||
// switch always lands somewhere useful (a filtered, possibly empty,
|
||||
// article list), so it's always shown.
|
||||
void useFetch<Category>(`/content/categories/${categoryMatch[1]}`, {
|
||||
key: `category-${categoryMatch[1]}-${locale}`,
|
||||
query: { lang: locale },
|
||||
})
|
||||
langSwitch.value = { available: true, fallback: '/en/blog' }
|
||||
return
|
||||
}
|
||||
|
||||
if (bare === '/come-difendersi-dal-corralito') {
|
||||
langSwitch.value = { available: false, fallback: '/en' }
|
||||
return
|
||||
}
|
||||
|
||||
langSwitch.value = { available: true, fallback: '/en' }
|
||||
})
|
||||
Reference in New Issue
Block a user