From 578c6733db92c4ff03a0750c93cadfdfd1516432 Mon Sep 17 00:00:00 2001 From: Jalil Arfaoui Date: Fri, 27 Feb 2026 01:33:57 +0100 Subject: [PATCH] =?UTF-8?q?Pages=20de=20d=C3=A9tail=20individuelles=20pour?= =?UTF-8?q?=20les=20projets=20(FR,=20EN,=20AR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les cartes projet sont désormais cliquables et mènent vers une page de détail qui affiche le contenu markdown riche (historique, architecture, impact) avec les technologies, liens externes et hreflang. --- src/components/code/ProjectCard.astro | 84 +++++++------- .../code/ProjectDetailContent.astro | 104 ++++++++++++++++++ src/pages/ar/برمجة/index.astro | 6 +- src/pages/ar/برمجة/مشاريع.astro | 6 +- src/pages/ar/برمجة/مشاريع/[slug].astro | 18 +++ src/pages/code/index.astro | 6 +- src/pages/code/projets.astro | 6 +- src/pages/code/projets/[slug].astro | 18 +++ src/pages/en/code/index.astro | 6 +- src/pages/en/code/projects.astro | 6 +- src/pages/en/code/projects/[slug].astro | 18 +++ src/utils/i18n.ts | 20 ++++ src/utils/page-translations.ts | 14 ++- 13 files changed, 257 insertions(+), 55 deletions(-) create mode 100644 src/components/code/ProjectDetailContent.astro create mode 100644 src/pages/ar/برمجة/مشاريع/[slug].astro create mode 100644 src/pages/code/projets/[slug].astro create mode 100644 src/pages/en/code/projects/[slug].astro diff --git a/src/components/code/ProjectCard.astro b/src/components/code/ProjectCard.astro index 69b7ae5..97604be 100644 --- a/src/components/code/ProjectCard.astro +++ b/src/components/code/ProjectCard.astro @@ -1,52 +1,52 @@ --- +import { t, type Locale } from '../../utils/i18n'; + interface Props { - title: string; - description: string; - technologies?: string[]; - url?: string; - github?: string; - featured?: boolean; + title: string; + description: string; + technologies?: string[]; + featured?: boolean; + detailUrl: string; + lang?: Locale; } -const { title, description, technologies, url, github, featured = false } = Astro.props; +const { title, description, technologies, featured = false, detailUrl, lang = 'fr' } = Astro.props; + +const isRtl = lang === 'ar'; +const readMoreLabel = t('projects', 'readMore', lang); --- -
-
-

- {title} -

-
+
+

+ {title} +

+
-

- {description} -

+

+ {description} +

- {technologies && technologies.length > 0 && ( -
- {technologies.map((tech) => ( - - {tech} - - ))} -
- )} + {technologies && technologies.length > 0 && ( +
+ {technologies.map((tech) => ( + + {tech} + + ))} +
+ )} -
- {url && ( - - Voir le site → - - )} - {github && ( - - GitHub → - - )} -
-
+ + {isRtl ? ( + <>{readMoreLabel} ← + ) : ( + <>{readMoreLabel} → + )} + + diff --git a/src/components/code/ProjectDetailContent.astro b/src/components/code/ProjectDetailContent.astro new file mode 100644 index 0000000..0ffbd09 --- /dev/null +++ b/src/components/code/ProjectDetailContent.astro @@ -0,0 +1,104 @@ +--- +import Layout from '../../layouts/main.astro'; +import { render } from 'astro:content'; +import { t, getProjectsBasePath, getDateLocale, type Locale } from '../../utils/i18n'; + +interface Props { + project: any; + lang: Locale; +} + +const { project, lang } = Astro.props; +const { Content } = await render(project); + +const projectsPath = getProjectsBasePath(lang); +const isRtl = lang === 'ar'; +const year = project.data.date.getFullYear(); +--- + + +
+ + {isRtl ? ( + <> + {t('projects', 'backToProjects', lang)} + + + + + ) : ( + <> + + + + {t('projects', 'backToProjects', lang)} + + )} + + +
+

{project.data.title}

+

{project.data.description}

+ +
+ {t('projects', 'startedIn', lang)} {year} +
+ + {project.data.technologies && project.data.technologies.length > 0 && ( +
+ {project.data.technologies.map((tech: string) => ( + + {tech} + + ))} +
+ )} + +
+ {project.data.url && ( + + {t('projects', 'visitSite', lang)} + + + + + )} + {project.data.github && ( + + {t('projects', 'viewOnGithub', lang)} + + + + + )} +
+
+ + {project.body && ( +
+ +
+ )} +
+
diff --git a/src/pages/ar/برمجة/index.astro b/src/pages/ar/برمجة/index.astro index bf1a1e2..12ec4ee 100644 --- a/src/pages/ar/برمجة/index.astro +++ b/src/pages/ar/برمجة/index.astro @@ -6,10 +6,12 @@ import Link from "../../../components/Link.astro"; import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; import SkillBadge from "../../../components/code/SkillBadge.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png"; import skillsData from "../../../data/skills.json"; const locale = "ar"; +const projectsBasePath = getProjectsBasePath(locale); const experiences = (await getCollection("experiences")) .filter((e) => e.data.lang === locale && !e.data.draft) @@ -103,9 +105,9 @@ function formatMonth(dateStr: string) { title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/ar/برمجة/مشاريع.astro b/src/pages/ar/برمجة/مشاريع.astro index 0a9b907..b54f0e2 100644 --- a/src/pages/ar/برمجة/مشاريع.astro +++ b/src/pages/ar/برمجة/مشاريع.astro @@ -2,8 +2,10 @@ import { getCollection } from "astro:content"; import Layout from "../../../layouts/main.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; const locale = "ar"; +const projectsBasePath = getProjectsBasePath(locale); const projects = (await getCollection("projects")) .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev") @@ -36,9 +38,9 @@ const projects = (await getCollection("projects")) title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/ar/برمجة/مشاريع/[slug].astro b/src/pages/ar/برمجة/مشاريع/[slug].astro new file mode 100644 index 0000000..7dfd612 --- /dev/null +++ b/src/pages/ar/برمجة/مشاريع/[slug].astro @@ -0,0 +1,18 @@ +--- +import ProjectDetailContent from '../../../../components/code/ProjectDetailContent.astro'; +import { getCollection } from 'astro:content'; +import { getProjectBaseSlug } from '../../../../utils/i18n'; + +export async function getStaticPaths() { + const allProjects = await getCollection('projects'); + const arProjects = allProjects.filter(p => p.data.lang === 'ar' && !p.data.draft && p.data.category === 'dev'); + return arProjects.map(project => ({ + params: { slug: getProjectBaseSlug(project.id) }, + props: { project }, + })); +} + +const { project } = Astro.props; +--- + + diff --git a/src/pages/code/index.astro b/src/pages/code/index.astro index 5be219c..11520d2 100644 --- a/src/pages/code/index.astro +++ b/src/pages/code/index.astro @@ -5,9 +5,11 @@ import Layout from "../../layouts/main.astro"; import Link from "../../components/Link.astro"; import FeaturedRecommendation from "../../components/code/FeaturedRecommendation.astro"; import ProjectCard from "../../components/code/ProjectCard.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../utils/i18n"; import logoTiqa from "../../assets/images/logo-tiqa-blanc.png"; const locale = "fr"; +const projectsBasePath = getProjectsBasePath(locale); const experiences = (await getCollection("experiences")) .filter((e) => e.data.lang === locale && !e.data.draft) @@ -99,9 +101,9 @@ function formatMonth(dateStr: string) { title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/code/projets.astro b/src/pages/code/projets.astro index a591eea..4cccdd0 100644 --- a/src/pages/code/projets.astro +++ b/src/pages/code/projets.astro @@ -2,8 +2,10 @@ import { getCollection } from "astro:content"; import Layout from "../../layouts/main.astro"; import ProjectCard from "../../components/code/ProjectCard.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../utils/i18n"; const locale = "fr"; +const projectsBasePath = getProjectsBasePath(locale); const projects = (await getCollection("projects")) .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev") @@ -36,9 +38,9 @@ const projects = (await getCollection("projects")) title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/code/projets/[slug].astro b/src/pages/code/projets/[slug].astro new file mode 100644 index 0000000..8880c0a --- /dev/null +++ b/src/pages/code/projets/[slug].astro @@ -0,0 +1,18 @@ +--- +import ProjectDetailContent from '../../../components/code/ProjectDetailContent.astro'; +import { getCollection } from 'astro:content'; +import { getProjectBaseSlug } from '../../../utils/i18n'; + +export async function getStaticPaths() { + const allProjects = await getCollection('projects'); + const frProjects = allProjects.filter(p => p.data.lang === 'fr' && !p.data.draft && p.data.category === 'dev'); + return frProjects.map(project => ({ + params: { slug: getProjectBaseSlug(project.id) }, + props: { project }, + })); +} + +const { project } = Astro.props; +--- + + diff --git a/src/pages/en/code/index.astro b/src/pages/en/code/index.astro index 6d0c775..4a8cc9d 100644 --- a/src/pages/en/code/index.astro +++ b/src/pages/en/code/index.astro @@ -6,10 +6,12 @@ import Link from "../../../components/Link.astro"; import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; import SkillBadge from "../../../components/code/SkillBadge.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png"; import skillsData from "../../../data/skills.json"; const locale = "en"; +const projectsBasePath = getProjectsBasePath(locale); const experiences = (await getCollection("experiences")) .filter((e) => e.data.lang === locale && !e.data.draft) @@ -103,9 +105,9 @@ function formatMonth(dateStr: string) { title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/en/code/projects.astro b/src/pages/en/code/projects.astro index 148da25..0c292a5 100644 --- a/src/pages/en/code/projects.astro +++ b/src/pages/en/code/projects.astro @@ -2,8 +2,10 @@ import { getCollection } from "astro:content"; import Layout from "../../../layouts/main.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; +import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; const locale = "en"; +const projectsBasePath = getProjectsBasePath(locale); const projects = (await getCollection("projects")) .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev") @@ -36,9 +38,9 @@ const projects = (await getCollection("projects")) title={project.data.title} description={project.data.description} technologies={project.data.technologies} - url={project.data.url} - github={project.data.github} featured={project.data.featured} + detailUrl={`${projectsBasePath}/${getProjectBaseSlug(project.id)}`} + lang={locale} /> ))} diff --git a/src/pages/en/code/projects/[slug].astro b/src/pages/en/code/projects/[slug].astro new file mode 100644 index 0000000..b6c71ad --- /dev/null +++ b/src/pages/en/code/projects/[slug].astro @@ -0,0 +1,18 @@ +--- +import ProjectDetailContent from '../../../../components/code/ProjectDetailContent.astro'; +import { getCollection } from 'astro:content'; +import { getProjectBaseSlug } from '../../../../utils/i18n'; + +export async function getStaticPaths() { + const allProjects = await getCollection('projects'); + const enProjects = allProjects.filter(p => p.data.lang === 'en' && !p.data.draft && p.data.category === 'dev'); + return enProjects.map(project => ({ + params: { slug: getProjectBaseSlug(project.id) }, + props: { project }, + })); +} + +const { project } = Astro.props; +--- + + diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index defb433..9de4640 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -93,6 +93,14 @@ export const translations = { nextImage: { fr: 'Image suivante', en: 'Next image', ar: 'الصورة التالية' }, goToImage: { fr: "Aller à l'image", en: 'Go to image', ar: 'انتقل إلى الصورة' }, }, + projects: { + visitSite: { fr: 'Voir le site', en: 'Visit site', ar: 'زيارة الموقع' }, + viewOnGithub: { fr: 'Voir sur GitHub', en: 'View on GitHub', ar: 'عرض على GitHub' }, + backToProjects: { fr: 'Projets', en: 'Projects', ar: 'المشاريع' }, + technologies: { fr: 'Technologies', en: 'Technologies', ar: 'التقنيات' }, + startedIn: { fr: 'Démarré en', en: 'Started in', ar: 'بدأ في' }, + readMore: { fr: 'Lire plus', en: 'Read more', ar: 'اقرأ المزيد' }, + }, pages: { home: { title: { fr: 'Jalil Arfaoui', en: 'Jalil Arfaoui', ar: 'جليل عرفاوي' }, @@ -215,4 +223,16 @@ export function getHomePath(locale: Locale): string { /** Slug de base d'un photo blog post depuis son id (ex: "2015/enigma.en" → "enigma") */ export function getPostBaseSlug(postId: string): string { return postId.replace(/^\d{4}\//, '').replace(/\.(en|ar)(\.md)?$/, ''); +} + +/** Slug de base d'un projet depuis son id (ex: "debats.en" → "debats") */ +export function getProjectBaseSlug(projectId: string): string { + return projectId.replace(/\.(en|ar)$/, ''); +} + +/** Chemin de base des projets selon la langue */ +export function getProjectsBasePath(locale: Locale): string { + if (locale === 'ar') return '/ar/برمجة/مشاريع'; + if (locale === 'en') return '/en/code/projects'; + return '/code/projets'; } \ No newline at end of file diff --git a/src/utils/page-translations.ts b/src/utils/page-translations.ts index fc8cefc..076c951 100644 --- a/src/utils/page-translations.ts +++ b/src/utils/page-translations.ts @@ -1,5 +1,5 @@ import type { Locale } from "./i18n"; -import { getPhotoAlbumsPath, getPhotoBlogPath } from "./i18n"; +import { getPhotoAlbumsPath, getPhotoBlogPath, getProjectsBasePath } from "./i18n"; /** * Groupes de pages traduites. @@ -57,6 +57,18 @@ const dynamicPatterns: DynamicPattern[] = [ }; }, }, + { + // Projects: /code/projets/{slug}, /en/code/projects/{slug}, /ar/برمجة/مشاريع/{slug} + regex: /^(?:\/code\/projets|\/en\/code\/projects|\/ar\/برمجة\/مشاريع)\/([^/]+)$/, + buildUrls: (match) => { + const slug = match[1]; + return { + fr: `${getProjectsBasePath("fr")}/${slug}`, + en: `${getProjectsBasePath("en")}/${slug}`, + ar: `${getProjectsBasePath("ar")}/${slug}`, + }; + }, + }, ]; function matchDynamicPattern(