diff --git a/package-lock.json b/package-lock.json index 851979a..255def6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,11 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "aos": "^2.3.4", + "i18next": "^23.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-github-btn": "^1.4.0", + "react-i18next": "^14.1.2", "react-icons": "^4.11.0", "react-scripts": "^5.0.1", "sass": "^1.69.4", @@ -8915,6 +8917,14 @@ "node": ">=12" } }, + "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==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -9058,6 +9068,28 @@ "node": ">=10.17.0" } }, + "node_modules/i18next": { + "version": "23.11.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz", + "integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==", + "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" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -14484,6 +14516,27 @@ "react": ">=16.3.0" } }, + "node_modules/react-i18next": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz", + "integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", @@ -16884,6 +16937,14 @@ "node": ">= 0.8" } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 4252d04..0386710 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,11 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "aos": "^2.3.4", + "i18next": "^23.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-github-btn": "^1.4.0", + "react-i18next": "^14.1.2", "react-icons": "^4.11.0", "react-scripts": "^5.0.1", "sass": "^1.69.4", diff --git a/src/App.js b/src/App.js index 4ffb5b2..abe5b39 100644 --- a/src/App.js +++ b/src/App.js @@ -9,6 +9,8 @@ import Footer from "./components/Footer.tsx"; import CV from "./components/CV.tsx"; import Menu from "./components/Menu.tsx"; import data from "./assets/DATA.ts"; +import { useTranslation } from "react-i18next"; +import i18n from './i18n.js'; function App() { const [theme, setTheme] = useState("light"); @@ -28,22 +30,34 @@ function App() { document.documentElement.classList.toggle("dark"); } + const { i18n } = useTranslation(); + + const toggleLanguage = () => { + i18n.changeLanguage(i18n.language === "fr" ? "en" : "fr"); + } + return (
-
+
+
- + +
+
+
); } diff --git a/src/assets/DATA.ts b/src/assets/DATA.ts index ccd0cda..c8d37a4 100644 --- a/src/assets/DATA.ts +++ b/src/assets/DATA.ts @@ -71,7 +71,7 @@ const Felix = { link: "https://github.com/modelec" }, { - title: "Mercury Cloud", + title: "MercuryCloud", description: "Projet d'herbergeur de serveur de jeu et VPS. Poste de support technique, administrateur des service VPS, Game et web.", tags: ["Linux", "Virtualisation", "CPanel", "Plesk", "WHMCS"], link: "https://mercurycloud.fr/" diff --git a/src/components/About.tsx b/src/components/About.tsx index 8c1b268..7713257 100644 --- a/src/components/About.tsx +++ b/src/components/About.tsx @@ -1,11 +1,13 @@ // @ts-ignore import React from 'react'; +import {useTranslation} from "react-i18next"; -function About({ref, title, description}) { +function About() { + const { t } = useTranslation(); return( -
-

{title}

-

{description}

+
+

{t('about.title')}

+

{t('about.description')}

); } diff --git a/src/components/CV.tsx b/src/components/CV.tsx index b98fb47..a1500ab 100644 --- a/src/components/CV.tsx +++ b/src/components/CV.tsx @@ -1,11 +1,13 @@ // @ts-ignore import React from 'react'; +import { useTranslation } from 'react-i18next'; function CV(){ + const { t } = useTranslation(); return ( -
-

Mon CV

- +
+

{t('cv.title')}

+
); } diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 55a107d..71f1800 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,10 +1,12 @@ // @ts-ignore import React from 'react'; import { FaGithub, FaTwitter, FaLinkedin, FaRegEnvelope } from 'react-icons/fa'; +import {useTranslation} from "react-i18next"; -function Card({name, title, social: {github, twitter, linkedin, mail}}){ +function Card({name, social: {github, twitter, linkedin, mail}}){ //Get avatar from gravatar using email const profile = `https://1.gravatar.com/avatar/4d43af207280d1d23e2a2905577c7b6167723fec2d33f946cc86f114c1a85b8d?size=256`; + const { t } = useTranslation(); return (
@@ -17,7 +19,7 @@ function Card({name, title, social: {github, twitter, linkedin, mail}}){

{name}

-

{title}

+

{t('card.title')}

) diff --git a/src/components/Project.tsx b/src/components/Project.tsx index 61bde6a..5147f5f 100644 --- a/src/components/Project.tsx +++ b/src/components/Project.tsx @@ -2,11 +2,13 @@ import React from 'react'; // @ts-ignore import ProjectCard from "./ProjectCard.tsx"; +import {useTranslation} from "react-i18next"; function Projects({projects}) { + const { t } = useTranslation(); return( -
-

Mes projets

+
+

{t('projects.title')}

{projects.map((project) => ( diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx index 66c2f18..a86ad0f 100644 --- a/src/components/ProjectCard.tsx +++ b/src/components/ProjectCard.tsx @@ -1,8 +1,12 @@ +// @ts-ignore import React from 'react'; import { FaExternalLinkAlt} from "react-icons/fa"; import GitHubButton from 'react-github-btn'; +import {useTranslation} from "react-i18next"; -const projectCard = ({ project: { title, description, tags, link} }) => { +const ProjectCard = ({ project }) => { + const { t } = useTranslation(); + const { title, link, tags } = project; return (
@@ -12,13 +16,13 @@ const projectCard = ({ project: { title, description, tags, link} }) => {
-

{description}

+

{t(`projects.${project.title}.description`)}

{tags.map((tag) => (
{tag}
))}
- {title !== "Mercury Cloud" && ( + {title !== "MercuryCloud" && (
Star {" "} @@ -29,4 +33,4 @@ const projectCard = ({ project: { title, description, tags, link} }) => { ); }; -export default projectCard; \ No newline at end of file +export default ProjectCard; \ No newline at end of file diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..5388e8c --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,52 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; + +i18n + .use(initReactI18next) + .init({ + resources: { + fr: { + translation: { + "about.title": "A propos de moi", + "about.description": "Je suis étudiant en 2e année à l'ISEN Nantes. Je suis passionné par l'informatique. J'ai appris à coder en autodidacte et je suis actuellement en train d'apprendre le C++ et le PHP. Je suis également passionné par l'électronique et le hardware. Je possède un homelab composé de 2 serveur, un DELL T320 et un DELL T330 les 2 sous proxmox.", + "card.title": "Etudiant en 2e année a l'ISEN Nantes", + "projects.title": "Mes projets", + "projects.SansDomaineFixe.xyz.description": "Site web offrant des enregistrements DNS gratuits", + "projects.Front end starter.description": "Mon starter personnel pour projet front end", + "projects.Project C - ISEN CIR 1.description": "Projet de fin de 1ere année à l'ISEN Nantes", + "projects.Projet robot.description": "Projet de robot avec le club Modelec ISEN pour la coupe de france de robotique (Developpement et déploiment sur Raspberry Pi)", + "projects.MercuryCloud.description": "Projet d'herbergeur de serveur de jeu et VPS. Poste de support technique, administrateur des service VPS, Game et web.", + "projects.Projet C++ - ISEN CIR 2.description": "Projet de fin de 4e semestre à l'ISEN Nantes. Création d'un jeu de type Tower Defense en C++ avec la librairie QT6.", + "cv.title": "Mon CV", + "nav.about": "A propos de moi", + "nav.projects": "Mes projets", + "nav.cv": "Mon CV", + }, + }, + en: { + translation: { + "cv.title": "My CV", + "about.title": "About me", + "about.description": "I am a second year student at ISEN Nantes. I am passionate about computer science. I learned to code on my own and I am currently learning C++ and PHP. I am also passionate about electronics and hardware. I have a homelab composed of 2 servers, a DELL T320 and a DELL T330 both under proxmox.", + "card.title": "Second year student at ISEN Nantes", + "projects.title": "My projects", + "projects.SansDomaineFixe.xyz.description": "Website offering free DNS records", + "projects.Front end starter.description": "My personal starter for front end projects", + "projects.Project C - ISEN CIR 1.description": "End of 1st year project at ISEN Nantes", + "projects.Projet robot.description": "Robot project with the Modelec ISEN club for the French robotics cup (Development and deployment on Raspberry Pi)", + "projects.MercuryCloud.description": "Game server and VPS hosting project. Technical support position, administrator of VPS, Game and web services.", + "projects.Projet C++ - ISEN CIR 2.description": "End of 4th semester project at ISEN Nantes. Creation of a Tower Defense type game in C++ with the QT6 library.", + "nav.about": "About me", + "nav.projects": "My projects", + "nav.cv": "My CV", + }, + }, + }, + lng: navigator.language.startsWith('fr') ? 'fr' : 'en', + fallbackLng: "en", + interpolation: { + escapeValue: false + }, + }); + +export default i18n; \ No newline at end of file