diff --git a/apps/web/app/[locale]/admin/audit/page.tsx b/apps/web/app/[locale]/admin/audit/page.tsx index c45c646fe..b619f166b 100644 --- a/apps/web/app/[locale]/admin/audit/page.tsx +++ b/apps/web/app/[locale]/admin/audit/page.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { AdminGuard } from '@kit/admin/components/admin-guard'; import { createModuleBuilderApi } from '@kit/module-builder/api'; import { formatDateTime } from '@kit/shared/dates'; @@ -37,6 +39,7 @@ async function AuditPage(props: AdminAuditPageProps) { const searchParams = await props.searchParams; const client = getSupabaseServerAdminClient(); const api = createModuleBuilderApi(client); + const t = await getTranslations('cms.audit'); const page = searchParams.page ? parseInt(searchParams.page, 10) : 1; @@ -52,8 +55,8 @@ async function AuditPage(props: AdminAuditPageProps) { return (
@@ -64,15 +67,25 @@ async function AuditPage(props: AdminAuditPageProps) { /> {/* Results table */} -
- +
+
- - - - - + + + + + @@ -129,7 +142,7 @@ async function AuditPage(props: AdminAuditPageProps) { page={page - 1} action={searchParams.action} table={searchParams.table} - label="Zurück" + label={t('paginationPrevious')} /> )} {page < totalPages && ( @@ -137,7 +150,7 @@ async function AuditPage(props: AdminAuditPageProps) { page={page + 1} action={searchParams.action} table={searchParams.table} - label="Weiter" + label={t('paginationNext')} /> )} diff --git a/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx index 6676ff638..51e39c6db 100644 --- a/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx +++ b/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { createClient } from '@supabase/supabase-js'; import { FileText, Download, Shield, Receipt, FileCheck } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { formatDate } from '@kit/shared/dates'; import { Badge } from '@kit/ui/badge'; @@ -15,6 +16,7 @@ interface Props { export default async function PortalDocumentsPage({ params }: Props) { const { slug } = await params; + const t = await getTranslations('portal'); const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, @@ -28,28 +30,28 @@ export default async function PortalDocumentsPage({ params }: Props) { .eq('slug', slug) .single(); if (!account) - return
Organisation nicht gefunden
; + return
{t('home.orgNotFound')}
; // Demo documents (in production: query invoices + cms_files for this member) const documents = [ { id: '1', title: 'Mitgliedsbeitrag 2026', - type: 'Rechnung', + type: t('documents.typeInvoice'), date: '2026-01-15', status: 'paid', }, { id: '2', title: 'Mitgliedsbeitrag 2025', - type: 'Rechnung', + type: t('documents.typeInvoice'), date: '2025-01-10', status: 'paid', }, { id: '3', title: 'Beitrittserklärung', - type: 'Dokument', + type: t('documents.typeDocument'), date: '2020-01-15', status: 'signed', }, @@ -58,25 +60,24 @@ export default async function PortalDocumentsPage({ params }: Props) { const getStatusBadge = (status: string) => { switch (status) { case 'paid': - return Bezahlt; + return {t('documents.statusPaid')}; case 'open': - return Offen; + return {t('documents.statusOpen')}; case 'signed': - return Unterschrieben; + return {t('documents.statusSigned')}; default: return {status}; } }; const getIcon = (type: string) => { - switch (type) { - case 'Rechnung': - return ; - case 'Dokument': - return ; - default: - return ; + if (type === t('documents.typeInvoice')) { + return ; } + if (type === t('documents.typeDocument')) { + return ; + } + return ; }; return ( @@ -85,29 +86,27 @@ export default async function PortalDocumentsPage({ params }: Props) {
-

Meine Dokumente

+

{t('documents.title')}

- - - +
- Verfügbare Dokumente + {t('documents.available')}

- {String(account.name)} — Dokumente und Rechnungen + {String(account.name)} — {t('documents.subtitle')}

{documents.length === 0 ? (
-

Keine Dokumente vorhanden

+

{t('documents.empty')}

) : (
@@ -129,7 +128,7 @@ export default async function PortalDocumentsPage({ params }: Props) { {getStatusBadge(doc.status)}
diff --git a/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx index 164f46867..e07488dbb 100644 --- a/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx +++ b/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx @@ -4,6 +4,7 @@ import { notFound } from 'next/navigation'; import { createClient } from '@supabase/supabase-js'; import { UserPlus, Shield, CheckCircle } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { formatDate } from '@kit/shared/dates'; import { Button } from '@kit/ui/button'; @@ -22,6 +23,7 @@ export default async function PortalInvitePage({ }: Props) { const { slug } = await params; const { token } = await searchParams; + const t = await getTranslations('portal'); if (!token) notFound(); @@ -51,16 +53,13 @@ export default async function PortalInvitePage({ -

Einladung ungültig

+

{t('invite.invalidTitle')}

- Diese Einladung ist abgelaufen, wurde bereits verwendet oder ist - ungültig. Bitte wenden Sie sich an Ihren Vereinsadministrator. + {t('invite.invalidDesc')}

- - - +
@@ -74,10 +73,9 @@ export default async function PortalInvitePage({ -

Einladung abgelaufen

+

{t('invite.expiredTitle')}

- Diese Einladung ist am {formatDate(invitation.expires_at)}{' '} - abgelaufen. Bitte fordern Sie eine neue Einladung an. + {t('invite.expiredDesc', { date: formatDate(invitation.expires_at) })}

@@ -92,18 +90,14 @@ export default async function PortalInvitePage({
- Einladung zum Mitgliederbereich + {t('invite.title')}

{String(account.name)}

-

- Sie wurden eingeladen, ein Konto für den Mitgliederbereich zu - erstellen. Damit können Sie Ihr Profil einsehen, Dokumente - herunterladen und Ihre Datenschutz-Einstellungen verwalten. -

+

{t('invite.invitedDesc')}

- +

- Ihre E-Mail-Adresse wurde vom Verein vorgegeben. + {t('invite.emailNote')}

- +
- + @@ -151,17 +145,17 @@ export default async function PortalInvitePage({

- Bereits ein Konto?{' '} + {t('invite.hasAccount')}{' '} - Anmelden + {t('invite.login')}

diff --git a/apps/web/app/[locale]/club/[slug]/portal/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/page.tsx index f54a9b211..61d73395d 100644 --- a/apps/web/app/[locale]/club/[slug]/portal/page.tsx +++ b/apps/web/app/[locale]/club/[slug]/portal/page.tsx @@ -3,10 +3,11 @@ import Link from 'next/link'; import { createClient } from '@supabase/supabase-js'; import { UserCircle, FileText, CreditCard, Shield } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { PortalLoginForm } from '@kit/site-builder/components'; import { Button } from '@kit/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Card, CardContent } from '@kit/ui/card'; interface Props { params: Promise<{ slug: string }>; @@ -14,6 +15,7 @@ interface Props { export default async function MemberPortalPage({ params }: Props) { const { slug } = await params; + const t = await getTranslations('portal'); const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, @@ -26,7 +28,7 @@ export default async function MemberPortalPage({ params }: Props) { .eq('slug', slug) .single(); if (!account) - return
Organisation nicht gefunden
; + return
{t('home.orgNotFound')}
; // Check if user is already logged in const { @@ -51,33 +53,31 @@ export default async function MemberPortalPage({ params }: Props) {

- Mitgliederbereich — {String(account.name)} + {t('home.membersArea')} — {String(account.name)}

{String(member.first_name)} {String(member.last_name)} - - - +

- Willkommen, {String(member.first_name)}! + {t('home.welcomeUser', { name: String(member.first_name) })}

-

Mein Profil

+

{t('home.profile')}

- Kontaktdaten und Datenschutz + {t('home.profileDesc')}

@@ -86,9 +86,9 @@ export default async function MemberPortalPage({ params }: Props) { -

Dokumente

+

{t('home.documents')}

- Rechnungen und Bescheinigungen + {t('home.documentsDesc')}

@@ -96,9 +96,9 @@ export default async function MemberPortalPage({ params }: Props) { -

Mitgliedsausweis

+

{t('home.memberCard')}

- Digital anzeigen + {t('home.memberCardDesc')}

@@ -114,12 +114,10 @@ export default async function MemberPortalPage({ params }: Props) {
-

Mitgliederbereich

- - - +

{t('home.membersArea')}

+
diff --git a/apps/web/app/[locale]/club/[slug]/portal/profile/_components/portal-linked-accounts.tsx b/apps/web/app/[locale]/club/[slug]/portal/profile/_components/portal-linked-accounts.tsx index 761e8c0a2..af847c91b 100644 --- a/apps/web/app/[locale]/club/[slug]/portal/profile/_components/portal-linked-accounts.tsx +++ b/apps/web/app/[locale]/club/[slug]/portal/profile/_components/portal-linked-accounts.tsx @@ -6,6 +6,7 @@ import type { Provider, UserIdentity } from '@supabase/supabase-js'; import { createClient } from '@supabase/supabase-js'; import { Link2, Link2Off, Loader2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { AlertDialog, @@ -39,6 +40,7 @@ function getSupabaseClient() { } export function PortalLinkedAccounts({ slug }: { slug: string }) { + const t = useTranslations('portal'); const [identities, setIdentities] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(null); @@ -177,22 +179,18 @@ export function PortalLinkedAccounts({ slug }: { slug: string }) { - Konto trennen? + {t('linkedAccounts.title')} - Möchten Sie die Verknüpfung mit{' '} - {PROVIDER_LABELS[identity.provider] ?? - identity.provider}{' '} - wirklich aufheben? Sie können sich dann nicht mehr - darüber anmelden. + {t('linkedAccounts.disconnectDesc')} - Abbrechen + {t('linkedAccounts.cancel')} handleUnlink(identity)} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > - Trennen + {t('linkedAccounts.disconnect')} @@ -207,7 +205,7 @@ export function PortalLinkedAccounts({ slug }: { slug: string }) { {availableProviders.length > 0 && (

- Konto verknüpfen für schnellere Anmeldung + {t('linkedAccounts.connect')}

diff --git a/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx index f809a1c59..7cf495b14 100644 --- a/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx +++ b/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx @@ -7,11 +7,10 @@ import { UserCircle, Mail, MapPin, - Phone, Shield, - Calendar, Link2, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { formatDate } from '@kit/shared/dates'; import { Button } from '@kit/ui/button'; @@ -27,6 +26,7 @@ interface Props { export default async function PortalProfilePage({ params }: Props) { const { slug } = await params; + const t = await getTranslations('portal'); const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, @@ -39,7 +39,7 @@ export default async function PortalProfilePage({ params }: Props) { .eq('slug', slug) .single(); if (!account) - return
Organisation nicht gefunden
; + return
{t('home.orgNotFound')}
; // Get current user const { @@ -61,17 +61,13 @@ export default async function PortalProfilePage({ params }: Props) { -

Kein Mitglied

+

{t('profile.noMemberTitle')}

- Ihr Benutzerkonto ist nicht mit einem Mitgliedsprofil in diesem - Verein verknüpft. Bitte wenden Sie sich an Ihren - Vereinsadministrator. + {t('profile.noMemberDesc')}

- - - +
@@ -86,13 +82,11 @@ export default async function PortalProfilePage({ params }: Props) {
-

Mein Profil

+

{t('profile.title')}

- - - +
@@ -108,8 +102,10 @@ export default async function PortalProfilePage({ params }: Props) { {String(m.first_name)} {String(m.last_name)}

- Nr. {String(m.member_number ?? '—')} — Mitglied seit{' '} - {formatDate(m.entry_date)} + {t('profile.memberSince', { + number: String(m.member_number ?? '—'), + date: formatDate(m.entry_date), + })}

@@ -120,28 +116,28 @@ export default async function PortalProfilePage({ params }: Props) { - Kontaktdaten + {t('profile.contactData')}
- +
- +
- +
- +
- +
@@ -151,24 +147,24 @@ export default async function PortalProfilePage({ params }: Props) { - Adresse + {t('profile.address')}
- +
- +
- +
- +
@@ -178,7 +174,7 @@ export default async function PortalProfilePage({ params }: Props) { - Anmeldemethoden + {t('profile.loginMethods')} @@ -190,29 +186,29 @@ export default async function PortalProfilePage({ params }: Props) { - Datenschutz-Einwilligungen + {t('profile.privacy')} {[ { key: 'gdpr_newsletter', - label: 'Newsletter per E-Mail', + label: t('profile.gdprNewsletter'), value: m.gdpr_newsletter, }, { key: 'gdpr_internet', - label: 'Veröffentlichung auf der Homepage', + label: t('profile.gdprInternet'), value: m.gdpr_internet, }, { key: 'gdpr_print', - label: 'Veröffentlichung in der Vereinszeitung', + label: t('profile.gdprPrint'), value: m.gdpr_print, }, { key: 'gdpr_birthday_info', - label: 'Geburtstagsinfo an Mitglieder', + label: t('profile.gdprBirthday'), value: m.gdpr_birthday_info, }, ].map(({ key, label, value }) => ( @@ -229,7 +225,7 @@ export default async function PortalProfilePage({ params }: Props) {
- +
diff --git a/apps/web/app/[locale]/home/[account]/bookings/[bookingId]/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/[bookingId]/page.tsx index d2bc4a83f..14c4bf3b7 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/[bookingId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/[bookingId]/page.tsx @@ -9,8 +9,9 @@ import { XCircle, User, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; -import { formatDate } from '@kit/shared/dates'; +import { formatDate, formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -24,35 +25,19 @@ import { import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; +import { + BOOKING_STATUS_VARIANT as STATUS_BADGE_VARIANT, + BOOKING_STATUS_LABEL_KEYS as STATUS_LABEL_KEYS, +} from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string; bookingId: string }>; } -const STATUS_BADGE_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - pending: 'secondary', - confirmed: 'default', - checked_in: 'info', - checked_out: 'outline', - cancelled: 'destructive', - no_show: 'destructive', -}; - -const STATUS_LABEL: Record = { - pending: 'Ausstehend', - confirmed: 'Bestätigt', - checked_in: 'Eingecheckt', - checked_out: 'Ausgecheckt', - cancelled: 'Storniert', - no_show: 'Nicht erschienen', -}; - export default async function BookingDetailPage({ params }: PageProps) { const { account, bookingId } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('bookings'); const { data: acct } = await client .from('accounts') @@ -62,7 +47,7 @@ export default async function BookingDetailPage({ params }: PageProps) { if (!acct) { return ( - + ); @@ -78,17 +63,17 @@ export default async function BookingDetailPage({ params }: PageProps) { if (!booking) { return ( - +

- Buchung mit ID "{bookingId}" wurde nicht gefunden. + {t('detail.notFoundDesc', { id: bookingId })}

- - - + {t('detail.backToBookings')} + +
); @@ -109,20 +94,20 @@ export default async function BookingDetailPage({ params }: PageProps) { const status = String(booking.status ?? 'pending'); return ( - +
{/* Header */}
- - - +
- {STATUS_LABEL[status] ?? status} + {t(STATUS_LABEL_KEYS[status] ?? status)}

ID: {bookingId}

@@ -131,12 +116,12 @@ export default async function BookingDetailPage({ params }: PageProps) {
- {/* Zimmer */} + {/* Room */} - Zimmer + {t('detail.room')} @@ -144,7 +129,7 @@ export default async function BookingDetailPage({ params }: PageProps) {
- Zimmernummer + {t('detail.roomNumber')} {String(room.room_number)} @@ -153,13 +138,15 @@ export default async function BookingDetailPage({ params }: PageProps) { {room.name && (
- Name + {t('rooms.name')} {String(room.name)}
)}
- Typ + + {t('detail.type')} + {String(room.room_type ?? '—')} @@ -167,25 +154,27 @@ export default async function BookingDetailPage({ params }: PageProps) {
) : (

- Kein Zimmer zugewiesen + {t('detail.noRoom')}

)} - {/* Gast */} + {/* Guest */} - Gast + {t('detail.guest')} {guest ? (
- Name + + {t('guests.name')} + {String(guest.first_name)} {String(guest.last_name)} @@ -193,7 +182,7 @@ export default async function BookingDetailPage({ params }: PageProps) { {guest.email && (
- E-Mail + {t('detail.email')} {String(guest.email)}
@@ -201,7 +190,7 @@ export default async function BookingDetailPage({ params }: PageProps) { {guest.phone && (
- Telefon + {t('detail.phone')} {String(guest.phone)}
@@ -209,25 +198,25 @@ export default async function BookingDetailPage({ params }: PageProps) {
) : (

- Kein Gast zugewiesen + {t('detail.noGuest')}

)} - {/* Aufenthalt */} + {/* Stay */} - Aufenthalt + {t('detail.stay')}
- Check-in + {t('list.checkIn')} {formatDate(booking.check_in)} @@ -235,7 +224,7 @@ export default async function BookingDetailPage({ params }: PageProps) {
- Check-out + {t('list.checkOut')} {formatDate(booking.check_out)} @@ -243,39 +232,41 @@ export default async function BookingDetailPage({ params }: PageProps) {
- Erwachsene + {t('detail.adults')} {booking.adults ?? '—'}
- Kinder + + {t('detail.children')} + {booking.children ?? 0}
- {/* Betrag */} + {/* Amount */} - Betrag + {t('detail.amount')}
- Gesamtpreis + {t('detail.totalPrice')} {booking.total_price != null - ? `${Number(booking.total_price).toFixed(2)} €` + ? formatCurrencyAmount(booking.total_price as number) : '—'}
{booking.notes && (
- Notizen + {t('detail.notes')}

{String(booking.notes)}

@@ -288,22 +279,22 @@ export default async function BookingDetailPage({ params }: PageProps) { {/* Status Workflow */} - Aktionen - Status der Buchung ändern + {t('detail.actions')} + {t('detail.changeStatus')}
{(status === 'pending' || status === 'confirmed') && ( )} {status === 'checked_in' && ( )} @@ -312,15 +303,18 @@ export default async function BookingDetailPage({ params }: PageProps) { status !== 'no_show' && ( )} {status === 'cancelled' || status === 'checked_out' ? (

- Diese Buchung ist{' '} - {status === 'cancelled' ? 'storniert' : 'abgeschlossen'} — - keine weiteren Aktionen verfügbar. + {t('detail.noMoreActions', { + statusLabel: + status === 'cancelled' + ? t('detail.cancelledStatus') + : t('detail.completedStatus'), + })}

) : null}
diff --git a/apps/web/app/[locale]/home/[account]/bookings/calendar/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/calendar/page.tsx index df3e7e21b..d0d81464f 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/calendar/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/calendar/page.tsx @@ -7,6 +7,7 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { getTranslations } from 'next-intl/server'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; @@ -52,6 +53,7 @@ function isDateInRange( export default async function BookingCalendarPage({ params }: PageProps) { const { account } = await params; + const t = await getTranslations('bookings'); const client = getSupabaseServerClient(); const { data: acct } = await client @@ -62,7 +64,7 @@ export default async function BookingCalendarPage({ params }: PageProps) { if (!acct) { return ( - + ); @@ -132,18 +134,18 @@ export default async function BookingCalendarPage({ params }: PageProps) { } return ( - +
{/* Header */}
- - - +

- Zimmerauslastung im Überblick + {t('calendar.subtitle')}

@@ -152,14 +154,14 @@ export default async function BookingCalendarPage({ params }: PageProps) {
- {MONTH_NAMES[month]} {year} -
@@ -205,15 +207,15 @@ export default async function BookingCalendarPage({ params }: PageProps) {
- Belegt + {t('calendar.occupied')}
- Frei + {t('calendar.free')}
- Heute + {t('calendar.today')}
@@ -225,12 +227,12 @@ export default async function BookingCalendarPage({ params }: PageProps) {

- Buchungen in diesem Monat + {t('calendar.bookingsThisMonth')}

{bookings.data.length}

- {occupiedDates.size} von {daysInMonth} Tagen belegt + {t('calendar.daysOccupied', { occupied: occupiedDates.size, total: daysInMonth })}
diff --git a/apps/web/app/[locale]/home/[account]/bookings/cancel-booking-button.tsx b/apps/web/app/[locale]/home/[account]/bookings/cancel-booking-button.tsx new file mode 100644 index 000000000..83b40d058 --- /dev/null +++ b/apps/web/app/[locale]/home/[account]/bookings/cancel-booking-button.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { useTransition } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { XCircle } from 'lucide-react'; +import { useTranslations } from 'next-intl'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@kit/ui/alert-dialog'; +import { Button } from '@kit/ui/button'; + +interface CancelBookingButtonProps { + bookingId: string; + accountId: string; +} + +export function CancelBookingButton({ + bookingId, + accountId, +}: CancelBookingButtonProps) { + const router = useRouter(); + const t = useTranslations('bookings'); + const [isPending, startTransition] = useTransition(); + + const handleCancel = () => { + startTransition(async () => { + try { + const response = await fetch(`/api/bookings/${bookingId}/cancel`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accountId }), + }); + + if (response.ok) { + router.refresh(); + } + } catch (error) { + console.error('Failed to cancel booking:', error); + } + }); + }; + + return ( + + + + ); +} diff --git a/apps/web/app/[locale]/home/[account]/bookings/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/page.tsx index 941263e9a..d77333e65 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/page.tsx @@ -1,9 +1,10 @@ import Link from 'next/link'; import { BedDouble, CalendarCheck, Plus, Euro, Search } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createBookingManagementApi } from '@kit/booking-management/api'; -import { formatDate } from '@kit/shared/dates'; +import { formatDate, formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -14,6 +15,10 @@ import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; +import { + BOOKING_STATUS_VARIANT as STATUS_BADGE_VARIANT, + BOOKING_STATUS_LABEL_KEYS as STATUS_LABEL_KEYS, +} from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string }>; @@ -22,26 +27,6 @@ interface PageProps { const PAGE_SIZE = 25; -const STATUS_BADGE_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - pending: 'secondary', - confirmed: 'default', - checked_in: 'info', - checked_out: 'outline', - cancelled: 'destructive', -}; - -const STATUS_LABEL: Record = { - pending: 'Ausstehend', - confirmed: 'Bestätigt', - checked_in: 'Eingecheckt', - checked_out: 'Ausgecheckt', - cancelled: 'Storniert', - no_show: 'Nicht erschienen', -}; - export default async function BookingsPage({ params, searchParams, @@ -49,6 +34,7 @@ export default async function BookingsPage({ const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('bookings'); const { data: acct } = await client .from('accounts') @@ -58,7 +44,7 @@ export default async function BookingsPage({ if (!acct) { return ( - + ); @@ -83,8 +69,7 @@ export default async function BookingsPage({ const { data: bookingsRaw, count: bookingsTotal } = await bookingsQuery; - /* eslint-disable @typescript-eslint/no-explicit-any */ - let bookingsData = (bookingsRaw ?? []) as Array>; + let bookingsData = (bookingsRaw ?? []) as Array>; const total = bookingsTotal ?? 0; // Post-filter by search query (guest name or room name/number) @@ -114,36 +99,34 @@ export default async function BookingsPage({ const totalPages = Math.ceil(total / PAGE_SIZE); return ( - +
{/* Header */}
-

- Zimmer und Buchungen verwalten -

+

{t('list.manage')}

- - - + {t('list.newBooking')} + +
{/* Stats */}
} /> } /> } /> @@ -152,23 +135,25 @@ export default async function BookingsPage({ {/* Search */}
- +
{searchQuery && ( - - - + )} @@ -176,17 +161,13 @@ export default async function BookingsPage({ {bookingsData.length === 0 ? ( } - title={ - searchQuery - ? 'Keine Buchungen gefunden' - : 'Keine Buchungen vorhanden' - } + title={searchQuery ? t('list.noResults') : t('list.noBookings')} description={ searchQuery - ? `Keine Ergebnisse für „${searchQuery}".` - : 'Erstellen Sie Ihre erste Buchung, um loszulegen.' + ? t('list.noResultsFor', { query: searchQuery }) + : t('list.createFirst') } - actionLabel={searchQuery ? undefined : 'Neue Buchung'} + actionLabel={searchQuery ? undefined : t('list.newBooking')} actionHref={ searchQuery ? undefined : `/home/${account}/bookings/new` } @@ -196,21 +177,33 @@ export default async function BookingsPage({ {searchQuery - ? `Ergebnisse (${bookingsData.length})` - : `Alle Buchungen (${total})`} + ? t('list.searchResults', { count: bookingsData.length }) + : t('list.allBookings', { count: total })} -
-
ZeitpunktAktionTabelleDatensatz-IDBenutzer-ID + {t('timestamp')} + + {t('action')} + + {t('table')} + + Datensatz-ID + + Benutzer-ID +
+
+
- - - - - - + + + + + + @@ -245,10 +238,10 @@ export default async function BookingsPage({ : '—'} @@ -277,30 +271,35 @@ export default async function BookingsPage({ {totalPages > 1 && !searchQuery && (

- Seite {page} von {totalPages} ({total} Einträge) + {t('common.page')} {page} {t('common.of')} {totalPages} ( + {total} {t('common.entries')})

{page > 1 ? ( - - - + ) : ( )} {page < totalPages ? ( - - - + ) : ( )}
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx index aeea7ca5d..ee776fc6f 100644 --- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx @@ -9,9 +9,10 @@ import { Clock, Pencil, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; -import { formatDate } from '@kit/shared/dates'; +import { formatDate, formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -19,6 +20,10 @@ import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; +import { + COURSE_STATUS_VARIANT, + COURSE_STATUS_LABEL_KEYS, +} from '~/lib/status-badges'; import { CreateSessionDialog } from './create-session-dialog'; import { DeleteCourseButton } from './delete-course-button'; @@ -27,29 +32,11 @@ interface PageProps { params: Promise<{ account: string; courseId: string }>; } -const STATUS_LABEL: Record = { - planned: 'Geplant', - open: 'Offen', - running: 'Laufend', - completed: 'Abgeschlossen', - cancelled: 'Abgesagt', -}; - -const STATUS_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - planned: 'secondary', - open: 'default', - running: 'info', - completed: 'outline', - cancelled: 'destructive', -}; - export default async function CourseDetailPage({ params }: PageProps) { const { account, courseId } = await params; const client = getSupabaseServerClient(); const api = createCourseManagementApi(client); + const t = await getTranslations('courses'); const [course, participants, sessions] = await Promise.all([ api.getCourse(courseId), @@ -69,7 +56,7 @@ export default async function CourseDetailPage({ params }: PageProps) { @@ -81,7 +68,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
-

Name

+

+ {t('detail.name')} +

{String(courseData.name)}

@@ -90,14 +79,17 @@ export default async function CourseDetailPage({ params }: PageProps) {
-

Status

+

+ {t('common.status')} +

- {STATUS_LABEL[String(courseData.status)] ?? - String(courseData.status)} + {t(COURSE_STATUS_LABEL_KEYS[String(courseData.status)] ?? + String(courseData.status))}
@@ -106,7 +98,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
-

Dozent

+

+ {t('detail.instructor')} +

{String(courseData.instructor_id ?? '—')}

@@ -117,7 +111,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
-

Beginn – Ende

+

+ {t('detail.dateRange')} +

{formatDate(courseData.start_date as string)} {' – '} @@ -130,10 +126,10 @@ export default async function CourseDetailPage({ params }: PageProps) {

-

Gebühr

+

{t('list.fee')}

{courseData.fee != null - ? `${Number(courseData.fee).toFixed(2)} €` + ? formatCurrencyAmount(courseData.fee as number) : '—'}

@@ -143,7 +139,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
-

Teilnehmer

+

+ {t('detail.participants')} +

{participants.length} / {String(courseData.capacity ?? '∞')}

@@ -152,25 +150,33 @@ export default async function CourseDetailPage({ params }: PageProps) {
- {/* Teilnehmer Section */} + {/* Participants Section */} - Teilnehmer - - - + {t('detail.participants')} + -
-
ZimmerGastAnreiseAbreiseStatusBetrag + {t('list.room')} + + {t('list.guest')} + + {t('list.checkIn')} + + {t('list.checkOut')} + + {t('list.status')} + + {t('list.amount')} +
- {formatDate(booking.check_in)} + {formatDate(booking.check_in as string)} - {formatDate(booking.check_out)} + {formatDate(booking.check_out as string)} - {STATUS_LABEL[String(booking.status)] ?? - String(booking.status)} + {t(STATUS_LABEL_KEYS[String(booking.status)] ?? String(booking.status))} {booking.total_price != null - ? `${Number(booking.total_price).toFixed(2)} €` + ? formatCurrencyAmount( + booking.total_price as number, + ) : '—'}
+
+
- - - - + + + + @@ -180,7 +186,7 @@ export default async function CourseDetailPage({ params }: PageProps) { colSpan={4} className="text-muted-foreground p-6 text-center" > - Keine Teilnehmer + {t('detail.noParticipants')} ) : ( @@ -211,28 +217,36 @@ export default async function CourseDetailPage({ params }: PageProps) { - {/* Termine Section */} + {/* Sessions Section */} - Termine + {t('detail.sessions')}
- - - +
-
-
NameE-MailStatusDatum + {t('detail.name')} + + {t('detail.email')} + + {t('common.status')} + + {t('detail.date')} +
+
+
- - - - + + + + @@ -242,7 +256,7 @@ export default async function CourseDetailPage({ params }: PageProps) { colSpan={4} className="text-muted-foreground p-6 text-center" > - Keine Termine + {t('detail.noSessions')} ) : ( @@ -258,7 +272,9 @@ export default async function CourseDetailPage({ params }: PageProps) {
DatumBeginnEndeAbgesagt? + {t('detail.date')} + + {t('list.startDate')} + + {t('list.endDate')} + + {t('detail.cancelled')} +
{String(s.end_time ?? '—')} {s.cancelled ? ( - Ja + + {t('common.yes')} + ) : ( '—' )} diff --git a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx index 7fcbc60e3..b036fc6d1 100644 --- a/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/calendar/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { ArrowLeft, ChevronLeft, ChevronRight } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; import { formatDate } from '@kit/shared/dates'; @@ -17,23 +18,6 @@ interface PageProps { searchParams: Promise>; } -const WEEKDAYS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; - -const MONTH_NAMES = [ - 'Januar', - 'Februar', - 'März', - 'April', - 'Mai', - 'Juni', - 'Juli', - 'August', - 'September', - 'Oktober', - 'November', - 'Dezember', -]; - function getDaysInMonth(year: number, month: number): number { return new Date(year, month + 1, 0).getDate(); } @@ -50,6 +34,7 @@ export default async function CourseCalendarPage({ const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -134,18 +119,22 @@ export default async function CourseCalendarPage({ courseItem.status === 'open' || courseItem.status === 'running', ); + // Use translation arrays for weekdays and months + const WEEKDAYS = t.raw('calendar.weekdays') as string[]; + const MONTH_NAMES = t.raw('calendar.months') as string[]; + return ( - +
{/* Header */}
- - - -

Kurstermine im Überblick

+ +

{t('calendar.overview')}

@@ -153,31 +142,31 @@ export default async function CourseCalendarPage({
- - - + {MONTH_NAMES[month]} {year} - - - +
@@ -222,15 +211,15 @@ export default async function CourseCalendarPage({
- Kurstag + {t('calendar.courseDay')}
- Frei + {t('calendar.free')}
- Heute + {t('calendar.today')}
@@ -239,12 +228,14 @@ export default async function CourseCalendarPage({ {/* Active Courses this Month */} - Aktive Kurse ({activeCourses.length}) + + {t('calendar.activeCourses', { count: activeCourses.length })} + {activeCourses.length === 0 ? (

- Keine aktiven Kurse in diesem Monat. + {t('calendar.noActiveCourses')}

) : (
@@ -271,8 +262,8 @@ export default async function CourseCalendarPage({ } > {String(course.status) === 'running' - ? 'Laufend' - : 'Offen'} + ? t('status.running') + : t('status.open')}
))} diff --git a/apps/web/app/[locale]/home/[account]/courses/page.tsx b/apps/web/app/[locale]/home/[account]/courses/page.tsx index d8d8e22d3..3f600eaaf 100644 --- a/apps/web/app/[locale]/home/[account]/courses/page.tsx +++ b/apps/web/app/[locale]/home/[account]/courses/page.tsx @@ -9,9 +9,10 @@ import { Calendar, Euro, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createCourseManagementApi } from '@kit/course-management/api'; -import { formatDate } from '@kit/shared/dates'; +import { formatDate, formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -24,7 +25,7 @@ import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; import { COURSE_STATUS_VARIANT, - COURSE_STATUS_LABEL, + COURSE_STATUS_LABEL_KEYS, } from '~/lib/status-badges'; interface PageProps { @@ -38,6 +39,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('courses'); const { data: acct } = await client .from('accounts') @@ -63,39 +65,41 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { const totalPages = Math.ceil(courses.total / PAGE_SIZE); return ( - +
{/* Header */}
-

Kursangebot verwalten

+

+ {t('pages.coursesDescription')} +

- - - + {t('nav.newCourse')} + +
{/* Stats */}
} /> } /> } /> } /> @@ -103,18 +107,18 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { {/* Search & Filters */} } - title="Keine Kurse vorhanden" - description="Erstellen Sie Ihren ersten Kurs, um loszulegen." - actionLabel="Neuer Kurs" + title={t('list.noCourses')} + description={t('list.createFirst')} + actionLabel={t('nav.newCourse')} actionHref={`/home/${account}/courses/new`} /> ) : ( - Alle Kurse ({courses.total}) + {t('list.title', { count: courses.total })} -
- +
+
- - - - - - - + + + + + + + @@ -178,8 +196,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { 'secondary' } > - {COURSE_STATUS_LABEL[String(course.status)] ?? - String(course.status)} + {t(COURSE_STATUS_LABEL_KEYS[String(course.status)] ?? String(course.status))} @@ -202,33 +219,38 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { {totalPages > 1 && (

- Seite {page} von {totalPages} ({courses.total} Einträge) + {t('common.page')} {page} {t('common.of')} {totalPages} ( + {courses.total} {t('common.entries')})

{page > 1 ? ( - - - + {t('common.previous')} + + ) : ( )} {page < totalPages ? ( - - - + + ) : ( )} diff --git a/apps/web/app/[locale]/home/[account]/error.tsx b/apps/web/app/[locale]/home/[account]/error.tsx new file mode 100644 index 000000000..059cc644a --- /dev/null +++ b/apps/web/app/[locale]/home/[account]/error.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { useEffect } from 'react'; + +import Link from 'next/link'; + +import { AlertTriangle } from 'lucide-react'; +import { useTranslations } from 'next-intl'; + +import { Button } from '@kit/ui/button'; + +export default function AccountError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + const t = useTranslations('common'); + + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+
+ +
+

{t('error.title')}

+

+ {t('error.description')} +

+
+ + +
+
+ ); +} diff --git a/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx b/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx index c73c4f696..610ad7b99 100644 --- a/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/events/[eventId]/page.tsx @@ -8,6 +8,7 @@ import { Pencil, UserPlus, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createEventManagementApi } from '@kit/event-management/api'; import { formatDate } from '@kit/shared/dates'; @@ -17,6 +18,7 @@ import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; import { CmsPageShell } from '~/components/cms-page-shell'; +import { EVENT_STATUS_LABEL_KEYS, EVENT_STATUS_VARIANT } from '~/lib/status-badges'; import { DeleteEventButton } from './delete-event-button'; @@ -24,38 +26,18 @@ interface PageProps { params: Promise<{ account: string; eventId: string }>; } -const STATUS_LABEL: Record = { - draft: 'Entwurf', - published: 'Veröffentlicht', - registration_open: 'Anmeldung offen', - registration_closed: 'Anmeldung geschlossen', - cancelled: 'Abgesagt', - completed: 'Abgeschlossen', -}; - -const STATUS_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - draft: 'secondary', - published: 'default', - registration_open: 'info', - registration_closed: 'outline', - cancelled: 'destructive', - completed: 'outline', -}; - export default async function EventDetailPage({ params }: PageProps) { const { account, eventId } = await params; const client = getSupabaseServerClient(); const api = createEventManagementApi(client); + const t = await getTranslations('cms.events'); const [event, registrations] = await Promise.all([ api.getEvent(eventId), api.getRegistrations(eventId), ]); - if (!event) return
Veranstaltung nicht gefunden
; + if (!event) return
{t('notFound')}
; const eventData = event as Record; @@ -67,7 +49,7 @@ export default async function EventDetailPage({ params }: PageProps) { @@ -76,18 +58,19 @@ export default async function EventDetailPage({ params }: PageProps) { {/* Header */}
-

{String(eventData.name)}

- {STATUS_LABEL[String(eventData.status)] ?? - String(eventData.status)} + {t(EVENT_STATUS_LABEL_KEYS[String(eventData.status)] ?? + String(eventData.status))}
@@ -97,7 +80,7 @@ export default async function EventDetailPage({ params }: PageProps) {
-

Datum

+

{t('date')}

{formatDate(eventData.event_date as string)}

@@ -108,7 +91,7 @@ export default async function EventDetailPage({ params }: PageProps) {
-

Uhrzeit

+

{t('time')}

{String(eventData.start_time ?? '—')} –{' '} {String(eventData.end_time ?? '—')} @@ -120,7 +103,7 @@ export default async function EventDetailPage({ params }: PageProps) {

-

Ort

+

{t('location')}

{String(eventData.location ?? '—')}

@@ -131,7 +114,9 @@ export default async function EventDetailPage({ params }: PageProps) {
-

Anmeldungen

+

+ {t('registrations')} +

{registrations.length} / {String(eventData.capacity ?? '∞')}

@@ -144,7 +129,7 @@ export default async function EventDetailPage({ params }: PageProps) { {eventData.description ? ( - Beschreibung + {t('description')}

@@ -157,22 +142,32 @@ export default async function EventDetailPage({ params }: PageProps) { {/* Registrations Table */} - Anmeldungen ({registrations.length}) + + {t('registrationsCount', { count: registrations.length })} + {registrations.length === 0 ? (

- Noch keine Anmeldungen + {t('noRegistrations')}

) : ( -
-
Kursnr.NameBeginnEndeStatusKapazitätGebühr + {t('list.courseNumber')} + + {t('list.courseName')} + + {t('list.startDate')} + + {t('list.endDate')} + + {t('list.status')} + + {t('list.capacity')} + + {t('list.fee')} +
@@ -189,7 +206,7 @@ export default async function CoursesPage({ params, searchParams }: PageProps) { {course.fee != null - ? `${Number(course.fee).toFixed(2)} €` + ? formatCurrencyAmount(course.fee as number) : '—'}
+
+
- - - - + + + + diff --git a/apps/web/app/[locale]/home/[account]/events/page.tsx b/apps/web/app/[locale]/home/[account]/events/page.tsx index 8572c7689..beba16cbd 100644 --- a/apps/web/app/[locale]/home/[account]/events/page.tsx +++ b/apps/web/app/[locale]/home/[account]/events/page.tsx @@ -21,7 +21,7 @@ import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; -import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL } from '~/lib/status-badges'; +import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL_KEYS } from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string }>; @@ -71,16 +71,15 @@ export default async function EventsPage({ params, searchParams }: PageProps) { {/* Header */}
-

{t('title')}

{t('description')}

- - - + +
{/* Stats */} @@ -119,24 +118,26 @@ export default async function EventsPage({ params, searchParams }: PageProps) { -
-
NameE-MailElternteilDatum + {t('name')} + + E-Mail + + {t('parentName')} + + {t('date')} +
+
+
- - + - - - - @@ -177,8 +178,7 @@ export default async function EventsPage({ params, searchParams }: PageProps) { 'secondary' } > - {EVENT_STATUS_LABEL[String(event.status)] ?? - String(event.status)} + {t(EVENT_STATUS_LABEL_KEYS[String(event.status)] ?? String(event.status))}
{t('name')} + + {t('name')} + {t('eventDate')} + {t('eventLocation')} + {t('capacity')} + {t('status')} + {t('registrations')}
@@ -202,24 +202,24 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
{events.page > 1 && ( - - - + + )} {events.page < events.totalPages && ( - - - + + )}
diff --git a/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx b/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx index cfdfd6fd3..7df203773 100644 --- a/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx +++ b/apps/web/app/[locale]/home/[account]/events/registrations/page.tsx @@ -13,7 +13,7 @@ import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; -import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL } from '~/lib/status-badges'; +import { EVENT_STATUS_VARIANT, EVENT_STATUS_LABEL_KEYS } from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string }>; @@ -63,7 +63,6 @@ export default async function EventRegistrationsPage({ params }: PageProps) {
{/* Header */}
-

{t('registrations')}

{t('registrationsOverview')}

@@ -103,26 +102,26 @@ export default async function EventRegistrationsPage({ params }: PageProps) { -
- +
+
- - - - - - @@ -157,7 +156,7 @@ export default async function EventRegistrationsPage({ params }: PageProps) { 'secondary' } > - {EVENT_STATUS_LABEL[event.status] ?? event.status} + {t(EVENT_STATUS_LABEL_KEYS[event.status] ?? event.status)}
+ {t('event')} + {t('eventDate')} + {t('status')} + {t('capacity')} + {t('registrations')} + {t('utilization')}
diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx index e6c898b2e..f22e3bdf0 100644 --- a/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/invoices/[id]/page.tsx @@ -1,40 +1,27 @@ import Link from 'next/link'; -import { ArrowLeft, Send, CheckCircle } from 'lucide-react'; +import { ArrowLeft } from 'lucide-react'; import { createFinanceApi } from '@kit/finance/api'; import { formatDate } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; -import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { getTranslations } from 'next-intl/server'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; +import { + INVOICE_STATUS_VARIANT, + INVOICE_STATUS_LABEL_KEYS, +} from '~/lib/status-badges'; + +import { MarkPaidButton, SendInvoiceButton } from '../invoice-action-buttons'; interface PageProps { params: Promise<{ account: string; id: string }>; } -const STATUS_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - draft: 'secondary', - sent: 'default', - paid: 'info', - overdue: 'destructive', - cancelled: 'destructive', -}; - -const STATUS_LABEL: Record = { - draft: 'Entwurf', - sent: 'Versendet', - paid: 'Bezahlt', - overdue: 'Überfällig', - cancelled: 'Storniert', -}; - const formatCurrency = (amount: unknown) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format( Number(amount), @@ -42,6 +29,7 @@ const formatCurrency = (amount: unknown) => export default async function InvoiceDetailPage({ params }: PageProps) { const { account, id } = await params; + const t = await getTranslations('finance'); const client = getSupabaseServerClient(); const { data: acct } = await client @@ -61,7 +49,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) { const items = (invoice.items ?? []) as Array>; return ( - +
{/* Back link */}
@@ -70,7 +58,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) { className="text-muted-foreground hover:text-foreground inline-flex items-center text-sm" > - Zurück zu Rechnungen + {t('invoices.backToList')}
@@ -78,17 +66,17 @@ export default async function InvoiceDetailPage({ params }: PageProps) { - Rechnung {String(invoice.invoice_number ?? '')} + {t('invoices.invoiceLabel', { number: String(invoice.invoice_number ?? '') })} - - {STATUS_LABEL[status] ?? status} + + {t(INVOICE_STATUS_LABEL_KEYS[status] ?? status)}
- Empfänger + {t('invoices.recipient')}
{String(invoice.recipient_name ?? '—')} @@ -96,7 +84,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- Rechnungsdatum + {t('invoices.issueDate')}
{formatDate(invoice.issue_date)} @@ -104,7 +92,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- Fälligkeitsdatum + {t('invoices.dueDate')}
{formatDate(invoice.due_date)} @@ -112,7 +100,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- Gesamtbetrag + {t('invoices.amount')}
{invoice.total_amount != null @@ -125,16 +113,10 @@ export default async function InvoiceDetailPage({ params }: PageProps) { {/* Actions */}
{status === 'draft' && ( - + )} {(status === 'sent' || status === 'overdue') && ( - + )}
@@ -143,26 +125,26 @@ export default async function InvoiceDetailPage({ params }: PageProps) { {/* Line Items */} - Positionen ({items.length}) + {t('invoiceForm.lineItems')} ({items.length}) {items.length === 0 ? (

- Keine Positionen vorhanden. + {t('invoices.noItems')}

) : ( -
- +
+
- - - + - + @@ -193,7 +175,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- Beschreibung + + {t('invoiceForm.itemDescription')} Menge - Einzelpreis + {t('invoiceForm.quantity')} + {t('invoices.unitPriceCol')} Gesamt{t('invoices.totalCol')}
- Zwischensumme + {t('invoiceForm.subtotal')} {formatCurrency(invoice.subtotal ?? 0)} @@ -201,7 +183,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- MwSt. ({Number(invoice.tax_rate ?? 19)}%) + {t('invoiceForm.tax', { rate: Number(invoice.tax_rate ?? 19) })} {formatCurrency(invoice.tax_amount ?? 0)} @@ -209,7 +191,7 @@ export default async function InvoiceDetailPage({ params }: PageProps) {
- Gesamtbetrag + {t('invoiceForm.total')} {formatCurrency(invoice.total_amount ?? 0)} diff --git a/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx b/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx index a1f4afe25..5c0a24ae8 100644 --- a/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/invoices/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { FileText, Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createFinanceApi } from '@kit/finance/api'; import { formatDate } from '@kit/shared/dates'; @@ -14,7 +15,7 @@ import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; import { INVOICE_STATUS_VARIANT, - INVOICE_STATUS_LABEL, + INVOICE_STATUS_LABEL_KEYS, } from '~/lib/status-badges'; interface PageProps { @@ -29,6 +30,7 @@ const formatCurrency = (amount: unknown) => export default async function InvoicesPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('finance'); const { data: acct } = await client .from('accounts') @@ -43,48 +45,61 @@ export default async function InvoicesPage({ params }: PageProps) { const invoices = invoicesResult.data; return ( - +
{/* Header */}
-

Rechnungen

Rechnungen verwalten

- - - + {t('invoices.newInvoice')} + +
{/* Table or Empty State */} {invoices.length === 0 ? ( } - title="Keine Rechnungen vorhanden" - description="Erstellen Sie Ihre erste Rechnung." - actionLabel="Neue Rechnung" + title={t('invoices.noInvoices')} + description={t('invoices.createFirst')} + actionLabel={t('invoices.newInvoice')} actionHref={`/home/${account}/finance/invoices/new`} /> ) : ( - Alle Rechnungen ({invoices.length}) + + {t('invoices.title')} ({invoices.length}) + -
- +
+
- - - - - - + + + + + + @@ -107,10 +122,10 @@ export default async function InvoicesPage({ params }: PageProps) { {String(invoice.recipient_name ?? '—')} diff --git a/apps/web/app/[locale]/home/[account]/finance/page.tsx b/apps/web/app/[locale]/home/[account]/finance/page.tsx index 25cb7d1c0..df8b50c35 100644 --- a/apps/web/app/[locale]/home/[account]/finance/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/page.tsx @@ -9,9 +9,10 @@ import { ChevronLeft, ChevronRight, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createFinanceApi } from '@kit/finance/api'; -import { formatDate } from '@kit/shared/dates'; +import { formatDate, formatCurrencyAmount } from '@kit/shared/dates'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -24,9 +25,9 @@ import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; import { BATCH_STATUS_VARIANT, - BATCH_STATUS_LABEL, + BATCH_STATUS_LABEL_KEYS, INVOICE_STATUS_VARIANT, - INVOICE_STATUS_LABEL, + INVOICE_STATUS_LABEL_KEYS, } from '~/lib/status-badges'; const PAGE_SIZE = 25; @@ -54,6 +55,7 @@ export default async function FinancePage({ params, searchParams }: PageProps) { const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('finance'); const { data: acct } = await client .from('accounts') @@ -98,64 +100,63 @@ export default async function FinancePage({ params, searchParams }: PageProps) { const queryBase = { q, status }; return ( - +
{/* Header */}
-

Finanzen

-

SEPA-Einzüge und Rechnungen

+

{t('dashboard.subtitle')}

- - - - - + - + {t('nav.newBatch')} + +
{/* Stats */}
} /> } /> } />
{/* Toolbar */} - Letzte SEPA-Einzüge ({batchesResult.total}) - - - + + {batches.length === 0 ? ( } - title="Keine SEPA-Einzüge" - description="Erstellen Sie Ihren ersten SEPA-Einzug." - actionLabel="Neuer SEPA-Einzug" + title={t('sepa.noBatches')} + description={t('sepa.createFirst')} + actionLabel={t('nav.newBatch')} actionHref={`/home/${account}/finance/sepa/new`} /> ) : ( -
-
Nr.EmpfängerDatumFälligBetragStatus + {t('invoices.invoiceNumber')} + + {t('invoices.recipient')} + + {t('invoices.issueDate')} + + {t('invoices.dueDate')} + + {t('common.amount')} + + {t('common.status')} +
- {formatDate(invoice.issue_date)} + {formatDate(invoice.issue_date as string | null)} - {formatDate(invoice.due_date)} + {formatDate(invoice.due_date as string | null)} {invoice.total_amount != null @@ -123,7 +138,7 @@ export default async function InvoicesPage({ params }: PageProps) { INVOICE_STATUS_VARIANT[status] ?? 'secondary' } > - {INVOICE_STATUS_LABEL[status] ?? status} + {t(INVOICE_STATUS_LABEL_KEYS[status] ?? status)}
+
+
- - - - + + + + @@ -205,22 +216,26 @@ export default async function FinancePage({ params, searchParams }: PageProps) { 'secondary' } > - {BATCH_STATUS_LABEL[String(batch.status)] ?? - String(batch.status)} + {t(BATCH_STATUS_LABEL_KEYS[String(batch.status)] ?? + String(batch.status))} ))} @@ -234,32 +249,42 @@ export default async function FinancePage({ params, searchParams }: PageProps) { {/* Invoices */} - Letzte Rechnungen ({invoicesResult.total}) - - - + + {invoices.length === 0 ? ( } - title="Keine Rechnungen" - description="Erstellen Sie Ihre erste Rechnung." - actionLabel="Neue Rechnung" + title={t('invoices.noInvoices')} + description={t('invoices.createFirst')} + actionLabel={t('invoices.newInvoice')} actionHref={`/home/${account}/finance/invoices/new`} /> ) : ( -
-
StatusTypBetragDatum + {t('common.status')} + + {t('common.type')} + + {t('common.amount')} + + {t('common.date')} +
{batch.batch_type === 'direct_debit' - ? 'Lastschrift' - : 'Überweisung'} + ? t('sepa.directDebit') + : t('sepa.creditTransfer')} {batch.total_amount != null - ? `${Number(batch.total_amount).toFixed(2)} €` + ? formatCurrencyAmount(batch.total_amount as number) : '—'} - {formatDate(batch.execution_date ?? batch.created_at)} + {formatDate( + (batch.execution_date ?? batch.created_at) as + | string + | null, + )}
+
+
- - - - + + + + @@ -281,7 +306,9 @@ export default async function FinancePage({ params, searchParams }: PageProps) { @@ -308,20 +334,21 @@ export default async function FinancePage({ params, searchParams }: PageProps) { {totalPages > 1 && (

- Seite {safePage} von {totalPages} + {t('common.page')} {safePage} {t('common.of')} {totalPages}

{safePage > 1 ? ( - - - + ) : ( - )} @@ -330,16 +357,17 @@ export default async function FinancePage({ params, searchParams }: PageProps) { {safePage < totalPages ? ( - - - + ) : ( - )}
diff --git a/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx b/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx index 7c12d0f93..4628ef032 100644 --- a/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/sepa/[batchId]/page.tsx @@ -8,33 +8,19 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { getTranslations } from 'next-intl/server'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; +import { + BATCH_STATUS_VARIANT, + BATCH_STATUS_LABEL_KEYS, +} from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string; batchId: string }>; } -const STATUS_VARIANT: Record< - string, - 'secondary' | 'default' | 'info' | 'outline' | 'destructive' -> = { - draft: 'secondary', - ready: 'default', - submitted: 'info', - completed: 'outline', - failed: 'destructive', -}; - -const STATUS_LABEL: Record = { - draft: 'Entwurf', - ready: 'Bereit', - submitted: 'Eingereicht', - completed: 'Abgeschlossen', - failed: 'Fehlgeschlagen', -}; - const ITEM_STATUS_VARIANT: Record< string, 'secondary' | 'default' | 'info' | 'outline' | 'destructive' @@ -44,12 +30,6 @@ const ITEM_STATUS_VARIANT: Record< failed: 'destructive', }; -const ITEM_STATUS_LABEL: Record = { - pending: 'Ausstehend', - processed: 'Verarbeitet', - failed: 'Fehlgeschlagen', -}; - const formatCurrency = (amount: unknown) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format( Number(amount), @@ -57,6 +37,7 @@ const formatCurrency = (amount: unknown) => export default async function SepaBatchDetailPage({ params }: PageProps) { const { account, batchId } = await params; + const t = await getTranslations('finance'); const client = getSupabaseServerClient(); const { data: acct } = await client @@ -79,7 +60,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) { const status = String(batch.status); return ( - +
{/* Back link */}
@@ -88,33 +69,33 @@ export default async function SepaBatchDetailPage({ params }: PageProps) { className="text-muted-foreground hover:text-foreground inline-flex items-center text-sm" > - Zurück zu SEPA-Lastschriften + {t('sepa.backToList')}
{/* Summary Card */} - {String(batch.description ?? 'SEPA-Einzug')} - - {STATUS_LABEL[status] ?? status} + {String(batch.description ?? t('sepa.batchFallbackName'))} + + {t(BATCH_STATUS_LABEL_KEYS[status] ?? status)}
- Typ + {t('common.type')}
{batch.batch_type === 'direct_debit' - ? 'Lastschrift' - : 'Überweisung'} + ? t('sepa.directDebit') + : t('sepa.creditTransfer')}
- Betrag + {t('common.amount')}
{batch.total_amount != null @@ -124,7 +105,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
- Anzahl + {t('sepa.itemCountLabel')}
{String(batch.item_count ?? items.length)} @@ -132,7 +113,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
- Ausführungsdatum + {t('sepa.executionDate')}
{formatDate(batch.execution_date)} @@ -143,7 +124,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) {
@@ -152,22 +133,22 @@ export default async function SepaBatchDetailPage({ params }: PageProps) { {/* Items Table */} - Positionen ({items.length}) + {t('sepa.itemCount')} ({items.length}) {items.length === 0 ? (

- Keine Positionen vorhanden. + {t('sepa.noItems')}

) : ( -
-
Nr.EmpfängerBetragStatus + {t('invoices.invoiceNumber')} + + {t('invoices.recipient')} + + {t('common.amount')} + + {t('common.status')} +
{invoice.total_amount != null - ? `${Number(invoice.total_amount).toFixed(2)} €` + ? formatCurrencyAmount( + invoice.total_amount as number, + ) : '—'} @@ -291,8 +318,7 @@ export default async function FinancePage({ params, searchParams }: PageProps) { 'secondary' } > - {INVOICE_STATUS_LABEL[String(invoice.status)] ?? - String(invoice.status)} + {t(INVOICE_STATUS_LABEL_KEYS[String(invoice.status)] ?? String(invoice.status))}
+
+
- - - - + + + + @@ -195,7 +176,7 @@ export default async function SepaBatchDetailPage({ params }: PageProps) { ITEM_STATUS_VARIANT[itemStatus] ?? 'secondary' } > - {ITEM_STATUS_LABEL[itemStatus] ?? itemStatus} + {t(`sepaItemStatus.${itemStatus}` as Parameters[0]) ?? itemStatus} diff --git a/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx b/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx index 9393f2554..760361965 100644 --- a/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx +++ b/apps/web/app/[locale]/home/[account]/finance/sepa/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { Landmark, Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createFinanceApi } from '@kit/finance/api'; import { formatDate } from '@kit/shared/dates'; @@ -12,7 +13,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; -import { BATCH_STATUS_VARIANT, BATCH_STATUS_LABEL } from '~/lib/status-badges'; +import { BATCH_STATUS_VARIANT, BATCH_STATUS_LABEL_KEYS } from '~/lib/status-badges'; interface PageProps { params: Promise<{ account: string }>; @@ -26,6 +27,7 @@ const formatCurrency = (amount: unknown) => export default async function SepaPage({ params }: PageProps) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('finance'); const { data: acct } = await client .from('accounts') @@ -40,53 +42,62 @@ export default async function SepaPage({ params }: PageProps) { const batches = batchesResult.data; return ( - +
{/* Header */}
-

SEPA-Lastschriften

Lastschrifteinzüge verwalten

- - - + {t('nav.newBatch')} + +
{/* Table or Empty State */} {batches.length === 0 ? ( } - title="Keine SEPA-Einzüge" - description="Erstellen Sie Ihren ersten SEPA-Einzug." - actionLabel="Neuer Einzug" + title={t('sepa.noBatches')} + description={t('sepa.createFirst')} + actionLabel={t('nav.newBatch')} actionHref={`/home/${account}/finance/sepa/new`} /> ) : ( - Alle Einzüge ({batches.length}) + + {t('sepa.title')} ({batches.length}) + -
-
NameIBANBetragStatusNameIBAN{t('common.amount')}{t('common.status')}
+
+
- - - - - - + + + + @@ -103,14 +114,13 @@ export default async function SepaPage({ params }: PageProps) { 'secondary' } > - {BATCH_STATUS_LABEL[String(batch.status)] ?? - String(batch.status)} + {t(BATCH_STATUS_LABEL_KEYS[String(batch.status)] ?? String(batch.status))} ))} diff --git a/apps/web/app/[locale]/home/[account]/loading.tsx b/apps/web/app/[locale]/home/[account]/loading.tsx index 4ea53181d..7a166a2e4 100644 --- a/apps/web/app/[locale]/home/[account]/loading.tsx +++ b/apps/web/app/[locale]/home/[account]/loading.tsx @@ -1,3 +1,21 @@ -import { GlobalLoader } from '@kit/ui/global-loader'; +import { PageBody } from '@kit/ui/page'; -export default GlobalLoader; +export default function AccountLoading() { + return ( + +
+
+
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+
+ {[1, 2, 3, 4, 5, 6].map((i) => ( +
+ ))} +
+
+ + ); +} diff --git a/apps/web/app/[locale]/home/[account]/meetings/protocols/[protocolId]/page.tsx b/apps/web/app/[locale]/home/[account]/meetings/protocols/[protocolId]/page.tsx index cdb314e89..92f838ca6 100644 --- a/apps/web/app/[locale]/home/[account]/meetings/protocols/[protocolId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/meetings/protocols/[protocolId]/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { ArrowLeft } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { formatDateFull } from '@kit/shared/dates'; import { createMeetingsApi } from '@kit/sitzungsprotokolle/api'; @@ -24,6 +25,7 @@ interface PageProps { export default async function ProtocolDetailPage({ params }: PageProps) { const { account, protocolId } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('meetings'); const { data: acct } = await client .from('accounts') @@ -40,14 +42,14 @@ export default async function ProtocolDetailPage({ params }: PageProps) { protocol = await api.getProtocol(protocolId); } catch { return ( - +
-

Protokoll nicht gefunden

+

{t('pages.notFound')}

- +
@@ -57,7 +59,7 @@ export default async function ProtocolDetailPage({ params }: PageProps) { const items = await api.listItems(protocolId); return ( - +
@@ -66,7 +68,7 @@ export default async function ProtocolDetailPage({ params }: PageProps) {
@@ -81,13 +83,12 @@ export default async function ProtocolDetailPage({ params }: PageProps) { {formatDateFull(protocol.meeting_date)} · - {MEETING_TYPE_LABELS[protocol.meeting_type] ?? - protocol.meeting_type} + {MEETING_TYPE_LABELS[protocol.status] ?? protocol.status} - {protocol.is_published ? ( - Veröffentlicht + {protocol.status === 'final' ? ( + {t('pages.statusPublished')} ) : ( - Entwurf + {t('pages.statusDraft')} )}
@@ -106,17 +107,17 @@ export default async function ProtocolDetailPage({ params }: PageProps) { Teilnehmer

- {protocol.attendees} + {String(protocol.attendees ?? '')}

)} - {protocol.remarks && ( + {protocol.summary && (

Anmerkungen

- {protocol.remarks} + {protocol.summary}

)} diff --git a/apps/web/app/[locale]/home/[account]/members-cms/invitations/invitations-view.tsx b/apps/web/app/[locale]/home/[account]/members-cms/invitations/invitations-view.tsx index e8d6a707c..68cf8b1f6 100644 --- a/apps/web/app/[locale]/home/[account]/members-cms/invitations/invitations-view.tsx +++ b/apps/web/app/[locale]/home/[account]/members-cms/invitations/invitations-view.tsx @@ -5,6 +5,7 @@ import { useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Mail, XCircle, Send } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { inviteMemberToPortal, @@ -63,6 +64,7 @@ export function InvitationsView({ accountId, account, }: InvitationsViewProps) { + const t = useTranslations('members'); const router = useRouter(); const [showDialog, setShowDialog] = useState(false); const [selectedMemberId, setSelectedMemberId] = useState(''); @@ -169,7 +171,7 @@ export function InvitationsView({ type="email" value={email} onChange={(e) => setEmail(e.target.value)} - placeholder="E-Mail eingeben..." + placeholder={t('invitations.emailPlaceholder')} data-test="invite-email-input" className="border-input bg-background flex h-9 w-full rounded-md border px-3 py-1 text-sm" /> @@ -204,19 +206,29 @@ export function InvitationsView({ Keine Einladungen vorhanden

- Senden Sie die erste Einladung zum Mitgliederportal. + {t('invitations.emptyDescription')}

) : ( -
-
StatusTyp - Beschreibung + + {t('common.status')} BetragAnzahl - Ausführungsdatum + + {t('common.type')} + + {t('common.description')} + + {t('sepa.totalAmount')} + + {t('sepa.itemCount')} + + {t('sepa.executionDate')}
{batch.batch_type === 'direct_debit' - ? 'Lastschrift' - : 'Überweisung'} + ? t('sepa.directDebit') + : t('sepa.creditTransfer')} - {formatDate(batch.execution_date)} + {formatDate(batch.execution_date as string | null)}
+
+
- - - - - + + + + + diff --git a/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx index 8dbff903c..ea8e2c2c3 100644 --- a/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx +++ b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { ArrowLeft, Pencil, Send, Users } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createNewsletterApi } from '@kit/newsletter/api'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; @@ -13,9 +14,9 @@ import { CmsPageShell } from '~/components/cms-page-shell'; import { StatsCard } from '~/components/stats-card'; import { NEWSLETTER_STATUS_VARIANT, - NEWSLETTER_STATUS_LABEL, + NEWSLETTER_STATUS_LABEL_KEYS, NEWSLETTER_RECIPIENT_STATUS_VARIANT, - NEWSLETTER_RECIPIENT_STATUS_LABEL, + NEWSLETTER_RECIPIENT_STATUS_LABEL_KEYS, } from '~/lib/status-badges'; import { DispatchNewsletterButton } from './dispatch-newsletter-button'; @@ -27,6 +28,7 @@ interface PageProps { export default async function NewsletterDetailPage({ params }: PageProps) { const { account, campaignId } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('newsletter'); const { data: acct } = await client .from('accounts') @@ -55,7 +57,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) { ).length; return ( - +
{/* Back link */}
@@ -65,7 +67,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) { data-test="newsletter-back-link" > - Zurück zu Newsletter + {t('detail.backToList')}
@@ -73,7 +75,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) { - {String(newsletter.subject ?? '(Kein Betreff)')} + {String(newsletter.subject ?? `(${t('list.noSubject')})`)}
{status === 'draft' && ( @@ -85,24 +87,24 @@ export default async function NewsletterDetailPage({ params }: PageProps) { )} - {NEWSLETTER_STATUS_LABEL[status] ?? status} + {t(NEWSLETTER_STATUS_LABEL_KEYS[status] ?? status)}
} /> } /> } /> @@ -123,22 +125,29 @@ export default async function NewsletterDetailPage({ params }: PageProps) { {/* Recipients Table */} - Empfänger ({recipients.length}) + + {t('detail.recipientsSection')} ({recipients.length}) + {recipients.length === 0 ? (

- Keine Empfänger hinzugefügt. Fügen Sie Empfänger aus Ihrer - Mitgliederliste hinzu. + {t('detail.noRecipients')}

) : ( -
-
E-MailStatusErstelltLäuft abAktionen + E-Mail + + Status + + Erstellt + + Läuft ab + + Aktionen +
+
+
- - - + + + @@ -162,8 +171,7 @@ export default async function NewsletterDetailPage({ params }: PageProps) { 'secondary' } > - {NEWSLETTER_RECIPIENT_STATUS_LABEL[rStatus] ?? - rStatus} + {t(NEWSLETTER_RECIPIENT_STATUS_LABEL_KEYS[rStatus] ?? rStatus)} diff --git a/apps/web/app/[locale]/home/[account]/newsletter/page.tsx b/apps/web/app/[locale]/home/[account]/newsletter/page.tsx index 21a4ee916..56287b1f4 100644 --- a/apps/web/app/[locale]/home/[account]/newsletter/page.tsx +++ b/apps/web/app/[locale]/home/[account]/newsletter/page.tsx @@ -8,6 +8,7 @@ import { Send, Users, } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { createNewsletterApi } from '@kit/newsletter/api'; import { formatDate } from '@kit/shared/dates'; @@ -23,7 +24,7 @@ import { EmptyState } from '~/components/empty-state'; import { StatsCard } from '~/components/stats-card'; import { NEWSLETTER_STATUS_VARIANT, - NEWSLETTER_STATUS_LABEL, + NEWSLETTER_STATUS_LABEL_KEYS, } from '~/lib/status-badges'; const PAGE_SIZE = 25; @@ -54,6 +55,7 @@ export default async function NewsletterPage({ const { account } = await params; const search = await searchParams; const client = getSupabaseServerClient(); + const t = await getTranslations('newsletter'); const { data: acct } = await client .from('accounts') @@ -80,10 +82,6 @@ export default async function NewsletterPage({ const totalPages = result.totalPages; const safePage = result.page; - const sentCount = newsletters.filter( - (n: Record) => n.status === 'sent', - ).length; - const totalRecipients = newsletters.reduce( (sum: number, n: Record) => sum + (Number(n.total_recipients) || 0), @@ -93,21 +91,18 @@ export default async function NewsletterPage({ const queryBase = { q, status }; return ( - +
{/* Header */}
-

Newsletter

-

- Newsletter erstellen und versenden -

+

{t('list.subtitle')}

@@ -115,17 +110,17 @@ export default async function NewsletterPage({ {/* Stats */}
} /> } /> } /> @@ -154,26 +149,38 @@ export default async function NewsletterPage({ {totalItems === 0 ? ( } - title="Keine Newsletter vorhanden" - description="Erstellen Sie Ihren ersten Newsletter, um loszulegen." - actionLabel="Neuer Newsletter" + title={t('list.noNewsletters')} + description={t('list.createFirst')} + actionLabel={t('list.newNewsletter')} actionHref={`/home/${account}/newsletter/new`} /> ) : ( - Alle Newsletter ({totalItems}) + + {t('list.title')} ({totalItems}) + -
-
NameE-MailStatus + {t('detail.recipientName')} + + {t('detail.recipientEmail')} + + {t('detail.recipientStatus')} +
+
+
- - - - - + + + + + @@ -187,7 +194,7 @@ export default async function NewsletterPage({ href={`/home/${account}/newsletter/${String(nl.id)}`} className="hover:underline" > - {String(nl.subject ?? '(Kein Betreff)')} + {String(nl.subject ?? t('list.noSubject'))} - - + + ))} @@ -222,16 +232,17 @@ export default async function NewsletterPage({

{safePage > 1 ? ( - - - + ) : ( - )} @@ -240,16 +251,17 @@ export default async function NewsletterPage({ {safePage < totalPages ? ( - - - + ) : ( - )}
diff --git a/apps/web/app/[locale]/home/[account]/site-builder/page.tsx b/apps/web/app/[locale]/home/[account]/site-builder/page.tsx index 31a75b381..025680709 100644 --- a/apps/web/app/[locale]/home/[account]/site-builder/page.tsx +++ b/apps/web/app/[locale]/home/[account]/site-builder/page.tsx @@ -1,6 +1,16 @@ import Link from 'next/link'; +interface SitePage { + id: string; + title: string; + slug: string; + is_published: boolean; + is_homepage: boolean; + updated_at: string | null; +} + import { Plus, Globe, FileText, Settings, ExternalLink } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { formatDate } from '@kit/shared/dates'; import { createSiteBuilderApi } from '@kit/site-builder/api'; @@ -14,6 +24,8 @@ import { AccountNotFound } from '~/components/account-not-found'; import { CmsPageShell } from '~/components/cms-page-shell'; import { EmptyState } from '~/components/empty-state'; +import { PublishToggleButton } from './publish-toggle-button'; + interface Props { params: Promise<{ account: string }>; } @@ -21,6 +33,8 @@ interface Props { export default async function SiteBuilderDashboard({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('siteBuilder'); + const { data: acct } = await client .from('accounts') .select('id') @@ -29,69 +43,77 @@ export default async function SiteBuilderDashboard({ params }: Props) { if (!acct) return ; const api = createSiteBuilderApi(client); - const pages = await api.listPages(acct.id); - const settings = await api.getSiteSettings(acct.id); - const posts = await api.listPosts(acct.id); + const [pages, settings, posts] = await Promise.all([ + api.listPages(acct.id), + api.getSiteSettings(acct.id), + api.listPosts(acct.id), + ]); const isOnline = Boolean(settings?.is_public); - const publishedCount = pages.filter( - (p: Record) => p.is_published, + const publishedCount = (pages as SitePage[]).filter( + (p) => p.is_published, ).length; return (
- - - - - + - + {t('dashboard.btnPosts', { count: posts.length })} + + {isOnline && ( - - - + {t('site.viewSite')} + + )}
- - - + {t('pages.newPage')} + +
-

Seiten

+

+ {t('site.stats.pages')} +

{pages.length}

-

Veröffentlicht

+

+ {t('site.stats.published')} +

{publishedCount}

-

Status

+

+ {t('site.stats.status')} +

- {isOnline ? 'Online' : 'Offline'} + + {isOnline ? t('pages.online') : t('pages.offline')} +

@@ -110,53 +134,82 @@ export default async function SiteBuilderDashboard({ params }: Props) { {pages.length === 0 ? ( } - title="Noch keine Seiten" - description="Erstellen Sie Ihre erste Seite mit dem visuellen Editor." - actionLabel="Erste Seite erstellen" + title={t('pages.noPagesYet')} + description={t('pages.noPageDesc')} + actionLabel={t('pages.firstPage')} actionHref={`/home/${account}/site-builder/new`} /> ) : ( -
-
BetreffStatusEmpfängerErstelltGesendet + {t('list.subject')} + + Status + + {t('list.recipients')} + + {t('list.created')} + + {t('list.sent')} +
@@ -197,8 +204,7 @@ export default async function NewsletterPage({ 'secondary' } > - {NEWSLETTER_STATUS_LABEL[String(nl.status)] ?? - String(nl.status)} + {t(NEWSLETTER_STATUS_LABEL_KEYS[String(nl.status)] ?? String(nl.status))} @@ -206,8 +212,12 @@ export default async function NewsletterPage({ ? String(nl.total_recipients) : '—'} {formatDate(nl.created_at)}{formatDate(nl.sent_at)} + {formatDate(nl.created_at as string | null)} + + {formatDate(nl.sent_at as string | null)} +
+
+
- - - - - - + + + + + + - {pages.map((page: Record) => ( + {(pages as SitePage[]).map((page) => ( - + - + ))} diff --git a/apps/web/app/[locale]/home/[account]/site-builder/posts/page.tsx b/apps/web/app/[locale]/home/[account]/site-builder/posts/page.tsx index 5fa694836..c3ae9673f 100644 --- a/apps/web/app/[locale]/home/[account]/site-builder/posts/page.tsx +++ b/apps/web/app/[locale]/home/[account]/site-builder/posts/page.tsx @@ -1,6 +1,14 @@ import Link from 'next/link'; import { Plus } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; + +interface SitePost { + id: string; + title: string; + status: string; + created_at: string | null; +} import { formatDate } from '@kit/shared/dates'; import { createSiteBuilderApi } from '@kit/site-builder/api'; @@ -20,6 +28,8 @@ interface Props { export default async function PostsManagerPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); + const t = await getTranslations('siteBuilder'); + const { data: acct } = await client .from('accounts') .select('id') @@ -33,46 +43,52 @@ export default async function PostsManagerPage({ params }: Props) { return (
{posts.length === 0 ? ( ) : ( -
-
TitelURLStatusStartseiteAktualisiertAktionen + {t('pages.colTitle')} + + {t('pages.colUrl')} + + {t('pages.colStatus')} + + {t('pages.colHomepage')} + + {t('pages.colUpdated')} + + {t('pages.colActions')} +
{String(page.title)}{page.title} - /{String(page.slug)} + /{page.slug} - {page.is_published ? 'Veröffentlicht' : 'Entwurf'} + {page.is_published + ? t('pages.statusPublished') + : t('pages.statusDraft')} {page.is_homepage ? '⭐' : '—'} + {page.is_homepage ? ( + + {t('pages.homepageLabel')} + + ) : ( + '—' + )} + {formatDate(page.updated_at)} - - - +
+
+
- - - + + + - {posts.map((post: Record) => ( + {(posts as SitePost[]).map((post) => ( - +
TitelStatusErstellt + {t('posts.colTitle')} + + {t('posts.colStatus')} + + {t('posts.colCreated')} +
{String(post.title)}{post.title} - {String(post.status)} + {post.status} diff --git a/apps/web/app/[locale]/home/[account]/site-builder/publish-toggle-button.tsx b/apps/web/app/[locale]/home/[account]/site-builder/publish-toggle-button.tsx new file mode 100644 index 000000000..f49cb0396 --- /dev/null +++ b/apps/web/app/[locale]/home/[account]/site-builder/publish-toggle-button.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { useTransition } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { useTranslations } from 'next-intl'; +import { toast } from '@kit/ui/sonner'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@kit/ui/alert-dialog'; +import { Button } from '@kit/ui/button'; + +interface PublishToggleButtonProps { + pageId: string; + accountId: string; + isPublished: boolean; +} + +export function PublishToggleButton({ + pageId, + accountId, + isPublished, +}: PublishToggleButtonProps) { + const router = useRouter(); + const t = useTranslations('siteBuilder'); + const [isPending, startTransition] = useTransition(); + + const handleToggle = () => { + startTransition(async () => { + try { + const response = await fetch(`/api/site-builder/pages/${pageId}/publish`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accountId, isPublished: !isPublished }), + }); + + if (!response.ok) { + toast.error(t('pages.toggleError')); + return; + } + + router.refresh(); + } catch (error) { + console.error('Failed to toggle publish state:', error); + toast.error(t('pages.toggleError')); + } + }); + }; + + return ( + + + {isPublished ? t('pages.hide') : t('pages.publish')} + + } + /> + + + + {isPublished ? t('pages.hideTitle') : t('pages.publishTitle')} + + + {isPublished ? t('pages.hideDesc') : t('pages.publishDesc')} + + + + {t('pages.cancelAction')} + + {isPublished ? t('pages.hide') : t('pages.publish')} + + + + + ); +} diff --git a/apps/web/components/account-not-found.tsx b/apps/web/components/account-not-found.tsx index e3dc96bac..ab28167a8 100644 --- a/apps/web/components/account-not-found.tsx +++ b/apps/web/components/account-not-found.tsx @@ -1,24 +1,40 @@ import Link from 'next/link'; import { AlertTriangle } from 'lucide-react'; +import { getTranslations } from 'next-intl/server'; import { Button } from '@kit/ui/button'; -export function AccountNotFound() { +interface AccountNotFoundProps { + title?: string; + description?: string; + buttonLabel?: string; +} + +export async function AccountNotFound({ + title, + description, + buttonLabel, +}: AccountNotFoundProps = {}) { + const t = await getTranslations('common'); + + const resolvedTitle = title ?? t('accountNotFoundCard.title'); + const resolvedDescription = description ?? t('accountNotFoundCard.description'); + const resolvedButtonLabel = buttonLabel ?? t('accountNotFoundCard.action'); + return (
-

Konto nicht gefunden

+

{resolvedTitle}

- Das angeforderte Konto existiert nicht oder Sie haben keine Berechtigung - darauf zuzugreifen. + {resolvedDescription}

- - - +
); diff --git a/apps/web/components/cms-page-shell.tsx b/apps/web/components/cms-page-shell.tsx index ca407e807..2445bcfd0 100644 --- a/apps/web/components/cms-page-shell.tsx +++ b/apps/web/components/cms-page-shell.tsx @@ -27,7 +27,7 @@ export function CmsPageShell({ } + description={description !== undefined ? description : } /> {children} diff --git a/apps/web/components/empty-state.tsx b/apps/web/components/empty-state.tsx index ddb8622bb..7bf1d7bb4 100644 --- a/apps/web/components/empty-state.tsx +++ b/apps/web/components/empty-state.tsx @@ -1,3 +1,5 @@ +import Link from 'next/link'; + import { Button } from '@kit/ui/button'; interface EmptyStateProps { @@ -28,16 +30,16 @@ export function EmptyState({ {icon} )} -

{title}

+

{title}

{description}

{actionLabel && (
{actionHref ? ( - - - + ) : ( )} diff --git a/apps/web/config/team-account-navigation.config.tsx b/apps/web/config/team-account-navigation.config.tsx index 5944a675d..e8911c2aa 100644 --- a/apps/web/config/team-account-navigation.config.tsx +++ b/apps/web/config/team-account-navigation.config.tsx @@ -32,6 +32,7 @@ import { FileText, FilePlus, FileStack, + FolderOpen, // Newsletter Mail, MailPlus, @@ -124,14 +125,15 @@ const getRoutes = (account: string) => { ), Icon: , }, - { - label: 'common:routes.memberPortal', - path: createPath( - pathsConfig.app.accountCmsMembers + '/portal', - account, - ), - Icon: , - }, + // NOTE: memberPortal page does not exist yet — nav entry commented out until built + // { + // label: 'common:routes.memberPortal', + // path: createPath( + // pathsConfig.app.accountCmsMembers + '/portal', + // account, + // ), + // Icon: , + // }, { label: 'common:routes.memberCards', path: createPath( @@ -326,6 +328,11 @@ const getRoutes = (account: string) => { ), Icon: , }, + { + label: 'common:routes.files', + path: createPath(pathsConfig.app.accountFiles, account), + Icon: , + }, ], }); } diff --git a/apps/web/i18n/messages/de/bookings.json b/apps/web/i18n/messages/de/bookings.json index 82f717a0e..035e9525c 100644 --- a/apps/web/i18n/messages/de/bookings.json +++ b/apps/web/i18n/messages/de/bookings.json @@ -15,17 +15,52 @@ "activeBookings": "Aktive Buchungen", "guest": "Gast", "room": "Zimmer", - "checkIn": "Check-in", - "checkOut": "Check-out", + "checkIn": "Anreise", + "checkOut": "Abreise", "nights": "Nächte", - "price": "Preis" + "price": "Preis", + "status": "Status", + "amount": "Betrag", + "total": "Gesamt", + "manage": "Zimmer und Buchungen verwalten", + "search": "Suchen", + "reset": "Zurücksetzen", + "noResults": "Keine Buchungen gefunden", + "noResultsFor": "Keine Ergebnisse für \"{query}\".", + "allBookings": "Alle Buchungen ({count})", + "searchResults": "Ergebnisse ({count})" }, "detail": { + "title": "Buchungsdetails", "notFound": "Buchung nicht gefunden", + "notFoundDesc": "Buchung mit ID \"{id}\" wurde nicht gefunden.", + "backToBookings": "Zurück zu Buchungen", "guestInfo": "Gastinformationen", "roomInfo": "Zimmerinformationen", "bookingDetails": "Buchungsdetails", - "extras": "Extras" + "extras": "Extras", + "room": "Zimmer", + "roomNumber": "Zimmernummer", + "type": "Typ", + "noRoom": "Kein Zimmer zugewiesen", + "guest": "Gast", + "email": "E-Mail", + "phone": "Telefon", + "noGuest": "Kein Gast zugewiesen", + "stay": "Aufenthalt", + "adults": "Erwachsene", + "children": "Kinder", + "amount": "Betrag", + "totalPrice": "Gesamtpreis", + "notes": "Notizen", + "actions": "Aktionen", + "changeStatus": "Status der Buchung ändern", + "checkIn": "Einchecken", + "checkOut": "Auschecken", + "cancel": "Stornieren", + "cancelledStatus": "storniert", + "completedStatus": "abgeschlossen", + "noMoreActions": "Diese Buchung ist {statusLabel} — keine weiteren Aktionen verfügbar." }, "form": { "room": "Zimmer *", @@ -51,21 +86,59 @@ "title": "Zimmer", "newRoom": "Neues Zimmer", "noRooms": "Keine Zimmer vorhanden", + "addFirst": "Fügen Sie Ihr erstes Zimmer hinzu.", + "manage": "Zimmerverwaltung", + "allRooms": "Alle Zimmer ({count})", + "roomNumber": "Zimmernr.", "name": "Name", "type": "Typ", "capacity": "Kapazität", - "price": "Preis/Nacht" + "price": "Preis/Nacht", + "active": "Aktiv" }, "guests": { "title": "Gäste", "newGuest": "Neuer Gast", "noGuests": "Keine Gäste vorhanden", + "addFirst": "Legen Sie Ihren ersten Gast an.", + "manage": "Gästeverwaltung", + "allGuests": "Alle Gäste ({count})", "name": "Name", "email": "E-Mail", "phone": "Telefon", + "city": "Stadt", + "country": "Land", "bookings": "Buchungen" }, "calendar": { - "title": "Belegungskalender" + "title": "Belegungskalender", + "subtitle": "Zimmerauslastung im Überblick", + "occupied": "Belegt", + "free": "Frei", + "today": "Heute", + "bookingsThisMonth": "Buchungen in diesem Monat", + "daysOccupied": "{occupied} von {total} Tagen belegt", + "previousMonth": "Vorheriger Monat", + "nextMonth": "Nächster Monat", + "backToBookings": "Zurück zu Buchungen" + }, + "newBooking": { + "title": "Neue Buchung", + "description": "Buchung erstellen" + }, + "common": { + "previous": "Zurück", + "next": "Weiter", + "page": "Seite", + "of": "von", + "entries": "Einträge", + "pageInfo": "Seite {page} von {total} ({entries} Einträge)" + }, + "cancel": { + "title": "Buchung stornieren?", + "description": "Diese Aktion kann nicht rückgängig gemacht werden. Die Buchung wird unwiderruflich storniert.", + "confirm": "Stornieren", + "cancel": "Abbrechen", + "cancelling": "Wird storniert..." } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/cms.json b/apps/web/i18n/messages/de/cms.json index 234ed8e3e..dbacee597 100644 --- a/apps/web/i18n/messages/de/cms.json +++ b/apps/web/i18n/messages/de/cms.json @@ -25,6 +25,9 @@ "advancedFilter": "Erweiterter Filter", "clearFilters": "Filter zurücksetzen", "noRecords": "Keine Datensätze gefunden", + "notFound": "Nicht gefunden", + "accountNotFound": "Account nicht gefunden", + "record": "Datensatz", "paginationSummary": "{total} Datensätze — Seite {page} von {totalPages}", "paginationPrevious": "← Zurück", "paginationNext": "Weiter →", @@ -167,7 +170,7 @@ }, "events": { "title": "Veranstaltungen", - "description": "Veranstaltungen und Ferienprogramme verwalten", + "description": "Beschreibung", "newEvent": "Neue Veranstaltung", "registrations": "Anmeldungen", "holidayPasses": "Ferienpässe", @@ -180,7 +183,15 @@ "noEvents": "Keine Veranstaltungen vorhanden", "noEventsDescription": "Erstellen Sie Ihre erste Veranstaltung, um loszulegen.", "name": "Name", - "status": "Status", + "status": { + "planned": "Geplant", + "open": "Offen", + "full": "Ausgebucht", + "running": "Laufend", + "completed": "Abgeschlossen", + "cancelled": "Abgesagt", + "registration_open": "Anmeldung offen" + }, "paginationPage": "Seite {page} von {totalPages}", "paginationPrevious": "Vorherige", "paginationNext": "Nächste", @@ -200,7 +211,28 @@ "price": "Preis", "validFrom": "Gültig von", "validUntil": "Gültig bis", - "newEventDescription": "Veranstaltung oder Ferienprogramm anlegen" + "newEventDescription": "Veranstaltung oder Ferienprogramm anlegen", + "detailTitle": "Veranstaltungsdetails", + "edit": "Bearbeiten", + "register": "Anmelden", + "date": "Datum", + "time": "Uhrzeit", + "location": "Ort", + "registrationsCount": "Anmeldungen ({count})", + "noRegistrations": "Noch keine Anmeldungen", + "parentName": "Elternteil", + "notFound": "Veranstaltung nicht gefunden", + "editTitle": "Bearbeiten", + "statusLabel": "Status", + "statusValues": { + "planned": "Geplant", + "open": "Offen", + "full": "Ausgebucht", + "running": "Laufend", + "completed": "Abgeschlossen", + "cancelled": "Abgesagt", + "registration_open": "Anmeldung offen" + } }, "finance": { "title": "Finanzen", @@ -255,7 +287,7 @@ }, "audit": { "title": "Protokoll", - "description": "Änderungsprotokoll einsehen", + "description": "Mandantenübergreifendes Änderungsprotokoll", "action": "Aktion", "user": "Benutzer", "table": "Tabelle", @@ -267,7 +299,9 @@ "update": "Geändert", "delete": "Gelöscht", "lock": "Gesperrt" - } + }, + "paginationPrevious": "← Zurück", + "paginationNext": "Weiter →" }, "permissions": { "modules.read": "Module lesen", @@ -778,4 +812,4 @@ "formatExcel": "Excel" } } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/common.json b/apps/web/i18n/messages/de/common.json index a10722e54..6de38a45f 100644 --- a/apps/web/i18n/messages/de/common.json +++ b/apps/web/i18n/messages/de/common.json @@ -18,6 +18,9 @@ "cancel": "Abbrechen", "clear": "Löschen", "notFound": "Nicht gefunden", + "accountNotFound": "Konto nicht gefunden", + "accountNotFoundDescription": "Das angeforderte Konto existiert nicht oder Sie haben keine Berechtigung darauf zuzugreifen.", + "backToDashboard": "Zum Dashboard", "backToHomePage": "Zurück zur Startseite", "goBack": "Erneut versuchen", "genericServerError": "Entschuldigung, ein Fehler ist aufgetreten.", @@ -63,14 +66,17 @@ "previous": "Zurück", "next": "Weiter", "recordCount": "{total} Datensätze", + "filesTitle": "Dateiverwaltung", + "filesSubtitle": "Dateien hochladen und verwalten", + "filesSearch": "Datei suchen...", + "deleteFile": "Datei löschen", + "deleteFileConfirm": "Möchten Sie diese Datei wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "routes": { - "home": "Startseite", "account": "Konto", "billing": "Abrechnung", "dashboard": "Dashboard", "settings": "Einstellungen", "profile": "Profil", - "people": "Personen", "clubMembers": "Vereinsmitglieder", "memberApplications": "Aufnahmeanträge", @@ -78,48 +84,40 @@ "memberCards": "Mitgliedsausweise", "memberDues": "Beitragskategorien", "accessAndRoles": "Zugänge & Rollen", - "courseManagement": "Kursverwaltung", "courseList": "Alle Kurse", "courseCalendar": "Kurskalender", "courseInstructors": "Kursleiter", "courseLocations": "Standorte", - "eventManagement": "Veranstaltungen", "eventList": "Alle Veranstaltungen", "eventRegistrations": "Anmeldungen", "holidayPasses": "Ferienpässe", - "bookingManagement": "Buchungsverwaltung", "bookingList": "Alle Buchungen", "bookingCalendar": "Belegungskalender", "bookingRooms": "Zimmer", "bookingGuests": "Gäste", - "financeManagement": "Finanzen", "financeOverview": "Übersicht", "financeInvoices": "Rechnungen", "financeSepa": "SEPA-Einzüge", "financePayments": "Zahlungen", - "documentManagement": "Dokumente", "documentOverview": "Übersicht", "documentGenerate": "Generieren", "documentTemplates": "Vorlagen", - + "files": "Dateiverwaltung", "newsletterManagement": "Newsletter", "newsletterCampaigns": "Kampagnen", "newsletterNew": "Neuer Newsletter", "newsletterTemplates": "Vorlagen", - "siteBuilder": "Website", "sitePages": "Seiten", "sitePosts": "Beiträge", "siteSettings": "Einstellungen", - "customModules": "Benutzerdefinierte Module", "moduleList": "Alle Module", - "fisheriesManagement": "Fischerei", "fisheriesOverview": "Übersicht", "fisheriesWaters": "Gewässer", @@ -127,12 +125,10 @@ "fisheriesCatchBooks": "Fangbücher", "fisheriesPermits": "Erlaubnisscheine", "fisheriesCompetitions": "Wettbewerbe", - "meetingProtocols": "Sitzungsprotokolle", "meetingsOverview": "Übersicht", "meetingsProtocols": "Protokolle", "meetingsTasks": "Offene Aufgaben", - "associationManagement": "Verbandsverwaltung", "associationOverview": "Übersicht", "associationHierarchy": "Organisationsstruktur", @@ -140,7 +136,6 @@ "associationEvents": "Geteilte Veranstaltungen", "associationReporting": "Berichte", "associationTemplates": "Geteilte Vorlagen", - "administration": "Administration", "accountSettings": "Kontoeinstellungen" }, @@ -172,6 +167,28 @@ "reject": "Ablehnen", "accept": "Akzeptieren" }, + "dashboard": { + "recentActivity": "Letzte Aktivität", + "recentActivityDescription": "Aktuelle Buchungen und Veranstaltungen", + "recentActivityEmpty": "Noch keine Aktivitäten", + "recentActivityEmptyDescription": "Aktuelle Buchungen und Veranstaltungen werden hier angezeigt.", + "quickActions": "Schnellaktionen", + "quickActionsDescription": "Häufig verwendete Aktionen", + "newMember": "Neues Mitglied", + "newCourse": "Neuer Kurs", + "createNewsletter": "Newsletter erstellen", + "newBooking": "Neue Buchung", + "newEvent": "Neue Veranstaltung", + "bookingFrom": "Buchung vom", + "members": "Mitglieder", + "courses": "Kurse", + "openInvoices": "Offene Rechnungen", + "newsletters": "Newsletter", + "membersDescription": "{total} gesamt, {pending} ausstehend", + "coursesDescription": "{total} gesamt, {participants} Teilnehmer", + "openInvoicesDescription": "Entwürfe zum Versenden", + "newslettersDescription": "Erstellt" + }, "dropzone": { "success": "{count} Datei(en) erfolgreich hochgeladen", "error": "Fehler beim Hochladen von {count} Datei(en)", @@ -187,5 +204,20 @@ "dragAndDrop": "Ziehen und ablegen oder", "select": "Dateien auswählen", "toUpload": "zum Hochladen" + }, + "error": { + "title": "Etwas ist schiefgelaufen", + "description": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.", + "retry": "Erneut versuchen", + "toDashboard": "Zum Dashboard" + }, + "pagination": { + "previous": "Vorherige Seite", + "next": "Nächste Seite" + }, + "accountNotFoundCard": { + "title": "Konto nicht gefunden", + "description": "Das angeforderte Konto existiert nicht oder Sie haben keine Berechtigung darauf zuzugreifen.", + "action": "Zum Dashboard" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/courses.json b/apps/web/i18n/messages/de/courses.json index 75ee6f99c..5e3986033 100644 --- a/apps/web/i18n/messages/de/courses.json +++ b/apps/web/i18n/messages/de/courses.json @@ -10,24 +10,61 @@ }, "pages": { "coursesTitle": "Kurse", + "coursesDescription": "Kursangebot verwalten", "newCourseTitle": "Neuer Kurs", + "newCourseDescription": "Kurs anlegen", + "editCourseTitle": "Bearbeiten", "calendarTitle": "Kurskalender", "categoriesTitle": "Kurskategorien", "instructorsTitle": "Kursleiter", "locationsTitle": "Standorte", "statisticsTitle": "Kurs-Statistiken" }, + "common": { + "all": "Alle", + "status": "Status", + "previous": "Zurück", + "next": "Weiter", + "page": "Seite", + "of": "von", + "entries": "Einträge", + "yes": "Ja", + "no": "Nein", + "name": "Name", + "email": "E-Mail", + "phone": "Telefon", + "date": "Datum", + "address": "Adresse", + "room": "Raum", + "parent": "Übergeordnet", + "description": "Beschreibung", + "edit": "Bearbeiten" + }, "list": { "searchPlaceholder": "Kurs suchen...", - "title": "Kurse ({count})", + "title": "Alle Kurse ({count})", "noCourses": "Keine Kurse vorhanden", "createFirst": "Erstellen Sie Ihren ersten Kurs, um loszulegen.", "courseNumber": "Kursnr.", - "courseName": "Kursname", + "courseName": "Name", "startDate": "Beginn", "endDate": "Ende", "participants": "Teilnehmer", - "fee": "Gebühr" + "fee": "Gebühr", + "status": "Status", + "capacity": "Kapazität" + }, + "stats": { + "total": "Gesamt", + "active": "Aktiv", + "totalCourses": "Kurse gesamt", + "activeCourses": "Aktive Kurse", + "participants": "Teilnehmer", + "completed": "Abgeschlossen", + "utilization": "Kursauslastung", + "distribution": "Verteilung", + "activeCoursesBadge": "Aktive Kurse ({count})", + "noActiveCourses": "Keine aktiven Kurse in diesem Monat." }, "detail": { "notFound": "Kurs nicht gefunden", @@ -37,7 +74,16 @@ "viewAttendance": "Anwesenheit anzeigen", "noParticipants": "Noch keine Teilnehmer.", "noSessions": "Noch keine Termine.", - "addParticipant": "Teilnehmer hinzufügen" + "addParticipant": "Teilnehmer hinzufügen", + "edit": "Bearbeiten", + "instructor": "Dozent", + "dateRange": "Beginn – Ende", + "viewAll": "Alle anzeigen", + "attendance": "Anwesenheit", + "name": "Name", + "email": "E-Mail", + "date": "Datum", + "cancelled": "Abgesagt" }, "form": { "basicData": "Grunddaten", @@ -65,28 +111,54 @@ "open": "Offen", "running": "Laufend", "completed": "Abgeschlossen", - "cancelled": "Abgesagt" + "cancelled": "Abgesagt", + "active": "Aktiv" }, "enrollment": { "enrolled": "Eingeschrieben", "waitlisted": "Warteliste", "cancelled": "Storniert", "completed": "Abgeschlossen", - "enrolledAt": "Eingeschrieben am" + "enrolledAt": "Eingeschrieben am", + "title": "Anmeldestatus", + "registrationDate": "Anmeldedatum" + }, + "participants": { + "title": "Teilnehmer", + "add": "Teilnehmer anmelden", + "none": "Keine Teilnehmer", + "noneDescription": "Melden Sie den ersten Teilnehmer für diesen Kurs an.", + "allTitle": "Alle Teilnehmer ({count})" }, "attendance": { "title": "Anwesenheit", "present": "Anwesend", "absent": "Abwesend", "excused": "Entschuldigt", - "session": "Termin" + "session": "Termin", + "noSessions": "Keine Termine vorhanden", + "noSessionsDescription": "Erstellen Sie zuerst Termine für diesen Kurs.", + "selectSession": "Termin auswählen", + "attendanceList": "Anwesenheitsliste", + "selectSessionPrompt": "Bitte wählen Sie einen Termin aus" }, "calendar": { "title": "Kurskalender", "courseDay": "Kurstag", "free": "Frei", "today": "Heute", - "weekdays": ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"], + "overview": "Kurstermine im Überblick", + "activeCourses": "Aktive Kurse ({count})", + "noActiveCourses": "Keine aktiven Kurse in diesem Monat.", + "weekdays": [ + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa", + "So" + ], "months": [ "Januar", "Februar", @@ -100,21 +172,42 @@ "Oktober", "November", "Dezember" - ] + ], + "previousMonth": "Vorheriger Monat", + "nextMonth": "Nächster Monat", + "backToCourses": "Zurück zu Kursen" }, "categories": { "title": "Kategorien", "newCategory": "Neue Kategorie", - "noCategories": "Keine Kategorien vorhanden." + "noCategories": "Keine Kategorien vorhanden.", + "manage": "Kurskategorien verwalten", + "allTitle": "Alle Kategorien ({count})", + "namePlaceholder": "z. B. Sprachkurse", + "descriptionPlaceholder": "Kurze Beschreibung" }, "instructors": { "title": "Kursleiter", "newInstructor": "Neuer Kursleiter", - "noInstructors": "Keine Kursleiter vorhanden." + "noInstructors": "Keine Kursleiter vorhanden.", + "manage": "Dozentenpool verwalten", + "allTitle": "Alle Dozenten ({count})", + "qualification": "Qualifikation", + "hourlyRate": "Stundensatz", + "firstNamePlaceholder": "Vorname", + "lastNamePlaceholder": "Nachname", + "qualificationsPlaceholder": "z. B. Zertifizierter Trainer, Erste-Hilfe-Ausbilder" }, "locations": { "title": "Standorte", "newLocation": "Neuer Standort", - "noLocations": "Keine Standorte vorhanden." + "noLocations": "Keine Standorte vorhanden.", + "manage": "Kurs- und Veranstaltungsorte verwalten", + "allTitle": "Alle Orte ({count})", + "noLocationsDescription": "Fügen Sie Ihren ersten Veranstaltungsort hinzu.", + "newLocationLabel": "Neuer Ort", + "namePlaceholder": "z. B. Vereinsheim", + "addressPlaceholder": "Musterstr. 1, 12345 Musterstadt", + "roomPlaceholder": "z. B. Raum 101" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/events.json b/apps/web/i18n/messages/de/events.json index 3c57cf7a7..94f42c84c 100644 --- a/apps/web/i18n/messages/de/events.json +++ b/apps/web/i18n/messages/de/events.json @@ -52,7 +52,8 @@ "full": "Ausgebucht", "running": "Laufend", "completed": "Abgeschlossen", - "cancelled": "Abgesagt" + "cancelled": "Abgesagt", + "registration_open": "Anmeldung offen" }, "registrationStatus": { "pending": "Ausstehend", diff --git a/apps/web/i18n/messages/de/finance.json b/apps/web/i18n/messages/de/finance.json index 4535e6ec5..02e8003b0 100644 --- a/apps/web/i18n/messages/de/finance.json +++ b/apps/web/i18n/messages/de/finance.json @@ -18,6 +18,7 @@ "invoices": { "title": "Rechnungen", "newInvoice": "Neue Rechnung", + "newInvoiceDesc": "Rechnung mit Positionen erstellen", "noInvoices": "Keine Rechnungen vorhanden", "createFirst": "Erstellen Sie Ihre erste Rechnung.", "invoiceNumber": "Rechnungsnr.", @@ -25,7 +26,14 @@ "issueDate": "Rechnungsdatum", "dueDate": "Fälligkeitsdatum", "amount": "Betrag", - "notFound": "Rechnung nicht gefunden" + "notFound": "Rechnung nicht gefunden", + "detailTitle": "Rechnungsdetails", + "backToList": "Zurück zu Rechnungen", + "invoiceLabel": "Rechnung {number}", + "unitPriceCol": "Einzelpreis", + "totalCol": "Gesamt", + "subtotalLabel": "Zwischensumme", + "noItems": "Keine Positionen vorhanden." }, "invoiceForm": { "title": "Rechnungsdaten", @@ -61,6 +69,7 @@ "sepa": { "title": "SEPA-Einzüge", "newBatch": "Neuer Einzug", + "newBatchDesc": "SEPA-Lastschrifteinzug erstellen", "noBatches": "Keine SEPA-Einzüge vorhanden", "createFirst": "Erstellen Sie Ihren ersten SEPA-Einzug.", "directDebit": "Lastschrift", @@ -69,7 +78,12 @@ "totalAmount": "Gesamtbetrag", "itemCount": "Positionen", "downloadXml": "XML herunterladen", - "notFound": "Einzug nicht gefunden" + "notFound": "Einzug nicht gefunden", + "detailTitle": "SEPA-Einzug Details", + "backToList": "Zurück zu SEPA-Lastschriften", + "itemCountLabel": "Anzahl", + "noItems": "Keine Positionen vorhanden.", + "batchFallbackName": "SEPA-Einzug" }, "sepaBatchForm": { "title": "SEPA-Einzug erstellen", @@ -88,26 +102,62 @@ "ready": "Bereit", "submitted": "Eingereicht", "executed": "Abgeschlossen", + "completed": "Abgeschlossen", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen" }, "sepaItemStatus": { "pending": "Ausstehend", "success": "Verarbeitet", + "processed": "Verarbeitet", "failed": "Fehlgeschlagen", "rejected": "Abgelehnt" }, "payments": { "title": "Zahlungsübersicht", + "subtitle": "Zusammenfassung aller Zahlungen und offenen Beträge", "paidInvoices": "Bezahlte Rechnungen", "openInvoices": "Offene Rechnungen", "overdueInvoices": "Überfällige Rechnungen", - "sepaBatches": "SEPA-Einzüge" + "sepaBatches": "SEPA-Einzüge", + "statPaid": "Bezahlt", + "statOpen": "Offen", + "statOverdue": "Überfällig", + "batchUnit": "Einzüge", + "viewInvoices": "Rechnungen anzeigen", + "viewBatches": "Einzüge anzeigen", + "invoicesOpenSummary": "{count} Rechnungen mit einem Gesamtbetrag von {total} sind offen.", + "noOpenInvoices": "Keine offenen Rechnungen vorhanden.", + "batchSummary": "{count} SEPA-Einzüge mit einem Gesamtvolumen von {total}.", + "noBatchesFound": "Keine SEPA-Einzüge vorhanden." }, "common": { "cancel": "Abbrechen", "creating": "Wird erstellt...", "membershipFee": "Mitgliedsbeitrag", - "sepaDirectDebit": "SEPA Einzug" + "sepaDirectDebit": "SEPA Einzug", + "showAll": "Alle anzeigen", + "page": "Seite", + "of": "von", + "noData": "Keine Daten", + "amount": "Betrag", + "status": "Status", + "previous": "Zurück", + "next": "Weiter", + "type": "Typ", + "date": "Datum", + "description": "Beschreibung" + }, + "status": { + "draft": "Entwurf", + "sent": "Versendet", + "paid": "Bezahlt", + "overdue": "Überfällig", + "cancelled": "Storniert", + "credited": "Gutgeschrieben", + "submitted": "Eingereicht", + "processing": "In Bearbeitung", + "completed": "Abgeschlossen", + "failed": "Fehlgeschlagen" } } diff --git a/apps/web/i18n/messages/de/meetings.json b/apps/web/i18n/messages/de/meetings.json index 041e40816..c36f14794 100644 --- a/apps/web/i18n/messages/de/meetings.json +++ b/apps/web/i18n/messages/de/meetings.json @@ -8,7 +8,14 @@ "pages": { "overviewTitle": "Sitzungsprotokolle", "protocolsTitle": "Sitzungsprotokolle - Protokolle", - "tasksTitle": "Sitzungsprotokolle - Aufgaben" + "tasksTitle": "Sitzungsprotokolle - Aufgaben", + "newProtocolTitle": "Neues Protokoll", + "protocolDetailTitle": "Sitzungsprotokoll", + "notFound": "Protokoll nicht gefunden", + "backToList": "Zurück zur Übersicht", + "back": "Zurück", + "statusPublished": "Veröffentlicht", + "statusDraft": "Entwurf" }, "dashboard": { "title": "Sitzungsprotokolle – Übersicht", @@ -77,4 +84,4 @@ "committee": "Ausschusssitzung", "other": "Sonstige" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/members.json b/apps/web/i18n/messages/de/members.json index 0c8d6e868..64cc9ede1 100644 --- a/apps/web/i18n/messages/de/members.json +++ b/apps/web/i18n/messages/de/members.json @@ -7,7 +7,8 @@ "departments": "Abteilungen", "cards": "Mitgliedsausweise", "import": "Import", - "statistics": "Statistiken" + "statistics": "Statistiken", + "invitations": "Portal-Einladungen" }, "list": { "searchPlaceholder": "Name, E-Mail oder Mitgliedsnr. suchen...", @@ -57,6 +58,8 @@ "form": { "createTitle": "Neues Mitglied anlegen", "editTitle": "Mitglied bearbeiten", + "newMemberTitle": "Neues Mitglied", + "newMemberDescription": "Mitglied manuell anlegen", "created": "Mitglied erfolgreich erstellt", "updated": "Mitglied aktualisiert", "errorCreating": "Fehler beim Erstellen", @@ -72,8 +75,15 @@ "excluded": "Ausgeschlossen", "deceased": "Verstorben" }, + "invitations": { + "title": "Portal-Einladungen", + "subtitle": "Einladungen zum Mitgliederportal verwalten", + "emailPlaceholder": "E-Mail eingeben...", + "emptyDescription": "Senden Sie die erste Einladung zum Mitgliederportal." + }, "applications": { "title": "Aufnahmeanträge ({count})", + "subtitle": "Mitgliedsanträge bearbeiten", "noApplications": "Keine offenen Aufnahmeanträge", "approve": "Genehmigen", "reject": "Ablehnen", @@ -87,6 +97,7 @@ }, "dues": { "title": "Beitragskategorien", + "subtitle": "Mitgliedsbeiträge verwalten", "name": "Name", "description": "Beschreibung", "amount": "Betrag", @@ -121,12 +132,35 @@ }, "departments": { "title": "Abteilungen", + "subtitle": "Sparten und Abteilungen verwalten", "noDepartments": "Keine Abteilungen vorhanden.", "createFirst": "Erstellen Sie Ihre erste Abteilung.", - "newDepartment": "Neue Abteilung" + "newDepartment": "Neue Abteilung", + "name": "Name", + "namePlaceholder": "z. B. Jugendabteilung", + "description": "Beschreibung", + "descriptionPlaceholder": "Kurze Beschreibung", + "actions": "Aktionen", + "created": "Abteilung erstellt", + "createError": "Fehler beim Erstellen der Abteilung", + "createDialogDescription": "Erstellen Sie eine neue Abteilung oder Sparte für Ihren Verein.", + "descriptionLabel": "Beschreibung (optional)", + "creating": "Wird erstellt…", + "create": "Erstellen", + "deleteTitle": "Abteilung löschen?", + "deleteConfirm": "\"{name}\" wird unwiderruflich gelöscht. Mitglieder dieser Abteilung werden keiner Abteilung mehr zugeordnet.", + "delete": "Löschen", + "deleteAria": "Abteilung löschen", + "cancel": "Abbrechen" }, "cards": { "title": "Mitgliedsausweise", + "subtitle": "Ausweise erstellen und verwalten", + "noMembers": "Keine aktiven Mitglieder", + "noMembersDesc": "Erstellen Sie zuerst Mitglieder, um Ausweise zu generieren.", + "inDevelopment": "Feature in Entwicklung", + "inDevelopmentDesc": "Die Ausweiserstellung für {count} aktive Mitglieder wird derzeit entwickelt. Diese Funktion wird in einem kommenden Update verfügbar sein.", + "manageMembersLabel": "Mitglieder verwalten", "memberCard": "MITGLIEDSAUSWEIS", "memberSince": "Mitglied seit", "validUntil": "Gültig bis", @@ -135,6 +169,7 @@ }, "import": { "title": "Mitglieder importieren", + "subtitle": "CSV-Datei importieren", "selectFile": "CSV-Datei auswählen", "mapColumns": "Spalten zuordnen", "preview": "Vorschau", @@ -165,4 +200,4 @@ "bic": "BIC", "accountHolder": "Kontoinhaber" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/newsletter.json b/apps/web/i18n/messages/de/newsletter.json index 362b2f314..4219bdf97 100644 --- a/apps/web/i18n/messages/de/newsletter.json +++ b/apps/web/i18n/messages/de/newsletter.json @@ -42,7 +42,10 @@ "scheduledDate": "Geplanter Versand (optional)", "scheduleHelp": "Leer lassen, um den Newsletter als Entwurf zu speichern.", "created": "Newsletter erfolgreich erstellt", - "errorCreating": "Fehler beim Erstellen des Newsletters" + "errorCreating": "Fehler beim Erstellen des Newsletters", + "editTitle": "Newsletter bearbeiten", + "newTitle": "Neuer Newsletter", + "newDescription": "Newsletter-Kampagne erstellen" }, "templates": { "title": "Newsletter-Vorlagen", @@ -60,7 +63,9 @@ "scheduled": "Geplant", "sending": "Wird versendet", "sent": "Gesendet", - "failed": "Fehlgeschlagen" + "failed": "Fehlgeschlagen", + "pending": "Ausstehend", + "bounced": "Zurückgewiesen" }, "recipientStatus": { "pending": "Ausstehend", @@ -71,6 +76,8 @@ "common": { "cancel": "Abbrechen", "creating": "Wird erstellt...", - "create": "Newsletter erstellen" + "create": "Newsletter erstellen", + "previous": "Zurück", + "next": "Weiter" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/de/portal.json b/apps/web/i18n/messages/de/portal.json new file mode 100644 index 000000000..1710ae986 --- /dev/null +++ b/apps/web/i18n/messages/de/portal.json @@ -0,0 +1,79 @@ +{ + "home": { + "membersArea": "Mitgliederbereich", + "welcome": "Willkommen", + "welcomeUser": "Willkommen, {name}!", + "backToWebsite": "← Website", + "backToPortal": "← Zurück zum Portal", + "backToWebsiteFull": "← Zurück zur Website", + "orgNotFound": "Organisation nicht gefunden", + "profile": "Mein Profil", + "profileDesc": "Kontaktdaten und Datenschutz", + "documents": "Dokumente", + "documentsDesc": "Rechnungen und Bescheinigungen", + "memberCard": "Mitgliedsausweis", + "memberCardDesc": "Digital anzeigen" + }, + "invite": { + "invalidTitle": "Einladung ungültig", + "invalidDesc": "Diese Einladung ist abgelaufen, wurde bereits verwendet oder ist ungültig. Bitte wenden Sie sich an Ihren Vereinsadministrator.", + "expiredTitle": "Einladung abgelaufen", + "expiredDesc": "Diese Einladung ist am {date} abgelaufen. Bitte fordern Sie eine neue Einladung an.", + "title": "Einladung zum Mitgliederbereich", + "invitedDesc": "Sie wurden eingeladen, ein Konto für den Mitgliederbereich zu erstellen. Damit können Sie Ihr Profil einsehen, Dokumente herunterladen und Ihre Datenschutz-Einstellungen verwalten.", + "emailLabel": "E-Mail-Adresse", + "emailNote": "Ihre E-Mail-Adresse wurde vom Verein vorgegeben.", + "passwordLabel": "Passwort festlegen *", + "passwordPlaceholder": "Mindestens 8 Zeichen", + "passwordConfirmLabel": "Passwort wiederholen *", + "passwordConfirmPlaceholder": "Passwort bestätigen", + "submit": "Konto erstellen & Einladung annehmen", + "hasAccount": "Bereits ein Konto?", + "login": "Anmelden", + "backToWebsite": "← Zur Website" + }, + "profile": { + "title": "Mein Profil", + "noMemberTitle": "Kein Mitglied", + "noMemberDesc": "Ihr Benutzerkonto ist nicht mit einem Mitgliedsprofil in diesem Verein verknüpft. Bitte wenden Sie sich an Ihren Vereinsadministrator.", + "back": "← Zurück", + "memberSince": "Nr. {number} — Mitglied seit {date}", + "contactData": "Kontaktdaten", + "firstName": "Vorname", + "lastName": "Nachname", + "email": "E-Mail", + "phone": "Telefon", + "mobile": "Mobil", + "address": "Adresse", + "street": "Straße", + "houseNumber": "Hausnummer", + "postalCode": "PLZ", + "city": "Ort", + "loginMethods": "Anmeldemethoden", + "privacy": "Datenschutz-Einwilligungen", + "gdprNewsletter": "Newsletter per E-Mail", + "gdprInternet": "Veröffentlichung auf der Homepage", + "gdprPrint": "Veröffentlichung in der Vereinszeitung", + "gdprBirthday": "Geburtstagsinfo an Mitglieder", + "saveChanges": "Änderungen speichern" + }, + "documents": { + "title": "Meine Dokumente", + "subtitle": "Dokumente und Rechnungen", + "available": "Verfügbare Dokumente", + "empty": "Keine Dokumente vorhanden", + "typeInvoice": "Rechnung", + "typeDocument": "Dokument", + "statusPaid": "Bezahlt", + "statusOpen": "Offen", + "statusSigned": "Unterschrieben", + "downloadPdf": "PDF" + }, + "linkedAccounts": { + "title": "Konto trennen?", + "disconnectDesc": "Ihr Social-Login-Konto wird getrennt. Sie können sich weiterhin per E-Mail und Passwort anmelden.", + "connect": "Konto verknüpfen für schnellere Anmeldung", + "disconnect": "Trennen", + "cancel": "Abbrechen" + } +} diff --git a/apps/web/i18n/messages/de/siteBuilder.json b/apps/web/i18n/messages/de/siteBuilder.json index 47375ca2f..230a78783 100644 --- a/apps/web/i18n/messages/de/siteBuilder.json +++ b/apps/web/i18n/messages/de/siteBuilder.json @@ -7,6 +7,7 @@ "pages": { "title": "Seiten", "newPage": "Neue Seite", + "newPageDescription": "Seite für Ihre Vereinswebsite erstellen", "noPages": "Keine Seiten vorhanden", "createFirst": "Erstellen Sie Ihre erste Seite.", "pageTitle": "Seitentitel *", @@ -18,21 +19,65 @@ "errorCreating": "Fehler beim Erstellen", "notFound": "Seite nicht gefunden", "published": "Seite veröffentlicht", - "error": "Fehler" + "error": "Fehler", + "colTitle": "Titel", + "colUrl": "URL", + "colStatus": "Status", + "colHomepage": "Startseite", + "colUpdated": "Aktualisiert", + "colActions": "Aktionen", + "statusPublished": "Veröffentlicht", + "statusDraft": "Entwurf", + "homepageLabel": "Startseite", + "edit": "Bearbeiten", + "totalPages": "Seiten", + "totalPublished": "Veröffentlicht", + "statusLabel": "Status", + "online": "Online", + "offline": "Offline", + "firstPage": "Erste Seite erstellen", + "noPageDesc": "Erstellen Sie Ihre erste Seite mit dem visuellen Editor.", + "noPagesYet": "Noch keine Seiten", + "hide": "Verstecken", + "publish": "Veröffentlichen", + "hideTitle": "Seite verstecken?", + "publishTitle": "Seite veröffentlichen?", + "hideDesc": "Die Seite wird für Besucher nicht mehr sichtbar sein.", + "publishDesc": "Die Seite wird öffentlich auf Ihrer Vereinswebseite sichtbar.", + "toggleError": "Status konnte nicht geändert werden.", + "cancelAction": "Abbrechen" + }, + "site": { + "viewSite": "Website ansehen", + "stats": { + "pages": "Seiten", + "published": "Veröffentlicht", + "status": "Status" + } }, "posts": { "title": "Beiträge", "newPost": "Neuer Beitrag", + "newPostDescription": "Beitrag erstellen", "noPosts": "Keine Beiträge vorhanden", "createFirst": "Erstellen Sie Ihren ersten Beitrag.", "postTitle": "Titel *", "content": "Beitragsinhalt (HTML erlaubt)...", "excerpt": "Kurzfassung", "postCreated": "Beitrag erstellt", - "errorCreating": "Fehler" + "errorCreating": "Fehler", + "colTitle": "Titel", + "colStatus": "Status", + "colCreated": "Erstellt", + "manage": "Neuigkeiten und Artikel verwalten", + "noPosts2": "Keine Beiträge", + "noPostDesc": "Erstellen Sie Ihren ersten Beitrag.", + "createPostLabel": "Beitrag erstellen" }, "settings": { - "title": "Einstellungen", + "title": "Website-Einstellungen", + "siteTitle": "Einstellungen", + "description": "Design und Kontaktdaten", "saved": "Einstellungen gespeichert", "error": "Fehler" }, @@ -49,5 +94,11 @@ "events": "Veranstaltungen", "loginError": "Fehler bei der Anmeldung", "connectionError": "Verbindungsfehler" + }, + "dashboard": { + "title": "Website-Baukasten", + "description": "Ihre Vereinswebseite verwalten", + "btnSettings": "Einstellungen", + "btnPosts": "Beiträge ({count})" } } diff --git a/apps/web/i18n/messages/en/bookings.json b/apps/web/i18n/messages/en/bookings.json index e9dd0b78d..1735d7169 100644 --- a/apps/web/i18n/messages/en/bookings.json +++ b/apps/web/i18n/messages/en/bookings.json @@ -18,14 +18,49 @@ "checkIn": "Check-in", "checkOut": "Check-out", "nights": "Nights", - "price": "Price" + "price": "Price", + "status": "Status", + "amount": "Amount", + "total": "Total", + "manage": "Manage rooms and bookings", + "search": "Search", + "reset": "Reset", + "noResults": "No bookings found", + "noResultsFor": "No results for \"{query}\".", + "allBookings": "All Bookings ({count})", + "searchResults": "Results ({count})" }, "detail": { + "title": "Booking Details", "notFound": "Booking not found", + "notFoundDesc": "Booking with ID \"{id}\" was not found.", + "backToBookings": "Back to Bookings", "guestInfo": "Guest Information", "roomInfo": "Room Information", "bookingDetails": "Booking Details", - "extras": "Extras" + "extras": "Extras", + "room": "Room", + "roomNumber": "Room Number", + "type": "Type", + "noRoom": "No room assigned", + "guest": "Guest", + "email": "Email", + "phone": "Phone", + "noGuest": "No guest assigned", + "stay": "Stay", + "adults": "Adults", + "children": "Children", + "amount": "Amount", + "totalPrice": "Total Price", + "notes": "Notes", + "actions": "Actions", + "changeStatus": "Change booking status", + "checkIn": "Check In", + "checkOut": "Check Out", + "cancel": "Cancel", + "cancelledStatus": "cancelled", + "completedStatus": "completed", + "noMoreActions": "This booking is {statusLabel} — no further actions available." }, "form": { "room": "Room *", @@ -51,21 +86,59 @@ "title": "Rooms", "newRoom": "New Room", "noRooms": "No rooms found", + "addFirst": "Add your first room.", + "manage": "Room Management", + "allRooms": "All Rooms ({count})", + "roomNumber": "Room No.", "name": "Name", "type": "Type", "capacity": "Capacity", - "price": "Price/Night" + "price": "Price/Night", + "active": "Active" }, "guests": { "title": "Guests", "newGuest": "New Guest", "noGuests": "No guests found", + "addFirst": "Add your first guest.", + "manage": "Guest Management", + "allGuests": "All Guests ({count})", "name": "Name", "email": "Email", "phone": "Phone", + "city": "City", + "country": "Country", "bookings": "Bookings" }, "calendar": { - "title": "Availability Calendar" + "title": "Availability Calendar", + "subtitle": "Room occupancy at a glance", + "occupied": "Occupied", + "free": "Free", + "today": "Today", + "bookingsThisMonth": "Bookings this month", + "daysOccupied": "{occupied} of {total} days occupied", + "previousMonth": "Previous Month", + "nextMonth": "Next Month", + "backToBookings": "Back to Bookings" + }, + "newBooking": { + "title": "New Booking", + "description": "Create booking" + }, + "common": { + "previous": "Previous", + "next": "Next", + "page": "Page", + "of": "of", + "entries": "entries", + "pageInfo": "Page {page} of {total} ({entries} entries)" + }, + "cancel": { + "title": "Cancel booking?", + "description": "This action cannot be undone. The booking will be permanently cancelled.", + "confirm": "Cancel Booking", + "cancel": "Dismiss", + "cancelling": "Cancelling..." } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/cms.json b/apps/web/i18n/messages/en/cms.json index d76e585e8..648fc1610 100644 --- a/apps/web/i18n/messages/en/cms.json +++ b/apps/web/i18n/messages/en/cms.json @@ -25,6 +25,9 @@ "advancedFilter": "Advanced Filter", "clearFilters": "Clear Filters", "noRecords": "No records found", + "notFound": "Not found", + "accountNotFound": "Account not found", + "record": "Record", "paginationSummary": "{total} records — Page {page} of {totalPages}", "paginationPrevious": "← Previous", "paginationNext": "Next →", @@ -167,7 +170,7 @@ }, "events": { "title": "Events", - "description": "Manage events and holiday programs", + "description": "Description", "newEvent": "New Event", "registrations": "Registrations", "holidayPasses": "Holiday Passes", @@ -180,7 +183,15 @@ "noEvents": "No events yet", "noEventsDescription": "Create your first event to get started.", "name": "Name", - "status": "Status", + "status": { + "planned": "Planned", + "open": "Open", + "full": "Full", + "running": "Running", + "completed": "Completed", + "cancelled": "Cancelled", + "registration_open": "Registration Open" + }, "paginationPage": "Page {page} of {totalPages}", "paginationPrevious": "Previous", "paginationNext": "Next", @@ -200,7 +211,19 @@ "price": "Price", "validFrom": "Valid From", "validUntil": "Valid Until", - "newEventDescription": "Create an event or holiday program" + "newEventDescription": "Create an event or holiday program", + "detailTitle": "Event Details", + "edit": "Edit", + "register": "Register", + "date": "Date", + "time": "Time", + "location": "Location", + "registrationsCount": "Registrations ({count})", + "noRegistrations": "No registrations yet", + "parentName": "Parent", + "notFound": "Event not found", + "editTitle": "Edit", + "statusLabel": "Status" }, "finance": { "title": "Finance", @@ -255,7 +278,7 @@ }, "audit": { "title": "Audit Log", - "description": "View change history", + "description": "Cross-tenant change log", "action": "Action", "user": "User", "table": "Table", @@ -267,7 +290,9 @@ "update": "Updated", "delete": "Deleted", "lock": "Locked" - } + }, + "paginationPrevious": "← Previous", + "paginationNext": "Next →" }, "permissions": { "modules.read": "Read Modules", @@ -289,7 +314,10 @@ "finance.write": "Edit Finance", "finance.sepa": "Execute SEPA Collections", "documents.generate": "Generate Documents", - "newsletter.send": "Send Newsletter" + "newsletter.send": "Send Newsletter", + "verband": { + "delete": "Delete Association Data" + } }, "status": { "active": "Active", @@ -297,5 +325,17 @@ "archived": "Archived", "locked": "Locked", "deleted": "Deleted" + }, + "fischerei": { + "inspectors": { + "removeInspector": "Remove Inspector" + }, + "waters": { + "location": "Location", + "waterTypes": { + "baggersee": "Gravel Pit", + "fluss": "River" + } + } } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/common.json b/apps/web/i18n/messages/en/common.json index af9ab96cb..fd6794f9b 100644 --- a/apps/web/i18n/messages/en/common.json +++ b/apps/web/i18n/messages/en/common.json @@ -18,6 +18,9 @@ "cancel": "Cancel", "clear": "Clear", "notFound": "Not Found", + "accountNotFound": "Account not found", + "accountNotFoundDescription": "The requested account does not exist or you do not have permission to access it.", + "backToDashboard": "Go to Dashboard", "backToHomePage": "Back to Home", "goBack": "Try Again", "genericServerError": "Sorry, something went wrong.", @@ -63,6 +66,11 @@ "previous": "Previous", "next": "Next", "recordCount": "{total} records", + "filesTitle": "File Management", + "filesSubtitle": "Upload and manage files", + "filesSearch": "Search files...", + "deleteFile": "Delete file", + "deleteFileConfirm": "Do you really want to delete this file? This action cannot be undone.", "routes": { "home": "Home", "account": "Account", @@ -70,7 +78,6 @@ "dashboard": "Dashboard", "settings": "Settings", "profile": "Profile", - "people": "People", "clubMembers": "Club Members", "memberApplications": "Applications", @@ -78,48 +85,40 @@ "memberCards": "Member Cards", "memberDues": "Dues Categories", "accessAndRoles": "Access & Roles", - "courseManagement": "Courses", "courseList": "All Courses", "courseCalendar": "Calendar", "courseInstructors": "Instructors", "courseLocations": "Locations", - "eventManagement": "Events", "eventList": "All Events", "eventRegistrations": "Registrations", "holidayPasses": "Holiday Passes", - "bookingManagement": "Bookings", "bookingList": "All Bookings", "bookingCalendar": "Availability Calendar", "bookingRooms": "Rooms", "bookingGuests": "Guests", - "financeManagement": "Finance", "financeOverview": "Overview", "financeInvoices": "Invoices", "financeSepa": "SEPA Batches", "financePayments": "Payments", - "documentManagement": "Documents", "documentOverview": "Overview", "documentGenerate": "Generate", "documentTemplates": "Templates", - + "files": "File Management", "newsletterManagement": "Newsletter", "newsletterCampaigns": "Campaigns", "newsletterNew": "New Newsletter", "newsletterTemplates": "Templates", - "siteBuilder": "Website", "sitePages": "Pages", "sitePosts": "Posts", "siteSettings": "Settings", - "customModules": "Custom Modules", "moduleList": "All Modules", - "fisheriesManagement": "Fisheries", "fisheriesOverview": "Overview", "fisheriesWaters": "Waters", @@ -127,12 +126,10 @@ "fisheriesCatchBooks": "Catch Books", "fisheriesPermits": "Permits", "fisheriesCompetitions": "Competitions", - "meetingProtocols": "Meeting Protocols", "meetingsOverview": "Overview", "meetingsProtocols": "Protocols", "meetingsTasks": "Open Tasks", - "associationManagement": "Association Management", "associationOverview": "Overview", "associationHierarchy": "Organization Structure", @@ -140,7 +137,6 @@ "associationEvents": "Shared Events", "associationReporting": "Reports", "associationTemplates": "Shared Templates", - "administration": "Administration", "accountSettings": "Account Settings" }, @@ -172,6 +168,28 @@ "reject": "Reject", "accept": "Accept" }, + "dashboard": { + "recentActivity": "Recent Activity", + "recentActivityDescription": "Latest bookings and events", + "recentActivityEmpty": "No activity yet", + "recentActivityEmptyDescription": "Recent bookings and events will appear here.", + "quickActions": "Quick Actions", + "quickActionsDescription": "Frequently used actions", + "newMember": "New Member", + "newCourse": "New Course", + "createNewsletter": "Create Newsletter", + "newBooking": "New Booking", + "newEvent": "New Event", + "bookingFrom": "Booking from", + "members": "Members", + "courses": "Courses", + "openInvoices": "Open Invoices", + "newsletters": "Newsletters", + "membersDescription": "{total} total, {pending} pending", + "coursesDescription": "{total} total, {participants} participants", + "openInvoicesDescription": "Drafts to send", + "newslettersDescription": "Created" + }, "dropzone": { "success": "Successfully uploaded {count} file(s)", "error": "Error uploading {count} file(s)", @@ -187,5 +205,20 @@ "dragAndDrop": "Drag and drop or", "select": "select files", "toUpload": "to upload" + }, + "error": { + "title": "Something went wrong", + "description": "An unexpected error occurred. Please try again.", + "retry": "Try again", + "toDashboard": "Go to Dashboard" + }, + "pagination": { + "previous": "Previous page", + "next": "Next page" + }, + "accountNotFoundCard": { + "title": "Account not found", + "description": "The requested account does not exist or you do not have permission to access it.", + "action": "Go to Dashboard" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/courses.json b/apps/web/i18n/messages/en/courses.json index 68ce07c83..3e5260d41 100644 --- a/apps/web/i18n/messages/en/courses.json +++ b/apps/web/i18n/messages/en/courses.json @@ -10,24 +10,61 @@ }, "pages": { "coursesTitle": "Courses", + "coursesDescription": "Manage course catalogue", "newCourseTitle": "New Course", + "newCourseDescription": "Create a course", + "editCourseTitle": "Edit", "calendarTitle": "Course Calendar", "categoriesTitle": "Course Categories", "instructorsTitle": "Instructors", "locationsTitle": "Locations", "statisticsTitle": "Course Statistics" }, + "common": { + "all": "All", + "status": "Status", + "previous": "Previous", + "next": "Next", + "page": "Page", + "of": "of", + "entries": "Entries", + "yes": "Yes", + "no": "No", + "name": "Name", + "email": "Email", + "phone": "Phone", + "date": "Date", + "address": "Address", + "room": "Room", + "parent": "Parent", + "description": "Description", + "edit": "Edit" + }, "list": { "searchPlaceholder": "Search courses...", - "title": "Courses ({count})", + "title": "All Courses ({count})", "noCourses": "No courses found", "createFirst": "Create your first course to get started.", "courseNumber": "Course No.", - "courseName": "Course Name", + "courseName": "Name", "startDate": "Start", "endDate": "End", "participants": "Participants", - "fee": "Fee" + "fee": "Fee", + "status": "Status", + "capacity": "Capacity" + }, + "stats": { + "total": "Total", + "active": "Active", + "totalCourses": "Total Courses", + "activeCourses": "Active Courses", + "participants": "Participants", + "completed": "Completed", + "utilization": "Course Utilization", + "distribution": "Distribution", + "activeCoursesBadge": "Active Courses ({count})", + "noActiveCourses": "No active courses this month." }, "detail": { "notFound": "Course not found", @@ -37,7 +74,16 @@ "viewAttendance": "View attendance", "noParticipants": "No participants yet.", "noSessions": "No sessions yet.", - "addParticipant": "Add Participant" + "addParticipant": "Add Participant", + "edit": "Edit", + "instructor": "Instructor", + "dateRange": "Start – End", + "viewAll": "View all", + "attendance": "Attendance", + "name": "Name", + "email": "Email", + "date": "Date", + "cancelled": "Cancelled" }, "form": { "basicData": "Basic Data", @@ -65,28 +111,54 @@ "open": "Open", "running": "Running", "completed": "Completed", - "cancelled": "Cancelled" + "cancelled": "Cancelled", + "active": "Active" }, "enrollment": { "enrolled": "Enrolled", "waitlisted": "Waitlisted", "cancelled": "Cancelled", "completed": "Completed", - "enrolledAt": "Enrolled on" + "enrolledAt": "Enrolled on", + "title": "Enrollment Status", + "registrationDate": "Registration Date" + }, + "participants": { + "title": "Participants", + "add": "Add Participant", + "none": "No Participants", + "noneDescription": "Register the first participant for this course.", + "allTitle": "All Participants ({count})" }, "attendance": { "title": "Attendance", "present": "Present", "absent": "Absent", "excused": "Excused", - "session": "Session" + "session": "Session", + "noSessions": "No sessions yet", + "noSessionsDescription": "Create sessions for this course first.", + "selectSession": "Select Session", + "attendanceList": "Attendance List", + "selectSessionPrompt": "Please select a session" }, "calendar": { "title": "Course Calendar", "courseDay": "Course Day", "free": "Free", "today": "Today", - "weekdays": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + "overview": "Overview of course dates", + "activeCourses": "Active Courses ({count})", + "noActiveCourses": "No active courses this month.", + "weekdays": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ], "months": [ "January", "February", @@ -100,21 +172,42 @@ "October", "November", "December" - ] + ], + "previousMonth": "Previous Month", + "nextMonth": "Next Month", + "backToCourses": "Back to Courses" }, "categories": { "title": "Categories", "newCategory": "New Category", - "noCategories": "No categories found." + "noCategories": "No categories found.", + "manage": "Manage course categories", + "allTitle": "All Categories ({count})", + "namePlaceholder": "e.g. Language Courses", + "descriptionPlaceholder": "Short description" }, "instructors": { "title": "Instructors", "newInstructor": "New Instructor", - "noInstructors": "No instructors found." + "noInstructors": "No instructors found.", + "manage": "Manage instructor pool", + "allTitle": "All Instructors ({count})", + "qualification": "Qualification", + "hourlyRate": "Hourly Rate", + "firstNamePlaceholder": "First name", + "lastNamePlaceholder": "Last name", + "qualificationsPlaceholder": "e.g. Certified Trainer, First Aid Instructor" }, "locations": { "title": "Locations", "newLocation": "New Location", - "noLocations": "No locations found." + "noLocations": "No locations found.", + "manage": "Manage course and event locations", + "allTitle": "All Locations ({count})", + "noLocationsDescription": "Add your first venue.", + "newLocationLabel": "New Location", + "namePlaceholder": "e.g. Club House", + "addressPlaceholder": "123 Main St, Springfield", + "roomPlaceholder": "e.g. Room 101" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/events.json b/apps/web/i18n/messages/en/events.json index bca4adc2c..9cddc0683 100644 --- a/apps/web/i18n/messages/en/events.json +++ b/apps/web/i18n/messages/en/events.json @@ -52,7 +52,8 @@ "full": "Full", "running": "Running", "completed": "Completed", - "cancelled": "Cancelled" + "cancelled": "Cancelled", + "registration_open": "Registration Open" }, "registrationStatus": { "pending": "Pending", diff --git a/apps/web/i18n/messages/en/finance.json b/apps/web/i18n/messages/en/finance.json index 22f3844e2..88593b0d6 100644 --- a/apps/web/i18n/messages/en/finance.json +++ b/apps/web/i18n/messages/en/finance.json @@ -18,6 +18,7 @@ "invoices": { "title": "Invoices", "newInvoice": "New Invoice", + "newInvoiceDesc": "Create invoice with line items", "noInvoices": "No invoices found", "createFirst": "Create your first invoice.", "invoiceNumber": "Invoice No.", @@ -25,7 +26,14 @@ "issueDate": "Issue Date", "dueDate": "Due Date", "amount": "Amount", - "notFound": "Invoice not found" + "notFound": "Invoice not found", + "detailTitle": "Invoice Details", + "backToList": "Back to Invoices", + "invoiceLabel": "Invoice {number}", + "unitPriceCol": "Unit Price", + "totalCol": "Total", + "subtotalLabel": "Subtotal", + "noItems": "No line items found." }, "invoiceForm": { "title": "Invoice Details", @@ -61,6 +69,7 @@ "sepa": { "title": "SEPA Batches", "newBatch": "New Batch", + "newBatchDesc": "Create SEPA direct debit batch", "noBatches": "No SEPA batches found", "createFirst": "Create your first SEPA batch.", "directDebit": "Direct Debit", @@ -69,7 +78,12 @@ "totalAmount": "Total Amount", "itemCount": "Items", "downloadXml": "Download XML", - "notFound": "Batch not found" + "notFound": "Batch not found", + "detailTitle": "SEPA Batch Details", + "backToList": "Back to SEPA Batches", + "itemCountLabel": "Count", + "noItems": "No items found.", + "batchFallbackName": "SEPA Batch" }, "sepaBatchForm": { "title": "Create SEPA Batch", @@ -88,26 +102,62 @@ "ready": "Ready", "submitted": "Submitted", "executed": "Executed", + "completed": "Completed", "failed": "Failed", "cancelled": "Cancelled" }, "sepaItemStatus": { "pending": "Pending", "success": "Processed", + "processed": "Processed", "failed": "Failed", "rejected": "Rejected" }, "payments": { "title": "Payment Overview", + "subtitle": "Summary of all payments and outstanding amounts", "paidInvoices": "Paid Invoices", "openInvoices": "Open Invoices", "overdueInvoices": "Overdue Invoices", - "sepaBatches": "SEPA Batches" + "sepaBatches": "SEPA Batches", + "statPaid": "Paid", + "statOpen": "Open", + "statOverdue": "Overdue", + "batchUnit": "batches", + "viewInvoices": "View Invoices", + "viewBatches": "View Batches", + "invoicesOpenSummary": "{count} invoices totaling {total} are open.", + "noOpenInvoices": "No open invoices.", + "batchSummary": "{count} SEPA batches totaling {total}.", + "noBatchesFound": "No SEPA batches found." }, "common": { "cancel": "Cancel", "creating": "Creating...", "membershipFee": "Membership Fee", - "sepaDirectDebit": "SEPA Direct Debit" + "sepaDirectDebit": "SEPA Direct Debit", + "showAll": "Show All", + "page": "Page", + "of": "of", + "noData": "No data", + "amount": "Amount", + "status": "Status", + "previous": "Previous", + "next": "Next", + "type": "Type", + "date": "Date", + "description": "Description" + }, + "status": { + "draft": "Draft", + "sent": "Sent", + "paid": "Paid", + "overdue": "Overdue", + "cancelled": "Cancelled", + "credited": "Credited", + "submitted": "Submitted", + "processing": "Processing", + "completed": "Completed", + "failed": "Failed" } } diff --git a/apps/web/i18n/messages/en/meetings.json b/apps/web/i18n/messages/en/meetings.json index 13c29c5cd..df6f6314f 100644 --- a/apps/web/i18n/messages/en/meetings.json +++ b/apps/web/i18n/messages/en/meetings.json @@ -8,7 +8,14 @@ "pages": { "overviewTitle": "Meeting Protocols", "protocolsTitle": "Meeting Protocols - Protocols", - "tasksTitle": "Meeting Protocols - Tasks" + "tasksTitle": "Meeting Protocols - Tasks", + "newProtocolTitle": "New Protocol", + "protocolDetailTitle": "Meeting Protocol", + "notFound": "Protocol not found", + "backToList": "Back to list", + "back": "Back", + "statusPublished": "Published", + "statusDraft": "Draft" }, "dashboard": { "title": "Meeting Protocols – Overview", @@ -77,4 +84,4 @@ "committee": "Committee Meeting", "other": "Other" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/members.json b/apps/web/i18n/messages/en/members.json index 635d923f6..86ece4094 100644 --- a/apps/web/i18n/messages/en/members.json +++ b/apps/web/i18n/messages/en/members.json @@ -7,7 +7,8 @@ "departments": "Departments", "cards": "Member Cards", "import": "Import", - "statistics": "Statistics" + "statistics": "Statistics", + "invitations": "Portal Invitations" }, "list": { "searchPlaceholder": "Search name, email, or member no...", @@ -57,6 +58,8 @@ "form": { "createTitle": "Create New Member", "editTitle": "Edit Member", + "newMemberTitle": "New Member", + "newMemberDescription": "Add member manually", "created": "Member created successfully", "updated": "Member updated", "errorCreating": "Error creating member", @@ -72,8 +75,15 @@ "excluded": "Excluded", "deceased": "Deceased" }, + "invitations": { + "title": "Portal Invitations", + "subtitle": "Manage invitations to the member portal", + "emailPlaceholder": "Enter email address...", + "emptyDescription": "Send the first invitation to the member portal." + }, "applications": { "title": "Membership Applications ({count})", + "subtitle": "Process membership applications", "noApplications": "No pending applications", "approve": "Approve", "reject": "Reject", @@ -87,6 +97,7 @@ }, "dues": { "title": "Dues Categories", + "subtitle": "Manage membership fees", "name": "Name", "description": "Description", "amount": "Amount", @@ -121,12 +132,35 @@ }, "departments": { "title": "Departments", + "subtitle": "Manage sections and departments", "noDepartments": "No departments found.", "createFirst": "Create your first department.", - "newDepartment": "New Department" + "newDepartment": "New Department", + "name": "Name", + "namePlaceholder": "e.g. Youth Division", + "description": "Description", + "descriptionPlaceholder": "Short description", + "actions": "Actions", + "created": "Department created", + "createError": "Failed to create department", + "createDialogDescription": "Create a new department or section for your organization.", + "descriptionLabel": "Description (optional)", + "creating": "Creating...", + "create": "Create", + "deleteTitle": "Delete department?", + "deleteConfirm": "\"{name}\" will be permanently deleted. Members of this department will no longer be assigned to any department.", + "delete": "Delete", + "deleteAria": "Delete department", + "cancel": "Cancel" }, "cards": { "title": "Member Cards", + "subtitle": "Create and manage member cards", + "noMembers": "No active members", + "noMembersDesc": "Create members first to generate cards.", + "inDevelopment": "Feature in Development", + "inDevelopmentDesc": "Card generation for {count} active members is currently in development. This feature will be available in an upcoming update.", + "manageMembersLabel": "Manage members", "memberCard": "MEMBER CARD", "memberSince": "Member since", "validUntil": "Valid until", @@ -135,6 +169,7 @@ }, "import": { "title": "Import Members", + "subtitle": "Import from CSV file", "selectFile": "Select CSV file", "mapColumns": "Map columns", "preview": "Preview", @@ -165,4 +200,4 @@ "bic": "BIC", "accountHolder": "Account Holder" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/newsletter.json b/apps/web/i18n/messages/en/newsletter.json index adab4cf4e..456f067de 100644 --- a/apps/web/i18n/messages/en/newsletter.json +++ b/apps/web/i18n/messages/en/newsletter.json @@ -42,7 +42,10 @@ "scheduledDate": "Scheduled Send (optional)", "scheduleHelp": "Leave empty to save the newsletter as a draft.", "created": "Newsletter created successfully", - "errorCreating": "Error creating newsletter" + "errorCreating": "Error creating newsletter", + "editTitle": "Edit Newsletter", + "newTitle": "New Newsletter", + "newDescription": "Create newsletter campaign" }, "templates": { "title": "Newsletter Templates", @@ -60,7 +63,9 @@ "scheduled": "Scheduled", "sending": "Sending", "sent": "Sent", - "failed": "Failed" + "failed": "Failed", + "pending": "Pending", + "bounced": "Bounced" }, "recipientStatus": { "pending": "Pending", @@ -71,6 +76,8 @@ "common": { "cancel": "Cancel", "creating": "Creating...", - "create": "Create Newsletter" + "create": "Create Newsletter", + "previous": "Previous", + "next": "Next" } -} +} \ No newline at end of file diff --git a/apps/web/i18n/messages/en/portal.json b/apps/web/i18n/messages/en/portal.json new file mode 100644 index 000000000..2b45537b8 --- /dev/null +++ b/apps/web/i18n/messages/en/portal.json @@ -0,0 +1,79 @@ +{ + "home": { + "membersArea": "Members Area", + "welcome": "Welcome", + "welcomeUser": "Welcome, {name}!", + "backToWebsite": "← Website", + "backToPortal": "← Back to Portal", + "backToWebsiteFull": "← Back to Website", + "orgNotFound": "Organisation not found", + "profile": "My Profile", + "profileDesc": "Contact details and privacy", + "documents": "Documents", + "documentsDesc": "Invoices and certificates", + "memberCard": "Membership Card", + "memberCardDesc": "View digitally" + }, + "invite": { + "invalidTitle": "Invitation invalid", + "invalidDesc": "This invitation has expired, has already been used, or is invalid. Please contact your club administrator.", + "expiredTitle": "Invitation expired", + "expiredDesc": "This invitation expired on {date}. Please request a new invitation.", + "title": "Invitation to the Members Area", + "invitedDesc": "You have been invited to create an account for the members area. This allows you to view your profile, download documents, and manage your privacy settings.", + "emailLabel": "Email Address", + "emailNote": "Your email address was provided by the club.", + "passwordLabel": "Set password *", + "passwordPlaceholder": "At least 8 characters", + "passwordConfirmLabel": "Repeat password *", + "passwordConfirmPlaceholder": "Confirm password", + "submit": "Create account & accept invitation", + "hasAccount": "Already have an account?", + "login": "Log in", + "backToWebsite": "← To Website" + }, + "profile": { + "title": "My Profile", + "noMemberTitle": "No Member", + "noMemberDesc": "Your user account is not linked to a member profile in this club. Please contact your club administrator.", + "back": "← Back", + "memberSince": "No. {number} — Member since {date}", + "contactData": "Contact Details", + "firstName": "First Name", + "lastName": "Last Name", + "email": "Email", + "phone": "Phone", + "mobile": "Mobile", + "address": "Address", + "street": "Street", + "houseNumber": "House Number", + "postalCode": "Postal Code", + "city": "City", + "loginMethods": "Login Methods", + "privacy": "Privacy Consents", + "gdprNewsletter": "Newsletter by email", + "gdprInternet": "Publication on the homepage", + "gdprPrint": "Publication in the club newsletter", + "gdprBirthday": "Birthday info for members", + "saveChanges": "Save Changes" + }, + "documents": { + "title": "My Documents", + "subtitle": "Documents and invoices", + "available": "Available Documents", + "empty": "No documents available", + "typeInvoice": "Invoice", + "typeDocument": "Document", + "statusPaid": "Paid", + "statusOpen": "Open", + "statusSigned": "Signed", + "downloadPdf": "PDF" + }, + "linkedAccounts": { + "title": "Disconnect account?", + "disconnectDesc": "Your social login account will be disconnected. You can still log in with email and password.", + "connect": "Link account for faster login", + "disconnect": "Disconnect", + "cancel": "Cancel" + } +} diff --git a/apps/web/i18n/messages/en/siteBuilder.json b/apps/web/i18n/messages/en/siteBuilder.json index a83e0cf5a..49c486dfb 100644 --- a/apps/web/i18n/messages/en/siteBuilder.json +++ b/apps/web/i18n/messages/en/siteBuilder.json @@ -7,6 +7,7 @@ "pages": { "title": "Pages", "newPage": "New Page", + "newPageDescription": "Create a page for your club website", "noPages": "No pages found", "createFirst": "Create your first page.", "pageTitle": "Page Title *", @@ -18,21 +19,65 @@ "errorCreating": "Error creating page", "notFound": "Page not found", "published": "Page published", - "error": "Error" + "error": "Error", + "colTitle": "Title", + "colUrl": "URL", + "colStatus": "Status", + "colHomepage": "Homepage", + "colUpdated": "Updated", + "colActions": "Actions", + "statusPublished": "Published", + "statusDraft": "Draft", + "homepageLabel": "Homepage", + "edit": "Edit", + "totalPages": "Pages", + "totalPublished": "Published", + "statusLabel": "Status", + "online": "Online", + "offline": "Offline", + "firstPage": "Create First Page", + "noPageDesc": "Create your first page with the visual editor.", + "noPagesYet": "No pages yet", + "hide": "Hide", + "publish": "Publish", + "hideTitle": "Hide page?", + "publishTitle": "Publish page?", + "hideDesc": "The page will no longer be visible to visitors.", + "publishDesc": "The page will be publicly visible on your club website.", + "toggleError": "Could not change status.", + "cancelAction": "Cancel" + }, + "site": { + "viewSite": "View Site", + "stats": { + "pages": "Pages", + "published": "Published", + "status": "Status" + } }, "posts": { "title": "Posts", "newPost": "New Post", + "newPostDescription": "Create a post", "noPosts": "No posts found", "createFirst": "Create your first post.", "postTitle": "Title *", "content": "Post content (HTML allowed)...", "excerpt": "Excerpt", "postCreated": "Post created", - "errorCreating": "Error" + "errorCreating": "Error", + "colTitle": "Title", + "colStatus": "Status", + "colCreated": "Created", + "manage": "Manage news and articles", + "noPosts2": "No posts", + "noPostDesc": "Create your first post.", + "createPostLabel": "Create Post" }, "settings": { - "title": "Settings", + "title": "Website Settings", + "siteTitle": "Settings", + "description": "Design and contact details", "saved": "Settings saved", "error": "Error" }, @@ -49,5 +94,11 @@ "events": "Events", "loginError": "Login error", "connectionError": "Connection error" + }, + "dashboard": { + "title": "Site Builder", + "description": "Manage your club website", + "btnSettings": "Settings", + "btnPosts": "Posts ({count})" } } diff --git a/apps/web/i18n/request.ts b/apps/web/i18n/request.ts index c8bf1faa0..44c89bd18 100644 --- a/apps/web/i18n/request.ts +++ b/apps/web/i18n/request.ts @@ -31,6 +31,7 @@ const namespaces = [ 'events', 'documents', 'bookings', + 'portal', ] as const; const isDevelopment = process.env.NODE_ENV === 'development'; diff --git a/apps/web/lib/status-badges.ts b/apps/web/lib/status-badges.ts index a50df0e67..24496be6f 100644 --- a/apps/web/lib/status-badges.ts +++ b/apps/web/lib/status-badges.ts @@ -9,12 +9,12 @@ export const MEMBER_STATUS_VARIANT: Record< excluded: 'destructive', }; -export const MEMBER_STATUS_LABEL: Record = { - active: 'Aktiv', - inactive: 'Inaktiv', - pending: 'Ausstehend', - resigned: 'Ausgetreten', - excluded: 'Ausgeschlossen', +export const MEMBER_STATUS_LABEL_KEYS: Record = { + active: 'status.active', + inactive: 'status.inactive', + pending: 'status.pending', + resigned: 'status.resigned', + excluded: 'status.excluded', }; export const INVOICE_STATUS_VARIANT: Record< @@ -28,12 +28,12 @@ export const INVOICE_STATUS_VARIANT: Record< cancelled: 'destructive', }; -export const INVOICE_STATUS_LABEL: Record = { - draft: 'Entwurf', - sent: 'Gesendet', - paid: 'Bezahlt', - overdue: 'Überfällig', - cancelled: 'Storniert', +export const INVOICE_STATUS_LABEL_KEYS: Record = { + draft: 'status.draft', + sent: 'status.sent', + paid: 'status.paid', + overdue: 'status.overdue', + cancelled: 'status.cancelled', }; export const BATCH_STATUS_VARIANT: Record< @@ -47,12 +47,12 @@ export const BATCH_STATUS_VARIANT: Record< failed: 'destructive', }; -export const BATCH_STATUS_LABEL: Record = { - draft: 'Entwurf', - submitted: 'Eingereicht', - processing: 'In Bearbeitung', - completed: 'Abgeschlossen', - failed: 'Fehlgeschlagen', +export const BATCH_STATUS_LABEL_KEYS: Record = { + draft: 'status.draft', + submitted: 'status.submitted', + processing: 'status.processing', + completed: 'status.completed', + failed: 'status.failed', }; export const NEWSLETTER_STATUS_VARIANT: Record< @@ -66,12 +66,12 @@ export const NEWSLETTER_STATUS_VARIANT: Record< failed: 'destructive', }; -export const NEWSLETTER_STATUS_LABEL: Record = { - draft: 'Entwurf', - scheduled: 'Geplant', - sending: 'Wird gesendet', - sent: 'Gesendet', - failed: 'Fehlgeschlagen', +export const NEWSLETTER_STATUS_LABEL_KEYS: Record = { + draft: 'status.draft', + scheduled: 'status.scheduled', + sending: 'status.sending', + sent: 'status.sent', + failed: 'status.failed', }; export const EVENT_STATUS_VARIANT: Record< @@ -84,15 +84,17 @@ export const EVENT_STATUS_VARIANT: Record< running: 'default', completed: 'default', cancelled: 'destructive', + registration_open: 'default', }; -export const EVENT_STATUS_LABEL: Record = { - planned: 'Geplant', - open: 'Offen', - full: 'Ausgebucht', - running: 'Laufend', - completed: 'Abgeschlossen', - cancelled: 'Abgesagt', +export const EVENT_STATUS_LABEL_KEYS: Record = { + planned: 'status.planned', + open: 'status.open', + full: 'status.full', + running: 'status.running', + completed: 'status.completed', + cancelled: 'status.cancelled', + registration_open: 'status.registration_open', }; export const COURSE_STATUS_VARIANT: Record< @@ -107,13 +109,13 @@ export const COURSE_STATUS_VARIANT: Record< cancelled: 'destructive', }; -export const COURSE_STATUS_LABEL: Record = { - planned: 'Geplant', - open: 'Offen', - active: 'Aktiv', - running: 'Laufend', - completed: 'Abgeschlossen', - cancelled: 'Abgesagt', +export const COURSE_STATUS_LABEL_KEYS: Record = { + planned: 'status.planned', + open: 'status.open', + active: 'status.active', + running: 'status.running', + completed: 'status.completed', + cancelled: 'status.cancelled', }; export const APPLICATION_STATUS_VARIANT: Record< @@ -126,11 +128,11 @@ export const APPLICATION_STATUS_VARIANT: Record< rejected: 'destructive', }; -export const APPLICATION_STATUS_LABEL: Record = { - submitted: 'Eingereicht', - review: 'In Prüfung', - approved: 'Genehmigt', - rejected: 'Abgelehnt', +export const APPLICATION_STATUS_LABEL_KEYS: Record = { + submitted: 'status.submitted', + review: 'status.review', + approved: 'status.approved', + rejected: 'status.rejected', }; export const NEWSLETTER_RECIPIENT_STATUS_VARIANT: Record< @@ -143,9 +145,90 @@ export const NEWSLETTER_RECIPIENT_STATUS_VARIANT: Record< bounced: 'destructive', }; -export const NEWSLETTER_RECIPIENT_STATUS_LABEL: Record = { - pending: 'Ausstehend', - sent: 'Gesendet', - failed: 'Fehlgeschlagen', - bounced: 'Zurückgewiesen', +export const NEWSLETTER_RECIPIENT_STATUS_LABEL_KEYS: Record = { + pending: 'status.pending', + sent: 'status.sent', + failed: 'status.failed', + bounced: 'status.bounced', }; + +export const BOOKING_STATUS_VARIANT: Record< + string, + 'default' | 'secondary' | 'destructive' | 'outline' | 'info' +> = { + pending: 'secondary', + confirmed: 'default', + checked_in: 'info', + checked_out: 'outline', + cancelled: 'destructive', + no_show: 'destructive', +}; + +export const BOOKING_STATUS_LABEL_KEYS: Record = { + pending: 'status.pending', + confirmed: 'status.confirmed', + checked_in: 'status.checked_in', + checked_out: 'status.checked_out', + cancelled: 'status.cancelled', + no_show: 'status.no_show', +}; + +export const MODULE_STATUS_VARIANT: Record< + string, + 'default' | 'secondary' | 'destructive' | 'outline' +> = { + published: 'default', + draft: 'outline', + archived: 'secondary', +}; + +export const MODULE_STATUS_LABEL_KEYS: Record = { + published: 'status.published', + draft: 'status.draft', + archived: 'status.archived', +}; + +export const SITE_PAGE_STATUS_LABEL_KEYS: Record = { + published: 'status.published', + draft: 'status.draft', +}; + +export const SITE_POST_STATUS_VARIANT: Record = + { + published: 'default', + draft: 'secondary', + }; + +export const SITE_POST_STATUS_LABEL_KEYS: Record = { + published: 'status.published', + draft: 'status.draft', +}; + +// --------------------------------------------------------------------------- +// Legacy named exports kept for backward-compat during incremental migration. +// These are DEPRECATED — prefer the *_LABEL_KEYS variants + t() in consumers. +// --------------------------------------------------------------------------- +/** @deprecated Use MEMBER_STATUS_LABEL_KEYS + t() */ +export const MEMBER_STATUS_LABEL = MEMBER_STATUS_LABEL_KEYS; +/** @deprecated Use INVOICE_STATUS_LABEL_KEYS + t() */ +export const INVOICE_STATUS_LABEL = INVOICE_STATUS_LABEL_KEYS; +/** @deprecated Use BATCH_STATUS_LABEL_KEYS + t() */ +export const BATCH_STATUS_LABEL = BATCH_STATUS_LABEL_KEYS; +/** @deprecated Use NEWSLETTER_STATUS_LABEL_KEYS + t() */ +export const NEWSLETTER_STATUS_LABEL = NEWSLETTER_STATUS_LABEL_KEYS; +/** @deprecated Use EVENT_STATUS_LABEL_KEYS + t() */ +export const EVENT_STATUS_LABEL = EVENT_STATUS_LABEL_KEYS; +/** @deprecated Use COURSE_STATUS_LABEL_KEYS + t() */ +export const COURSE_STATUS_LABEL = COURSE_STATUS_LABEL_KEYS; +/** @deprecated Use APPLICATION_STATUS_LABEL_KEYS + t() */ +export const APPLICATION_STATUS_LABEL = APPLICATION_STATUS_LABEL_KEYS; +/** @deprecated Use NEWSLETTER_RECIPIENT_STATUS_LABEL_KEYS + t() */ +export const NEWSLETTER_RECIPIENT_STATUS_LABEL = NEWSLETTER_RECIPIENT_STATUS_LABEL_KEYS; +/** @deprecated Use BOOKING_STATUS_LABEL_KEYS + t() */ +export const BOOKING_STATUS_LABEL = BOOKING_STATUS_LABEL_KEYS; +/** @deprecated Use MODULE_STATUS_LABEL_KEYS + t() */ +export const MODULE_STATUS_LABEL = MODULE_STATUS_LABEL_KEYS; +/** @deprecated Use SITE_PAGE_STATUS_LABEL_KEYS + t() */ +export const SITE_PAGE_STATUS_LABEL = SITE_PAGE_STATUS_LABEL_KEYS; +/** @deprecated Use SITE_POST_STATUS_LABEL_KEYS + t() */ +export const SITE_POST_STATUS_LABEL = SITE_POST_STATUS_LABEL_KEYS;