Files
Wabenhain/src/lib/content-registry.ts
T
Nick SzaunigandClaude Sonnet 5 286387d5ec refactor: /admin/inhalte in Bilder/Text trennen, KI-untypische Formulierungen in Website-Texten
Content-Editor zeigt Bild-/Video-Felder jetzt gebuendelt oben und
Textfelder gebuendelt darunter (je Seite ein eigenes Speichern-Formular),
statt beides pro Bereich gemischt untereinander. Ausserdem alle
Gedankenstrich-Konstruktionen und aehnliche KI-Textmuster aus den
Website-Texten (de/en) entfernt und durch natuerlichere Formulierungen
ersetzt.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-11 15:48:03 +02:00

114 lines
10 KiB
TypeScript

import { getDictionary } from "@/i18n/get-dictionary";
import { siteConfig } from "@/lib/site-config";
import type { Locale } from "@/i18n/config";
import type { TitledText, LegalSectionContent } from "@/i18n/dictionary-type";
export type ContentFieldType =
| "text"
| "textarea"
| "list-simple"
| "list-titled"
| "list-numbered"
| "richtext-sections"
| "image"
| "video";
export interface ContentFieldDef {
key: string;
group: "company" | "home" | "pages" | "legal" | "social";
labelDe: string;
labelEn: string;
type: ContentFieldType;
/** false = ein einziger Wert unabhaengig von der Locale (Firmendaten). */
localized: boolean;
/** Liefert den aktuellen Fallback-Text im selben Format, das der Parser fuer diesen Typ erwartet. */
getDefault: (locale: Locale) => string;
}
function joinSimpleList(items: string[]): string {
return items.join("\n");
}
function joinTitledList(items: TitledText[]): string {
return items.map((i) => `${i.title} | ${i.text}`).join("\n");
}
function joinNumberedList(items: { number: string; title: string; text: string }[]): string {
return items.map((i) => `${i.number} | ${i.title} | ${i.text}`).join("\n");
}
function joinSections(sections: LegalSectionContent[]): string {
return sections
.map((s) => `## ${s.heading}\n\n${s.paragraphs.join("\n\n")}`)
.join("\n\n");
}
export const CONTENT_REGISTRY: ContentFieldDef[] = [
// --- Firmendaten (nicht lokalisiert) ---
{ key: "company.name", group: "company", labelDe: "Firmenname", labelEn: "Company name", type: "text", localized: false, getDefault: () => siteConfig.name },
{ key: "company.claim", group: "company", labelDe: "Slogan", labelEn: "Tagline", type: "text", localized: false, getDefault: () => siteConfig.claim },
{ key: "company.claimAlt", group: "company", labelDe: "Alternativer Slogan", labelEn: "Alternate tagline", type: "text", localized: false, getDefault: () => siteConfig.claimAlt },
{ key: "company.description", group: "company", labelDe: "Kurzbeschreibung", labelEn: "Short description", type: "textarea", localized: false, getDefault: () => siteConfig.description },
{ key: "company.location", group: "company", labelDe: "Standort (Anzeigetext)", labelEn: "Location (display text)", type: "text", localized: false, getDefault: () => siteConfig.location },
{ key: "company.email", group: "company", labelDe: "E-Mail", labelEn: "Email", type: "text", localized: false, getDefault: () => siteConfig.email },
{ key: "company.phone", group: "company", labelDe: "Telefon", labelEn: "Phone", type: "text", localized: false, getDefault: () => siteConfig.phone },
{ key: "company.street", group: "company", labelDe: "Straße", labelEn: "Street", type: "text", localized: false, getDefault: () => siteConfig.address.street },
{ key: "company.zip", group: "company", labelDe: "PLZ", labelEn: "ZIP", type: "text", localized: false, getDefault: () => siteConfig.address.zip },
{ key: "company.city", group: "company", labelDe: "Ort", labelEn: "City", type: "text", localized: false, getDefault: () => siteConfig.address.city },
// --- Homepage (lokalisiert) ---
{ key: "home.hero.intro", group: "home", labelDe: "Hero: Einleitungstext", labelEn: "Hero: Intro text", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.hero.intro },
{ key: "home.hero.footnote", group: "home", labelDe: "Hero: Fußnote", labelEn: "Hero: Footnote", type: "text", localized: true, getDefault: (l) => getDictionary(l).home.hero.footnote },
{ key: "home.originStory.paragraph1", group: "home", labelDe: "Herkunft: Absatz 1", labelEn: "Origin: Paragraph 1", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.originStory.paragraph1 },
{ key: "home.originStory.paragraph2", group: "home", labelDe: "Herkunft: Absatz 2", labelEn: "Origin: Paragraph 2", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.originStory.paragraph2 },
{ key: "home.originStory.facts", group: "home", labelDe: "Herkunft: Fakten (eine Zeile je Fakt)", labelEn: "Origin: Facts (one per line)", type: "list-simple", localized: true, getDefault: (l) => joinSimpleList(getDictionary(l).home.originStory.facts) },
{ key: "home.philosophy.items", group: "home", labelDe: "Philosophie: Punkte (Titel | Text, eine Zeile je Punkt)", labelEn: "Philosophy: Items (Title | Text, one per line)", type: "list-titled", localized: true, getDefault: (l) => joinTitledList(getDictionary(l).home.philosophy.items) },
{ key: "home.processTimeline.steps", group: "home", labelDe: "Ablauf: Schritte (Nr. | Titel | Text, eine Zeile je Schritt)", labelEn: "Process: Steps (No. | Title | Text, one per line)", type: "list-numbered", localized: true, getDefault: (l) => joinNumberedList(getDictionary(l).home.processTimeline.steps) },
{ key: "home.regionalSection.paragraph1", group: "home", labelDe: "Regional: Absatz 1", labelEn: "Regional: Paragraph 1", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.regionalSection.paragraph1 },
{ key: "home.regionalSection.paragraph2", group: "home", labelDe: "Regional: Absatz 2", labelEn: "Regional: Paragraph 2", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.regionalSection.paragraph2 },
{ key: "home.sustainability.items", group: "home", labelDe: "Nachhaltigkeit: Punkte (Titel | Text, eine Zeile je Punkt)", labelEn: "Sustainability: Items (Title | Text, one per line)", type: "list-titled", localized: true, getDefault: (l) => joinTitledList(getDictionary(l).home.sustainability.items) },
{ key: "home.businessTeaser.paragraph", group: "home", labelDe: "Geschäftskunden-Hinweis: Text", labelEn: "Business teaser: Text", type: "textarea", localized: true, getDefault: (l) => getDictionary(l).home.businessTeaser.paragraph },
// --- Homepage-Bilder (nicht lokalisiert — ein Bild gilt fuer beide
// Sprachen; Wert ist der von app/api/admin/upload/route.ts zurueckgegebene
// Pfad, z.B. "/uploads/<zufaellig>.webp". Leer = kein Bild gepflegt, die
// Komponente zeigt dann weiterhin den bisherigen CSS-Platzhalter, siehe
// components/content-image.tsx.) Deckt jede Stelle in hero.tsx/
// home-sections-a.tsx/home-sections-b.tsx ab, die ein festes,
// redaktionelles Bild zeigt — der homepage-Produkt-Spotlight-Abschnitt
// (ProductSpotlight) ist bewusst NICHT hier: er zeigt kein festes Bild,
// sondern das jeweils dynamisch ausgewaehlte Produkt und nutzt dafuer
// dessen eigenes Product.images (siehe admin/produkte).
{ key: "home.hero.image", group: "home", labelDe: "Hero: Bild", labelEn: "Hero: Image", type: "image", localized: false, getDefault: () => "" },
// Optionales Hintergrundvideo fuer den Hero (MP4/WebM, siehe
// app/api/admin/upload-video/route.ts). Wenn gepflegt, zeigt hero.tsx das
// Video statt des Hero-Bilds/Platzhalters an derselben Stelle — home.hero.image
// bleibt als Fallback/Poster-Bild erhalten, falls das Video wieder entfernt wird.
{ key: "home.hero.video", group: "home", labelDe: "Hero: Hintergrundvideo (MP4/WebM, optional, ersetzt das Hero-Bild)", labelEn: "Hero: Background video (MP4/WebM, optional, replaces the hero image)", type: "video", localized: false, getDefault: () => "" },
{ key: "home.originStory.image", group: "home", labelDe: "Herkunft: Bild", labelEn: "Origin: Image", type: "image", localized: false, getDefault: () => "" },
{ key: "home.regionalSection.image", group: "home", labelDe: "Regional: Bild", labelEn: "Regional: Image", type: "image", localized: false, getDefault: () => "" },
{ key: "home.businessTeaser.image", group: "home", labelDe: "Geschäftskunden-Hinweis: Bild", labelEn: "Business teaser: Image", type: "image", localized: false, getDefault: () => "" },
// --- Weitere Seiten (nicht lokalisiert) ---
{ key: "imkerei.image", group: "pages", labelDe: "Imkerei-Seite: Bild", labelEn: "Imkerei page: Image", type: "image", localized: false, getDefault: () => "" },
{ key: "herkunft.image", group: "pages", labelDe: "Herkunft-Seite: Bild", labelEn: "Herkunft page: Image", type: "image", localized: false, getDefault: () => "" },
{ key: "unternehmen.image", group: "pages", labelDe: "Geschäftskunden-Seite: Bild", labelEn: "Business page: Image", type: "image", localized: false, getDefault: () => "" },
// --- Social Media (nicht lokalisiert, leer = Icon wird im Footer nicht
// angezeigt, siehe components/site-footer.tsx Footer()) ---
{ key: "social.tiktok", group: "social", labelDe: "TikTok-Profil (URL)", labelEn: "TikTok profile (URL)", type: "text", localized: false, getDefault: () => "" },
{ key: "social.youtube", group: "social", labelDe: "YouTube-Kanal (URL)", labelEn: "YouTube channel (URL)", type: "text", localized: false, getDefault: () => "" },
{ key: "social.instagram", group: "social", labelDe: "Instagram-Profil (URL)", labelEn: "Instagram profile (URL)", type: "text", localized: false, getDefault: () => "" },
// --- Rechtstexte (lokalisiert) ---
{ key: "legal.impressum", group: "legal", labelDe: "Impressum", labelEn: "Legal notice", type: "richtext-sections", localized: true, getDefault: (l) => joinSections(getDictionary(l).impressumPage.sections) },
{ key: "legal.datenschutz", group: "legal", labelDe: "Datenschutz", labelEn: "Privacy policy", type: "richtext-sections", localized: true, getDefault: (l) => joinSections(getDictionary(l).datenschutzPage.sections) },
{ key: "legal.agb", group: "legal", labelDe: "AGB", labelEn: "Terms and conditions", type: "richtext-sections", localized: true, getDefault: (l) => joinSections(getDictionary(l).agbPage.sections) },
{ key: "legal.widerruf", group: "legal", labelDe: "Widerrufsbelehrung", labelEn: "Right of withdrawal", type: "richtext-sections", localized: true, getDefault: (l) => joinSections(getDictionary(l).widerrufPage.sections) },
{ key: "legal.cookieRichtlinie", group: "legal", labelDe: "Cookie-Richtlinie", labelEn: "Cookie policy", type: "richtext-sections", localized: true, getDefault: (l) => joinSections(getDictionary(l).cookieRichtliniePage.sections) },
];
export function getContentFieldDef(key: string): ContentFieldDef | undefined {
return CONTENT_REGISTRY.find((f) => f.key === key);
}