Compare commits
2
Commits
1247a2ca07
...
v2.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd920627fa | ||
|
|
ac536bd152 |
@@ -0,0 +1,98 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.1.0] - 2026-09-11
|
||||
|
||||
### Added
|
||||
|
||||
- English version of the site (`/en/*`) alongside the Italian original, hand-rolled without an
|
||||
i18n library: `/en`, `/en/blog`, `/en/blog/[slug]`, `/en/category/[slug]` and the corralito page
|
||||
all get an English counterpart, sharing markup with their Italian page via a `locale` prop.
|
||||
- Manual, per-record English fields on content: `titleEn`/`contentEn`/`coverAltEn` on articles,
|
||||
`nameEn` on categories — translated by the editor in the admin UI, no auto-translation.
|
||||
- English translation of the corralito training-courses page specifically (it was left Italian-only
|
||||
when `/en` support first landed, then translated in a follow-up pass).
|
||||
- Numbered pagination on the blog archive and category pages (first, last, current page and its
|
||||
neighbors, collapsing gaps into `…`), replacing plain previous/next links.
|
||||
- `claude-md-management` and `ui-ux-pro-max` Claude Code plugins, enabled repo-wide via
|
||||
`.claude/settings.json`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Redesigned the language switch: both languages are shown at once ("Italiano / English"), the
|
||||
current one as plain non-interactive text, moved to the header's top-right corner instead of
|
||||
stacked under the logo.
|
||||
|
||||
### Fixed
|
||||
|
||||
- An article/category with no English translation now 404s cleanly on its own `/en` page and is
|
||||
filtered out of `/en` listings, instead of silently showing Italian text on an English URL.
|
||||
- Pagination links no longer report every page as "current" to assistive technology — caused by
|
||||
`NuxtLink`/`RouterLink`'s built-in active-state detection comparing only the route path, not its
|
||||
query string.
|
||||
|
||||
## [2.0.0] - 2026-09-11
|
||||
|
||||
### Added
|
||||
|
||||
- `docs/` — architecture, content model, and frontend reference documentation.
|
||||
- ESLint, via the official `@nuxt/eslint` module.
|
||||
- Full test suite: unit (Vitest), integration (a real Nitro server against an ephemeral
|
||||
PocketBase), and end-to-end (Playwright) — under `tests/` at the repo root.
|
||||
|
||||
### Changed
|
||||
|
||||
- Lightened the (still Strapi-based, at the time) CMS: dropped the unused cloud plugin, disabled
|
||||
telemetry and promotional UI — a stopgap before the PocketBase migration below.
|
||||
- Replaced the Strapi + PostgreSQL CMS with PocketBase: a single binary with embedded SQLite and
|
||||
per-collection API rules. Frontend endpoints moved to `/content/*`; `/api/*` is now reserved for
|
||||
PocketBase's own REST/file API.
|
||||
- Local development now bind-mounts PocketBase's data directory (`pocketbase/pb_data/`) instead of
|
||||
using a named Docker volume, so test data can be inspected or swapped directly on the host.
|
||||
- `articles.content` now has an explicit 10000-character cap, instead of relying on an undocumented
|
||||
PocketBase default that silently rejected longer articles.
|
||||
|
||||
## [1.0.0] - 2026-08-28
|
||||
|
||||
Initial release.
|
||||
|
||||
### Added
|
||||
|
||||
- Strapi CMS with `Article` and `Category` content types.
|
||||
- Nuxt frontend: blog index, article detail, and category pages.
|
||||
- Docker Compose stack: PostgreSQL, Caddy reverse proxy, and the app services.
|
||||
- Project documentation, license, and `.gitignore`; docs for the stack, dev workflow, production
|
||||
deployment, and generating `.env` secrets.
|
||||
- Editorial-magazine restyle of the frontend, with the CorralitoComing seal as site logo and
|
||||
favicon (every size).
|
||||
- Contact address and official social-brand links in the footer.
|
||||
- Author name and publication time shown on articles.
|
||||
- Responsive layout for phone, tablet, and desktop.
|
||||
- Landing page content and a dedicated courses page.
|
||||
- A language switcher supporting English, Italian, Spanish, and French (later removed — see
|
||||
Removed, below).
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed the site to CorralitoComing and switched the interface to English (later reverted to
|
||||
Italian-only, see Removed).
|
||||
- Simplified the home page and navigation down to a single "read the latest article" link.
|
||||
- The CMS admin panel is served under `/admin` on the public domain; every Strapi plugin path is
|
||||
routed there, not just `/admin`.
|
||||
|
||||
### Removed
|
||||
|
||||
- The English/Italian/Spanish/French language switcher — reverted to an Italian-only interface,
|
||||
relying on the visitor's browser to translate the page instead.
|
||||
|
||||
### Fixed
|
||||
|
||||
- ACME DNS resolution failures in the Caddy container.
|
||||
|
||||
### Security
|
||||
|
||||
- `ops/` (holds VPS credentials) added to `.gitignore` — never committed.
|
||||
+5
-4
@@ -8,7 +8,7 @@
|
||||
| `/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` | *(none)* | `app/pages/come-difendersi-dal-corralito.vue` | Fully static marketing page, no PocketBase data, Italian only — see [English content](#english-content-1). |
|
||||
| `/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.
|
||||
|
||||
@@ -47,8 +47,9 @@ Nitro locale-awareness, and most of the SEO work are custom code either way).
|
||||
by the time a page component's own `<script setup>` would set that state, the header has already
|
||||
resolved and rendered with whatever the default was — middleware runs, and fully resolves,
|
||||
before the layout/page tree starts rendering at all, which is the only way to avoid that race.
|
||||
When unavailable, the switch falls back to the other language's blog index (articles/categories)
|
||||
or home page (the static corralito page, which has no English version at all).
|
||||
When unavailable, the switch falls back to the other language's blog index/category list; every
|
||||
other route (including the static corralito page) exists in both languages unconditionally, so
|
||||
the mechanical `/en` prefix swap is always valid there.
|
||||
- **SEO**: `app/composables/useSeo.ts` centralises canonical URL, Open Graph, JSON-LD, and
|
||||
`hreflang` alternate links (`it`, `en`, `x-default`) for a given `{ locale, path, ... }` — `path`
|
||||
is always the bare, unprefixed path, so IT/EN pages for the same content always agree on each
|
||||
@@ -166,7 +167,7 @@ layout shift results.
|
||||
render its own `<a>` — `RouterLink`'s built-in active-class/`aria-current` detection compares
|
||||
only the route's `path`, not its query string, so with plain `NuxtLink`s every `?page=N` link
|
||||
would end up (wrongly) marked as the current page.
|
||||
- `app/components/views/*.vue` (`HomeView`, `BlogIndexView`, `ArticleView`, `CategoryView`) — the
|
||||
- `app/components/views/*.vue` (`HomeView`, `BlogIndexView`, `ArticleView`, `CategoryView`, `CorralitoView`) — the
|
||||
actual page markup/logic, parametrized by a `locale` prop; the real `pages/**` and `pages/en/**`
|
||||
files are thin wrappers around these (see [English content](#english-content)). Registered
|
||||
without Nuxt's default nested-directory name prefix
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import type { Locale } from '#shared/utils/locale'
|
||||
import { STRINGS } from '#shared/utils/i18n'
|
||||
|
||||
const props = defineProps<{ locale: Locale }>()
|
||||
|
||||
const strings = computed(() => STRINGS[props.locale])
|
||||
const { public: config } = useRuntimeConfig()
|
||||
|
||||
useSeo({
|
||||
locale: props.locale,
|
||||
path: '/come-difendersi-dal-corralito',
|
||||
title: strings.value.corralito.metaTitle,
|
||||
description: strings.value.corralito.metaDescription,
|
||||
image: `${config.siteUrl}/protesta-bancaria.jpg`,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<header class="article-header">
|
||||
<h1>{{ strings.navCorralito }}</h1>
|
||||
</header>
|
||||
|
||||
<img
|
||||
class="cover"
|
||||
src="/protesta-bancaria.jpg"
|
||||
:alt="strings.corralito.coverAlt"
|
||||
width="770"
|
||||
height="420"
|
||||
>
|
||||
|
||||
<div class="prose">
|
||||
<p>{{ strings.corralito.p1 }}</p>
|
||||
<p>{{ strings.corralito.p2 }}</p>
|
||||
<p>{{ strings.corralito.topicsIntro }}</p>
|
||||
|
||||
<ul>
|
||||
<li v-for="topic in strings.corralito.topics" :key="topic">{{ topic }}</li>
|
||||
</ul>
|
||||
|
||||
<p>{{ strings.corralito.disclaimer }}</p>
|
||||
</div>
|
||||
|
||||
<div class="contact-cta">
|
||||
<p class="kicker">{{ strings.corralito.contactKicker }}</p>
|
||||
<p class="contact-links">
|
||||
<a :href="`mailto:${SITE_EMAIL}`">{{ strings.corralito.emailLink }}</a>
|
||||
<span aria-hidden="true">·</span>
|
||||
<a href="https://t.me/Ito1505" target="_blank" rel="noopener">{{ strings.corralito.telegramLink }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.article-header {
|
||||
max-width: 46rem;
|
||||
margin: 0 auto 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.article-header {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
margin-block: 0 3rem;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.prose {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.contact-cta {
|
||||
max-width: var(--width-prose);
|
||||
margin: 3rem auto 0;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact-links {
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.contact-links a {
|
||||
padding-bottom: 0.35rem;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--color-ink);
|
||||
}
|
||||
|
||||
.contact-links a:hover {
|
||||
color: var(--color-ink);
|
||||
}
|
||||
</style>
|
||||
@@ -27,8 +27,8 @@ const strings = computed(() => STRINGS[locale.value])
|
||||
<ul>
|
||||
<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>
|
||||
<NuxtLink :to="localePath(locale, '/come-difendersi-dal-corralito')">{{ strings.navCorralito }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -55,8 +55,8 @@ const strings = computed(() => STRINGS[locale.value])
|
||||
<ul>
|
||||
<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>
|
||||
<NuxtLink :to="localePath(locale, '/come-difendersi-dal-corralito')">{{ strings.navCorralito }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -38,10 +38,5 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (bare === '/come-difendersi-dal-corralito') {
|
||||
langSwitch.value = { available: false, fallback: '/en' }
|
||||
return
|
||||
}
|
||||
|
||||
langSwitch.value = { available: true, fallback: '/en' }
|
||||
})
|
||||
|
||||
@@ -1,132 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
const { public: config } = useRuntimeConfig()
|
||||
const route = useRoute()
|
||||
const canonical = computed(() => `${config.siteUrl}${route.path}`)
|
||||
|
||||
const title = 'Corsi di formazione sulle criptovalute'
|
||||
const description = 'Corsi pratici, individuali o di gruppo, per capire come funzionano le criptovalute e difendersi dal corralito.'
|
||||
|
||||
useSeoMeta({
|
||||
title,
|
||||
description,
|
||||
ogTitle: title,
|
||||
ogDescription: description,
|
||||
ogType: 'website',
|
||||
ogUrl: canonical,
|
||||
ogImage: `${config.siteUrl}/protesta-bancaria.jpg`,
|
||||
})
|
||||
|
||||
useHead({ link: [{ rel: 'canonical', href: canonical }] })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<header class="article-header">
|
||||
<h1>Come difendersi dal corralito</h1>
|
||||
</header>
|
||||
|
||||
<img
|
||||
class="cover"
|
||||
src="/protesta-bancaria.jpg"
|
||||
alt="Manifesti e scritte contro le banche durante una crisi bancaria"
|
||||
width="770"
|
||||
height="420"
|
||||
>
|
||||
|
||||
<div class="prose">
|
||||
<p>
|
||||
Stai pensando di avvicinarti al mondo delle criptovalute (un mercato parallelo al
|
||||
mercato tradizionale finanziario), ma non sai da dove cominciare?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
È possibile organizzare corsi individuali o di gruppo dedicati alla conoscenza e al
|
||||
funzionamento delle criptovalute, con un approccio prevalentemente pratico e senza
|
||||
fornire consigli o raccomandazioni finanziarie.
|
||||
</p>
|
||||
|
||||
<p>Durante il corso possiamo affrontare, tra gli altri, questi argomenti:</p>
|
||||
|
||||
<ul>
|
||||
<li>quali sono i principali tipi di criptovalute e quali differenze esistono tra loro;</li>
|
||||
<li>come funziona una blockchain e cosa significa realmente effettuare una transazione;</li>
|
||||
<li>dove e come vengono conservate le criptovalute: wallet, exchange e sistemi di custodia;</li>
|
||||
<li>come acquistare e vendere criptovalute e quali sono le principali modalità disponibili;</li>
|
||||
<li>come utilizzare un wallet e gestire correttamente le proprie chiavi;</li>
|
||||
<li>quali sono i principali rischi legati alla sicurezza e alla custodia;</li>
|
||||
<li>come orientarsi nel vastissimo universo delle criptovalute e dei token.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Non si tratta di consulenza finanziaria e non verranno fornite indicazioni su quali
|
||||
criptovalute acquistare o vendere. L'obiettivo è fornire conoscenze e strumenti per
|
||||
comprendere questo nuovo mondo e muoversi al suo interno con maggiore consapevolezza.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="contact-cta">
|
||||
<p class="kicker">Per informazioni sui corsi, modalità e disponibilità</p>
|
||||
<p class="contact-links">
|
||||
<a :href="`mailto:${SITE_EMAIL}`">Scrivimi via email</a>
|
||||
<span aria-hidden="true">·</span>
|
||||
<a href="https://t.me/Ito1505" target="_blank" rel="noopener">Scrivimi su Telegram</a>
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
<CorralitoView locale="it" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.article-header {
|
||||
max-width: 46rem;
|
||||
margin: 0 auto 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.article-header {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
margin-block: 0 3rem;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.prose {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.contact-cta {
|
||||
max-width: var(--width-prose);
|
||||
margin: 3rem auto 0;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact-links {
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.contact-links a {
|
||||
padding-bottom: 0.35rem;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--color-ink);
|
||||
}
|
||||
|
||||
.contact-links a:hover {
|
||||
color: var(--color-ink);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<CorralitoView locale="en" />
|
||||
</template>
|
||||
@@ -62,6 +62,27 @@ export const STRINGS = {
|
||||
byAuthor: 'Di {author}',
|
||||
backToArticles: 'Torna a tutti gli articoli',
|
||||
},
|
||||
corralito: {
|
||||
metaTitle: 'Corsi di formazione sulle criptovalute',
|
||||
metaDescription: 'Corsi pratici, individuali o di gruppo, per capire come funzionano le criptovalute e difendersi dal corralito.',
|
||||
coverAlt: 'Manifesti e scritte contro le banche durante una crisi bancaria',
|
||||
p1: 'Stai pensando di avvicinarti al mondo delle criptovalute (un mercato parallelo al mercato tradizionale finanziario), ma non sai da dove cominciare?',
|
||||
p2: "È possibile organizzare corsi individuali o di gruppo dedicati alla conoscenza e al funzionamento delle criptovalute, con un approccio prevalentemente pratico e senza fornire consigli o raccomandazioni finanziarie.",
|
||||
topicsIntro: 'Durante il corso possiamo affrontare, tra gli altri, questi argomenti:',
|
||||
topics: [
|
||||
'quali sono i principali tipi di criptovalute e quali differenze esistono tra loro;',
|
||||
'come funziona una blockchain e cosa significa realmente effettuare una transazione;',
|
||||
'dove e come vengono conservate le criptovalute: wallet, exchange e sistemi di custodia;',
|
||||
'come acquistare e vendere criptovalute e quali sono le principali modalità disponibili;',
|
||||
'come utilizzare un wallet e gestire correttamente le proprie chiavi;',
|
||||
'quali sono i principali rischi legati alla sicurezza e alla custodia;',
|
||||
'come orientarsi nel vastissimo universo delle criptovalute e dei token.',
|
||||
],
|
||||
disclaimer: "Non si tratta di consulenza finanziaria e non verranno fornite indicazioni su quali criptovalute acquistare o vendere. L'obiettivo è fornire conoscenze e strumenti per comprendere questo nuovo mondo e muoversi al suo interno con maggiore consapevolezza.",
|
||||
contactKicker: 'Per informazioni sui corsi, modalità e disponibilità',
|
||||
emailLink: 'Scrivimi via email',
|
||||
telegramLink: 'Scrivimi su Telegram',
|
||||
},
|
||||
},
|
||||
en: {
|
||||
skipLink: 'Skip to content',
|
||||
@@ -69,7 +90,7 @@ export const STRINGS = {
|
||||
navAriaLabel: 'Main navigation',
|
||||
navHome: 'Home',
|
||||
navBlog: 'All articles',
|
||||
navCorralito: '',
|
||||
navCorralito: 'How to protect yourself from the corralito',
|
||||
footerNavAriaLabel: 'Navigate',
|
||||
footerNavigaKicker: 'Navigate',
|
||||
footerSeguiciKicker: 'Follow us',
|
||||
@@ -118,6 +139,27 @@ export const STRINGS = {
|
||||
byAuthor: 'By {author}',
|
||||
backToArticles: 'Back to all articles',
|
||||
},
|
||||
corralito: {
|
||||
metaTitle: 'Cryptocurrency training courses',
|
||||
metaDescription: 'Practical, individual or group courses to understand how cryptocurrencies work and protect yourself from the corralito.',
|
||||
coverAlt: 'Banners and graffiti against banks during a banking crisis',
|
||||
p1: "Are you thinking about getting into the world of cryptocurrencies (a market parallel to the traditional financial market), but don't know where to start?",
|
||||
p2: 'I can organize individual or group courses dedicated to understanding how cryptocurrencies work, with a mostly hands-on approach and without giving financial advice or recommendations.',
|
||||
topicsIntro: 'During the course we can cover, among others, these topics:',
|
||||
topics: [
|
||||
'the main types of cryptocurrencies and the differences between them;',
|
||||
'how a blockchain works and what actually happens when you make a transaction;',
|
||||
'where and how cryptocurrencies are stored: wallets, exchanges and custody systems;',
|
||||
'how to buy and sell cryptocurrencies and the main methods available;',
|
||||
'how to use a wallet and correctly manage your own keys;',
|
||||
'the main risks related to security and custody;',
|
||||
'how to find your way around the vast universe of cryptocurrencies and tokens.',
|
||||
],
|
||||
disclaimer: 'This is not financial advice, and no indications will be given on which cryptocurrencies to buy or sell. The goal is to provide knowledge and tools to understand this new world and navigate it with greater awareness.',
|
||||
contactKicker: 'For information on courses, format and availability',
|
||||
emailLink: 'Email me',
|
||||
telegramLink: 'Message me on Telegram',
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<Locale, unknown>
|
||||
|
||||
|
||||
@@ -41,11 +41,14 @@ test('language switch falls back to the English blog index for an untranslated a
|
||||
await expect(languageSwitch).toHaveAttribute('href', '/en/blog')
|
||||
})
|
||||
|
||||
test('language switch on the Italian-only corralito page falls back to the English home page', async ({
|
||||
page,
|
||||
}) => {
|
||||
test('the corralito page has an English translation', async ({ page }) => {
|
||||
await page.goto('/come-difendersi-dal-corralito')
|
||||
|
||||
const languageSwitch = page.getByRole('link', { name: 'English' })
|
||||
await expect(languageSwitch).toHaveAttribute('href', '/en')
|
||||
await expect(languageSwitch).toHaveAttribute('href', '/en/come-difendersi-dal-corralito')
|
||||
|
||||
await page.goto('/en/come-difendersi-dal-corralito')
|
||||
await expect(
|
||||
page.getByRole('heading', { level: 1, name: 'How to protect yourself from the corralito' })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user