// @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 = { 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: { title: 'Updates', unread: '{count} neu', allRead: 'Alles gelesen', tabUnread: 'Nachrichten', tabUploads: 'Uploads', tabAll: 'Alle Updates', emptyStatus: 'Keine Upload-Hinweise oder Wartungen aktiv.', }, }, 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: { title: 'Updates', unread: '{count} new', allRead: 'All read', tabUnread: 'Messages', tabUploads: 'Uploads', tabAll: 'All updates', emptyStatus: 'No upload status or maintenance active.', }, }, 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); }