mirror of
https://github.com/BreizhHardware/portfolio.git
synced 2026-03-18 21:40:29 +01:00
Add french and english language and toggle
This commit is contained in:
61
package-lock.json
generated
61
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
18
src/App.js
18
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 (
|
||||
<div className="min-h-screen py-10 px-3 sm:px-5 bg-gray-100 dark:bg-gray-900">
|
||||
<Menu />
|
||||
<div data-aos="face-down" data-aos-duration="800">
|
||||
<div data-aos="face-down" data-aos-duration="800" id="top">
|
||||
<Card name={data.name} title={data.title} social={data.social} />
|
||||
</div>
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="about"/>
|
||||
<div data-aos="fade-up" data-aos-duration="800" data-aos-delay="400">
|
||||
<About title={data.about.title} description={data.about.description} />
|
||||
<About />
|
||||
<Skills skills={data.skills} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="projects" />
|
||||
<Project projects={data.projects} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="cv" />
|
||||
<CV />
|
||||
<Footer />
|
||||
</div>
|
||||
<button onClick={toggleTheme} className="fixed bottom-5 right-5 p-2 rounded-full bg-gray-100 dark:bg-gray-900 shadow-md">
|
||||
{theme === "light" ? <FaMoon size={20} className="text-gray-800" /> : <FaSun size={20} className="text-gray-200" />}
|
||||
</button>
|
||||
<button onClick={toggleLanguage} className="fixed bottom-5 right-16 p-2 rounded-full bg-gray-100 dark:bg-gray-900 shadow-md text-gray-800 dark:text-gray-200">
|
||||
{i18n.language === "fr" ? "EN" : "FR"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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(
|
||||
<div className="max-w-4xl mx-auto mt-16" id="about">
|
||||
<p className="text-2xl md:text-4xl font-bold text-center text-gray-800 dark:text-gray-200">{title}</p>
|
||||
<p className="text-base text-left md:text-center text-gray-600 leading-relaxed mt-4 dark:text-gray-400">{description}</p>
|
||||
<div className="max-w-4xl mx-auto mt-16">
|
||||
<p className="text-2xl md:text-4xl font-bold text-center text-gray-800 dark:text-gray-200">{t('about.title')}</p>
|
||||
<p className="text-base text-left md:text-center text-gray-600 leading-relaxed mt-4 dark:text-gray-400">{t('about.description')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function CV(){
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto mt-16 flex justify-center items-center flex-col gap-1" id="cv">
|
||||
<p className="text-2xl md:text-4xl font-bold text-center text-gray-800 dark:text-gray-200">Mon CV</p>
|
||||
<iframe src="/CV%20Félix%20MARQUET.pdf" className="w-full md:w-3/4 h-64 md:h-96 lg:h-screen" title="CV Félix MARQUET"></iframe>
|
||||
<div className="max-w-4xl mx-auto mt-16 flex justify-center items-center flex-col gap-1 mb-4">
|
||||
<p className="text-2xl md:text-4xl font-bold text-center text-gray-800 dark:text-gray-200">{t('cv.title')}</p>
|
||||
<iframe src="/CV%20Félix%20MARQUET.pdf" className="w-full md:w-3/4 h-64 md:h-96 lg:h-screen mt-3" title="CV Félix MARQUET"></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-full" id="top">
|
||||
<div className="flex flex-col items-center justify-center max-w-xs mx-auto bg-white shadow-xl rounded-xl p-5 dark:bg-gray-800">
|
||||
@@ -17,7 +19,7 @@ function Card({name, title, social: {github, twitter, linkedin, mail}}){
|
||||
</div>
|
||||
<div className="text-center mt-5">
|
||||
<p className="text-xl sm:text-2xl text-gray-900 dark:text-white">{name}</p>
|
||||
<p className="text-xs sm:text-base text-gray-600 pt-2 pb-4 px-5 w-auto inline-block border-b-2 dark:text-gray-200"> {title} </p>
|
||||
<p className="text-xs sm:text-base text-gray-600 pt-2 pb-4 px-5 w-auto inline-block border-b-2 dark:text-gray-200"> {t('card.title')} </p>
|
||||
<div className="flex align-center justify-center mt-4">
|
||||
<a
|
||||
className="text-xl m-1 p-1 sm:m-2 sm:p-2 text-gray-800 hover:bg-gray-800 rounded-full hover:text-white transition-colors duration-300 dark:text-gray-200 dark:hover:bg-gray-200 dark:hover:text-gray-800 dark:hover:bg-opacity-20"
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
//@ts-ignore
|
||||
import React from 'react';
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
function Menu() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<nav className="fixed top-0 w-full bg-gray-100 dark:bg-gray-900 z-50 shadow-md shadow-gray-800 dark:shadow-gray-200">
|
||||
<ul className="flex justify-around">
|
||||
<li><a href="#top" className="text-gray-800 dark:text-gray-200">Félix MARQUET</a></li>
|
||||
<li><a href="#about" className="text-gray-800 dark:text-gray-200">A propos de moi</a></li>
|
||||
<li><a href="#projects" className="text-gray-800 dark:text-gray-200">Mes projets</a></li>
|
||||
<li><a href="#cv" className="text-gray-800 dark:text-gray-200">Mon CV</a></li>
|
||||
<li><a href="#about" className="text-gray-800 dark:text-gray-200">{t('nav.about')}</a></li>
|
||||
<li><a href="#projects" className="text-gray-800 dark:text-gray-200">{t('nav.projects')}</a></li>
|
||||
<li><a href="#cv" className="text-gray-800 dark:text-gray-200">{t('nav.cv')}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
<div id="projects">
|
||||
<h1 className="mt-8 text-2xl md:text-4xl text-center font-extrabold dark:text-gray-200">Mes projets</h1>
|
||||
<div>
|
||||
<h1 className="mt-8 text-2xl md:text-4xl text-center font-extrabold dark:text-gray-200">{t('projects.title')}</h1>
|
||||
<div className="flex-row flex-wrap flex justify-center gap-4">
|
||||
{projects.map((project) => (
|
||||
<ProjectCard project={project} />
|
||||
|
||||
@@ -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 (
|
||||
<div className="group w-full sm:w-5/12 m-4 mx-auto p-6 rounded-xl border-2 border-gray-300 dark:border-gray-700">
|
||||
<a href={link}>
|
||||
@@ -12,13 +16,13 @@ const projectCard = ({ project: { title, description, tags, link} }) => {
|
||||
</h1>
|
||||
</a>
|
||||
<hr className="my-4" />
|
||||
<p className="dark:text-gray-300">{description}</p>
|
||||
<p className="dark:text-gray-300">{t(`projects.${project.title}.description`)}</p>
|
||||
<div className="mt-4 mb-8 flex flex-wrap justify-center items-center gap-2">
|
||||
{tags.map((tag) => (
|
||||
<div className="px-4 py-1 border-2 rounded-full dark:text-gray-300">{tag}</div>
|
||||
))}
|
||||
</div>
|
||||
{title !== "Mercury Cloud" && (
|
||||
{title !== "MercuryCloud" && (
|
||||
<div className="w-full text-center">
|
||||
<GitHubButton href={link} data-color-scheme="no-preference: light; light: light; dark: dark;" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star ntkme/github-buttons on GitHub">Star</GitHubButton>
|
||||
{" "}
|
||||
@@ -29,4 +33,4 @@ const projectCard = ({ project: { title, description, tags, link} }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default projectCard;
|
||||
export default ProjectCard;
|
||||
52
src/i18n.js
Normal file
52
src/i18n.js
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user