Serve the site in English, Spanish and Arabic, add a captcha, make the message optional

- Move every route under app/[lang]; a proxy keeps English on unprefixed URLs and serves /es and /ar
- Interface strings and homepage copy per locale; content pages load a translated JSON when one exists
- Language switch in the header, hreflang alternates and a multilingual sitemap
- Right-to-left layout for Arabic with logical CSS properties, Noto Sans Arabic and no letter-spacing
- Self-hosted proof-of-work "I'm not a robot" check (no third-party account) replaces Turnstile
- The message field is optional; failed attempts refill the form instead of clearing it
This commit is contained in:
Alejandro Veras
2026-10-13 10:49:14 -04:00
parent 2f9b63ccac
commit 5bf472b735
65 changed files with 2386 additions and 756 deletions
+32
View File
@@ -0,0 +1,32 @@
// Locale settings shared by the proxy, server components and client components.
export const locales = ["en", "es", "ar"] as const;
export type Locale = (typeof locales)[number];
/** English is served from the root (`/about`); the others carry a prefix (`/es/about`). */
export const defaultLocale: Locale = "en";
export const localeNames: Record<Locale, string> = { en: "English", es: "Español", ar: "العربية" };
export const localeShort: Record<Locale, string> = { en: "EN", es: "ES", ar: "ع" };
/** Open Graph locale tags. */
export const ogLocale: Record<Locale, string> = { en: "en_US", es: "es_ES", ar: "ar_AE" };
export const isLocale = (value: string | undefined): value is Locale =>
(locales as readonly string[]).includes(value ?? "");
export const dirOf = (locale: Locale) => (locale === "ar" ? "rtl" : "ltr");
/** Prefix an internal path with the locale. Hashes and external URLs pass through untouched. */
export function localize(path: string, locale: Locale): string {
if (locale === defaultLocale || !path.startsWith("/")) return path;
return path === "/" ? `/${locale}` : `/${locale}${path}`;
}
/** Split a browser pathname into its locale and the locale-free path. */
export function splitLocale(pathname: string): { locale: Locale; path: string } {
const first = pathname.split("/")[1];
if (isLocale(first)) {
return { locale: first, path: pathname.slice(first.length + 1) || "/" };
}
return { locale: defaultLocale, path: pathname };
}