# Frontend (Nuxt) ## Pages | Route | English counterpart | File(s) | Behavior | |---|---|---|---| | `/` | `/en` | `app/pages/index.vue` + `app/pages/en/index.vue`, both thin wrappers around `app/components/views/HomeView.vue` | Static hero/intro copy plus the single latest article, fetched via `/content/articles` (page 1), shown as a featured block. | | `/blog` | `/en/blog` | `app/pages/blog/index.vue` + `app/pages/en/blog/index.vue` → `BlogIndexView.vue` | Paginated archive (`PAGE_SIZE = 12`), grid of `ArticleCard`, prev/next via `?page=`. | | `/blog/[slug]` | `/en/blog/[slug]` | `app/pages/blog/[slug].vue` + `app/pages/en/blog/[slug].vue` → `ArticleView.vue` | Full article: fetches `/content/articles/:slug`, renders the pre-converted `article.html`, SEO meta, canonical URL, Open Graph, JSON-LD `BlogPosting`, breadcrumb to its category. | | `/category/[slug]` | `/en/category/[slug]` | `app/pages/category/[slug].vue` + `app/pages/en/category/[slug].vue` → `CategoryView.vue` | Fetches the category by slug, then a paginated, category-filtered article list; 404s if the category doesn't exist. | | `/come-difendersi-dal-corralito` | `/en/come-difendersi-dal-corralito` | `app/pages/come-difendersi-dal-corralito.vue` + `app/pages/en/come-difendersi-dal-corralito.vue` → `CorralitoView.vue` | Fully static marketing page, no PocketBase data — copy lives in `STRINGS[locale].corralito` (`shared/utils/i18n.ts`), same pattern as every other view. | All pages are SSR (`useFetch`/`useSeoMeta`); nothing blog-related is client-only-rendered. ### English content Each dynamic route has a `/en/`-prefixed counterpart, implemented as a thin page file (`app/pages/en/**`) that renders the exact same view component as its Italian counterpart with `locale="en"` — no template is duplicated. `app/components/views/*.vue` hold the actual markup and logic; the two page files per route only differ in that one prop. This is deliberately hand-rolled (no `@nuxtjs/i18n` or similar): two locales, ~20 short UI strings, no plurals — a library's routing and message-catalog machinery would be more code than the plain approach below, and CLAUDE.md's existing minimalism rule ("no dependency without a concrete, current need") still applies once you account for what a library *wouldn't* solve for you here (translation-availability fallback logic, Nitro locale-awareness, and most of the SEO work are custom code either way). - **Locale detection**: purely from the URL path (`useLocale()` composable — `/en` or `/en/...` is English, everything else Italian). No cookie, no `Accept-Language` negotiation. - **Static UI strings**: `frontend/shared/utils/i18n.ts` — a plain `{ it: {...}, en: {...} }` object (`STRINGS`), keyed by role, with an `interpolate()` helper for the handful of strings that take a parameter (e.g. `"Pagina {current} di {total}"`). This is the *only* place UI copy lives; don't hardcode a string in a component when adding a new one. - **Locale-aware paths**: `frontend/shared/utils/locale.ts` — `localePath(locale, path)` prefixes a bare path with `/en` when needed; `bareLocalePath(path)` does the inverse. - **Dates**: `formatDate`/`formatDateTime` (`shared/utils/format.ts`) take an optional second `Locale` argument (`'it' | 'en'`, default `'it'`) and format via `Intl.DateTimeFormat` with `it-IT`/`en-GB`. No new dependency, and every pre-existing call site keeps working unchanged. - **`/content/*` endpoints**: all four take an optional `?lang=en` query param (`langParam()` in `server/utils/queries.ts`, default `it`). See below for the fallback policy. - **The language-switch button** (`app/components/LanguageSwitch.vue`, rendered in the header by `default.vue`) needs to know, for the *current* page, whether an equivalent page exists in the other language — an article/category with no translation shouldn't link to a page that then 404s. That information is resolved by `app/middleware/lang-switch.global.ts` **before** the page renders, into a shared `useLangSwitchState()` (`{ available: boolean; fallback: string }`). This has to be a route middleware, not a `ref` set from inside the page component itself: a layout renders its header (where the switch lives) before the page content in document order, so by the time a page component's own `