Something went wrong. Try again.
Central platform for European atproto.<cc> country community websites
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101// Locale routing for the scope-driven builds.//// Locale pages used to exist as real files (src/pages/about.astro + src/pages/nl/about.astro).// That made `i18n.locales` decorative: a .eu build declaring English-only still emitted every// Dutch page, because Astro cannot skip a route that exists on disk. Only a DYNAMIC route can// opt out, which is what this enables — one `src/pages/[...lang]/about.astro` whose// getStaticPaths asks the scope which locales to build.//// The rest parameter is what keeps the default locale at the root: `lang: undefined` produces// /about, `lang: 'nl'` produces /nl/about. That matches `prefixDefaultLocale: false`.
import { getScope } from '../config/scopes';
const scope = getScope(import.meta.env.PUBLIC_SITE_SCOPE);
export const LOCALES = scope.locales;export const DEFAULT_LOCALE = scope.locales[0];
export type Lang = string;
/** getStaticPaths() for every localized page. */export function localePaths() { return LOCALES.map((locale, i) => ({ // The default locale takes the bare path; the rest are prefixed. params: { lang: i === 0 ? undefined : locale }, props: { lang: locale }, }));}
/** Resolve the locale for a request, tolerating a missing rest param. */export function langOf(params: { lang?: string }): Lang { return params.lang ?? DEFAULT_LOCALE;}
/** Pick a value for the current locale, falling back to the default locale's value. * * A scope can declare a locale before its copy exists (.be declares fr today). Falling back is * better than an empty <title>, but it is a stopgap: untranslated pages should be visible, so * missing keys warn at build time rather than passing silently. */export function t<T>(table: Record<string, T>, lang: Lang, what = 'value'): T { if (lang in table) return table[lang]; console.warn(`[i18n] missing ${what} for locale "${lang}"; falling back to "${DEFAULT_LOCALE}"`); return table[DEFAULT_LOCALE];}
/** Endonyms for the language switcher. Codes are ambiguous once a third locale exists — on * atproto.be "NL" is both a language and a country — so the header speaks language names and * the footer speaks domains, and the two share no vocabulary. */export const ENDONYM: Record<string, string> = { en: 'English', nl: 'Nederlands', fr: 'Français', de: 'Deutsch', hu: 'Magyar', pl: 'Polski', es: 'Español', pt: 'Português', ca: 'Català', cs: 'Čeština',};
/** BCP-47-ish locale for og:locale. Region is the scope's country where we have one. */export const OG_LOCALE: Record<string, string> = { en: 'en_GB', nl: 'nl_NL', fr: 'fr_BE', de: 'de_BE', hu: 'hu_HU', pl: 'pl_PL', es: 'es_ES', pt: 'pt_PT', ca: 'ca_ES', cs: 'cs_CZ',};
/** Path for `path` in `lang`. The default locale lives at the root, others under /<lang>/. */export function localeHref(lang: Lang, path: string): string { const p = path.startsWith('/') ? path : `/${path}`; return lang === DEFAULT_LOCALE ? p : `/${lang}${p === '/' ? '/' : p}`;}
/** Strip any known locale prefix, giving the path as the default locale would serve it. */export function stripLocale(path: string): string { for (const l of LOCALES) { if (l === DEFAULT_LOCALE) continue; if (path === `/${l}` || path === `/${l}/`) return '/'; if (path.startsWith(`/${l}/`)) return path.slice(l.length + 1); } return path;}
/** The same page in every locale this build serves — for the switcher and for hreflang. * Only locales this scope actually builds appear, so a build can never advertise a language * it does not serve. */export function localeAlternates(path: string): { lang: Lang; href: string; label: string }[] { const base = stripLocale(path); return [...LOCALES] .sort((a, b) => (ENDONYM[a] ?? a).localeCompare(ENDONYM[b] ?? b)) .map((l) => ({ lang: l, href: localeHref(l, base), label: ENDONYM[l] ?? l.toUpperCase() }));}