From cc09505eca2f431d23110a43114bdd61a5de68e7 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Fri, 5 Sep 2025 11:59:18 -0400 Subject: [PATCH] feat(i18n): add route manifest and use it for language toggle + hreflang; add alias redirects to avoid 404s on mismatched slugs --- public/_redirects | 7 +++ src/i18n/routes.ts | 71 +++++++++++++++++++++++++ src/layouts/BaseLayout.astro | 19 ++++--- src/pages/en/apartments/index.astro | 21 ++++++++ src/pages/en/apartments/t2-corail.astro | 24 +++++++++ src/pages/en/apartments/t3-azur.astro | 24 +++++++++ src/pages/en/contact.astro | 55 +++++++++++++++++++ src/pages/en/index.astro | 66 +++++++++++++++++++++++ src/pages/en/location-access.astro | 15 ++++++ src/pages/en/rates-availability.astro | 29 ++++++++++ src/pages/en/reviews.astro | 20 +++++++ src/pages/en/thank-you.astro | 15 ++++++ 12 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 src/i18n/routes.ts create mode 100644 src/pages/en/apartments/index.astro create mode 100644 src/pages/en/apartments/t2-corail.astro create mode 100644 src/pages/en/apartments/t3-azur.astro create mode 100644 src/pages/en/contact.astro create mode 100644 src/pages/en/index.astro create mode 100644 src/pages/en/location-access.astro create mode 100644 src/pages/en/rates-availability.astro create mode 100644 src/pages/en/reviews.astro create mode 100644 src/pages/en/thank-you.astro diff --git a/public/_redirects b/public/_redirects index a103200..f734844 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1 +1,8 @@ / /fr/ 301! +# Helpful aliases to avoid 404s from external links +/fr/reviews /fr/avis 301 +/en/avis /en/reviews 301 +/fr/location-access /fr/acces 301 +/en/acces /en/location-access 301 +/fr/rates-availability /fr/tarifs-disponibilites 301 +/en/tarifs-disponibilites /en/rates-availability 301 diff --git a/src/i18n/routes.ts b/src/i18n/routes.ts new file mode 100644 index 0000000..32001ad --- /dev/null +++ b/src/i18n/routes.ts @@ -0,0 +1,71 @@ +export type Locale = 'fr' | 'en'; + +export type RouteKey = + | 'home' + | 'apartments' + | 'apartment_t2' + | 'apartment_t3' + | 'reviews' + | 'location' + | 'rates' + | 'contact' + | 'thank_you'; + +export const routes: Record> = { + home: { fr: '/fr/', en: '/en/' }, + apartments: { fr: '/fr/appartements/', en: '/en/apartments/' }, + apartment_t2: { fr: '/fr/appartements/t2-corail/', en: '/en/apartments/t2-corail/' }, + apartment_t3: { fr: '/fr/appartements/t3-azur/', en: '/en/apartments/t3-azur/' }, + reviews: { fr: '/fr/avis/', en: '/en/reviews/' }, + location: { fr: '/fr/acces/', en: '/en/location-access/' }, + rates: { fr: '/fr/tarifs-disponibilites/', en: '/en/rates-availability/' }, + contact: { fr: '/fr/contact/', en: '/en/contact/' }, + thank_you: { fr: '/fr/merci/', en: '/en/thank-you/' }, +}; + +const normalize = (p: string) => { + if (!p) return '/'; + let out = p.startsWith('/') ? p : `/${p}`; + if (!out.endsWith('/')) out = `${out}/`; + return out; +}; + +export function keyForPath(pathname: string): RouteKey | null { + const n = normalize(pathname); + for (const key of Object.keys(routes) as RouteKey[]) { + const localizations = routes[key]; + if (n === localizations.fr || n === localizations.en) return key; + } + return null; +} + +export function siblingPath(pathname: string, locale: Locale): string { + const key = keyForPath(pathname); + if (key) return routes[key][locale]; + return routes.home[locale]; +} + +export function hrefFor(key: RouteKey, locale: Locale): string { + return routes[key][locale]; +} + +export function navFor(locale: Locale): Array<{ label: string; href: string }> { + return locale === 'en' + ? [ + { label: 'Apartments', href: routes.apartments.en }, + { label: 'Reviews', href: routes.reviews.en }, + { label: 'Location & Access', href: routes.location.en }, + { label: 'Rates', href: routes.rates.en }, + ] + : [ + { label: 'Appartements', href: routes.apartments.fr }, + { label: 'Avis', href: routes.reviews.fr }, + { label: 'Accès', href: routes.location.fr }, + { label: 'Tarifs', href: routes.rates.fr }, + ]; +} + +export function ctaLabelFor(locale: Locale): string { + return locale === 'en' ? 'Send a Request' : 'Envoyer une demande'; +} + diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 41d4a3a..6a9f569 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,11 +1,13 @@ --- import '../styles/global.css'; +import { siblingPath, hrefFor, navFor, ctaLabelFor } from '../i18n/routes'; const { title = 'VillaFleurie', lang = 'fr', description = 'Séjours confortables au Gosier pour couples et petites familles' } = Astro.props; const pathname = Astro.url.pathname; -const toFr = (p: string) => p.startsWith('/en/') ? `/fr/${p.slice(4)}` : (p.startsWith('/fr/') ? p : '/fr/'); -const toEn = (p: string) => p.startsWith('/fr/') ? `/en/${p.slice(4)}` : (p.startsWith('/en/') ? p : '/en/'); -const altFr = toFr(pathname); -const altEn = toEn(pathname); +const altFr = siblingPath(pathname, 'fr'); +const altEn = siblingPath(pathname, 'en'); +const nav = navFor(lang); +const ctaLabel = ctaLabelFor(lang); +const homeHref = hrefFor('home', lang); --- @@ -22,16 +24,13 @@ const altEn = toEn(pathname);