Skip to content

🔄 Circulation des Données

Vue d'ensemble

Ce document explique comment les données circulent dans mon application, de leur source jusqu'à l'affichage final.


1. Chargement Initial de la Page

Flux Complet

Utilisateur visite le site

/fr/ ou /en/ (ou redirection depuis /)

src/pages/[locale]/index.astro

Import des données (CV + Mentions légales)

Passage aux composants via props

Rendu HTML statique (SSG)

Envoi au navigateur

Détail Étape par Étape

Étape 1 : URL

L'utilisateur visite :

  • https://simonchabrier.fr/ → Redirigé vers /fr/ ou /en/
  • https://simonchabrier.fr/fr/ → Page française
  • https://simonchabrier.fr/en/ → Page anglaise

Étape 2 : Page Astro

astro
---
// src/pages/fr/index.astro
import * as cv from "@cv-fr";
import * as legal from "@mentions-fr";
import Layout from "@/layouts/Layout.astro";
import Hero from "@/components/sections/Hero.astro";
// ... autres imports

const { basics, work, education, skills, projects } = cv;
---

Ce qui se passe :

  • Astro charge les fichiers JSON au moment du build
  • Les données sont disponibles immédiatement (pas de fetch)
  • TypeScript vérifie les types

Étape 3 : Layout

astro
<Layout
  title={`${basics.name} - ${basics.label}`}
  image={basics.image}
  summary={basics.summary}
  theme="system"
  locale="fr"
>
  <main>
    <!-- Sections ici -->
  </main>
</Layout>

Ce qui se passe :

  • Layout reçoit les props pour les meta tags
  • Génère <head> avec SEO
  • Charge les styles et scripts

Étape 4 : Composants Sections

astro
<Hero basics={basics} locale="fr" />
<About about={basics.summary} locale="fr" />
<Experience work={work} locale="fr" />
<Skills skills={skills} locale="fr" />

Ce qui se passe :

  • Chaque composant reçoit ses données spécifiques
  • Le prop locale permet de charger les traductions
  • Rendu HTML statique

2. Flux de Traduction (i18n)

Schéma

Composant (Hero.astro)

t("sections:hero.showProfile", { lng: "fr", name: "Simon" })

src/utils/i18n.ts

Charge public/locales/fr/sections.json (avec cache)

Navigate dans l'objet: sections.hero.showProfile

Récupère: "Voir le profil de {{name}} sur"

Interpole {{name}} → "Simon"

Retourne: "Voir le profil de Simon sur"

Exemple Concret

astro
---
// src/components/sections/Hero.astro
import { t } from "@/utils/i18n";

const { basics, locale } = Astro.props;

const showProfileText = t("sections:hero.showProfile", {
  lng: locale,
  name: basics.name
});
// → "Voir le profil de Simon sur" (si locale="fr")
---

<a
  href={basics.profiles[0].url}
  aria-label={`${showProfileText} ${basics.profiles[0].network}`}
>
  {showProfileText} {basics.profiles[0].network}
</a>

Résultat HTML :

html
<a href="https://github.com/simonchabrier"
   aria-label="Voir le profil de Simon sur GitHub">
  Voir le profil de Simon sur GitHub
</a>

3. Flux de Thème

Schéma

Page Load

Hero.astro / ThemeSwitch.astro

initializeTheme(select) [theme.ts]

Check localStorage.getItem("theme")

┌─────────────────┬─────────────────┐
│ null            │ "light" / "dark" │
│ (1ère visite)   │ (déjà visité)    │
└─────────────────┴─────────────────┘
    ↓                     ↓
getSystemTheme()    Utilise la valeur stockée
    ↓                     ↓
"light" ou "dark"    "light" ou "dark"
    ↓                     ↓
    └────────┬────────────┘

Applique classe sur <html>:
- Pas de classe → light
- class="dark" → dark

CSS variables de public/themes/themes.css
s'appliquent automatiquement

Tailwind utilise ces variables
via classes skin-*

Code Détaillé

1. Initialisation au Chargement

astro
<!-- Hero.astro -->
<script>
  import { initializeTheme, setupPrintHandlers } from "@/utils/theme";

  const select = document.getElementById("themeSwitch");
  if (select) {
    initializeTheme(select);
    setupPrintHandlers(select);
  }
</script>

2. Fonction initializeTheme

typescript
// src/utils/theme.ts
export function initializeTheme(selectElement: HTMLSelectElement): void {
  const stored = getStoredTheme(); // localStorage.getItem("theme")

  if (stored) {
    // Utilise le thème stocké
    selectElement.value = stored;
    updateTheme(stored);
  } else {
    // 1ère visite : utilise le thème système
    selectElement.value = "system";
    updateTheme("system");
  }
}

3. Fonction updateTheme

typescript
export function updateTheme(value: Theme): void {
  localStorage.setItem("theme", value);

  const html = document.documentElement;

  if (value === "system") {
    const systemTheme = getSystemTheme(); // prefers-color-scheme
    html.classList.toggle("dark", systemTheme === "dark");
  } else {
    html.classList.toggle("dark", value === "dark");
  }
}

4. Application des Styles

css
/* public/themes/themes.css */
:root {
  --color-fill: 255, 255, 255;  /* Blanc */
  --color-text-base: 30, 41, 59;
}

.dark {
  --color-fill: 17, 24, 39;  /* Gris foncé */
  --color-text-base: 226, 232, 240;
}
html
<!-- Tailwind utilise automatiquement la bonne variable -->
<div class="bg-skin-fill text-skin-base">
  <!-- Blanc/noir en light, gris foncé/clair en dark -->
</div>

4. Flux de Changement de Langue

Schéma

User clique sur LocaleSwitch (select)

Event "change" sur <select>

switchLocale(targetLocale) [locale.ts]

localStorage.setItem("locale", targetLocale)

window.location.href = `/${targetLocale}/`

Rechargement de la page avec nouvelle locale

Toutes les traductions sont dans la nouvelle langue

Code Détaillé

1. Composant LocaleSwitch

astro
<!-- src/components/LocaleSwitch.astro -->
<select id="localeSwitch">
  <option value="fr" selected={locale === "fr"}>🇫🇷 Français</option>
  <option value="en" selected={locale === "en"}>🇬🇧 English</option>
</select>

<script>
  import { switchLocale } from "@/utils/locale";

  const select = document.getElementById("localeSwitch");

  select?.addEventListener("change", function() {
    switchLocale(this.value);
  });
</script>

2. Fonction switchLocale

typescript
// src/utils/locale.ts
export function switchLocale(targetLocale: Locale): void {
  // Sauvegarde la préférence
  localStorage.setItem("locale", targetLocale);

  // Redirige vers la nouvelle URL
  window.location.href = `/${targetLocale}/`;
}

3. Page de Redirection

astro
<!-- src/pages/index.astro -->
---
// Vérifie localStorage pour rediriger automatiquement
---

<script>
  const stored = localStorage.getItem("locale");

  if (stored && ["fr", "en"].includes(stored)) {
    window.location.href = `/${stored}/`;
  } else {
    // Détecte la langue du navigateur
    const browserLang = navigator.language.split("-")[0];
    window.location.href = browserLang === "en" ? "/en/" : "/fr/";
  }
</script>

5. Flux de la Palette de Commandes (HotKeyPad)

Schéma

Page Load

KeyboardManager.astro charge

Génère profilesInfo avec icônes SVG (string)

Stocke dans data-info attribute

Script client récupère data-info

Parse JSON → Array d'objets Info

Crée commands avec handlers

hotkeypad.setCommands([...])

User appuie sur Cmd+K

Palette s'ouvre (HotKeyPad)

User clique sur "GitHub"

handler() → window.open(url, "_blank")

Code Détaillé

1. Génération des Commandes (côté serveur)

astro
---
// src/components/KeyboardManager.astro
import { SOCIAL_ICONS_SVG } from "@/constants/social-icons-svg";

const profilesInfo = profiles.map(({ network, url }) => {
  const icon = SOCIAL_ICONS_SVG[network];
  const firstLetter = network[0].toUpperCase();

  return {
    id: network,
    section: "Social",
    title: network,
    url,
    icon,
    hotkey: `ctrl+${firstLetter}`
  };
});
---

<div id="hotkeypad" data-info={JSON.stringify(profilesInfo)}></div>

2. Initialisation HotKeyPad (côté client)

typescript
<script>
  import HotKeyPad from "hotkeypad";

  const hotkeypad = new HotKeyPad({
    placeholder: "Search..."
  });

  // Récupère les données du serveur
  const info = hotkeypad.instance.getAttribute("data-info") ?? "[]";
  const parsedInfo = JSON.parse(info);

  // Transforme en commands avec handlers
  const data = parsedInfo.map(({ url, hotkey, icon, id, title, section }) => {
    return {
      id,
      title,
      icon,
      hotkey,
      section,
      handler: () => {
        window.open(url, "_blank");
      }
    };
  });

  // Configure HotKeyPad
  hotkeypad.setCommands([
    {
      id: "print",
      title: "Imprimer",
      icon: "...",
      hotkey: "ctrl+P",
      section: "Actions",
      handler: () => window.print()
    },
    ...data
  ]);
</script>

6. Flux des Props entre Composants

Schéma Hiérarchique

pages/fr/index.astro
    ├─ basics, work, skills, ...

    ├─→ Layout.astro
    │    ├─ title
    │    ├─ image
    │    ├─ summary
    │    └─ locale

    ├─→ Hero.astro
    │    ├─ basics (complet)
    │    └─ locale

    ├─→ Experience.astro
    │    ├─ work (array)
    │    └─ locale

    ├─→ Skills.astro
    │    ├─ skills (array)
    │    └─ locale

    └─→ KeyboardManager.astro
         ├─ profiles (basics.profiles)
         └─ locale

Flux Type-Safe

astro
---
// Page
import type { Basics, Work, Skills } from "@cvType";

const basics: Basics = cv.basics;
const work: Work[] = cv.work;
const skills: Skills[] = cv.skills;
---

<Hero basics={basics} locale="fr" />
<!-- TypeScript vérifie que basics est bien de type Basics -->

<Experience work={work} locale="fr" />
<!-- TypeScript vérifie que work est bien de type Work[] -->

7. Flux de Chargement des Assets

Images

cv-fr.json: "image": "./../avatar_254.webp"

Import dans pages/fr/index.astro

Passage à Layout.astro

Nettoyage du chemin:
image.replace(/^(\.\.\/|\.\/)+/, '/')

"/avatar_254.webp"

URL absolue:
"https://simonchabrier.fr/avatar_254.webp"

Utilisé dans:
- <img src={image} />
- <meta property="og:image" content={url} />

CSS Thèmes

Layout.astro

<link rel="preload" href="/themes/themes.css"
      as="style"
      onload="this.rel='stylesheet';" />

Chargement asynchrone (n'bloque pas le rendu)

CSS variables disponibles

Tailwind les utilise via classes skin-*

Traductions

Composant appelle t("sections:hero.title", { lng: "fr" })

i18n.ts vérifie le cache

Cache miss → import(`/public/locales/fr/sections.json`)

Stocke dans translationsCache["fr-sections"]

Retourne la valeur

Prochains appels utilisent le cache (rapide)

Résumé des Flux Principaux

FluxDéclencheurRésultat
Chargement initialURL visitéeHTML statique généré et envoyé
TraductionAppel t()String traduite avec interpolation
ThèmeChangement selectClasse dark ajoutée/retirée sur <html>
LangueChangement selectRedirection vers /[locale]/
PaletteCmd+KModale s'ouvre, commandes cliquables
PropsRender composantDonnées passées du parent aux enfants