Files
florian.wilde 2a2c5affd5 feat: Tage als geschlossen markierbar (Feiertage o. Ä.)
Im Wochenplan lässt sich pro Tag ein optionaler, mehrsprachiger
Schließungsgrund hinterlegen. Geschlossene Tage werden auf der
öffentlichen Speisekarte (Desktop + mobil) und im RSS-Feed als
"Geschlossen" statt mit Essenszeiten angezeigt, im Wochenplan bleibt
"Mahlzeit hinzufügen" für diesen Tag deaktiviert.
2026-07-20 12:35:00 +02:00

338 lines
14 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Übersetzungswörterbücher für die komplette Oberfläche.
// "de" ist die Referenzstruktur; en/pl werden dagegen typgeprüft,
// damit kein Schlüssel in einer Sprache fehlt.
const de = {
common: {
save: "Speichern",
cancel: "Abbrechen",
delete: "Löschen",
edit: "Bearbeiten",
add: "Hinzufügen",
back: "Zurück",
login: "Anmelden",
logout: "Abmelden",
loading: "Lade…",
you: "(du)",
name: "Name",
email: "E-Mail-Adresse",
password: "Passwort",
role: "Rolle",
roleAdmin: "Administrator",
roleStaff: "Kantinenmitarbeiter",
},
weekdays: {
long: ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"],
short: ["Mo", "Di", "Mi", "Do", "Fr"],
},
dateLocale: "de-DE",
header: {
canteen: "Kantine",
menuGroup: "Speiseplan",
weekPlan: "Wochenplan",
sections: "Essenszeiten",
dishes: "Gerichte",
labels: "Allergene & Zusatzstoffe",
adminGroup: "Verwaltung",
users: "Benutzer",
design: "Design",
languages: "Sprachen",
suggestions: "Vorschläge",
openSuggestions: "{count} offene Rezeptvorschläge",
viewMenu: "Speisekarte ansehen",
myAccount: "Mein Konto",
menuToggle: "Menü öffnen/schließen",
},
login: {
title: "Anmeldung",
submit: "Anmelden",
or: "oder",
passkeyButton: "Mit Passkey anmelden",
ssoButton: "Mit Single Sign-On anmelden",
backToMenu: "← Zur Speisekarte",
errorInvalidCredentials: "E-Mail-Adresse oder Passwort ist falsch.",
errorPasskeyCancelled: "Passkey-Anmeldung wurde abgebrochen.",
errorGeneric: "Anmeldung fehlgeschlagen: {message}",
errorPasswordDisabled:
"Die Anmeldung mit Passwort ist für dieses Konto deaktiviert. Bitte mit Passkey anmelden.",
errorOidcInvalidState:
"Die SSO-Anmeldung ist abgelaufen oder ungültig. Bitte erneut versuchen.",
errorOidcUnknownUser:
"Für diese E-Mail-Adresse existiert kein Konto. Bitte an die Verwaltung wenden.",
errorOidcFailed: "SSO-Anmeldung fehlgeschlagen. Bitte erneut versuchen.",
},
publicMenu: {
prevWeek: "← Vorherige Woche",
nextWeek: "Nächste Woche →",
prevWeekShort: "← Woche",
nextWeekShort: "Woche →",
today: "Heute",
dayTab: "Tag",
weekTab: "Woche",
allergens: "Allergene",
additives: "Zusatzstoffe",
labelingNoticeTitle: "Allergene & Zusatzstoffe",
suggestDish: "Rezept vorschlagen",
noEntry: "–",
rssFeed: "RSS-Feed abonnieren",
closedLabel: "Geschlossen",
},
weekEditor: {
title: "Wochenplan",
loading: "Lade Wochenplan…",
changeDish: "Anderes Gericht wählen",
addMeal: "Mahlzeit hinzufügen",
newDishesHint: "Neue Gerichte legst du unter {link} an.",
newDishesLinkLabel: "Gerichte",
loadError: "Laden fehlgeschlagen: {message}",
duplicateEntry: "Dieses Gericht ist an diesem Tag bereits zugewiesen.",
saveError: "Speichern fehlgeschlagen: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
closeDay: "Tag schließen",
reopenDay: "Wieder öffnen",
closedLabel: "Geschlossen",
closedReasonLabel: "Grund (optional)",
closedReasonPlaceholder: "z. B. Weihnachtsfeiertage",
},
dishManager: {
title: "Gerichte",
newDish: "Neues Gericht",
editDish: "Gericht bearbeiten",
nameLabel: "Bezeichnung",
autoTranslate: "Automatisch übersetzen",
translating: "Übersetze…",
translated: "Übersetzt.",
priceLabel: "Preis in €",
pricePlaceholder: "3,50",
searchPlaceholder: "Gericht suchen…",
selectAll: "Alle auswählen",
selectedCount: "{count} ausgewählt",
deleteSelected: "Ausgewählte löschen",
deleteSelectedCount: "Ausgewählte löschen ({count})",
empty: "Noch keine Gerichte angelegt.",
noResults: "Keine Treffer für „{query}“.",
confirmDelete:
"„{name}“ wirklich löschen? Das Gericht wird auch aus allen Wochenplänen entfernt.",
confirmDeleteSelected:
"{count} Gericht(e) wirklich löschen? Sie werden auch aus allen Wochenplänen entfernt.",
invalidPrice: "Bitte einen gültigen Preis angeben, z. B. 3,50",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
exportButton: "Exportieren",
importButton: "Importieren",
exportError: "Export fehlgeschlagen: {message}",
importError: "Import fehlgeschlagen: {message}",
importSuccess: "{created} neu angelegt, {updated} aktualisiert.",
importSkipped: "{count} Zeile(n) übersprungen: {reasons}",
importSkippedRow: "Zeile {row}: {reason}",
labelingDisabledHint:
"Allergene/Zusatzstoffe sind aktuell deaktiviert (Hinweismodus aktiv, siehe „Allergene & Zusatzstoffe“).",
},
codeList: {
allergensTitle: "Allergene",
additivesTitle: "Zusatzstoffe",
allergenCodeLabel: "Kürzel (z. B. A)",
additiveCodeLabel: "Kürzel (z. B. 1)",
nameLabel: "Bezeichnung",
empty: "Keine Einträge.",
confirmDelete: "„{code} – {name}“ wirklich löschen?",
duplicateCode: "Das Kürzel „{code}“ existiert bereits.",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
exportButton: "Exportieren",
importButton: "Importieren",
exportError: "Export fehlgeschlagen: {message}",
importError: "Import fehlgeschlagen: {message}",
importSuccess: "{created} neu angelegt, {updated} aktualisiert.",
importSkipped: "{count} Zeile(n) übersprungen: {reasons}",
importSkippedRow: "Zeile {row}: {reason}",
},
sectionManager: {
title: "Essenszeiten",
newSection: "Neue Essenszeit",
editSection: "Essenszeit bearbeiten",
nameLabel: "Bezeichnung",
timeFrom: "Von",
timeTo: "Bis",
moveUp: "Nach oben",
moveDown: "Nach unten",
empty: "Noch keine Essenszeiten angelegt.",
confirmDelete:
"„{name}“ wirklich löschen? Zugewiesene Gerichte werden auch aus allen Wochenplänen entfernt.",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
},
userManager: {
title: "Benutzer",
newUser: "Neuer Benutzer",
editUser: "Benutzer bearbeiten",
newPasswordLabel: "Neues Passwort (optional)",
repeatPasswordLabel: "Passwort wiederholen",
passwordMismatch: "Die Passwörter stimmen nicht überein.",
confirmDelete:
"Benutzer „{email}“ wirklich löschen? Der Zugang wird dauerhaft entfernt.",
loadError: "Laden fehlgeschlagen: {message}",
},
designEditor: {
title: "Design",
logoTitle: "Logo",
logoHint: "Das Logo wird zentriert über der Speisekarte angezeigt.",
logoNone: "Kein Logo hinterlegt.",
logoUpload: "Logo hochladen",
logoRemove: "Logo entfernen",
logoUpdated: "Logo aktualisiert.",
logoRemoved: "Logo entfernt.",
faviconTitle: "Favicon",
faviconHint:
"Wird als Icon im Browser-Tab angezeigt. Ohne eigenes Favicon gilt das Standard-Icon der Anwendung.",
faviconNone: "Kein eigenes Favicon hinterlegt.",
faviconUpload: "Favicon hochladen",
faviconRemove: "Favicon entfernen",
faviconUpdated: "Favicon aktualisiert.",
faviconRemoved: "Favicon entfernt.",
invalidImage: "Bitte eine Bilddatei auswählen (PNG, JPG, SVG, WebP, ICO).",
colorsTitle: "Farbgebung",
colorsSave: "Farben speichern",
colorsReset: "Auf Standard zurücksetzen",
colorsSaved: "Farben gespeichert.",
colorPrimary: "Primärfarbe (Tabellenkopf)",
colorPrimaryDark: "Primärfarbe dunkel (Kopfleiste intern)",
colorAccent: "Akzentfarbe",
colorHeaderText: "Schriftfarbe im Tabellenkopf",
colorRowBg: "Zellen-Hintergrund",
colorRowBgAlt: "Zellen-Hintergrund (alternierend)",
colorAddBtn: "Button „Hinzufügen“",
colorDeleteBtn: "Button „Löschen“",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
uploadError: "Upload fehlgeschlagen: {message}",
confirmRemoveLogo: "Logo wirklich entfernen?",
confirmRemoveFavicon: "Favicon wirklich entfernen?",
},
labelingNotice: {
title: "Anzeige von Allergenen & Zusatzstoffen",
hint:
"Standardmäßig sieht jeder Gast eine detaillierte Auswahl der Allergene/Zusatzstoffe je Gericht. Alternativ kann stattdessen nur ein Hinweistext angezeigt werden – die Auswahl je Gericht wird dann auch in der Gerichte-Verwaltung ausgeblendet.",
modeDetailed: "Detaillierte Auswahl je Gericht anzeigen",
modeNotice: "Nur Hinweistext anzeigen",
noticeDeLabel: "Hinweistext (Deutsch)",
noticeEnLabel: "Hinweistext (Englisch)",
noticePlLabel: "Hinweistext (Polnisch)",
save: "Speichern",
saved: "Gespeichert.",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
},
passkeys: {
title: "Passkeys",
hint: "Mit einem Passkey (Fingerabdruck, Gesichtserkennung oder Sicherheitsschlüssel) kannst du dich künftig ohne Passwort anmelden. Passkeys gelten pro Domain – nach einem Domainwechsel müssen sie neu registriert werden.",
register: "Neuen Passkey registrieren",
empty: "Noch keine Passkeys registriert.",
createdOn: "– angelegt am {date}",
registered: "Passkey wurde erfolgreich registriert.",
registerError: "Registrierung fehlgeschlagen: {message}",
registerCancelled: "Die Passkey-Registrierung wurde abgebrochen.",
loadError: "Passkeys konnten nicht geladen werden: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
confirmDelete: "Diesen Passkey wirklich löschen?",
},
account: {
title: "Mein Konto",
personalTitle: "Persönliche Daten",
saveName: "Name speichern",
nameSaved: "Name gespeichert.",
nameRequired: "Bitte einen Namen angeben.",
emailTitle: "E-Mail-Adresse",
changeEmail: "E-Mail-Adresse ändern",
emailChanged: "E-Mail-Adresse geändert.",
passwordTitle: "Passwort ändern",
currentPassword: "Aktuelles Passwort (zur Verifizierung)",
newPassword: "Neues Passwort",
newPasswordRepeat: "Neues Passwort wiederholen",
changePassword: "Passwort ändern",
passwordChanged: "Passwort geändert.",
passwordMismatch: "Die Passwörter stimmen nicht überein.",
passwordTooShort: "Das neue Passwort muss mindestens 8 Zeichen lang sein.",
passwordAuthTitle: "Anmeldung mit Passwort",
passwordAuthHint:
"Du kannst die Anmeldung mit E-Mail und Passwort für dein Konto deaktivieren und dich künftig ausschließlich per Passkey anmelden. Dafür muss mindestens ein Passkey registriert sein.",
passwordAuthCheckbox: "Anmeldung mit E-Mail und Passwort erlaubt",
passwordAuthEnabled: "Passwort-Anmeldung aktiviert.",
passwordAuthDisabled: "Passwort-Anmeldung deaktiviert.",
passwordAuthNeedsPasskey:
"Ohne registrierten Passkey kannst du die Passwort-Anmeldung nicht deaktivieren – sonst wärst du ausgesperrt. Registriere zuerst einen Passkey weiter unten.",
confirmDisablePasswordAuth:
"Anmeldung mit E-Mail und Passwort wirklich deaktivieren? Du kannst dich danach nur noch per Passkey anmelden.",
wrongCurrentPassword: "Das aktuelle Passwort ist falsch.",
emailAlreadyUsed: "Diese E-Mail-Adresse wird bereits verwendet.",
saveError: "Speichern fehlgeschlagen: {message}",
changeError: "Ändern fehlgeschlagen: {message}",
},
suggestions: {
formTitle: "Rezept vorschlagen",
formHint:
"Fehlt dir ein Gericht auf der Speisekarte? Schlage es hier vor – das Kantinen-Team sichtet deinen Vorschlag.",
nameLabel: "Gerichtname",
descriptionLabel: "Beschreibung / Zutaten (optional)",
submit: "Vorschlag senden",
submitting: "Wird gesendet…",
success: "Danke! Dein Vorschlag wurde übermittelt.",
duplicate: "Dieses Gericht gibt es bereits auf der Speisekarte.",
genericError: "Senden fehlgeschlagen: {message}",
nameRequired: "Bitte einen Namen angeben.",
backToMenu: "← Zur Speisekarte",
reviewTitle: "Rezeptvorschläge",
reviewEmpty: "Noch keine Vorschläge.",
accept: "Als Gericht übernehmen",
reject: "Ablehnen",
confirmReject: "Vorschlag „{name}“ wirklich ablehnen und löschen?",
accepted: "Als Gericht übernommen.",
rejected: "Vorschlag abgelehnt.",
loadError: "Laden fehlgeschlagen: {message}",
actionError: "Aktion fehlgeschlagen: {message}",
submittedOn: "eingereicht am {date}",
},
languageManager: {
title: "Sprachen",
hint:
"Deutsch ist die feste Basis-Sprache und immer vollständig. Weitere Sprachen werden als Übersetzungsdatei importiert und automatisch mit Deutsch aufgefüllt, wenn einzelne Begriffe fehlen.",
codeColumn: "Code",
labelColumn: "Anzeigename",
activeColumn: "Aktiv",
completenessColumn: "Vollständigkeit",
baseBadge: "Basis-Sprache",
complete: "Vollständig",
completeness: "{percent} % übersetzt",
outdated: "{count} neue(r) Begriff(e) seit letztem Import",
downloadTemplate: "Vorlage herunterladen",
uploadTranslation: "Übersetzung hochladen",
newLanguageTitle: "Neue Sprache anlegen",
newLanguageCodeLabel: "Sprachcode (z. B. fr)",
newLanguageLabelLabel: "Anzeigename (z. B. Français)",
create: "Anlegen",
delete: "Löschen",
confirmDelete:
"„{label}“ wirklich löschen? Alle Übersetzungen dieser Sprache gehen verloren.",
cannotDeleteBase: "Die Basis-Sprache kann nicht gelöscht werden.",
cannotDeactivateLast: "Mindestens eine Sprache muss aktiv bleiben.",
loadError: "Laden fehlgeschlagen: {message}",
saveError: "Speichern fehlgeschlagen: {message}",
deleteError: "Löschen fehlgeschlagen: {message}",
importSuccess: "{count} Begriffe importiert.",
importError: "Import fehlgeschlagen: {message}",
importInvalidFile: "Die Datei konnte nicht gelesen werden (ungültiges JSON).",
importMissingPlaceholder:
"„{key}“: Platzhalter {placeholder} fehlt in der Übersetzung – Zeile übersprungen.",
duplicateCode: "Der Sprachcode „{code}“ existiert bereits.",
invalidCode: "Bitte einen Sprachcode aus 2–5 Kleinbuchstaben angeben.",
},
};
type Dict = typeof de;
export type { Dict };
export const dictionaries = { de };