feat: Wabenhain Honig-Shop Anwendung
Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei: Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich (Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung, PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma- Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
This commit is contained in:
1 parent
5c76fcc266
commit
c7927f2883
240 files changed
+26096
-137
No files matched your search
@@ -0,0 +1,22 @@
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { isLocale, LOCALE_COOKIE } from "./config";
|
||||
|
||||
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
||||
|
||||
/** Setzt die Sprachpraeferenz per Cookie und macht den gesamten Seitenbaum ungueltig, damit alle Server Components sofort in der neuen Sprache neu rendern. */
|
||||
export async function setLocaleAction(formData: FormData): Promise<void> {
|
||||
const locale = formData.get("locale");
|
||||
if (typeof locale !== "string" || !isLocale(locale)) return;
|
||||
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(LOCALE_COOKIE, locale, {
|
||||
path: "/",
|
||||
maxAge: ONE_YEAR_SECONDS,
|
||||
sameSite: "lax",
|
||||
});
|
||||
|
||||
revalidatePath("/", "layout");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Zentrale Sprachkonfiguration: um eine weitere Sprache zu ergaenzen, genuegt
|
||||
// es, sie hier zur locales-Liste hinzuzufuegen und eine passende Datei unter
|
||||
// dictionaries/<code>.ts anzulegen (siehe dictionaries/en.ts als Vorlage) —
|
||||
// get-dictionary.ts, der LanguageSwitcher und alle Verbraucher der
|
||||
// Dictionary lesen die Liste dynamisch, kein Code an anderer Stelle muss
|
||||
// angepasst werden.
|
||||
export const locales = ["de", "en"] as const;
|
||||
export type Locale = (typeof locales)[number];
|
||||
|
||||
export const defaultLocale: Locale = "de";
|
||||
|
||||
export const localeLabels: Record<Locale, string> = {
|
||||
de: "Deutsch",
|
||||
en: "English",
|
||||
};
|
||||
|
||||
export const LOCALE_COOKIE = "locale";
|
||||
|
||||
export function isLocale(value: string | undefined | null): value is Locale {
|
||||
return !!value && (locales as readonly string[]).includes(value);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
||||
import type { Locale } from "./config";
|
||||
import type { Dictionary } from "./dictionary-type";
|
||||
import { de } from "./dictionaries/de";
|
||||
import { en } from "./dictionaries/en";
|
||||
|
||||
// Neue Sprache ergaenzen: hier den Import + Eintrag hinzufuegen (und die
|
||||
// Sprache in i18n/config.ts zur locales-Liste ergaenzen) — mehr Stellen
|
||||
// muessen dafuer nicht angepasst werden.
|
||||
const dictionaries: Record<Locale, Dictionary> = { de, en };
|
||||
|
||||
export function getDictionary(locale: Locale): Dictionary {
|
||||
return dictionaries[locale];
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
import type { Locale } from "./config";
|
||||
import type { Dictionary } from "./dictionary-type";
|
||||
import { getDictionary } from "./get-dictionary";
|
||||
|
||||
interface I18nContextValue {
|
||||
locale: Locale;
|
||||
t: Dictionary;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextValue | null>(null);
|
||||
|
||||
/**
|
||||
* Stellt Locale + Uebersetzungswoerterbuch fuer Client Components bereit.
|
||||
* Nimmt bewusst nur die Locale (ein String) als Prop entgegen und loest das
|
||||
* Dictionary selbst per getDictionary() auf, statt das fertige Objekt vom
|
||||
* Server-Layout hereinzureichen — Dictionaries enthalten Funktionswerte
|
||||
* (z.B. fuer Pluralformen), und Funktionen lassen sich nicht als Props von
|
||||
* Server an Client Components uebergeben (RSC-Serialisierungsgrenze).
|
||||
* Server Components rufen stattdessen direkt getDictionary(await
|
||||
* getLocale()) auf, ohne diesen Provider zu benoetigen.
|
||||
*/
|
||||
export function I18nProvider({ locale, children }: { locale: Locale; children: ReactNode }) {
|
||||
const dict = getDictionary(locale);
|
||||
return <I18nContext.Provider value={{ locale, t: dict }}>{children}</I18nContext.Provider>;
|
||||
}
|
||||
|
||||
export function useI18n(): I18nContextValue {
|
||||
const ctx = useContext(I18nContext);
|
||||
if (!ctx) throw new Error("useI18n muss innerhalb von <I18nProvider> verwendet werden.");
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { defaultLocale, isLocale, LOCALE_COOKIE, type Locale } from "./config";
|
||||
|
||||
/** Serverseitige Spracherkennung ueber das Locale-Cookie, mit Fallback auf Deutsch. */
|
||||
export async function getLocale(): Promise<Locale> {
|
||||
const cookieStore = await cookies();
|
||||
const value = cookieStore.get(LOCALE_COOKIE)?.value;
|
||||
return isLocale(value) ? value : defaultLocale;
|
||||
}
|
||||
Reference in new issue
Block a user