Ajout de getLocalizedCollection/getLocalizedEntry dans content-i18n.ts pour fusionner automatiquement les entrées localisées avec leur base FR. Schémas adaptés (champs partagés optionnels), 78 fichiers de contenu allégés, consommateurs migrés.
140 lines
4.5 KiB
TypeScript
140 lines
4.5 KiB
TypeScript
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' };
|
|
return date.toLocaleDateString(locales[lang] || 'fr-FR', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
});
|
|
};
|
|
|
|
const blogCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
date: z.date(),
|
|
category: z.enum(['pro', 'comedy', 'photo']),
|
|
tags: z.array(z.string()).optional(),
|
|
image: z.string().optional(),
|
|
imageAlt: z.string().optional(),
|
|
draft: z.boolean().default(false),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}).transform((data) => ({
|
|
...data,
|
|
dateFormatted: formatDate(data.date, data.lang),
|
|
})),
|
|
});
|
|
|
|
const projectsCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/projects", ...stripExtension('md') }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
date: z.date().optional(),
|
|
category: z.enum(['dev', 'comedy', 'photo']).optional(),
|
|
technologies: z.array(z.string()).optional(),
|
|
url: z.string().url().optional(),
|
|
github: z.string().url().optional(),
|
|
image: z.string().optional(),
|
|
imageAlt: z.string().optional(),
|
|
featured: z.boolean().optional(),
|
|
draft: z.boolean().optional(),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}).transform((data) => ({
|
|
...data,
|
|
dateFormatted: data.date ? formatDate(data.date, data.lang) : undefined,
|
|
})),
|
|
});
|
|
|
|
const experiencesCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/experiences", ...stripExtension('md') }),
|
|
schema: z.object({
|
|
role: z.string(),
|
|
company: z.string().optional(),
|
|
companyUrl: z.string().url().optional(),
|
|
logo: z.string().optional(),
|
|
location: z.string().optional(),
|
|
startDate: z.string().optional(),
|
|
endDate: z.string().optional(),
|
|
technologies: z.array(z.string()).optional(),
|
|
type: z.enum(['employment', 'freelance', 'teaching', 'community', 'entrepreneurship']).optional(),
|
|
featured: z.boolean().optional(),
|
|
draft: z.boolean().optional(),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}),
|
|
});
|
|
|
|
const recommendationsCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/recommendations" }),
|
|
schema: z.object({
|
|
author: z.string(),
|
|
authorRole: z.string(),
|
|
company: z.string().optional(),
|
|
avatar: z.string().optional(),
|
|
url: z.string().url().optional(),
|
|
date: z.date(),
|
|
featured: z.boolean().default(false),
|
|
relationship: z.string().optional(),
|
|
lang: z.enum(['fr', 'en']).default('fr'),
|
|
}),
|
|
});
|
|
|
|
const talksCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/talks" }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
date: z.date(),
|
|
dateFormatted: z.string(),
|
|
event: z.string(),
|
|
location: z.string(),
|
|
slides: z.string().url().optional(),
|
|
video: z.string().url().optional(),
|
|
image: z.string().optional(),
|
|
imageAlt: z.string().optional(),
|
|
tags: z.array(z.string()).optional(),
|
|
draft: z.boolean().default(false),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}),
|
|
});
|
|
|
|
const photoBlogPostsCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.md", base: "./src/content/photoBlogPosts", ...stripExtension('md') }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
date: z.date().optional(),
|
|
coverImage: z.string().optional(),
|
|
tags: z.array(z.string()).optional(),
|
|
featured: z.boolean().optional(),
|
|
draft: z.boolean().optional(),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}),
|
|
});
|
|
|
|
const photoCategoriesCollection = defineCollection({
|
|
loader: glob({ pattern: "**/*.json", base: "./src/content/photoCategories", ...stripExtension('json') }),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
subtitle: z.string(),
|
|
order: z.number().optional(),
|
|
lang: z.enum(['fr', 'en', 'ar']).default('fr'),
|
|
}),
|
|
});
|
|
|
|
export const collections = {
|
|
blog: blogCollection,
|
|
projects: projectsCollection,
|
|
experiences: experiencesCollection,
|
|
recommendations: recommendationsCollection,
|
|
talks: talksCollection,
|
|
photoBlogPosts: photoBlogPostsCollection,
|
|
photoCategories: photoCategoriesCollection,
|
|
};
|