diff --git a/astro.config.mjs b/astro.config.mjs index fa979ac..b53bbef 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from "astro/config"; import sitemap from "@astrojs/sitemap"; import tailwind from "@astrojs/tailwind"; +import { getAlternateUrls } from "./src/utils/page-translations.ts"; // https://astro.build/config export default defineConfig({ @@ -10,13 +11,21 @@ export default defineConfig({ integrations: [ tailwind(), sitemap({ - i18n: { - defaultLocale: "fr", - locales: { - fr: "fr", - en: "en", - ar: "ar", - }, + serialize(item) { + const url = new URL(item.url); + const pathname = decodeURIComponent(url.pathname).replace(/\/$/, "") || "/"; + const alternates = getAlternateUrls(pathname); + if (alternates) { + const fullUrl = (path) => + path === "/" ? `${url.origin}/` : `${url.origin}${path}/`; + item.links = [ + { lang: "fr", url: fullUrl(alternates.fr) }, + { lang: "en", url: fullUrl(alternates.en) }, + { lang: "ar", url: fullUrl(alternates.ar) }, + { lang: "x-default", url: fullUrl(alternates.fr) }, + ]; + } + return item; }, }), ], diff --git a/src/utils/page-translations.ts b/src/utils/page-translations.ts index df469dd..0763a97 100644 --- a/src/utils/page-translations.ts +++ b/src/utils/page-translations.ts @@ -1,4 +1,5 @@ import type { Locale } from "./i18n"; +import { getPhotoAlbumsPath, getPhotoBlogPath } from "./i18n"; /** * Groupes de pages traduites. @@ -14,6 +15,7 @@ const translationGroups: Record[] = [ { fr: "/code/competences", en: "/en/code/skills", ar: "/ar/برمجة/مهارات" }, { fr: "/theatre", en: "/en/acting", ar: "/ar/مسرح" }, { fr: "/photo", en: "/en/photo", ar: "/ar/تصوير" }, + { fr: "/photo/blog", en: "/en/photo/blog", ar: "/ar/تصوير/مدونة" }, ]; /** Index inversé : pathname → groupe de traduction */ @@ -24,13 +26,59 @@ for (const group of translationGroups) { } } +/** Pattern dynamique pour les routes paramétrées */ +interface DynamicPattern { + regex: RegExp; + buildUrls: (match: RegExpMatchArray) => Record; +} + +const dynamicPatterns: DynamicPattern[] = [ + { + // Photo albums: /photo/albums/{cat}, /en/photo/albums/{cat}, /ar/تصوير/ألبومات/{cat} + regex: /^(?:\/photo\/albums|\/en\/photo\/albums|\/ar\/تصوير\/ألبومات)\/([^/]+)$/, + buildUrls: (match) => { + const category = match[1]; + return { + fr: `${getPhotoAlbumsPath("fr")}/${category}`, + en: `${getPhotoAlbumsPath("en")}/${category}`, + ar: `${getPhotoAlbumsPath("ar")}/${category}`, + }; + }, + }, + { + // Photo blog posts: /photo/blog/{year}/{slug}, /en/photo/blog/{year}/{slug}, /ar/تصوير/مدونة/{year}/{slug} + regex: /^(?:\/photo\/blog|\/en\/photo\/blog|\/ar\/تصوير\/مدونة)\/(\d{4})\/([^/]+)$/, + buildUrls: (match) => { + const [, year, slug] = match; + return { + fr: `${getPhotoBlogPath("fr")}/${year}/${slug}`, + en: `${getPhotoBlogPath("en")}/${year}/${slug}`, + ar: `${getPhotoBlogPath("ar")}/${year}/${slug}`, + }; + }, + }, +]; + +function matchDynamicPattern( + pathname: string, +): Record | undefined { + for (const pattern of dynamicPatterns) { + const match = pathname.match(pattern.regex); + if (match) { + return pattern.buildUrls(match); + } + } + return undefined; +} + /** * Retourne les URLs alternatives pour un pathname donné. * Les trailing slashes sont normalisés. + * Essaie d'abord le lookup statique, puis le matching dynamique. */ export function getAlternateUrls( pathname: string, ): Record | undefined { const normalized = pathname.replace(/\/$/, "") || "/"; - return pathIndex.get(normalized); + return pathIndex.get(normalized) ?? matchDynamicPattern(normalized); }