diff --git a/docs/EVENTS.md b/docs/EVENTS.md new file mode 100644 index 0000000..ef6beea --- /dev/null +++ b/docs/EVENTS.md @@ -0,0 +1,70 @@ +# Events System + +## Overview + +Events are automatically parsed from `src/events.yaml`, localized (EN/FR), and rendered in templates via the `{{> events }}` partial. + +## Event Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `date` | String or Object | ✅ | Single: `"YYYY-MM-DD"`
Range: `{ start: "...", end: "..." }` | +| `title` | String or i18n | ✅ | Event name | +| `description` | String or i18n | | Additional details | +| `location` | String or i18n | | Event location | +| `url` | String | | Event website link | +| `source` | String | | Presentation/slides PDF path | + +### i18n Format + +```yaml +# Localized (recommended) +title: + en: "English Title" + fr: "Titre Français" + +# OR simple fallback (same for all locales) +location: "Paris, France" +``` + +**Fallback:** Missing locale defaults to English. + +## Examples + +```yaml +# Single date +- date: "2026-04-22" + title: + en: OKDP at Devoxx Paris + fr: OKDP à Devoxx Paris + location: Palais des Congrès, Paris + +# Date range +- date: + start: "2025-12-10" + end: "2025-12-11" + title: OSXP (Open Source eXPérience) + +# With link +- date: "2026-02-03" + title: Cloud Native Days + source: /presentations/slides.pdf +``` + +## Date Formatting + +Dates are auto-formatted per locale: + +| Input | EN | FR | +|-------|----|----| +| `"2026-02-03"` | February 3, 2026 | 3 février 2026 | +| `{ start: "2025-12-10", end: "2025-12-11" }` | December 10 → 11, 2025 | 10 → 11 décembre 2025 | + +## Important Notes + +**Always quote dates** in YAML: + +```yaml +date: "2026-02-03" # Correct +date: 2026-02-03 # Wrong +``` diff --git a/en/index.html b/en/index.html index 10f1c41..c8fa381 100644 --- a/en/index.html +++ b/en/index.html @@ -32,6 +32,7 @@
  • About
  • Architecture
  • Roadmap
  • +
  • Events
  • Community
  • Architecture
  • Roadmap
  • +
  • Events
  • Community
  • @@ -457,6 +460,110 @@

    Call for Contributions

    + +
    +
    +
    +

    Events

    +

    OKDP presentations, conferences, and community gatherings.

    +
    + + +
    Upcoming
    +
    +
    + 2026-06-04 +
    +
    TOSIT-Day
    +
    + Caisse des Dépôts et Consignations +
    +
    + + +
    + + + + + Past Events (10) + +
    +
    + ── 2026 + +
    +
    + 2026-05-07 +
    +
    OKDP presentation at Docaposte
    +
    +
    +
    + 2026-05-05 +
    +
    OKDP presentation at the French Ministry of Agriculture and Food Sovereignty
    +
    +
    +
    + 2026-04-22 +
    +
    OKDP presentation at Devoxx Paris
    +
    + Palais des Congrès, Paris +
    +
    + 2026-04-21 +
    +
    OKDP presentation at INERIS
    +
    +
    +
    + 2026-04-03 +
    +
    OKDP presentation at Arkéa
    +
    +
    +
    + 2026-03-27 +
    +
    OKDP presentation at Kiira
    +
    +
    +
    + 2026-02-18 +
    +
    OKDP presentation at Tasmane / Agirc-Arrco
    +
    +
    +
    + 2026-02-12 +
    +
    OKDP presentation at Wescale
    +
    +
    +
    + 2026-02-03 +
    +
    Cloud Native Days France 2026
    +
    with TOSIT booth
    +
    +
    +
    + ── 2025 + +
    +
    + 2025-12-10 → 11 +
    +
    OSXP (Open Source eXPérience)
    +
    +
    +
    +
    +
    +
    +
    diff --git a/en/roadmap/index.html b/en/roadmap/index.html index 085fce3..1131e8b 100644 --- a/en/roadmap/index.html +++ b/en/roadmap/index.html @@ -31,6 +31,7 @@
  • About
  • Architecture
  • Roadmap
  • +
  • Events
  • Community
  • Architecture
  • Roadmap
  • +
  • Events
  • Community
  • diff --git a/index.html b/index.html index 4b4b2ae..248c460 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,7 @@
  • À propos
  • Architecture
  • Roadmap
  • +
  • Événements
  • Communauté
  • Architecture
  • Roadmap
  • +
  • Événements
  • Communauté
  • @@ -457,6 +460,110 @@

    Appel à contribution

  • + +
    +
    +
    +

    Événements

    +

    Présentations OKDP, conférences et rassemblements communautaires.

    +
    + + +
    À venir
    +
    +
    + 2026-06-04 +
    +
    TOSIT-Day
    +
    + Caisse des Dépôts et Consignations +
    +
    + + +
    + + + + + Événements passés (10) + +
    +
    + ── 2026 + +
    +
    + 2026-05-07 +
    +
    Présentation d'OKDP à Docaposte
    +
    +
    +
    + 2026-05-05 +
    +
    Présentation d'OKDP au Ministère de l'Agriculture, de l'Agro-alimentaire et de la Souveraineté alimentaire
    +
    +
    +
    + 2026-04-22 +
    +
    Présentation d'OKDP à Devoxx Paris
    +
    + Palais des Congrès, Paris +
    +
    + 2026-04-21 +
    +
    Présentation d'OKDP à l'INERIS
    +
    +
    +
    + 2026-04-03 +
    +
    Présentation d'OKDP à Arkéa
    +
    +
    +
    + 2026-03-27 +
    +
    Présentation d'OKDP à Kiira
    +
    +
    +
    + 2026-02-18 +
    +
    Présentation d'OKDP à Tasmane / Agirc-Arrco
    +
    +
    +
    + 2026-02-12 +
    +
    Présentation d'OKDP à Wescale (ESN)
    +
    +
    +
    + 2026-02-03 +
    +
    Cloud Native Days France 2026
    +
    avec stand TOSIT
    +
    +
    +
    + ── 2025 + +
    +
    + 2025-12-10 → 11 +
    +
    OSXP (Open Source eXPérience)
    +
    +
    +
    +
    +
    +
    +
    diff --git a/package-lock.json b/package-lock.json index 553197b..1b23799 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "devDependencies": { "autoprefixer": "^10.4.17", "handlebars": "^4.7.8", + "js-yaml": "^4.1.0", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "vite": "^5.1.0" @@ -729,6 +730,13 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/autoprefixer": { "version": "10.4.18", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", @@ -1297,6 +1305,19 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", diff --git a/package.json b/package.json index 454aa50..6f876cc 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "devDependencies": { "autoprefixer": "^10.4.17", "handlebars": "^4.7.8", + "js-yaml": "^4.1.0", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "vite": "^5.1.0" diff --git a/roadmap/index.html b/roadmap/index.html index 1b331c8..19b6a27 100644 --- a/roadmap/index.html +++ b/roadmap/index.html @@ -31,6 +31,7 @@
  • À propos
  • Architecture
  • Roadmap
  • +
  • Événements
  • Communauté
  • Architecture
  • Roadmap
  • +
  • Événements
  • Communauté
  • diff --git a/scripts/events.js b/scripts/events.js new file mode 100644 index 0000000..c720b05 --- /dev/null +++ b/scripts/events.js @@ -0,0 +1,228 @@ +const yaml = require('js-yaml'); +const fs = require('fs'); + +/** + * Get a localized field value with fallback support + * @param {string|object} field - The field to localize (can be string or object with locale keys) + * @param {string} locale - Target locale (e.g., 'en', 'fr') + * @param {string} fallbackLocale - Fallback locale if target not found + * @returns {string} Localized value + */ +function getLocalizedField(field, locale, fallbackLocale = 'en') { + if (!field) return ''; + if (typeof field === 'string') return field; // Direct string fallback + return field[locale] || field[fallbackLocale] || ''; +} + +/** + * Get compact ISO date string for display + * @param {string|object} dateInfo - Date or date range + * @returns {string} Compact ISO representation + */ +function getISODate(dateInfo) { + if (typeof dateInfo === 'string') { + return dateInfo; + } + + if (dateInfo && typeof dateInfo === 'object' && dateInfo.start) { + if (!dateInfo.end) return dateInfo.start; + + // Same month: 2026-06-11 → 12 + if (dateInfo.start.slice(0, 7) === dateInfo.end.slice(0, 7)) { + return `${dateInfo.start} → ${dateInfo.end.slice(8)}`; + } + + // Different month/year: 2026-06-11 → 2026-06-12 + return `${dateInfo.start} → ${dateInfo.end}`; + } + + return ''; +} + +/** + * Check if an event is in the past + * @param {object} event - Event object + * @returns {boolean} True if event has passed + */ +function isPastEvent(event) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + let compareDate; + if (typeof event.date === 'string') { + compareDate = new Date(event.date); + } else if (event.date && event.date.end) { + compareDate = new Date(event.date.end); + } else if (event.date && event.date.start) { + compareDate = new Date(event.date.start); + } else { + return false; + } + + return compareDate < today; +} + +/** + * Get sort date for an event + * @param {object} event - Event object + * @returns {Date} Date to use for sorting + */ +function getEventSortDate(event) { + if (typeof event.date === 'string') { + return new Date(event.date); + } + if (event.date && event.date.start) { + return new Date(event.date.start); + } + return new Date(0); +} + +/** + * Localize events array for a specific language and separate into upcoming/past + * @param {Array} events - Array of event objects + * @param {string} locale - Target locale + * @returns {Object} Object with upcoming, past, and pastGrouped arrays + */ +function localizeEvents(events, locale) { + if (!events || !Array.isArray(events)) { + return { upcoming: [], past: [], pastGrouped: [] }; + } + + const localized = events.map(event => ({ + id: event.id, + date: event.date, + dateISO: getISODate(event.date), + source: event.source || null, + title: getLocalizedField(event.title, locale), + description: getLocalizedField(event.description, locale) || null, + location: getLocalizedField(event.location, locale) || null, + isPast: isPastEvent(event), + year: getEventSortDate(event).getFullYear(), + })); + + // Separate and sort + const upcoming = localized + .filter(e => !e.isPast) + .sort((a, b) => getEventSortDate(a) - getEventSortDate(b)); + + const past = localized + .filter(e => e.isPast) + .sort((a, b) => getEventSortDate(b) - getEventSortDate(a)); + + // Group past events by year + const pastByYear = new Map(); + for (const event of past) { + const year = event.year; + if (!pastByYear.has(year)) { + pastByYear.set(year, []); + } + pastByYear.get(year).push(event); + } + + const pastGrouped = Array.from(pastByYear.entries()) + .sort((a, b) => b[0] - a[0]) + .map(([year, items]) => ({ year, items })); + + return { upcoming, past, pastGrouped }; +} + +/** + * Validate events data structure + * @param {Array} events - Array of event objects + * @param {Array} supportedLocales - Array of supported locale codes + */ +function validateEvents(events, supportedLocales = ['en', 'fr']) { + if (!events || !Array.isArray(events)) { + console.warn('⚠️ No events array found'); + return; + } + + const errors = []; + const warnings = []; + + events.forEach((event, index) => { + // Required fields + if (!event.title) { + errors.push(`Event ${event.id || index} missing required field: title`); + } + if (!event.date) { + errors.push(`Event ${event.id || index} missing required field: date`); + } + + // Date validation + if (event.date && typeof event.date === 'object') { + if (!event.date.start) { + errors.push(`Event ${event.id || index} has date range but missing 'start'`); + } + if (!event.date.end) { + errors.push(`Event ${event.id || index} has date range but missing 'end'`); + } + + if (event.date.start && event.date.end) { + const start = new Date(event.date.start); + const end = new Date(event.date.end); + if (start > end) { + errors.push(`Event ${event.id || index} has end date before start date`); + } + } + } + + // Translation validation + ['title', 'description', 'location'].forEach(field => { + if (event[field] && typeof event[field] === 'object') { + supportedLocales.forEach(locale => { + if (!event[field][locale]) { + warnings.push(`Event ${event.id || index} missing ${locale} translation for: ${field}`); + } + }); + } + }); + }); + + if (warnings.length > 0) { + console.warn('⚠️ Event warnings:'); + warnings.forEach(w => console.warn(` ${w}`)); + } + + if (errors.length > 0) { + console.error('❌ Event validation failed:'); + errors.forEach(e => console.error(` ${e}`)); + throw new Error('Event validation failed'); + } + + console.log(`✅ Validated ${events.length} event(s)`); +} + +/** + * Load and parse events from YAML file + * @param {string} eventsPath - Path to events YAML file + * @returns {Array} Array of event objects + */ +function loadEvents(eventsPath) { + try { + if (!fs.existsSync(eventsPath)) { + console.warn(`⚠️ Events file not found at ${eventsPath}`); + return []; + } + + const fileContent = fs.readFileSync(eventsPath, 'utf8'); + const data = yaml.load(fileContent); + + if (!data || !data.events) { + console.warn('⚠️ No events found in YAML file'); + return []; + } + + validateEvents(data.events); + + return data.events; + } catch (error) { + console.error(`❌ Error loading events: ${error.message}`); + return []; + } +} + +module.exports = { + loadEvents, + localizeEvents, +}; diff --git a/scripts/generate.js b/scripts/generate.js index e1e2f74..5667cfd 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -1,12 +1,14 @@ const Handlebars = require('handlebars'); const fs = require('fs'); const path = require('path'); +const { loadEvents, localizeEvents } = require('./events'); const srcDir = path.join(__dirname, '../src'); const localesDir = path.join(srcDir, 'locales'); const partialsDir = path.join(srcDir, 'partials'); const templatePath = path.join(srcDir, 'template.html'); const roadmapTemplatePath = path.join(srcDir, 'roadmap-template.html'); +const eventsPath = path.join(srcDir, 'events.yaml'); console.log('Generating sites...'); @@ -39,6 +41,9 @@ Handlebars.registerHelper('computeStatus', function (features) { const mainTemplate = Handlebars.compile(fs.readFileSync(templatePath, 'utf8')); const roadmapTemplate = Handlebars.compile(fs.readFileSync(roadmapTemplatePath, 'utf8')); +// Load events +const events = loadEvents(eventsPath); + const languages = [ { code: 'fr', isDefault: true }, { code: 'en', isDefault: false } @@ -57,6 +62,7 @@ languages.forEach(lang => { const context = { ...content, + eventsList: localizeEvents(events, lang.code), currentLang: lang.code, frUrl: lang.code === 'fr' ? '#' : '../', enUrl: lang.code === 'en' ? '#' : (lang.isDefault ? 'en/' : '../en/'), diff --git a/src/events.yaml b/src/events.yaml new file mode 100644 index 0000000..508bf35 --- /dev/null +++ b/src/events.yaml @@ -0,0 +1,67 @@ +events: + - date: + start: "2025-12-10" + end: "2025-12-11" + title: OSXP (Open Source eXPérience) + location: Cité des Sciences et de l'Industrie, Paris + + - date: "2026-02-03" + title: + fr: Cloud Native Days France 2026 + en: Cloud Native Days France 2026 + description: + fr: avec stand TOSIT + en: with TOSIT booth + location: CENTQUATRE-PARIS, Paris + + - date: "2026-02-12" + title: + fr: Présentation d'OKDP à Wescale (ESN) + en: OKDP presentation at Wescale + location: remote + + - date: "2026-02-18" + title: + fr: Présentation d'OKDP à Tasmane / Agirc-Arrco + en: OKDP presentation at Tasmane / Agirc-Arrco + location: remote + + - date: "2026-03-27" + title: + fr: Présentation d'OKDP à Kiira + en: OKDP presentation at Kiira + location: remote + + - date: "2026-04-03" + title: + fr: Présentation d'OKDP à Arkéa + en: OKDP presentation at Arkéa + location: remote + + - date: "2026-04-21" + title: + fr: Présentation d'OKDP à l'INERIS + en: OKDP presentation at INERIS + location: remote + + - date: "2026-04-22" + title: + fr: Présentation d'OKDP à Devoxx Paris + en: OKDP presentation at Devoxx Paris + location: Palais des Congrès, Paris + + - date: "2026-05-05" + title: + fr: Présentation d'OKDP au Ministère de l'Agriculture, de l'Agro-alimentaire et de la Souveraineté alimentaire + en: OKDP presentation at the French Ministry of Agriculture and Food Sovereignty + location: remote + + - date: "2026-05-07" + title: + fr: Présentation d'OKDP à Docaposte + en: OKDP presentation at Docaposte + location: remote + + - date: "2026-06-04" + title: TOSIT-Day + location: Caisse des Dépôts et Consignations diff --git a/src/locales/en.json b/src/locales/en.json index 75bf042..512cbaf 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -8,6 +8,7 @@ "about": "About", "architecture": "Architecture", "roadmap": "Roadmap", + "events": "Events", "community": "Community" }, "hero": { @@ -185,6 +186,14 @@ "desc2": "The association brings together numerous companies and administrations, including BPCE (Banque Populaire, Caisse d'Epargne et Natixis), Société Générale, among others. It also hosts the TDP project, initiated by DGFiP and EDF.", "desc3": "Participation in TOSIT projects is open to all." }, + "events": { + "title": "Events", + "subtitle": "OKDP presentations, conferences, and community gatherings.", + "upcoming": "Upcoming", + "past": "Past Events", + "noUpcoming": "No upcoming events at the moment.", + "noEvents": "No events at the moment." + }, "footer": { "copyright": "© 2026 TOSIT — The Open Source I Trust.", "license": "Crafted by the community • Licensed under Apache License v2.0" diff --git a/src/locales/fr.json b/src/locales/fr.json index bf18f74..4534369 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -8,6 +8,7 @@ "about": "À propos", "architecture": "Architecture", "roadmap": "Roadmap", + "events": "Événements", "community": "Communauté" }, "hero": { @@ -185,6 +186,14 @@ "desc2": "L'association rassemble de nombreuses entreprises et administrations, dont BPCE (Banque Populaire, Caisse d'Epargne et Natixis), Société Générale, entre autres. Elle héberge également le projet TDP, initié par la DGFiP et EDF.", "desc3": "La participation aux projets TOSIT est ouverte à tous." }, + "events": { + "title": "Événements", + "subtitle": "Présentations OKDP, conférences et rassemblements communautaires.", + "upcoming": "À venir", + "past": "Événements passés", + "noUpcoming": "Aucun événement à venir pour le moment.", + "noEvents": "Aucun événement pour le moment." + }, "footer": { "copyright": "© 2026 TOSIT — The Open Source I Trust.", "license": "Façonné par la communauté • Sous licence Apache V2.0" diff --git a/src/partials/events.html b/src/partials/events.html new file mode 100644 index 0000000..3b138ab --- /dev/null +++ b/src/partials/events.html @@ -0,0 +1,92 @@ +
    +
    +
    +

    {{ events.title }}

    +

    {{ events.subtitle }}

    +
    + + {{#if eventsList}} + {{#if eventsList.upcoming}} + +
    {{ events.upcoming }}
    +
    + {{#each eventsList.upcoming}} +
    + {{this.dateISO}} +
    +
    {{this.title}}
    + {{#if this.description}} +
    {{this.description}}
    + {{/if}} + {{#if this.source}} + + {{#if this.source}} + + + + {{/if}} + {{this.source}} + + {{/if}} +
    + {{#if this.location}} + {{this.location}} + {{/if}} +
    + {{/each}} +
    + {{else}} +
    {{ events.upcoming }}
    +
    +
    {{ events.noUpcoming }}
    +
    + {{/if}} + + {{#if eventsList.pastGrouped}} + +
    + + + + + {{ events.past }} ({{eventsList.past.length}}) + +
    + {{#each eventsList.pastGrouped}} +
    + ── {{this.year}} + +
    + {{#each this.items}} +
    + {{this.dateISO}} +
    +
    {{this.title}}
    + {{#if this.description}} +
    {{this.description}}
    + {{/if}} + {{#if this.source}} + + + + + {{this.source}} + + {{/if}} +
    + {{#if this.location}} + {{this.location}} + {{/if}} +
    + {{/each}} + {{/each}} +
    +
    + {{/if}} + {{else}} +
    +

    {{ events.noEvents }}

    +
    + {{/if}} +
    +
    diff --git a/src/partials/header.html b/src/partials/header.html index ac72d94..a344d05 100644 --- a/src/partials/header.html +++ b/src/partials/header.html @@ -12,6 +12,7 @@
  • {{ nav.about }}
  • {{ nav.architecture }}
  • {{ nav.roadmap }}
  • +
  • {{ nav.events }}
  • {{ nav.community }}
  • {{ nav.architecture }}
  • {{ nav.roadmap }}
  • +
  • {{ nav.events }}
  • {{ nav.community }}
  • diff --git a/src/template.html b/src/template.html index b42b933..fc2f6e1 100644 --- a/src/template.html +++ b/src/template.html @@ -267,6 +267,9 @@

    {{ community.contribute.title }}

  • + + {{> events }} +