feat: Init Change to UseAppRouter

This commit is contained in:
Philip 2024-10-14 22:09:25 +02:00
parent 39416c973a
commit 82ad7b2818
26 changed files with 333 additions and 433 deletions

View file

@ -1,20 +1,23 @@
const million = require("million/compiler"); import million from 'million/compiler';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
let nextConfig = { let nextConfig = {
output: "standalone", output: "standalone",
reactStrictMode: true, reactStrictMode: true,
productionBrowserSourceMaps: true, productionBrowserSourceMaps: true,
i18n: {
locales: ["de", "en", "fr", "es", "pl", "cz"],
defaultLocale: "en",
},
async rewrites() { async rewrites() {
return [ return [
{ {
source: "/datenschutz", source: "/datenschutz",
destination: "/privacy-policy", destination: "/privacy-policy",
}, },
{
source: "/impressum",
destination: "/en/impressum",
},
]; ];
}, },
}; };
@ -23,6 +26,8 @@ const millionConfig = {
auto: { rsc: true }, auto: { rsc: true },
}; };
// Apply Million's optimization
nextConfig = million.next(nextConfig, millionConfig); nextConfig = million.next(nextConfig, millionConfig);
module.exports = nextConfig; // Apply next-intl plugin
export default withNextIntl(nextConfig);

38
package-lock.json generated
View file

@ -19,6 +19,7 @@
"next": "14.2.15", "next": "14.2.15",
"next-intl": "^3.20.0", "next-intl": "^3.20.0",
"nookies": "^2.5.2", "nookies": "^2.5.2",
"prom-client": "^15.1.3",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"sass": "^1.79.4", "sass": "^1.79.4",
@ -2869,6 +2870,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -3987,6 +3997,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bintrees": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==",
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -7715,6 +7731,19 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/prom-client": {
"version": "15.1.3",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
"integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api": "^1.4.0",
"tdigest": "^0.1.1"
},
"engines": {
"node": "^16 || ^18 || >=20"
}
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -8651,6 +8680,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
"license": "MIT",
"dependencies": {
"bintrees": "1.0.2"
}
},
"node_modules/temp-dir": { "node_modules/temp-dir": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",

View file

@ -22,6 +22,7 @@
"next": "14.2.15", "next": "14.2.15",
"next-intl": "^3.20.0", "next-intl": "^3.20.0",
"nookies": "^2.5.2", "nookies": "^2.5.2",
"prom-client": "^15.1.3",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"sass": "^1.79.4", "sass": "^1.79.4",

1
project.inlang/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
cache

View file

@ -1,16 +1,17 @@
import { GetStaticPropsContext } from "next"; "use client";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
const Impressum = () => { export default function Impressum() {
const t = useTranslations(); const t = useTranslations();
const [impressum, setImpressum] = useState(""); const [impressum, setImpressum] = useState("");
useEffect(() => { useEffect(() => {
fetch("https://philipbrembeck.com/impressum.txt", { method: "GET" }) fetch("https://philipbrembeck.com/impressum.txt")
.then((response) => response.text()) .then((response) => response.text())
.then((text) => setImpressum(text)) .then((text) => setImpressum(text))
.catch((error) => console.error(error)); .catch((error) => console.error(error));
@ -25,14 +26,4 @@ const Impressum = () => {
</Container> </Container>
</> </>
); );
};
export default Impressum;
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
} }

View file

@ -1,10 +1,8 @@
import { GetStaticPropsContext } from "next";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import IngredientsCheck from "@/components/ingredientscheck"; import IngredientsCheck from "@/components/ingredientscheck";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
export default function ingredients() { export default function IngredientsPage() {
return ( return (
<> <>
<div id="modal-root"></div> <div id="modal-root"></div>
@ -15,11 +13,3 @@ export default function ingredients() {
</> </>
); );
} }
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
}

View file

@ -0,0 +1,67 @@
import "@/styles/style.scss";
import type { Metadata, Viewport } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import Nav from "@/components/nav";
export const metadata: Metadata = {
title: "Is it vegan? Veganify",
description:
"Are you unsure whether a product is vegan or not? With Veganify you can scan the bar code of an item while shopping and check whether it is vegan or not and that without a lot of other unnecessary information! Try it out now!",
openGraph: {
title: "Veganify",
type: "website",
url: "https://veganify.app",
images: [{ url: "https://veganify.app/img/og_image.png" }],
siteName: "Veganify",
},
twitter: {
card: "summary_large_image",
images: [{ url: "https://veganify.app/img/og_image.png", alt: "Veganify" }],
},
appleWebApp: {
capable: true,
title: "Veganify",
statusBarStyle: "default",
},
manifest: "/manifest.json",
icons: {
icon: "../favicon.ico",
apple: "../img/icon.png",
},
applicationName: "Veganify",
};
export const viewport: Viewport = {
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#7f8fa6" },
{ media: "(prefers-color-scheme: dark)", color: "#000000" },
],
width: "device-width",
initialScale: 1,
maximumScale: 1,
viewportFit: "cover",
};
export default async function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
<div id="modal-root"></div>
<Nav />
{children}
</NextIntlClientProvider>
</body>
</html>
);
}

View file

@ -1,8 +1,7 @@
import { GetStaticPropsContext } from "next"; "use client";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import { useLocale, useTranslations } from "next-intl";
import { useRouter } from "next/router";
import { useTranslations } from "next-intl";
import { setCookie } from "nookies"; import { setCookie } from "nookies";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
@ -10,17 +9,17 @@ import SupportOption from "@/components/elements/contents/donate";
import OLEDMode from "@/components/elements/contents/oledmode"; import OLEDMode from "@/components/elements/contents/oledmode";
import ModalWrapper from "@/components/elements/modalwrapper"; import ModalWrapper from "@/components/elements/modalwrapper";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
import { Link } from "@/i18n/routing";
export default function More() { export default function More() {
const router = useRouter();
const t = useTranslations("More"); const t = useTranslations("More");
const currentLocale = useLocale();
function handleLanguageChange(locale: string) { function handleLanguageChange(locale: string) {
setCookie(null, "NEXT_LOCALE", locale, { setCookie(null, "NEXT_LOCALE", locale, {
maxAge: 30 * 24 * 60 * 60, // 30 days maxAge: 30 * 24 * 60 * 60, // 30 days
path: "/", path: "/",
}); });
router.push("/more", undefined, { locale });
} }
return ( return (
@ -99,10 +98,7 @@ export default function More() {
<span className="unknown icon-right-open"></span> <span className="unknown icon-right-open"></span>
</div> </div>
</Link> </Link>
<a <a href="https://frontendnet.work/veganify-api" className="Grid links">
href="https://frontendnet.work/veganify-api"
className="Grid links"
>
<div className="Grid-cell description">{t("apidocumentation")}</div> <div className="Grid-cell description">{t("apidocumentation")}</div>
<div className="Grid-cell icons"> <div className="Grid-cell icons">
<span className="unknown icon-right-open"></span> <span className="unknown icon-right-open"></span>
@ -131,114 +127,39 @@ export default function More() {
/> />
<h1>{t("language")}</h1> <h1>{t("language")}</h1>
</span> </span>
{[
{ code: "en", name: "english" },
{ code: "de", name: "german" },
{ code: "es", name: "spanish" },
{ code: "fr", name: "french" },
{ code: "pl", name: "polish" },
{ code: "cz", name: "czech" },
].map(({ code, name }) => (
<Link <Link
key={code}
className="nolink" className="nolink"
href="/more" href={`/more`}
locale="en" locale={
onClick={() => handleLanguageChange("en")} code as "en" | "de" | "es" | "fr" | "pl" | "cz" | undefined
}
onClick={() => handleLanguageChange(code)}
> >
<div <div
className={router.locale === "en" ? "option active" : "option"} className={
currentLocale === code ? "option active" : "option"
}
> >
<input <input
className="form-check-input" className="form-check-input"
type="radio" type="radio"
name="flexRadioDefault" name="flexRadioDefault"
checked={router.locale === "en"} checked={currentLocale === code}
readOnly
/> />
<span className="price">{t("english")}</span> <span className="price">{t(name)}</span>
</div>
</Link>
<Link
className="nolink"
href="/more"
locale="de"
onClick={() => handleLanguageChange("de")}
>
<div
className={router.locale === "de" ? "option active" : "option"}
>
<input
className="form-check-input"
type="radio"
name="flexRadioDefault"
checked={router.locale === "de"}
/>
<span className="price">{t("german")}</span>
</div>
</Link>
<Link
className="nolink"
href="/more"
locale="es"
onClick={() => handleLanguageChange("es")}
>
<div
className={router.locale === "es" ? "option active" : "option"}
>
<input
className="form-check-input"
type="radio"
name="flexRadioDefault"
checked={router.locale === "es"}
/>
<span className="price">{t("spanish")}</span>
</div>
</Link>
<Link
className="nolink"
href="/more"
locale="fr"
onClick={() => handleLanguageChange("fr")}
>
<div
className={router.locale === "fr" ? "option active" : "option"}
>
<input
className="form-check-input"
type="radio"
name="flexRadioDefault"
checked={router.locale === "fr"}
/>
<span className="price">{t("french")}</span>
</div>
</Link>
<Link
className="nolink"
href="/more"
locale="pl"
onClick={() => handleLanguageChange("pl")}
>
<div
className={router.locale === "pl" ? "option active" : "option"}
>
<input
className="form-check-input"
type="radio"
name="flexRadioDefault"
checked={router.locale === "pl"}
/>
<span className="price">{t("polish")}</span>
</div>
</Link>
<Link
className="nolink"
href="/more"
locale="cz"
onClick={() => handleLanguageChange("cz")}
>
<div
className={router.locale === "cz" ? "option active" : "option"}
>
<input
className="form-check-input"
type="radio"
name="flexRadioDefault"
checked={router.locale === "cz"}
/>
<span className="price">{t("czech")}</span>
</div> </div>
</Link> </Link>
))}
<span className="info" id="cookieinfo"> <span className="info" id="cookieinfo">
{t("thissetsacookie")} {t("thissetsacookie")}
</span> </span>
@ -256,11 +177,3 @@ export default function More() {
</> </>
); );
} }
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
}

View file

@ -1,4 +1,4 @@
import { GetStaticPropsContext } from "next"; import { Metadata } from "next";
import ProductSearch from "@/components/check"; import ProductSearch from "@/components/check";
import InstallPrompt from "@/components/elements/pwainstall"; import InstallPrompt from "@/components/elements/pwainstall";
@ -6,6 +6,11 @@ import Shortcut from "@/components/elements/shortcutinstall";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
export const metadata: Metadata = {
title: "Veganify - Check if products are vegan",
description: "Scan barcodes to check if products are vegan",
};
export default function Home() { export default function Home() {
return ( return (
<> <>
@ -24,11 +29,3 @@ export default function Home() {
</> </>
); );
} }
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
}

View file

@ -0,0 +1,32 @@
"use client";
import { useTranslations } from "next-intl";
import { useState, useEffect } from "react";
import Container from "@/components/elements/container";
import Nav from "@/components/nav";
export default function PrivacyPolicy() {
const t = useTranslations("Privacy");
const [datenschutz, setDatenschutz] = useState("");
useEffect(() => {
fetch("https://philipbrembeck.com/datenschutz.txt")
.then((response) => response.text())
.then((text) => setDatenschutz(text))
.catch((error) => console.error(error));
}, []);
return (
<>
<Nav />
<Container>
<p className="small">{t("germanonly")}</p>
<div
className="privacy"
dangerouslySetInnerHTML={{ __html: datenschutz }}
/>
</Container>
</>
);
}

View file

@ -1,4 +1,3 @@
import { GetStaticPropsContext } from "next";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
@ -16,11 +15,3 @@ export default function TOS() {
</> </>
); );
} }
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
}

View file

@ -1,7 +1,16 @@
import Router from 'next/router'; "use client";
const BackButton = () => ( import { useRouter } from "next/navigation";
<span onClick={() => Router.back()} style={{cursor: "pointer"}} className="icon-left-open back"/>
); const BackButton = () => {
const router = useRouter();
return (
<span
onClick={() => router.back()}
style={{ cursor: "pointer" }}
className="icon-left-open back"
/>
);
};
export default BackButton; export default BackButton;

View file

@ -1,3 +1,5 @@
"use client";
import Veganify from "@frontendnetwork/veganify"; import Veganify from "@frontendnetwork/veganify";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@ -60,7 +62,6 @@ const ProductSearch = () => {
); );
setLoading(false); setLoading(false);
if (data.status === 200) { if (data.status === 200) {
if ("product" in data) { if ("product" in data) {
setResult(data.product); setResult(data.product);
} }

View file

@ -1,3 +1,5 @@
"use client";
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import Image from "next/image"; import Image from "next/image";

View file

@ -1,3 +1,5 @@
"use client";
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";

View file

@ -1,7 +1,9 @@
"use client";
import Veganify from "@frontendnetwork/veganify"; import Veganify from "@frontendnetwork/veganify";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import React, { useState, FormEvent } from "react"; import React, { useState, useCallback } from "react";
import ModalWrapper from "@/components/elements/modalwrapper"; import ModalWrapper from "@/components/elements/modalwrapper";
@ -14,37 +16,41 @@ const IngredientsCheck = () => {
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => { const handleSubmit = useCallback(
async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setVegan(null); setVegan(null);
setSurelyVegan([]); setSurelyVegan([]);
setNotVegan([]); setNotVegan([]);
setMaybeVegan([]); setMaybeVegan([]);
setError(false); setError(false);
event.preventDefault();
const ingredients = event.currentTarget.elements.namedItem(
"ingredients"
) as HTMLInputElement;
const checkIngredients = async () => { const formData = new FormData(event.currentTarget);
const ingredients = formData.get("ingredients") as string;
if (!ingredients) {
setError(true);
return;
}
setLoading(true); setLoading(true);
try { try {
const data = await Veganify.checkIngredientsList( const data = await Veganify.checkIngredientsList(
ingredients.value, ingredients,
process.env.NEXT_PUBLIC_STAGING === "true" ? true : false process.env.NEXT_PUBLIC_STAGING === "true"
); );
setVegan(data.data.vegan); setVegan(data.data.vegan);
setSurelyVegan(data.data.surely_vegan); setSurelyVegan(data.data.surely_vegan);
setNotVegan(data.data.not_vegan); setNotVegan(data.data.not_vegan);
setMaybeVegan(data.data.maybe_vegan); setMaybeVegan(data.data.maybe_vegan);
setLoading(false);
} catch (error) { } catch (error) {
setError(true); setError(true);
} finally {
setLoading(false); setLoading(false);
} }
}; },
[]
checkIngredients(); );
};
return ( return (
<> <>
@ -68,9 +74,9 @@ const IngredientsCheck = () => {
placeholder={t("entercommaseperated")} placeholder={t("entercommaseperated")}
/> />
<button <button
type="submit"
name="checkingredients" name="checkingredients"
aria-label={t("submit")} aria-label={t("submit")}
role="button"
> >
<span className="icon-right-open"></span> <span className="icon-right-open"></span>
</button> </button>

View file

@ -1,76 +1,17 @@
import { GetStaticPropsContext } from "next"; "use client";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useEffect } from "react";
import { Link, usePathname } from "@/i18n/routing";
export default function Nav() { export default function Nav() {
const t = useTranslations("Nav"); const t = useTranslations("Nav");
const router = useRouter(); const pathname = usePathname();
useEffect(() => {
const localStorageValue = localStorage.getItem("oled");
if (
localStorageValue === "true" &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
document.documentElement.setAttribute("data-theme", "oled");
const themeColorMeta = document.querySelector('meta[name="theme-color"][media="(prefers-color-scheme: dark)"]');
if (themeColorMeta) {
themeColorMeta.setAttribute("content", "#000");
}
}
}, []);
return ( return (
<>
<Head>
<title>{t("title")}</title>
<meta name="description" content={t("description")} />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<meta property="og:title" content={t("title")} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://veganify.app" />
<meta
name="twitter:image:src"
content="https://veganify.app/img/og_image.png"
/>
<meta property="twitter:image:alt" content="Veganify" />
<meta name="twitter:card" content="summary_large_image" />
<meta
property="og:image"
content="https://veganify.app/img/og_image.png"
/>
<meta property="og:image:alt" content="Veganify" />
<meta property="og:site_name" content="Veganify" />
<meta property="og:type" content="object" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.ico" />
<link rel="apple-touch-icon" href="../img/icon.png" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="msapplication-starturl" content="/" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#7f8fa6" key="pcl" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#141414" key="pcd" />
<meta name="application-name" content="Veganify" />
<meta name="apple-mobile-web-app-title" content="Veganify" />
</Head>
<nav className="nav"> <nav className="nav">
<div className="flex-container"> <div className="flex-container">
<div <div className={pathname === "/" ? "flex-item active" : "flex-item"}>
className={
router.pathname == "/" ? "flex-item active" : "flex-item"
}
>
<Link href="/"> <Link href="/">
<span className="icon icon-vegancheck"></span> <span className="icon icon-vegancheck"></span>
<span className="menu-item">{t("home")}</span> <span className="menu-item">{t("home")}</span>
@ -78,9 +19,7 @@ export default function Nav() {
</div> </div>
<div <div
className={ className={
router.pathname == "/ingredients" pathname === "/ingredients" ? "flex-item active" : "flex-item"
? "flex-item active"
: "flex-item"
} }
> >
<Link href="/ingredients"> <Link href="/ingredients">
@ -91,7 +30,7 @@ export default function Nav() {
<div <div
className={ className={
["/more", "/tos", "/privacy-policy", "/impressum"].includes( ["/more", "/tos", "/privacy-policy", "/impressum"].includes(
router.pathname pathname
) )
? "flex-item active" ? "flex-item active"
: "flex-item" : "flex-item"
@ -104,14 +43,5 @@ export default function Nav() {
</div> </div>
</div> </div>
</nav> </nav>
</>
); );
} }
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`..//locales/${locale}.json`)).default,
},
};
}

View file

@ -1,13 +0,0 @@
import { GetStaticProps } from "next";
interface Props {
messages: Record<string, string>;
}
export const getStaticProps: GetStaticProps<Props> = async ({ locale }) => {
return {
props: {
messages: require(`/locales/${locale}.json`),
},
};
};

13
src/i18n/request.ts Normal file
View file

@ -0,0 +1,13 @@
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { routing } from "./routing";
export default getRequestConfig(async ({ locale }) => {
// Validate that the incoming `locale` parameter is valid
if (!routing.locales.includes(locale as any)) notFound();
return {
messages: (await import(`../locales/${locale}.json`)).default,
};
});

10
src/i18n/routing.ts Normal file
View file

@ -0,0 +1,10 @@
import { createSharedPathnamesNavigation } from "next-intl/navigation";
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "de", "es", "fr", "pl", "cz"],
defaultLocale: "en",
});
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation(routing);

12
src/middleware.ts Normal file
View file

@ -0,0 +1,12 @@
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing, {
localeDetection: true,
});
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(de|en|es|fr|pl|cz)/:path*"],
};

View file

@ -1,36 +0,0 @@
import { GetStaticPropsContext } from "next";
import { useTranslations } from "next-intl";
import Container from "@/components/elements/container";
import Nav from "@/components/nav";
export default function NotFound() {
const t = useTranslations("404");
return (
<>
<Nav />
<Container heading={t("error404")}>
<p>{t("pagedoesnotexist")}</p>
<p
dangerouslySetInnerHTML={{
__html: t("message", {
statuspage: `<a href="https://stats.uptimerobot.com/LY1gRuP5j6/789004495">${t(
"statuspage"
)}</a>`,
mastodon:
'<a href="https://veganism.social/@vegancheck">Mastodon</a>',
}),
}}
/>
</Container>
</>
);
}
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default,
},
};
}

View file

@ -1,20 +0,0 @@
import "@/styles/style.scss";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import { NextIntlClientProvider } from "next-intl";
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
return (
<>
<NextIntlClientProvider
locale={router.locale}
timeZone="Europe/Vienna"
messages={pageProps.messages}
>
<Component {...pageProps} />
</NextIntlClientProvider>
</>
);
}

View file

@ -1,13 +0,0 @@
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}

View file

@ -1,40 +0,0 @@
import { GetStaticPropsContext } from 'next'
import { useTranslations } from 'next-intl';
import React, { useState, useEffect } from 'react';
import Container from "@/components/elements/container";
import Nav from "@/components/nav";
const PrivacyPolicy = () => {
const t = useTranslations('Privacy');
const [datenschutz, setDatenschutz] = useState('');
useEffect(() => {
fetch('https://philipbrembeck.com/datenschutz.txt')
.then(response => response.text())
.then(text => setDatenschutz(text))
.catch(error => console.error(error));
}, []);
return (
<>
<Nav />
<Container>
<p className="small">
{t('germanonly')}
</p>
<div className="privacy" dangerouslySetInnerHTML={{ __html: datenschutz }} />
</Container>
</>
);
};
export default PrivacyPolicy;
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: (await import(`../locales/${locale}.json`)).default
}
};
}

View file

@ -17,8 +17,19 @@
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "./types/**/*.d.ts"], "plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"./types/**/*.d.ts",
".next/types/**/*.ts"
],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }