---
title: 'Guides pratiques'
description: 'Recettes pas à pas pour les tâches courantes de contribution : langues, thèmes, déploiement, migrations.'
slug: 'guides-pratiques'
weight: 2
date: 2026-06-16
---

## Ajouter une nouvelle langue

> TrainUs propose déjà trois langues aujourd'hui — anglais, français et danois (`da`) — pas seulement la paire EN/FR mise en avant sur le site du projet. Les étapes ci-dessous montrent comment en ajouter une de plus, avec l'espagnol (`es`) comme exemple travaillé.

Ajouter une langue demande quatre points de contact : un nouveau fichier de traduction, son enregistrement dans l'initialiseur i18n, une option dans le sélecteur des Réglages, et une entrée dans le journal de décisions.

1. `src/i18n/locales/<code>.json` — créer le fichier de traduction
2. `src/i18n/index.js` — importer et enregistrer la locale
3. `src/views/SettingsView.vue` — ajouter l'`<option>` dans le sélecteur de langue
4. `docs/decisions-log.md` — consigner la décision

### 1. Créer le fichier de traduction

Copiez `src/i18n/locales/en.json` vers `src/i18n/locales/<code>.json`, où `<code>` est le code de langue à deux lettres BCP 47 (par exemple `es`, `de`, `it`).

Traduisez chaque valeur de chaîne. Ne changez **aucun** nom de clé. Conservez tous les marqueurs d'interpolation (`{{date}}`, `{{title}}`, etc.) et les entités HTML exactement comme dans la source.

### 2. Enregistrer la locale dans i18next

Ouvrez `src/i18n/index.js`. Ajoutez un import en haut, à côté des autres :

```js
import es from './locales/es.json'
```

Puis ajoutez la locale à l'objet `resources` dans `i18next.init` :

```js
resources: {
  en: { translation: en },
  fr: { translation: fr },
  da: { translation: da },
  es: { translation: es },   // ← nouveau
},
```

### 3. Ajouter l'option dans le sélecteur des Réglages

Ouvrez `src/views/SettingsView.vue` et repérez l'élément `<select>` avec `id="settings-language"`. Ajoutez une `<option>` après les autres :

```html
<option value="es">Español</option>
```

Utilisez le nom natif de la langue comme libellé (pas le nom anglais), pour que les locuteurs puissent l'identifier sans avoir à lire l'anglais.

### 4. Mettre à jour le journal de décisions

Ajoutez une entrée dans `docs/decisions-log.md` consignant l'ajout et sa raison.

### Liste de vérification

- [ ] `src/i18n/locales/<code>.json` créé et entièrement traduit
- [ ] Import ajouté dans `src/i18n/index.js`
- [ ] `<option>` ajoutée dans `src/views/SettingsView.vue`
- [ ] La langue est sélectionnable dans les Réglages et l'interface change correctement
- [ ] `docs/decisions-log.md` mis à jour

---

## Ajouter un nouveau thème

Un thème est un ensemble nommé de surcharges de variables CSS, scopées à un attribut `[data-theme='<name>']`. L'application lit le thème actif depuis la base de données et pose cet attribut sur `<html>` avant le premier rendu.

1. `src/styles/variables.css` — définir le bloc de variables CSS
2. `src/i18n/locales/{en,fr,da}.json` — ajouter le libellé d'affichage, dans toutes les langues prises en charge
3. `src/views/SettingsView.vue` — ajouter l'`<option>` dans le sélecteur de thème
4. Test manuel dans le navigateur
5. `docs/decisions-log.md` — consigner la décision

### 1. Définir les variables CSS

Ouvrez `src/styles/variables.css` et ajoutez un nouveau bloc à la fin. Copiez le bloc `:root` comme point de départ et ne changez que les valeurs qui diffèrent du thème clair par défaut.

```css
/* Mon thème */
[data-theme='<name>'] {
  --bg-color1: ...;
  --bg-color2: ...;
  --front-color1: ...;
  --front-color2: ...;
  --navbar-bg-color: ...;
  --navbar-front-color: ...;
  --navbar-hover-bg-color: ...;
  --navbar-hover-front-color: ...;
  --actionbar-bg-color: ...;
  --actionbar-font-color: ...;
  --border-color: ...;
  --input-bg: ...;
  --input-border: ...;
  --btn-primary-bg: ...;
  --btn-primary-color: ...;
  --btn-danger-bg: ...;
  --btn-danger-color: ...;
  --success-color: ...;
  --warning-color: ...;
}
```

La valeur du sélecteur (par exemple `trainus`) devient la clé du thème partout ailleurs. Voir la table de référence des variables en bas de cette recette pour savoir ce que chacune affecte.

### 2. Ajouter le libellé d'affichage dans toutes les langues prises en charge

TrainUs propose déjà trois langues aujourd'hui — anglais, français et danois (voir « Ajouter une nouvelle langue » ci-dessus) — donc le libellé du nouveau thème doit avoir une entrée dans **les trois** fichiers de locale, pas seulement en anglais et en français. Ajoutez `"themeMyName"` à l'objet `"settings"`, après `"themeDark"`, dans chacun :

`src/i18n/locales/en.json` :

```json
"themeMyName": "My Theme Label"
```

`src/i18n/locales/fr.json` :

```json
"themeMyName": "Mon thème"
```

`src/i18n/locales/da.json` :

```json
"themeMyName": "Mit tema"
```

Si une langue se retrouve sans la clé, i18next retombe sur `fallbackLng: 'en'` plutôt que de planter — mais ça vaut quand même le coup de le repérer pendant le test manuel ci-dessous, car un libellé resté en anglais dans une autre langue passe facilement inaperçu.

### 3. Ajouter l'option dans le sélecteur des réglages

Dans `src/views/SettingsView.vue`, repérez le bloc `<select>` du thème (recherchez `settings-theme`). Ajoutez une `<option>` après les autres :

```html
<option value="<name>">{{ $t('settings.themeMyName') }}</option>
```

La `value` doit correspondre au nom du sélecteur défini à l'étape 1.

### 4. Test manuel

1. `npm run dev`
2. Ouvrez Réglages → Thème → sélectionnez votre nouveau thème.
3. Vérifiez : la barre de navigation, le fond, les boutons et les champs s'affichent tous avec la nouvelle palette.
4. Rechargez la page pour confirmer que le thème persiste (chargé depuis la base de données au démarrage).
5. Parcourez chaque langue (anglais, français, danois) et vérifiez que le libellé du thème est bien traduit, sans retomber sur l'anglais.
6. Exportez les réglages puis réimportez-les pour confirmer que la clé du thème survit à l'aller-retour.

### 5. Mettre à jour le journal de décisions

Ajoutez une entrée dans `docs/decisions-log.md` consignant l'ajout et sa raison.

### Référence des variables

| Variable                     | Affecte                                 |
| ---------------------------- | --------------------------------------- |
| `--bg-color1`                | Fond principal de la page               |
| `--bg-color2`                | Fond des cartes / surfaces secondaires  |
| `--front-color1`             | Texte principal                         |
| `--front-color2`             | Texte secondaire / discret              |
| `--navbar-bg-color`          | Fond du menu de navigation latéral      |
| `--navbar-front-color`       | Texte et icônes du menu latéral         |
| `--navbar-hover-bg-color`    | Fond au survol d'un élément de menu     |
| `--navbar-hover-front-color` | Texte au survol d'un élément de menu    |
| `--actionbar-bg-color`       | Fond de la barre d'action en haut       |
| `--actionbar-font-color`     | Texte et icônes de la barre d'action    |
| `--border-color`             | Bordures des cartes et des champs       |
| `--input-bg`                 | Fond des champs de saisie               |
| `--input-border`             | Bordure des champs de saisie            |
| `--btn-primary-bg`           | Fond du bouton principal                |
| `--btn-primary-color`        | Texte du bouton principal               |
| `--btn-danger-bg`            | Fond du bouton danger / suppression     |
| `--btn-danger-color`         | Texte du bouton danger                  |
| `--success-color`            | Messages et indicateurs de succès       |
| `--warning-color`            | Messages et indicateurs d'avertissement |

### Liste de vérification

- [ ] Bloc de variables CSS ajouté dans `src/styles/variables.css`
- [ ] Libellé d'affichage ajouté dans toutes les langues prises en charge (anglais, français, danois)
- [ ] `<option>` ajoutée dans `src/views/SettingsView.vue`
- [ ] Thème sélectionnable, persistant au rechargement, et qui survit à l'export/import
- [ ] `docs/decisions-log.md` mis à jour

---

## Ajouter une migration de base de données

> **Demandez d'abord.** Selon les règles du projet, `SCHEMA_VERSION` n'est incrémenté qu'une fois un schéma réellement déployé auprès des utilisateurs — et seulement après confirmation avec le responsable du projet. Ajouter une colonne directement dans `SCHEMA_SQL` (dans `src/db/schema.js`) suffit pour les changements en place avant une publication ; aucune des étapes ci-dessous n'est nécessaire dans ce cas. Ne passez par une vraie migration que lorsque des utilisateurs existants ont déjà des données sous l'ancien schéma.

Une augmentation de `SCHEMA_VERSION` s'accompagne toujours d'exactement une entrée dans le registre `RELEASES` (`src/db/releases.js`), qui est la source de vérité unique lue à la fois par la migration au démarrage et par la vérification de compatibilité de Restaurer la base de données.

1. Augmentez `SCHEMA_VERSION` dans `src/db/schema.js`.
2. Écrivez le SQL qui fait passer une base de données existante de l'ancienne version à la nouvelle.
3. Ajoutez l'entrée correspondante dans `RELEASES` dans `src/db/releases.js`.
4. Testez le chemin de mise à niveau sur une base encore à l'ancienne version.
5. Mettez à jour la documentation de référence.

### 1. Augmenter la version

```js
// src/db/schema.js
export const SCHEMA_VERSION = 2 // était 1
```

### 2. Écrire le script de migration

Un script de migration est du SQL classique — `ALTER TABLE`, remplissage de données, tout ce qui est nécessaire pour amener l'ancien schéma au niveau du nouveau. Il s'exécute dans la même transaction que l'estampillage `schema_version`, donc un échec partiel se rétablit proprement.

### 3. Déclarer la release

```js
// src/db/releases.js
export const RELEASES = [
  { schemaVersion: 1, appVersion: '1.0.0', dbBreak: false, migrationScript: null },
  {
    schemaVersion: 2,
    appVersion: '1.1.0',
    dbBreak: true,
    migrationScript: `ALTER TABLE exercise ADD COLUMN category TEXT;`,
  },
]
```

- `dbBreak: false` avec `migrationScript: null` est une entrée « no-op » valide (seule la ligne `schema_version` est estampillée) — à utiliser pour une augmentation de version qui n'a pas besoin de toucher aux données existantes.
- `dbBreak: true` exige un `migrationScript` non nul ; `getMigrationScripts()` lève une erreur si un script manque pour une release « breaking » dans la plage demandée.

### 4. Tester le chemin de mise à niveau

Deux chemins de code lisent `RELEASES`, et les deux doivent être vérifiés :

- **Migration au démarrage** : ouvrez l'application avec une base existante estampillée à l'ancien `schema_version`. L'application doit afficher l'écran bloquant « téléchargez d'abord une sauvegarde », puis appliquer votre script et continuer avec les données intactes.
- **Restaurer la base de données** : restaurez une sauvegarde `.sqlite3` prise à l'ancienne version de schéma dans une version de l'application tournant sur la nouvelle — `checkCompatibility()` doit détecter l'écart et exécuter le même script pendant l'étape d'adaptation de la restauration.

Voir la [Référence]({{< relref "technical.fr.md" >}}) pour la machine à états complète de la migration et son diagramme de flux, et [Comprendre l'architecture]({{< relref "understanding-the-architecture.fr.md" >}}) pour le pourquoi de cette conception (sauvegarde forcée, migrations transactionnelles, échec sécurisé sur une base plus récente que l'application).

### 5. Mettre à jour la documentation

- Le tableau du schéma et le diagramme entité-relation dans la [Référence]({{< relref "technical.fr.md" >}})
- `docs/decisions-log.md`, avec la date, la décision et la justification

### Liste de vérification

- [ ] Confirmation obtenue du responsable du projet qu'une vraie augmentation de version (pas un changement en place) est justifiée
- [ ] `SCHEMA_VERSION` augmenté
- [ ] Entrée `RELEASES` ajoutée avec un `migrationScript` correct (ou `null` pour un no-op)
- [ ] Migration au démarrage testée sur une base à l'ancienne version
- [ ] Adaptation de Restaurer la base de données testée sur une sauvegarde à l'ancienne version
- [ ] Documentation de référence et `docs/decisions-log.md` mis à jour

---

## Déployer TrainUs

TrainUs est un site statique : `npm run build` génère tout ce qu'il faut dans `dist/`, prêt à être copié sur n'importe quel serveur web ou CDN. Le déploiement est unique : chaque nouvelle build remplace la précédente à la racine du site, sans dossiers par version. Le service worker gère les mises à jour de code côté client, et le système de migration au démarrage (voir [Référence]({{< relref "technical.fr.md" >}})) gère les changements de schéma de base de données, si bien que les mêmes données OPFS sont simplement conservées.

```bash
npm run build      # génère les fichiers statiques dans dist/
npm run preview    # prévisualiser la build de production en local
```

TrainUs utilise le VFS `opfs-sahpool` (voir la [Référence]({{< relref "technical.fr.md" >}})), qui n'a pas strictement besoin des en-têtes COOP/COEP. Les poser tout de même est recommandé par défense en profondeur, et ils pourraient devenir nécessaires si le VFS utilisé venait à changer — le serveur de développement les pose déjà par précaution :

```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```

**Nginx**

```nginx
location / {
    add_header Cross-Origin-Opener-Policy "same-origin";
    add_header Cross-Origin-Embedder-Policy "require-corp";
}
```

**Apache**

```apache
<IfModule mod_headers.c>
    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
```

**Caddy**

```caddy
header {
    Cross-Origin-Opener-Policy "same-origin"
    Cross-Origin-Embedder-Policy "require-corp"
}
```

**Netlify** (`public/_headers`)

```
/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp
```

**Vercel** (`vercel.json`)

```json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
        { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
      ]
    }
  ]
}
```

### Liste de vérification

- [ ] `npm run build` terminé sans erreur
- [ ] `dist/` déployé à la racine du site, en remplaçant la build précédente
- [ ] En-têtes COOP/COEP configurés sur le serveur (recommandé, pas strictement requis pour `opfs-sahpool`)
- [ ] L'application se charge et OPFS s'initialise correctement
