diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ef1ad5711..7880d0e42 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -15,11 +15,14 @@
"@tanstack/react-router": "1.19.1",
"axios": "1.9.0",
"form-data": "4.0.2",
+ "i18next": "^25.3.2",
+ "i18next-browser-languagedetector": "^8.2.0",
"next-themes": "^0.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^5.0.0",
"react-hook-form": "7.49.3",
+ "react-i18next": "^15.6.0",
"react-icons": "^5.5.0"
},
"devDependencies": {
@@ -300,13 +303,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.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
"engines": {
"node": ">=6.9.0"
}
@@ -3381,6 +3381,15 @@
"react-is": "^16.7.0"
}
},
+ "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/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
@@ -3391,6 +3400,46 @@
"node": ">=16.17.0"
}
},
+ "node_modules/i18next": {
+ "version": "25.3.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.2.tgz",
+ "integrity": "sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6"
+ },
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
+ "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -4002,6 +4051,32 @@
"react": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/react-i18next": {
+ "version": "15.6.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz",
+ "integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0",
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
@@ -4030,11 +4105,6 @@
"url": "https://paulmillr.com/funding/"
}
},
- "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=="
- },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -4254,7 +4324,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
- "dev": true,
+ "devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4379,6 +4449,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",
@@ -4644,12 +4723,9 @@
}
},
"@babel/runtime": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
- "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
- "requires": {
- "regenerator-runtime": "^0.14.0"
- }
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="
},
"@babel/types": {
"version": "7.23.9",
@@ -6669,12 +6745,36 @@
"react-is": "^16.7.0"
}
},
+ "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==",
+ "requires": {
+ "void-elements": "3.1.0"
+ }
+ },
"human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true
},
+ "i18next": {
+ "version": "25.3.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.2.tgz",
+ "integrity": "sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==",
+ "requires": {
+ "@babel/runtime": "^7.27.6"
+ }
+ },
+ "i18next-browser-languagedetector": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
+ "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
+ "requires": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -7074,6 +7174,15 @@
"integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==",
"requires": {}
},
+ "react-i18next": {
+ "version": "15.6.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz",
+ "integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==",
+ "requires": {
+ "@babel/runtime": "^7.27.6",
+ "html-parse-stringify": "^3.0.1"
+ }
+ },
"react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
@@ -7091,11 +7200,6 @@
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
"dev": true
},
- "regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
- },
"resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -7246,7 +7350,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
- "dev": true
+ "devOptional": true
},
"ufo": {
"version": "1.5.4",
@@ -7293,6 +7397,11 @@
"tinyglobby": "^0.2.13"
}
},
+ "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=="
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 1760a3472..7febc568f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,11 +18,14 @@
"@tanstack/react-router": "1.19.1",
"axios": "1.9.0",
"form-data": "4.0.2",
+ "i18next": "^25.3.2",
+ "i18next-browser-languagedetector": "^8.2.0",
"next-themes": "^0.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^5.0.0",
"react-hook-form": "7.49.3",
+ "react-i18next": "^15.6.0",
"react-icons": "^5.5.0"
},
"devDependencies": {
diff --git a/frontend/src/components/Admin/AddUser.tsx b/frontend/src/components/Admin/AddUser.tsx
index db353a3a2..8abdbdac1 100644
--- a/frontend/src/components/Admin/AddUser.tsx
+++ b/frontend/src/components/Admin/AddUser.tsx
@@ -1,5 +1,6 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import { type UserCreate, UsersService } from "@/client"
import type { ApiError } from "@/client/core/ApiError"
@@ -33,6 +34,7 @@ interface UserCreateForm extends UserCreate {
}
const AddUser = () => {
+ const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
const queryClient = useQueryClient()
const { showSuccessToast } = useCustomToast()
@@ -60,7 +62,7 @@ const AddUser = () => {
mutationFn: (data: UserCreate) =>
UsersService.createUser({ requestBody: data }),
onSuccess: () => {
- showSuccessToast("User created successfully.")
+ showSuccessToast(t("messages.success.userCreated"))
reset()
setIsOpen(false)
},
@@ -86,32 +88,32 @@ const AddUser = () => {
diff --git a/frontend/src/components/Admin/DeleteUser.tsx b/frontend/src/components/Admin/DeleteUser.tsx
index f3e7db317..a2773adf8 100644
--- a/frontend/src/components/Admin/DeleteUser.tsx
+++ b/frontend/src/components/Admin/DeleteUser.tsx
@@ -2,6 +2,7 @@ import { Button, DialogTitle, Text } from "@chakra-ui/react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react"
import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import { FiTrash2 } from "react-icons/fi"
import { UsersService } from "@/client"
@@ -18,6 +19,7 @@ import {
import useCustomToast from "@/hooks/useCustomToast"
const DeleteUser = ({ id }: { id: string }) => {
+ const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
const queryClient = useQueryClient()
const { showSuccessToast, showErrorToast } = useCustomToast()
@@ -33,11 +35,11 @@ const DeleteUser = ({ id }: { id: string }) => {
const mutation = useMutation({
mutationFn: deleteUser,
onSuccess: () => {
- showSuccessToast("The user was deleted successfully")
+ showSuccessToast(t("messages.success.userDeleted"))
setIsOpen(false)
},
onError: () => {
- showErrorToast("An error occurred while deleting the user")
+ showErrorToast(t("messages.error.userDeleteError"))
},
onSettled: () => {
queryClient.invalidateQueries()
@@ -59,19 +61,17 @@ const DeleteUser = ({ id }: { id: string }) => {