diff --git a/Front-end/package-lock.json b/Front-end/package-lock.json index 4ac76c5..102cc6d 100644 --- a/Front-end/package-lock.json +++ b/Front-end/package-lock.json @@ -13,12 +13,15 @@ "@emotion/styled": "^11.14.0", "@mui/material": "^7.0.1", "axios": "^1.8.4", + "i18next": "^26.0.1", + "i18next-browser-languagedetector": "^8.2.1", "jwt-decode": "^4.0.0", "lucide-react": "^0.427.0", "react": "^18.3.1", "react-charts": "^3.0.0-beta.57", "react-circle-progress-bar": "^0.1.4", "react-dom": "^18.3.1", + "react-i18next": "^17.0.1", "react-router-dom": "^7.4.0", "recharts": "^2.15.1" }, @@ -266,13 +269,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } @@ -3807,6 +3807,56 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.1.tgz", + "integrity": "sha512-vtz5sXU4+nkCm8yEU+JJ6yYIx0mkg9e68W0G0PXpnOsmzLajNsW5o28DJMqbajxfsfq0gV3XdrBudsDQnwxfsQ==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5259,6 +5309,33 @@ "react": "^18.3.1" } }, + "node_modules/react-i18next": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.1.tgz", + "integrity": "sha512-iG65FGnFHcYyHNuT01ukffYWCOBFTWSdVD8EZd/dCVWgtjFPObcSsvYYNwcsokO/rDcTb5d6D8Acv8MrOdm6Hw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 26.0.1", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5421,12 +5498,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -6307,6 +6378,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6468,6 +6548,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/Front-end/package.json b/Front-end/package.json index 36bfe68..6c314e3 100644 --- a/Front-end/package.json +++ b/Front-end/package.json @@ -14,12 +14,15 @@ "@emotion/styled": "^11.14.0", "@mui/material": "^7.0.1", "axios": "^1.8.4", + "i18next": "^26.0.1", + "i18next-browser-languagedetector": "^8.2.1", "jwt-decode": "^4.0.0", "lucide-react": "^0.427.0", "react": "^18.3.1", "react-charts": "^3.0.0-beta.57", "react-circle-progress-bar": "^0.1.4", "react-dom": "^18.3.1", + "react-i18next": "^17.0.1", "react-router-dom": "^7.4.0", "recharts": "^2.15.1" }, diff --git a/Front-end/src/components/BatterieInfo.jsx b/Front-end/src/components/BatterieInfo.jsx index 642fe31..4a0b2b9 100644 --- a/Front-end/src/components/BatterieInfo.jsx +++ b/Front-end/src/components/BatterieInfo.jsx @@ -1,8 +1,10 @@ import React from "react"; import { Battery } from "lucide-react"; import Progress from "react-circle-progress-bar"; +import { useTranslation } from "react-i18next"; function BatterieInfo({ object }) { + const { t } = useTranslation(); return (
@@ -10,13 +12,13 @@ function BatterieInfo({ object }) {

- Etat de la batterie + {t('components.batterieInfo.title')}

- Type de batterie :{" "} + {t('components.batterieInfo.batteryType')}{" "} {object.type_batterie}

diff --git a/Front-end/src/components/FormNewObject.jsx b/Front-end/src/components/FormNewObject.jsx index 10c93c8..fa62272 100644 --- a/Front-end/src/components/FormNewObject.jsx +++ b/Front-end/src/components/FormNewObject.jsx @@ -3,8 +3,10 @@ import { BadgePlus } from "lucide-react"; import axios from "axios"; import { API_BASE_URL } from "../config"; import { useAuth } from "../AuthContext"; +import { useTranslation } from "react-i18next"; function FormNewObject({ isAdmin }) { + const { t } = useTranslation(); const { user } = useAuth(); const [categorie, setCategorie] = useState(); const [description, setDescription] = useState(""); @@ -35,12 +37,12 @@ function FormNewObject({ isAdmin }) { proprio_id, }) .then((response) => { - setMessRequete("Votre objet à bien été enregistré !"); + setMessRequete(t('components.formNewObject.successRecord')); setEnregistre(true); console.log("Ajout de l'objet réussit :", response.data); }) .catch((error) => { - setMessRequete("Il y a eu une erreur dans l'ajout de votre objet !"); + setMessRequete(t('components.formNewObject.errorRecord')); console.error("Erreur lors de l'ajout de l'objet :", error); }); setVerif(false); @@ -54,7 +56,7 @@ function FormNewObject({ isAdmin }) { .get(`${API_BASE_URL}/getCategories`) .then((response) => { if (response.data.length === 0) { - console.warn("Aucune catégorie disponible."); + console.warn(t('components.formNewObject.noCategory')); } else { setCategorie(response.data); } @@ -93,7 +95,7 @@ function FormNewObject({ isAdmin }) {
{isAdmin ? (

- Ajouter un nouvel objet + {t('components.formNewObject.addTitle')}

) : ( <> @@ -102,8 +104,8 @@ function FormNewObject({ isAdmin }) {

{!verif - ? "Entrez les données de votre nouvel objet" - : "Êtes-vous sûr de ces données ?"} + ? t('components.formNewObject.enterData') + : t('components.formNewObject.confirmData')}

)} @@ -113,7 +115,7 @@ function FormNewObject({ isAdmin }) { htmlFor="nom" className="block mb-2 text-sm font-medium text-gray-900" > - Nom : + {t('components.formNewObject.name')} - Description : + {t('components.formNewObject.description')} - Type : + {t('components.formNewObject.type')} - Type de batterie : + {t('components.formNewObject.batteryType')} - Propriétaire : + {t('components.formNewObject.owner')}
- Active + {t('components.formNewObject.active')}
@@ -258,14 +260,14 @@ function FormNewObject({ isAdmin }) { type={"submit"} className="text-blue-500 hover:cursor-pointer hover:underline mb-2" > - {!verif ? "Confirmer les informations" : "Oui je suis sûr !"} + {!verif ? t('components.formNewObject.confirmInfos') : t('components.formNewObject.sureBtn')}

diff --git a/Front-end/src/components/Header.jsx b/Front-end/src/components/Header.jsx index 5e99021..d610740 100644 --- a/Front-end/src/components/Header.jsx +++ b/Front-end/src/components/Header.jsx @@ -1,9 +1,12 @@ import React, { useState, useEffect } from "react"; import { X, Menu, LogIn, UserPlus, LogOut, User } from "lucide-react"; import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { useAuth } from "../AuthContext"; +import LanguageSwitcher from "./LanguageSwitcher"; function Header() { + const { t } = useTranslation(); const { token, user, logout } = useAuth(); const [isMenuOpen, setIsMenuOpen] = useState(false); const [showAdminDropdown, setShowAdminDropdown] = useState(false); @@ -37,7 +40,7 @@ function Header() { onClick={() => setIsMenuOpen(false)} className="text-gray-600 hover:text-indigo-600" > - Accueil + {t('header.home')}

  • @@ -46,7 +49,7 @@ function Header() { onClick={() => setIsMenuOpen(false)} className="text-gray-600 hover:text-indigo-600" > - À propos + {t('header.about')}
  • {!token ? ( @@ -58,7 +61,7 @@ function Header() { className="hover:text-indigo-600 flex items-center gap-2" > - Connexion + {t('header.login')}
  • @@ -68,7 +71,7 @@ function Header() { className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700" > - Inscription + {t('header.signup')}
  • @@ -81,7 +84,7 @@ function Header() { onClick={() => setIsMenuOpen(false)} className="text-gray-600 hover:text-indigo-600" > - Visualisation + {t('header.visualizer')} ) : ( @@ -91,7 +94,7 @@ function Header() { onClick={() => setIsMenuOpen(false)} className="text-gray-600 hover:text-indigo-600" > - Gestion + {t('header.manage')} )} @@ -101,7 +104,7 @@ function Header() { onClick={toggleAdminDropdown} className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none" > - Admin + {t('header.admin')} {setShowAdminDropdown(false);setIsMenuOpen(false);}} > - Dashboard + {t('header.dashboard')} {setShowAdminDropdown(false);setIsMenuOpen(false);}} > - Gestion des Utilisateurs + {t('header.manageUsers')} {setShowAdminDropdown(false);setIsMenuOpen(false);}} > - Gestion des Objets Connectés + {t('header.manageObjects')} )} @@ -144,8 +147,7 @@ function Header() { className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" > - Profil - + {t('header.profile')}
  • @@ -157,7 +159,7 @@ function Header() { className="flex items-center gap-2 text-gray-600 hover:text-red-600" > - Déconnexion + {t('header.logout')}
  • @@ -166,14 +168,15 @@ function Header() { {!token ? ( -
    +
    + setIsMenuOpen(false)} className="hover:text-indigo-600 flex items-center gap-2" > - Connexion + {t('header.login')} - Inscription + {t('header.signup')}
    ) : ( -
    +
    + setIsMenuOpen(false)} className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" > -
    )} diff --git a/Front-end/src/components/InfoObject.jsx b/Front-end/src/components/InfoObject.jsx index f0f9249..814e992 100644 --- a/Front-end/src/components/InfoObject.jsx +++ b/Front-end/src/components/InfoObject.jsx @@ -1,8 +1,10 @@ import React from "react"; import { Info } from "lucide-react"; import { useAuth } from "../AuthContext"; +import { useTranslation } from "react-i18next"; function InfoObject({ object,defafficherModif }) { + const { t } = useTranslation(); const {user} = useAuth(); return (
    @@ -10,37 +12,37 @@ function InfoObject({ object,defafficherModif }) {
    -

    Informations

    +

    {t('components.infoObject.title')}

    -

    Description :

    +

    {t('components.infoObject.description')}

    {object.description}

    -

    Type :

    +

    {t('components.infoObject.type')}

    {object.type}

    -

    Localisation :

    +

    {t('components.infoObject.location')}

    {object.location}

    -

    Status :

    +

    {t('components.infoObject.status')}

    {object.status}

    - Derniere mise à jour : + {t('components.infoObject.lastUpdate')}

    {object.last_update}

    {user?.role!=="user"&&(
    - defafficherModif(true))}>Modifier ces infos + defafficherModif(true))}>{t('components.infoObject.modify')}
    )}
    diff --git a/Front-end/src/components/LanguageSwitcher.jsx b/Front-end/src/components/LanguageSwitcher.jsx new file mode 100644 index 0000000..47adc33 --- /dev/null +++ b/Front-end/src/components/LanguageSwitcher.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function LanguageSwitcher() { + const { i18n } = useTranslation(); + + const changeLanguage = (lng) => { + i18n.changeLanguage(lng); + }; + + return ( +
    + + +
    + ); +} diff --git a/Front-end/src/components/MeteoGraph.jsx b/Front-end/src/components/MeteoGraph.jsx index 0305881..2bf6aa3 100644 --- a/Front-end/src/components/MeteoGraph.jsx +++ b/Front-end/src/components/MeteoGraph.jsx @@ -14,8 +14,10 @@ import { import { Wind } from "lucide-react"; import axios from "axios"; import { API_BASE_URL } from "../config"; +import { useTranslation } from "react-i18next"; function MeteoGraph({ object, categorie, Logo,reference}) { + const { t } = useTranslation(); const [rawData, setRawData] = useState([]); const identifiant = object.id; useEffect(() => { @@ -51,15 +53,15 @@ function MeteoGraph({ object, categorie, Logo,reference}) {
    {categorie === "temperature" ? (

    - Historique de la température + {t('components.meteoGraph.historyTemp')}

    ) : categorie === "humidity" ? (

    - Historique de l'humidité + {t('components.meteoGraph.historyHum')}

    ) : (

    - Historique de la pression + {t('components.meteoGraph.historyPres')}

    )} @@ -86,7 +88,7 @@ function MeteoGraph({ object, categorie, Logo,reference}) { stroke="#8884d8" activeDot={{ r: 8 }} /> - + diff --git a/Front-end/src/components/MeteoInfos.jsx b/Front-end/src/components/MeteoInfos.jsx index 439ed2e..1e77960 100644 --- a/Front-end/src/components/MeteoInfos.jsx +++ b/Front-end/src/components/MeteoInfos.jsx @@ -5,8 +5,10 @@ import { API_BASE_URL } from "../config"; import BoutonGraphique from "./BoutonGraphique"; import AlertInactive from "./AlertInactive"; import ParticularMeteo from "./ParticularMeteo"; +import { useTranslation } from "react-i18next"; function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) { + const { t } = useTranslation(); const [rawData, setRawData] = useState([]); const [AffAlert, setAffAlert] = useState(false); const [AffRegles, setAffRegles] = useState(false); @@ -25,15 +27,14 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) { return (
    {AffAlert && object.status === "active" && ( - )}
    -

    Météo actuelle

    +

    {t('components.meteoInfos.currentWeather')}

    {lastData ? (
    @@ -41,7 +42,7 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) { type="temperature" data={lastData} Icon={Thermometer} - texte1="Température" + texte1={t('components.meteoInfos.temperature')} texte2="°C" graphStates={graphStates} setGraphStates={setGraphStates} @@ -52,7 +53,7 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) { type="pressure" data={lastData} Icon={CircleGauge} - texte1="Pression" + texte1={t('components.meteoInfos.pressure')} texte2="hPa" graphStates={graphStates} setGraphStates={setGraphStates} @@ -63,18 +64,18 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) { type="humidity" data={lastData} Icon={Droplet} - texte1="Humidité" + texte1={t('components.meteoInfos.humidity')} texte2="%" graphStates={graphStates} setGraphStates={setGraphStates} graphRefs={graphRefs} />

    - Dernier enregistrement : {lastData.timestamp} + {t('components.meteoInfos.lastRecord')} {lastData.timestamp}

    ) : ( -

    Chargement des données...

    +

    {t('components.meteoInfos.loading')}

    )}
    ); diff --git a/Front-end/src/components/ModifObject.jsx b/Front-end/src/components/ModifObject.jsx index 6d10d08..6cb1c40 100644 --- a/Front-end/src/components/ModifObject.jsx +++ b/Front-end/src/components/ModifObject.jsx @@ -3,8 +3,10 @@ import { Info } from "lucide-react"; import axios from "axios"; import { API_BASE_URL } from "../config"; import {useAuth} from "../AuthContext"; +import { useTranslation } from "react-i18next"; function ModifObject({ object, defafficherModif }) { + const { t } = useTranslation(); const {user}=useAuth(); const [description, setDescription] = useState(object.description || ""); const [type, setType] = useState(object.type || ""); @@ -53,7 +55,7 @@ function ModifObject({ object, defafficherModif }) {

    - Modifier les infos + {t('components.modifObject.title')}

    @@ -61,7 +63,7 @@ function ModifObject({ object, defafficherModif }) { htmlFor="description" className="block mb-2 text-sm font-medium text-gray-900" > - Description + {t('components.modifObject.description')} - Type : + {t('components.modifObject.type')} - Localisation : + {t('components.modifObject.location')}
    - Active + {t('components.modifObject.active')}
    @@ -145,14 +147,14 @@ function ModifObject({ object, defafficherModif }) { type="submit" className="text-blue-500 hover:cursor-pointer hover:underline" > - Confirmer les modifications + {t('components.modifObject.confirmMods')}
    diff --git a/Front-end/src/components/ParticularMeteo.jsx b/Front-end/src/components/ParticularMeteo.jsx index 2a9f233..7abb082 100644 --- a/Front-end/src/components/ParticularMeteo.jsx +++ b/Front-end/src/components/ParticularMeteo.jsx @@ -5,6 +5,7 @@ import Slider from "@mui/material/Slider"; import { API_BASE_URL } from "../config"; import axios from "axios"; import { useAuth } from "../AuthContext"; +import { useTranslation } from "react-i18next"; const identifiant = new URLSearchParams(window.location.search).get("id"); function ParticularMeteo({ @@ -17,6 +18,7 @@ function ParticularMeteo({ setGraphStates, graphRefs, }) { + const { t } = useTranslation(); const {user} = useAuth(); const [affRegles, setAffRegles] = useState(false); const [rangeValue, setRangeValue] = useState([0, 0]); @@ -133,7 +135,7 @@ function ParticularMeteo({ {affRegles && (

    - Définissez la valeur seuil pour l'alerte : + {t('components.particularMeteo.defineLimit')}

    {color=="text-red-600" &&( -

    Attention, la valeur actuelle est hors des bornes que vous avez définit !

    +

    {t('components.particularMeteo.outOfBounds')}

    )}
    )} diff --git a/Front-end/src/components/UserInfosObject.jsx b/Front-end/src/components/UserInfosObject.jsx index 0ea3c13..25f4ede 100644 --- a/Front-end/src/components/UserInfosObject.jsx +++ b/Front-end/src/components/UserInfosObject.jsx @@ -2,9 +2,10 @@ import React,{useEffect, useState} from "react"; import { User } from "lucide-react"; import axios from "axios"; import { API_BASE_URL } from "../config"; - +import { useTranslation } from "react-i18next"; function UserInfosObject({ user}) { + const { t } = useTranslation(); const [userInfo,setuserInfo]=useState({}); useEffect(()=>{ console.log(user); @@ -27,20 +28,20 @@ function UserInfosObject({ user}) {
    -

    Propriétaire

    +

    {t('components.userInfosObject.title')}

    -

    Pseudo :

    +

    {t('components.userInfosObject.pseudo')}

    {userInfo.pseudo}

    -

    Genre :

    +

    {t('components.userInfosObject.gender')}

    {userInfo.gender}

    -

    Nombre de points :

    +

    {t('components.userInfosObject.points')}

    {userInfo.points}

    diff --git a/Front-end/src/components/WindGraph.jsx b/Front-end/src/components/WindGraph.jsx index 1509c96..c0e86e5 100644 --- a/Front-end/src/components/WindGraph.jsx +++ b/Front-end/src/components/WindGraph.jsx @@ -4,8 +4,10 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Responsi import { Wind } from "lucide-react"; import axios from "axios"; import { API_BASE_URL } from "../config"; +import { useTranslation } from "react-i18next"; function WindGraph({ object,reference }) { + const { t } = useTranslation(); const [rawData, setRawData] = useState([]); const identifiant = object.id; useEffect(() => { @@ -18,9 +20,9 @@ function WindGraph({ object,reference }) { const { wind_speed, timestamp,wind_direction } = payload[0].payload; return (
    -

    Date: {timestamp}

    -

    Vitesse du vent: {wind_speed} km/h

    -

    Direction du vent: {wind_direction}

    +

    {t('components.windGraph.date')} {timestamp}

    +

    {t('components.windGraph.windSpeed')} {wind_speed} km/h

    +

    {t('components.windGraph.windDirection')} {wind_direction}

    ); } @@ -38,7 +40,7 @@ function WindGraph({ object,reference }) {
    -

    Historique du vent

    +

    {t('components.windGraph.title')}

    { @@ -27,7 +29,7 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})
    -

    Vent actuel

    +

    {t('components.windInfo.currentWind')}

    {lastData ? (
    @@ -46,7 +48,7 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})
    -

    Valeur

    +

    {t('components.windInfo.value')}

    {lastData.wind_speed} Km/h

    @@ -61,11 +63,11 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})

    - Dernier enregistrement : {lastData.timestamp} + {t('components.windInfo.lastRecord')} {lastData.timestamp}

    ) : ( -

    Chargement des données...

    +

    {t('components.windInfo.loading')}

    )} ); diff --git a/Front-end/src/i18n.js b/Front-end/src/i18n.js new file mode 100644 index 0000000..06724a1 --- /dev/null +++ b/Front-end/src/i18n.js @@ -0,0 +1,24 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +import frTranslation from './locales/fr.json'; +import enTranslation from './locales/en.json'; + +const resources = { + fr: { translation: frTranslation }, + en: { translation: enTranslation } +}; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + fallbackLng: 'fr', + interpolation: { + escapeValue: false + } + }); + +export default i18n; diff --git a/Front-end/src/locales/en.json b/Front-end/src/locales/en.json new file mode 100644 index 0000000..dc21653 --- /dev/null +++ b/Front-end/src/locales/en.json @@ -0,0 +1,363 @@ +{ + "header": { + "home": "Home", + "about": "About", + "login": "Log in", + "signup": "Sign up", + "visualizer": "Visualizer", + "manage": "Manage", + "dashboard": "Dashboard", + "manageUsers": "Manage Users", + "manageObjects": "Manage Connected Objects", + "profile": "Profile", + "logout": "Log out", + "admin": "Admin" + }, + "home": { + "title": "Real-time weather", + "subtitle": "Accurate and personalized forecasts to help you plan your day with peace of mind.", + "searchPlaceholder": "Search for a city ...", + "noCityFound": "No city found.", + "partlyCloudy": "Partly cloudy", + "feelsLike": "Feels like", + "humidity": "Humidity", + "wind": "Wind", + "rain": "Precipitation", + "sunrise": "Sunrise", + "sunset": "Sunset", + "hourlyForecast": "Hourly Forecast", + "dailyForecast": "5-day Forecast", + "weatherServices": "Weather Services", + "viewObjectsTitle": "Visualization of connected weather devices", + "viewObjectsDesc": "Browse all connected devices and their data throughout France", + "viewObjectsBtn": "View objects", + "signupTitle": "Sign up!", + "signupDesc": "Having an account on our site allows you to consult all connected weather objects throughout France.", + "signupBtn": "Sign up", + "loginTitle": "Log in!", + "loginDesc": "Happy to see you again! Log back into your account just as you left it!", + "loginBtn": "Log in", + "consultObjectsTitle": "Consult connected weather devices", + "consultObjectsDesc": "Access real-time data from connected weather devices, modify their parameters and check the history of measurements.", + "consultObjectsBtn": "Explore objects", + "addObjTitle": "Add a new connected device", + "addObjDesc": "Easily integrate a new connected device by entering its information and configuring its parameters for optimal management.", + "addObjBtn": "Add a device", + "adminDashTitle": "Access the Administration dashboard", + "adminDashDesc": "You will be able to manage the site's users as well as the connected devices", + "adminDashBtn": "Administration Dashboard", + "manageObjTitle": "Management of connected devices", + "manageObjDesc": "This module allows you to easily and effectively manage sensors and connected stations in France.", + "manageObjBtn": "Device Management", + "footerRights": "© 2025 VigiMétéo. All rights reserved." + }, + "auth": { + "login": { + "title": "Log in", + "emailLabel": "Email:", + "passwordLabel": "Password:", + "submitButton": "Log in", + "noAccount": "Don't have an account?", + "signupLink": "Sign up here", + "loading": "Logging in...", + "success": "Login successful! Redirecting...", + "missingToken": "Authentication failed: missing token in response", + "incorrectAuth": "Incorrect email or password", + "invalidData": "Invalid form data", + "serverError": "Server error. Please try again later.", + "networkError": "Cannot reach the server. Check your internet connection.", + "genericError": "An error occurred. Please try again." + }, + "signup": { + "title": "Sign up", + "firstNameLabel": "First Name:", + "lastNameLabel": "Last Name:", + "pseudoLabel": "Username:", + "genderLabel": "Gender:", + "genderMale": "Male", + "genderFemale": "Female", + "emailLabel": "Email:", + "passwordLabel": "Password:", + "confirmPasswordLabel": "Confirm Password:", + "submitButton": "Sign up", + "hasAccount": "Already have an account?", + "loginLink": "Log in here", + "passNoMatch": "Passwords do not match!", + "success": "Sign up successful!", + "error": "Error during sign up" + } + }, + "profile": { + "title": "My Profile", + "personalInfo": "Personal Information", + "loyaltyPoints": "Loyalty points:", + "firstName": "First Name:", + "lastName": "Last Name:", + "pseudo": "Username:", + "email": "Email:", + "save": "Save", + "firstNameL": "First Name", + "lastNameL": "Last Name", + "pseudoL": "Username", + "emailL": "Email", + "changePasswordTitle": "Change Password", + "currentPassword": "Current password:", + "newPassword": "New password:", + "confirmNewPassword": "Confirm new password:", + "errorMismatch": "The new passwords do not match!", + "successPass": "Password successfully changed!", + "successUpdate": "Profile successfully updated!", + "errorGeneric": "An error occurred" + }, + "about": { + "missionTitle": "Our Mission", + "missionDesc": "Our mission is to provide a complete and innovative solution for climate and environmental monitoring of the French territory. By combining high-quality precise weather forecasts with efficient IoT device management, we aim to offer a centralized platform to monitor local weather conditions in real time, while facilitating the analysis of data collected by IoT devices deployed across the country.", + "whoTitle": "Who are we?", + "whoDesc": "We are a team of tech, innovation, and environmental enthusiasts. We firmly believe that combining real-time weather data and the Internet of Things (IoT) can have a major impact on territorial management. Whether for local authorities, businesses, or public actors, our platform offers the tools needed for proactive and responsive environmental management.", + "visionTitle": "Our Vision", + "visionDesc": "In a world where climate conditions change rapidly, it is essential to anticipate and react effectively to meteorological phenomena. Thanks to our connected devices and intuitive interface, we allow users to track conditions in real time and act accordingly. From climate risk management to urban planning, our platform helps decision-makers make informed choices based on reliable, local data.", + "objectivesTitle": "The Objectives of Our Platform", + "obj1Desc": "Thanks to our connected devices, we collect local weather data, allowing continuous monitoring of climate conditions across the French territory.", + "obj1Title": "Real-time Monitoring", + "obj2Desc": "Using the best weather forecasting technologies, we provide you with accurate forecasts, whether it's temperature, wind speed, or air quality.", + "obj2Title": "Reliable Prediction", + "obj3Desc": "We allow users to easily manage their connected devices (weather stations, sensors, etc.) through a simple interface, while providing real-time tracking of their status and data.", + "obj3Title": "IoT Device Management", + "obj4Desc": "Our platform sends you instant alerts regarding extreme weather phenomena, allowing you to make quick and appropriate decisions.", + "obj4Title": "Rapid Response to Climate Alerts" + }, + "admin": { + "sidebar": { + "panel": "Admin Panel", + "dashboard": "Dashboard", + "users": "Users", + "objects": "Device Management" + }, + "dashboard": { + "title": "Dashboard", + "manageWidgetsEnd": "Done Managing", + "manageWidgets": "Manage Widgets", + "summary": "Dashboard Summary", + "totalUsers": "Total Users", + "lastLog": "Last Log", + "noLog": "No log available", + "usersList": "User Management", + "username": "Username", + "email": "Email", + "access": "Access", + "noUser": "No users available", + "seeMore": "See more", + "objectsManagement": "Connected Device Management", + "consultObjects": "Consult connected devices", + "addObject": "Add a new device", + "objectsList": "List of Devices and Tools/Services", + "requestDelete": "Device delete request", + "generateReports": "Generate usage reports:", + "requestObjects": "Request devices", + "reportsStats": "Reports and Statistics", + "exportCsv": "Export to CSV", + "energyConsumption": "Total energy consumption", + "energyConsumptionDesc": "1372 kWh accumulated (estimated)", + "connectionRate": "User connection rate", + "connectionRateDesc": "87% of users active this month", + "mostUsedServices": "Most used services", + "addWidget": "Add a widget", + "chooseWidget": "Choose a widget type", + "widgetSummary": "Dashboard Summary", + "widgetUsers": "User Management", + "widgetObjects": "Connected Device Management", + "widgetObjectsList": "List of Devices & Tools", + "widgetReports": "Reports and Statistics", + "widgetDelete": "Device delete request", + "cancel": "Cancel" + }, + "user": { + "title": "User Management", + "subtitle": "Add a user from this form", + "firstName": "First Name", + "lastName": "Last Name", + "pseudo": "Username", + "email": "Email", + "password": "Password", + "genderMale": "Male", + "genderFemale": "Female", + "genderOther": "Other", + "addUserBtn": "Add User", + "manageTitle": "Manage users from this panel.", + "gender": "Gender", + "accessLevel": "Access Level", + "points": "Points", + "actions": "Actions", + "changeBtn": "Change", + "deleteBtn": "Delete", + "logsTitle": "Connection history and logs", + "action": "Action", + "timestamp": "Timestamp", + "downloadLogs": "Download Logs", + "successAdd": "User successfully added!", + "errorAdd": "Error adding user!", + "confirmDelete": "Are you sure you want to delete user {name}? This action may result in the deletion of associated objects.", + "successDelete": "User successfully deleted!", + "successLevel": "Access level successfully changed!", + "errorLevel": "There was an error changing the access level!", + "successPoints": "Points successfully saved!", + "errorPoints": "There was an error saving the points!" + }, + "adminObjet": { + "title": "Administration of Devices and Tools/Services", + "catsTitle": "Category Management", + "newCatPlaceholder": "New category", + "addBtn": "Add", + "deleteBtn": "Delete", + "listTitle": "List of Devices and Tools/Services", + "sortBy": "-- Sort by --", + "sortOwner": "Owner", + "sortLocation": "Location", + "sortType": "Type", + "sortStatus": "Status", + "colName": "Name", + "colDesc": "Description", + "colType": "Type", + "colLocation": "Location", + "colOwner": "Owner", + "colStatus": "Status", + "noObjects": "No object or service available.", + "requestsTitle": "Device Deletion Requests", + "colObjId": "Device ID", + "colUserId": "User ID", + "colReqDate": "Request Date", + "acceptBtn": "Accept", + "rejectBtn": "Reject", + "noRequests": "No deletion requests found.", + "confirmCatDelete": "Are you sure you want to delete category {cat}? This action might impact devices.", + "successRecord": "Your device has been recorded successfully!", + "successReqDelete": "The request has been successfully deleted!", + "successObjDelete": "Your device has been successfully deleted!", + "errorReqDelete": "There was an error deleting the request!", + "errorObjDelete": "There was an error deleting your device!" + } + }, + "gestion": { + "title": "Welcome to the module", + "moduleName": "Management", + "description": "This module allows you to easily and effectively manage connected sensors and stations in France.", + "consultTitle": "Consult connected weather devices", + "consultDesc": "Access real-time data from connected weather devices, modify their parameters and view the history of measurements.", + "exploreBtn": "Explore devices", + "addTitle": "Add a new connected device", + "addDesc": "Easily integrate a new connected device by providing its information and configuring its parameters for optimal management.", + "addBtn": "Add a device", + "objectManagement": { + "successDeleteReq": "Deletion request sent to administrator.", + "errorDeleteReq": "Error, request already sent to administrator.", + "titleAdmin": "Management", + "titleUser": "Visualization", + "titleSuffix": "of", + "titleObjects": "Connected Devices.", + "searchPlaceholder": "Search by name, category, or keywords...", + "filterAll": "All", + "filterStation": "Weather Station", + "filterSensor": "Sensor", + "filterActive": "Active", + "filterInactive": "Inactive", + "noObjects": "No device found", + "moreInfo": "More info", + "deleteItem": "Delete device", + "seeMore": "See more" + }, + "objet": { + "dashboardTitle": "Dashboard - ", + "errorFetch": "Error fetching device" + }, + "addObject": { + "title": "New device" + } + }, + "components": { + "formNewObject": { + "successRecord": "Your device has been successfully registered!", + "errorRecord": "There was an error adding your device!", + "noCategory": "No category available.", + "addTitle": "Add a new device", + "enterData": "Enter the data for your new device", + "confirmData": "Are you sure about this data?", + "name": "Name:", + "description": "Description:", + "type": "Type:", + "selectType": "-- Select a type --", + "location": "Location:", + "batteryType": "Battery type:", + "owner": "Owner:", + "status": "Status:", + "inactive": "Inactive", + "active": "Active", + "confirmInfos": "Confirm information", + "sureBtn": "Yes, I am sure!", + "deleteInfos": "Delete information", + "changeBtn": "No, I want to change!" + }, + "infoObject": { + "title": "Information", + "description": "Description:", + "type": "Type:", + "location": "Location:", + "status": "Status:", + "lastUpdate": "Last update:", + "modify": "Modify this information" + }, + "modifObject": { + "title": "Modify information", + "description": "Description:", + "type": "Type:", + "location": "Location:", + "status": "Status:", + "inactive": "Inactive", + "active": "Active", + "confirmMods": "Confirm modifications", + "cancelMods": "Cancel modifications" + }, + "particularMeteo": { + "defineLimit": "Set the threshold value for the alert:", + "outOfBounds": "Warning, the current value is out of the bounds you defined!", + "setAlert": "Set alert" + }, + "meteoInfos": { + "alertInactive": "This device might be inactive due to lack of data. You can make it inactive by changing its status.", + "currentWeather": "Current Weather", + "temperature": "Temperature", + "pressure": "Pressure", + "humidity": "Humidity", + "lastRecord": "Last record:", + "loading": "Loading data..." + }, + "batterieInfo": { + "title": "Battery Status", + "batteryType": "Battery type:" + }, + "userInfosObject": { + "title": "Owner", + "pseudo": "Username:", + "gender": "Gender:", + "points": "Number of points:" + }, + "windGraph": { + "title": "Wind History", + "date": "Date:", + "windSpeed": "Wind speed:", + "windDirection": "Wind direction:" + }, + "meteoGraph": { + "historyTemp": "Temperature History", + "historyHum": "Humidity History", + "historyPres": "Pressure History", + "average": "Average" + }, + "windInfo": { + "currentWind": "Current Wind", + "value": "Value", + "lastRecord": "Last record:", + "loading": "Loading data..." + } + } +} + diff --git a/Front-end/src/locales/fr.json b/Front-end/src/locales/fr.json new file mode 100644 index 0000000..5c2ac5a --- /dev/null +++ b/Front-end/src/locales/fr.json @@ -0,0 +1,363 @@ +{ + "header": { + "home": "Accueil", + "about": "À propos", + "login": "Connexion", + "signup": "Inscription", + "visualizer": "Visualisation", + "manage": "Gestion", + "dashboard": "Dashboard", + "manageUsers": "Gestion des Utilisateurs", + "manageObjects": "Gestion des Objets Connectés", + "profile": "Profil", + "logout": "Déconnexion", + "admin": "Admin" + }, + "home": { + "title": "La météo en temps réel", + "subtitle": "Prévisions précises et personnalisées pour vous aider à planifier votre journée en toute sérénité.", + "searchPlaceholder": "Rechercher une ville ...", + "noCityFound": "Aucune ville trouvée.", + "partlyCloudy": "Partiellement nuageux", + "feelsLike": "Ressenti", + "humidity": "Humidité", + "wind": "Vent", + "rain": "Précipitations", + "sunrise": "Lever du soleil", + "sunset": "Coucher du soleil", + "hourlyForecast": "Prévisions horaires", + "dailyForecast": "Prévisions 5 jours", + "weatherServices": "Services météo", + "viewObjectsTitle": "Visualisation des objets connectés météorologiques", + "viewObjectsDesc": "Consultez l'ensemble des objets connectés ainsi que leurs données dans l'ensemble de la France", + "viewObjectsBtn": "Voir les objets", + "signupTitle": "Inscrivez-vous !", + "signupDesc": "Avoir un compte sur notre site permet de consulter l'intégralité des objets connectés météorologiques dans l'ensemble de la France", + "signupBtn": "S'inscrire", + "loginTitle": "Connectez-vous !", + "loginDesc": "Heureux de vous retrouver ! Retrouvez votre compte tel que vous l'avez laissé !", + "loginBtn": "Se connecter", + "consultObjectsTitle": "Consulter les objets connectés météorologiques", + "consultObjectsDesc": "Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.", + "consultObjectsBtn": "Explorer les objets", + "addObjTitle": "Ajouter un nouvel objet connecté", + "addObjDesc": "Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.", + "addObjBtn": "Ajouter un objet", + "adminDashTitle": "Accéder au tableau d'Administration", + "adminDashDesc": "Vous pourrez gérer les utilisateurs du site mais aussi les objets connectés", + "adminDashBtn": "Tableau d'Administration", + "manageObjTitle": "Gestion des objets connectés", + "manageObjDesc": "Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.", + "manageObjBtn": "Gestion des objets", + "footerRights": "© 2025 VigiMétéo. Tous droits réservés." + }, + "auth": { + "login": { + "title": "Connexion", + "emailLabel": "Email:", + "passwordLabel": "Mot de passe:", + "submitButton": "Se connecter", + "noAccount": "Vous n'avez pas de compte ?", + "signupLink": "Inscrivez-vous ici", + "loading": "Connexion en cours...", + "success": "Connexion réussie! Redirection...", + "missingToken": "Authentification échouée : token manquant dans la réponse", + "incorrectAuth": "Email ou mot de passe incorrect", + "invalidData": "Données de formulaire invalides", + "serverError": "Erreur serveur. Veuillez réessayer plus tard.", + "networkError": "Impossible de joindre le serveur. Vérifiez votre connexion internet.", + "genericError": "Une erreur s'est produite. Veuillez réessayer." + }, + "signup": { + "title": "Inscription", + "firstNameLabel": "Prénom:", + "lastNameLabel": "Nom:", + "pseudoLabel": "Pseudo:", + "genderLabel": "Sexe:", + "genderMale": "Homme", + "genderFemale": "Femme", + "emailLabel": "Email:", + "passwordLabel": "Mot de passe:", + "confirmPasswordLabel": "Confirmer le mot de passe:", + "submitButton": "S'inscrire", + "hasAccount": "Vous avez déjà un compte ?", + "loginLink": "Connectez-vous ici", + "passNoMatch": "Les mots de passe ne correspondent pas !", + "success": "Inscription réussie !", + "error": "Erreur lors de l'inscription" + } + }, + "profile": { + "title": "Mon Profil", + "personalInfo": "Informations Personnelles", + "loyaltyPoints": "Points de fidélité:", + "firstName": "Prénom:", + "lastName": "Nom:", + "pseudo": "Pseudo:", + "email": "Email:", + "save": "Sauvegarder", + "firstNameL": "Prénom", + "lastNameL": "Nom", + "pseudoL": "Pseudo", + "emailL": "Email", + "changePasswordTitle": "Modifier le mot de passe", + "currentPassword": "Mot de passe actuel:", + "newPassword": "Nouveau mot de passe:", + "confirmNewPassword": "Confirmer le nouveau mot de passe:", + "errorMismatch": "Les nouveaux mots de passe ne correspondent pas !", + "successPass": "Mot de passe modifié avec succès !", + "successUpdate": "Profil mis à jour avec succès !", + "errorGeneric": "Une erreur est survenue" + }, + "about": { + "missionTitle": "Notre mission", + "missionDesc": "Notre mission est de fournir une solution complète et innovante pour la surveillance climatique et environnementale du territoire français. En combinant des prévisions météorologiques de haute qualité avec une gestion efficace des objets connectés, nous visons à offrir une plateforme centralisée permettant de surveiller en temps réel les conditions météorologiques locales, tout en facilitant l'analyse des données collectées par des objets connectés déployés à travers le pays.", + "whoTitle": "Qui sommes-nous ?", + "whoDesc": "Nous sommes une équipe de passionnés de technologie, d’innovation et d’environnement. Nous croyons fermement que la combinaison de la donnée météorologique en temps réel et de l’Internet des Objets (IoT) peut avoir un impact majeur sur la gestion des territoires. Que ce soit pour les collectivités locales, les entreprises ou les acteurs publics, notre plateforme offre les outils nécessaires pour une gestion proactive et réactive de l’environnement.", + "visionTitle": "Notre Vision", + "visionDesc": "Dans un monde où les conditions climatiques évoluent rapidement, il est essentiel de pouvoir anticiper et réagir efficacement face aux phénomènes météorologiques. Grâce à nos objets connectés et à notre interface intuitive, nous permettons aux utilisateurs de suivre les conditions en temps réel et d’agir en conséquence. De la gestion des risques climatiques à la planification urbaine, notre plateforme aide les décideurs à prendre des décisions éclairées basées sur des données fiables et locales.", + "objectivesTitle": "Les Objectifs de Notre Plateforme", + "obj1Desc": "Grâce à nos objets connectés, nous collectons des données météorologiques locales, permettant une surveillance continue des conditions climatiques sur tout le territoire français.", + "obj1Title": "Surveillance en temps réel", + "obj2Desc": "En utilisant les meilleures technologies de prévision météorologique, nous vous fournissons des prévisions précises, qu’il s’agisse de la température, de la vitesse du vent ou de la qualité de l’air.", + "obj2Title": "Prédiction fiable", + "obj3Desc": "Nous permettons aux utilisateurs de gérer facilement leurs objets connectés (stations météo, capteurs, etc.) à travers une interface simple, tout en offrant un suivi en temps réel de leur statut et de leurs données.", + "obj3Title": "Gestion des objets connectés", + "obj4Desc": "Notre plateforme vous envoie des alertes instantanées concernant les phénomènes météorologiques extrêmes, vous permettant de prendre des décisions rapides et adaptées.", + "obj4Title": "Réponse rapide aux alertes climatiques" + }, + "admin": { + "sidebar": { + "panel": "Admin Panel", + "dashboard": "Tableau de bord", + "users": "Utilisateurs", + "objects": "Gestion des objets" + }, + "dashboard": { + "title": "Dashboard", + "manageWidgetsEnd": "Terminer la gestion", + "manageWidgets": "Gérer les widgets", + "summary": "Résumé du tableau de bord", + "totalUsers": "Total Utilisateur", + "lastLog": "Dernier Log", + "noLog": "Aucun log", + "usersList": "Gestion des Utilisateurs", + "username": "Username", + "email": "Email", + "access": "Access", + "noUser": "Aucun utilisateur disponible", + "seeMore": "Voir plus", + "objectsManagement": "Gestion des Objets Connectés", + "consultObjects": "Consulter les objets connectés", + "addObject": "Ajouter un nouvel objet", + "objectsList": "Liste des Objets et Outils/Services", + "requestDelete": "Requête suppression objets", + "generateReports": "Générer des rapports d'utilisation :", + "requestObjects": "Requête objets", + "reportsStats": "Rapports et Statistiques", + "exportCsv": "Exporter en CSV", + "energyConsumption": "Consommation énergétique totale", + "energyConsumptionDesc": "1372 kWh cumulés (estimation)", + "connectionRate": "Taux de connexion des utilisateurs", + "connectionRateDesc": "87% des utilisateurs actifs ce mois-ci", + "mostUsedServices": "Services les plus utilisés", + "addWidget": "Ajouter un widget", + "chooseWidget": "Choisir un type de widget", + "widgetSummary": "Dashboard Summary", + "widgetUsers": "Gestion des Utilisateurs", + "widgetObjects": "Gestion des Objets Connectés", + "widgetObjectsList": "Liste des Objets et Outils/Services", + "widgetReports": "Rapports et Statistiques", + "widgetDelete": "Demande de suppression d'objets", + "cancel": "Annuler" + }, + "user": { + "title": "Gestion des utilisateurs", + "subtitle": "Ajoutez un utilisateur à partir de ce formulaire", + "firstName": "Prénom", + "lastName": "Nom", + "pseudo": "Pseudo", + "email": "Email", + "password": "Mot de passe", + "genderMale": "Homme", + "genderFemale": "Femme", + "genderOther": "Autre", + "addUserBtn": "Ajouter utilisateur", + "manageTitle": "Gérez les utilisateurs à partir de ce panneau.", + "gender": "Genre", + "accessLevel": "Niveau d'accès", + "points": "Points", + "actions": "Actions", + "changeBtn": "Changer", + "deleteBtn": "Supprimer", + "logsTitle": "Historique des connexions et journal des logs", + "action": "Action", + "timestamp": "Timestamp", + "downloadLogs": "Télécharger les logs", + "successAdd": "Ajout de l'utilisateur réussi !", + "errorAdd": "Erreur lors de l'ajout de l'utilisateur !", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer l'utilisateur {name} ? Cette action pourrait entraîner la suppression des objets associés.", + "successDelete": "L'utilisateur a bien été supprimé !", + "successLevel": "Le changement de niveau a bien été enregistré !", + "errorLevel": "Il y a eu une erreur dans le changement de niveau !", + "successPoints": "Les points ont bien été enregistrés !", + "errorPoints": "Il y a eu une erreur dans l'ajout des points !" + }, + "adminObjet": { + "title": "Administration des Objets et Outils/Services", + "catsTitle": "Gestion des Catégories", + "newCatPlaceholder": "Nouvelle catégorie", + "addBtn": "Ajouter", + "deleteBtn": "Supprimer", + "listTitle": "Liste des Objets et Outils/Services", + "sortBy": "-- Trier par --", + "sortOwner": "Propriétaire", + "sortLocation": "Lieux", + "sortType": "Type", + "sortStatus": "Status", + "colName": "Nom", + "colDesc": "Description", + "colType": "Type", + "colLocation": "Localisation", + "colOwner": "Propriétaire", + "colStatus": "Status", + "noObjects": "Aucun objet ou service disponible.", + "requestsTitle": "Demandes de Suppression d'Objets", + "colObjId": "Objet ID", + "colUserId": "Utilisateur ID", + "colReqDate": "Date de Requête", + "acceptBtn": "Accepter", + "rejectBtn": "Refuser", + "noRequests": "Aucune demande de suppression trouvée.", + "confirmCatDelete": "Êtes-vous sûr de vouloir supprimer la catégorie {cat} ? Cette action pourrait impacter des objets.", + "successRecord": "Votre objet a bien été enregistré !", + "successReqDelete": "La demande a bien été supprimée !", + "successObjDelete": "Votre objet a bien été supprimé !", + "errorReqDelete": "Il y a eu une erreur dans la suppression de la demande !", + "errorObjDelete": "Il y a eu une erreur dans la suppression de votre objet !" + } + }, + "gestion": { + "title": "Bienvenue dans le module", + "moduleName": "Gestion", + "description": "Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.", + "consultTitle": "Consulter les objets connectés météorologiques", + "consultDesc": "Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.", + "exploreBtn": "Explorer les objets", + "addTitle": "Ajouter un nouvel objet connecté", + "addDesc": "Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.", + "addBtn": "Ajouter un objet", + "objectManagement": { + "successDeleteReq": "Demande de suppression envoyée à l'administrateur.", + "errorDeleteReq": "Erreur, demande déjà envoyée à l'administrateur.", + "titleAdmin": "Gestion", + "titleUser": "Visualisation", + "titleSuffix": "des", + "titleObjects": "Objets connectés.", + "searchPlaceholder": "Chercher par nom, catégorie ou mot clés...", + "filterAll": "Tout", + "filterStation": "Station météo", + "filterSensor": "Capteur", + "filterActive": "Actif", + "filterInactive": "Inactif", + "noObjects": "Aucun objet trouvé", + "moreInfo": "Plus d'infos", + "deleteItem": "Supprimer l'objet", + "seeMore": "Voir plus" + }, + "objet": { + "dashboardTitle": "Tableau de bord - ", + "errorFetch": "Erreur de récupération de l'objet" + }, + "addObject": { + "title": "Nouvel objet" + } + }, + "components": { + "formNewObject": { + "successRecord": "Votre objet a bien été enregistré !", + "errorRecord": "Il y a eu une erreur dans l'ajout de votre objet !", + "noCategory": "Aucune catégorie disponible.", + "addTitle": "Ajouter un nouvel objet", + "enterData": "Entrez les données de votre nouvel objet", + "confirmData": "Êtes-vous sûr de ces données ?", + "name": "Nom :", + "description": "Description :", + "type": "Type :", + "selectType": "-- Sélectionner un type --", + "location": "Localisation :", + "batteryType": "Type de batterie :", + "owner": "Propriétaire :", + "status": "Status :", + "inactive": "Inactive", + "active": "Active", + "confirmInfos": "Confirmer les informations", + "sureBtn": "Oui je suis sûr !", + "deleteInfos": "Supprimer les informations", + "changeBtn": "Non je veux changer !" + }, + "infoObject": { + "title": "Informations", + "description": "Description :", + "type": "Type :", + "location": "Localisation :", + "status": "Status :", + "lastUpdate": "Dernière mise à jour :", + "modify": "Modifier ces infos" + }, + "modifObject": { + "title": "Modifier les infos", + "description": "Description :", + "type": "Type :", + "location": "Localisation :", + "status": "Status :", + "inactive": "Inactive", + "active": "Active", + "confirmMods": "Confirmer les modifications", + "cancelMods": "Annuler les modifications" + }, + "particularMeteo": { + "defineLimit": "Définissez la valeur seuil pour l'alerte :", + "outOfBounds": "Attention, la valeur actuelle est hors des bornes que vous avez définies !", + "setAlert": "Définir alerte" + }, + "meteoInfos": { + "alertInactive": "Cet objet peut être inactif dû à son manque de données. Vous pouvez le rendre inactif en changeant son statut.", + "currentWeather": "Météo actuelle", + "temperature": "Température", + "pressure": "Pression", + "humidity": "Humidité", + "lastRecord": "Dernier enregistrement :", + "loading": "Chargement des données..." + }, + "batterieInfo": { + "title": "Etat de la batterie", + "batteryType": "Type de batterie :" + }, + "userInfosObject": { + "title": "Propriétaire", + "pseudo": "Pseudo :", + "gender": "Genre :", + "points": "Nombre de points :" + }, + "windGraph": { + "title": "Historique du vent", + "date": "Date :", + "windSpeed": "Vitesse du vent :", + "windDirection": "Direction du vent :" + }, + "meteoGraph": { + "historyTemp": "Historique de la température", + "historyHum": "Historique de l'humidité", + "historyPres": "Historique de la pression", + "average": "Moyenne" + }, + "windInfo": { + "currentWind": "Vent actuel", + "value": "Valeur", + "lastRecord": "Dernier enregistrement :", + "loading": "Chargement des données..." + } + } +} + diff --git a/Front-end/src/main.jsx b/Front-end/src/main.jsx index 987c65a..26f752f 100644 --- a/Front-end/src/main.jsx +++ b/Front-end/src/main.jsx @@ -2,6 +2,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.jsx'; import './index.css'; +import './i18n'; createRoot(document.getElementById('root')).render( diff --git a/Front-end/src/pages/About.jsx b/Front-end/src/pages/About.jsx index 125d056..5951ffc 100644 --- a/Front-end/src/pages/About.jsx +++ b/Front-end/src/pages/About.jsx @@ -1,6 +1,8 @@ import React from "react"; +import { useTranslation } from "react-i18next"; function About() { + const { t } = useTranslation(); return (
    @@ -10,17 +12,10 @@ function About() { {/* Section Notre mission */}

    - Notre mission + {t('about.missionTitle')}

    - Notre mission est de fournir une solution complète et innovante - pour la surveillance climatique et environnementale du - territoire français. En combinant des prévisions météorologiques - de haute qualité avec une gestion efficace des objets connectés, - nous visons à offrir une plateforme centralisée permettant de - surveiller en temps réel les conditions météorologiques locales, - tout en facilitant l'analyse des données collectées par des - objets connectés déployés à travers le pays. + {t('about.missionDesc')}

    - Qui sommes-nous ? + {t('about.whoTitle')}

    - Nous sommes une équipe de passionnés de technologie, - d’innovation et d’environnement. Nous croyons fermement que la - combinaison de la donnée météorologique en temps réel et de - l’Internet des Objets (IoT) peut avoir un impact majeur sur la - gestion des territoires. Que ce soit pour les collectivités - locales, les entreprises ou les acteurs publics, notre - plateforme offre les outils nécessaires pour une gestion - proactive et réactive de l’environnement. + {t('about.whoDesc')}

    {/* Section Notre Vision */}

    - Notre Vision + {t('about.visionTitle')}

    - Dans un monde où les conditions climatiques évoluent rapidement, - il est essentiel de pouvoir anticiper et réagir efficacement - face aux phénomènes météorologiques. Grâce à nos objets - connectés et à notre interface intuitive, nous permettons aux - utilisateurs de suivre les conditions en temps réel et d’agir en - conséquence. De la gestion des risques climatiques à la - planification urbaine, notre plateforme aide les décideurs à - prendre des décisions éclairées basées sur des données fiables - et locales. + {t('about.visionDesc')}

    - Les Objectifs de Notre Plateforme + {t('about.objectivesTitle')}

    {/* Objectif 1 */} @@ -90,14 +70,11 @@ function About() { />

    - Grâce à nos objets connectés, nous collectons des données - météorologiques locales, permettant une surveillance - continue des conditions climatiques sur tout le territoire - français. + {t('about.obj1Desc')}

    - Surveillance en temps réel + {t('about.obj1Title')}

    @@ -110,13 +87,10 @@ function About() { />

    - En utilisant les meilleures technologies de prévision - météorologique, nous vous fournissons des prévisions - précises, qu’il s’agisse de la température, de la vitesse du - vent ou de la qualité de l’air. + {t('about.obj2Desc')}

    -

    Prédiction fiable

    +

    {t('about.obj2Title')}

    {/* Objectif 3 */} @@ -128,14 +102,11 @@ function About() { />

    - Nous permettons aux utilisateurs de gérer facilement leurs - objets connectés (stations météo, capteurs, etc.) à travers - une interface simple, tout en offrant un suivi en temps réel - de leur statut et de leurs données. + {t('about.obj3Desc')}

    - Gestion des objets connectés + {t('about.obj3Title')}

    @@ -148,13 +119,11 @@ function About() { />

    - Notre plateforme vous envoie des alertes instantanées - concernant les phénomènes météorologiques extrêmes, vous - permettant de prendre des décisions rapides et adaptées. + {t('about.obj4Desc')}

    - Réponse rapide aux alertes climatiques + {t('about.obj4Title')}

    diff --git a/Front-end/src/pages/Admin/AdminObjet.jsx b/Front-end/src/pages/Admin/AdminObjet.jsx index 78dfd95..1bc5a8f 100644 --- a/Front-end/src/pages/Admin/AdminObjet.jsx +++ b/Front-end/src/pages/Admin/AdminObjet.jsx @@ -1,11 +1,13 @@ import React, { useState, useEffect } from "react"; import Sidebar from "./sidebar.jsx"; import axios from "axios"; +import { useTranslation } from "react-i18next"; import { API_BASE_URL } from "../../config"; import AddObject from "../Gestion/AddObject.jsx"; import FormNewObject from "../../components/FormNewObject.jsx"; function AdminObjet() { + const { t } = useTranslation(); const [categories, setCategories] = useState(); const [newCategory, setNewCategory] = useState(""); const [objects, setObjects] = useState([]); @@ -49,7 +51,7 @@ function AdminObjet() { }; const handleDeleteCategory = (categoryToDelete) => { const confirmation = window.confirm( - `Êtes-vous sûr de vouloir supprimer la catégorie "${categoryToDelete}" ? Cette action pourrait impacter des objets.` + t('admin.adminObjet.confirmCatDelete').replace('{cat}', categoryToDelete) ); if (confirmation) { @@ -108,7 +110,7 @@ function AdminObjet() { status: status, }; setObjects([...objects, newObj]); - setMessRequete("Votre objet a bien été enregistré !"); + setMessRequete(t('admin.adminObjet.successRecord')); setEnregistre(true); setVerif(false); resetForm(); @@ -123,13 +125,13 @@ function AdminObjet() { id, }) .then((response) => { - setMessRequete("La demande à bien été supprimé !"); + setMessRequete(t('admin.adminObjet.successReqDelete')); console.log("La demande à été supprimée :", response.data); window.location.reload(); }) .catch((error) => { setMessRequete( - "Il y a eu une erreur dans la suppression de la demande !" + t('admin.adminObjet.errorReqDelete') ); console.error("Erreur lors de la suppression de la demande :", error); }); @@ -141,13 +143,13 @@ function AdminObjet() { id, }) .then((response) => { - alert("Votre objet à bien été supprimé !"); + alert(t('admin.adminObjet.successObjDelete')); console.log("Votre objet à été supprimé :", response.data); window.location.reload(); }) .catch((error) => { alert( - "Il y a eu une erreur dans la suppression de votre objet !" + t('admin.adminObjet.errorObjDelete') ); console.error("Erreur lors de la suppression de l'objet :", error); }); @@ -168,17 +170,17 @@ function AdminObjet() {

    - Administration des Objets et Outils/Services + {t('admin.adminObjet.title')}

    - Gestion des Catégories + {t('admin.adminObjet.catsTitle')}

    setNewCategory(e.target.value)} className="flex-1 border border-gray-300 rounded-lg p-2 mr-4" @@ -187,7 +189,7 @@ function AdminObjet() { onClick={handleAddCategory} className="bg-indigo-600 text-white px-4 py-2 rounded-lg" > - Ajouter + {t('admin.adminObjet.addBtn')}
      @@ -202,7 +204,7 @@ function AdminObjet() { onClick={() => handleDeleteCategory(cat)} className="text-red-600 hover:underline" > - Supprimer + {t('admin.adminObjet.deleteBtn')} ))} @@ -213,18 +215,18 @@ function AdminObjet() {

      - Liste des Objets et Outils/Services + {t('admin.adminObjet.listTitle')}

      @@ -232,22 +234,22 @@ function AdminObjet() { - Nom + {t('admin.adminObjet.colName')} - Description + {t('admin.adminObjet.colDesc')} - Type + {t('admin.adminObjet.colType')} - Localisation + {t('admin.adminObjet.colLocation')} - Propriétaire + {t('admin.adminObjet.colOwner')} - Status + {t('admin.adminObjet.colStatus')} @@ -283,7 +285,7 @@ function AdminObjet() { onClick={() => handleDeleteObject(obj.id)} className="text-red-600 hover:underline" > - Supprimer + {t('admin.adminObjet.deleteBtn')} @@ -294,7 +296,7 @@ function AdminObjet() { colSpan="7" className="px-6 py-4 text-center text-sm text-gray-500" > - Aucun objet ou service disponible. + {t('admin.adminObjet.noObjects')} )} @@ -307,7 +309,7 @@ function AdminObjet() {

      - Demandes de Suppression d'Objets + {t('admin.adminObjet.requestsTitle')}

      @@ -315,10 +317,10 @@ function AdminObjet() { ID - Objet ID - Utilisateur ID - Date de Requête - Actions + {t('admin.adminObjet.colObjId')} + {t('admin.adminObjet.colUserId')} + {t('admin.adminObjet.colReqDate')} + {t('admin.user.actions')} @@ -333,13 +335,13 @@ function AdminObjet() { className="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded-md text-sm" onClick={() => handleDeleteObject(request.object_id)} > - Accepter + {t('admin.adminObjet.acceptBtn')} @@ -350,7 +352,7 @@ function AdminObjet() { colSpan="5" className="px-6 py-4 text-center text-sm text-gray-500" > - Aucune demande de suppression trouvée. + {t('admin.adminObjet.noRequests')} )} diff --git a/Front-end/src/pages/Admin/Dashboard.jsx b/Front-end/src/pages/Admin/Dashboard.jsx index 71f7e16..c6a18ed 100644 --- a/Front-end/src/pages/Admin/Dashboard.jsx +++ b/Front-end/src/pages/Admin/Dashboard.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import Sidebar from "./sidebar.jsx"; import { RadioTower,Minus, ArrowRight, BadgePlus, Settings } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { API_BASE_URL } from "../../config.js"; import axios from "axios"; @@ -41,6 +42,7 @@ const initialWidgets = [ ]; function Dashboard() { + const { t } = useTranslation(); const [users, setUsers] = useState([]); const [logs, setLogs] = useState([ @@ -94,18 +96,18 @@ function Dashboard() {
      -

      Dashboard

      +

      {t('admin.dashboard.title')}

      @@ -129,21 +131,21 @@ function Dashboard() { {widget.type === "summary" && (

      - Résumé du tableau de bord + {t('admin.dashboard.summary')}

      -

      Total Utilisateur

      +

      {t('admin.dashboard.totalUsers')}

      {users.length}

      -

      Dernier Log

      +

      {t('admin.dashboard.lastLog')}

      {logs.length > 0 ? (

      {logs[logs.length - 1].username} -{" "} {logs[logs.length - 1].action}

      ) : ( -

      Aucun log

      +

      {t('admin.dashboard.noLog')}

      )}
      @@ -152,20 +154,20 @@ function Dashboard() { {widget.type === "users" && (

      - Gestion des Utilisateurs + {t('admin.dashboard.usersList')}

      @@ -189,7 +191,7 @@ function Dashboard() { colSpan="3" className="px-2 py-1 border border-gray-200 text-center" > - Aucun utilisateur disponible + {t('admin.dashboard.noUser')} )} @@ -200,7 +202,7 @@ function Dashboard() { className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md" onClick={() => (window.location.href = "/user")} > - Voir plus + {t('admin.dashboard.seeMore')} )} @@ -208,7 +210,7 @@ function Dashboard() { {widget.type === "objects" && (

      - Gestion des Objets Connectés + {t('admin.dashboard.objectsManagement')}

      @@ -226,7 +228,7 @@ function Dashboard() { className="flex items-center text-indigo-600 hover:text-indigo-700" > - Ajouter un nouvel objet + {t('admin.dashboard.addObject')}
      @@ -236,7 +238,7 @@ function Dashboard() { {widget.type === "adminobjet" && (

      - Liste des Objets et Outils/Services + {t('admin.dashboard.objectsList')}

        {adminObjects.slice(0, 2).map((obj) => ( @@ -254,7 +256,7 @@ function Dashboard() { onClick={() => (window.location.href = "/adminobjet")} className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md" > - Voir plus + {t('admin.dashboard.seeMore')}
      )} @@ -262,19 +264,19 @@ function Dashboard() { {widget.type === "requestObject" && (

      - Requête suppression objets + {t('admin.dashboard.requestDelete')}

      - Générer des rapports d'utilisation : + {t('admin.dashboard.generateReports')}

      @@ -283,19 +285,19 @@ function Dashboard() { {widget.type === "reporting" && (

      - Rapports et Statistiques + {t('admin.dashboard.reportsStats')}

      - Générer des rapports d'utilisation : + {t('admin.dashboard.generateReports')}

      @@ -303,23 +305,23 @@ function Dashboard() {

      - Consommation énergétique totale + {t('admin.dashboard.energyConsumption')}

      - 1372 kWh cumulés (estimation) + {t('admin.dashboard.energyConsumptionDesc')}

      - Taux de connexion des utilisateurs + {t('admin.dashboard.connectionRate')}

      - 87% des utilisateurs actifs ce mois-ci + {t('admin.dashboard.connectionRateDesc')}

      - Services les plus utilisés + {t('admin.dashboard.mostUsedServices')}

      • Consultation des données météo
      • @@ -339,7 +341,7 @@ function Dashboard() { >
      @@ -349,51 +351,51 @@ function Dashboard() {

      - Choisir un type de widget + {t('admin.dashboard.chooseWidget')}

      diff --git a/Front-end/src/pages/Admin/User.jsx b/Front-end/src/pages/Admin/User.jsx index 2803950..db0631c 100644 --- a/Front-end/src/pages/Admin/User.jsx +++ b/Front-end/src/pages/Admin/User.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import Sidebar from "./sidebar.jsx"; +import { useTranslation } from "react-i18next"; import { API_BASE_URL } from "../../config.js"; import axios from "axios"; @@ -7,6 +8,7 @@ const thTd = "p-2 border border-gray-300 text-left"; const th = `${thTd} bg-gray-100`; function User() { + const { t } = useTranslation(); const [users, setUsers] = useState([]); const [logs, setLogs] = useState([]); @@ -33,11 +35,11 @@ function User() { }) .then((response) => { logAction(name, "Utilisateur ajouté"); - alert("Ajout de l'utilisateur réussit !"); + alert(t('admin.user.successAdd')); window.location.reload(); }) .catch((error) => { - alert("Erreur lors de l'ajout de l'utilisateur !"); + alert(t('admin.user.errorAdd')); }); }; @@ -52,7 +54,7 @@ function User() { if (user) { const confirmation = window.confirm( - `Êtes-vous sûr de vouloir supprimer l'utilisateur "${user.name}" ? Cette action pourrait entraîner la suppression des objets associés.` + t('admin.user.confirmDelete').replace('{name}', user.name) ); if (confirmation) { axios @@ -60,7 +62,7 @@ function User() { id: userId, }) .then((response) => { - alert("L'utilisateur a bien été supprimé !"); + alert(t('admin.user.successDelete')); console.log("L'utilisateur a été supprimé :", response.data); window.location.reload(); }) @@ -99,11 +101,11 @@ function User() { points: user.points, }) .then((response) => { - alert("Le changement de niveau a bien été enregistré !"); + alert(t('admin.user.successLevel')); console.log("Changement de niveau réussit :", response.data); }) .catch((error) => { - alert("Il y a eu une erreur dans le changement de niveau !"); + alert(t('admin.user.errorLevel')); console.error("Erreur lors du changement de niveau :", error); }); logAction( @@ -128,11 +130,11 @@ function User() { points: user.points, }) .then((response) => { - alert("Les points ont bien été enregistrés !"); + alert(t('admin.user.successPoints')); console.log("Ajout des points réussi :", response.data); }) .catch((error) => { - alert("Il y a eu une erreur dans l'ajout des points !"); + alert(t('admin.user.errorPoints')); console.error("Erreur lors de l'ajout des points :", error); }); logAction(user.name, `Points ajustés à ${pointsToAdd}`); @@ -167,9 +169,9 @@ function User() {

      - Gestion des utilisateurs + {t('admin.user.title')}

      -

      Ajoutez un utilisateur à partir de ce formulaire

      +

      {t('admin.user.subtitle')}

      @@ -177,7 +179,7 @@ function User() { setname(e.target.value)} required @@ -187,7 +189,7 @@ function User() { setSurname(e.target.value)} required @@ -197,7 +199,7 @@ function User() { setPseudo(e.target.value)} required @@ -207,7 +209,7 @@ function User() { setEmail(e.target.value)} required @@ -217,7 +219,7 @@ function User() { setPassword(e.target.value)} required @@ -229,9 +231,9 @@ function User() { value={gender} onChange={(e) => setGender(e.target.value)} > - - - + + +
      @@ -240,7 +242,7 @@ function User() { className="w-full sm:w-auto px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-medium border-none rounded-md transition-colors" type="submit" > - Ajouter utilisateur + {t('admin.user.addUserBtn')}
      @@ -248,18 +250,18 @@ function User() { {/*Tableau utilisateur*/}
      -

      Gérez les utilisateurs à partir de ce panneau.

      +

      {t('admin.user.manageTitle')}

      - Username + {t('admin.dashboard.username')} - Email + {t('admin.dashboard.email')} - Access + {t('admin.dashboard.access')}
      - - - - - - - - + + + + + + + + @@ -300,7 +302,7 @@ function User() { className="ml-2 p-2 bg-green-600 text-white border-none rounded-md" onClick={() => handleAdjustPoints(user.id)} > - Changer + {t('admin.user.changeBtn')} @@ -319,15 +321,15 @@ function User() {

      - Historique des connexions et journal des logs{" "} + {t('admin.user.logsTitle')}

      NomPrénomPseudoEmailGenreNiveau d'accèsPointsActions{t('admin.user.lastName')}{t('admin.user.firstName')}{t('admin.user.pseudo')}{t('admin.user.email')}{t('admin.user.gender')}{t('admin.user.accessLevel')}{t('admin.user.points')}{t('admin.user.actions')}
      @@ -308,7 +310,7 @@ function User() { className="p-2 bg-red-600 text-white border-none rounded-md" onClick={() => handleDeleteUser(user.id)} > - Supprimer + {t('admin.user.deleteBtn')}
      - - - + + + @@ -345,7 +347,7 @@ function User() { onClick={downloadLogs} className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md" > - Télécharger les logs + {t('admin.user.downloadLogs')} diff --git a/Front-end/src/pages/Admin/sidebar.jsx b/Front-end/src/pages/Admin/sidebar.jsx index b5e3ac7..885d6e7 100644 --- a/Front-end/src/pages/Admin/sidebar.jsx +++ b/Front-end/src/pages/Admin/sidebar.jsx @@ -1,7 +1,9 @@ import React from "react"; import { Menu, X } from "lucide-react"; +import { useTranslation } from "react-i18next"; function Sidebar({ isOpen, toggleSidebar }) { + const { t } = useTranslation(); return (
      NomActionTimestamp{t('admin.user.lastName')}{t('admin.user.action')}{t('admin.user.timestamp')}