1522 lines
56 KiB
TypeScript
1522 lines
56 KiB
TypeScript
// @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: {
|
||
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.',
|
||
},
|
||
guest_limit_exceeded: {
|
||
title: 'Gäste-Limit erreicht',
|
||
description: 'Dieses Event hat sein Gäste-Limit erreicht. Bitte wende dich an die Veranstalter:innen.',
|
||
},
|
||
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',
|
||
loadingTitle: 'Artikel wird geladen',
|
||
loadingDescription: 'Wir holen die neuesten Infos für dich.',
|
||
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.',
|
||
},
|
||
guest_limit_exceeded: {
|
||
title: 'Guest limit reached',
|
||
description: 'This event has reached its guest allowance. Please contact the organisers.',
|
||
},
|
||
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',
|
||
loadingTitle: 'Loading article',
|
||
loadingDescription: 'Fetching the latest details for you.',
|
||
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);
|
||
}
|