mirror of
https://github.com/rjNemo/vf-site
synced 2026-06-06 09:16:39 +00:00
refactor(i18n): replace hrefFor usages with astro:i18n getRelativeLocaleUrl; update config and docs
This commit is contained in:
parent
2e9aa1dc31
commit
7613249b56
16 changed files with 110 additions and 114 deletions
|
|
@ -20,7 +20,7 @@
|
|||
- UI: Tailwind CSS v4 (brand tokens: `--color-brand`, `--color-brand-600`).
|
||||
- Icons: `lucide-astro` inline SVGs; use `text-brand` for accent.
|
||||
- JS: vanilla only (no jQuery). Prefer lightweight patterns (e.g., scrollBy carousels).
|
||||
- i18n: use `hrefFor()` and `siblingPath()` for all internal links and language toggles.
|
||||
- i18n: prefer `getRelativeLocaleUrl()` from `astro:i18n` for links; use `siblingPath()` for toggles when slugs differ.
|
||||
- Content slugs differ by locale (e.g., FR `avis` ↔ EN `reviews`) — never hardcode.
|
||||
|
||||
## Testing Guidelines
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
i18n: {
|
||||
locales: ["fr", "en"],
|
||||
defaultLocale: "fr",
|
||||
routing: {
|
||||
prefixDefaultLocale: true,
|
||||
redirectToDefaultLocale: true,
|
||||
},
|
||||
},
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
}
|
||||
});
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
1
dist/index.html
vendored
1
dist/index.html
vendored
|
|
@ -0,0 +1 @@
|
|||
<!doctype html><title>Redirecting to: /fr</title><meta http-equiv="refresh" content="0;url=/fr"><meta name="robots" content="noindex"><link rel="canonical" href="/fr"><body> <a href="/fr">Redirecting from <code>/</code> to <code>/fr</code></a></body>
|
||||
|
|
@ -1,78 +1,67 @@
|
|||
export type Locale = 'fr' | 'en';
|
||||
export type Locale = "fr" | "en";
|
||||
|
||||
export type RouteKey =
|
||||
| 'home'
|
||||
| 'apartments'
|
||||
| 'apartment_t2'
|
||||
| 'apartment_t3'
|
||||
| 'reviews'
|
||||
| 'location'
|
||||
| 'rates'
|
||||
| 'contact'
|
||||
| 'thank_you'
|
||||
| 'terms'
|
||||
| 'privacy'
|
||||
| 'cancellation'
|
||||
| 'house_rules';
|
||||
| "home"
|
||||
| "apartments"
|
||||
| "apartment_t2"
|
||||
| "apartment_t3"
|
||||
| "reviews"
|
||||
| "location"
|
||||
| "rates"
|
||||
| "contact"
|
||||
| "thank_you"
|
||||
| "terms"
|
||||
| "privacy"
|
||||
| "cancellation"
|
||||
| "house_rules";
|
||||
|
||||
export const routes: Record<RouteKey, Record<Locale, string>> = {
|
||||
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/' },
|
||||
terms: { fr: '/fr/politiques/conditions/', en: '/en/policies/terms/' },
|
||||
privacy: { fr: '/fr/politiques/confidentialite/', en: '/en/policies/privacy/' },
|
||||
cancellation: { fr: '/fr/politiques/annulation/', en: '/en/policies/cancellation/' },
|
||||
house_rules: { fr: '/fr/politiques/reglement/', en: '/en/policies/house-rules/' },
|
||||
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/" },
|
||||
terms: { fr: "/fr/politiques/conditions/", en: "/en/policies/terms/" },
|
||||
privacy: {
|
||||
fr: "/fr/politiques/confidentialite/",
|
||||
en: "/en/policies/privacy/",
|
||||
},
|
||||
cancellation: {
|
||||
fr: "/fr/politiques/annulation/",
|
||||
en: "/en/policies/cancellation/",
|
||||
},
|
||||
house_rules: {
|
||||
fr: "/fr/politiques/reglement/",
|
||||
en: "/en/policies/house-rules/",
|
||||
},
|
||||
};
|
||||
|
||||
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'
|
||||
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: "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 },
|
||||
{ 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';
|
||||
return locale === "en" ? "Send a Request" : "Envoyer une demande";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import { siblingPath, hrefFor, navFor, ctaLabelFor } from '../i18n/routes';
|
||||
import { navFor, ctaLabelFor } from '../i18n/routes';
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const { title = 'VillaFleurie', lang = 'fr', description = 'Séjours confortables au Gosier pour couples et petites familles' } = Astro.props;
|
||||
const pathname = Astro.url.pathname;
|
||||
const altFr = siblingPath(pathname, 'fr');
|
||||
const altEn = siblingPath(pathname, 'en');
|
||||
const altFr = "/fr/"
|
||||
const altEn = "/en/"
|
||||
const nav = navFor(lang);
|
||||
const ctaLabel = ctaLabelFor(lang);
|
||||
const homeHref = hrefFor('home', lang);
|
||||
const homeHref = getRelativeLocaleUrl(lang, '/');
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
|
|
@ -36,7 +36,7 @@ const homeHref = hrefFor('home', lang);
|
|||
</button>
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
{nav.map((n) => <a href={n.href} class="text-sm hover:underline">{n.label}</a>)}
|
||||
<a href={hrefFor('contact', lang)} class="inline-flex items-center rounded-lg bg-brand px-3 py-2 text-white hover:bg-brand-600">{ctaLabel}</a>
|
||||
<a href={getRelativeLocaleUrl(lang, '/contact/')} class="inline-flex items-center rounded-lg bg-brand px-3 py-2 text-white hover:bg-brand-600">{ctaLabel}</a>
|
||||
<div class="ml-2 flex items-center gap-2 text-xs">
|
||||
<a href={altFr} class={"hover:underline " + (lang==='fr' ? 'font-semibold text-brand-600' : '')}>FR</a>
|
||||
<span class="text-slate-400">|</span>
|
||||
|
|
@ -48,7 +48,7 @@ const homeHref = hrefFor('home', lang);
|
|||
<div id="mobile-menu" class="mt-3 hidden md:hidden border-t border-slate-200 pt-3">
|
||||
<div class="flex flex-col gap-3">
|
||||
{nav.map((n) => <a href={n.href} class="text-base">{n.label}</a>)}
|
||||
<a href={hrefFor('contact', lang)} class="inline-flex items-center justify-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">{ctaLabel}</a>
|
||||
<a href={getRelativeLocaleUrl(lang, '/contact/')} class="inline-flex items-center justify-center rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">{ctaLabel}</a>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-slate-500">Lang:</span>
|
||||
<a href={altFr} class={"hover:underline " + (lang==='fr' ? 'font-semibold text-brand-600' : '')}>FR</a>
|
||||
|
|
@ -71,17 +71,17 @@ const homeHref = hrefFor('home', lang);
|
|||
<ul class="flex gap-4">
|
||||
{lang === 'en' ? (
|
||||
<>
|
||||
<li><a class="underline" href={hrefFor('terms','en')}>Terms</a></li>
|
||||
<li><a class="underline" href={hrefFor('privacy','en')}>Privacy</a></li>
|
||||
<li><a class="underline" href={hrefFor('cancellation','en')}>Cancellation</a></li>
|
||||
<li><a class="underline" href={hrefFor('house_rules','en')}>House Rules</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('en','/policies/terms/')}>Terms</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('en','/policies/privacy/')}>Privacy</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('en','/policies/cancellation/')}>Cancellation</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('en','/policies/house-rules/')}>House Rules</a></li>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<li><a class="underline" href={hrefFor('terms','fr')}>Conditions</a></li>
|
||||
<li><a class="underline" href={hrefFor('privacy','fr')}>Confidentialité</a></li>
|
||||
<li><a class="underline" href={hrefFor('cancellation','fr')}>Annulation</a></li>
|
||||
<li><a class="underline" href={hrefFor('house_rules','fr')}>Règlement intérieur</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('fr','/politiques/conditions/')}>Conditions</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('fr','/politiques/confidentialite/')}>Confidentialité</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('fr','/politiques/annulation/')}>Annulation</a></li>
|
||||
<li><a class="underline" href={getRelativeLocaleUrl('fr','/politiques/reglement/')}>Règlement intérieur</a></li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const t2Href = hrefFor('apartment_t2', 'en');
|
||||
const t3Href = hrefFor('apartment_t3', 'en');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const t2Href = getRelativeLocaleUrl('en','/apartments/t2-corail/');
|
||||
const t3Href = getRelativeLocaleUrl('en','/apartments/t3-azur/');
|
||||
---
|
||||
<BaseLayout title="Apartments" lang="en">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const contactHref = hrefFor('contact', 'en');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const contactHref = getRelativeLocaleUrl('en','/contact/');
|
||||
---
|
||||
<BaseLayout title="T2 Corail" lang="en">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const contactHref = hrefFor('contact', 'en');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const contactHref = getRelativeLocaleUrl('en','/contact/');
|
||||
---
|
||||
<BaseLayout title="T3 Azur" lang="en">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../i18n/routes';
|
||||
const thankHref = hrefFor('thank_you', 'en');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const thankHref = getRelativeLocaleUrl('en', '/thank-you/');
|
||||
---
|
||||
<BaseLayout title="Contact" lang="en">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
@ -46,7 +46,7 @@ const thankHref = hrefFor('thank_you', 'en');
|
|||
</label>
|
||||
<label class="md:col-span-2 flex items-start gap-2 text-sm">
|
||||
<input type="checkbox" required name="consent_privacy" />
|
||||
<span>I agree to the use of my data to process my request in accordance with the <a class="underline" href={hrefFor('privacy','en')}>Privacy Policy</a>.</span>
|
||||
<span>I agree to the use of my data to process my request in accordance with the <a class="underline" href={getRelativeLocaleUrl('en','/policies/privacy/')}>Privacy Policy</a>.</span>
|
||||
</label>
|
||||
<div class="md:col-span-2">
|
||||
<button class="rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Send</button>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../i18n/routes';
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
import { Umbrella, Car, Wifi, Snowflake, Utensils, Home as HomeIcon, Users, Baby } from 'lucide-astro';
|
||||
const title = 'Home';
|
||||
const contactHref = hrefFor('contact', 'en');
|
||||
const t2Href = hrefFor('apartment_t2', 'en');
|
||||
const t3Href = hrefFor('apartment_t3', 'en');
|
||||
const contactHref = getRelativeLocaleUrl('en', '/contact/');
|
||||
const t2Href = getRelativeLocaleUrl('en','/apartments/t2-corail/');
|
||||
const t3Href = getRelativeLocaleUrl('en','/apartments/t3-azur/');
|
||||
---
|
||||
<BaseLayout title={title} lang="en" description="Comfortable stays in Le Gosier for couples and small families">
|
||||
<section class="relative h-[70vh] md:h-[80vh]">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const t2Href = hrefFor('apartment_t2', 'fr');
|
||||
const t3Href = hrefFor('apartment_t3', 'fr');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const t2Href = getRelativeLocaleUrl('fr','/appartements/t2-corail/');
|
||||
const t3Href = getRelativeLocaleUrl('fr','/appartements/t3-azur/');
|
||||
---
|
||||
<BaseLayout title="Appartements" lang="fr">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const contactHref = hrefFor('contact', 'fr');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const contactHref = getRelativeLocaleUrl('fr','/contact/');
|
||||
---
|
||||
<BaseLayout title="T2 Corail" lang="fr">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../../i18n/routes';
|
||||
const contactHref = hrefFor('contact', 'fr');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const contactHref = getRelativeLocaleUrl('fr','/contact/');
|
||||
---
|
||||
<BaseLayout title="T3 Azur" lang="fr">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../i18n/routes';
|
||||
const thankHref = hrefFor('thank_you', 'fr');
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
const thankHref = getRelativeLocaleUrl('fr', '/merci/');
|
||||
---
|
||||
<BaseLayout title="Contact" lang="fr">
|
||||
<div class="mx-auto max-w-6xl px-6 py-10">
|
||||
|
|
@ -46,7 +46,7 @@ const thankHref = hrefFor('thank_you', 'fr');
|
|||
</label>
|
||||
<label class="md:col-span-2 flex items-start gap-2 text-sm">
|
||||
<input type="checkbox" required name="consent_privacy" />
|
||||
<span>J’accepte que mes données soient utilisées pour traiter ma demande conformément à la <a class="underline" href={hrefFor('privacy','fr')}>Politique de Confidentialité</a>.</span>
|
||||
<span>J’accepte que mes données soient utilisées pour traiter ma demande conformément à la <a class="underline" href={getRelativeLocaleUrl('fr','/politiques/confidentialite/')}>Politique de Confidentialité</a>.</span>
|
||||
</label>
|
||||
<div class="md:col-span-2">
|
||||
<button class="rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand-600">Envoyer</button>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { hrefFor } from '../../i18n/routes';
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
import { Umbrella, Car, Wifi, Snowflake, Utensils, Home as HomeIcon, Users, Baby } from 'lucide-astro';
|
||||
const title = 'Accueil';
|
||||
const contactHref = hrefFor('contact', 'fr');
|
||||
const t2Href = hrefFor('apartment_t2', 'fr');
|
||||
const t3Href = hrefFor('apartment_t3', 'fr');
|
||||
const contactHref = getRelativeLocaleUrl('fr', '/contact/');
|
||||
const t2Href = getRelativeLocaleUrl('fr','/appartements/t2-corail/');
|
||||
const t3Href = getRelativeLocaleUrl('fr','/appartements/t3-azur/');
|
||||
---
|
||||
<BaseLayout title={title} lang="fr" description="Séjours confortables au Gosier pour couples et petites familles">
|
||||
<section class="relative h-[70vh] md:h-[80vh]">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
---
|
||||
// Redirect root to FR default
|
||||
Astro.redirect('/fr/');
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue