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}
+
+ ))}
+
+ )}
-
-
+
+ {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();
+---
+
+
+
+
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(