Des pointages tapés au bord du terrain, sur le site avant le début de la partie suivante
Montreal United offre du mentorat et du sport organisé aux jeunes de Montréal, dont deux tournois de basketball annuels. J'ai repris leur site WordPress bilingue en septembre 2026 et je leur ai bâti une deuxième chose qu'ils avaient demandée : une façon de gérer un tournoi depuis un téléphone, au gymnase. Les divisions, les poules, les parties générées et les pointages s'entrent sur le téléphone, et la page publique les reprend d'elle-même, en anglais et en français. La partie que j'ai la plus soignée est celle qui refuse, parce que la personne qui s'en sert est debout à la table des marqueurs avec une partie qui se termine derrière elle.
Montreal United est un organisme sans but lucratif qui offre du mentorat et du sport organisé aux jeunes de Montréal. Deux tournois de basketball ancrent leur année : le Lindsay A. Burnett Invitational et No Politics, Just Ball. J'ai repris le site web en septembre 2026 et je l'exploite depuis. C'est WordPress sur Divi avec WPML, ce qui veut dire que chaque page existe deux fois, une fois en anglais et une fois en français, et que les deux doivent rester synchronisées sinon le côté français pourrit en silence.

La moitié qui n'est pas le site web
La consigne qui comptait ne portait pas du tout sur le site. Pendant un tournoi, les pointages existent sur papier à la table des marqueurs et nulle part ailleurs, jusqu'à ce que quelqu'un rentre à la maison et les saisisse. Les parents dans le gymnase rafraîchissent une page qui n'a pas changé depuis jeudi. Ce que l'organisme voulait, c'était pouvoir mettre à jour les équipes et les pointages depuis un téléphone, sur le plancher, et que le site s'occupe du reste. La seconde moitié de ce mandat est donc un outil : une extension WordPress avec ses propres tables et un espace REST réservé aux administrateurs, jumelée à une application web progressive qui s'installe à partir d'un lien. Pas de magasin d'applications, pas de file d'attente de révision, pas d'étape de compilation de leur côté. On ouvre un lien, on l'ajoute à l'écran d'accueil, et ça se comporte comme une application.

Un tournoi est un brouillon jusqu'à ce que quelqu'un en décide autrement
La personne qui s'en sert est debout à la table des marqueurs, avec une partie qui se termine derrière elle et un parent qui pose une question. Ce contexte a décidé l'essentiel de la conception. Chaque tournoi commence comme un brouillon, et le site public n'affiche jamais que les tournois publiés, donc rien de ce qu'on fait dans l'application n'atteint le site public avant d'avoir appuyé sur un bouton qui le dit. Il y a un tournoi d'essai en une touche qui se remplit de parties d'exemple à la date du jour, et il porte sur chaque écran une bannière disant qu'il n'est pas sur le site et qu'on peut tout essayer. Publier et retirer sont deux boutons explicites distincts derrière une confirmation, et la confirmation avertit quand un autre tournoi est déjà en ligne ou quand le nom ressemble à un essai. Effacer tous les pointages est annulable. Renommer une équipe est annulable.

Se tromper de pointage est normal, donc corriger devait coûter aussi peu que saisir. Rouvrir une partie terminée affiche une note qui reconnaît l'erreur et permet de changer l'un ou l'autre des chiffres, et la carte enregistrée se met à jour immédiatement avec le gagnant en gras. La barre d'annulation est en haut de l'écran plutôt que dans un menu, parce que le moment où on en a besoin est celui où on a le moins envie de la chercher.

Les divisions, les poules, et les parties entre les deux
Un tournoi ici, c'est huit divisions par âge et par genre, chacune avec ses équipes, et l'édition 2026 a compté quatre-vingt-huit parties. L'application bâtit cette structure au lieu de demander à quelqu'un de la saisir : on ajoute les équipes à une division, on les répartit en poules par classement ou à la main, et on génère le tournoi à la ronde. Les classements sortent avec les bris d'égalité appliqués, et les parties croisées qui suivent résolvent leurs rangs à partir des résultats de poule au lieu de devoir être ressaisies. Regénérer est sans danger, parce que c'est un bouton distinct qui dit ce qu'il s'apprête à remplacer.

Le moment qui justifie tout
Voici tout l'intérêt du projet en une phrase : un pointage tapé à la table des marqueurs est sur le site public avant le début de la partie suivante, et personne n'a rien fait pour que ça arrive. La page publique du tournoi se rafraîchit d'elle-même, donc le parent debout dans le gymnase avec un téléphone, et le grand-parent qui suit depuis une autre ville, voient le même chiffre dans la minute. Pas d'exportation, pas de téléversement, pas de bénévole devant un portable ce soir-là.

Les deux langues, sinon ça ne sort pas
Nous sommes à Montréal, l'organisme sert des familles dans les deux langues, et une page française en retard sur l'anglaise est pire qu'une absence de page française, parce qu'elle a l'air entretenue sans l'être. Les vues de tournoi sont donc bilingues à la source : le même horaire, les mêmes divisions, poules et classements s'affichent sur /tournament/ et /fr/tournoi/, avec les noms de divisions repris des termes français de l'organisme plutôt que devinés par une machine. La suite de validation vérifie que les dates de tournoi apparaissent sur les pages d'accueil, de contact et de tournoi dans les deux langues, pour qu'une modification qui ne met à jour que l'anglais échoue avant d'atteindre qui que ce soit.

La moitié ingrate : de 2,5 secondes à environ 150 millisecondes
Le site fait tourner trente-cinq extensions, et une requête non mise en cache passait environ 2,5 secondes dans WordPress avant d'envoyer un octet, dont environ 0,8 pour WooCommerce à lui seul. Le réflexe tentant est de commencer à supprimer. Le bon réflexe, pour un organisme bénévole qui dépend de ces extensions pour les inscriptions et les paiements, était de placer une mise en cache des pages devant et de laisser la pile tranquille. J'ai ajouté un préchauffeur qui reconstruit chaque page par lots, enregistre sa progression, revérifie l'ensemble à la fin d'un passage et recommence si quelque chose a effacé des pages en cours de route. Les vingt-quatre pages publiques sont maintenant servies depuis le cache en environ 150 millisecondes.

Ce qui a été difficile
Trois choses, dont aucune que j'aurais prévue. L'hébergeur refuse tout corps de requête au-delà d'un mégaoctet : Apache répond avant que PHP ne s'exécute, donc le point d'accès média ne peut tout simplement pas recevoir une photo prise directement avec un téléphone, ce qui est exactement ce que le client essaierait en premier. J'ai mesuré la limite, 1024 Ko acceptés et 1100 Ko refusés, puis j'ai écrit un téléversement par morceaux qui reconstitue le fichier à partir de plus petites pièces, vérifie le SHA-256 du résultat contre celui envoyé, et ne l'enregistre qu'ensuite. Deuxièmement, WPML traduit les fichiers joints, donc enregistrer une page française réécrit en silence chaque identifiant de galerie vers un jumeau français pointant sur les mêmes fichiers, ce qui fait échouer toute comparaison naïve de relecture sur chaque page française contenant une galerie. Troisièmement, le constructeur de pages lançait automatiquement la première vidéo d'un onglet, sans sourdine, dès qu'un visiteur ouvrait cet onglet, et comme cliquer sur l'onglet compte comme un geste de l'utilisateur, la politique de lecture automatique du navigateur ne l'arrêtait pas. Celui-là était en ligne et personne ne l'avait signalé.
Ce que j'ai appris
La fonctionnalité dont je suis le plus fier est celle qui ne fait rien. Un tournoi d'essai, un état brouillon, une confirmation qui lit la situation et avertit quand autre chose est déjà en ligne : rien de tout ça n'apparaît dans une liste de fonctionnalités, et c'est tout ça qui fait que quelqu'un s'en servira vraiment pendant un vrai tournoi au lieu d'attendre d'être rentré. La deuxième leçon est plus ancienne et je la réapprends sans cesse. Tout ce que j'ai livré ici est vérifié par un script qui s'exécute contre le site en ligne, maintenant 104 assertions dont un aller-retour effectué avec le compte du client, parce que sur un site bilingue le mode de défaillance n'est jamais une page cassée, c'est une page française qui a cessé de correspondre à l'anglaise il y a quatre semaines.
La suite
- Les tournois 2027, les premiers où l'outil fera tourner une fin de semaine en direct dès la première partie, au lieu d'être testé contre une saison déjà terminée.
- Faire relever la limite de requête d'un mégaoctet chez l'hébergeur, pour que le client puisse téléverser une photo directement depuis un téléphone sans que le contournement par morceaux serve de béquille.
- Une galerie rotative de photos de tournoi sur les pages de tournoi, en attente des photos plutôt que du code.
- Poursuivre le travail courant dont le site vit vraiment : le contenu, les dates de tournoi, les formulaires d'inscription à chaque cycle, les mises à jour de WordPress et des extensions, les sauvegardes et la surveillance.
Montreal United: bilingual WordPress site for a Montreal youth basketball non-profit, plus a phone tool that puts tournament scores on it live
Voir le projet
