mirror of
				https://github.com/theoludwig/theoludwig.git
				synced 2025-10-14 20:23:25 +02:00 
			
		
		
		
	feat: translate Curriculum Vitae in both English and French
This commit is contained in:
		| @@ -161,17 +161,10 @@ code .line:last-child { | ||||
|   width: 100% !important; | ||||
| } | ||||
|  | ||||
| .container-fluid { | ||||
|   padding-right: 15px; | ||||
|   padding-left: 15px; | ||||
|   margin-right: auto; | ||||
|   margin-left: auto; | ||||
| } | ||||
| .curriculum-vitae { | ||||
|   background: #f0f0f0; | ||||
|   color: #333; | ||||
|   line-height: 1.42857143; | ||||
|   font-size: 14px; | ||||
|   font-family: Arial, sans-serif; | ||||
|  | ||||
|   hr { | ||||
|     margin-top: 15px; | ||||
| @@ -182,9 +175,6 @@ code .line:last-child { | ||||
|   p { | ||||
|     margin: 0; | ||||
|   } | ||||
|   strong { | ||||
|     font-weight: 600; | ||||
|   } | ||||
|   a { | ||||
|     color: #337ab7; | ||||
|     text-decoration: none; | ||||
| @@ -231,7 +221,6 @@ code .line:last-child { | ||||
|   h5, | ||||
|   h6 { | ||||
|     font-family: inherit; | ||||
|     font-weight: 500; | ||||
|     line-height: 1.1; | ||||
|     color: inherit; | ||||
|   } | ||||
| @@ -247,41 +236,16 @@ code .line:last-child { | ||||
|   h5 { | ||||
|     font-size: 14px; | ||||
|   } | ||||
|   .row { | ||||
|     margin-right: -15px; | ||||
|     margin-left: -15px; | ||||
|   } | ||||
|   .clear-margin { | ||||
|     margin: 0; | ||||
|   } | ||||
|   .relative { | ||||
|     position: relative; | ||||
|   } | ||||
|   .center-block { | ||||
|     display: block; | ||||
|     margin-right: auto; | ||||
|     margin-left: auto; | ||||
|   } | ||||
|   .text-muted { | ||||
|     color: #777; | ||||
|   } | ||||
|   .text-uppercase { | ||||
|     text-transform: uppercase; | ||||
|   } | ||||
|   .list-unstyled { | ||||
|     padding-left: 0; | ||||
|     list-style: none; | ||||
|   } | ||||
|  | ||||
|   .main { | ||||
|     padding: 5px; | ||||
|   } | ||||
|   .title { | ||||
|     font-weight: 600; | ||||
|   } | ||||
|  | ||||
|   .profile-card-wrapper { | ||||
|     position: relative; | ||||
|   ul { | ||||
|     padding-inline-start: 20px; | ||||
|   } | ||||
|  | ||||
|   .card-wrapper { | ||||
| @@ -289,10 +253,6 @@ code .line:last-child { | ||||
|     padding: 5px; | ||||
|   } | ||||
|  | ||||
|   .profile-card-wrapper .profile-card { | ||||
|     padding: 10px; | ||||
|   } | ||||
|  | ||||
|   .card { | ||||
|     background: white; | ||||
|     border-radius: 3px; | ||||
| @@ -308,21 +268,9 @@ code .line:last-child { | ||||
|     vertical-align: middle; | ||||
|     border: 0; | ||||
|   } | ||||
|   .contact-details { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|   } | ||||
|   .contact-details .detail { | ||||
|     position: relative; | ||||
|     min-height: 1px; | ||||
|     padding: 10px; | ||||
|   } | ||||
|   .social-links { | ||||
|     line-height: 2.5; | ||||
|   } | ||||
|   .experience-description { | ||||
|     margin-top: 10px; | ||||
|   } | ||||
|  | ||||
|   .background-details .detail { | ||||
|     display: table; | ||||
| @@ -350,15 +298,9 @@ code .line:last-child { | ||||
|     min-height: 0; | ||||
|   } | ||||
|  | ||||
|   .card-skills { | ||||
|     position: relative; | ||||
|   } | ||||
|   .labels { | ||||
|     line-height: 2; | ||||
|   } | ||||
|   .space-top { | ||||
|     margin-top: 10px; | ||||
|   } | ||||
|   .label { | ||||
|     display: inline; | ||||
|     padding: 0.2em 0.6em 0.3em; | ||||
| @@ -380,7 +322,4 @@ code .line:last-child { | ||||
|   .label-keyword p { | ||||
|     margin: 0; | ||||
|   } | ||||
|   .section-separated { | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -12,7 +12,8 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives", | ||||
|     "lint:typescript": "tsc --noEmit" | ||||
|     "lint:typescript": "tsc --noEmit", | ||||
|     "test": "vitest run" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "deepmerge": "catalog:", | ||||
| @@ -28,6 +29,7 @@ | ||||
|     "@types/react-dom": "catalog:", | ||||
|     "@total-typescript/ts-reset": "catalog:", | ||||
|     "eslint": "catalog:", | ||||
|     "typescript": "catalog:" | ||||
|     "typescript": "catalog:", | ||||
|     "vitest": "catalog:" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										7
									
								
								packages/i18n/src/tests/translations.test-d.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/i18n/src/tests/translations.test-d.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import { expectTypeOf, test } from "vitest" | ||||
| import en from "../translations/en-US.json" | ||||
| import fr from "../translations/fr-FR.json" | ||||
|  | ||||
| test("translations types should match", () => { | ||||
|   expectTypeOf(en).toEqualTypeOf(fr) | ||||
| }) | ||||
| @@ -7,9 +7,6 @@ | ||||
|     "en-US": "English", | ||||
|     "fr-FR": "French" | ||||
|   }, | ||||
|   "common": { | ||||
|     "others": "Others" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "All rights reserved" | ||||
|   }, | ||||
| @@ -58,7 +55,8 @@ | ||||
|       "frontend": "Frontend", | ||||
|       "backend": "Backend", | ||||
|       "software-tools": "Software and tools", | ||||
|       "driving-license": "Permis B" | ||||
|       "others": "Others", | ||||
|       "driving-license": "Driving license" | ||||
|     }, | ||||
|     "portfolio": { | ||||
|       "title": "Portfolio", | ||||
| @@ -77,54 +75,54 @@ | ||||
|     } | ||||
|   }, | ||||
|   "curriculum-vitae": { | ||||
|     "description": "Développeur Full Stack • Étudiant", | ||||
|     "description": "Developer Full Stack • Student", | ||||
|     "about": { | ||||
|       "title": "À propos", | ||||
|       "description": "Je me demande constamment comment améliorer notre présent, afin de rendre notre futur meilleur, particulièrement grâce aux progrès de l'informatique. <br></br> Ma priorité réside dans la création d'expériences utilisateurs (UX) intuitives, répondant aux besoins des utilisateurs de la manière la plus efficace que possible." | ||||
|       "title": "About", | ||||
|       "description": "I constantly wonder how to improve our present, to make our future better, particularly thanks to the advancements in computer science. <br></br> My priority is to craft intuitive user experiences (UX), that meet the needs of the users in the most efficient way possible." | ||||
|     }, | ||||
|     "education": { | ||||
|       "title": "Formations", | ||||
|       "title": "Studies", | ||||
|       "iut": { | ||||
|         "study-type": "Bachelor Universitaire de Technologie (BUT) Informatique", | ||||
|         "institution": "IUT Robert Schuman à Illkirch-Graffenstaden", | ||||
|         "study-type": "University Bachelor of Technology (BUT) Computer Science", | ||||
|         "institution": "IUT Robert Schuman in Illkirch-Graffenstaden", | ||||
|         "years": { | ||||
|           "2023-2024": { | ||||
|             "title": "2023 - 2024", | ||||
|             "description": "3ème année", | ||||
|             "description": "3rd year", | ||||
|             "courses": { | ||||
|               "web": "Développement Web en Node.js et React.js", | ||||
|               "ci-cd": "Intégration/Déploiement Continue et Docker", | ||||
|               "complexity-algorithms": "Complexité Algorithmique Théorique et Pratique en C++", | ||||
|               "no-sql": "Base de données NoSQL (Redis, MongoDB, Cassandra)" | ||||
|               "web": "Web development in Node.js and React.js", | ||||
|               "ci-cd": "Continuous Integration/Deployment (CI/CD) and Docker", | ||||
|               "complexity-algorithms": "Theoretical and Practical Algorithmic Complexity in C++", | ||||
|               "no-sql": "NoSQL database (Redis, MongoDB, Cassandra)" | ||||
|             } | ||||
|           }, | ||||
|           "2022-2023": { | ||||
|             "title": "2022 - 2023", | ||||
|             "description": "2ème année", | ||||
|             "description": "2nd year", | ||||
|             "courses": { | ||||
|               "web": "Développement Web avec le framework Laravel en PHP", | ||||
|               "tests": "Qualité de développement et Tests automatisés", | ||||
|               "clean-code": "Patrons et Principes de conceptions (Code maintenable et réutilisable) en UML", | ||||
|               "systems-c": "Programmation systèmes en C (Multi-Thread, Serveur/Client UDP/TCP)", | ||||
|               "sql-security": "Sécurisation des accès à la base de données et PL/SQL" | ||||
|               "web": "Web development with the Laravel framework in PHP", | ||||
|               "tests": "Development Quality and Automated Testing", | ||||
|               "clean-code": "Design Patterns and Principles (Maintainable and Reusable Code) in UML", | ||||
|               "systems-c": "Systems programming in C (Multi-Thread, Server/Client UDP/TCP)", | ||||
|               "sql-security": "Securing database access and PL/SQL" | ||||
|             } | ||||
|           }, | ||||
|           "2021-2022": { | ||||
|             "title": "2021 - 2022", | ||||
|             "description": "1ère année", | ||||
|             "description": "1st year", | ||||
|             "courses": { | ||||
|               "java": "Développement Orientée Objet en Java", | ||||
|               "systems-c": "Programmation systèmes en C (Allocation mémoire, Pointeurs, Structures)", | ||||
|               "windows-forms": "Développement d'application Windows Forms (.NET Framework) en C#", | ||||
|               "sql": "Base de données relationnelles et langage SQL" | ||||
|               "java": "Object Oriented Development in Java", | ||||
|               "systems-c": "Systems programming in C (Memory allocation, Pointers, Structures)", | ||||
|               "windows-forms": "Windows Forms (.NET Framework) Application Development in C#", | ||||
|               "sql": "Relational database and SQL language" | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "lycee": { | ||||
|         "study-type": "Baccalauréat Général (Mathématiques et Numériques Sciences Informatiques)", | ||||
|         "institution": "Lycée Heinrich Nessel à Haguenau", | ||||
|         "score": "Mention Assez Bien", | ||||
|         "study-type": "General Baccalaureate (Mathematics and Computer Science)", | ||||
|         "institution": "Heinrich Nessel High School in Haguenau", | ||||
|         "score": "Mention Quite Good", | ||||
|         "years": { | ||||
|           "2019-2021": { | ||||
|             "title": "2019 - 2021" | ||||
| @@ -133,52 +131,21 @@ | ||||
|       } | ||||
|     }, | ||||
|     "work": { | ||||
|       "title": "Expériences", | ||||
|       "title": "Work experiences", | ||||
|       "ircad": { | ||||
|         "summary": "Développement de WebSurg, une université virtuelle consacrée à la formation médico-chirurgicale, en React.js/Next.js et API Platform avec Symfony.", | ||||
|         "website": "https://ircad.fr/", | ||||
|         "name": "IRCAD", | ||||
|         "location": "1 Place de l'Hôpital, 67000 Strasbourg", | ||||
|         "position": "Alternant Développeur Web Full Stack", | ||||
|         "startDate": "2023-08-28", | ||||
|         "endDate": "2024-09-02", | ||||
|         "duration": "1 an" | ||||
|         "summary": "Development of WebSurg, a virtual university dedicated to medical-surgical training, in React.js/Next.js and API Platform with Symfony.", | ||||
|         "position": "Full Stack Web Developer Apprentice", | ||||
|         "duration": "1 year" | ||||
|       }, | ||||
|       "numerize": { | ||||
|         "summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL.", | ||||
|         "website": "https://numerize.com/", | ||||
|         "name": "Numerize", | ||||
|         "location": "4 Rue Sophie Germain, 67720 Hœrdt", | ||||
|         "position": "Stagiaire Développeur Web Full Stack", | ||||
|         "startDate": "2023-04-11", | ||||
|         "endDate": "2023-07-26", | ||||
|         "duration": "4 mois" | ||||
|       } | ||||
|     }, | ||||
|     "interests-work": { | ||||
|       "nuit-info-2021": { | ||||
|         "summary": "Développement site web en React.js et Strapi.<br></br> Classé n°1 en France sur le Défi de l'entreprise <toolpad-link>ToolPad</toolpad-link>.", | ||||
|         "website": "https://nuitdelinfo.com/", | ||||
|         "name": "La Nuit de l'info 2021", | ||||
|         "position": "Participation en équipe de 5 personnes", | ||||
|         "startDate": "2021-12-02", | ||||
|         "endDate": "2021-12-03", | ||||
|         "duration": "1 semaine" | ||||
|       }, | ||||
|       "wild-code-school": { | ||||
|         "summary": "Hackathon développement d'une landing page et web scraping.", | ||||
|         "website": "https://wildcodeschool.fr/", | ||||
|         "name": "Wild Code School", | ||||
|         "location": "32 Rue du Bassin d'Austerlitz, 67100 Strasbourg", | ||||
|         "position": "Initiation métier Développeur web", | ||||
|         "startDate": "2019-06-24", | ||||
|         "endDate": "2019-06-28", | ||||
|         "duration": "1 semaine" | ||||
|         "summary": "Development of an DMS (Document Management System) tool in React.js, Laravel and GraphQL.", | ||||
|         "position": "Full Stack Web Developer Intern", | ||||
|         "duration": "4 months" | ||||
|       } | ||||
|     }, | ||||
|     "interests": { | ||||
|       "open-source": "Enthousiaste de l'Open-Source", | ||||
|       "high-tech": "Passionné de High-Tech" | ||||
|       "open-source": "Open-Source Enthusiast", | ||||
|       "high-tech": "Passionate about High-Tech" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,9 +7,6 @@ | ||||
|     "en-US": "Anglais", | ||||
|     "fr-FR": "Français" | ||||
|   }, | ||||
|   "common": { | ||||
|     "others": "Autres" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "Tous droits réservés" | ||||
|   }, | ||||
| @@ -58,6 +55,7 @@ | ||||
|       "frontend": "Frontend", | ||||
|       "backend": "Backend", | ||||
|       "software-tools": "Logiciels et outils", | ||||
|       "others": "Autres", | ||||
|       "driving-license": "Permis B" | ||||
|     }, | ||||
|     "portfolio": { | ||||
| @@ -83,7 +81,7 @@ | ||||
|       "description": "Je me demande constamment comment améliorer notre présent, afin de rendre notre futur meilleur, particulièrement grâce aux progrès de l'informatique. <br></br> Ma priorité réside dans la création d'expériences utilisateurs (UX) intuitives, répondant aux besoins des utilisateurs de la manière la plus efficace que possible." | ||||
|     }, | ||||
|     "education": { | ||||
|       "title": "Formations", | ||||
|       "title": "Études", | ||||
|       "iut": { | ||||
|         "study-type": "Bachelor Universitaire de Technologie (BUT) Informatique", | ||||
|         "institution": "IUT Robert Schuman à Illkirch-Graffenstaden", | ||||
| @@ -133,49 +131,18 @@ | ||||
|       } | ||||
|     }, | ||||
|     "work": { | ||||
|       "title": "Expériences", | ||||
|       "title": "Expériences professionnelles", | ||||
|       "ircad": { | ||||
|         "summary": "Développement de WebSurg, une université virtuelle consacrée à la formation médico-chirurgicale, en React.js/Next.js et API Platform avec Symfony.", | ||||
|         "website": "https://ircad.fr/", | ||||
|         "name": "IRCAD", | ||||
|         "location": "1 Place de l'Hôpital, 67000 Strasbourg", | ||||
|         "position": "Alternant Développeur Web Full Stack", | ||||
|         "startDate": "2023-08-28", | ||||
|         "endDate": "2024-09-02", | ||||
|         "position": "Apprenti Développeur Web Full Stack", | ||||
|         "duration": "1 an" | ||||
|       }, | ||||
|       "numerize": { | ||||
|         "summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL.", | ||||
|         "website": "https://numerize.com/", | ||||
|         "name": "Numerize", | ||||
|         "location": "4 Rue Sophie Germain, 67720 Hœrdt", | ||||
|         "position": "Stagiaire Développeur Web Full Stack", | ||||
|         "startDate": "2023-04-11", | ||||
|         "endDate": "2023-07-26", | ||||
|         "duration": "4 mois" | ||||
|       } | ||||
|     }, | ||||
|     "interests-work": { | ||||
|       "nuit-info-2021": { | ||||
|         "summary": "Développement site web en React.js et Strapi. <br></br> Classé n°1 en France sur le Défi de l'entreprise <toolpad-link>ToolPad</toolpad-link>.", | ||||
|         "website": "https://nuitdelinfo.com/", | ||||
|         "name": "La Nuit de l'info 2021", | ||||
|         "position": "Participation en équipe de 5 personnes", | ||||
|         "startDate": "2021-12-02", | ||||
|         "endDate": "2021-12-03", | ||||
|         "duration": "1 semaine" | ||||
|       }, | ||||
|       "wild-code-school": { | ||||
|         "summary": "Hackathon développement d'une landing page et web scraping.", | ||||
|         "website": "https://wildcodeschool.fr/", | ||||
|         "name": "Wild Code School", | ||||
|         "location": "32 Rue du Bassin d'Austerlitz, 67100 Strasbourg", | ||||
|         "position": "Initiation métier Développeur web", | ||||
|         "startDate": "2019-06-24", | ||||
|         "endDate": "2019-06-28", | ||||
|         "duration": "1 semaine" | ||||
|       } | ||||
|     }, | ||||
|     "interests": { | ||||
|       "open-source": "Enthousiaste de l'Open-Source", | ||||
|       "high-tech": "Passionné de High-Tech" | ||||
|   | ||||
							
								
								
									
										9
									
								
								packages/i18n/vitest.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/i18n/vitest.config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { defineConfig } from "vitest/config" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   test: { | ||||
|     typecheck: { | ||||
|       enabled: true, | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
| @@ -9,9 +9,9 @@ export interface CurriculumVitaeProps {} | ||||
|  | ||||
| export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => { | ||||
|   return ( | ||||
|     <main className="curriculum-vitae container-fluid"> | ||||
|       <div className="row main clearfix"> | ||||
|         <section className="col-md-3 card-wrapper profile-card-wrapper affix"> | ||||
|     <main className="curriculum-vitae mx-auto px-4 text-sm"> | ||||
|       <div className="-mx-4 p-2"> | ||||
|         <section className="col-md-3 card-wrapper relative"> | ||||
|           <CurriculumVitaeProfile /> | ||||
|  | ||||
|           <div className="card background-card"> | ||||
| @@ -20,14 +20,14 @@ export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => { | ||||
|  | ||||
|               <hr /> | ||||
|  | ||||
|               <section className="section-separated"> | ||||
|               <section className="flex"> | ||||
|                 <CurriculumVitaeEducation /> | ||||
|                 <CurriculumVitaeSkills /> | ||||
|               </section> | ||||
|  | ||||
|               <hr /> | ||||
|  | ||||
|               <section className="section-separated"> | ||||
|               <section className="flex"> | ||||
|                 <CurriculumVitaeWork /> | ||||
|                 <CurriculumVitaeInterests /> | ||||
|               </section> | ||||
|   | ||||
| @@ -9,13 +9,98 @@ export const CurriculumVitaeEducation: React.FC< | ||||
| > = () => { | ||||
|   const t = useTranslations() | ||||
|  | ||||
|   const educations = [ | ||||
|     { | ||||
|       years: t("curriculum-vitae.education.iut.years.2023-2024.title"), | ||||
|       studyType: t("curriculum-vitae.education.iut.study-type"), | ||||
|       institution: t("curriculum-vitae.education.iut.institution"), | ||||
|       score: t("curriculum-vitae.education.iut.years.2023-2024.description"), | ||||
|       courses: [ | ||||
|         t("curriculum-vitae.education.iut.years.2023-2024.courses.web"), | ||||
|         t("curriculum-vitae.education.iut.years.2023-2024.courses.ci-cd"), | ||||
|         t( | ||||
|           "curriculum-vitae.education.iut.years.2023-2024.courses.complexity-algorithms", | ||||
|         ), | ||||
|         t("curriculum-vitae.education.iut.years.2023-2024.courses.no-sql"), | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       years: t("curriculum-vitae.education.iut.years.2022-2023.title"), | ||||
|       studyType: t("curriculum-vitae.education.iut.study-type"), | ||||
|       institution: t("curriculum-vitae.education.iut.institution"), | ||||
|       score: t("curriculum-vitae.education.iut.years.2022-2023.description"), | ||||
|       courses: [ | ||||
|         t("curriculum-vitae.education.iut.years.2022-2023.courses.web"), | ||||
|         t("curriculum-vitae.education.iut.years.2022-2023.courses.tests"), | ||||
|         t("curriculum-vitae.education.iut.years.2022-2023.courses.clean-code"), | ||||
|         t("curriculum-vitae.education.iut.years.2022-2023.courses.systems-c"), | ||||
|         t( | ||||
|           "curriculum-vitae.education.iut.years.2022-2023.courses.sql-security", | ||||
|         ), | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       years: t("curriculum-vitae.education.iut.years.2021-2022.title"), | ||||
|       studyType: t("curriculum-vitae.education.iut.study-type"), | ||||
|       institution: t("curriculum-vitae.education.iut.institution"), | ||||
|       score: t("curriculum-vitae.education.iut.years.2021-2022.description"), | ||||
|       courses: [ | ||||
|         t("curriculum-vitae.education.iut.years.2021-2022.courses.java"), | ||||
|         t("curriculum-vitae.education.iut.years.2021-2022.courses.systems-c"), | ||||
|         t( | ||||
|           "curriculum-vitae.education.iut.years.2021-2022.courses.windows-forms", | ||||
|         ), | ||||
|         t("curriculum-vitae.education.iut.years.2021-2022.courses.sql"), | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       years: t("curriculum-vitae.education.lycee.years.2019-2021.title"), | ||||
|       studyType: t("curriculum-vitae.education.lycee.study-type"), | ||||
|       institution: t("curriculum-vitae.education.lycee.institution"), | ||||
|       score: t("curriculum-vitae.education.lycee.score"), | ||||
|       courses: [], | ||||
|     }, | ||||
|   ] | ||||
|  | ||||
|   return ( | ||||
|     <CurriculumVitaeSection | ||||
|       id="education" | ||||
|       title={t("curriculum-vitae.education.title")} | ||||
|       icon={<FaGraduationCap size={24} />} | ||||
|     > | ||||
|       <p>Test</p> | ||||
|       <ul className="list-unstyled m-0"> | ||||
|         {educations.map((education) => { | ||||
|           return ( | ||||
|             <li key={education.years} className="card card-nested"> | ||||
|               <div className="content"> | ||||
|                 <p className="relative m-0"> | ||||
|                   <strong>{education.studyType}</strong> | ||||
|                 </p> | ||||
|  | ||||
|                 <p className="relative m-0"> | ||||
|                   <strong>{education.score}</strong> | ||||
|                 </p> | ||||
|  | ||||
|                 <p className="text-muted m-0">{education.institution}</p> | ||||
|  | ||||
|                 <p className="text-muted m-0"> | ||||
|                   <small>{education.years}</small> | ||||
|                 </p> | ||||
|  | ||||
|                 {education.courses.length > 0 ? ( | ||||
|                   <ul className="educational-courses"> | ||||
|                     {education.courses.map((course) => { | ||||
|                       return <li key={course}>{course}</li> | ||||
|                     })} | ||||
|                   </ul> | ||||
|                 ) : ( | ||||
|                   <></> | ||||
|                 )} | ||||
|               </div> | ||||
|             </li> | ||||
|           ) | ||||
|         })} | ||||
|       </ul> | ||||
|     </CurriculumVitaeSection> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -9,13 +9,28 @@ export const CurriculumVitaeInterests: React.FC< | ||||
| > = () => { | ||||
|   const t = useTranslations() | ||||
|  | ||||
|   const interests = [ | ||||
|     t("curriculum-vitae.interests.open-source"), | ||||
|     t("curriculum-vitae.interests.high-tech"), | ||||
|   ] | ||||
|  | ||||
|   return ( | ||||
|     <CurriculumVitaeSection | ||||
|       id="interests" | ||||
|       title={t("home.interests.title")} | ||||
|       icon={<FaHeart size={24} />} | ||||
|     > | ||||
|       <p>Test</p> | ||||
|       <ul className="list-unstyled m-0"> | ||||
|         {interests.map((interest) => { | ||||
|           return ( | ||||
|             <li key={interest} className="card card-nested"> | ||||
|               <p> | ||||
|                 <strong>{interest}</strong> | ||||
|               </p> | ||||
|             </li> | ||||
|           ) | ||||
|         })} | ||||
|       </ul> | ||||
|     </CurriculumVitaeSection> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -11,11 +11,11 @@ export const CurriculumVitaeProfile: React.FC< | ||||
|   const t = useTranslations() | ||||
|  | ||||
|   return ( | ||||
|     <div className="card profile-card"> | ||||
|     <div className="card p-2"> | ||||
|       <div className="profile-pic-container"> | ||||
|         <div className="profile-pic"> | ||||
|           <Image | ||||
|             className="media-object img-circle center-block" | ||||
|             className="mx-auto block" | ||||
|             alt={t("meta.title")} | ||||
|             src="/images/logo_background.webp" | ||||
|             width={800} | ||||
| @@ -33,8 +33,8 @@ export const CurriculumVitaeProfile: React.FC< | ||||
|           <h5 className="text-muted">{t("home.about.nationality.value")}</h5> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className="contact-details clearfix"> | ||||
|         <div className="detail"> | ||||
|       <div className="flex justify-center"> | ||||
|         <div className="relative px-3"> | ||||
|           <span className="info"> | ||||
|             <a className="link-disguise" href="mailto:contact@theoludwig.fr"> | ||||
|               contact@theoludwig.fr | ||||
|   | ||||
| @@ -14,7 +14,7 @@ export const CurriculumVitaeSection: React.FC<CurriculumVitaeSectionProps> = ( | ||||
|       <div className="icon">{icon}</div> | ||||
|  | ||||
|       <div className="info"> | ||||
|         <h4 className="title text-uppercase">{title}</h4> | ||||
|         <h4 className="font-semibold uppercase">{title}</h4> | ||||
|  | ||||
|         <div className="content">{children}</div> | ||||
|       </div> | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| import { useTranslations } from "next-intl" | ||||
| import { FaToolbox } from "react-icons/fa" | ||||
| import { | ||||
|   SKILL_CATEGORIES, | ||||
|   SKILL_NAMES_BY_CATEGORY, | ||||
| } from "../Home/Skills/skills" | ||||
| import { CurriculumVitaeSection } from "./CurriculumVitaeSection" | ||||
|  | ||||
| export interface CurriculumVitaeSkillsProps {} | ||||
| @@ -9,13 +13,47 @@ export const CurriculumVitaeSkills: React.FC< | ||||
| > = () => { | ||||
|   const t = useTranslations() | ||||
|  | ||||
|   const skills = [ | ||||
|     ...SKILL_CATEGORIES.map((category) => { | ||||
|       const skillNames = SKILL_NAMES_BY_CATEGORY[category] | ||||
|       return { | ||||
|         category, | ||||
|         skillNames, | ||||
|       } | ||||
|     }), | ||||
|     { | ||||
|       category: "others", | ||||
|       skillNames: [t("locales.en-US"), t("home.skills.driving-license")], | ||||
|     }, | ||||
|   ] as const | ||||
|  | ||||
|   return ( | ||||
|     <CurriculumVitaeSection | ||||
|       id="skills" | ||||
|       title={t("home.skills.title")} | ||||
|       icon={<FaToolbox size={24} />} | ||||
|     > | ||||
|       <p>Test</p> | ||||
|       <ul className="list-unstyled m-0"> | ||||
|         {skills.map(({ category, skillNames }) => { | ||||
|           return ( | ||||
|             <li key={category} className="card card-nested relative"> | ||||
|               <div className="skill-info"> | ||||
|                 <strong>{t(`home.skills.${category}`)}</strong> | ||||
|  | ||||
|                 <div className="labels mt-2"> | ||||
|                   {skillNames.map((skillName) => { | ||||
|                     return ( | ||||
|                       <p key={skillName} className="label label-keyword"> | ||||
|                         {skillName} | ||||
|                       </p> | ||||
|                     ) | ||||
|                   })} | ||||
|                 </div> | ||||
|               </div> | ||||
|             </li> | ||||
|           ) | ||||
|         })} | ||||
|       </ul> | ||||
|     </CurriculumVitaeSection> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -7,13 +7,63 @@ export interface CurriculumVitaeWorkProps {} | ||||
| export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => { | ||||
|   const t = useTranslations() | ||||
|  | ||||
|   const workExperiences = [ | ||||
|     { | ||||
|       summary: t("curriculum-vitae.work.ircad.summary"), | ||||
|       website: "https://ircad.fr/", | ||||
|       name: "IRCAD", | ||||
|       location: "1 Place de l'Hôpital, FR-67000 Strasbourg", | ||||
|       position: t("curriculum-vitae.work.ircad.position"), | ||||
|       dates: "28/08/2023 - 02/09/2024", | ||||
|       duration: t("curriculum-vitae.work.ircad.duration"), | ||||
|     }, | ||||
|     { | ||||
|       summary: t("curriculum-vitae.work.numerize.summary"), | ||||
|       website: "https://numerize.com/", | ||||
|       name: "Numerize", | ||||
|       location: "4 Rue Sophie Germain, FR-67720 Hœrdt", | ||||
|       position: t("curriculum-vitae.work.numerize.position"), | ||||
|       dates: "11/04/2023 - 26/07/2023", | ||||
|       duration: t("curriculum-vitae.work.numerize.duration"), | ||||
|     }, | ||||
|   ] | ||||
|  | ||||
|   return ( | ||||
|     <CurriculumVitaeSection | ||||
|       id="work-experience" | ||||
|       title={t("curriculum-vitae.work.title")} | ||||
|       icon={<MdWork size={24} />} | ||||
|     > | ||||
|       <p>Test</p> | ||||
|       <ul className="list-unstyled m-0"> | ||||
|         {workExperiences.map((workExperience) => { | ||||
|           return ( | ||||
|             <li key={workExperience.name} className="card card-nested"> | ||||
|               <p className="relative m-0"> | ||||
|                 <strong> | ||||
|                   <a href={workExperience.website} target="_blank"> | ||||
|                     {workExperience.name} | ||||
|                   </a> | ||||
|                 </strong> | ||||
|               </p> | ||||
|  | ||||
|               <p className="relative m-0"> | ||||
|                 <strong>{workExperience.position}</strong> | ||||
|               </p> | ||||
|  | ||||
|               <p className="text-muted"> | ||||
|                 <small> | ||||
|                   <span className="space-right"> | ||||
|                     {workExperience.dates} ({workExperience.duration}) | ||||
|                   </span> | ||||
|                 </small> | ||||
|               </p> | ||||
|               <div className="mt-2"> | ||||
|                 <p>{workExperience.summary}</p> | ||||
|               </div> | ||||
|             </li> | ||||
|           ) | ||||
|         })} | ||||
|       </ul> | ||||
|     </CurriculumVitaeSection> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { useMemo } from "react" | ||||
| import { Link } from "../../Design/Link/Link" | ||||
| import { useTheme } from "../../Layout/Header/SwitchTheme" | ||||
| import type { SkillName } from "./skills" | ||||
| import { skills } from "./skills" | ||||
| import { SKILLS } from "./skills" | ||||
|  | ||||
| export interface SkillItemProps { | ||||
|   skillName: SkillName | ||||
| @@ -14,7 +14,7 @@ export interface SkillItemProps { | ||||
| export const SkillItem: React.FC<SkillItemProps> = (props) => { | ||||
|   const { skillName } = props | ||||
|  | ||||
|   const skill = skills[skillName] | ||||
|   const skill = SKILLS[skillName] | ||||
|  | ||||
|   const { theme } = useTheme() | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { useTranslations } from "next-intl" | ||||
| import { Section, SectionTitle } from "../../Layout/Section/Section" | ||||
| import { SkillItem } from "./SkillItem" | ||||
| import { SkillsSection } from "./SkillsSection" | ||||
| import { SKILL_CATEGORIES, SKILL_NAMES_BY_CATEGORY } from "./skills" | ||||
|  | ||||
| export interface SkillsProps {} | ||||
|  | ||||
| @@ -12,34 +13,17 @@ export const Skills: React.FC<SkillsProps> = () => { | ||||
|     <Section verticalSpacing horizontalSpacing id="skills"> | ||||
|       <SectionTitle>{t("home.skills.title")}</SectionTitle> | ||||
|  | ||||
|       <SkillsSection title={t("home.skills.programming-languages")}> | ||||
|         <SkillItem skillName="TypeScript" /> | ||||
|         <SkillItem skillName="Python" /> | ||||
|         <SkillItem skillName="C/C++" /> | ||||
|         <SkillItem skillName="PHP" /> | ||||
|       </SkillsSection> | ||||
|       {SKILL_CATEGORIES.map((category) => { | ||||
|         const skillNames = SKILL_NAMES_BY_CATEGORY[category] | ||||
|  | ||||
|       <SkillsSection title={t("home.skills.frontend")}> | ||||
|         <SkillItem skillName="HTML" /> | ||||
|         <SkillItem skillName="CSS" /> | ||||
|         <SkillItem skillName="Tailwind CSS" /> | ||||
|         <SkillItem skillName="React.js (+ Next.js)" /> | ||||
|       </SkillsSection> | ||||
|  | ||||
|       <SkillsSection title={t("home.skills.backend")}> | ||||
|         <SkillItem skillName="Laravel" /> | ||||
|         <SkillItem skillName="Node.js" /> | ||||
|         <SkillItem skillName="Fastify" /> | ||||
|         <SkillItem skillName="PostgreSQL" /> | ||||
|       </SkillsSection> | ||||
|  | ||||
|       <SkillsSection title={t("home.skills.software-tools")}> | ||||
|         <SkillItem skillName="GNU/Linux" /> | ||||
|         <SkillItem skillName="Arch Linux" /> | ||||
|         <SkillItem skillName="Visual Studio Code" /> | ||||
|         <SkillItem skillName="Git" /> | ||||
|         <SkillItem skillName="Docker" /> | ||||
|       </SkillsSection> | ||||
|         return ( | ||||
|           <SkillsSection key={category} title={t(`home.skills.${category}`)}> | ||||
|             {skillNames.map((skillName) => { | ||||
|               return <SkillItem key={skillName} skillName={skillName} /> | ||||
|             })} | ||||
|           </SkillsSection> | ||||
|         ) | ||||
|       })} | ||||
|     </Section> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ export interface Skill { | ||||
|   image: string | { [key: string]: string } | ||||
| } | ||||
|  | ||||
| export const skills = { | ||||
| export const SKILLS = { | ||||
|   JavaScript: { | ||||
|     link: "https://developer.mozilla.org/docs/Web/JavaScript", | ||||
|     image: "/images/skills/JavaScript.webp", | ||||
| @@ -112,4 +112,27 @@ export const skills = { | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export type SkillName = keyof typeof skills | ||||
| export type SkillName = keyof typeof SKILLS | ||||
|  | ||||
| export const SKILL_CATEGORIES = [ | ||||
|   "programming-languages", | ||||
|   "frontend", | ||||
|   "backend", | ||||
|   "software-tools", | ||||
| ] as const | ||||
| export type SkillCategory = (typeof SKILL_CATEGORIES)[number] | ||||
|  | ||||
| export const SKILL_NAMES_BY_CATEGORY = { | ||||
|   "programming-languages": ["TypeScript", "Python", "C/C++", "PHP"], | ||||
|   frontend: ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"], | ||||
|   backend: ["Laravel", "Node.js", "Fastify", "PostgreSQL"], | ||||
|   "software-tools": [ | ||||
|     "GNU/Linux", | ||||
|     "Arch Linux", | ||||
|     "Visual Studio Code", | ||||
|     "Git", | ||||
|     "Docker", | ||||
|   ], | ||||
| } as const satisfies { | ||||
|   [key in SkillCategory]: SkillName[] | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -642,6 +642,9 @@ importers: | ||||
|       typescript: | ||||
|         specifier: 'catalog:' | ||||
|         version: 5.5.4 | ||||
|       vitest: | ||||
|         specifier: 'catalog:' | ||||
|         version: 2.0.4(@types/node@22.0.0)(@vitest/browser@2.0.4)(@vitest/ui@2.0.4)(terser@5.31.3) | ||||
|  | ||||
|   packages/react-hooks: | ||||
|     dependencies: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user