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 :

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

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

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 :

<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.

/* 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 :

"themeMyName": "My Theme Label"

src/i18n/locales/fr.json :

"themeMyName": "Mon thème"

src/i18n/locales/da.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 :

<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

VariableAffecte
--bg-color1Fond principal de la page
--bg-color2Fond des cartes / surfaces secondaires
--front-color1Texte principal
--front-color2Texte secondaire / discret
--navbar-bg-colorFond du menu de navigation latéral
--navbar-front-colorTexte et icônes du menu latéral
--navbar-hover-bg-colorFond au survol d’un élément de menu
--navbar-hover-front-colorTexte au survol d’un élément de menu
--actionbar-bg-colorFond de la barre d’action en haut
--actionbar-font-colorTexte et icônes de la barre d’action
--border-colorBordures des cartes et des champs
--input-bgFond des champs de saisie
--input-borderBordure des champs de saisie
--btn-primary-bgFond du bouton principal
--btn-primary-colorTexte du bouton principal
--btn-danger-bgFond du bouton danger / suppression
--btn-danger-colorTexte du bouton danger
--success-colorMessages et indicateurs de succès
--warning-colorMessages 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

// 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

// 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 pour la machine à états complète de la migration et son diagramme de flux, et Comprendre l’architecture 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
  • 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) 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.

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), 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

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

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

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)

{
  "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