Compare commits

...

3 commits

Author SHA1 Message Date
66c391a5de Ajout d'une page 404 multilingue (FR/EN/AR) 2026-02-18 18:32:13 +01:00
0644038d64 Redirection permanente /photos → https://photos.jalil.arfaoui.net 2026-02-18 18:11:38 +01:00
ae565d46ac Migration Astro v4 → v5 avec Content Layer API
- Mise à jour astro@5.17, @astrojs/tailwind@6, @astrojs/check
- Remplacement des content collections legacy par des loaders glob()
- Déplacement src/content/config.ts → src/content.config.ts
- entry.slug → entry.id, entry.render() → render(entry)
- Ajout de generateId personnalisé pour préserver les points dans les IDs des fichiers multilingues (.en, .ar)
2026-02-18 18:11:29 +01:00
9 changed files with 1725 additions and 1340 deletions

View file

@ -6,6 +6,12 @@ import tailwind from "@astrojs/tailwind";
export default defineConfig({
devToolbar: { enabled: false },
integrations: [tailwind()],
redirects: {
"/photos": {
status: 301,
destination: "https://photos.jalil.arfaoui.net"
}
},
i18n: {
defaultLocale: "fr",
locales: ["fr", "en", "ar"],

2982
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,12 +13,12 @@
"check": "biome check --apply-unsafe ."
},
"devDependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/check": "^0.9.6",
"@astrojs/tailwind": "^6.0.2",
"@biomejs/biome": "1.7.3",
"@tailwindcss/typography": "^0.5.13",
"@types/node": "^25.0.3",
"astro": "^4.8.2",
"astro": "^5.17.2",
"dotenv": "^17.2.3",
"tailwindcss": "^3.4.3",
"tsx": "^4.21.0",

View file

@ -4,6 +4,7 @@ import CategoryNav from '../CategoryNav.astro';
import AlbumHeader from '../AlbumHeader.astro';
import MasonryGallery from '../MasonryGallery.astro';
import Lightbox from '../Lightbox.astro';
import { render } from 'astro:content';
import { getPostBaseSlug, type Locale } from '../../../utils/i18n';
interface Props {
@ -16,7 +17,7 @@ const { post, lang = 'fr' } = Astro.props;
// Importer toutes les images du dossier photos
const allImages = import.meta.glob<{ default: ImageMetadata }>('/src/assets/images/photos/blog/**/*.{jpg,jpeg,png,webp}');
const { Content } = await post.render();
const { Content } = await render(post);
const baseSlug = getPostBaseSlug(post.id);

View file

@ -7,7 +7,7 @@ const { count } = Astro.props;
const postsLoop = allPosts.slice(0, count).map((post) => {
return {
...(post.data || {}),
link: `/post/${post.slug}`,
link: `/post/${post.id}`,
};
});
---

View file

@ -1,4 +1,9 @@
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
/** Préserve les points dans les IDs (ex: "portraits.en.json" → "portraits.en") */
const stripExtension = (ext: string) =>
({ generateId: ({ entry }: { entry: string }) => entry.replace(new RegExp(`\\.${ext}$`), '') });
const formatDate = (date: Date, lang: string = 'fr') => {
const locales: Record<string, string> = { fr: 'fr-FR', en: 'en-US', ar: 'ar-SA' };
@ -10,7 +15,7 @@ const formatDate = (date: Date, lang: string = 'fr') => {
};
const blogCollection = defineCollection({
type: "content",
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
@ -28,7 +33,7 @@ const blogCollection = defineCollection({
});
const projectsCollection = defineCollection({
type: "content",
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
description: z.string(),
@ -47,7 +52,7 @@ const projectsCollection = defineCollection({
});
const talksCollection = defineCollection({
type: "content",
loader: glob({ pattern: "**/*.md", base: "./src/content/talks" }),
schema: z.object({
title: z.string(),
description: z.string(),
@ -66,7 +71,7 @@ const talksCollection = defineCollection({
});
const photoBlogPostsCollection = defineCollection({
type: "content",
loader: glob({ pattern: "**/*.md", base: "./src/content/photoBlogPosts", ...stripExtension('md') }),
schema: z.object({
title: z.string(),
description: z.string(),
@ -80,7 +85,7 @@ const photoBlogPostsCollection = defineCollection({
});
const photoCategoriesCollection = defineCollection({
type: "data",
loader: glob({ pattern: "**/*.json", base: "./src/content/photoCategories", ...stripExtension('json') }),
schema: z.object({
title: z.string(),
subtitle: z.string(),

39
src/pages/404.astro Normal file
View file

@ -0,0 +1,39 @@
---
import Layout from "../layouts/main.astro";
---
<Layout title="404">
<div class="relative z-20 flex flex-col items-center justify-center w-full max-w-2xl mx-auto px-7 mt-16 mb-16 md:mt-24 lg:mt-32 text-center min-h-[50vh]">
<h1 class="text-8xl md:text-9xl font-bold text-neutral-200 dark:text-neutral-800 select-none">
404
</h1>
<p id="message" class="mt-6 text-xl text-neutral-600 dark:text-neutral-400">
Cette page n'existe pas.
</p>
<a id="home-link" href="/" class="mt-8 inline-flex items-center px-6 py-3 text-sm font-semibold text-white bg-blue-600 rounded-full hover:bg-blue-700 transition-colors duration-200">
Retour à l'accueil
</a>
</div>
</Layout>
<script is:inline>
(function () {
var path = window.location.pathname;
var msg = document.getElementById('message');
var link = document.getElementById('home-link');
if (path.startsWith('/en')) {
msg.textContent = 'This page does not exist.';
link.textContent = 'Back to home';
link.href = '/en';
} else if (path.startsWith('/ar')) {
msg.textContent = 'هذه الصفحة غير موجودة.';
link.textContent = 'العودة إلى الصفحة الرئيسية';
link.href = '/ar';
msg.dir = 'rtl';
link.dir = 'rtl';
}
})();
</script>

View file

@ -1,16 +1,16 @@
---
import { getCollection } from "astro:content";
import { getCollection, render } from "astro:content";
export async function getStaticPaths() {
const postEntries = await getCollection("blog");
return postEntries.map((entry) => ({
params: { slug: entry.slug },
params: { slug: entry.id },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
const { Content } = await render(entry);
---
<Content />

View file

@ -170,7 +170,7 @@ export function getHomePath(locale: Locale): string {
return locale === 'fr' ? '/' : `/${locale}`;
}
/** Slug de base d'un photo blog post depuis son id (ex: "2015/enigma.en.md" → "enigma") */
/** 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)?\.mdx?$/, '');
return postId.replace(/^\d{4}\//, '').replace(/\.(en|ar)(\.md)?$/, '');
}