diff --git a/src/components/code/CareerTimeline.astro b/src/components/code/CareerTimeline.astro index 871f75b..f54a078 100644 --- a/src/components/code/CareerTimeline.astro +++ b/src/components/code/CareerTimeline.astro @@ -1,7 +1,8 @@ --- -import { getCollection, render } from "astro:content"; +import { render } from "astro:content"; import { Image } from "astro:assets"; import { t, getDateLocale, type Locale } from "../../utils/i18n"; +import { getLocalizedCollection } from "../../utils/content-i18n"; import "../../styles/exp-card.css"; interface Props { @@ -53,9 +54,9 @@ function computeDuration(startStr: string, endStr?: string) { return formatDuration(years, months); } -const experiences = (await getCollection("experiences")) - .filter((e) => e.data.lang === locale && !e.data.draft) - .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1)); +const experiences = (await getLocalizedCollection("experiences", locale)) + .filter((e) => !e.data.draft) + .sort((a, b) => (b.data.startDate! > a.data.startDate! ? 1 : -1)); const presentLabel = t('career', 'present', locale); const logoAltPrefix = t('career', 'logoAlt', locale); diff --git a/src/components/photo/CategoryGrid.astro b/src/components/photo/CategoryGrid.astro index 85d4cc6..f47ba7e 100644 --- a/src/components/photo/CategoryGrid.astro +++ b/src/components/photo/CategoryGrid.astro @@ -3,7 +3,7 @@ import CategoryNav from './CategoryNav.astro'; import HeroViewport from './HeroViewport.astro'; import Lightbox from './Lightbox.astro'; import { Picture } from 'astro:assets'; -import { getEntry } from 'astro:content'; +import { getLocalizedEntry } from '../../utils/content-i18n'; import { getCategoryEntryId, type Locale } from '../../utils/i18n'; interface Props { @@ -13,10 +13,9 @@ interface Props { const { category, lang = 'fr' } = Astro.props; -// Récupérer les métadonnées de la catégorie (localisées avec fallback FR) +// Récupérer les métadonnées de la catégorie (localisées avec fusion FR) const entryId = getCategoryEntryId(category, lang); -const categoryData = await getEntry('photoCategories', entryId) - ?? await getEntry('photoCategories', category); +const categoryData = await getLocalizedEntry('photoCategories', entryId, lang); // Auto-détection des images du dossier de la catégorie const allImages = import.meta.glob<{ default: ImageMetadata }>('/src/assets/images/photos/categories/**/*.{jpg,jpeg,png,webp}'); diff --git a/src/components/photo/CategoryNav.astro b/src/components/photo/CategoryNav.astro index 5c2ad34..a4ab3f0 100644 --- a/src/components/photo/CategoryNav.astro +++ b/src/components/photo/CategoryNav.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../utils/content-i18n'; import HomeIcon from '../icons/HomeIcon.astro'; import { t, getPhotoBasePath, getPhotoBlogPath, getPhotoAlbumsPath, getHomePath, type Locale } from '../../utils/i18n'; @@ -15,10 +15,8 @@ const photoBasePath = getPhotoBasePath(lang); const homePath = getHomePath(lang); // Récupérer les catégories depuis la collection, filtrées par langue -const allCategories = await getCollection('photoCategories'); -const langCategories = allCategories.filter(c => (c.data.lang ?? 'fr') === lang); -const effectiveCategories = langCategories.length > 0 ? langCategories : allCategories.filter(c => (c.data.lang ?? 'fr') === 'fr'); -const sortedCategories = effectiveCategories.sort((a, b) => (a.data.order || 99) - (b.data.order || 99)); +const localizedCategories = await getLocalizedCollection('photoCategories', lang); +const sortedCategories = localizedCategories.sort((a, b) => (a.data.order || 99) - (b.data.order || 99)); // Extraire l'id de base (sans suffixe de langue) const categories = sortedCategories.map(cat => ({ diff --git a/src/components/photo/ExploreSection.astro b/src/components/photo/ExploreSection.astro index 3556a9d..f210ca6 100644 --- a/src/components/photo/ExploreSection.astro +++ b/src/components/photo/ExploreSection.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../utils/content-i18n'; import { t, getPhotoBlogPath, getPhotoAlbumsPath, type Locale } from '../../utils/i18n'; interface Props { @@ -9,10 +9,8 @@ interface Props { const { lang = 'fr' } = Astro.props; // Récupérer les catégories filtrées par langue -const allCategories = await getCollection('photoCategories'); -const langCategories = allCategories.filter(c => (c.data.lang ?? 'fr') === lang); -const effectiveCategories = langCategories.length > 0 ? langCategories : allCategories.filter(c => (c.data.lang ?? 'fr') === 'fr'); -const sortedCategories = effectiveCategories.sort((a, b) => (a.data.order || 99) - (b.data.order || 99)); +const sortedCategories = (await getLocalizedCollection('photoCategories', lang)) + .sort((a, b) => (a.data.order || 99) - (b.data.order || 99)); ---
diff --git a/src/components/photo/Lightbox.astro b/src/components/photo/Lightbox.astro index c5b4ce7..76d0c68 100644 --- a/src/components/photo/Lightbox.astro +++ b/src/components/photo/Lightbox.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../utils/content-i18n'; import { t, type Locale } from '../../utils/i18n'; interface Props { @@ -15,12 +15,10 @@ const { images, albumTitle = '', showCategory = false, category = '', lang = 'fr const imagesForJS = JSON.stringify(images); // Construire les labels depuis la collection filtrée par langue -const allCategories = await getCollection('photoCategories'); -const langCategories = allCategories.filter(c => (c.data.lang ?? 'fr') === lang); -const effectiveCategories = langCategories.length > 0 ? langCategories : allCategories.filter(c => (c.data.lang ?? 'fr') === 'fr'); +const localizedCategories = await getLocalizedCollection('photoCategories', lang); const categoryLabels: Record = { 'blog': t('photo', 'photoFeed', lang), - ...Object.fromEntries(effectiveCategories.map(cat => [cat.id.replace(/\.(en|ar)$/, ''), cat.data.title])) + ...Object.fromEntries(localizedCategories.map(cat => [cat.id.replace(/\.(en|ar)$/, ''), cat.data.title])) }; --- diff --git a/src/components/photo/pages/PhotoAlbumContent.astro b/src/components/photo/pages/PhotoAlbumContent.astro index e2c8862..daaf3f8 100644 --- a/src/components/photo/pages/PhotoAlbumContent.astro +++ b/src/components/photo/pages/PhotoAlbumContent.astro @@ -2,7 +2,7 @@ import PhotoLayout from '../../../layouts/PhotoLayout.astro'; import CategoryGrid from '../CategoryGrid.astro'; import { getCategoryEntryId, type Locale } from '../../../utils/i18n'; -import { getEntry } from 'astro:content'; +import { getLocalizedEntry } from '../../../utils/content-i18n'; interface Props { category: string; @@ -13,8 +13,7 @@ const { category, lang = 'fr' } = Astro.props; // Récupérer le titre localisé pour le title de la page const entryId = getCategoryEntryId(category, lang); -const categoryData = await getEntry('photoCategories', entryId) - ?? await getEntry('photoCategories', category); +const categoryData = await getLocalizedEntry('photoCategories', entryId, lang); const categoryLabel = categoryData?.data.title || category; const categorySubtitle = categoryData?.data.subtitle || ""; diff --git a/src/components/photo/pages/PhotoBlogIndexContent.astro b/src/components/photo/pages/PhotoBlogIndexContent.astro index f6a542d..a6b8df9 100644 --- a/src/components/photo/pages/PhotoBlogIndexContent.astro +++ b/src/components/photo/pages/PhotoBlogIndexContent.astro @@ -1,7 +1,7 @@ --- import PhotoLayout from '../../../layouts/PhotoLayout.astro'; import CategoryNav from '../CategoryNav.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../utils/content-i18n'; import { Picture } from 'astro:assets'; import { t, getPhotoBlogPath, getDateLocale, getPostBaseSlug, type Locale } from '../../../utils/i18n'; @@ -20,8 +20,7 @@ const allImages = import.meta.glob<{ default: ImageMetadata }>( ); // Récupération des posts photo filtrés par langue -const allPhotoBlogPosts = (await getCollection('photoBlogPosts')) - .filter(post => (post.data.lang ?? 'fr') === lang); +const allPhotoBlogPosts = await getLocalizedCollection('photoBlogPosts', lang); // Tri par date (plus récent en premier) const sortedPosts = allPhotoBlogPosts.sort((a, b) => diff --git a/src/content.config.ts b/src/content.config.ts index d72d472..cdd0a23 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -37,19 +37,19 @@ const projectsCollection = defineCollection({ schema: z.object({ title: z.string(), description: z.string(), - date: z.date(), - category: z.enum(['dev', 'comedy', 'photo']), + 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().default(false), - draft: z.boolean().default(false), + featured: z.boolean().optional(), + draft: z.boolean().optional(), lang: z.enum(['fr', 'en', 'ar']).default('fr'), }).transform((data) => ({ ...data, - dateFormatted: formatDate(data.date, data.lang), + dateFormatted: data.date ? formatDate(data.date, data.lang) : undefined, })), }); @@ -57,16 +57,16 @@ const experiencesCollection = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/content/experiences", ...stripExtension('md') }), schema: z.object({ role: z.string(), - company: z.string(), + company: z.string().optional(), companyUrl: z.string().url().optional(), logo: z.string().optional(), location: z.string().optional(), - startDate: z.string(), + startDate: z.string().optional(), endDate: z.string().optional(), technologies: z.array(z.string()).optional(), - type: z.enum(['employment', 'freelance', 'teaching', 'community', 'entrepreneurship']), - featured: z.boolean().default(false), - draft: z.boolean().default(false), + 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'), }), }); @@ -110,11 +110,11 @@ const photoBlogPostsCollection = defineCollection({ schema: z.object({ title: z.string(), description: z.string(), - date: z.date(), - coverImage: z.string(), + date: z.date().optional(), + coverImage: z.string().optional(), tags: z.array(z.string()).optional(), - featured: z.boolean().default(false), - draft: z.boolean().default(false), + featured: z.boolean().optional(), + draft: z.boolean().optional(), lang: z.enum(['fr', 'en', 'ar']).default('fr'), }), }); diff --git a/src/content/experiences/araymond.ar.md b/src/content/experiences/araymond.ar.md index 1208f13..5e75861 100644 --- a/src/content/experiences/araymond.ar.md +++ b/src/content/experiences/araymond.ar.md @@ -1,14 +1,6 @@ --- role: "مهندس معماري للواجهات" -company: "ARaymond" -companyUrl: "https://www.araymond.com/" -logo: "araymond.png" location: "غرونوبل" -startDate: "2022-01" -endDate: "2023-01" -technologies: ["TypeScript", "React.js", "Redux", "Nx", "Vite"] -type: "freelance" -featured: true lang: "ar" --- diff --git a/src/content/experiences/araymond.en.md b/src/content/experiences/araymond.en.md index 371a4b5..5d630cb 100644 --- a/src/content/experiences/araymond.en.md +++ b/src/content/experiences/araymond.en.md @@ -1,14 +1,5 @@ --- role: "Frontend Architect" -company: "ARaymond" -companyUrl: "https://www.araymond.com/" -logo: "araymond.png" -location: "Grenoble" -startDate: "2022-01" -endDate: "2023-01" -technologies: ["TypeScript", "React.js", "Redux", "Nx", "Vite"] -type: "freelance" -featured: true lang: "en" --- diff --git a/src/content/experiences/champollion.ar.md b/src/content/experiences/champollion.ar.md index 3ab5e8f..d55a7f9 100644 --- a/src/content/experiences/champollion.ar.md +++ b/src/content/experiences/champollion.ar.md @@ -1,13 +1,6 @@ --- role: "أستاذ هندسة البرمجيات" -company: "جامعة شامبوليون" -companyUrl: "https://www.univ-jfc.fr/" -logo: "champollion.png" location: "ألبي" -startDate: "2019-09" -technologies: ["TypeScript", "JavaScript", "Node.js", "TDD", "Clean Code"] -type: "teaching" -featured: true lang: "ar" --- diff --git a/src/content/experiences/champollion.en.md b/src/content/experiences/champollion.en.md index eee1e05..7d37105 100644 --- a/src/content/experiences/champollion.en.md +++ b/src/content/experiences/champollion.en.md @@ -1,13 +1,5 @@ --- role: "Software Engineering Professor" -company: "Université Champollion" -companyUrl: "https://www.univ-jfc.fr/" -logo: "champollion.png" -location: "Albi" -startDate: "2019-09" -technologies: ["TypeScript", "JavaScript", "Node.js", "TDD", "Clean Code"] -type: "teaching" -featured: true lang: "en" --- diff --git a/src/content/experiences/dismoi.ar.md b/src/content/experiences/dismoi.ar.md index 875a0b9..59fccd6 100644 --- a/src/content/experiences/dismoi.ar.md +++ b/src/content/experiences/dismoi.ar.md @@ -1,13 +1,6 @@ --- role: "حرفي برمجيات / مؤسس مشارك" -company: "DisMoi" -companyUrl: "https://github.com/dis-moi" -logo: "dismoi.png" location: "عن بُعد" -startDate: "2019-01" -endDate: "2021-06" -technologies: ["TypeScript", "React.js", "Redux", "Web Extension", "Node.js"] -type: "entrepreneurship" lang: "ar" --- diff --git a/src/content/experiences/dismoi.en.md b/src/content/experiences/dismoi.en.md index 891eb4d..4e397ae 100644 --- a/src/content/experiences/dismoi.en.md +++ b/src/content/experiences/dismoi.en.md @@ -1,13 +1,5 @@ --- role: "Software Craftsman / Cofounder" -company: "DisMoi" -companyUrl: "https://github.com/dis-moi" -logo: "dismoi.png" -location: "Remote" -startDate: "2019-01" -endDate: "2021-06" -technologies: ["TypeScript", "React.js", "Redux", "Web Extension", "Node.js"] -type: "entrepreneurship" lang: "en" --- diff --git a/src/content/experiences/esn81.ar.md b/src/content/experiences/esn81.ar.md index ed2ccf0..37299e2 100644 --- a/src/content/experiences/esn81.ar.md +++ b/src/content/experiences/esn81.ar.md @@ -1,12 +1,6 @@ --- role: "مدرّس تطوير" -company: "ESN 81" -companyUrl: "https://www.esn81.fr/" location: "كاستر" -startDate: "2020-09" -endDate: "2021-06" -technologies: ["JavaScript", "Node.js"] -type: "teaching" lang: "ar" --- diff --git a/src/content/experiences/esn81.en.md b/src/content/experiences/esn81.en.md index 91c4f41..4a67a0d 100644 --- a/src/content/experiences/esn81.en.md +++ b/src/content/experiences/esn81.en.md @@ -1,12 +1,5 @@ --- role: "Development Teacher" -company: "ESN 81" -companyUrl: "https://www.esn81.fr/" -location: "Castres" -startDate: "2020-09" -endDate: "2021-06" -technologies: ["JavaScript", "Node.js"] -type: "teaching" lang: "en" --- diff --git a/src/content/experiences/ethemis-junior.ar.md b/src/content/experiences/ethemis-junior.ar.md index f664dc8..dca8101 100644 --- a/src/content/experiences/ethemis-junior.ar.md +++ b/src/content/experiences/ethemis-junior.ar.md @@ -1,13 +1,6 @@ --- role: "مطوّر ويب" -company: "e-Themis" -companyUrl: "https://www.e-themis.com/" -logo: "ethemis.png" location: "فرساي" -startDate: "2002-06" -endDate: "2002-12" -technologies: ["PHP", "JavaScript", "HTML", "MySQL"] -type: "employment" lang: "ar" --- diff --git a/src/content/experiences/ethemis-junior.en.md b/src/content/experiences/ethemis-junior.en.md index f5054b4..629b429 100644 --- a/src/content/experiences/ethemis-junior.en.md +++ b/src/content/experiences/ethemis-junior.en.md @@ -1,13 +1,5 @@ --- role: "Web Developer" -company: "e-Themis" -companyUrl: "https://www.e-themis.com/" -logo: "ethemis.png" -location: "Versailles" -startDate: "2002-06" -endDate: "2002-12" -technologies: ["PHP", "JavaScript", "HTML", "MySQL"] -type: "employment" lang: "en" --- diff --git a/src/content/experiences/ethemis-senior.ar.md b/src/content/experiences/ethemis-senior.ar.md index da1bae5..b680fe5 100644 --- a/src/content/experiences/ethemis-senior.ar.md +++ b/src/content/experiences/ethemis-senior.ar.md @@ -1,13 +1,6 @@ --- role: "مهندس برمجيات أول" -company: "e-Themis" -companyUrl: "https://www.e-themis.com/" -logo: "ethemis.png" location: "فرساي" -startDate: "2011-01" -endDate: "2015-12" -technologies: ["PHP", "Symfony", "JavaScript", "MySQL", "Linux"] -type: "employment" lang: "ar" --- diff --git a/src/content/experiences/ethemis-senior.en.md b/src/content/experiences/ethemis-senior.en.md index baeb940..b8798ba 100644 --- a/src/content/experiences/ethemis-senior.en.md +++ b/src/content/experiences/ethemis-senior.en.md @@ -1,13 +1,5 @@ --- role: "Senior Software Engineer" -company: "e-Themis" -companyUrl: "https://www.e-themis.com/" -logo: "ethemis.png" -location: "Versailles" -startDate: "2011-01" -endDate: "2015-12" -technologies: ["PHP", "Symfony", "JavaScript", "MySQL", "Linux"] -type: "employment" lang: "en" --- diff --git a/src/content/experiences/freelance.ar.md b/src/content/experiences/freelance.ar.md index 3becfab..c293098 100644 --- a/src/content/experiences/freelance.ar.md +++ b/src/content/experiences/freelance.ar.md @@ -1,11 +1,6 @@ --- role: "مطوّر ويب وبرمجيات" -company: "مستقل" location: "إيل دو فرانس" -startDate: "2003-01" -endDate: "2006-12" -technologies: ["PHP", "JavaScript", "HTML", "MySQL", "Linux"] -type: "freelance" lang: "ar" --- diff --git a/src/content/experiences/freelance.en.md b/src/content/experiences/freelance.en.md index 1b9625b..153827f 100644 --- a/src/content/experiences/freelance.en.md +++ b/src/content/experiences/freelance.en.md @@ -1,11 +1,5 @@ --- role: "Web & Software Developer" -company: "Freelance" -location: "Île-de-France" -startDate: "2003-01" -endDate: "2006-12" -technologies: ["PHP", "JavaScript", "HTML", "MySQL", "Linux"] -type: "freelance" lang: "en" --- diff --git a/src/content/experiences/go-decision.ar.md b/src/content/experiences/go-decision.ar.md index 8d4fa75..897172a 100644 --- a/src/content/experiences/go-decision.ar.md +++ b/src/content/experiences/go-decision.ar.md @@ -1,12 +1,6 @@ --- role: "مدير تقني" -company: "GoBuild (Go-Decision)" -companyUrl: "https://www.gobuild.fr" location: "ليون" -startDate: "2020-06" -endDate: "2022-01" -technologies: ["TypeScript", "React.js", "Elixir", "PostgreSQL", "Docker"] -type: "employment" lang: "ar" --- diff --git a/src/content/experiences/go-decision.en.md b/src/content/experiences/go-decision.en.md index f5c9900..ad4549d 100644 --- a/src/content/experiences/go-decision.en.md +++ b/src/content/experiences/go-decision.en.md @@ -1,12 +1,5 @@ --- role: "CTO" -company: "GoBuild (Go-Decision)" -companyUrl: "https://www.gobuild.fr" -location: "Lyon" -startDate: "2020-06" -endDate: "2022-01" -technologies: ["TypeScript", "React.js", "Elixir", "PostgreSQL", "Docker"] -type: "employment" lang: "en" --- diff --git a/src/content/experiences/libeo.ar.md b/src/content/experiences/libeo.ar.md index 8434d8e..b26c3ec 100644 --- a/src/content/experiences/libeo.ar.md +++ b/src/content/experiences/libeo.ar.md @@ -1,13 +1,6 @@ --- role: "مهندس برمجيات fullstack" -company: "Libeo" -companyUrl: "https://www.libeo.io/" -logo: "libeo.png" location: "عن بُعد" -startDate: "2021-01" -endDate: "2021-06" -technologies: ["TypeScript", "React.js", "Node.js", "GraphQL"] -type: "freelance" lang: "ar" --- diff --git a/src/content/experiences/libeo.en.md b/src/content/experiences/libeo.en.md index 3559884..4b50182 100644 --- a/src/content/experiences/libeo.en.md +++ b/src/content/experiences/libeo.en.md @@ -1,13 +1,5 @@ --- role: "Fullstack Software Engineer" -company: "Libeo" -companyUrl: "https://www.libeo.io/" -logo: "libeo.png" -location: "Remote" -startDate: "2021-01" -endDate: "2021-06" -technologies: ["TypeScript", "React.js", "Node.js", "GraphQL"] -type: "freelance" lang: "en" --- diff --git a/src/content/experiences/obat.ar.md b/src/content/experiences/obat.ar.md index f832957..8b69be5 100644 --- a/src/content/experiences/obat.ar.md +++ b/src/content/experiences/obat.ar.md @@ -1,13 +1,6 @@ --- role: "مهندس برمجيات أول" -company: "Obat" -companyUrl: "https://www.obat.fr/" -logo: "obat.png" location: "عن بُعد" -startDate: "2023-02" -endDate: "2024-01" -technologies: ["TypeScript", "React.js", "Node.js", "NestJS", "PostgreSQL"] -type: "freelance" lang: "ar" --- diff --git a/src/content/experiences/obat.en.md b/src/content/experiences/obat.en.md index 969ee6f..aa5a0b1 100644 --- a/src/content/experiences/obat.en.md +++ b/src/content/experiences/obat.en.md @@ -1,13 +1,5 @@ --- role: "Senior Software Engineer" -company: "Obat" -companyUrl: "https://www.obat.fr/" -logo: "obat.png" -location: "Remote" -startDate: "2023-02" -endDate: "2024-01" -technologies: ["TypeScript", "React.js", "Node.js", "NestJS", "PostgreSQL"] -type: "freelance" lang: "en" --- diff --git a/src/content/experiences/team-logics.ar.md b/src/content/experiences/team-logics.ar.md index c0f8d3f..fdc6561 100644 --- a/src/content/experiences/team-logics.ar.md +++ b/src/content/experiences/team-logics.ar.md @@ -1,12 +1,6 @@ --- role: "مؤسّس ومدير عام" -company: "Team Logics" location: "فرساي" -startDate: "2007-01" -endDate: "2011-12" -technologies: ["PHP", "CakePHP", "JavaScript", "MySQL", "Linux"] -type: "entrepreneurship" -featured: true lang: "ar" --- diff --git a/src/content/experiences/team-logics.en.md b/src/content/experiences/team-logics.en.md index f9b17b6..c2a68a5 100644 --- a/src/content/experiences/team-logics.en.md +++ b/src/content/experiences/team-logics.en.md @@ -1,12 +1,5 @@ --- role: "Founder & CEO" -company: "Team Logics" -location: "Versailles" -startDate: "2007-01" -endDate: "2011-12" -technologies: ["PHP", "CakePHP", "JavaScript", "MySQL", "Linux"] -type: "entrepreneurship" -featured: true lang: "en" --- diff --git a/src/content/experiences/urssaf.ar.md b/src/content/experiences/urssaf.ar.md index 968b958..93f91ac 100644 --- a/src/content/experiences/urssaf.ar.md +++ b/src/content/experiences/urssaf.ar.md @@ -1,13 +1,6 @@ --- role: "مطوّر رئيسي" -company: "Urssaf Caisse nationale" -companyUrl: "https://www.urssaf.fr/" -logo: "urssaf.png" location: "عن بُعد / باريس" -startDate: "2024-02" -technologies: ["TypeScript", "React.js", "Publicodes", "Node.js", "GitHub"] -type: "freelance" -featured: true lang: "ar" --- diff --git a/src/content/experiences/urssaf.en.md b/src/content/experiences/urssaf.en.md index e4ecf6b..d9b3dee 100644 --- a/src/content/experiences/urssaf.en.md +++ b/src/content/experiences/urssaf.en.md @@ -1,13 +1,5 @@ --- role: "Lead Developer" -company: "Urssaf Caisse nationale" -companyUrl: "https://www.urssaf.fr/" -logo: "urssaf.png" -location: "Remote / Paris" -startDate: "2024-02" -technologies: ["TypeScript", "React.js", "Publicodes", "Node.js", "GitHub"] -type: "freelance" -featured: true lang: "en" --- diff --git a/src/content/experiences/veepee.ar.md b/src/content/experiences/veepee.ar.md index ab91ffc..3efa4d8 100644 --- a/src/content/experiences/veepee.ar.md +++ b/src/content/experiences/veepee.ar.md @@ -1,14 +1,6 @@ --- role: "مطوّر رئيسي ← قائد تقني" -company: "Veepee" -companyUrl: "https://www.veepee.com/" -logo: "veepee.png" location: "باريس" -startDate: "2016-02" -endDate: "2019-06" -technologies: ["TypeScript", "JavaScript", "React.js", "Redux", "redux-saga", "RxJS", "Node.js", "Webpack"] -type: "freelance" -featured: true lang: "ar" --- diff --git a/src/content/experiences/veepee.en.md b/src/content/experiences/veepee.en.md index a4c6874..b46fbd2 100644 --- a/src/content/experiences/veepee.en.md +++ b/src/content/experiences/veepee.en.md @@ -1,14 +1,5 @@ --- role: "Lead Developer → Tech Lead" -company: "Veepee" -companyUrl: "https://www.veepee.com/" -logo: "veepee.png" -location: "Paris" -startDate: "2016-02" -endDate: "2019-06" -technologies: ["TypeScript", "JavaScript", "React.js", "Redux", "redux-saga", "RxJS", "Node.js", "Webpack"] -type: "freelance" -featured: true lang: "en" --- diff --git a/src/content/photoBlogPosts/2011/eroll.ar.md b/src/content/photoBlogPosts/2011/eroll.ar.md index 97bc904..5c220f7 100644 --- a/src/content/photoBlogPosts/2011/eroll.ar.md +++ b/src/content/photoBlogPosts/2011/eroll.ar.md @@ -1,11 +1,6 @@ --- title: "تصوير إيرول" description: "أراد إيرول صورًا له لإعداد كتاب أعمال. عملنا طوال اليوم لتنويع الأجواء..." -date: 2011-10-02 -coverImage: "18-Eroll-Shooting-1-19.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2011/eroll.en.md b/src/content/photoBlogPosts/2011/eroll.en.md index 5826d40..ff9bf7a 100644 --- a/src/content/photoBlogPosts/2011/eroll.en.md +++ b/src/content/photoBlogPosts/2011/eroll.en.md @@ -1,11 +1,6 @@ --- title: "Shooting Eroll" description: "Eroll wanted some photos of him in order to have a modeling book. We worked all day in order to have some ambiance variations..." -date: 2011-10-02 -coverImage: "18-Eroll-Shooting-1-19.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2011/inox-park-2011.ar.md b/src/content/photoBlogPosts/2011/inox-park-2011.ar.md index 48d1535..fc2b47e 100644 --- a/src/content/photoBlogPosts/2011/inox-park-2011.ar.md +++ b/src/content/photoBlogPosts/2011/inox-park-2011.ar.md @@ -1,11 +1,6 @@ --- title: "Inox Park Paris 2011" description: "بعد نجاحه في 2010، يعود مهرجان Inox Park Paris إلى جزيرة شاتو في نسخته الثانية. ثلاث مسارح، 15 دي جي، 12 ساعة من الحفل في الهواء الطلق: Tiësto، Joachim Garraud، Sven Väth، Steve Aoki..." -date: 2011-09-10 -coverImage: "01-Inox-Park-Paris-Chatou-2011.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2011/inox-park-2011.en.md b/src/content/photoBlogPosts/2011/inox-park-2011.en.md index 1b26f7b..cc2eacd 100644 --- a/src/content/photoBlogPosts/2011/inox-park-2011.en.md +++ b/src/content/photoBlogPosts/2011/inox-park-2011.en.md @@ -1,11 +1,6 @@ --- title: "Inox Park Paris 2011" description: "After its 2010 success, the Inox Park Paris Electro Festival is back to the island of Chatou for its second edition. Three stages, 15 DJs, 12 hours of outdoor party: Tiësto, Joachim Garraud, Sven Väth, Steve Aoki..." -date: 2011-09-10 -coverImage: "01-Inox-Park-Paris-Chatou-2011.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2012/schoolbag-operation-2012.ar.md b/src/content/photoBlogPosts/2012/schoolbag-operation-2012.ar.md index 4c1c11a..ec1be81 100644 --- a/src/content/photoBlogPosts/2012/schoolbag-operation-2012.ar.md +++ b/src/content/photoBlogPosts/2012/schoolbag-operation-2012.ar.md @@ -1,11 +1,6 @@ --- title: "عملية المحفظة 2012" description: "توزيع محافظ مدرسية مجانية في مدارس محرومة من طرف جمعية محلية (JCI)، طنجة، المغرب." -date: 2012-09-30 -coverImage: "35-Moroccan-Schoolgirls.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2012/schoolbag-operation-2012.en.md b/src/content/photoBlogPosts/2012/schoolbag-operation-2012.en.md index d3c46c9..daa01b7 100644 --- a/src/content/photoBlogPosts/2012/schoolbag-operation-2012.en.md +++ b/src/content/photoBlogPosts/2012/schoolbag-operation-2012.en.md @@ -1,11 +1,6 @@ --- title: "Schoolbag Operation 2012" description: "During a distribution of free schoolbags in poor schools by a local association (JCI), Tangier, Morocco." -date: 2012-09-30 -coverImage: "35-Moroccan-Schoolgirls.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2012/tangier-walk.ar.md b/src/content/photoBlogPosts/2012/tangier-walk.ar.md index 2a3de64..d9fcf57 100644 --- a/src/content/photoBlogPosts/2012/tangier-walk.ar.md +++ b/src/content/photoBlogPosts/2012/tangier-walk.ar.md @@ -1,11 +1,6 @@ --- title: "جولة في طنجة" description: "جولة فوتوغرافية في شوارع طنجة." -date: 2012-05-26 -coverImage: "01-Observer-le-changement.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2012/tangier-walk.en.md b/src/content/photoBlogPosts/2012/tangier-walk.en.md index f183a2b..a61b468 100644 --- a/src/content/photoBlogPosts/2012/tangier-walk.en.md +++ b/src/content/photoBlogPosts/2012/tangier-walk.en.md @@ -1,11 +1,6 @@ --- title: "Tangier Walk" description: "A photographic walk through the streets of Tangier." -date: 2012-05-26 -coverImage: "01-Observer-le-changement.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2013/helsinki.ar.md b/src/content/photoBlogPosts/2013/helsinki.ar.md index 838469a..6871755 100644 --- a/src/content/photoBlogPosts/2013/helsinki.ar.md +++ b/src/content/photoBlogPosts/2013/helsinki.ar.md @@ -1,11 +1,6 @@ --- title: "هلسنكي" description: "اختفى الثلج من هلسنكي وسرعان ما أفسح المجال للربيع..." -date: 2013-05-15 -coverImage: "01-Library-of-University-of-Helsinki.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2013/helsinki.en.md b/src/content/photoBlogPosts/2013/helsinki.en.md index 4eae523..1468e88 100644 --- a/src/content/photoBlogPosts/2013/helsinki.en.md +++ b/src/content/photoBlogPosts/2013/helsinki.en.md @@ -1,11 +1,6 @@ --- title: "Helsinki" description: "The snow has disappeared from Helsinki and quickly gave way to spring..." -date: 2013-05-15 -coverImage: "01-Library-of-University-of-Helsinki.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2013/ifrane-hike.ar.md b/src/content/photoBlogPosts/2013/ifrane-hike.ar.md index 9654570..b278522 100644 --- a/src/content/photoBlogPosts/2013/ifrane-hike.ar.md +++ b/src/content/photoBlogPosts/2013/ifrane-hike.ar.md @@ -1,11 +1,6 @@ --- title: "نزهة في إفران" description: "نزهة شتوية في جبال الأطلس المتوسط." -date: 2013-01-13 -coverImage: "03-3.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2013/ifrane-hike.en.md b/src/content/photoBlogPosts/2013/ifrane-hike.en.md index a98ec48..08ef362 100644 --- a/src/content/photoBlogPosts/2013/ifrane-hike.en.md +++ b/src/content/photoBlogPosts/2013/ifrane-hike.en.md @@ -1,11 +1,6 @@ --- title: "Ifrane Hike" description: "Winter hike in the Middle Atlas mountains." -date: 2013-01-13 -coverImage: "03-3.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2014/london-calling.ar.md b/src/content/photoBlogPosts/2014/london-calling.ar.md index fa28e16..6754911 100644 --- a/src/content/photoBlogPosts/2014/london-calling.ar.md +++ b/src/content/photoBlogPosts/2014/london-calling.ar.md @@ -1,11 +1,6 @@ --- title: "London Calling" description: "عطلة نهاية أسبوع فوتوغرافية في لندن." -date: 2014-07-15 -coverImage: "01-The-sky-inside.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2014/london-calling.en.md b/src/content/photoBlogPosts/2014/london-calling.en.md index 2bb25f0..f4af90f 100644 --- a/src/content/photoBlogPosts/2014/london-calling.en.md +++ b/src/content/photoBlogPosts/2014/london-calling.en.md @@ -1,11 +1,6 @@ --- title: "London Calling" description: "A photographic weekend in London." -date: 2014-07-15 -coverImage: "01-The-sky-inside.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2014/sequanian-sunday.ar.md b/src/content/photoBlogPosts/2014/sequanian-sunday.ar.md index 6b468c3..48b8cab 100644 --- a/src/content/photoBlogPosts/2014/sequanian-sunday.ar.md +++ b/src/content/photoBlogPosts/2014/sequanian-sunday.ar.md @@ -1,11 +1,6 @@ --- title: "أحد سيكواني" description: "نزهة يوم أحد على ضفاف نهر السين." -date: 2014-05-18 -coverImage: "04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2014/sequanian-sunday.en.md b/src/content/photoBlogPosts/2014/sequanian-sunday.en.md index a2255c3..e364b97 100644 --- a/src/content/photoBlogPosts/2014/sequanian-sunday.en.md +++ b/src/content/photoBlogPosts/2014/sequanian-sunday.en.md @@ -1,11 +1,6 @@ --- title: "Sequanian Sunday" description: "A sunday walk near the Seine." -date: 2014-05-18 -coverImage: "04-La-Defense-seen-from-Pont-de-Suresnes-2.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2014/wandering-tangier-medina.ar.md b/src/content/photoBlogPosts/2014/wandering-tangier-medina.ar.md index c14ca9e..4ada44f 100644 --- a/src/content/photoBlogPosts/2014/wandering-tangier-medina.ar.md +++ b/src/content/photoBlogPosts/2014/wandering-tangier-medina.ar.md @@ -1,11 +1,6 @@ --- title: "تجوال في مدينة طنجة القديمة" description: "أثناء التجوال في أزقة طنجة القديمة، صادفت ساعاتيًا، ونجّارًا، وقمرًا عملاقًا..." -date: 2014-08-10 -coverImage: "01-The-watchmaker.jpg" -tags: [] -featured: true -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2014/wandering-tangier-medina.en.md b/src/content/photoBlogPosts/2014/wandering-tangier-medina.en.md index c71ad87..2ac318b 100644 --- a/src/content/photoBlogPosts/2014/wandering-tangier-medina.en.md +++ b/src/content/photoBlogPosts/2014/wandering-tangier-medina.en.md @@ -1,11 +1,6 @@ --- title: "Wandering Tangier Medina" description: "Walking in the streets of the old Tangier, met a watchmaker, a carpenter and a super-moon..." -date: 2014-08-10 -coverImage: "01-The-watchmaker.jpg" -tags: [] -featured: true -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2015/enigma.ar.md b/src/content/photoBlogPosts/2015/enigma.ar.md index ccc9ccf..e560576 100644 --- a/src/content/photoBlogPosts/2015/enigma.ar.md +++ b/src/content/photoBlogPosts/2015/enigma.ar.md @@ -4,11 +4,6 @@ description: | إنيغما كانت مسابقة بحث عن الكنز بين المدارس نظّمها المركز العالي للدراسات CESIM يوم السبت، تحت عنوان «البحث عن كنز ابن بطوطة المنسي». 4 فرق طنجاوية دُعيت لتمثيل مؤسساتها. تغطية. -date: 2015-04-25 -coverImage: "01-Enigma-v1.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2015/enigma.en.md b/src/content/photoBlogPosts/2015/enigma.en.md index 143522d..16a5790 100644 --- a/src/content/photoBlogPosts/2015/enigma.en.md +++ b/src/content/photoBlogPosts/2015/enigma.en.md @@ -4,11 +4,6 @@ description: | Enigma was an inter-school treasure hunt organized this saturday by the CESIM, on the theme "In search of forgotten treasure of Ibn Battuta". 4 Tangier teams were invited to represent their respective institutions. Recap. -date: 2015-04-25 -coverImage: "01-Enigma-v1.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2015/field-of-stones.ar.md b/src/content/photoBlogPosts/2015/field-of-stones.ar.md index 8c2e424..b3097bc 100644 --- a/src/content/photoBlogPosts/2015/field-of-stones.ar.md +++ b/src/content/photoBlogPosts/2015/field-of-stones.ar.md @@ -1,11 +1,6 @@ --- title: "Field of Stones" description: "كواليس تصوير غلاف ألبوم ماركو وولتر. أراد أن تُلتقط الصورة في سينماتيك طنجة. لا وقت، لا إضاءة، لكن لا خيار. حاولنا تقديم أفضل ما لدينا..." -date: 2015-04-02 -coverImage: "01-Marco-Wolter-Field-of-Stones-2.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2015/field-of-stones.en.md b/src/content/photoBlogPosts/2015/field-of-stones.en.md index 9a43434..9668da1 100644 --- a/src/content/photoBlogPosts/2015/field-of-stones.en.md +++ b/src/content/photoBlogPosts/2015/field-of-stones.en.md @@ -1,11 +1,6 @@ --- title: "Field of Stones" description: "Making of the album cover for Marco Wolter. He wanted it to be shot in the Cinémathèque of Tangier. We had no time, no light, but no choice. We tried to make the best of it..." -date: 2015-04-02 -coverImage: "01-Marco-Wolter-Field-of-Stones-2.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2015/no-wind-las-cuevas.ar.md b/src/content/photoBlogPosts/2015/no-wind-las-cuevas.ar.md index 84fa318..0d6a1c0 100644 --- a/src/content/photoBlogPosts/2015/no-wind-las-cuevas.ar.md +++ b/src/content/photoBlogPosts/2015/no-wind-las-cuevas.ar.md @@ -1,11 +1,6 @@ --- title: "لا رياح في لاس كويفاس" description: "كان من المفترض أن يكون يومًا مثاليًا لتطيير طائرتنا الورقية: مشمس وعاصف. مشمس كان، لكن الرياح لم تأتِ أبدًا." -date: 2015-01-10 -coverImage: "13-No-wind-at-Las-Cuevas.jpg" -tags: [] -featured: false -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2015/no-wind-las-cuevas.en.md b/src/content/photoBlogPosts/2015/no-wind-las-cuevas.en.md index 370c88f..171b396 100644 --- a/src/content/photoBlogPosts/2015/no-wind-las-cuevas.en.md +++ b/src/content/photoBlogPosts/2015/no-wind-las-cuevas.en.md @@ -1,11 +1,6 @@ --- title: "No Wind at Las Cuevas" description: "It was supposed to be a perfect day for flying our kite: sunny and windy. Sunny it was, but the wind never came." -date: 2015-01-10 -coverImage: "13-No-wind-at-Las-Cuevas.jpg" -tags: [] -featured: false -draft: false lang: en --- diff --git a/src/content/photoBlogPosts/2015/wedding-aurore-thomas.ar.md b/src/content/photoBlogPosts/2015/wedding-aurore-thomas.ar.md index fa00e71..becdf7e 100644 --- a/src/content/photoBlogPosts/2015/wedding-aurore-thomas.ar.md +++ b/src/content/photoBlogPosts/2015/wedding-aurore-thomas.ar.md @@ -1,11 +1,6 @@ --- title: "زفاف أورور وتوما" description: "كان لي شرف ومتعة أن أكون شاهد توما في زفافه الجميل مع أورور. ليس سهلًا التصوير في نفس الوقت، لكن كل الصور مليئة بالحب." -date: 2015-09-26 -coverImage: "10-Mariage-Aurore-Thomas-10.jpg" -tags: [] -featured: true -draft: false lang: ar --- diff --git a/src/content/photoBlogPosts/2015/wedding-aurore-thomas.en.md b/src/content/photoBlogPosts/2015/wedding-aurore-thomas.en.md index 5300588..4dc4566 100644 --- a/src/content/photoBlogPosts/2015/wedding-aurore-thomas.en.md +++ b/src/content/photoBlogPosts/2015/wedding-aurore-thomas.en.md @@ -1,11 +1,6 @@ --- title: "Wedding Aurore & Thomas" description: "I had the honor and pleasure to be Thomas' best man for his beautiful wedding with Aurore. Not easy to shoot pictures though, but all of them are filled with love." -date: 2015-09-26 -coverImage: "10-Mariage-Aurore-Thomas-10.jpg" -tags: [] -featured: true -draft: false lang: en --- diff --git a/src/content/photoCategories/cultures.ar.json b/src/content/photoCategories/cultures.ar.json index d3b08a1..5d43361 100644 --- a/src/content/photoCategories/cultures.ar.json +++ b/src/content/photoCategories/cultures.ar.json @@ -1,6 +1,5 @@ { "title": "ثقافات وتقاليد", "subtitle": "ثراء التقاليد الإنسانية", - "order": 4, "lang": "ar" } diff --git a/src/content/photoCategories/cultures.en.json b/src/content/photoCategories/cultures.en.json index 3b41b45..25296fa 100644 --- a/src/content/photoCategories/cultures.en.json +++ b/src/content/photoCategories/cultures.en.json @@ -1,6 +1,5 @@ { "title": "Cultures & Traditions", "subtitle": "Richness of human traditions", - "order": 4, "lang": "en" } diff --git a/src/content/photoCategories/engines.ar.json b/src/content/photoCategories/engines.ar.json index eca0907..723c875 100644 --- a/src/content/photoCategories/engines.ar.json +++ b/src/content/photoCategories/engines.ar.json @@ -1,6 +1,5 @@ { "title": "محرّكات", "subtitle": "ميكانيكا وقوة في حركة", - "order": 7, "lang": "ar" } diff --git a/src/content/photoCategories/engines.en.json b/src/content/photoCategories/engines.en.json index 8120dfb..6fab5f2 100644 --- a/src/content/photoCategories/engines.en.json +++ b/src/content/photoCategories/engines.en.json @@ -1,6 +1,5 @@ { "title": "Engines", "subtitle": "Mechanics and power in motion", - "order": 7, "lang": "en" } diff --git a/src/content/photoCategories/everyday.ar.json b/src/content/photoCategories/everyday.ar.json index 2f6adfb..c60bcac 100644 --- a/src/content/photoCategories/everyday.ar.json +++ b/src/content/photoCategories/everyday.ar.json @@ -1,6 +1,5 @@ { "title": "يوميات", "subtitle": "لحظات من الحياة اليومية", - "order": 8, "lang": "ar" } diff --git a/src/content/photoCategories/everyday.en.json b/src/content/photoCategories/everyday.en.json index 2d54833..431d663 100644 --- a/src/content/photoCategories/everyday.en.json +++ b/src/content/photoCategories/everyday.en.json @@ -1,6 +1,5 @@ { "title": "Everyday Life", "subtitle": "Moments of everyday life", - "order": 8, "lang": "en" } diff --git a/src/content/photoCategories/music.ar.json b/src/content/photoCategories/music.ar.json index 9440905..a983d3d 100644 --- a/src/content/photoCategories/music.ar.json +++ b/src/content/photoCategories/music.ar.json @@ -1,6 +1,5 @@ { "title": "موسيقى واحتفالات", "subtitle": "نغمات وأغانٍ واهتزازات تحتفي بلحظات حياتنا الكبيرة والصغيرة", - "order": 5, "lang": "ar" } diff --git a/src/content/photoCategories/music.en.json b/src/content/photoCategories/music.en.json index ff41e49..6c2cb13 100644 --- a/src/content/photoCategories/music.en.json +++ b/src/content/photoCategories/music.en.json @@ -1,6 +1,5 @@ { "title": "Music & Celebrations", "subtitle": "Notes, songs and vibrations celebrating life's big and small moments", - "order": 5, "lang": "en" } diff --git a/src/content/photoCategories/nature.ar.json b/src/content/photoCategories/nature.ar.json index b172734..4dc1aa4 100644 --- a/src/content/photoCategories/nature.ar.json +++ b/src/content/photoCategories/nature.ar.json @@ -1,6 +1,5 @@ { "title": "طبيعة", "subtitle": "سحر العالم الطبيعي", - "order": 3, "lang": "ar" } diff --git a/src/content/photoCategories/nature.en.json b/src/content/photoCategories/nature.en.json index 3977f30..046548b 100644 --- a/src/content/photoCategories/nature.en.json +++ b/src/content/photoCategories/nature.en.json @@ -1,6 +1,5 @@ { "title": "Nature", "subtitle": "The magic of the natural world", - "order": 3, "lang": "en" } diff --git a/src/content/photoCategories/places.ar.json b/src/content/photoCategories/places.ar.json index e9c12c1..9a5ded8 100644 --- a/src/content/photoCategories/places.ar.json +++ b/src/content/photoCategories/places.ar.json @@ -1,6 +1,5 @@ { "title": "مناظر طبيعية", "subtitle": "جمال المناظر والأماكن", - "order": 2, "lang": "ar" } diff --git a/src/content/photoCategories/places.en.json b/src/content/photoCategories/places.en.json index 0726251..1312105 100644 --- a/src/content/photoCategories/places.en.json +++ b/src/content/photoCategories/places.en.json @@ -1,6 +1,5 @@ { "title": "Landscapes", "subtitle": "Beauty of landscapes and places", - "order": 2, "lang": "en" } diff --git a/src/content/photoCategories/portraits.ar.json b/src/content/photoCategories/portraits.ar.json index 52216d0..4f21ec5 100644 --- a/src/content/photoCategories/portraits.ar.json +++ b/src/content/photoCategories/portraits.ar.json @@ -1,6 +1,5 @@ { "title": "بورتريهات", "subtitle": "تعابير ومشاعر ملتقطة", - "order": 1, "lang": "ar" } diff --git a/src/content/photoCategories/portraits.en.json b/src/content/photoCategories/portraits.en.json index e153033..e030375 100644 --- a/src/content/photoCategories/portraits.en.json +++ b/src/content/photoCategories/portraits.en.json @@ -1,6 +1,5 @@ { "title": "Portraits", "subtitle": "Captured expressions and emotions", - "order": 1, "lang": "en" } diff --git a/src/content/photoCategories/sports.ar.json b/src/content/photoCategories/sports.ar.json index 7f787b5..3c9e037 100644 --- a/src/content/photoCategories/sports.ar.json +++ b/src/content/photoCategories/sports.ar.json @@ -1,6 +1,5 @@ { "title": "رياضة", "subtitle": "حركة وجهد وتجاوز للذات", - "order": 6, "lang": "ar" } diff --git a/src/content/photoCategories/sports.en.json b/src/content/photoCategories/sports.en.json index 89a2c0c..9c18344 100644 --- a/src/content/photoCategories/sports.en.json +++ b/src/content/photoCategories/sports.en.json @@ -1,6 +1,5 @@ { "title": "Sports", "subtitle": "Movement, effort and pushing limits", - "order": 6, "lang": "en" } diff --git a/src/content/projects/debats.ar.md b/src/content/projects/debats.ar.md index 4afdd23..2d9546f 100644 --- a/src/content/projects/debats.ar.md +++ b/src/content/projects/debats.ar.md @@ -1,11 +1,6 @@ --- title: "Débats.co" description: "منصّة تعاونية لتلخيص النقاشات المجتمعية. ويكيبيديا المواقف." -date: 2015-01-01 -category: "dev" -technologies: ["Next.js", "React", "TypeScript", "Supabase", "PostgreSQL", "Effect TS"] -url: "https://debats.co" -featured: true lang: "ar" --- diff --git a/src/content/projects/debats.en.md b/src/content/projects/debats.en.md index f67579a..7329583 100644 --- a/src/content/projects/debats.en.md +++ b/src/content/projects/debats.en.md @@ -1,11 +1,6 @@ --- title: "Débats.co" description: "Collaborative platform for synthesizing public debates. The Wikipedia of public stances." -date: 2015-01-01 -category: "dev" -technologies: ["Next.js", "React", "TypeScript", "Supabase", "PostgreSQL", "Effect TS"] -url: "https://debats.co" -featured: true lang: "en" --- diff --git a/src/content/projects/dismoi.ar.md b/src/content/projects/dismoi.ar.md index ad93cc5..2726e16 100644 --- a/src/content/projects/dismoi.ar.md +++ b/src/content/projects/dismoi.ar.md @@ -1,12 +1,6 @@ --- title: "DisMoi" description: "إضافة متصفّح في مجال التكنولوجيا المدنية تضيف معلومات سياقية على أي صفحة ويب." -date: 2019-01-01 -category: "dev" -technologies: ["TypeScript", "React.js", "Redux", "Web Extension", "PHP", "Symfony"] -url: "https://www.dismoi.io/" -github: "https://github.com/dis-moi" -featured: true lang: "ar" --- diff --git a/src/content/projects/dismoi.en.md b/src/content/projects/dismoi.en.md index d4e2daf..32a6a1c 100644 --- a/src/content/projects/dismoi.en.md +++ b/src/content/projects/dismoi.en.md @@ -1,12 +1,6 @@ --- title: "DisMoi" description: "Civic tech browser extension that adds contextual information to any web page." -date: 2019-01-01 -category: "dev" -technologies: ["TypeScript", "React.js", "Redux", "Web Extension", "PHP", "Symfony"] -url: "https://www.dismoi.io/" -github: "https://github.com/dis-moi" -featured: true lang: "en" --- diff --git a/src/content/projects/icu.ar.md b/src/content/projects/icu.ar.md index 20ab084..c83d261 100644 --- a/src/content/projects/icu.ar.md +++ b/src/content/projects/icu.ar.md @@ -1,9 +1,6 @@ --- title: "ICU" description: "منصة مشاركة صور عبر الإنترنت، مشروع شخصي تاريخي." -date: 2005-01-01 -category: "dev" -technologies: ["PHP", "JavaScript", "MySQL"] lang: "ar" --- diff --git a/src/content/projects/icu.en.md b/src/content/projects/icu.en.md index f34f02b..73e6a82 100644 --- a/src/content/projects/icu.en.md +++ b/src/content/projects/icu.en.md @@ -1,9 +1,6 @@ --- title: "ICU" description: "Online photo sharing platform, a historical personal project." -date: 2005-01-01 -category: "dev" -technologies: ["PHP", "JavaScript", "MySQL"] lang: "en" --- diff --git a/src/content/projects/mon-entreprise.ar.md b/src/content/projects/mon-entreprise.ar.md index f9abb24..69166a0 100644 --- a/src/content/projects/mon-entreprise.ar.md +++ b/src/content/projects/mon-entreprise.ar.md @@ -1,12 +1,6 @@ --- title: "mon-entreprise.urssaf.fr" description: "المساعد الرسمي لروّاد الأعمال في فرنسا. أكثر من 20 محاكيًا اجتماعيًا-ضريبيًا، مليون مستخدم شهريًا." -date: 2024-01-01 -category: "dev" -technologies: ["TypeScript", "React.js", "Publicodes", "Redux", "Node.js"] -url: "https://mon-entreprise.urssaf.fr/" -github: "https://github.com/betagouv/mon-entreprise" -featured: false lang: "ar" --- diff --git a/src/content/projects/mon-entreprise.en.md b/src/content/projects/mon-entreprise.en.md index 1e625e6..2aa5109 100644 --- a/src/content/projects/mon-entreprise.en.md +++ b/src/content/projects/mon-entreprise.en.md @@ -1,12 +1,6 @@ --- title: "mon-entreprise.urssaf.fr" description: "The official assistant for entrepreneurs in France. Over 20 socio-fiscal simulators, one million users per month." -date: 2024-01-01 -category: "dev" -technologies: ["TypeScript", "React.js", "Publicodes", "Redux", "Node.js"] -url: "https://mon-entreprise.urssaf.fr/" -github: "https://github.com/betagouv/mon-entreprise" -featured: false lang: "en" --- diff --git a/src/content/projects/ngine.ar.md b/src/content/projects/ngine.ar.md index 8555ccb..6d650a8 100644 --- a/src/content/projects/ngine.ar.md +++ b/src/content/projects/ngine.ar.md @@ -1,9 +1,6 @@ --- title: "N.Gine" description: "نظام إدارة محتوى خاص بُني من الصفر، استُخدم للعديد من مشاريع العملاء." -date: 2004-01-01 -category: "dev" -technologies: ["PHP", "JavaScript", "MySQL"] lang: "ar" --- diff --git a/src/content/projects/ngine.en.md b/src/content/projects/ngine.en.md index 305a610..7121a21 100644 --- a/src/content/projects/ngine.en.md +++ b/src/content/projects/ngine.en.md @@ -1,9 +1,6 @@ --- title: "N.Gine" description: "Proprietary CMS built from scratch, used for many client projects." -date: 2004-01-01 -category: "dev" -technologies: ["PHP", "JavaScript", "MySQL"] lang: "en" --- diff --git a/src/pages/ar/index.astro b/src/pages/ar/index.astro index d571607..1b933d2 100644 --- a/src/pages/ar/index.astro +++ b/src/pages/ar/index.astro @@ -1,16 +1,17 @@ --- import { Image } from "astro:assets"; import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../utils/content-i18n"; import Layout from "../../layouts/main.astro"; import jalilPhoto from "../../assets/images/jalil-2.jpg"; -const currentMission = (await getCollection("experiences")) - .filter((e) => e.data.lang === "ar" && !e.data.draft && !e.data.endDate) +const currentMission = (await getLocalizedCollection("experiences", "ar")) + .filter((e) => !e.data.draft && !e.data.endDate) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1))[0]; -const featuredProjects = (await getCollection("projects")) - .filter((p) => p.data.lang === "ar" && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) +const featuredProjects = (await getLocalizedCollection("projects", "ar")) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()) .slice(0, 2); const recommendationCount = (await getCollection("recommendations")).length; diff --git a/src/pages/ar/برمجة/index.astro b/src/pages/ar/برمجة/index.astro index dc22f4e..f5ded2b 100644 --- a/src/pages/ar/برمجة/index.astro +++ b/src/pages/ar/برمجة/index.astro @@ -1,5 +1,6 @@ --- import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../../utils/content-i18n"; import { Image } from "astro:assets"; import Layout from "../../../layouts/main.astro"; import Link from "../../../components/Link.astro"; @@ -17,15 +18,15 @@ import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png"; const locale = "ar"; const projectsBasePath = getProjectsBasePath(locale); -const experiences = (await getCollection("experiences")) - .filter((e) => e.data.lang === locale && !e.data.draft) +const experiences = (await getLocalizedCollection("experiences", locale)) + .filter((e) => !e.data.draft) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1)); const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4); -const projects = (await getCollection("projects")) - .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()); const recommendations = (await getCollection("recommendations")) .filter((r) => r.data.featured) diff --git a/src/pages/ar/برمجة/مشاريع.astro b/src/pages/ar/برمجة/مشاريع.astro index b54f0e2..a6a17e5 100644 --- a/src/pages/ar/برمجة/مشاريع.astro +++ b/src/pages/ar/برمجة/مشاريع.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../../utils/content-i18n"; import Layout from "../../../layouts/main.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; @@ -7,11 +7,11 @@ 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") +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev") .sort((a, b) => { if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1; - return b.data.date.getTime() - a.data.date.getTime(); + return b.data.date!.getTime() - a.data.date!.getTime(); }); --- diff --git a/src/pages/ar/برمجة/مشاريع/[slug].astro b/src/pages/ar/برمجة/مشاريع/[slug].astro index 7dfd612..6444d56 100644 --- a/src/pages/ar/برمجة/مشاريع/[slug].astro +++ b/src/pages/ar/برمجة/مشاريع/[slug].astro @@ -1,11 +1,11 @@ --- import ProjectDetailContent from '../../../../components/code/ProjectDetailContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../../utils/content-i18n'; 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'); + const arProjects = (await getLocalizedCollection('projects', 'ar')) + .filter(p => !p.data.draft && p.data.category === 'dev'); return arProjects.map(project => ({ params: { slug: getProjectBaseSlug(project.id) }, props: { project }, diff --git a/src/pages/ar/تصوير/مدونة/[year]/[slug].astro b/src/pages/ar/تصوير/مدونة/[year]/[slug].astro index 3ffe1b1..1b39f3c 100644 --- a/src/pages/ar/تصوير/مدونة/[year]/[slug].astro +++ b/src/pages/ar/تصوير/مدونة/[year]/[slug].astro @@ -1,11 +1,10 @@ --- import PhotoBlogPostContent from '../../../../../components/photo/pages/PhotoBlogPostContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../../../utils/content-i18n'; import { getPostBaseSlug } from '../../../../../utils/i18n'; export async function getStaticPaths() { - const allPhotoBlogPosts = await getCollection('photoBlogPosts'); - const arPosts = allPhotoBlogPosts.filter(post => post.data.lang === 'ar'); + const arPosts = await getLocalizedCollection('photoBlogPosts', 'ar'); return arPosts.map(post => { const slug = getPostBaseSlug(post.id); return { diff --git a/src/pages/code/index.astro b/src/pages/code/index.astro index 8293e30..8189d25 100644 --- a/src/pages/code/index.astro +++ b/src/pages/code/index.astro @@ -1,5 +1,6 @@ --- import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../utils/content-i18n"; import { Image } from "astro:assets"; import Layout from "../../layouts/main.astro"; import Link from "../../components/Link.astro"; @@ -17,15 +18,15 @@ 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) +const experiences = (await getLocalizedCollection("experiences", locale)) + .filter((e) => !e.data.draft) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1)); const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4); -const projects = (await getCollection("projects")) - .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()); const recommendations = (await getCollection("recommendations")) .filter((r) => r.data.featured) diff --git a/src/pages/code/projets.astro b/src/pages/code/projets.astro index 4cccdd0..2e6648e 100644 --- a/src/pages/code/projets.astro +++ b/src/pages/code/projets.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../utils/content-i18n"; import Layout from "../../layouts/main.astro"; import ProjectCard from "../../components/code/ProjectCard.astro"; import { getProjectBaseSlug, getProjectsBasePath } from "../../utils/i18n"; @@ -7,11 +7,11 @@ 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") +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev") .sort((a, b) => { if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1; - return b.data.date.getTime() - a.data.date.getTime(); + return b.data.date!.getTime() - a.data.date!.getTime(); }); --- diff --git a/src/pages/code/projets/[slug].astro b/src/pages/code/projets/[slug].astro index 8880c0a..13574f4 100644 --- a/src/pages/code/projets/[slug].astro +++ b/src/pages/code/projets/[slug].astro @@ -1,11 +1,11 @@ --- import ProjectDetailContent from '../../../components/code/ProjectDetailContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../utils/content-i18n'; 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'); + const frProjects = (await getLocalizedCollection('projects', 'fr')) + .filter(p => !p.data.draft && p.data.category === 'dev'); return frProjects.map(project => ({ params: { slug: getProjectBaseSlug(project.id) }, props: { project }, diff --git a/src/pages/en/code/index.astro b/src/pages/en/code/index.astro index eb8f1e7..9da6fa7 100644 --- a/src/pages/en/code/index.astro +++ b/src/pages/en/code/index.astro @@ -1,5 +1,6 @@ --- import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../../utils/content-i18n"; import { Image } from "astro:assets"; import Layout from "../../../layouts/main.astro"; import Link from "../../../components/Link.astro"; @@ -17,15 +18,15 @@ import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png"; const locale = "en"; const projectsBasePath = getProjectsBasePath(locale); -const experiences = (await getCollection("experiences")) - .filter((e) => e.data.lang === locale && !e.data.draft) +const experiences = (await getLocalizedCollection("experiences", locale)) + .filter((e) => !e.data.draft) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1)); const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4); -const projects = (await getCollection("projects")) - .filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()); const recommendations = (await getCollection("recommendations")) .filter((r) => r.data.featured) diff --git a/src/pages/en/code/projects.astro b/src/pages/en/code/projects.astro index 0c292a5..834a556 100644 --- a/src/pages/en/code/projects.astro +++ b/src/pages/en/code/projects.astro @@ -1,5 +1,5 @@ --- -import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../../utils/content-i18n"; import Layout from "../../../layouts/main.astro"; import ProjectCard from "../../../components/code/ProjectCard.astro"; import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n"; @@ -7,11 +7,11 @@ 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") +const projects = (await getLocalizedCollection("projects", locale)) + .filter((p) => !p.data.draft && p.data.category === "dev") .sort((a, b) => { if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1; - return b.data.date.getTime() - a.data.date.getTime(); + return b.data.date!.getTime() - a.data.date!.getTime(); }); --- diff --git a/src/pages/en/code/projects/[slug].astro b/src/pages/en/code/projects/[slug].astro index b6c71ad..815e1fc 100644 --- a/src/pages/en/code/projects/[slug].astro +++ b/src/pages/en/code/projects/[slug].astro @@ -1,11 +1,11 @@ --- import ProjectDetailContent from '../../../../components/code/ProjectDetailContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../../utils/content-i18n'; 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'); + const enProjects = (await getLocalizedCollection('projects', 'en')) + .filter(p => !p.data.draft && p.data.category === 'dev'); return enProjects.map(project => ({ params: { slug: getProjectBaseSlug(project.id) }, props: { project }, diff --git a/src/pages/en/index.astro b/src/pages/en/index.astro index 5b1ac32..ffa4fb6 100644 --- a/src/pages/en/index.astro +++ b/src/pages/en/index.astro @@ -1,16 +1,17 @@ --- import { Image } from "astro:assets"; import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../../utils/content-i18n"; import Layout from "../../layouts/main.astro"; import jalilPhoto from "../../assets/images/jalil-2.jpg"; -const currentMission = (await getCollection("experiences")) - .filter((e) => e.data.lang === "en" && !e.data.draft && !e.data.endDate) +const currentMission = (await getLocalizedCollection("experiences", "en")) + .filter((e) => !e.data.draft && !e.data.endDate) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1))[0]; -const featuredProjects = (await getCollection("projects")) - .filter((p) => p.data.lang === "en" && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) +const featuredProjects = (await getLocalizedCollection("projects", "en")) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()) .slice(0, 2); const recommendationCount = (await getCollection("recommendations")).length; diff --git a/src/pages/en/photo/blog/[year]/[slug].astro b/src/pages/en/photo/blog/[year]/[slug].astro index 6bc3fee..8d94d3b 100644 --- a/src/pages/en/photo/blog/[year]/[slug].astro +++ b/src/pages/en/photo/blog/[year]/[slug].astro @@ -1,11 +1,10 @@ --- import PhotoBlogPostContent from '../../../../../components/photo/pages/PhotoBlogPostContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../../../utils/content-i18n'; import { getPostBaseSlug } from '../../../../../utils/i18n'; export async function getStaticPaths() { - const allPhotoBlogPosts = await getCollection('photoBlogPosts'); - const enPosts = allPhotoBlogPosts.filter(post => post.data.lang === 'en'); + const enPosts = await getLocalizedCollection('photoBlogPosts', 'en'); return enPosts.map(post => { const slug = getPostBaseSlug(post.id); return { diff --git a/src/pages/index.astro b/src/pages/index.astro index b15b56c..81426d0 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,16 +1,17 @@ --- import { Image } from "astro:assets"; import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../utils/content-i18n"; import Layout from "../layouts/main.astro"; import jalilPhoto from "../assets/images/jalil-2.jpg"; -const currentMission = (await getCollection("experiences")) - .filter((e) => e.data.lang === "fr" && !e.data.draft && !e.data.endDate) +const currentMission = (await getLocalizedCollection("experiences", "fr")) + .filter((e) => !e.data.draft && !e.data.endDate) .sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1))[0]; -const featuredProjects = (await getCollection("projects")) - .filter((p) => p.data.lang === "fr" && !p.data.draft && p.data.category === "dev" && p.data.featured) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) +const featuredProjects = (await getLocalizedCollection("projects", "fr")) + .filter((p) => !p.data.draft && p.data.category === "dev" && p.data.featured) + .sort((a, b) => b.data.date!.getTime() - a.data.date!.getTime()) .slice(0, 2); const recommendationCount = (await getCollection("recommendations")).length; diff --git a/src/pages/photo/blog/[year]/[slug].astro b/src/pages/photo/blog/[year]/[slug].astro index f7ef21d..91436ff 100644 --- a/src/pages/photo/blog/[year]/[slug].astro +++ b/src/pages/photo/blog/[year]/[slug].astro @@ -1,12 +1,10 @@ --- import PhotoBlogPostContent from '../../../../components/photo/pages/PhotoBlogPostContent.astro'; -import { getCollection } from 'astro:content'; +import { getLocalizedCollection } from '../../../../utils/content-i18n'; import { getPostBaseSlug } from '../../../../utils/i18n'; export async function getStaticPaths() { - const allPhotoBlogPosts = await getCollection('photoBlogPosts'); - // Uniquement les posts FR (lang absent ou 'fr') - const frPosts = allPhotoBlogPosts.filter(post => (post.data.lang ?? 'fr') === 'fr'); + const frPosts = await getLocalizedCollection('photoBlogPosts', 'fr'); return frPosts.map(post => { const slug = getPostBaseSlug(post.id); return { diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index 71a2ed3..8c24fd5 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,12 +1,10 @@ import rss from "@astrojs/rss"; import type { APIContext } from "astro"; -import { getCollection } from "astro:content"; +import { getLocalizedCollection } from "../utils/content-i18n"; import { getPostBaseSlug } from "../utils/i18n"; export async function GET(context: APIContext) { - const photoBlogPosts = await getCollection("photoBlogPosts"); - const frPosts = photoBlogPosts - .filter((post) => (post.data.lang ?? "fr") === "fr") + const frPosts = (await getLocalizedCollection("photoBlogPosts", "fr")) .sort( (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(), diff --git a/src/utils/content-i18n.ts b/src/utils/content-i18n.ts new file mode 100644 index 0000000..e4644d4 --- /dev/null +++ b/src/utils/content-i18n.ts @@ -0,0 +1,99 @@ +import { getCollection, getEntry, type CollectionKey } from "astro:content"; +import type { Locale } from "./i18n"; + +/** + * Valeurs par défaut pour les champs booléens optionnels. + * Appliquées après fusion pour garantir qu'ils sont toujours `boolean`, jamais `undefined`. + */ +const BOOLEAN_DEFAULTS: Record = { + featured: false, + draft: false, +}; + +/** + * Fusionne une entrée locale avec son entrée de base FR. + * Les champs définis dans l'entrée locale surchargent ceux de la base. + * Les champs absents (undefined) sont hérités de la base. + * Les booléens (featured, draft) sont garantis non-undefined après fusion. + */ +function mergeWithBase }>( + localeEntry: T, + baseEntry: T, +): T { + const mergedData = { ...baseEntry.data }; + for (const [key, value] of Object.entries(localeEntry.data)) { + if (value !== undefined) { + mergedData[key] = value; + } + } + for (const [key, defaultValue] of Object.entries(BOOLEAN_DEFAULTS)) { + if (mergedData[key] === undefined) { + mergedData[key] = defaultValue; + } + } + return { ...localeEntry, data: mergedData as T['data'] }; +} + +/** + * Applique les valeurs par défaut booléennes aux entrées FR (qui ne passent pas par mergeWithBase). + */ +function applyDefaults }>(entry: T): T { + const data = { ...entry.data }; + for (const [key, defaultValue] of Object.entries(BOOLEAN_DEFAULTS)) { + data[key] ??= defaultValue; + } + return { ...entry, data: data as T['data'] }; +} + +/** + * Déduit l'ID de base FR à partir d'un ID localisé. + * Ex: "ethemis-senior.en" → "ethemis-senior", "2015/wandering.ar" → "2015/wandering" + */ +function getBaseId(id: string, locale: Locale): string { + return locale === 'fr' ? id : id.replace(`.${locale}`, ''); +} + +/** + * Retourne les entrées d'une collection pour une locale donnée, + * avec fusion automatique des champs hérités de la base FR. + */ +export async function getLocalizedCollection( + name: C, + locale: Locale, +) { + const allEntries = await getCollection(name); + const localeEntries = allEntries.filter((e: any) => e.data.lang === locale); + + if (locale === 'fr') return localeEntries.map((e: any) => applyDefaults(e)); + + const baseEntries = allEntries.filter((e: any) => e.data.lang === 'fr'); + const baseMap = new Map(baseEntries.map((e: any) => [e.id, e])); + + return localeEntries.map((entry: any) => { + const baseId = getBaseId(entry.id, locale); + const base = baseMap.get(baseId); + return base ? mergeWithBase(entry, base) : applyDefaults(entry); + }); +} + +/** + * Retourne une entrée localisée avec fusion depuis la base FR. + * Utilisé pour les getEntry() individuels (ex: photoCategories). + */ +export async function getLocalizedEntry( + name: C, + id: string, + locale: Locale, +) { + const entry = await getEntry(name, id as any); + if (!entry) return entry; + if (locale === 'fr') return applyDefaults(entry); + + const baseId = getBaseId(id, locale); + if (baseId === id) return applyDefaults(entry); + + const baseEntry = await getEntry(name, baseId as any); + if (!baseEntry) return applyDefaults(entry); + + return mergeWithBase(entry, baseEntry); +}