feat: localize guest endpoints and caching

This commit is contained in:
Codex Agent
2025-11-12 15:48:06 +01:00
parent d91108c883
commit 062932ce38
19 changed files with 1538 additions and 595 deletions

View File

@@ -3,6 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { ChevronRight } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslation } from '../i18n/useTranslation';
interface Emotion {
id: number;
@@ -33,6 +34,7 @@ export default function EmotionPicker({
const [emotions, setEmotions] = useState<Emotion[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { locale } = useTranslation();
// Fallback emotions (when API not available yet)
const fallbackEmotions: Emotion[] = [
@@ -53,7 +55,12 @@ export default function EmotionPicker({
setError(null);
// Try API first
const response = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}/emotions`);
const response = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}/emotions?locale=${encodeURIComponent(locale)}`, {
headers: {
Accept: 'application/json',
'X-Locale': locale,
},
});
if (response.ok) {
const data = await response.json();
setEmotions(Array.isArray(data) ? data : fallbackEmotions);
@@ -72,7 +79,7 @@ export default function EmotionPicker({
}
fetchEmotions();
}, [eventKey]);
}, [eventKey, locale]);
const handleEmotionSelect = (emotion: Emotion) => {
if (onSelect) {

View File

@@ -1,17 +1,20 @@
import React from 'react';
import { cn } from '@/lib/utils';
import { Sparkles, Flame, UserRound, Camera } from 'lucide-react';
import { useTranslation } from '../i18n/useTranslation';
export type GalleryFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
const filterConfig: Array<{ value: GalleryFilter; label: string; icon: React.ReactNode }> = [
{ value: 'latest', label: 'Neueste', icon: <Sparkles className="h-4 w-4" aria-hidden /> },
{ value: 'popular', label: 'Beliebt', icon: <Flame className="h-4 w-4" aria-hidden /> },
{ value: 'mine', label: 'Meine', icon: <UserRound className="h-4 w-4" aria-hidden /> },
{ value: 'photobooth', label: 'Fotobox', icon: <Camera className="h-4 w-4" aria-hidden /> },
const filterConfig: Array<{ value: GalleryFilter; labelKey: string; icon: React.ReactNode }> = [
{ value: 'latest', labelKey: 'galleryPage.filters.latest', icon: <Sparkles className="h-4 w-4" aria-hidden /> },
{ value: 'popular', labelKey: 'galleryPage.filters.popular', icon: <Flame className="h-4 w-4" aria-hidden /> },
{ value: 'mine', labelKey: 'galleryPage.filters.mine', icon: <UserRound className="h-4 w-4" aria-hidden /> },
{ value: 'photobooth', labelKey: 'galleryPage.filters.photobooth', icon: <Camera className="h-4 w-4" aria-hidden /> },
];
export default function FiltersBar({ value, onChange, className }: { value: GalleryFilter; onChange: (v: GalleryFilter) => void; className?: string }) {
const { t } = useTranslation();
return (
<div
className={cn(
@@ -32,7 +35,7 @@ export default function FiltersBar({ value, onChange, className }: { value: Gall
)}
>
{filter.icon}
{filter.label}
{t(filter.labelKey)}
</button>
))}
</div>

View File

@@ -4,13 +4,15 @@ import { Card, CardContent } from '@/components/ui/card';
import { getDeviceId } from '../lib/device';
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
import { Heart } from 'lucide-react';
import { useTranslation } from '../i18n/useTranslation';
type Props = { token: string };
type PreviewFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
export default function GalleryPreview({ token }: Props) {
const { photos, loading } = usePollGalleryDelta(token);
const { locale } = useTranslation();
const { photos, loading } = usePollGalleryDelta(token, locale);
const [mode, setMode] = React.useState<PreviewFilter>('latest');
const items = React.useMemo(() => {