refactor: theme from lovable

This commit is contained in:
2026-05-01 13:45:28 -04:00
parent ace21f0467
commit 567d96cd07
316 changed files with 16572 additions and 162 deletions

View File

@@ -0,0 +1,46 @@
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
import { useLocation } from "react-router-dom";
import type { Locale, Localized } from "@/types/sections";
import { detectLocaleFromPath } from "./routes";
interface LocaleContextValue {
locale: Locale;
setLocale: (l: Locale) => void;
t: <T>(value: Localized<T>) => T;
}
const LocaleContext = createContext<LocaleContextValue | null>(null);
export function LocaleProvider({ children }: { children: ReactNode }) {
const location = useLocation();
const pathLocale = detectLocaleFromPath(location.pathname);
const [locale, setLocaleState] = useState<Locale>(pathLocale);
// Keep locale in sync with the URL (URL is the source of truth).
useEffect(() => {
if (pathLocale !== locale) setLocaleState(pathLocale);
}, [pathLocale, locale]);
// Keep <html lang> attribute in sync.
useEffect(() => {
document.documentElement.lang = locale === "fr" ? "fr-CA" : "en-CA";
}, [locale]);
const value = useMemo<LocaleContextValue>(
() => ({
locale,
setLocale: setLocaleState,
t: <T,>(v: Localized<T>) => v[locale],
}),
[locale],
);
return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
}
export function useLocale() {
const ctx = useContext(LocaleContext);
if (!ctx) throw new Error("useLocale must be used within <LocaleProvider>");
return ctx;
}

View File

@@ -0,0 +1,65 @@
import type { Locale } from "@/types/sections";
/**
* Page keys used internally. Each maps to FR and EN URL slugs.
* The FR slug is the canonical one (no language prefix).
* EN routes live under /en/<slug>.
*/
export type PageKey =
| "home"
| "municipality"
| "mayor"
| "council"
| "services"
| "documents"
| "community"
| "communityPhotos"
| "activitiesPhotos"
| "developmentPlan"
| "townMap"
| "businesses"
| "events"
| "publicNotices"
| "minutes"
| "newsletters";
export const ROUTES: Record<PageKey, { fr: string; en: string }> = {
home: { fr: "/", en: "/en" },
municipality: { fr: "/municipalite", en: "/en/municipality" },
mayor: { fr: "/mot-du-maire", en: "/en/word-from-the-mayor" },
council: { fr: "/conseil-municipal", en: "/en/our-council" },
services: { fr: "/services-municipaux-et-publics", en: "/en/our-services" },
documents: { fr: "/documents-importants", en: "/en/important-documents" },
community: { fr: "/notre-communaute", en: "/en/our-community" },
communityPhotos: { fr: "/album-photos-communaute", en: "/en/community-photo-album" },
activitiesPhotos: { fr: "/album-photos-activites", en: "/en/activities-photo-album" },
developmentPlan: { fr: "/plan-de-developpement", en: "/en/development-plan" },
townMap: { fr: "/carte-de-la-ville", en: "/en/town-map" },
businesses: { fr: "/entreprises-locales", en: "/en/local-businesses" },
events: { fr: "/evenements", en: "/en/events" },
publicNotices: { fr: "/avis-publics", en: "/en/public-notices" },
minutes: { fr: "/proces-verbaux", en: "/en/minutes" },
newsletters: { fr: "/bulletins", en: "/en/newsletters" },
};
export function getPath(page: PageKey, locale: Locale): string {
return ROUTES[page][locale];
}
/** Given the current pathname, return the equivalent path in the target locale. */
export function switchLocalePath(pathname: string, target: Locale): string {
const normalized = pathname.replace(/\/+$/, "") || "/";
for (const key of Object.keys(ROUTES) as PageKey[]) {
const fr = ROUTES[key].fr;
const en = ROUTES[key].en;
if (normalized === fr || normalized === en) {
return ROUTES[key][target];
}
}
return target === "en" ? "/en" : "/";
}
/** Detect the locale implied by a pathname (anything under /en/* or /en is EN). */
export function detectLocaleFromPath(pathname: string): Locale {
return pathname === "/en" || pathname.startsWith("/en/") ? "en" : "fr";
}

View File

@@ -0,0 +1,67 @@
import type { Locale } from "@/types/sections";
/**
* UI strings (chrome): nav labels, footer, buttons. Page content lives in src/content/*.
*/
export const UI = {
nav: {
home: { fr: "Accueil", en: "Home" },
municipality: { fr: "Municipalité", en: "Municipality" },
mayor: { fr: "Mot du maire", en: "A Word from the Mayor" },
council: { fr: "Conseil municipal", en: "Our Council" },
services: { fr: "Services municipaux et publics",en: "Our Services" },
documents: { fr: "Documents importants", en: "Important Documents" },
community: { fr: "Notre communauté", en: "Our Community" },
communityPhotos: { fr: "Album photos communauté", en: "Community Photo Album" },
activitiesPhotos: { fr: "Album photos activités", en: "Activities Photo Album" },
developmentPlan: { fr: "Plan de développement", en: "Development Plan" },
townMap: { fr: "Carte de la ville", en: "Town Map" },
businesses: { fr: "Entreprises locales", en: "Local Businesses" },
events: { fr: "Événements", en: "Events" },
publicNotices: { fr: "Avis publics", en: "Public Notices" },
minutes: { fr: "Procès-verbaux", en: "Minutes" },
newsletters: { fr: "Bulletins", en: "Newsletters" },
},
topbar: {
home: { fr: "Accueil", en: "Home" },
hours: { fr: "Mar. - Ven. 8 h à 16 h", en: "Tues. - Fri. 8:00 a.m. to 4:00 p.m." },
},
cta: {
learnMore: { fr: "En savoir plus", en: "Learn more" },
contact: { fr: "Nous contacter", en: "Contact us" },
viewAll: { fr: "Tout voir", en: "View all" },
send: { fr: "Envoyer", en: "Send" },
download: { fr: "Télécharger", en: "Download" },
openPdf: { fr: "Ouvrir le PDF", en: "Open PDF" },
backHome: { fr: "Retour à l'accueil", en: "Back to home" },
},
form: {
name: { fr: "Nom", en: "Name" },
email: { fr: "Courriel", en: "Email" },
subject: { fr: "Sujet", en: "Subject" },
message: { fr: "Message", en: "Message" },
soon: { fr: "Lenvoi du formulaire sera activé prochainement. Vous pouvez nous joindre par courriel ou téléphone.",
en: "Form submission will be enabled soon. You can reach us by email or phone in the meantime." },
},
footer: {
address: { fr: "Adresse", en: "Address" },
contact: { fr: "Coordonnées", en: "Contact" },
hours: { fr: "Heures douverture", en: "Office hours" },
quick: { fr: "Liens rapides", en: "Quick links" },
rights: { fr: "Tous droits réservés.", en: "All rights reserved." },
},
events: {
upcoming: { fr: "À venir", en: "Upcoming" },
},
documents: {
pdf: { fr: "Document PDF", en: "PDF document" },
requestOnDemand: {
fr: "Document disponible sur demande à la municipalité.",
en: "Document available on request from the municipality.",
},
},
} as const;
export function t<T extends { fr: string; en: string }>(value: T, locale: Locale): string {
return value[locale];
}