From c7a22151035a3693518502aa9445f1adc51c4b2f Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Thu, 15 Jan 2026 14:08:57 +0100 Subject: [PATCH] feat: translation competition manager part --- src/main/webapp/public/locales/en/cm.json | 143 +++++++++++++++++ src/main/webapp/public/locales/fr/cm.json | 143 +++++++++++++++++ .../src/pages/competition/editor/CMAdmin.jsx | 150 ++++++++---------- .../competition/editor/CMTChronoPanel.jsx | 32 ++-- .../competition/editor/CMTMatchPanel.jsx | 60 ++++--- .../src/pages/competition/editor/CMTPoint.jsx | 6 +- .../src/pages/competition/editor/CMTable.jsx | 25 +-- .../editor/CategoryAdminContent.jsx | 68 ++++---- .../editor/CompetitionManagerRoot.jsx | 19 ++- .../pages/competition/editor/PubAffWindow.jsx | 10 +- .../editor/SelectCombModalContent.jsx | 46 +++--- 11 files changed, 488 insertions(+), 214 deletions(-) create mode 100644 src/main/webapp/public/locales/en/cm.json create mode 100644 src/main/webapp/public/locales/fr/cm.json diff --git a/src/main/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json new file mode 100644 index 0000000..b21846e --- /dev/null +++ b/src/main/webapp/public/locales/en/cm.json @@ -0,0 +1,143 @@ +{ + "--SélectionnerUnCombattant--": "-- Select a fighter --", + "--Tous--": "-- All --", + "actuel": "Current", + "administration": "Administration", + "adresseDuServeur": "Server address", + "ajouter": "Add", + "ajouterDesCombattants": "Add fighters", + "attention": "Warning", + "aucuneConfigurationObs": "No OBS configuration found, please import one", + "bleu": "Blue", + "blue": "Blue", + "catégorie": "Category", + "chrono.+/-...S": "+/- ... s", + "chrono.+10S": "+10 s", + "chrono.+1S": "+1 s", + "chrono.-10S": "-10 s", + "chrono.-1S": "-1 s", + "chrono.arrêter": "Stop", + "chrono.définirLeTemps": "Set time", + "chrono.démarrer": "Start", + "chrono.editionTemps": "Time editing", + "chrono.entrezLeTempsEnS": "Enter time in seconds", + "chrono.recapTemps": "Time: {{temps}}, pause: {{pause}}", + "chronomètre": "Stopwatch", + "club": "Club", + "compétition": "Competition", + "compétitionManager": "Competition manager", + "config.obs.dossierDesResources": "Resources folder", + "config.obs.motDePasseDuServeur": "Server password", + "config.obs.warn1": "/! The password will be stored in plain text; it is recommended to use it only on OBS WebSocket and to change it between each competition", + "config.obs.ws": "ws://", + "configurationObs": "OBS Configuration", + "confirm1": "This match already has results; are you sure you want to delete it?", + "confirm2.msg": "Do you really want to change the tournament tree size or the loser matches? This will modify existing matches (including possible deletions)!", + "confirm2.title": "Tournament tree change", + "confirm3.msg": "Do you really want to remove the {{typeStr}} part from the category? This will delete the matches contained in this part!", + "confirm3.title": "Change category type", + "confirm4.msg": "Do you really want to delete the category {{name}}. This will delete all associated matches!", + "confirm4.title": "Delete category", + "conserverUniquementLesMatchsTerminés": "Keep only finished matches", + "contre": "vs", + "créerLesMatchs": "Create matches", + "demi-finalesEtFinales": "Semi-finals and finals", + "duréePause": "Pause duration", + "duréeRound": "Round duration", + "editionDeLaCatégorie": "Edit category", + "enregister": "Save", + "enregistrer": "Save", + "epéeBouclier": "Sword and shield", + "err1": "A fighter cannot fight themselves!", + "err2": "The combat zone name format is invalid. Please separate names with ';'.", + "err3": "At least one type (pool or tournament) must be selected.", + "erreurLorsDeLaCopieDansLePresse": "Error while copying to clipboard: ", + "erreurLorsDeLaCréationDesMatchs": "Error while creating matches: ", + "exporter": "Export", + "fermer": "Close", + "finalesUniquement": "Finals only", + "genre": "Gender", + "genre.f": "F", + "genre.h": "M", + "genre.na": "NA", + "inscrit": "Registered", + "manche": "Round", + "matchPourLesPerdantsDuTournoi": "Match for tournament losers:", + "matches": "Matches", + "modifier": "Edit", + "msg1": "There are already matches in this pool; what do you want to do with them?", + "neRienConserver": "Keep nothing", + "no": "No.", + "nom": "Name", + "nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')", + "nouvelle...": "New...", + "obs.préfixDesSources": "Source prefix", + "pays": "Country", + "poids": "Weight", + "poule": "Pool", + "poulePour": "Pool for: ", + "préparation...": "Preparing...", + "rouge": "Red", + "réinitialiser": "Reset", + "résultat": "Result", + "sansPoule": "No pool", + "sauvegarde": "Backup", + "sauvegarder": "Save", + "score": "Score", + "score.err1": "Cannot end a tournament match in a draw.", + "score.spe": "Special scores:
-997: disqualified
-998: absent
-999: forfeit", + "scores": "Scores", + "secrétariatsDeLice": "Ring secretariats", + "select.aucunCombattantDisponible": "No fighters available", + "select.aucunCombattantSélectionné": "No fighters selected", + "select.msg1": "(0 = disabled)", + "select.recherche": "Search", + "select.sélectionnerDesCombatants": "Select fighters", + "select.à": "to", + "serveur": "Server", + "suivant": "Next", + "supprimer": "Delete", + "sélectionneLesModesDaffichage": "Select display modes", + "sélectionner": "Select", + "terminé": "Finished", + "texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.", + "toast.createCategory.error": "Error while creating the category", + "toast.createCategory.pending": "Creating category...", + "toast.createCategory.success": "Category created!", + "toast.deleteCategory.error": "Error while deleting the category", + "toast.deleteCategory.pending": "Deleting category...", + "toast.deleteCategory.success": "Category deleted!", + "toast.matchs.create.error": "Error while creating matches.", + "toast.matchs.create.pending": "Creating matches in progress...", + "toast.matchs.create.success": "Matches created successfully.", + "toast.updateCategory.error": "Error while updating the category", + "toast.updateCategory.pending": "Updating category...", + "toast.updateCategory.success": "Category updated!", + "toast.updateMatchScore.error": "Error while updating match score", + "toast.updateMatchScore.pending": "Updating match score...", + "toast.updateMatchScore.success": "Match score updated!", + "toast.updateTrees.error": "Error while updating trees", + "toast.updateTrees.init.error": "Error while creating trees", + "toast.updateTrees.init.pending": "Creating tournament trees...", + "toast.updateTrees.init.success": "Trees created!", + "toast.updateTrees.pending": "Updating tournament trees...", + "toast.updateTrees.success": "Trees updated!", + "tournoi": "Tournament", + "tournois": "Tournaments", + "tousLesMatchs": "All matches", + "toutConserver": "Keep all", + "ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration", + "ttm.admin.scripte": "Copy integration script", + "ttm.table.inverserLaPosition": "Reverse fighter positions on this screen", + "ttm.table.obs": "Short click: Load configuration and connect. Long click: Ring configuration", + "ttm.table.pub_aff": "Open public display", + "ttm.table.pub_score": "Show scores on public display", + "type": "Type", + "téléchargement": "Download", + "téléchargementEnCours": "Downloading...", + "téléchargementTerminé!": "Download completed!", + "uneCatégorie": "a category", + "valider": "Validate", + "zone": "Zone", + "zoneDeCombat": "Combat zone" +} diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json new file mode 100644 index 0000000..6f620c6 --- /dev/null +++ b/src/main/webapp/public/locales/fr/cm.json @@ -0,0 +1,143 @@ +{ + "--SélectionnerUnCombattant--": "-- Sélectionner un combattant --", + "--Tous--": "-- Tous --", + "actuel": "Actuel", + "administration": "Administration", + "adresseDuServeur": "Adresse du serveur", + "ajouter": "Ajouter", + "ajouterDesCombattants": "Ajouter des combattants", + "attention": "Attention", + "aucuneConfigurationObs": "Aucune configuration OBS trouvée, veuillez en importer une", + "bleu": "Bleu", + "blue": "Blue", + "catégorie": "Catégorie", + "chrono.+/-...S": "+/- ... s", + "chrono.+10S": "+10 s", + "chrono.+1S": "+1 s", + "chrono.-10S": "-10 s", + "chrono.-1S": "-1 s", + "chrono.arrêter": "Arrêter", + "chrono.définirLeTemps": "Définir le temps", + "chrono.démarrer": "Démarrer", + "chrono.editionTemps": "Edition temps", + "chrono.entrezLeTempsEnS": "Entrez le temps en s", + "chrono.recapTemps": "Temps: {{temps}}, pause: {{pause}}", + "chronomètre": "Chronomètre", + "club": "Club", + "compétition": "Compétition", + "compétitionManager": "Compétition manager", + "config.obs.dossierDesResources": "Dossier des resources", + "config.obs.motDePasseDuServeur": "Mot de passe du serveur", + "config.obs.warn1": "/! Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en changer entre chaque compétition", + "config.obs.ws": "ws://", + "configurationObs": "Configuration OBS", + "confirm1": "Ce match a déjà des résultats, êtes-vous sûr de vouloir le supprimer ?", + "confirm2.msg": "Voulez-vous vraiment changer la taille de l'arbre du tournoi ou les matchs pour les perdants ? Cela va modifier les matchs existants (incluant des possibles suppressions)!", + "confirm2.title": "Changement de l'arbre du tournoi", + "confirm3.msg": "Voulez-vous vraiment enlever la partie {{typeStr}} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !", + "confirm3.title": "Changement de type de catégorie", + "confirm4.msg": "Voulez-vous vraiment supprimer la catégorie {{name}}. Cela va supprimer tous les matchs associés !", + "confirm4.title": "Suppression de la catégorie", + "conserverUniquementLesMatchsTerminés": "Conserver uniquement les matchs terminés", + "contre": "contre", + "créerLesMatchs": "Créer les matchs", + "demi-finalesEtFinales": "Demi-finales et finales", + "duréePause": "Durée pause", + "duréeRound": "Durée round", + "editionDeLaCatégorie": "Edition de la catégorie", + "enregister": "Enregister", + "enregistrer": "Enregistrer", + "epéeBouclier": "Epée bouclier", + "err1": "Un combattant ne peut pas s'affronter lui-même !", + "err2": "Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.", + "err3": "Au moins un type (poule ou tournoi) doit être sélectionné.", + "erreurLorsDeLaCopieDansLePresse": "Erreur lors de la copie dans le presse-papier : ", + "erreurLorsDeLaCréationDesMatchs": "Erreur lors de la création des matchs: ", + "exporter": "Exporter", + "fermer": "Fermer", + "finalesUniquement": "Finales uniquement", + "genre": "Genre", + "genre.f": "F", + "genre.h": "H", + "genre.na": "NA", + "inscrit": "Inscrit", + "manche": "Manche", + "matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:", + "matches": "Matches", + "modifier": "Modifier", + "msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?", + "neRienConserver": "Ne rien conserver", + "no": "N°", + "nom": "Nom", + "nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')", + "nouvelle...": "Nouvelle...", + "obs.préfixDesSources": "Préfix des sources", + "pays": "Pays", + "poids": "Poids", + "poule": "Poule", + "poulePour": "Poule pour: ", + "préparation...": "Préparation...", + "rouge": "Rouge", + "réinitialiser": "Réinitialiser", + "résultat": "Résultat", + "sansPoule": "Sans poule", + "sauvegarde": "Sauvegarde", + "sauvegarder": "Sauvegarder", + "score": "Score", + "score.err1": "Impossible de terminer un match nul en tournois.", + "score.spe": "Score speciaux :
-997 : disqualifié
-998 : absent
-999 : forfait", + "scores": "Scores", + "secrétariatsDeLice": "Secrétariats de lice", + "select.aucunCombattantDisponible": "Aucun combattant disponible", + "select.aucunCombattantSélectionné": "Aucun combattant sélectionné", + "select.msg1": "(0 = désactivé)", + "select.recherche": "Recherche", + "select.sélectionnerDesCombatants": "Sélectionner des combatants", + "select.à": "à", + "serveur": "Serveur", + "suivant": "Suivant", + "supprimer": "Supprimer", + "sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage", + "sélectionner": "Sélectionner", + "terminé": "Terminé", + "texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.", + "toast.createCategory.error": "Erreur lors de la création de la catégorie", + "toast.createCategory.pending": "Création de la catégorie...", + "toast.createCategory.success": "Catégorie créée !", + "toast.deleteCategory.error": "Erreur lors de la suppression de la catégorie", + "toast.deleteCategory.pending": "Suppression de la catégorie...", + "toast.deleteCategory.success": "Catégorie supprimée !", + "toast.matchs.create.error": "Erreur lors de la création des matchs.", + "toast.matchs.create.pending": "Création des matchs en cours...", + "toast.matchs.create.success": "Matchs créés avec succès.", + "toast.updateCategory.error": "Erreur lors de la mise à jour de la catégorie", + "toast.updateCategory.pending": "Mise à jour de la catégorie...", + "toast.updateCategory.success": "Catégorie mise à jour !", + "toast.updateMatchScore.error": "Erreur lors de la mise à jour du score du match", + "toast.updateMatchScore.pending": "Mise à jour du score du match...", + "toast.updateMatchScore.success": "Score du match mis à jour !", + "toast.updateTrees.error": "Erreur lors de la mise à jour des arbres", + "toast.updateTrees.init.error": "Erreur lors de la création des arbres", + "toast.updateTrees.init.pending": "Création des arbres du tournoi...", + "toast.updateTrees.init.success": "Arbres créés !", + "toast.updateTrees.pending": "Mise à jour des arbres du tournoi...", + "toast.updateTrees.success": "Arbres mis à jour !", + "tournoi": "Tournoi", + "tournois": "Tournois", + "tousLesMatchs": "Tous les matchs", + "toutConserver": "Tout conserver", + "ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs", + "ttm.admin.scripte": "Copier le scripte d'intégration", + "ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran", + "ttm.table.obs": "Clique court : Charger la configuration et se connecter. Clique long : Configuration de la lice", + "ttm.table.pub_aff": "Ouvrir l'affichage public", + "ttm.table.pub_score": "Afficher les scores sur l'affichage public", + "type": "Type", + "téléchargement": "Téléchargement", + "téléchargementEnCours": "Téléchargement en cours...", + "téléchargementTerminé!": "Téléchargement terminé !", + "uneCatégorie": "une catégorie", + "valider": "Valider", + "zone": "Zone", + "zoneDeCombat": "Zone de combat" +} diff --git a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index bbbaf72..ced95a6 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -12,6 +12,9 @@ import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts"; import JSZip from "jszip"; import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx"; import {faGlobe} from "@fortawesome/free-solid-svg-icons"; +import {Trans, useTranslation} from "react-i18next"; +import i18n from "i18next"; +import {getToastMessage} from "../../../utils/Tools.js"; const vite_url = import.meta.env.VITE_URL; @@ -139,7 +142,7 @@ async function downloadResourcesAsZip(resourceList) { completed++; const progress = Math.round((completed / resourceList.length) * 100); progressBar.style.width = `${progress}%`; - progressText.textContent = `Téléchargement (${completed}/${resourceList.length}) : ${result.filename}`; + progressText.textContent = `${i18n.t('téléchargement')} (${completed}/${resourceList.length}) : ${result.filename}`; return result; }) ); @@ -155,13 +158,14 @@ async function downloadResourcesAsZip(resourceList) { // Fermer la modale modal.hide(); - progressText.textContent = "Téléchargement terminé !"; + progressText.textContent = i18n.t('téléchargementTerminé!'); } function Menu({menuActions, compUuid}) { const e = document.getElementById("actionMenu") const longPress = useRef({time: null, timer: null, button: null}); const obsModal = useRef(null); + const {t} = useTranslation("cm"); for (const x of tto) x.dispose(); @@ -224,9 +228,9 @@ function Menu({menuActions, compUuid}) { initCompetitionApi("${vite_url}/api/public/result/${compUuid}") ` ).then(() => { - toast.success("Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress."); + toast.success(t('texteCopiéDansLePresse')); }).catch(err => { - toast.error("Erreur lors de la copie dans le presse-papier : " + err); + toast.error(t('erreurLorsDeLaCopieDansLePresse') + err); }); } @@ -241,12 +245,12 @@ function Menu({menuActions, compUuid}) { onMouseDown={() => longPressDown("obs")} onMouseUp={() => longPressUp("obs")} data-bs-toggle="tooltip2" data-bs-placement="top" - data-bs-title="Clique court : Télécharger les ressources. Clique long : Créer la configuration obs"/> + data-bs-title={t('ttm.admin.obs')}/> copyScriptToClipboard()} data-bs-toggle="tooltip2" data-bs-placement="top" - data-bs-title="Copier le scripte d'intégration"/> + data-bs-title={t('ttm.admin.scripte')}/> , document.getElementById("actionMenu"))}
- /!\ Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en - changer entre chaque compétition + {t('config.obs.warn1')}
- Adresse du serveur - ws:// + {t('adresseDuServeur')} + {t('config.obs.ws')} /
- Mot de passe du serveur + {t('config.obs.motDePasseDuServeur')}
- Dossier des resources + {t('config.obs.dossierDesResources')}
- - + +
@@ -293,14 +296,14 @@ function Menu({menuActions, compUuid}) {
-
Téléchargement en cours...
+
{t('téléchargementEnCours')}
-
Préparation...
+
{t('préparation...')}
@@ -314,6 +317,7 @@ function CategoryHeader({cat, setCatId}) { const confirmRef = useRef(); const [modal, setModal] = useState({}) const [confirm, setConfirm] = useState({}) + const {t} = useTranslation("cm"); const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading); const {dispatch} = useWS(); @@ -368,17 +372,17 @@ function CategoryHeader({cat, setCatId}) { return
-
Edition de la catégorie
+
{t('editionDeLaCatégorie')}
{cat && -
Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} | +
Type: {(cat.type & 1) !== 0 ? t('poule') : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? t('tournois') : ""} | Zone: {cat.liceName}
}
@@ -409,17 +413,18 @@ function CategoryHeader({cat, setCatId}) { function ModalContent({state, setCatId, setConfirm, confirmRef}) { const [name, setName] = useState("") - const [lice, setLice] = useState("A") + const [lice, setLice] = useState("1") const [poule, setPoule] = useState(true) const [tournoi, setTournoi] = useState(false) const [size, setSize] = useState(4) const [loserMatch, setLoserMatch] = useState(1) + const {t} = useTranslation("cm"); const {sendRequest} = useWS(); useEffect(() => { setName(state.name || ""); - setLice(state.liceName || "A"); + setLice(state.liceName || "1"); setPoule(((state.type || 1) & 1) !== 0); setTournoi((state.type & 2) !== 0); @@ -445,13 +450,13 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { const regex = /^([^;]+;)*[^;]+$/; if (regex.test(lice.trim()) === false) { - toast.error("Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'."); + toast.error(t('err2')); return; } const nType = (poule ? 1 : 0) + (tournoi ? 2 : 0); if (nType === 0) { - toast.error("Au moins un type (poule ou tournoi) doit être sélectionné."); + toast.error(t('err3')); return; } @@ -479,8 +484,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { console.log(tournoi, size, nbMatch, loserMatch, oldSubTree); if (tournoi && (size !== nbMatch * 2 || loserMatch !== oldSubTree)) { setConfirm({ - title: "Changement de l'arbre du tournoi", - message: `Voulez-vous vraiment changer la taille de l'arbre du tournoi ou les matchs pour les perdants ? Cela va modifier les matchs existants (incluant des possibles suppressions)!`, + title: t('confirm2.title'), + message: t('confirm2.msg'), confirm: () => { const trees2 = build_tree(size, loserMatch) const newTrees = [] @@ -495,48 +500,30 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { newTrees.push(trees2.at(i)); } - toast.promise(sendRequest('updateTrees', {categoryId: state.id, trees: newTrees}), - { - pending: 'Mise à jour des arbres du tournoi...', - success: 'Arbres mis à jour !', - error: 'Erreur lors de la mise à jour des arbres' - } + toast.promise(sendRequest('updateTrees', {categoryId: state.id, trees: newTrees}), getToastMessage("toast.updateTrees") ).then(__ => { - toast.promise(sendRequest('updateCategory', newData), - { - pending: 'Mise à jour de la catégorie...', - success: 'Catégorie mise à jour !', - error: 'Erreur lors de la mise à jour de la catégorie' - } - ) + toast.promise(sendRequest('updateCategory', newData), getToastMessage("toast.updateCategory")) }) } }) confirmRef.current.click(); } else { - toast.promise(sendRequest('updateCategory', newData), - { - pending: 'Mise à jour de la catégorie...', - success: 'Catégorie mise à jour !', - error: 'Erreur lors de la mise à jour de la catégorie' - } - ) + toast.promise(sendRequest('updateCategory', newData), getToastMessage("toast.updateCategory")) } } if (nType !== state.type) { let typeStr = ""; if ((state.type & 1) !== 0 && (nType & 1) === 0) - typeStr += "poule "; + typeStr += `${t('poule').toLowerCase()} `; if ((state.type & 2) !== 0 && (nType & 2) === 0) - typeStr += "tournoi "; + typeStr += `${t('tournoi').toLowerCase()} `; setConfirm({ - title: "Changement de type de catégorie", - message: `Voulez-vous vraiment enlever la partie ${typeStr} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !`, + title: t('confirm3.title'), + message: t('confirm3.msg', {typeStr: typeStr.trim()}), confirm: () => { - setTimeout(() => - applyChanges(), 500); + setTimeout(() => applyChanges(), 500); } }) confirmRef.current.click(); @@ -544,23 +531,17 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { applyChanges(); } } else { - toast.promise(sendRequest('createCategory', {name: name.trim(), liceName: lice.trim(), type: nType}), - { - pending: 'Création de la catégorie...', - success: 'Catégorie créée !', - error: 'Erreur lors de la création de la catégorie' - } + toast.promise(sendRequest('createCategory', { + name: name.trim(), + liceName: lice.trim(), + type: nType + }), getToastMessage("toast.createCategory") ).then(id => { if (tournoi) { const trees = build_tree(size, loserMatch) console.log("Creating trees for new category:", trees); - toast.promise(sendRequest('updateTrees', {categoryId: id, trees: trees}), - { - pending: 'Création des arbres du tournoi...', - success: 'Arbres créés !', - error: 'Erreur lors de la création des arbres' - } + toast.promise(sendRequest('updateTrees', {categoryId: id, trees: trees}), getToastMessage("toast.updateTrees.init") ).finally(() => setCatId(id)) } else { setCatId(id); @@ -580,36 +561,36 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { return
-

{state.id === undefined ? "Ajouter" : "Modifier"} une catégorie

+

{state.id === undefined ? t('ajouter') : t('modifier')} {t('uneCatégorie')}

- - {t('nom')} + setName(e.target.value)}/>
- - t (séparée par des ';') + setLice(e.target.value)}/>
- +
setPoule(e.target.checked)}/> - +
setTournoi(e.target.checked)}/> - +
@@ -620,14 +601,14 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
- Match pour les perdants du tournoi: + {t('matchPourLesPerdantsDuTournoi')}
{ if (e.target.checked) setLoserMatch(-1) }}/>
@@ -636,7 +617,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { if (e.target.checked) setLoserMatch(1) }}/>
@@ -645,31 +626,26 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { if (e.target.checked) setLoserMatch(0) }}/>
- - + + {state.id !== undefined && } + }}>{t('supprimer')}}
} diff --git a/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx b/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx index 2c744b5..02d9fa0 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx @@ -1,6 +1,7 @@ import React, {useEffect, useRef, useState} from "react"; import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx"; import {timePrint} from "../../../utils/Tools.js"; +import {useTranslation} from "react-i18next"; export function ChronoPanel() { const [config, setConfig] = useState({ @@ -11,6 +12,7 @@ export function ChronoPanel() { const chronoText = useRef(null) const state = useRef({chronoState: 0, countBlink: 20, lastColor: "#000000", lastTimeStr: "00:00"}) const publicAffDispatch = usePubAffDispatch(); + const {t} = useTranslation("cm"); const addTime = (time) => setChrono(prev => ({...prev, time: prev.time - time})) const isRunning = () => chrono.startTime !== 0 @@ -111,11 +113,11 @@ export function ChronoPanel() { onClick={__ => isRunning() ? setChrono(prev => ({...prev, time: prev.time + Date.now() - prev.startTime, startTime: 0})) : setChrono(prev => ({...prev, startTime: Date.now()}))}> - {isRunning() ? "Arrêter" : "Démarrer"} + {isRunning() ? t('chrono.arrêter') : t('chrono.démarrer')}
@@ -125,29 +127,29 @@ export function ChronoPanel() {
- - + +
- - + +
-
Temps: {timePrint(config.time)}, pause: {timePrint(config.pause)}
+
{t('chrono.recapTemps', {temps: timePrint(config.time), pause: timePrint(config.pause)})}
-
@@ -155,25 +157,25 @@ export function ChronoPanel() {
-
Edition temps
+
{t('chrono.editionTemps')}
- Durée round + {t('duréeRound')} (mm:ss)
- Durée pause + {t('duréePause')} (mm:ss)
- - + +
diff --git a/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx index 93cb135..db2cc35 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx @@ -6,12 +6,13 @@ import {DrawGraph} from "../../result/DrawGraph.jsx"; import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; import {useRequestWS, useWS} from "../../../hooks/useWS.jsx"; import {MarchReducer} from "../../../utils/MatchReducer.jsx"; -import {scorePrint, win} from "../../../utils/Tools.js"; +import {getToastMessage, scorePrint, win} from "../../../utils/Tools.js"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons"; import {toast} from "react-toastify"; import "./CMTMatchPanel.css" import {useOBS} from "../../../hooks/useOBS.jsx"; +import {useTranslation} from "react-i18next"; function CupImg() { return { const categoryListener = ({data}) => { @@ -50,12 +52,12 @@ export function CategorieSelect({catId, setCatId, menuActions}) { const cat = cats?.find(c => c.id === catId); useEffect(() => { - setText("poule",cat ? cat.name : ""); + setText("poule", cat ? cat.name : ""); }, [cat, connected]); return <>
-
Catégorie
+
{t('catégorie')}
{ setLice(e.target.value); localStorage.setItem("cm_lice", e.target.value); @@ -259,10 +263,10 @@ function MatchList({matches, cat, menuActions}) { Z P - N° + {t('no')} - Rouge - Blue + {t('rouge')} + {t('blue')} @@ -288,7 +292,8 @@ function MatchList({matches, cat, menuActions}) {
- {activeMatch && } + {activeMatch && + } } @@ -362,7 +367,8 @@ function BuildTree({treeData, matches, menuActions}) {
{currentMatch?.matchSelect && - } + } } @@ -379,6 +385,7 @@ function ScorePanel({matchId, matchs, match, menuActions}) { function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { const {sendRequest} = useWS() const setLoading = useLoadingSwitcher() + const {t} = useTranslation("cm"); const [end, setEnd] = useState(match?.end || false) const [scoreIn, setScoreIn] = useState("") @@ -396,13 +403,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { menuActions.current.saveScore = (scoreRed, scoreBlue) => { const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0; const newScore = {n_round: maxRound, s1: scoreRed, s2: scoreBlue}; - toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}), - { - pending: 'Sauvegarde du score...', - success: 'Score sauvegardé !', - error: 'Erreur lors de la sauvegarde du score' - } - ); + toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}), getToastMessage("toast.updateMatchScore")); } return () => menuActions.current.saveScore = undefined; }, [matchId]) @@ -443,8 +444,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round); - console.log("Updating score", matchId, round, comb, scoreIn_, score); - let newScore; if (score) { if (comb === 1) @@ -460,8 +459,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { return } - console.log("Updating score", matchId, newScore); - setLoading(1) sendRequest('updateMatchScore', {matchId: matchId, ...newScore}) .finally(() => { @@ -485,7 +482,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { if (end) { if (win(match?.scores) === 0 && match.categorie_ord === -42) { - toast.error("Impossible de terminer un match nul en tournois."); + toast.error(t('score.err1')); setEnd(false); return; } @@ -529,21 +526,18 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) { const o = [...tooltipTriggerList] o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) - const tt = "Score speciaux :
" + - "-997 : disqualifié
" + - "-998 : absent
" + - "-999 : forfait" + const tt = t('score.spe') const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0; return
-
Scores {t('scores')}
- - - + + + @@ -568,7 +562,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
setEnd(e.target.checked)}/> - +
{ setRevers(!revers) @@ -44,8 +46,8 @@ export function PointPanel({menuActions}) { {revers && red}
- - + +
} diff --git a/src/main/webapp/src/pages/competition/editor/CMTable.jsx b/src/main/webapp/src/pages/competition/editor/CMTable.jsx index f0d6980..9e2dc71 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTable.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTable.jsx @@ -14,12 +14,14 @@ import {PointPanel} from "./CMTPoint.jsx"; import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS.jsx"; import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts"; import {toast} from "react-toastify"; +import {useTranslation} from "react-i18next"; export function CMTable() { const combDispatch = useCombsDispatch() const [catId, setCatId] = useState(-1); const menuActions = useRef({}); const {data} = useRequestWS("getRegister", null) + const {t} = useTranslation("cm"); useEffect(() => { if (data === null) @@ -33,14 +35,14 @@ export function CMTable() {
-
Chronomètre
+
{t('chronomètre')}
-
Score
+
{t('score')}
@@ -48,7 +50,7 @@ export function CMTable() {
-
Matches
+
{t('matches')}
@@ -74,6 +76,7 @@ function Menu({menuActions}) { const {connected, connect, disconnect} = useOBS(); const longPress = useRef({time: null, timer: null, button: null}); const obsModal = useRef(null); + const {t} = useTranslation("cm"); const externalWindow = useRef(null) const containerEl = useRef(document.createElement("div")) @@ -154,7 +157,7 @@ function Menu({menuActions}) { connect("ws://" + config.adresse + "/", config.password, config.assets_dir); }) .catch(() => { - toast.error("Aucune configuration OBS trouvée, veuillez en importer une"); + toast.error(t('aucuneConfigurationObs')); }); } } @@ -182,21 +185,21 @@ function Menu({menuActions}) {
+ data-bs-title={t('ttm.table.inverserLaPosition')}/> longPressDown("obs")} onMouseUp={() => longPressUp("obs")} data-bs-toggle="tooltip2" data-bs-placement="top" - data-bs-title="Clique court : Charger la configuration et se connecter. Clique long : Configuration de la lice"/> + data-bs-title={t('ttm.table.obs')}/>
+ data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title={t('ttm.table.pub_aff')}/> + data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title={t('ttm.table.pub_score')}/> , document.getElementById("actionMenu"))} {externalWindow.current && createPortal(, containerEl.current)} @@ -213,15 +216,15 @@ function Menu({menuActions}) {
- Préfix des sources + {t('obs.préfixDesSources')} sub
- - + +
diff --git a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx index e54ad8e..9840be8 100644 --- a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx @@ -15,7 +15,8 @@ import {CSS} from '@dnd-kit/utilities'; import {toast} from "react-toastify"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faTrash} from "@fortawesome/free-solid-svg-icons"; -import {win} from "../../../utils/Tools.js"; +import {getToastMessage, win} from "../../../utils/Tools.js"; +import {useTranslation} from "react-i18next"; const vite_url = import.meta.env.VITE_URL; @@ -169,6 +170,7 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) { const combDispatch = useCombsDispatch() const {dispatch} = useWS() const [modalId, setModalId] = useState(null) + const {t} = useTranslation("cm"); useEffect(() => { const sendRegister = ({data}) => { @@ -218,7 +220,7 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) { return <>
MancheRougeBleu{t('manche')}{t('rouge')}{t('bleu')}
- - - + + + - - + + - + @@ -637,7 +638,7 @@ function MatchList({matches, cat, groups, reducer}) { setCombSelect(Number(e.target.value))}> - + {combsIDs.map((combId) => ( ))} diff --git a/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx b/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx index 2a535e5..8c97604 100644 --- a/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx +++ b/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx @@ -9,12 +9,14 @@ import {CMTable} from "./CMTable.jsx"; import {ThreeDots} from "react-loader-spinner"; import {AxiosError} from "../../../components/AxiosError.jsx"; import {useFetch} from "../../../hooks/useFetch.js"; +import {useTranslation} from "react-i18next"; const vite_url = import.meta.env.VITE_URL; export default function CompetitionManagerRoot() { + const {t} = useTranslation("cm"); return <> -

Compétition manager

+

{t('compétitionManager')}

}/> @@ -40,9 +42,10 @@ function Home() { } function MakeCentralPanel({data, navigate}) { + const {t} = useTranslation("cm"); return <>
-

Compétition:

+

{t('compétition')}:

{data.sort((a, b) => new Date(b.date.split('T')[0]) - new Date(a.date.split('T')[0])).map((o) => (
  • { const timer = setInterval(() => { @@ -91,7 +95,7 @@ function WSStatus({setPerm}) { return

    {welcomeData.name}

    -
    Serveur: {t('serveur')}:
    @@ -100,16 +104,17 @@ function WSStatus({setPerm}) { function Home2({perm}) { const nav = useNavigate(); + const {t} = useTranslation("cm"); return
    -

    Sélectionne les modes d'affichage

    +

    {t('sélectionneLesModesDaffichage')}

    {perm === "ADMIN" && <> - - + + } {perm === "TABLE" && <> - + }
    diff --git a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx index 3b8cd31..a798cd6 100644 --- a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx +++ b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx @@ -3,6 +3,7 @@ import {usePubAffState} from "../../../hooks/useExternalWindow.jsx"; import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx"; import {useMemo, useRef} from 'react'; import {useWS} from "../../../hooks/useWS.jsx"; +import {useTranslation} from "react-i18next"; const vite_url = import.meta.env.VITE_URL; @@ -17,6 +18,7 @@ export function PubAffWindow({document}) { const chronoText = useRef(null) const state2 = useRef({lastColor: "#ffffff", lastTimeStr: "--:--"}) const state = usePubAffState(); + const {t} = useTranslation("cm"); document.title = "Affichage Public"; document.body.className = "bg-dark text-white overflow-hidden"; @@ -55,24 +57,24 @@ export function PubAffWindow({document}) {
    - Actuel + {t('actuel')}
    - contre + {t('contre')}
    - Suivant + {t('suivant')}
    - contre + {t('contre')}
    diff --git a/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx b/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx index 0240fe3..a247106 100644 --- a/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx @@ -3,6 +3,7 @@ import {useEffect, useReducer, useState} from "react"; import {CatList, getCatName} from "../../../utils/Tools.js"; import {CombName} from "../../../hooks/useComb.jsx"; import {useWS} from "../../../hooks/useWS.jsx"; +import {useTranslation} from "react-i18next"; function SelectReducer(state, action) { switch (action.type) { @@ -54,6 +55,7 @@ function SelectReducer(state, action) { export function SelectCombModalContent({data, setGroups}) { const country = useCountries('fr') + const {t} = useTranslation("cm"); const {dispatch} = useWS() const [dispo, dispoReducer] = useReducer(SelectReducer, {}) const [select, selectReducer] = useReducer(SelectReducer, {}) @@ -153,20 +155,20 @@ export function SelectCombModalContent({data, setGroups}) { return <>
    -

    Sélectionner des combatants

    +

    {t('select.sélectionnerDesCombatants')}

    - + setSearch(e.target.value)}/>
    - + setClub(e.target.value)}> - + {clubList.sort((a, b) => a.localeCompare(b)).map((club) => ( ))} @@ -189,50 +191,50 @@ export function SelectCombModalContent({data, setGroups}) {
    - +
    setGender((prev) => { return {...prev, H: e.target.checked} })}/> - +
    setGender((prev) => { return {...prev, F: e.target.checked} })}/> - +
    setGender((prev) => { return {...prev, NA: e.target.checked} })}/> - +
    - +
    - +
    setWeightMin(Number(e.target.value))}/>
    -
    à
    +
    {t('select.à')}
    setWeightMax(Number(e.target.value))}/>
    -
    (0 = désactivé)
    +
    {t('select.msg1')}
    @@ -240,9 +242,9 @@ export function SelectCombModalContent({data, setGroups}) {
    -
    Inscrit
    +
    {t('inscrit')}
    - {dispoFiltered && Object.keys(dispoFiltered).length === 0 &&
    Aucun combattant disponible
    } + {dispoFiltered && Object.keys(dispoFiltered).length === 0 &&
    {t('select.aucunCombattantDisponible')}
    } {Object.keys(dispoFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
    -
    Sélectionner
    +
    {t('sélectionner')}
    - {selectFiltered && Object.keys(selectFiltered).length === 0 &&
    Aucun combattant sélectionné
    } + {selectFiltered && Object.keys(selectFiltered).length === 0 &&
    {t('select.aucunCombattantSélectionné')}
    } {Object.keys(selectFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
    - +
    - + { if (/^[a-zA-Z0-9]$/.test(e.target.value)) setTargetGroupe(e.target.value) }}/> - +
    }
  • PouleZone{t('no')}{t('poule')}{t('zone')} RougeBlue{t('rouge')}{t('blue')} Résultat{t('résultat')}