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:
2026-09-11 13:53:17 +02:00
parent 847edc7397
commit 33b49c5dbb
40 changed files with 1285 additions and 576 deletions
+33 -17
View File
@@ -1,26 +1,36 @@
<script setup lang="ts">
import { STRINGS } from '#shared/utils/i18n'
import { localePath } from '#shared/utils/locale'
const locale = useLocale()
const strings = computed(() => STRINGS[locale.value])
</script>
<template>
<div class="page">
<a class="skip-link" href="#main">Vai al contenuto</a>
<a class="skip-link" href="#main">{{ strings.skipLink }}</a>
<header class="site-header">
<div class="container">
<p class="wordmark">
<NuxtLink to="/">
<NuxtLink :to="localePath(locale, '/')">
<img src="/logo.png" :alt="SITE_NAME" width="400" height="400">
</NuxtLink>
</p>
<p class="tagline kicker">Articoli, guide e analisi.</p>
<p class="tagline kicker">{{ strings.tagline }}</p>
<p class="lang-switch-header">
<LanguageSwitch />
</p>
</div>
<nav class="site-nav" aria-label="Navigazione principale">
<nav class="site-nav" :aria-label="strings.navAriaLabel">
<div class="container nav-inner">
<ul>
<li><NuxtLink to="/">Home</NuxtLink></li>
<li><NuxtLink to="/blog">Tutti gli articoli</NuxtLink></li>
<li><NuxtLink to="/come-difendersi-dal-corralito">Come difendersi dal corralito</NuxtLink></li>
<li><NuxtLink :to="localePath(locale, '/')">{{ strings.navHome }}</NuxtLink></li>
<li><NuxtLink :to="localePath(locale, '/blog')">{{ strings.navBlog }}</NuxtLink></li>
<li v-if="locale === 'it'">
<NuxtLink to="/come-difendersi-dal-corralito">{{ strings.navCorralito }}</NuxtLink>
</li>
</ul>
</div>
</nav>
@@ -34,24 +44,26 @@
<div class="container footer-inner">
<div class="footer-block">
<img class="footer-logo" src="/logo.png" :alt="SITE_NAME" width="400" height="400">
<p class="muted">Articoli, guide e analisi.</p>
<p class="muted">{{ strings.tagline }}</p>
<address class="contact">
Contatti:
{{ strings.contactLabel }}
<a :href="`mailto:${SITE_EMAIL}`">{{ SITE_EMAIL }}</a>
</address>
</div>
<nav class="footer-block" aria-label="Naviga">
<p class="kicker">Naviga</p>
<nav class="footer-block" :aria-label="strings.footerNavAriaLabel">
<p class="kicker">{{ strings.footerNavigaKicker }}</p>
<ul>
<li><NuxtLink to="/">Home</NuxtLink></li>
<li><NuxtLink to="/blog">Tutti gli articoli</NuxtLink></li>
<li><NuxtLink to="/come-difendersi-dal-corralito">Come difendersi dal corralito</NuxtLink></li>
<li><NuxtLink :to="localePath(locale, '/')">{{ strings.navHome }}</NuxtLink></li>
<li><NuxtLink :to="localePath(locale, '/blog')">{{ strings.navBlog }}</NuxtLink></li>
<li v-if="locale === 'it'">
<NuxtLink to="/come-difendersi-dal-corralito">{{ strings.navCorralito }}</NuxtLink>
</li>
</ul>
</nav>
<nav class="footer-block" aria-label="Seguici">
<p class="kicker">Seguici</p>
<nav class="footer-block" :aria-label="strings.footerFollowAriaLabel">
<p class="kicker">{{ strings.footerSeguiciKicker }}</p>
<ul>
<li v-for="social in SOCIAL_LINKS" :key="social.name">
<a class="social" :href="social.url" target="_blank" rel="noopener me">
@@ -64,7 +76,7 @@
</div>
<p class="container copyright kicker">
&copy; {{ new Date().getFullYear() }} {{ SITE_NAME }}. Tutti i diritti riservati.
&copy; {{ new Date().getFullYear() }} {{ SITE_NAME }}. {{ strings.copyright }}
</p>
</footer>
</div>
@@ -109,6 +121,10 @@
margin-top: 0.6rem;
}
.lang-switch-header {
margin-top: 0.5rem;
}
.site-nav {
margin-top: 1.25rem;
border-top: 1px solid var(--color-border);