--- import { getScope, siteName, resolveScopeId, socialUrl, uiAccentPack, navFor } from '../config/scopes'; import { localeHref, localeAlternates, OG_LOCALE, DEFAULT_LOCALE, LOCALES } from '../i18n/paths'; import { existingCommunities } from '../data/communities'; import '@fontsource-variable/atkinson-hyperlegible-next'; import '@fontsource-variable/jetbrains-mono'; import '../styles/global.css'; import '../styles/country-packs.css'; import Wordmark from '../components/Wordmark.astro'; import ThemeToggle from '../components/ThemeToggle.astro'; import Auth from '../components/Auth.astro'; import LikeActions from '../components/LikeActions.astro'; import RepostActions from '../components/RepostActions.astro'; import FollowActions from '../components/FollowActions.astro'; import SubscribeActions from '../components/SubscribeActions.astro'; import RsvpActions from '../components/RsvpActions.astro'; import MembershipActions from '../components/MembershipActions.astro'; import { useTranslations, type Lang } from '../i18n/ui'; interface Props { title?: string; lang?: Lang; /** Per-page meta/OG description. Falls back to the site tagline. */ description?: string; /** Keep the page out of search engines (still reachable by URL). For share-only pages. */ noindex?: boolean; /** Client-side language auto-routing sends a default-locale page to the visitor's preferred * built locale. Turn OFF on pages that exist only in the default locale (event guides, 404, * offline, go-eu): they have no localized counterpart, so redirecting a non-English visitor * lands on a 404, and the 404 page re-running the script is what stacked /cs/cs/cs forever. */ autoRoute?: boolean; } const { title, lang = 'en', description: pageDescription, noindex = false, autoRoute = true, } = Astro.props; const t = useTranslations(lang); // Every in-site link goes through localeHref: a two-way `lang === 'en' ? … : '/nl/…'` sent // every non-English visitor to Dutch, which made /fr/ a dead end on .be and put a Dutch // switcher on .eu, a build with no Dutch at all. const home = localeHref(lang, '/'); const membersHref = localeHref(lang, '/members'); const eventsHref = localeHref(lang, '/events'); const toolsHref = localeHref(lang, '/tools'); // Stats destination differs by scope: every national site has its own /community-size page, the // .eu aggregate has the /stats directory. Always present now (each scope has one). const scopeIdForStats = resolveScopeId(import.meta.env.PUBLIC_SITE_SCOPE); const scopeForNav = getScope(scopeIdForStats); const statsHref = scopeForNav.aggregate ? localeHref(lang, '/stats') : localeHref(lang, '/community-size'); const buildersHref = localeHref(lang, '/builders'); // Only verifier scopes link /verify; a non-verifier build (e.g. .hu) has no such page. const verifyHref = scopeForNav.verify ? localeHref(lang, '/verify') : null; const aboutHref = localeHref(lang, '/about'); const privacyHref = localeHref(lang, '/privacy'); const termsHref = localeHref(lang, '/terms'); // This build's own community account (Say hi / footer Follow + mu.social link), per scope — never // hardcode .nl's, or every national site would send people to the Dutch account. const followUrl = socialUrl(scopeForNav); const followDid = scopeForNav.social.did; const site = Astro.site?.toString().replace(/\/$/, '') ?? 'https://atproto.nl'; // Per-scope social card, so a shared .be/.eu/.no link does not show the Dutch OG image. const ogImage = `${site}/brand/${scopeIdForStats === 'nl' ? 'og.png' : `og-${scopeIdForStats}.png`}`; // Host of this build's own domain, to mark the current site in the communities list (never hide // it: a three-item list with one marked reads as a map; removing the current one reads as a toggle). const siteHost = (() => { try { return new URL(site).host; } catch { return ''; } })(); // The footer lists national communities only. City communities are a separate tier: a city is // never a country and never stands in for one, so they deliberately do not appear in the footer // or the per-country statistics. const nationalCommunities = existingCommunities.filter((c) => c.scope === 'national'); const isCurrentCommunity = (url: string) => { try { return new URL(url).host === siteHost; } catch { return false; } }; // Language switch keeps you on the same page in the other language, instead of // always going home. Guides live in English only, so their NL link falls back to /nl/events/. const path = Astro.url.pathname; // Built from the scope, so a build can never advertise a language it does not serve. let alternates = localeAlternates(path); // Event guides are English-only: point other locales at the events index rather than a 404. if (/^\/(?:[a-z]{2}\/)?events\/[^/]+\/?$/.test(path)) { alternates = alternates.map((a) => a.lang === DEFAULT_LOCALE ? a : { ...a, href: localeHref(a.lang, '/events/') }, ); } const selfHref = alternates.find((a) => a.lang === lang)?.href ?? path; const defaultHref = alternates.find((a) => a.lang === DEFAULT_LOCALE)?.href ?? '/'; const canonical = `${site}${path}`; // The site name comes from the scope, not the catalogue: it does not change per language, and // the catalogue's copy said "atproto.nl" on every build. const scope = getScope(import.meta.env.PUBLIC_SITE_SCOPE); // Per-country header menu visibility (defaults all shown); `about` is structural, `verify` is // governed separately by `scope.verify`. See scopes.ts `nav` / navFor(). const nav = navFor(scope); const SITE_NAME = siteName(scope); const pageTitle = title ? `${title} · ${SITE_NAME}` : SITE_NAME; const description = pageDescription ?? t('site.tagline'); const orgLd = { '@context': 'https://schema.org', '@type': 'Organization', name: SITE_NAME, url: `${site}/`, logo: `${site}/brand/avatar-${scope.cc}-512.png`, description: t('site.tagline'), sameAs: [followUrl, 'https://tangled.org/atcommons.eu/website'], }; ---
{LOCALES.length > 1 && autoRoute && ( )}