Aggiunge menu lingua dropdown e base i18n IT/EN/TH.
Introduce routing multilingua con locale tailandese, selettore lingua a tendina con icone bandiera SVG e gestione chiusura click-outside/Esc, oltre a integrazione Turnstile lato form contatti. Made-with: Cursor
This commit is contained in:
@ -37,4 +37,11 @@ export default defineConfig({
|
||||
|
||||
adapter: cloudflare(),
|
||||
integrations: [alpinejs()],
|
||||
i18n: {
|
||||
defaultLocale: 'it',
|
||||
locales: ['it', 'en', 'th'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
6
public/images/flags/en.svg
Normal file
6
public/images/flags/en.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||
<rect width="48" height="32" fill="#012169" />
|
||||
<path d="M0 0l48 32M48 0L0 32" stroke="#fff" stroke-width="6" />
|
||||
<path d="M24 0v32M0 16h48" stroke="#fff" stroke-width="10" />
|
||||
<path d="M24 0v32M0 16h48" stroke="#c8102e" stroke-width="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
5
public/images/flags/it.svg
Normal file
5
public/images/flags/it.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||
<rect width="16" height="32" x="0" y="0" fill="#009246" />
|
||||
<rect width="16" height="32" x="16" y="0" fill="#ffffff" />
|
||||
<rect width="16" height="32" x="32" y="0" fill="#ce2b37" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 272 B |
5
public/images/flags/th.svg
Normal file
5
public/images/flags/th.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32" aria-hidden="true">
|
||||
<rect width="48" height="32" x="0" y="0" fill="#a51931" />
|
||||
<rect width="48" height="24" x="0" y="4" fill="#ffffff" />
|
||||
<rect width="48" height="16" x="0" y="8" fill="#2d2a4a" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 270 B |
@ -5,6 +5,7 @@ const cf = cta.contactForm;
|
||||
const model = {
|
||||
actionUrl: cf.actionUrl,
|
||||
mailto: cf.mailto,
|
||||
turnstileSiteKey: cf.turnstileSiteKey,
|
||||
notifySubject: cf.notifySubject,
|
||||
successRedirect: cf.successRedirect,
|
||||
fieldName: cf.fieldName,
|
||||
@ -15,6 +16,8 @@ const model = {
|
||||
};
|
||||
const hasFormspree =
|
||||
typeof cf.actionUrl === 'string' && cf.actionUrl.length > 0;
|
||||
const hasTurnstile =
|
||||
typeof cf.turnstileSiteKey === 'string' && cf.turnstileSiteKey.length > 0;
|
||||
const nextUrl =
|
||||
typeof cf.successRedirect === 'string' && cf.successRedirect.length > 0
|
||||
? cf.successRedirect
|
||||
@ -178,6 +181,23 @@ const fieldClass =
|
||||
>
|
||||
</p>
|
||||
|
||||
{
|
||||
hasFormspree && hasTurnstile ? (
|
||||
<div class="pt-1">
|
||||
<div
|
||||
class="cf-turnstile"
|
||||
data-sitekey={cf.turnstileSiteKey}
|
||||
data-theme="dark"
|
||||
data-language="it"
|
||||
data-size="normal"
|
||||
></div>
|
||||
<p class="mt-1.5 text-xs leading-relaxed text-nx-muted">
|
||||
Verifica anti-spam: completa il controllo prima di inviare il messaggio.
|
||||
</p>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
<div class="pt-1">
|
||||
<button
|
||||
type="submit"
|
||||
@ -191,6 +211,16 @@ const fieldClass =
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
hasFormspree && hasTurnstile ? (
|
||||
<script
|
||||
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||
async
|
||||
defer
|
||||
></script>
|
||||
) : null
|
||||
}
|
||||
|
||||
<script>
|
||||
import '../scripts/nx-contact-form.ts';
|
||||
</script>
|
||||
|
||||
@ -17,6 +17,16 @@ const notifySubject =
|
||||
|
||||
const linkClass =
|
||||
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
||||
const path = Astro.url.pathname;
|
||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||
const withLocale = (href: string) => {
|
||||
if (currentLocale === 'it') return href;
|
||||
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||
return href;
|
||||
};
|
||||
---
|
||||
|
||||
<footer id="site-footer" class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
||||
@ -35,7 +45,7 @@ const linkClass =
|
||||
<ul class="space-y-2.5">
|
||||
{footer.homeNav.map((link) => (
|
||||
<li>
|
||||
<a class={linkClass} href={link.href}>
|
||||
<a class={linkClass} href={withLocale(link.href)}>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
@ -52,7 +62,7 @@ const linkClass =
|
||||
<ul class="space-y-2.5">
|
||||
{footer.legalColumnNav.map((link) => (
|
||||
<li>
|
||||
<a class={linkClass} href={link.href}>
|
||||
<a class={linkClass} href={withLocale(link.href)}>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
@ -101,7 +111,7 @@ const linkClass =
|
||||
{footer.companyNav.map((link) => (
|
||||
<li>
|
||||
{link.href ? (
|
||||
<a class={linkClass} href={link.href}>
|
||||
<a class={linkClass} href={withLocale(link.href)}>
|
||||
{link.label}
|
||||
</a>
|
||||
) : (
|
||||
|
||||
@ -30,6 +30,11 @@ export const cta = {
|
||||
actionUrl: '',
|
||||
/** Formspree: redirect dopo invio (`_next`) — URL **assoluta** in produzione */
|
||||
successRedirect: '',
|
||||
/**
|
||||
* Cloudflare Turnstile (anti-spam): inserisci qui la site key pubblica.
|
||||
* Se vuota, il CAPTCHA non viene mostrato.
|
||||
*/
|
||||
turnstileSiteKey: '',
|
||||
/** Formspree `_subject` e oggetto mailto */
|
||||
notifySubject: 'Richiesta contatto — sito NexStudio',
|
||||
/** Se `actionUrl` è vuoto: email per `mailto:` (es. `info@tuodominio.com`) */
|
||||
|
||||
@ -12,10 +12,13 @@ const {
|
||||
title = 'NexStudio — Software su misura',
|
||||
description = 'Studio di sviluppo software: web app, API, automazioni e prodotti digitali, con focus su qualità e time-to-market.',
|
||||
} = Astro.props;
|
||||
const pathname = Astro.url.pathname;
|
||||
const localeMatch = pathname.match(/^\/(en|th)(\/|$)/);
|
||||
const htmlLang = localeMatch?.[1] ?? 'it';
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="it" class="bg-nx-bg">
|
||||
<html lang={htmlLang} class="bg-nx-bg">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
@ -27,6 +27,26 @@ const printDate = new Intl.DateTimeFormat('it-IT', {
|
||||
dateStyle: 'long',
|
||||
}).format(new Date());
|
||||
const printSource = Astro.url.href;
|
||||
const path = Astro.url.pathname;
|
||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||
const strippedPath = path.replace(/^\/(en|th)(?=\/|$)/, '') || '/';
|
||||
const localeHref = (locale: 'it' | 'en' | 'th') => {
|
||||
if (locale === 'it') return strippedPath;
|
||||
return `/${locale}${strippedPath === '/' ? '' : strippedPath}`;
|
||||
};
|
||||
const localize = (href: string) => {
|
||||
if (currentLocale === 'it') return href;
|
||||
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||
return href;
|
||||
};
|
||||
const langMeta = {
|
||||
it: { code: 'IT', label: 'Italiano', flagSrc: '/images/flags/it.svg' },
|
||||
en: { code: 'EN', label: 'English', flagSrc: '/images/flags/en.svg' },
|
||||
th: { code: 'TH', label: 'ไทย', flagSrc: '/images/flags/th.svg' },
|
||||
} as const;
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description}>
|
||||
@ -43,14 +63,75 @@ const printSource = Astro.url.href;
|
||||
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
href={localize('/')}
|
||||
class="text-lg font-semibold tracking-tight text-nx-fg hover:text-white sm:text-xl"
|
||||
>NexStudio</a
|
||||
>
|
||||
<a
|
||||
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
||||
href="/#contatti">Contattaci</a
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<details class="relative" data-lang-dropdown>
|
||||
<summary
|
||||
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||
aria-label="Seleziona lingua"
|
||||
>
|
||||
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
<span>{langMeta[currentLocale].code}</span>
|
||||
<svg class="h-3.5 w-3.5 text-nx-muted" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<div
|
||||
class="absolute right-0 top-12 z-50 min-w-44 overflow-hidden rounded-lg border border-nx-border bg-nx-elevated/95 p-1 shadow-xl backdrop-blur"
|
||||
role="menu"
|
||||
>
|
||||
<a
|
||||
href={localeHref('it')}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'it' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'it' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.it.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.it.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.it.code}</span>
|
||||
</a>
|
||||
<a
|
||||
href={localeHref('en')}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'en' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'en' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.en.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.en.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.en.code}</span>
|
||||
</a>
|
||||
<a
|
||||
href={localeHref('th')}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'th' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'th' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.th.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.th.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.th.code}</span>
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
<a
|
||||
class="text-sm font-medium text-sky-400/90 hover:text-sky-300"
|
||||
href={localize('/#contatti')}>Contattaci</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="relative z-[1] flex-1 px-4 py-12 sm:px-6 sm:py-16">
|
||||
@ -86,3 +167,7 @@ const printSource = Astro.url.href;
|
||||
<SiteFooter />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
import '../scripts/nx-lang-dropdown.ts';
|
||||
</script>
|
||||
|
||||
5
src/pages/en/about.astro
Normal file
5
src/pages/en/about.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../about.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/codice-etico.astro
Normal file
5
src/pages/en/codice-etico.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../codice-etico.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/cookies.astro
Normal file
5
src/pages/en/cookies.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../cookies.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/dove-siamo.astro
Normal file
5
src/pages/en/dove-siamo.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../dove-siamo.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/gdpr.astro
Normal file
5
src/pages/en/gdpr.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../gdpr.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/index.astro
Normal file
5
src/pages/en/index.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../index.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/modello-organizzativo.astro
Normal file
5
src/pages/en/modello-organizzativo.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../modello-organizzativo.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/news.astro
Normal file
5
src/pages/en/news.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../news.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/newsletter/grazie.astro
Normal file
5
src/pages/en/newsletter/grazie.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../../newsletter/grazie.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/privacy.astro
Normal file
5
src/pages/en/privacy.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../privacy.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/en/terms.astro
Normal file
5
src/pages/en/terms.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../terms.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
@ -18,6 +18,24 @@ import {
|
||||
} from '../data/home';
|
||||
|
||||
const techSliderItems = stackSection.techSlider.items;
|
||||
const path = Astro.url.pathname;
|
||||
const localeMatch = path.match(/^\/(en|th)(\/|$)/);
|
||||
const currentLocale = localeMatch?.[1] ?? 'it';
|
||||
const localize = (href: string) => {
|
||||
if (currentLocale === 'it') return href;
|
||||
if (href.startsWith(`/${currentLocale}`)) return href;
|
||||
if (href.startsWith('/#')) return `/${currentLocale}${href}`;
|
||||
if (href.startsWith('/')) return `/${currentLocale}${href}`;
|
||||
return href;
|
||||
};
|
||||
const langItHref = '/';
|
||||
const langEnHref = '/en';
|
||||
const langThHref = '/th';
|
||||
const langMeta = {
|
||||
it: { code: 'IT', label: 'Italiano', flagSrc: '/images/flags/it.svg' },
|
||||
en: { code: 'EN', label: 'English', flagSrc: '/images/flags/en.svg' },
|
||||
th: { code: 'TH', label: 'ไทย', flagSrc: '/images/flags/th.svg' },
|
||||
} as const;
|
||||
---
|
||||
|
||||
<BaseLayout title={siteMeta.title} description={siteMeta.description}>
|
||||
@ -37,7 +55,7 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
<div
|
||||
class="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6"
|
||||
>
|
||||
<a href="/" class="group no-underline">
|
||||
<a href={localize('/')} class="group no-underline">
|
||||
<span
|
||||
class="text-2xl font-semibold tracking-tight text-nx-fg group-hover:text-white sm:text-3xl"
|
||||
>{navigation.brandName}</span
|
||||
@ -55,7 +73,7 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
return (
|
||||
<a
|
||||
class="nx-nav-link"
|
||||
href={item.href}
|
||||
href={localize(item.href)}
|
||||
data-section={sectionId}
|
||||
data-nav-active="false"
|
||||
>
|
||||
@ -66,6 +84,65 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2 sm:gap-3">
|
||||
<details class="relative hidden sm:block" data-lang-dropdown>
|
||||
<summary
|
||||
class="inline-flex h-10 cursor-pointer list-none items-center gap-2 rounded-lg border border-nx-border/80 bg-nx-bg/50 px-3 text-xs font-medium text-nx-fg transition-colors hover:border-sky-500/35 hover:text-white"
|
||||
aria-label="Seleziona lingua"
|
||||
>
|
||||
<img src={langMeta[currentLocale].flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
<span>{langMeta[currentLocale].code}</span>
|
||||
<svg class="h-3.5 w-3.5 text-nx-muted" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<div
|
||||
class="absolute right-0 top-12 z-50 min-w-44 overflow-hidden rounded-lg border border-nx-border bg-nx-elevated/95 p-1 shadow-xl backdrop-blur"
|
||||
role="menu"
|
||||
>
|
||||
<a
|
||||
href={langItHref}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'it' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'it' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.it.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.it.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.it.code}</span>
|
||||
</a>
|
||||
<a
|
||||
href={langEnHref}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'en' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'en' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.en.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.en.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.en.code}</span>
|
||||
</a>
|
||||
<a
|
||||
href={langThHref}
|
||||
class:list={[
|
||||
'flex items-center justify-between rounded-md px-3 py-2 text-sm text-nx-muted transition-colors hover:bg-white/5 hover:text-nx-fg',
|
||||
currentLocale === 'th' && 'bg-white/5 text-nx-fg',
|
||||
]}
|
||||
aria-current={currentLocale === 'th' ? 'page' : undefined}
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<img src={langMeta.th.flagSrc} alt="" class="h-3.5 w-5 rounded-[2px] object-cover" loading="lazy" decoding="async" />
|
||||
{langMeta.th.label}
|
||||
</span>
|
||||
<span class="text-xs">{langMeta.th.code}</span>
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
<button
|
||||
type="button"
|
||||
data-nx-chat-open
|
||||
@ -122,7 +199,7 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
return (
|
||||
<a
|
||||
class="nx-nav-link block py-1"
|
||||
href={item.href}
|
||||
href={localize(item.href)}
|
||||
data-section={sectionId}
|
||||
data-nav-active="false"
|
||||
@click="mobile = false"
|
||||
@ -663,5 +740,6 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
</div>
|
||||
<script>
|
||||
import '../scripts/nx-nav-spy.ts';
|
||||
import '../scripts/nx-lang-dropdown.ts';
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
||||
5
src/pages/th/about.astro
Normal file
5
src/pages/th/about.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../about.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/codice-etico.astro
Normal file
5
src/pages/th/codice-etico.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../codice-etico.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/cookies.astro
Normal file
5
src/pages/th/cookies.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../cookies.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/dove-siamo.astro
Normal file
5
src/pages/th/dove-siamo.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../dove-siamo.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/gdpr.astro
Normal file
5
src/pages/th/gdpr.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../gdpr.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/index.astro
Normal file
5
src/pages/th/index.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../index.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/modello-organizzativo.astro
Normal file
5
src/pages/th/modello-organizzativo.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../modello-organizzativo.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/news.astro
Normal file
5
src/pages/th/news.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../news.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/newsletter/grazie.astro
Normal file
5
src/pages/th/newsletter/grazie.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../../newsletter/grazie.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/privacy.astro
Normal file
5
src/pages/th/privacy.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../privacy.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
5
src/pages/th/terms.astro
Normal file
5
src/pages/th/terms.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import Page from '../terms.astro';
|
||||
---
|
||||
|
||||
<Page />
|
||||
@ -2,6 +2,7 @@
|
||||
interface ContactFormCfg {
|
||||
actionUrl: string;
|
||||
mailto: string;
|
||||
turnstileSiteKey: string;
|
||||
notifySubject: string;
|
||||
successRedirect: string;
|
||||
fieldName: string;
|
||||
@ -18,6 +19,7 @@ function readCfg(): ContactFormCfg {
|
||||
return {
|
||||
actionUrl: '',
|
||||
mailto: '',
|
||||
turnstileSiteKey: '',
|
||||
notifySubject: '',
|
||||
successRedirect: '',
|
||||
fieldName: 'name',
|
||||
@ -39,6 +41,23 @@ function bind(): void {
|
||||
const err = document.getElementById('nx-contact-form-error');
|
||||
if (err) err.classList.add('hidden');
|
||||
|
||||
const hasTurnstile =
|
||||
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
||||
if (cfg.actionUrl && hasTurnstile) {
|
||||
const token = form.querySelector<HTMLInputElement>(
|
||||
'input[name="cf-turnstile-response"]',
|
||||
)?.value;
|
||||
if (!token) {
|
||||
e.preventDefault();
|
||||
if (err) {
|
||||
err.textContent =
|
||||
'Completa il controllo anti-spam prima di inviare il messaggio.';
|
||||
err.classList.remove('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.actionUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
32
src/scripts/nx-lang-dropdown.ts
Normal file
32
src/scripts/nx-lang-dropdown.ts
Normal file
@ -0,0 +1,32 @@
|
||||
function bindLanguageDropdown(): void {
|
||||
const dropdowns = Array.from(
|
||||
document.querySelectorAll<HTMLDetailsElement>('details[data-lang-dropdown]'),
|
||||
);
|
||||
if (!dropdowns.length) return;
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target as Node | null;
|
||||
dropdowns.forEach((dd) => {
|
||||
if (!dd.open) return;
|
||||
if (target && dd.contains(target)) return;
|
||||
dd.open = false;
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Escape') return;
|
||||
dropdowns.forEach((dd) => {
|
||||
dd.open = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bindLanguageDropdown, {
|
||||
once: true,
|
||||
});
|
||||
} else {
|
||||
bindLanguageDropdown();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user