Skip to content

Commit

Permalink
Merge pull request #3 from drik98/feature/i18n
Browse files Browse the repository at this point in the history
Add support for internationalization
  • Loading branch information
drik98 authored Jul 31, 2024
2 parents cff0970 + 5b9ba24 commit 5d4de67
Show file tree
Hide file tree
Showing 23 changed files with 627 additions and 206 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# ignore generated pdf files
*.pdf
35 changes: 35 additions & 0 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import "bootstrap/dist/css/bootstrap.min.css";
import "font-awesome/css/font-awesome.min.css";
import { supportedLocales } from "@/util/i18n";
import "../globals.scss";

// export const metadata: Metadata = {
// title: "Hendrik Schmitz | Portfolio",
// description:
// "Experienced Fullstack Software Engineer with a strong focus on Frontend Development. Explore my portfolio showcasing innovative web solutions and cutting-edge technology expertise.",
// };

//function to generate the routes for all the locales
export async function generateStaticParams() {
return supportedLocales.map((locale) => ({ locale }));
}

export default function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: {
locale: string;
};
}>) {
return (
<html lang={params.locale}>
<head>
<link rel="icon" href="/favicon.ico" sizes="any" />
</head>
<body>{children}</body>
</html>
);
}
54 changes: 54 additions & 0 deletions app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { notFound } from "next/navigation";
import { Metadata } from "next";
import { Locale } from "@/util/i18n";

import About from "@/components/About";
import Banner from "@/components/Banner";
import Contact from "@/components/Contact";
import Education from "@/components/Education";
import Experience from "@/components/Experience";
import Footer from "@/components/Footer";
import Header from "@/components/Header";
import Projects from "@/components/Projects";
import Skills from "@/components/Skills";

export async function generateMetadata({
params,
}: {
params: { locale: Locale };
}): Promise<Metadata> {
const messages = await getMessages(params.locale);
return messages.metadata;
}

export default async function Home({
params,
}: Readonly<{
params: {
locale: Locale;
};
}>) {
const messages = await getMessages(params.locale);

return (
<>
<Header messages={messages} locale={params.locale} />
<Banner messages={messages} locale={params.locale} />
<About messages={messages} locale={params.locale} />
<Experience messages={messages} locale={params.locale} />
<Education messages={messages} locale={params.locale} />
<Projects messages={messages} locale={params.locale} />
<Skills messages={messages} />
<Contact messages={messages} />
<Footer />
</>
);
}

async function getMessages(locale: string) {
try {
return (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
}
26 changes: 5 additions & 21 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import type { Metadata } from "next";
import "bootstrap/dist/css/bootstrap.min.css";
import "font-awesome/css/font-awesome.min.css";
import "./globals.scss";
import { ReactNode } from "react";

export const metadata: Metadata = {
title: "Hendrik Schmitz | Mein Portfolio",
description:
"Experienced Fullstack Software Engineer with a strong focus on Frontend Development. Explore my portfolio showcasing innovative web solutions and cutting-edge technology expertise.",
type Props = {
children: ReactNode;
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="de">
<head>
<link rel="icon" href="/favicon.ico" sizes="any" />
</head>
<body>{children}</body>
</html>
);
export default function RootLayout({ children }: Props) {
return children;
}
28 changes: 6 additions & 22 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import About from "@/components/About";
import Banner from "@/components/Banner";
import Contact from "@/components/Contact";
import Education from "@/components/Education";
import Experience from "@/components/Experience";
import Footer from "@/components/Footer";
import Header from "@/components/Header";
import Projects from "@/components/Projects";
import Skills from "@/components/Skills";
import { redirect } from "next/navigation";
import { defaultLocale } from "@/util/i18n";

/**
* Just redirect to the default language
*/
export default function Home() {
return (
<>
<Header />
<Banner />
<About />
<Experience />
<Education />
<Projects />
<Skills />
<Contact />
<Footer />
</>
);
redirect(`/${defaultLocale}`);
}
70 changes: 28 additions & 42 deletions components/About.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,54 @@
import Image from "next-export-optimize-images/image";
import experience from "@/content/experience.json";
import profilePic from "@/cv/profile_picture.jpg";
import styles from "./About.module.scss";
import { formatDate } from "@/util/date-time";
import { Locale } from "@/util/i18n";

const birthDate = new Date("1998-06-30");
const currentDate = new Date();
const difference = currentDate.getTime() - birthDate.getTime();
const ageDate = new Date(difference);
const currentAge = Math.abs(ageDate.getUTCFullYear() - 1970);

export default function About() {
const [currentJob] = experience;

export default function About({
messages,
locale,
}: {
messages: any;
locale: Locale;
}) {
const aboutMyself = messages.about.myself
.replace("{currentAge}", currentAge)
.replace("{startDate}", formatDate(new Date(currentJob.startDate), locale))
.replace("{jobTitle}", currentJob.title)
.replace("{company}", currentJob.company);

return (
<div id="about" className={styles.about}>
<div className="container">
<div className={styles.aboutRow}>
<div className="col-md-4">
<h2 className="heading">Über mich</h2>
<h2 className="heading">{messages.header.sections.about}</h2>
<Image
className={styles.profilePicture}
src={profilePic}
alt="Hendrik Schmitz"
/>
</div>
<div className="col-md-8">
<p>
Mein Name ist Hendrik Schmitz. Ich bin {currentAge} Jahre alt
komme aus Aachen und lebe dort auch derzeit. Ich habe den Dualen
Studiengang{" "}
<a href="https://www.fh-aachen.de/studium/angewandte-mathematik-und-informatik-bsc/">
Scientific Programming
</a>{" "}
absolviert und somit meinen Bachelor of Science an der{" "}
<a href="https://www.fh-aachen.de/">FH Aachen</a> erworben und
meine Ausbildung zum{" "}
<a href="https://www.matse-ausbildung.de/">
Mathematisch technischen Softwareentwickler
</a>{" "}
beim{" "}
<a href="https://www.wzl.rwth-aachen.de/">
Werkzeugmaschinenlabor WZL der RWTH Aachen
</a>{" "}
abgeschlossen. Seit Dezember 2023 bin ich Senior
Softwareentwickler bei{" "}
<a href="https://www.itemis.com/">itemis</a> und arbeite
hauptsächlich als Fullstack Entwickler mit vue.js und kotlin.
</p>
<br />
<p>
In meiner achtjährigen beruflichen Laufbahn habe ich umfangreiche
Erfahrungen in der Frontend-Entwicklung gesammelt, wobei ich mich
auf Vue.js spezialisiert habe. Darüber hinaus verfüge ich über
umfangreiche Kenntnisse im Back-End, da ich zuvor hauptsächlich
mit Java und Kotlin gearbeitet habe. Diese Erfahrung ermöglicht es
mir, sowohl bei der Frontend- als auch bei der Backend-Entwicklung
effektiv zusammenzuarbeiten und nahtlose Integrationen zu
gewährleisten.
</p>
<p dangerouslySetInnerHTML={{ __html: aboutMyself }} />
<br />
<p>
Dank meines fundierten Fachwissens und meiner umfangreichen
Erfahrung bin ich in der Lage, auf effektive Weise mit
verschiedenen Akteuren in der Softwareentwicklung
zusammenzuarbeiten, darunter Product Owner, Designer,
Backend-Entwickler und DevOps-Experten.
</p>
{messages.about.carrer.map(
(paragraph: string, index: number, array: string[]) => (
<>
<p key={index}>{paragraph}</p>
{index !== array.length - 1 ? <br /> : null}
</>
)
)}
</div>
</div>
</div>
Expand Down
18 changes: 14 additions & 4 deletions components/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { Locale } from "@/util/i18n";
import styles from "./Banner.module.scss";

export default function Banner() {
export default function Banner({
messages,
locale,
}: {
messages: any;
locale: Locale;
}) {
return (
<div className={styles.banner}>
<div className={styles.bannerContent}>
<h1>Hendrik Schmitz</h1>
<h2>Software Engineer</h2>
<a href="/cv_hendrik_schmitz_de.pdf" download="cv-hendrik-schmitz.pdf">
Download CV
<h2>{messages.banner.jobTitle}</h2>
<a
href={`/cv_hendrik_schmitz_${locale}.pdf`}
download="cv-hendrik-schmitz.pdf"
>
{messages.banner.downloadCv}
</a>
</div>

Expand Down
21 changes: 15 additions & 6 deletions components/Contact.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import styles from "./Contact.module.scss";

export default function Contact() {
export default function Contact({ messages }: { messages: any }) {
return (
<div id="contact" className={styles.contact}>
<h2>Kontakt</h2>
<h2>{messages.header.sections.contact}</h2>
<div className={styles.contactForm}>
<form method="POST" action="https://formspree.io/[email protected]">
<input
type="hidden"
name="_subject"
value="Kontaktanfrage via Portfolio"
value={messages.contact.emailSubject}
/>
<input type="email" name="_replyto" placeholder="E-Mail" required />
<textarea name="message" placeholder="Nachricht" required></textarea>
<button type="submit">Send</button>
<input
type="email"
name="_replyto"
placeholder={messages.contact.email}
required
/>
<textarea
name="message"
placeholder={messages.contact.message}
required
></textarea>
<button type="submit">{messages.contact.submit}</button>
</form>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/DetailedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StaticImageData } from "next/image";
import Image from "next-export-optimize-images/image";
import styles from "./DetailedItem.module.scss";
import { formatDateRange } from "@/util/date-time";
import { Locale } from "@/util/i18n";

export default function DetailedItem({
startDate,
Expand All @@ -13,6 +14,7 @@ export default function DetailedItem({
keyPoints,
showDateRange,
className,
locale,
}: {
startDate?: Date;
endDate?: Date;
Expand All @@ -23,6 +25,7 @@ export default function DetailedItem({
keyPoints: string[];
showDateRange?: boolean;
className?: string;
locale?: Locale;
}) {
return (
<div className={[styles.detailedItem, className ?? ""].join(" ")}>
Expand All @@ -37,7 +40,7 @@ export default function DetailedItem({
<h3>{educator}</h3>
{showDateRange ? (
<span className="education-date">
{formatDateRange(startDate, endDate)}
{formatDateRange(startDate, endDate, locale)}
</span>
) : null}
<h4>{title}</h4>
Expand Down
Loading

1 comment on commit 5d4de67

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Published on https://smtz.dev as production
🚀 Deployed on https://66aaa786e48fd20fd5336d31--smtz.netlify.app

Please sign in to comment.