Files
fotospiel-app/resources/js/guest/i18n/messages.ts
Codex Agent 53eb560aa5
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Add live show player playback and effects
2026-01-05 18:31:01 +01:00

1498 lines
55 KiB
TypeScript
Raw 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.
// @ts-nocheck
export const SUPPORTED_LOCALES = [
{ code: 'de', label: 'Deutsch', flag: '🇩🇪' },
{ code: 'en', label: 'English', flag: '🇬🇧' },
] as const;
export type LocaleCode = typeof SUPPORTED_LOCALES[number]['code'];
type NestedMessages = {
[key: string]: string | NestedMessages;
};
export const DEFAULT_LOCALE: LocaleCode = 'de';
export const messages: Record<LocaleCode, NestedMessages> = {
de: {
common: {
hi: 'Hi',
refresh: 'Aktualisieren',
pullToRefresh: 'Zum Aktualisieren ziehen',
releaseToRefresh: 'Zum Aktualisieren loslassen',
refreshing: 'Aktualisiere...',
updateAvailable: 'Neue Version verfügbar.',
updateAction: 'Aktualisieren',
offlineReady: 'Offline bereit.',
actions: {
close: 'Schließen',
loading: 'Lädt...',
},
},
navigation: {
home: 'Start',
tasks: 'Aufgaben',
achievements: 'Erfolge',
gallery: 'Galerie',
},
header: {
loading: 'Lade Event...',
stats: {
online: 'online',
tasksSolved: 'Aufgaben gelöst',
},
helpGallery: 'Hilfe zu Galerie & Teilen',
notifications: {
tabStatus: 'Upload-Status',
},
},
liveShowPlayer: {
title: 'Live Show',
loading: 'Live Show wird geladen...',
connection: {
live: 'Live',
sync: 'Sync',
},
controls: {
play: 'Weiter',
pause: 'Pause',
fullscreen: 'Vollbild',
exitFullscreen: 'Vollbild verlassen',
offline: 'Offline',
paused: 'Pausiert',
},
empty: {
title: 'Noch keine Live-Fotos',
description: 'Warte auf die ersten Uploads...',
},
error: {
title: 'Live Show nicht erreichbar',
description: 'Bitte überprüfe den Live-Link.',
},
},
eventAccess: {
loading: {
title: 'Wir prüfen deinen Zugang...',
subtitle: 'Einen Moment bitte.',
},
error: {
invalid_token: {
title: 'Zugriffscode ungültig',
description: 'Der eingegebene Code konnte nicht verifiziert werden.',
ctaLabel: 'Neuen Code anfordern',
},
token_revoked: {
title: 'Zugriffscode deaktiviert',
description: 'Dieser Code wurde zurückgezogen. Bitte fordere einen neuen Code an.',
ctaLabel: 'Neuen Code anfordern',
},
token_expired: {
title: 'Zugriffscode abgelaufen',
description: 'Der Code ist nicht mehr gültig. Aktualisiere deinen Code, um fortzufahren.',
ctaLabel: 'Code aktualisieren',
},
token_rate_limited: {
title: 'Zu viele Versuche',
description: 'Es gab zu viele Eingaben in kurzer Zeit. Warte kurz und versuche es erneut.',
hint: 'Tipp: Eine erneute Eingabe ist in wenigen Minuten möglich.',
},
access_rate_limited: {
title: 'Zu viele Aufrufe',
description: 'Es gab sehr viele Aufrufe in kurzer Zeit. Warte kurz und versuche es erneut.',
hint: 'Tipp: Du kannst es gleich noch einmal versuchen.',
},
gallery_expired: {
title: 'Galerie nicht mehr verfügbar',
description: 'Die Galerie zu diesem Event ist nicht mehr zugänglich.',
ctaLabel: 'Neuen Code anfordern',
},
event_not_public: {
title: 'Event nicht öffentlich',
description: 'Dieses Event ist aktuell nicht öffentlich zugänglich.',
hint: 'Nimm Kontakt mit den Veranstalter:innen auf, um Zugang zu erhalten.',
},
network_error: {
title: 'Verbindungsproblem',
description: 'Wir konnten keine Verbindung zum Server herstellen. Prüfe deine Internetverbindung und versuche es erneut.',
},
server_error: {
title: 'Server nicht erreichbar',
description: 'Der Server reagiert derzeit nicht. Versuche es später erneut.',
},
default: {
title: 'Event nicht erreichbar',
description: 'Wir konnten dein Event nicht laden. Bitte versuche es erneut.',
ctaLabel: 'Zur Code-Eingabe',
},
},
},
profileSetup: {
loading: 'Lade Event...',
error: {
default: 'Event nicht gefunden.',
backToStart: 'Zurück zur Startseite',
},
card: {
description: 'Fange den schönsten Moment ein!',
},
form: {
label: 'Dein Name (z.B. Anna)',
placeholder: 'Dein Name',
submit: 'Los gehts!',
submitting: 'Speichere...',
},
},
landing: {
pageTitle: 'Willkommen bei der Fotospiel App!',
headline: 'Elegante Erinnerungen, live erzählt.',
subheadline: 'Hier beginnt euer Fotoabenteuer gemeinsam, intuitiv und live.',
join: {
title: 'Event beitreten',
description: 'Scanne den QR-Code oder gib den Code manuell ein.',
button: 'Event beitreten',
buttonLoading: 'Prüfe...',
},
scan: {
start: 'QR-Code scannen',
stop: 'Scanner stoppen',
manualDivider: 'Oder manuell eingeben',
},
input: {
placeholder: 'Event-Code eingeben',
},
errors: {
eventClosed: 'Event nicht gefunden oder geschlossen.',
network: 'Netzwerkfehler. Bitte später erneut versuchen.',
camera: 'Kamera-Zugriff fehlgeschlagen. Bitte erlaube den Zugriff und versuche es erneut.',
},
},
home: {
fallbackGuestName: 'Gast',
welcomeLine: 'Willkommen {name}!',
swipeHint: 'Wische für mehr Aufgaben',
introRotating: {
0: 'Hilf uns, diesen besonderen Tag mit deinen schönsten Momenten festzuhalten.',
1: 'Fang die Stimmung des Events ein und teile sie mit allen Gästen.',
2: 'Deine Sicht zählt: Halte Augenblicke fest, die sonst niemand bemerkt.',
3: 'Erfülle kleine Fotoaufgaben und fülle die gemeinsame Galerie mit Leben.',
4: 'Zeig uns, was du siehst deine Fotos erzählen die Geschichte dieses Tages.',
5: 'Mach aus Schnappschüssen gemeinsame Erinnerungen in einer großen Event-Galerie.',
6: 'Diese App ist eure Fotozentrale fotografiere, lade hoch und begeistere die anderen.',
7: 'Sorge dafür, dass kein wichtiger Moment verloren geht mit deinen Bildern.',
8: 'Lass dich von Fotoaufgaben inspirieren und halte die besonderen Szenen fest.',
9: 'Mach mit beim Fotospiel: Deine Fotos machen dieses Event unvergesslich.',
},
hero: {
subtitle: 'Willkommen zur Party',
title: 'Hey {name}!',
description: 'Du bist bereit für "{eventName}". Fang die Highlights des Events ein und teile sie mit allen Gästen.',
progress: {
some: 'Schon {count} Aufgaben erledigt weiter so!',
none: 'Starte mit deiner ersten Aufgabe wir zählen auf dich!',
},
defaultEventName: 'Dein Event',
},
stats: {
online: 'Gleichzeitig online',
tasksSolved: 'Aufgaben gelöst',
lastUpload: 'Letzter Upload',
completedTasks: 'Deine erledigten Aufgaben',
},
actions: {
title: 'Deine Aktionen',
subtitle: 'Wähle aus, womit du starten willst',
queueButton: 'Uploads in Warteschlange ansehen',
items: {
tasks: {
label: 'Aufgabe ziehen',
description: 'Hol dir deine nächste Challenge',
},
upload: {
label: 'Direkt hochladen',
description: 'Teile deine neuesten Fotos',
},
gallery: {
label: 'Galerie ansehen',
description: 'Lass dich von anderen inspirieren',
},
},
},
checklist: {
title: 'Dein Fortschritt',
description: 'Halte dich an diese drei kurzen Schritte für die besten Ergebnisse.',
steps: {
first: 'Aufgabe auswählen oder starten',
second: 'Emotion festhalten und Foto schießen',
third: 'Bild hochladen und den Moment teilen',
},
},
latestUpload: {
none: 'Noch kein Upload',
invalid: 'Noch kein Upload',
justNow: 'Gerade eben',
minutes: 'vor {count} Min',
hours: 'vor {count} Std',
days: 'vor {count} Tagen',
},
},
achievements: {
page: {
title: 'Erfolge',
subtitle: 'Behalte deine Highlights, Badges und die aktivsten Gäste im Blick.',
loadError: 'Erfolge konnten nicht geladen werden.',
retry: 'Erneut versuchen',
buttons: {
personal: 'Meine Erfolge',
event: 'Event Highlights',
feed: 'Live Feed',
},
},
personal: {
greeting: 'Hi {name}!',
stats: '{photos} Fotos | {tasks} Aufgaben | {likes} Likes',
actions: {
upload: 'Neues Foto hochladen',
tasks: 'Aufgabe ziehen',
},
},
badges: {
title: 'Badges',
description: 'Dein Fortschritt bei den verfügbaren Erfolgen.',
empty: 'Noch keine Badges verfügbar.',
},
leaderboard: {
description: 'Top 5 Teilnehmer dieses Events',
uploadsTitle: 'Top Uploads',
uploadsEmpty: 'Noch keine Uploads sobald Fotos vorhanden sind, erscheinen sie hier.',
likesTitle: 'Beliebteste Gäste',
likesEmpty: 'Likes fehlen noch motiviere die Gäste, Fotos zu liken.',
guestFallback: 'Gast',
item: {
photos: '{count} Fotos',
likes: '{count} Likes',
},
},
timeline: {
title: 'Timeline',
description: 'Wie das Event im Laufe der Zeit Fahrt aufgenommen hat.',
row: '{photos} Fotos | {guests} Gäste',
},
feed: {
title: 'Live Feed',
description: 'Die neuesten Momente aus deinem Event.',
empty: 'Noch keine Uploads starte die Kamera und lege los!',
taskLabel: 'Aufgabe: {task}',
likesLabel: '{count} Likes',
thumbnailAlt: 'Vorschau',
},
highlights: {
topTitle: 'Publikumsliebling',
topDescription: 'Das Foto mit den meisten Likes.',
noPreview: 'Kein Vorschau-Bild',
likesAmount: '{count} Likes',
taskLabel: 'Aufgabe: {task}',
trendingTitle: 'Trend-Emotion',
trendingDescription: 'Diese Stimmung taucht gerade besonders oft auf.',
trendingCount: '{count} Fotos mit dieser Stimmung',
},
},
tasks: {
page: {
eyebrow: 'Aufgaben-Zentrale',
title: 'Deine nächste Aufgabe',
subtitle: 'Wähle eine Stimmung aus oder lass dich überraschen.',
swipeHint: 'Tipp: Links wischen = neue Aufgabe · Rechts wischen = Inspiration',
completedLabel: 'Schon erledigt',
ctaStart: "Los geht's!",
shuffleCta: 'Was Neues!',
shuffleButton: 'Shuffle',
inspirationTitle: 'Foto-Inspiration',
inspirationLoading: 'lädt…',
inspirationEmptyTitle: 'Noch kein Foto zu dieser Aufgabe',
inspirationEmptyDescription: 'Sei die/der Erste und lade eins hoch',
inspirationMore: 'Mehr',
inspirationError: 'Fotos konnten nicht geladen werden',
suggestionsEyebrow: 'Mehr Inspiration',
suggestionsTitle: 'Spring direkt zur nächsten Aufgabe',
noTasksAlert: 'Für dieses Event sind derzeit keine Aufgaben hinterlegt.',
emptyTitle: 'Keine passende Aufgabe gefunden',
emptyDescriptionWithTasks: 'Für deine aktuelle Stimmung gibt es gerade keine Aufgabe. Wähle eine andere Stimmung oder lade neue Aufgaben.',
emptyDescriptionNoTasks: 'Hier sind noch keine Aufgaben hinterlegt. Bitte versuche es später erneut.',
reloadButton: 'Aufgaben neu laden',
filters: {
none: 'Kein Filter',
recentFallback: 'Stimmung wählen',
showAll: 'Alle zeigen',
dialogTitle: 'Alle verfügbaren Stimmungen',
empty: 'Für dieses Event stehen noch keine Stimmungen bereit.',
countOne: '{count} Aufgabe',
countMany: '{count} Aufgaben',
},
},
},
notFound: {
title: 'Nicht gefunden',
description: 'Die Seite konnte nicht gefunden werden.',
},
galleryCountdown: {
expiresIn: 'Noch {days} Tage online',
expiresToday: 'Letzter Tag!',
expired: 'Galerie abgelaufen',
description: 'Sichere jetzt deine Lieblingsfotos die Galerie verschwindet bald.',
expiredDescription: 'Nur die Veranstalter:innen können die Galerie jetzt noch verlängern.',
ctaUpload: 'Letzte Fotos hochladen',
},
galleryPublic: {
title: 'Galerie',
loading: 'Galerie wird geladen ...',
loadingMore: 'Weitere Fotos werden geladen',
loadError: 'Die Galerie konnte nicht geladen werden.',
loadMore: 'Mehr anzeigen',
download: 'Herunterladen',
expiredTitle: 'Galerie nicht verfügbar',
expiredDescription: 'Die Galerie für dieses Event ist abgelaufen.',
emptyTitle: 'Noch keine Fotos',
emptyDescription: 'Sobald Fotos freigegeben sind, erscheinen sie hier.',
lightboxGuestFallback: 'Gast',
},
galleryPage: {
title: 'Galerie',
subtitle: 'Live-Impressionen deines Events',
loadingEvent: 'Event-Info wird geladen...',
eventNotFound: 'Event nicht gefunden.',
hero: {
label: 'Live-Galerie',
stats: '{photoCount} Fotos · {likeCount} ❤️ · {guestCount} Gäste online',
newPhotos: '{count} neue Fotos ansehen',
upload: 'Neues Foto hochladen',
eventFallback: 'Event',
},
loading: 'Lade…',
photo: {
justNow: 'Gerade eben',
anonymous: 'Gast',
alt: 'Foto {id}{suffix}',
altTaskSuffix: ' - {task}',
likeAria: 'Foto liken',
shareAria: 'Foto teilen',
},
filters: {
latest: 'Neueste',
popular: 'Beliebt',
mine: 'Meine',
photobooth: 'Fotobox',
},
badge: {
newPhotos: '{count} neue Fotos',
},
},
share: {
title: 'Geteiltes Foto',
defaultEvent: 'Ein besonderer Moment',
button: 'Teilen',
copyLink: 'Link kopieren',
copySuccess: 'Link kopiert!',
manualPrompt: 'Link kopieren',
openEvent: 'Event öffnen',
loading: 'Moment wird geladen...',
expiredTitle: 'Link abgelaufen',
expiredDescription: 'Dieser Link ist nicht mehr verfügbar.',
shareText: 'Schau dir diesen Moment bei Fotospiel an.',
error: 'Teilen fehlgeschlagen',
whatsapp: 'WhatsApp',
imessage: 'Nachrichten',
copyError: 'Link konnte nicht kopiert werden.',
},
uploadQueue: {
title: 'Uploads',
description: 'Warteschlange mit Fortschritt und erneuten Versuchen; Hintergrund-Sync umschalten.',
},
pendingUploads: {
title: 'Uploads in Prüfung',
subtitle: 'Deine Fotos warten noch auf die Freigabe.',
successTitle: 'Upload gespeichert',
successBody: 'Dein Foto ist hochgeladen und wartet auf die Freigabe.',
emptyTitle: 'Keine wartenden Uploads',
emptyBody: 'Wenn du ein Foto hochlädst, erscheint es hier bis zur Freigabe.',
cta: 'Weiteres Foto aufnehmen',
refresh: 'Aktualisieren',
loading: 'Lade Uploads...',
error: 'Laden fehlgeschlagen. Bitte versuche es erneut.',
card: {
pending: 'Wartet auf Freigabe',
uploadedAt: 'Hochgeladen {time}',
justNow: 'Gerade eben',
},
},
lightbox: {
taskLabel: 'Aufgabe',
loadingTask: 'Lade Aufgabe...',
photoAlt: 'Foto {id}{suffix}',
photoAltTaskSuffix: ' - {taskTitle}',
fallbackTitle: 'Aufgabe {id}',
unknownTitle: 'Unbekannte Aufgabe {id}',
errors: {
notFound: 'Foto nicht gefunden',
loadFailed: 'Fehler beim Laden des Fotos',
},
},
upload: {
cameraTitle: 'Kamera',
preparing: 'Aufgabe und Kamera werden vorbereitet ...',
loadError: {
title: 'Aufgabe konnte nicht geladen werden. Du kannst trotzdem ein Foto machen.',
retry: 'Nochmal versuchen',
},
primer: {
title: 'Bereit für dein Shooting?',
body: {
part1: 'Lass uns sicherstellen, dass alles sitzt: prüfe das Licht, wisch die Kamera sauber und richte alle Personen im Bild aus.',
part2: 'Du kannst zwischen Front- und Rückkamera wechseln und bei Bedarf ein Raster aktivieren.',
},
dismiss: 'Verstanden',
},
hud: {
title: 'Live-Missionen',
subtitle: 'Bleib im Flow Kamera bereithalten.',
moodLabel: 'Stimmung: {mood}',
moodFallback: 'Freestyle',
ctaLabel: 'Inspiration öffnen',
cards: {
online: 'Gäste online',
completed: 'Aufgaben gelöst',
lastUpload: 'Letzter Upload',
},
progressLabel: 'Story {count}/{target} aktiv',
liveGuests: '{count} Gäste live',
relative: {
now: 'Gerade eben',
minutes: 'vor {count} Min',
hours: 'vor {count} Std',
days: 'vor {count} Tagen',
},
},
limitSummary: {
title: 'Uploads & Slots',
subtitle: 'Dein Event-Paket im Überblick',
badgeLabel: 'Aktuell',
cards: {
photos: {
title: 'Fotos insgesamt',
remaining: '{remaining} von {limit} frei',
unlimited: 'Unlimitierte Foto-Uploads aktiv',
},
guests: {
title: 'Geräte im Einsatz',
remaining: '{remaining} Slots verfügbar',
unlimited: 'Unlimitierte Geräte freigeschaltet',
},
},
badges: {
ok: 'OK',
warning: 'Bald voll',
limit_reached: 'Limit erreicht',
unlimited: 'Unlimitiert',
},
},
cameraUnsupported: {
title: 'Kamera nicht verfügbar',
message: 'Dein Gerät unterstützt keine Kameravorschau im Browser. Du kannst stattdessen Fotos aus deiner Galerie hochladen.',
openGallery: 'Foto aus Galerie wählen',
},
cameraBlocked: {
title: 'Kamera durch Sicherheitsrichtlinie blockiert',
message: 'Die Kamera ist durch die Sicherheitsrichtlinie dieser Seite blockiert. Öffne den Event-Link im Browser oder lade ein Foto aus der Galerie hoch.',
hint: 'Tipp: Wenn du in einer In-App-Ansicht bist, öffne den Link in Safari/Chrome und lade die Seite neu.',
},
cameraDenied: {
title: 'Kamera-Zugriff verweigert',
explanation: 'Du musst den Zugriff auf die Kamera erlauben, um Fotos aufnehmen zu können.',
reopenPrompt: 'Systemdialog erneut öffnen',
chooseFile: 'Foto aus Galerie wählen',
prompt: 'Wir brauchen Zugriff auf deine Kamera. Erlaube die Anfrage oder wähle alternativ ein Foto aus deiner Galerie.',
hint: 'Tipp: Prüfe in den Browser-Einstellungen, ob Kamera-Zugriff erlaubt ist, und lade die Seite neu.',
},
cameraError: {
title: 'Kamera konnte nicht gestartet werden',
explanation: 'Wir konnten keine Verbindung zur Kamera herstellen. Prüfe die Berechtigungen oder starte dein Gerät neu.',
tryAgain: 'Nochmals versuchen',
hint: 'Tipp: Schließe andere Apps mit Kamerazugriff und versuche es erneut.',
},
readyOverlay: {
title: 'Kamera bereit',
message: 'Wenn alle im Bild sind, kannst du den Countdown starten oder ein vorhandenes Foto wählen.',
start: 'Countdown starten',
chooseFile: 'Foto auswählen',
},
taskInfo: {
countdown: 'Countdown',
emotion: 'Stimmung: {value}',
instructionsPrefix: 'Hinweis',
difficulty: {
easy: 'Leicht',
medium: 'Medium',
hard: 'Herausfordernd',
},
timeEstimate: '{count} Min',
fallbackTitle: 'Aufgabe {id}',
fallbackDescription: 'Halte den Moment fest und teile ihn mit allen Gästen.',
fallbackInstructions: 'Positioniere alle im Bild, starte den Countdown und lass die Emotion wirken.',
badge: 'Aufgabe #{id}',
},
countdown: {
ready: 'Bereit machen ...',
},
review: {
retake: 'Nochmal aufnehmen',
keep: 'Foto verwenden',
readyAnnouncement: 'Foto aufgenommen. Bitte Vorschau prüfen.',
},
liveShow: {
title: 'Live-Show',
description: 'Zeige dieses Foto direkt auf der Live-Show.',
toggle: 'Live-Show aktivieren',
immediate: 'Erscheint sofort auf der Leinwand.',
reviewed: 'Wird nach Freigabe für die Live-Show angezeigt.',
},
status: {
saving: 'Speichere Foto...',
processing: 'Verarbeite Foto...',
uploading: 'Foto wird hochgeladen...',
preparing: 'Foto wird vorbereitet...',
optimizing: 'Foto wird optimiert...',
completed: 'Upload abgeschlossen.',
failed: 'Upload fehlgeschlagen. Bitte versuche es erneut.',
},
demoReadOnly: 'Uploads sind in der Demo deaktiviert.',
optimizedNotice: 'Wir haben dein Foto verkleinert, damit der Upload schneller klappt. Eingespart: {saved}',
optimizedFallback: 'Optimierung nicht möglich wir laden das Original hoch.',
retrying: 'Verbindung holperig neuer Versuch ({attempt}).',
controls: {
toggleGrid: 'Raster umschalten',
toggleCountdown: 'Countdown umschalten',
toggleMirror: 'Spiegelung für Frontkamera umschalten',
toggleFlash: 'Blitzpräferenz umschalten',
capture: 'Foto aufnehmen',
switchCamera: 'Kamera wechseln',
chooseFile: 'Foto auswählen',
},
limitReached: 'Upload-Limit erreicht ({used} / {max} Fotos). Bitte kontaktiere die Veranstalter für ein Upgrade.',
limitUnlimited: 'unbegrenzt',
limitWarning: 'Nur noch {remaining} von {max} Fotos möglich. Bitte kontaktiere die Veranstalter für ein Upgrade.',
galleryWarningDay: 'Galerie läuft in {days} Tag ab. Teile deine Fotos rechtzeitig!',
galleryWarningDays: 'Galerie läuft in {days} Tagen ab. Teile deine Fotos rechtzeitig!',
packageStatus: {
title: 'Dein Paketstatus',
subtitle: 'Behalte deine Kontingente im Blick, bevor es eng wird.',
badges: {
ok: 'Bereit',
warning: 'Hinweis',
limit_reached: 'Limit erreicht',
unlimited: 'Unbegrenzt',
},
cards: {
photos: {
title: 'Fotos',
remaining: 'Nur noch {remaining} von {limit} Fotos möglich',
unlimited: 'Unbegrenzte Foto-Uploads inklusive',
},
guests: {
title: 'Gäste',
remaining: '{remaining} Gäste frei (max. {limit})',
unlimited: 'Beliebig viele Gäste erlaubt',
},
},
},
dialogs: {
close: 'Verstanden',
photoLimit: {
title: 'Upload-Limit erreicht',
description: 'Es wurden {used} von {limit} Fotos hochgeladen. Bitte kontaktiere das Team für ein Upgrade.',
hint: 'Tipp: Größere Pakete schalten sofort wieder Uploads frei.',
},
deviceLimit: {
title: 'Gerätelimit erreicht',
description: 'Dieses Gerät hat sein Upload-Kontingent ausgeschöpft.',
hint: 'Nutze ein anderes Gerät oder sprich die Veranstalter:innen an.',
},
packageMissing: {
title: 'Event pausiert Uploads',
description: 'Für dieses Event sind aktuell keine Uploads freigeschaltet.',
hint: 'Bitte kontaktiere die Veranstalter:innen für weitere Informationen.',
},
galleryExpired: {
title: 'Galerie abgelaufen',
description: 'Die Galerie ist geschlossen neue Uploads sind nicht mehr möglich.',
hint: 'Nur die Veranstalter:innen können die Galerie verlängern.',
},
csrf: {
title: 'Sitzung abgelaufen',
description: 'Bitte lade die Seite neu und versuche den Upload anschließend erneut.',
hint: 'Aktualisiere die Seite, um eine neue Sitzung zu starten.',
},
generic: {
title: 'Upload fehlgeschlagen',
description: 'Der Upload konnte nicht abgeschlossen werden. Bitte versuche es später erneut.',
hint: 'Bleibt das Problem bestehen, kontaktiere die Veranstalter:innen.',
},
},
errors: {
photoLimit: 'Upload-Limit erreicht. Bitte kontaktiere die Veranstalter für ein Upgrade.',
deviceLimit: 'Dieses Gerät hat das Upload-Limit erreicht. Bitte wende dich an die Veranstalter.',
packageMissing: 'Dieses Event akzeptiert derzeit keine Uploads.',
galleryExpired: 'Die Galerie ist abgelaufen. Uploads sind nicht mehr möglich.',
generic: 'Upload fehlgeschlagen. Bitte versuche es erneut.',
tooLargeHint: 'Das Foto war zu groß. Bitte erneut versuchen wir verkleinern es automatisch.',
},
cameraInactive: 'Kamera ist nicht aktiv. {hint}',
cameraInactiveHint: 'Tippe auf "{label}", um loszulegen.',
captureError: 'Foto konnte nicht erstellt werden.',
feedError: 'Kamera liefert kein Bild. Bitte starte die Kamera neu.',
canvasError: 'Canvas konnte nicht initialisiert werden.',
limitCheckError: 'Fehler beim Prüfen des Upload-Limits. Upload deaktiviert.',
galleryPickError: 'Auswahl fehlgeschlagen. Bitte versuche es erneut.',
captureButton: 'Foto aufnehmen',
galleryButton: 'Foto aus Galerie wählen',
switchCamera: 'Kamera wechseln',
countdownLabel: 'Countdown: {seconds}s',
countdownReady: 'Bereit machen ...',
buttons: {
startCamera: 'Kamera starten',
tryAgain: 'Erneut versuchen',
recheckCamera: 'Zugriff erneut prüfen',
},
},
settings: {
title: 'Einstellungen',
subtitle: 'Verwalte deinen Gastzugang, rechtliche Dokumente und lokale Daten.',
language: {
title: 'Sprache',
description: 'Wähle deine bevorzugte Sprache für diese Veranstaltung.',
activeBadge: 'aktiv',
option: {
de: 'Deutsch',
en: 'English',
},
},
name: {
title: 'Dein Name',
description: 'Passe an, wie wir dich im Event begrüßen. Der Name wird nur lokal gespeichert.',
label: 'Anzeigename',
placeholder: 'z.B. Anna',
save: 'Name speichern',
saving: 'Speichere...',
reset: 'Zurücksetzen',
saved: 'Gespeichert (ok)',
loading: 'Lade gespeicherten Namen...',
},
haptics: {
title: 'Haptisches Feedback',
description: 'Kurze Vibrationen bei Likes, Uploads und Aktualisierungen.',
label: 'Vibrationen aktivieren',
unsupported: 'Auf diesem Gerät nicht verfügbar.',
},
legal: {
title: 'Rechtliches',
description: 'Die rechtlich verbindlichen Texte sind jederzeit hier abrufbar.',
loading: 'Dokument wird geladen...',
error: 'Das Dokument konnte nicht geladen werden. Bitte versuche es später erneut.',
fallbackTitle: 'Rechtlicher Hinweis',
section: {
impressum: 'Impressum',
privacy: 'Datenschutz',
terms: 'AGB',
},
},
cache: {
title: 'Offline-Cache',
description: 'Lösche lokale Daten, falls Inhalte veraltet erscheinen oder Uploads hängen bleiben.',
clear: 'Cache leeren',
clearing: 'Leere Cache...',
cleared: 'Cache gelöscht.',
note: 'Dies betrifft nur diesen Browser. Wartende Uploads können verloren gehen.',
},
help: {
title: 'Hilfecenter',
description: 'Öffne das Hilfecenter mit Schritt-für-Schritt-Anleitungen.',
cta: 'Hilfecenter öffnen',
},
footer: {
notice: 'Gastbereich - Daten werden lokal im Browser gespeichert.',
},
sheet: {
openLabel: 'Einstellungen öffnen',
backLabel: 'Zurück',
legalDescription: 'Rechtlicher Hinweis',
},
},
help: {
center: {
title: 'Hilfecenter',
subtitle: 'Antworten für Gäste nach dem ersten Laden auch offline verfügbar.',
searchPlaceholder: 'Suche nach Thema oder Stichwort',
offlineBadge: 'Offline-Version',
offlineDescription: 'Du siehst eine zwischengespeicherte Version. Geh online für aktuelle Inhalte.',
empty: 'Keine Artikel gefunden.',
error: 'Hilfe konnte nicht geladen werden.',
retry: 'Erneut versuchen',
listTitle: 'Alle Artikel',
},
article: {
back: 'Zurück zur Übersicht',
updated: 'Aktualisiert am {date}',
relatedTitle: 'Verwandte Artikel',
unavailable: 'Dieser Artikel ist nicht verfügbar.',
reload: 'Neu laden',
},
},
},
en: {
common: {
hi: 'Hi',
refresh: 'Refresh',
pullToRefresh: 'Pull to refresh',
releaseToRefresh: 'Release to refresh',
refreshing: 'Refreshing...',
updateAvailable: 'A new version is available.',
updateAction: 'Update',
offlineReady: 'Offline ready.',
actions: {
close: 'Close',
loading: 'Loading...',
},
},
navigation: {
home: 'Home',
tasks: 'Tasks',
achievements: 'Achievements',
gallery: 'Gallery',
},
header: {
loading: 'Loading event...',
stats: {
online: 'online',
tasksSolved: 'tasks solved',
},
helpGallery: 'Help: Gallery & sharing',
notifications: {
tabStatus: 'Upload status',
},
},
liveShowPlayer: {
title: 'Live Show',
loading: 'Loading Live Show...',
connection: {
live: 'Live',
sync: 'Sync',
},
controls: {
play: 'Play',
pause: 'Pause',
fullscreen: 'Fullscreen',
exitFullscreen: 'Exit fullscreen',
offline: 'Offline',
paused: 'Paused',
},
empty: {
title: 'No live photos yet',
description: 'Waiting for the first uploads...',
},
error: {
title: 'Live Show unavailable',
description: 'Please check the live link.',
},
},
eventAccess: {
loading: {
title: 'Checking your access...',
subtitle: 'Hang tight for a moment.',
},
error: {
invalid_token: {
title: 'Access code invalid',
description: 'We could not verify the code you entered.',
ctaLabel: 'Request new code',
},
token_revoked: {
title: 'Access code revoked',
description: 'This code was revoked. Please request a new one.',
ctaLabel: 'Request new code',
},
token_expired: {
title: 'Access code expired',
description: 'The code is no longer valid. Refresh your code to continue.',
ctaLabel: 'Refresh code',
},
token_rate_limited: {
title: 'Too many attempts',
description: 'There were too many attempts in a short time. Wait a bit and try again.',
hint: 'Tip: You can retry in a few minutes.',
},
access_rate_limited: {
title: 'Too many requests',
description: 'There were too many requests in a short time. Please wait a moment and try again.',
hint: 'Tip: You can retry shortly.',
},
gallery_expired: {
title: 'Gallery unavailable',
description: 'The gallery for this event is no longer accessible.',
ctaLabel: 'Request new code',
},
event_not_public: {
title: 'Event not public',
description: 'This event is not publicly accessible right now.',
hint: 'Contact the organizers to get access.',
},
network_error: {
title: 'Connection issue',
description: 'We could not reach the server. Check your connection and try again.',
},
server_error: {
title: 'Server unavailable',
description: 'The server is currently unavailable. Please try again later.',
},
default: {
title: 'Event unavailable',
description: 'We could not load your event. Please try again.',
ctaLabel: 'Back to code entry',
},
},
},
profileSetup: {
loading: 'Loading event...',
error: {
default: 'Event not found.',
backToStart: 'Back to start',
},
card: {
description: 'Capture the best moment!',
},
form: {
label: 'Your name (e.g. Anna)',
placeholder: 'Your name',
submit: "Let's go!",
submitting: 'Saving...',
},
},
landing: {
pageTitle: 'Welcome to the Fotospiel App!',
headline: 'An elegant way to tell memories live.',
subheadline: 'Start your collaborative photo story—intuitive, fast, live.',
join: {
title: 'Join the event',
description: 'Scan the QR code or enter the code manually.',
button: 'Join event',
buttonLoading: 'Checking...',
},
scan: {
start: 'Scan QR code',
stop: 'Stop scanner',
manualDivider: 'Or enter it manually',
},
input: {
placeholder: 'Enter event code',
},
errors: {
eventClosed: 'Event not found or closed.',
network: 'Network error. Please try again later.',
camera: 'Camera access failed. Allow access and try again.',
},
},
home: {
fallbackGuestName: 'Guest',
welcomeLine: 'Welcome {name}!',
swipeHint: 'Swipe for more missions',
introRotating: {
0: 'Help us capture this special day with your favourite moments.',
1: 'Capture the mood of the event and share it with everyone.',
2: 'Your view matters: save the moments that others might miss.',
3: 'Complete playful photo missions and fill the shared gallery with life.',
4: 'Show us what you see your photos tell the story of this day.',
5: 'Turn quick snapshots into shared memories in one big event gallery.',
6: 'This app is your photo hub shoot, upload, and delight the other guests.',
7: 'Make sure no important moment gets lost with your pictures.',
8: 'Let photo missions inspire you and capture the scenes that really matter.',
9: 'Join the photo game: your pictures make this event unforgettable.',
},
hero: {
subtitle: 'Welcome to the party',
title: 'Hey {name}!',
description: 'You are ready for "{eventName}". Capture the highlights and share them with everyone.',
progress: {
some: 'Already {count} tasks done - keep going!',
none: 'Start with your first task - we are counting on you!',
},
defaultEventName: 'Your event',
},
stats: {
online: 'Guests online',
tasksSolved: 'Tasks completed',
lastUpload: 'Latest upload',
completedTasks: 'Your completed tasks',
},
actions: {
title: 'Your actions',
subtitle: 'Choose how you want to start',
queueButton: 'View uploads in queue',
items: {
tasks: {
label: 'Draw a task',
description: 'Grab your next challenge',
},
upload: {
label: 'Upload directly',
description: 'Share your latest photos',
},
gallery: {
label: 'Browse gallery',
description: 'Get inspired by others',
},
},
},
checklist: {
title: 'Your progress',
description: 'Follow these three quick steps for the best results.',
steps: {
first: 'Pick or start a task',
second: 'Capture the emotion and take the photo',
third: 'Upload the picture and share the moment with everyone',
},
},
latestUpload: {
none: 'No upload yet',
invalid: 'No upload yet',
justNow: 'Just now',
minutes: '{count} min ago',
hours: '{count} h ago',
days: '{count} days ago',
},
},
achievements: {
page: {
title: 'Achievements',
subtitle: 'Keep an eye on highlights, badges, and the most active guests.',
loadError: 'Achievements could not be loaded.',
retry: 'Try again',
buttons: {
personal: 'My achievements',
event: 'Event highlights',
feed: 'Live feed',
},
},
personal: {
greeting: 'Hi {name}!',
stats: '{photos} photos | {tasks} tasks | {likes} likes',
actions: {
upload: 'Upload photo',
tasks: 'Draw a task',
},
},
badges: {
title: 'Badges',
description: 'Your progress across available achievements.',
empty: 'No badges unlocked yet.',
},
leaderboard: {
description: 'Top 5 participants of this event',
uploadsTitle: 'Top uploads',
uploadsEmpty: 'No uploads yet once photos arrive you will see them here.',
likesTitle: 'Most liked guests',
likesEmpty: 'No likes yet motivate guests to like photos.',
guestFallback: 'Guest',
item: {
photos: '{count} photos',
likes: '{count} likes',
},
},
timeline: {
title: 'Timeline',
description: 'How the event gained momentum throughout the day.',
row: '{photos} photos | {guests} guests',
},
feed: {
title: 'Live feed',
description: 'The freshest moments from your event.',
empty: 'No uploads yet grab your camera and start!',
taskLabel: 'Task: {task}',
likesLabel: '{count} likes',
thumbnailAlt: 'Preview',
},
highlights: {
topTitle: 'Audience favorite',
topDescription: 'The photo with the most likes.',
noPreview: 'No preview image',
likesAmount: '{count} likes',
taskLabel: 'Task: {task}',
trendingTitle: 'Trending emotion',
trendingDescription: 'This mood appears most often right now.',
trendingCount: '{count} photos in this mood',
},
},
tasks: {
page: {
eyebrow: 'Mission hub',
title: 'Your next task',
subtitle: 'Pick a mood or stay spontaneous.',
swipeHint: 'Tip: Swipe left for a new task · right for inspiration',
completedLabel: 'Already done',
ctaStart: "Let's go!",
shuffleCta: 'Something new!',
shuffleButton: 'Shuffle',
inspirationTitle: 'Photo inspiration',
inspirationLoading: 'loading…',
inspirationEmptyTitle: 'No photo for this task yet',
inspirationEmptyDescription: 'Be the first one to upload!',
inspirationMore: 'More',
inspirationError: 'Photos could not be loaded',
suggestionsEyebrow: 'More inspiration',
suggestionsTitle: 'Jump straight to the next task',
noTasksAlert: 'No tasks available for this event yet.',
emptyTitle: 'No matching task found',
emptyDescriptionWithTasks: 'No task matches this mood right now. Pick another mood or load new tasks.',
emptyDescriptionNoTasks: 'No tasks are available yet. Please try again later.',
reloadButton: 'Reload tasks',
filters: {
none: 'No filter',
recentFallback: 'Select mood',
showAll: 'Show all',
dialogTitle: 'All available moods',
empty: 'No moods are available for this event yet.',
countOne: '{count} task',
countMany: '{count} tasks',
},
},
},
notFound: {
title: 'Not found',
description: 'We could not find the page you requested.',
},
galleryCountdown: {
expiresIn: '{days} days remaining',
expiresToday: 'Final day!',
expired: 'Gallery expired',
description: 'Save your favourite photos before the gallery goes offline.',
expiredDescription: 'Only the organizers can extend the gallery now.',
ctaUpload: 'Share last photos',
},
galleryPublic: {
title: 'Gallery',
loading: 'Loading gallery ...',
loadingMore: 'Loading more photos',
loadError: 'The gallery could not be loaded.',
loadMore: 'Show more',
download: 'Download',
expiredTitle: 'Gallery unavailable',
expiredDescription: 'The gallery for this event has expired.',
emptyTitle: 'No photos yet',
emptyDescription: 'Once photos are approved they will appear here.',
lightboxGuestFallback: 'Guest',
},
galleryPage: {
title: 'Gallery',
subtitle: 'Live impressions from your event',
loadingEvent: 'Loading event info…',
eventNotFound: 'Event not found.',
hero: {
label: 'Live gallery',
stats: '{photoCount} photos · {likeCount} ❤️ · {guestCount} guests online',
newPhotos: 'View {count} new photos',
upload: 'Upload new photo',
eventFallback: 'Event',
},
loading: 'Loading…',
photo: {
justNow: 'Just now',
anonymous: 'Guest',
alt: 'Photo {id}{suffix}',
altTaskSuffix: ' - {task}',
likeAria: 'Like photo',
shareAria: 'Share photo',
},
filters: {
latest: 'Newest',
popular: 'Popular',
mine: 'My photos',
photobooth: 'Photo booth',
},
badge: {
newPhotos: '{count} new photos',
},
},
share: {
title: 'Shared photo',
defaultEvent: 'A special moment',
button: 'Share',
copyLink: 'Copy link',
copyError: 'Link could not be copied.',
manualPrompt: 'Copy link',
openEvent: 'Open event',
loading: 'Loading moment...',
expiredTitle: 'Link expired',
expiredDescription: 'This link is no longer available.',
shareText: 'Check out this moment on Fotospiel.',
error: 'Share failed',
},
uploadQueue: {
title: 'Uploads',
description: 'Queue with progress/retry and background sync toggle.',
},
pendingUploads: {
title: 'Pending uploads',
subtitle: 'Your photos are waiting for approval.',
successTitle: 'Upload saved',
successBody: 'Your photo is uploaded and waiting for approval.',
emptyTitle: 'No pending uploads',
emptyBody: 'Once you upload a photo, it will appear here until it is approved.',
cta: 'Take another photo',
refresh: 'Refresh',
loading: 'Loading uploads...',
error: 'Failed to load uploads. Please try again.',
card: {
pending: 'Waiting for approval',
uploadedAt: 'Uploaded {time}',
justNow: 'Just now',
},
},
lightbox: {
taskLabel: 'Task',
loadingTask: 'Loading task...',
photoAlt: 'Photo {id}{suffix}',
photoAltTaskSuffix: ' - {taskTitle}',
fallbackTitle: 'Task {id}',
unknownTitle: 'Unknown task {id}',
errors: {
notFound: 'Photo not found',
loadFailed: 'Failed to load photo',
},
},
upload: {
cameraTitle: 'Camera',
preparing: 'Preparing task and camera ...',
loadError: {
title: 'Task could not be loaded. You can still take a photo.',
retry: 'Try again',
},
primer: {
title: 'Ready for your shoot?',
body: {
part1: 'Make sure everything is set: check the lighting, clean the lens, and line everyone up in the frame.',
part2: 'You can switch between front and back camera and enable the grid if needed.',
},
dismiss: 'Got it',
},
hud: {
title: 'Live missions',
subtitle: 'Stay in the flow keep the camera ready.',
moodLabel: 'Mood: {mood}',
moodFallback: 'Freestyle',
ctaLabel: 'Open inspiration',
cards: {
online: 'Guests online',
completed: 'Tasks completed',
lastUpload: 'Latest upload',
},
progressLabel: 'Story {count}/{target} active',
liveGuests: '{count} guests live',
relative: {
now: 'Just now',
minutes: '{count} min ago',
hours: '{count} h ago',
days: '{count} days ago',
},
},
limitSummary: {
title: 'Uploads & slots',
subtitle: 'Your event package overview',
badgeLabel: 'Current',
cards: {
photos: {
title: 'Photos total',
remaining: '{remaining} of {limit} free',
unlimited: 'Unlimited photo uploads',
},
guests: {
title: 'Devices in use',
remaining: '{remaining} slots available',
unlimited: 'Unlimited devices enabled',
},
},
badges: {
ok: 'OK',
warning: 'Almost full',
limit_reached: 'Limit reached',
unlimited: 'Unlimited',
},
},
cameraUnsupported: {
title: 'Camera not available',
message: 'Your device does not support live camera preview in this browser. You can upload photos from your gallery instead.',
openGallery: 'Choose photo from gallery',
},
cameraBlocked: {
title: 'Camera blocked by security policy',
message: 'Camera access is blocked by the site security policy. Open the event link in your browser or upload a photo from your gallery.',
hint: 'Tip: If you are in an in-app browser, open the link in Safari/Chrome and reload the page.',
},
cameraDenied: {
title: 'Camera access denied',
explanation: 'Allow camera access to capture photos.',
reopenPrompt: 'Open system dialog again',
chooseFile: 'Choose photo from gallery',
prompt: 'We need access to your camera. Allow the request or pick a photo from your gallery.',
hint: 'Tip: Check your browser settings for camera permissions and reload the page.',
},
cameraError: {
title: 'Camera could not be started',
explanation: 'We could not connect to the camera. Check permissions or restart your device.',
tryAgain: 'Try again',
hint: 'Tip: Close other apps that might be using the camera and try again.',
},
readyOverlay: {
title: 'Camera ready',
message: 'Once everyone is in frame, start the countdown or pick an existing photo.',
start: 'Start countdown',
chooseFile: 'Choose photo',
},
taskInfo: {
countdown: 'Countdown',
emotion: 'Emotion: {value}',
instructionsPrefix: 'Hint',
difficulty: {
easy: 'Easy',
medium: 'Medium',
hard: 'Challenging',
},
timeEstimate: '{count} min',
fallbackTitle: 'Task {id}',
fallbackDescription: 'Capture the moment and share it with everyone.',
fallbackInstructions: 'Line everyone up, start the countdown, and let the emotion shine.',
badge: 'Task #{id}',
},
countdown: {
ready: 'Get ready ...',
},
review: {
retake: 'Retake photo',
keep: 'Use this photo',
readyAnnouncement: 'Photo captured. Please review the preview.',
},
liveShow: {
title: 'Live Show',
description: 'Show this photo on the live screen.',
toggle: 'Enable Live Show',
immediate: 'Shows immediately on the big screen.',
reviewed: 'Shown after approval for the Live Show.',
},
status: {
saving: 'Saving photo...',
processing: 'Processing photo...',
uploading: 'Uploading photo...',
preparing: 'Preparing photo...',
optimizing: 'Optimizing photo...',
completed: 'Upload complete.',
failed: 'Upload failed. Please try again.',
},
demoReadOnly: 'Uploads are disabled in demo mode.',
optimizedNotice: 'We optimized your photo to speed up the upload. Saved: {saved}',
optimizedFallback: 'Could not optimize uploading the original.',
retrying: 'Connection unstable retrying ({attempt}).',
controls: {
toggleGrid: 'Toggle grid',
toggleCountdown: 'Toggle countdown',
toggleMirror: 'Toggle mirroring for front camera',
toggleFlash: 'Toggle flash',
capture: 'Capture photo',
switchCamera: 'Switch camera',
chooseFile: 'Choose photo',
},
limitReached: 'Upload limit reached ({used} / {max} photos). Contact the organizers for an upgrade.',
limitUnlimited: 'unlimited',
limitWarning: 'Only {remaining} of {max} photos left. Please contact the organizers for an upgrade.',
galleryWarningDay: 'Gallery expires in {days} day. Upload your photos soon!',
galleryWarningDays: 'Gallery expires in {days} days. Upload your photos soon!',
packageStatus: {
title: 'Your package status',
subtitle: 'Keep an eye on your remaining allowances.',
badges: {
ok: 'Ready',
warning: 'Heads-up',
limit_reached: 'Limit reached',
unlimited: 'Unlimited',
},
cards: {
photos: {
title: 'Photos',
remaining: '{remaining} of {limit} photo slots left',
unlimited: 'Unlimited photo uploads included',
},
guests: {
title: 'Guests',
remaining: '{remaining} guest slots free (max {limit})',
unlimited: 'Unlimited guests allowed',
},
},
},
dialogs: {
close: 'Got it',
photoLimit: {
title: 'Upload limit reached',
description: '{used} of {limit} photos are already uploaded. Please reach out to upgrade your package.',
hint: 'Tip: Upgrading the package re-enables uploads instantly.',
},
deviceLimit: {
title: 'Device limit reached',
description: 'This device has used all available upload slots.',
hint: 'Try a different device or contact the organizers.',
},
packageMissing: {
title: 'Uploads paused',
description: 'This event is currently not accepting new uploads.',
hint: 'Check in with the organizers for the latest status.',
},
galleryExpired: {
title: 'Gallery closed',
description: 'The gallery has expired and no new uploads can be added.',
hint: 'Only the organizers can extend the gallery window.',
},
csrf: {
title: 'Session expired',
description: 'Refresh the page and try the upload again.',
hint: 'Reload the page to start a new session.',
},
generic: {
title: 'Upload failed',
description: 'We could not complete the upload. Please try again later.',
hint: 'If it keeps happening, reach out to the organizers.',
},
},
errors: {
photoLimit: 'Upload limit reached. Contact the organizers for an upgrade.',
deviceLimit: 'This device reached its upload limit. Please contact the organizers.',
packageMissing: 'This event is not accepting uploads right now.',
galleryExpired: 'The gallery has expired. Uploads are no longer possible.',
generic: 'Upload failed. Please try again.',
tooLargeHint: 'The photo was too large. Please try again — we compress it automatically.',
},
cameraInactive: 'Camera is not active. {hint}',
cameraInactiveHint: 'Tap "{label}" to get started.',
captureError: 'Photo could not be created.',
feedError: 'Camera feed not available. Please restart the camera.',
canvasError: 'Canvas could not be initialised.',
limitCheckError: 'Failed to check upload limits. Upload disabled.',
galleryPickError: 'Selection failed. Please try again.',
captureButton: 'Capture photo',
galleryButton: 'Choose from gallery',
switchCamera: 'Switch camera',
countdownLabel: 'Countdown: {seconds}s',
countdownReady: 'Get ready ...',
buttons: {
startCamera: 'Start camera',
tryAgain: 'Try again',
recheckCamera: 'Recheck access',
},
},
settings: {
title: 'Settings',
subtitle: 'Manage your guest access, legal documents, and local data.',
language: {
title: 'Language',
description: 'Choose your preferred language for this event.',
activeBadge: 'active',
option: {
de: 'German',
en: 'English',
},
},
name: {
title: 'Your name',
description: 'Update how we greet you inside the event. The name is stored locally only.',
label: 'Display name',
placeholder: 'e.g. Anna',
save: 'Save name',
saving: 'Saving...',
reset: 'Reset',
saved: 'Saved',
loading: 'Loading saved name...',
},
haptics: {
title: 'Haptic feedback',
description: 'Short vibrations for likes, uploads, and refreshes.',
label: 'Enable vibrations',
unsupported: 'Not available on this device.',
},
legal: {
title: 'Legal',
description: 'The legally binding documents are always available here.',
loading: 'Loading document...',
error: 'The document could not be loaded. Please try again later.',
fallbackTitle: 'Legal notice',
section: {
impressum: 'Imprint',
privacy: 'Privacy',
terms: 'Terms',
},
},
cache: {
title: 'Offline cache',
description: 'Clear local data if content looks outdated or uploads get stuck.',
clear: 'Clear cache',
clearing: 'Clearing cache...',
cleared: 'Cache cleared.',
note: 'This only affects this browser. Pending uploads may be lost.',
},
help: {
title: 'Help Center',
description: 'Open the help center for guides and quick answers.',
cta: 'Open help center',
},
footer: {
notice: 'Guest area - data is stored locally in the browser.',
},
sheet: {
openLabel: 'Open settings',
backLabel: 'Back',
legalDescription: 'Legal notice',
},
},
help: {
center: {
title: 'Help Center',
subtitle: 'Guides for guests available offline after the first sync.',
searchPlaceholder: 'Search by topic or keyword',
offlineBadge: 'Offline copy',
offlineDescription: 'You are viewing cached content. Go online to refresh articles.',
empty: 'No articles found.',
error: 'Help could not be loaded.',
retry: 'Try again',
listTitle: 'All articles',
},
article: {
back: 'Back to overview',
updated: 'Updated on {date}',
relatedTitle: 'Related articles',
unavailable: 'This article is unavailable.',
reload: 'Reload',
},
},
},
};
export function isLocaleCode(value: string | null | undefined): value is LocaleCode {
return SUPPORTED_LOCALES.some((item) => item.code === value);
}
export function translate(locale: LocaleCode, key: string): string | undefined {
const segments = key.split('.');
const tryLocale = (loc: LocaleCode): string | undefined => {
let current: unknown = messages[loc];
for (const segment of segments) {
if (!current || typeof current !== 'object' || !(segment in current)) {
return undefined;
}
current = (current as NestedMessages)[segment];
}
return typeof current === 'string' ? current : undefined;
};
return tryLocale(locale) ?? tryLocale(DEFAULT_LOCALE);
}