mirror of
https://github.com/theoludwig/theoludwig.git
synced 2024-12-08 00:44:30 +01:00
feat: translate Curriculum Vitae in both English and French
This commit is contained in:
parent
a596d1c443
commit
012fea869f
@ -161,17 +161,10 @@ code .line:last-child {
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid {
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-left: 15px;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
.curriculum-vitae {
|
.curriculum-vitae {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
color: #333;
|
color: #333;
|
||||||
line-height: 1.42857143;
|
font-family: Arial, sans-serif;
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
@ -182,9 +175,6 @@ code .line:last-child {
|
|||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
strong {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
a {
|
a {
|
||||||
color: #337ab7;
|
color: #337ab7;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -231,7 +221,6 @@ code .line:last-child {
|
|||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
@ -247,41 +236,16 @@ code .line:last-child {
|
|||||||
h5 {
|
h5 {
|
||||||
font-size: 14px;
|
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 {
|
.text-muted {
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
.text-uppercase {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.list-unstyled {
|
.list-unstyled {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
ul {
|
||||||
padding: 5px;
|
padding-inline-start: 20px;
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-card-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-wrapper {
|
.card-wrapper {
|
||||||
@ -289,10 +253,6 @@ code .line:last-child {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-card-wrapper .profile-card {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -308,21 +268,9 @@ code .line:last-child {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
.contact-details {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.contact-details .detail {
|
|
||||||
position: relative;
|
|
||||||
min-height: 1px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.social-links {
|
.social-links {
|
||||||
line-height: 2.5;
|
line-height: 2.5;
|
||||||
}
|
}
|
||||||
.experience-description {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.background-details .detail {
|
.background-details .detail {
|
||||||
display: table;
|
display: table;
|
||||||
@ -350,15 +298,9 @@ code .line:last-child {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-skills {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.labels {
|
.labels {
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
.space-top {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.label {
|
.label {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0.2em 0.6em 0.3em;
|
padding: 0.2em 0.6em 0.3em;
|
||||||
@ -380,7 +322,4 @@ code .line:last-child {
|
|||||||
.label-keyword p {
|
.label-keyword p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.section-separated {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives",
|
"lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives",
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "tsc --noEmit",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge": "catalog:",
|
"deepmerge": "catalog:",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
"@total-typescript/ts-reset": "catalog:",
|
||||||
"eslint": "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",
|
"en-US": "English",
|
||||||
"fr-FR": "French"
|
"fr-FR": "French"
|
||||||
},
|
},
|
||||||
"common": {
|
|
||||||
"others": "Others"
|
|
||||||
},
|
|
||||||
"footer": {
|
"footer": {
|
||||||
"all-rights-reserved": "All rights reserved"
|
"all-rights-reserved": "All rights reserved"
|
||||||
},
|
},
|
||||||
@ -58,7 +55,8 @@
|
|||||||
"frontend": "Frontend",
|
"frontend": "Frontend",
|
||||||
"backend": "Backend",
|
"backend": "Backend",
|
||||||
"software-tools": "Software and tools",
|
"software-tools": "Software and tools",
|
||||||
"driving-license": "Permis B"
|
"others": "Others",
|
||||||
|
"driving-license": "Driving license"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"title": "Portfolio",
|
"title": "Portfolio",
|
||||||
@ -77,54 +75,54 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"curriculum-vitae": {
|
"curriculum-vitae": {
|
||||||
"description": "Développeur Full Stack • Étudiant",
|
"description": "Developer Full Stack • Student",
|
||||||
"about": {
|
"about": {
|
||||||
"title": "À propos",
|
"title": "About",
|
||||||
"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."
|
"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": {
|
"education": {
|
||||||
"title": "Formations",
|
"title": "Studies",
|
||||||
"iut": {
|
"iut": {
|
||||||
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
"study-type": "University Bachelor of Technology (BUT) Computer Science",
|
||||||
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
"institution": "IUT Robert Schuman in Illkirch-Graffenstaden",
|
||||||
"years": {
|
"years": {
|
||||||
"2023-2024": {
|
"2023-2024": {
|
||||||
"title": "2023 - 2024",
|
"title": "2023 - 2024",
|
||||||
"description": "3ème année",
|
"description": "3rd year",
|
||||||
"courses": {
|
"courses": {
|
||||||
"web": "Développement Web en Node.js et React.js",
|
"web": "Web development in Node.js and React.js",
|
||||||
"ci-cd": "Intégration/Déploiement Continue et Docker",
|
"ci-cd": "Continuous Integration/Deployment (CI/CD) and Docker",
|
||||||
"complexity-algorithms": "Complexité Algorithmique Théorique et Pratique en C++",
|
"complexity-algorithms": "Theoretical and Practical Algorithmic Complexity in C++",
|
||||||
"no-sql": "Base de données NoSQL (Redis, MongoDB, Cassandra)"
|
"no-sql": "NoSQL database (Redis, MongoDB, Cassandra)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2022-2023": {
|
"2022-2023": {
|
||||||
"title": "2022 - 2023",
|
"title": "2022 - 2023",
|
||||||
"description": "2ème année",
|
"description": "2nd year",
|
||||||
"courses": {
|
"courses": {
|
||||||
"web": "Développement Web avec le framework Laravel en PHP",
|
"web": "Web development with the Laravel framework in PHP",
|
||||||
"tests": "Qualité de développement et Tests automatisés",
|
"tests": "Development Quality and Automated Testing",
|
||||||
"clean-code": "Patrons et Principes de conceptions (Code maintenable et réutilisable) en UML",
|
"clean-code": "Design Patterns and Principles (Maintainable and Reusable Code) in UML",
|
||||||
"systems-c": "Programmation systèmes en C (Multi-Thread, Serveur/Client UDP/TCP)",
|
"systems-c": "Systems programming in C (Multi-Thread, Server/Client UDP/TCP)",
|
||||||
"sql-security": "Sécurisation des accès à la base de données et PL/SQL"
|
"sql-security": "Securing database access and PL/SQL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2021-2022": {
|
"2021-2022": {
|
||||||
"title": "2021 - 2022",
|
"title": "2021 - 2022",
|
||||||
"description": "1ère année",
|
"description": "1st year",
|
||||||
"courses": {
|
"courses": {
|
||||||
"java": "Développement Orientée Objet en Java",
|
"java": "Object Oriented Development in Java",
|
||||||
"systems-c": "Programmation systèmes en C (Allocation mémoire, Pointeurs, Structures)",
|
"systems-c": "Systems programming in C (Memory allocation, Pointers, Structures)",
|
||||||
"windows-forms": "Développement d'application Windows Forms (.NET Framework) en C#",
|
"windows-forms": "Windows Forms (.NET Framework) Application Development in C#",
|
||||||
"sql": "Base de données relationnelles et langage SQL"
|
"sql": "Relational database and SQL language"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lycee": {
|
"lycee": {
|
||||||
"study-type": "Baccalauréat Général (Mathématiques et Numériques Sciences Informatiques)",
|
"study-type": "General Baccalaureate (Mathematics and Computer Science)",
|
||||||
"institution": "Lycée Heinrich Nessel à Haguenau",
|
"institution": "Heinrich Nessel High School in Haguenau",
|
||||||
"score": "Mention Assez Bien",
|
"score": "Mention Quite Good",
|
||||||
"years": {
|
"years": {
|
||||||
"2019-2021": {
|
"2019-2021": {
|
||||||
"title": "2019 - 2021"
|
"title": "2019 - 2021"
|
||||||
@ -133,52 +131,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"work": {
|
"work": {
|
||||||
"title": "Expériences",
|
"title": "Work experiences",
|
||||||
"ircad": {
|
"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.",
|
"summary": "Development of WebSurg, a virtual university dedicated to medical-surgical training, in React.js/Next.js and API Platform with Symfony.",
|
||||||
"website": "https://ircad.fr/",
|
"position": "Full Stack Web Developer Apprentice",
|
||||||
"name": "IRCAD",
|
"duration": "1 year"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"numerize": {
|
"numerize": {
|
||||||
"summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL.",
|
"summary": "Development of an DMS (Document Management System) tool in React.js, Laravel and GraphQL.",
|
||||||
"website": "https://numerize.com/",
|
"position": "Full Stack Web Developer Intern",
|
||||||
"name": "Numerize",
|
"duration": "4 months"
|
||||||
"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": {
|
"interests": {
|
||||||
"open-source": "Enthousiaste de l'Open-Source",
|
"open-source": "Open-Source Enthusiast",
|
||||||
"high-tech": "Passionné de High-Tech"
|
"high-tech": "Passionate about High-Tech"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,6 @@
|
|||||||
"en-US": "Anglais",
|
"en-US": "Anglais",
|
||||||
"fr-FR": "Français"
|
"fr-FR": "Français"
|
||||||
},
|
},
|
||||||
"common": {
|
|
||||||
"others": "Autres"
|
|
||||||
},
|
|
||||||
"footer": {
|
"footer": {
|
||||||
"all-rights-reserved": "Tous droits réservés"
|
"all-rights-reserved": "Tous droits réservés"
|
||||||
},
|
},
|
||||||
@ -58,6 +55,7 @@
|
|||||||
"frontend": "Frontend",
|
"frontend": "Frontend",
|
||||||
"backend": "Backend",
|
"backend": "Backend",
|
||||||
"software-tools": "Logiciels et outils",
|
"software-tools": "Logiciels et outils",
|
||||||
|
"others": "Autres",
|
||||||
"driving-license": "Permis B"
|
"driving-license": "Permis B"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"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."
|
"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": {
|
"education": {
|
||||||
"title": "Formations",
|
"title": "Études",
|
||||||
"iut": {
|
"iut": {
|
||||||
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
||||||
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
||||||
@ -133,49 +131,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"work": {
|
"work": {
|
||||||
"title": "Expériences",
|
"title": "Expériences professionnelles",
|
||||||
"ircad": {
|
"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.",
|
"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/",
|
"position": "Apprenti Développeur Web Full Stack",
|
||||||
"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"
|
"duration": "1 an"
|
||||||
},
|
},
|
||||||
"numerize": {
|
"numerize": {
|
||||||
"summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL.",
|
"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",
|
"position": "Stagiaire Développeur Web Full Stack",
|
||||||
"startDate": "2023-04-11",
|
|
||||||
"endDate": "2023-07-26",
|
|
||||||
"duration": "4 mois"
|
"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": {
|
"interests": {
|
||||||
"open-source": "Enthousiaste de l'Open-Source",
|
"open-source": "Enthousiaste de l'Open-Source",
|
||||||
"high-tech": "Passionné de High-Tech"
|
"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> = () => {
|
export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => {
|
||||||
return (
|
return (
|
||||||
<main className="curriculum-vitae container-fluid">
|
<main className="curriculum-vitae mx-auto px-4 text-sm">
|
||||||
<div className="row main clearfix">
|
<div className="-mx-4 p-2">
|
||||||
<section className="col-md-3 card-wrapper profile-card-wrapper affix">
|
<section className="col-md-3 card-wrapper relative">
|
||||||
<CurriculumVitaeProfile />
|
<CurriculumVitaeProfile />
|
||||||
|
|
||||||
<div className="card background-card">
|
<div className="card background-card">
|
||||||
@ -20,14 +20,14 @@ export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => {
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<section className="section-separated">
|
<section className="flex">
|
||||||
<CurriculumVitaeEducation />
|
<CurriculumVitaeEducation />
|
||||||
<CurriculumVitaeSkills />
|
<CurriculumVitaeSkills />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<section className="section-separated">
|
<section className="flex">
|
||||||
<CurriculumVitaeWork />
|
<CurriculumVitaeWork />
|
||||||
<CurriculumVitaeInterests />
|
<CurriculumVitaeInterests />
|
||||||
</section>
|
</section>
|
||||||
|
@ -9,13 +9,98 @@ export const CurriculumVitaeEducation: React.FC<
|
|||||||
> = () => {
|
> = () => {
|
||||||
const t = useTranslations()
|
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 (
|
return (
|
||||||
<CurriculumVitaeSection
|
<CurriculumVitaeSection
|
||||||
id="education"
|
id="education"
|
||||||
title={t("curriculum-vitae.education.title")}
|
title={t("curriculum-vitae.education.title")}
|
||||||
icon={<FaGraduationCap size={24} />}
|
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>
|
</CurriculumVitaeSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,28 @@ export const CurriculumVitaeInterests: React.FC<
|
|||||||
> = () => {
|
> = () => {
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
|
const interests = [
|
||||||
|
t("curriculum-vitae.interests.open-source"),
|
||||||
|
t("curriculum-vitae.interests.high-tech"),
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CurriculumVitaeSection
|
<CurriculumVitaeSection
|
||||||
id="interests"
|
id="interests"
|
||||||
title={t("home.interests.title")}
|
title={t("home.interests.title")}
|
||||||
icon={<FaHeart size={24} />}
|
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>
|
</CurriculumVitaeSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@ export const CurriculumVitaeProfile: React.FC<
|
|||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card profile-card">
|
<div className="card p-2">
|
||||||
<div className="profile-pic-container">
|
<div className="profile-pic-container">
|
||||||
<div className="profile-pic">
|
<div className="profile-pic">
|
||||||
<Image
|
<Image
|
||||||
className="media-object img-circle center-block"
|
className="mx-auto block"
|
||||||
alt={t("meta.title")}
|
alt={t("meta.title")}
|
||||||
src="/images/logo_background.webp"
|
src="/images/logo_background.webp"
|
||||||
width={800}
|
width={800}
|
||||||
@ -33,8 +33,8 @@ export const CurriculumVitaeProfile: React.FC<
|
|||||||
<h5 className="text-muted">{t("home.about.nationality.value")}</h5>
|
<h5 className="text-muted">{t("home.about.nationality.value")}</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="contact-details clearfix">
|
<div className="flex justify-center">
|
||||||
<div className="detail">
|
<div className="relative px-3">
|
||||||
<span className="info">
|
<span className="info">
|
||||||
<a className="link-disguise" href="mailto:contact@theoludwig.fr">
|
<a className="link-disguise" href="mailto:contact@theoludwig.fr">
|
||||||
contact@theoludwig.fr
|
contact@theoludwig.fr
|
||||||
|
@ -14,7 +14,7 @@ export const CurriculumVitaeSection: React.FC<CurriculumVitaeSectionProps> = (
|
|||||||
<div className="icon">{icon}</div>
|
<div className="icon">{icon}</div>
|
||||||
|
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<h4 className="title text-uppercase">{title}</h4>
|
<h4 className="font-semibold uppercase">{title}</h4>
|
||||||
|
|
||||||
<div className="content">{children}</div>
|
<div className="content">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import { FaToolbox } from "react-icons/fa"
|
import { FaToolbox } from "react-icons/fa"
|
||||||
|
import {
|
||||||
|
SKILL_CATEGORIES,
|
||||||
|
SKILL_NAMES_BY_CATEGORY,
|
||||||
|
} from "../Home/Skills/skills"
|
||||||
import { CurriculumVitaeSection } from "./CurriculumVitaeSection"
|
import { CurriculumVitaeSection } from "./CurriculumVitaeSection"
|
||||||
|
|
||||||
export interface CurriculumVitaeSkillsProps {}
|
export interface CurriculumVitaeSkillsProps {}
|
||||||
@ -9,13 +13,47 @@ export const CurriculumVitaeSkills: React.FC<
|
|||||||
> = () => {
|
> = () => {
|
||||||
const t = useTranslations()
|
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 (
|
return (
|
||||||
<CurriculumVitaeSection
|
<CurriculumVitaeSection
|
||||||
id="skills"
|
id="skills"
|
||||||
title={t("home.skills.title")}
|
title={t("home.skills.title")}
|
||||||
icon={<FaToolbox size={24} />}
|
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>
|
</CurriculumVitaeSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,63 @@ export interface CurriculumVitaeWorkProps {}
|
|||||||
export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
||||||
const t = useTranslations()
|
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 (
|
return (
|
||||||
<CurriculumVitaeSection
|
<CurriculumVitaeSection
|
||||||
id="work-experience"
|
id="work-experience"
|
||||||
title={t("curriculum-vitae.work.title")}
|
title={t("curriculum-vitae.work.title")}
|
||||||
icon={<MdWork size={24} />}
|
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>
|
</CurriculumVitaeSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { useMemo } from "react"
|
|||||||
import { Link } from "../../Design/Link/Link"
|
import { Link } from "../../Design/Link/Link"
|
||||||
import { useTheme } from "../../Layout/Header/SwitchTheme"
|
import { useTheme } from "../../Layout/Header/SwitchTheme"
|
||||||
import type { SkillName } from "./skills"
|
import type { SkillName } from "./skills"
|
||||||
import { skills } from "./skills"
|
import { SKILLS } from "./skills"
|
||||||
|
|
||||||
export interface SkillItemProps {
|
export interface SkillItemProps {
|
||||||
skillName: SkillName
|
skillName: SkillName
|
||||||
@ -14,7 +14,7 @@ export interface SkillItemProps {
|
|||||||
export const SkillItem: React.FC<SkillItemProps> = (props) => {
|
export const SkillItem: React.FC<SkillItemProps> = (props) => {
|
||||||
const { skillName } = props
|
const { skillName } = props
|
||||||
|
|
||||||
const skill = skills[skillName]
|
const skill = SKILLS[skillName]
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { useTranslations } from "next-intl"
|
|||||||
import { Section, SectionTitle } from "../../Layout/Section/Section"
|
import { Section, SectionTitle } from "../../Layout/Section/Section"
|
||||||
import { SkillItem } from "./SkillItem"
|
import { SkillItem } from "./SkillItem"
|
||||||
import { SkillsSection } from "./SkillsSection"
|
import { SkillsSection } from "./SkillsSection"
|
||||||
|
import { SKILL_CATEGORIES, SKILL_NAMES_BY_CATEGORY } from "./skills"
|
||||||
|
|
||||||
export interface SkillsProps {}
|
export interface SkillsProps {}
|
||||||
|
|
||||||
@ -12,34 +13,17 @@ export const Skills: React.FC<SkillsProps> = () => {
|
|||||||
<Section verticalSpacing horizontalSpacing id="skills">
|
<Section verticalSpacing horizontalSpacing id="skills">
|
||||||
<SectionTitle>{t("home.skills.title")}</SectionTitle>
|
<SectionTitle>{t("home.skills.title")}</SectionTitle>
|
||||||
|
|
||||||
<SkillsSection title={t("home.skills.programming-languages")}>
|
{SKILL_CATEGORIES.map((category) => {
|
||||||
<SkillItem skillName="TypeScript" />
|
const skillNames = SKILL_NAMES_BY_CATEGORY[category]
|
||||||
<SkillItem skillName="Python" />
|
|
||||||
<SkillItem skillName="C/C++" />
|
|
||||||
<SkillItem skillName="PHP" />
|
|
||||||
</SkillsSection>
|
|
||||||
|
|
||||||
<SkillsSection title={t("home.skills.frontend")}>
|
return (
|
||||||
<SkillItem skillName="HTML" />
|
<SkillsSection key={category} title={t(`home.skills.${category}`)}>
|
||||||
<SkillItem skillName="CSS" />
|
{skillNames.map((skillName) => {
|
||||||
<SkillItem skillName="Tailwind CSS" />
|
return <SkillItem key={skillName} skillName={skillName} />
|
||||||
<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>
|
</SkillsSection>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ export interface Skill {
|
|||||||
image: string | { [key: string]: string }
|
image: string | { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const skills = {
|
export const SKILLS = {
|
||||||
JavaScript: {
|
JavaScript: {
|
||||||
link: "https://developer.mozilla.org/docs/Web/JavaScript",
|
link: "https://developer.mozilla.org/docs/Web/JavaScript",
|
||||||
image: "/images/skills/JavaScript.webp",
|
image: "/images/skills/JavaScript.webp",
|
||||||
@ -112,4 +112,27 @@ export const skills = {
|
|||||||
},
|
},
|
||||||
} as const
|
} 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:
|
typescript:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.5.4
|
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:
|
packages/react-hooks:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
Loading…
Reference in New Issue
Block a user