Fil d'Ariane automatique basé sur le pathname : Accueil → Section → Sous-page. Adapté aux 3 langues (Accueil/Home/الرئيسية). Absent sur les pages d'accueil.
192 lines
5.9 KiB
Text
192 lines
5.9 KiB
Text
---
|
|
import type { ImageMetadata } from "astro";
|
|
import { getAlternateUrls } from "../utils/page-translations";
|
|
import defaultOgImage from "../assets/images/jalil-2.jpg";
|
|
|
|
interface Props {
|
|
title: string;
|
|
description: string;
|
|
ogImage?: ImageMetadata;
|
|
ogType?: string;
|
|
article?: { publishedTime?: string; tags?: string[] };
|
|
noindex?: boolean;
|
|
}
|
|
|
|
const {
|
|
title,
|
|
description,
|
|
ogImage,
|
|
ogType = "website",
|
|
article,
|
|
noindex = false,
|
|
} = Astro.props;
|
|
|
|
const siteUrl = Astro.site!.origin;
|
|
const canonicalUrl = new URL(Astro.url.pathname, siteUrl).href;
|
|
const imageUrl = new URL((ogImage ?? defaultOgImage).src, siteUrl).href;
|
|
|
|
// Locale detection
|
|
const pathname = Astro.url.pathname.replace(/\/$/, "") || "/";
|
|
const locale = pathname.startsWith("/en")
|
|
? "en"
|
|
: pathname.startsWith("/ar")
|
|
? "ar"
|
|
: "fr";
|
|
|
|
const ogLocaleMap: Record<string, string> = {
|
|
fr: "fr_FR",
|
|
en: "en_US",
|
|
ar: "ar_SA",
|
|
};
|
|
|
|
// Hreflang
|
|
const alternateUrls = getAlternateUrls(pathname);
|
|
|
|
// Home pages for WebSite schema
|
|
const isHomePage = pathname === "/" || pathname === "/en" || pathname === "/ar";
|
|
|
|
// Person JSON-LD (on every page)
|
|
const personJsonLd = {
|
|
"@context": "https://schema.org",
|
|
"@type": "Person",
|
|
"@id": `${siteUrl}/#person`,
|
|
name: "Jalil Arfaoui",
|
|
url: siteUrl,
|
|
image: imageUrl,
|
|
jobTitle: "Software Craftsman",
|
|
knowsAbout: [
|
|
"Software Craftsmanship",
|
|
"TDD",
|
|
"DDD",
|
|
"Improv Theater",
|
|
"Photography",
|
|
],
|
|
sameAs: [
|
|
"https://www.linkedin.com/in/jalil/",
|
|
"https://github.com/JalilArfaoui",
|
|
"https://gitlab.gnome.org/Jalil",
|
|
"https://forge.tiqa.fr/jalil",
|
|
"https://framagit.org/jalil",
|
|
"https://www.malt.fr/profile/jalilarfaoui?overview",
|
|
"https://www.collective.work/profile/jalil-arfaoui-mrr",
|
|
"https://500px.com/p/jalilarfaoui",
|
|
"https://commons.wikimedia.org/wiki/User:JalilArfaoui",
|
|
"https://www.instagram.com/l.i.l.a.j",
|
|
"https://x.com/jalilarfaoui",
|
|
],
|
|
};
|
|
|
|
// Article JSON-LD (for blog posts / articles)
|
|
const articleJsonLd =
|
|
ogType === "article" && article
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "BlogPosting",
|
|
headline: title,
|
|
description,
|
|
image: imageUrl,
|
|
url: canonicalUrl,
|
|
inLanguage: locale,
|
|
...(article.publishedTime && { datePublished: article.publishedTime }),
|
|
author: { "@id": `${siteUrl}/#person` },
|
|
}
|
|
: null;
|
|
|
|
// BreadcrumbList JSON-LD (all pages except home)
|
|
const breadcrumbJsonLd = !isHomePage
|
|
? (() => {
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
// Build cumulative paths: /en/code → [{name: "Home", url: /en}, {name: "Code", url: /en/code}]
|
|
const homeUrl = locale === "fr" ? "/" : `/${locale}`;
|
|
const homeName = locale === "ar" ? "الرئيسية" : locale === "en" ? "Home" : "Accueil";
|
|
const items = [{ name: homeName, url: new URL(homeUrl, siteUrl).href }];
|
|
// Skip locale prefix for building readable names
|
|
const startIdx = locale !== "fr" ? 1 : 0;
|
|
let cumulativePath = locale !== "fr" ? `/${locale}` : "";
|
|
for (let i = startIdx; i < segments.length; i++) {
|
|
cumulativePath += `/${segments[i]}`;
|
|
const name = decodeURIComponent(segments[i])
|
|
.replace(/-/g, " ")
|
|
.replace(/^\w/, (c) => c.toUpperCase());
|
|
items.push({ name, url: new URL(cumulativePath, siteUrl).href });
|
|
}
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "BreadcrumbList",
|
|
itemListElement: items.map((item, i) => ({
|
|
"@type": "ListItem",
|
|
position: i + 1,
|
|
name: item.name,
|
|
item: item.url,
|
|
})),
|
|
};
|
|
})()
|
|
: null;
|
|
|
|
// WebSite JSON-LD (only on home pages)
|
|
const websiteJsonLd = isHomePage
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "WebSite",
|
|
name: "Jalil Arfaoui",
|
|
url: siteUrl,
|
|
inLanguage: locale,
|
|
author: { "@id": `${siteUrl}/#person` },
|
|
}
|
|
: null;
|
|
---
|
|
|
|
<!-- Meta tags -->
|
|
<meta name="description" content={description} />
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:title" content={title} />
|
|
<meta property="og:description" content={description} />
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<meta property="og:image" content={imageUrl} />
|
|
<meta property="og:type" content={ogType} />
|
|
<meta property="og:locale" content={ogLocaleMap[locale]} />
|
|
<meta property="og:site_name" content="Jalil Arfaoui" />
|
|
{article?.publishedTime && (
|
|
<meta property="article:published_time" content={article.publishedTime} />
|
|
)}
|
|
{article?.tags?.map((tag) => (
|
|
<meta property="article:tag" content={tag} />
|
|
))}
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:site" content="@jalilarfaoui" />
|
|
<meta name="twitter:title" content={title} />
|
|
<meta name="twitter:description" content={description} />
|
|
<meta name="twitter:image" content={imageUrl} />
|
|
|
|
<!-- Hreflang -->
|
|
{alternateUrls && (
|
|
<>
|
|
<link rel="alternate" hreflang="fr" href={new URL(alternateUrls.fr, siteUrl).href} />
|
|
<link rel="alternate" hreflang="en" href={new URL(alternateUrls.en, siteUrl).href} />
|
|
<link rel="alternate" hreflang="ar" href={new URL(alternateUrls.ar, siteUrl).href} />
|
|
<link rel="alternate" hreflang="x-default" href={new URL(alternateUrls.fr, siteUrl).href} />
|
|
</>
|
|
)}
|
|
|
|
<!-- JSON-LD Person -->
|
|
<script type="application/ld+json" set:html={JSON.stringify(personJsonLd)} />
|
|
|
|
<!-- JSON-LD Article (blog posts only) -->
|
|
{articleJsonLd && (
|
|
<script type="application/ld+json" set:html={JSON.stringify(articleJsonLd)} />
|
|
)}
|
|
|
|
<!-- JSON-LD BreadcrumbList (inner pages only) -->
|
|
{breadcrumbJsonLd && (
|
|
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbJsonLd)} />
|
|
)}
|
|
|
|
<!-- JSON-LD WebSite (home pages only) -->
|
|
{websiteJsonLd && (
|
|
<script type="application/ld+json" set:html={JSON.stringify(websiteJsonLd)} />
|
|
)}
|