Héritage i18n : les fichiers EN/AR ne surchargent que les champs traduits, les métadonnées partagées sont héritées de la base FR
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.
This commit is contained in:
parent
a79e075fbb
commit
366d18764b
103 changed files with 199 additions and 489 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}');
|
||||
|
|
|
|||
|
|
@ -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 => ({
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
---
|
||||
|
||||
<section id="explore-section" class="explore-section">
|
||||
|
|
|
|||
|
|
@ -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<string, string> = {
|
||||
'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]))
|
||||
};
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || "";
|
||||
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
role: "مطوّر ويب وبرمجيات"
|
||||
company: "مستقل"
|
||||
location: "إيل دو فرانس"
|
||||
startDate: "2003-01"
|
||||
endDate: "2006-12"
|
||||
technologies: ["PHP", "JavaScript", "HTML", "MySQL", "Linux"]
|
||||
type: "freelance"
|
||||
lang: "ar"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "تصوير إيرول"
|
||||
description: "أراد إيرول صورًا له لإعداد كتاب أعمال. عملنا طوال اليوم لتنويع الأجواء..."
|
||||
date: 2011-10-02
|
||||
coverImage: "18-Eroll-Shooting-1-19.jpg"
|
||||
tags: []
|
||||
featured: false
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "عملية المحفظة 2012"
|
||||
description: "توزيع محافظ مدرسية مجانية في مدارس محرومة من طرف جمعية محلية (JCI)، طنجة، المغرب."
|
||||
date: 2012-09-30
|
||||
coverImage: "35-Moroccan-Schoolgirls.jpg"
|
||||
tags: []
|
||||
featured: false
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "جولة في طنجة"
|
||||
description: "جولة فوتوغرافية في شوارع طنجة."
|
||||
date: 2012-05-26
|
||||
coverImage: "01-Observer-le-changement.jpg"
|
||||
tags: []
|
||||
featured: false
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "نزهة في إفران"
|
||||
description: "نزهة شتوية في جبال الأطلس المتوسط."
|
||||
date: 2013-01-13
|
||||
coverImage: "03-3.jpg"
|
||||
tags: []
|
||||
featured: false
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "تجوال في مدينة طنجة القديمة"
|
||||
description: "أثناء التجوال في أزقة طنجة القديمة، صادفت ساعاتيًا، ونجّارًا، وقمرًا عملاقًا..."
|
||||
date: 2014-08-10
|
||||
coverImage: "01-The-watchmaker.jpg"
|
||||
tags: []
|
||||
featured: true
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,6 @@ description: |
|
|||
إنيغما كانت مسابقة بحث عن الكنز بين المدارس نظّمها المركز العالي للدراسات CESIM يوم السبت، تحت عنوان «البحث عن كنز ابن بطوطة المنسي». 4 فرق طنجاوية دُعيت لتمثيل مؤسساتها.
|
||||
|
||||
تغطية.
|
||||
date: 2015-04-25
|
||||
coverImage: "01-Enigma-v1.jpg"
|
||||
tags: []
|
||||
featured: false
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
---
|
||||
title: "زفاف أورور وتوما"
|
||||
description: "كان لي شرف ومتعة أن أكون شاهد توما في زفافه الجميل مع أورور. ليس سهلًا التصوير في نفس الوقت، لكن كل الصور مليئة بالحب."
|
||||
date: 2015-09-26
|
||||
coverImage: "10-Mariage-Aurore-Thomas-10.jpg"
|
||||
tags: []
|
||||
featured: true
|
||||
draft: false
|
||||
lang: ar
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "ثقافات وتقاليد",
|
||||
"subtitle": "ثراء التقاليد الإنسانية",
|
||||
"order": 4,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Cultures & Traditions",
|
||||
"subtitle": "Richness of human traditions",
|
||||
"order": 4,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "محرّكات",
|
||||
"subtitle": "ميكانيكا وقوة في حركة",
|
||||
"order": 7,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Engines",
|
||||
"subtitle": "Mechanics and power in motion",
|
||||
"order": 7,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "يوميات",
|
||||
"subtitle": "لحظات من الحياة اليومية",
|
||||
"order": 8,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Everyday Life",
|
||||
"subtitle": "Moments of everyday life",
|
||||
"order": 8,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "موسيقى واحتفالات",
|
||||
"subtitle": "نغمات وأغانٍ واهتزازات تحتفي بلحظات حياتنا الكبيرة والصغيرة",
|
||||
"order": 5,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Music & Celebrations",
|
||||
"subtitle": "Notes, songs and vibrations celebrating life's big and small moments",
|
||||
"order": 5,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "طبيعة",
|
||||
"subtitle": "سحر العالم الطبيعي",
|
||||
"order": 3,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Nature",
|
||||
"subtitle": "The magic of the natural world",
|
||||
"order": 3,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "مناظر طبيعية",
|
||||
"subtitle": "جمال المناظر والأماكن",
|
||||
"order": 2,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Landscapes",
|
||||
"subtitle": "Beauty of landscapes and places",
|
||||
"order": 2,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "بورتريهات",
|
||||
"subtitle": "تعابير ومشاعر ملتقطة",
|
||||
"order": 1,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Portraits",
|
||||
"subtitle": "Captured expressions and emotions",
|
||||
"order": 1,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "رياضة",
|
||||
"subtitle": "حركة وجهد وتجاوز للذات",
|
||||
"order": 6,
|
||||
"lang": "ar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"title": "Sports",
|
||||
"subtitle": "Movement, effort and pushing limits",
|
||||
"order": 6,
|
||||
"lang": "en"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
---
|
||||
title: "ICU"
|
||||
description: "منصة مشاركة صور عبر الإنترنت، مشروع شخصي تاريخي."
|
||||
date: 2005-01-01
|
||||
category: "dev"
|
||||
technologies: ["PHP", "JavaScript", "MySQL"]
|
||||
lang: "ar"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
---
|
||||
title: "N.Gine"
|
||||
description: "نظام إدارة محتوى خاص بُني من الصفر، استُخدم للعديد من مشاريع العملاء."
|
||||
date: 2004-01-01
|
||||
category: "dev"
|
||||
technologies: ["PHP", "JavaScript", "MySQL"]
|
||||
lang: "ar"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue