Unify frontend German UI copy

This commit is contained in:
AzuTear
2026-06-17 14:11:30 +02:00
parent 12cf63ef49
commit 6b679937fe
12 changed files with 59 additions and 59 deletions
@@ -136,7 +136,7 @@ function clearFilters() {
<template>
<div class="space-y-6">
<AdminPageHeader
eyebrow="Candidates"
eyebrow="Kandidaten"
title="Kandidatenbasis pflegen"
description="Schneller finden, sauber pruefen, gezielt bearbeiten: die Kandidaten sind jetzt nach Jahr, Kategorie und Handle besser steuerbar."
/>
@@ -160,7 +160,7 @@ function clearFilters() {
<div class="border-b border-violet-100 bg-white/70 p-6">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.26em] text-violet-500">Kandidaten Center</p>
<p class="text-xs font-semibold uppercase tracking-[0.26em] text-violet-500">Kandidatenbereich</p>
<h2 class="mt-2 font-[Cormorant_Garamond] text-4xl text-violet-800">Suchen, pruefen, aktualisieren</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-500">
Filtere nach Kategorie oder Handle und bearbeite nur den Kandidaten, der wirklich geaendert werden muss.
@@ -229,8 +229,8 @@ function clearFilters() {
/>
</label>
<label class="space-y-2">
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Display Name</span>
<input v-model="candidateForms[candidate.id].displayName" type="text" class="h-12 w-full rounded-2xl border border-violet-200 bg-white px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100" placeholder="Display Name" />
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Anzeigename</span>
<input v-model="candidateForms[candidate.id].displayName" type="text" class="h-12 w-full rounded-2xl border border-violet-200 bg-white px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100" placeholder="Anzeigename" />
</label>
<label class="space-y-2">
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Handle</span>
@@ -275,7 +275,7 @@ function clearFilters() {
/>
</label>
<label class="space-y-2">
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Display Name</span>
<span class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Anzeigename</span>
<input v-model="newCandidateForm.displayName" type="text" class="h-12 w-full rounded-2xl border border-violet-200 px-4 text-sm outline-none transition focus:border-violet-400 focus:ring-4 focus:ring-violet-100" placeholder="z.B. Jayuhime" />
</label>
<label class="space-y-2">
@@ -302,7 +302,7 @@ function clearFilters() {
<Card class="p-5">
<div class="flex items-center gap-3">
<Sparkles class="h-5 w-5 text-amber-500" />
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Workflow</p>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Ablauf</p>
</div>
<div class="mt-4 space-y-3 text-sm leading-6 text-slate-600">
<p class="flex gap-3"><Users class="mt-0.5 h-4 w-4 shrink-0 text-violet-500" /> Erst Kandidaten suchen, damit du keine Duplikate anlegst.</p>
@@ -21,7 +21,7 @@ const quickActions = computed(() => [
hint: 'Freitext-Nominierungen bearbeiten',
},
{
label: 'Offene Risk Flags',
label: 'Offene Risikohinweise',
value: store.admin.riskFlags.length,
to: '/admin/risk',
hint: 'auffaellige Muster pruefen',
@@ -44,9 +44,9 @@ const quickActions = computed(() => [
<template>
<div class="space-y-6">
<AdminPageHeader
eyebrow="Dashboard"
eyebrow="Uebersicht"
title="Was braucht gerade Aufmerksamkeit?"
description="Das Dashboard zeigt dir zuerst die offenen Arbeitsstapel und fuehrt dich mit Quick Actions direkt in den passenden Bereich."
description="Die Uebersicht zeigt dir zuerst die offenen Arbeitsstapel und fuehrt dich mit Schnellzugriffen direkt in den passenden Bereich."
/>
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
@@ -76,10 +76,10 @@ const quickActions = computed(() => [
<div class="grid gap-6 xl:grid-cols-[1.05fr_0.95fr]">
<Card class="p-7">
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Top Kategorien nach Votes</h2>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Top Kategorien nach Stimmen</h2>
<DataTable :value="topCategories" class="mt-6" striped-rows>
<Column field="category" header="Kategorie" />
<Column field="votes" header="Votes">
<Column field="votes" header="Stimmen">
<template #body="{ data }">
{{ Number(data.votes).toLocaleString('de-DE') }}
</template>
+3 -3
View File
@@ -12,11 +12,11 @@ const store = useAwardsStore()
const authStore = useAuthStore()
const navItems = [
{ label: 'Dashboard', to: '/admin/dashboard', description: 'Status, Trends, Quick Actions', icon: LayoutDashboard, badge: () => null },
{ label: 'Uebersicht', to: '/admin/dashboard', description: 'Status, Trends, Schnellzugriffe', icon: LayoutDashboard, badge: () => null },
{ label: 'Jahre', to: '/admin/years', description: 'Jahresstatus, Kategorien, Limits', icon: CalendarCog, badge: () => `${store.adminSeasonDetail.categories.length}` },
{ label: 'Candidates', to: '/admin/candidates', description: 'Kandidatenbasis pro Jahr pflegen', icon: Users, badge: () => `${store.adminSeasonDetail.candidates.length}` },
{ label: 'Kandidaten', to: '/admin/candidates', description: 'Kandidatenbasis pro Jahr pflegen', icon: Users, badge: () => `${store.adminSeasonDetail.candidates.length}` },
{ label: 'Reviews', to: '/admin/reviews', description: 'Freitext-Nominierungen bearbeiten', icon: Sparkles, badge: () => `${store.adminSeasonDetail.pendingNominations.length}` },
{ label: 'Risk & Audit', to: '/admin/risk', description: 'Flags pruefen, Aktionen nachvollziehen', icon: AlertTriangle, badge: () => `${store.admin.riskFlags.length}` },
{ label: 'Risiko & Audit', to: '/admin/risk', description: 'Hinweise pruefen, Aktionen nachvollziehen', icon: AlertTriangle, badge: () => `${store.admin.riskFlags.length}` },
]
const currentSeason = computed(() => store.adminSeasonDetail)
@@ -72,7 +72,7 @@ async function rejectNomination(nominationId: number) {
try {
await store.rejectAdminNomination(nominationId, selectedSeasonId.value)
adminMessage.value = 'Nominierung wurde aus der Review Queue entfernt.'
adminMessage.value = 'Nominierung wurde aus der Review-Liste entfernt.'
} catch (error) {
adminError.value = error instanceof Error ? error.message : 'Nominierung konnte nicht verworfen werden.'
} finally {
@@ -94,7 +94,7 @@ async function rejectNomination(nominationId: number) {
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Review Queue</h2>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Review-Liste</h2>
<p class="mt-2 text-sm text-slate-500">Freitext-Nominierungen und Alias-Faelle, die das Team direkt in Kandidaten ueberfuehren oder verwerfen kann.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
@@ -114,7 +114,7 @@ async function rejectNomination(nominationId: number) {
v-model="reviewFilter"
type="text"
class="rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Nach Kategorie, Nominierung oder User filtern"
placeholder="Nach Kategorie, Nominierung oder Nutzer filtern"
/>
<div class="rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
Tipp: Suche erst nach dem Problemfall, dann entscheide uebernehmen oder verwerfen.
@@ -145,7 +145,7 @@ async function rejectNomination(nominationId: number) {
v-model="reviewForms[nomination.id].displayName"
type="text"
class="rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Display Name"
placeholder="Anzeigename"
/>
<input
v-model="reviewForms[nomination.id].channelSlug"
@@ -157,7 +157,7 @@ async function rejectNomination(nominationId: number) {
v-model="reviewForms[nomination.id].platform"
type="text"
class="rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Platform"
placeholder="Plattform"
/>
</div>
+12 -12
View File
@@ -43,9 +43,9 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
try {
await store.resolveRiskFlag(riskFlagId, status)
adminMessage.value = `Risk Flag ${riskFlagId} wurde als ${status} markiert.`
adminMessage.value = `Risikohinweis ${riskFlagId} wurde aktualisiert.`
} catch (error) {
adminError.value = error instanceof Error ? error.message : 'Risk Flag konnte nicht aktualisiert werden.'
adminError.value = error instanceof Error ? error.message : 'Risikohinweis konnte nicht aktualisiert werden.'
} finally {
riskSaving.value = null
}
@@ -55,7 +55,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<template>
<div class="space-y-6">
<AdminPageHeader
eyebrow="Risk & Audit"
eyebrow="Risiko & Audit"
title="Auffaellige Muster und Admin-Aktionen verfolgen"
description="Dieser Bereich trennt operative Risiko-Sichtung von der Nachvollziehbarkeit. So findest du sowohl offene Flags als auch bereits ausgefuehrte Eingriffe deutlich schneller."
/>
@@ -64,7 +64,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Risk Center</h2>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Risikopruefung</h2>
<p class="mt-2 text-sm text-slate-500">Auffaellige Login-, Nominierungs- und Voting-Muster fuer die manuelle Sichtung.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
@@ -84,10 +84,10 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
v-model="riskFilter"
type="text"
class="rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Risk Flags nach Typ, User oder IP filtern"
placeholder="Risikohinweise nach Typ, Nutzer oder IP filtern"
/>
<div class="rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
Tipp: Filtere erst auf den Problemtyp und resolve dann nur den geprueften Fall.
Tipp: Filtere erst auf den Problemtyp und markiere dann nur den geprueften Fall.
</div>
</div>
@@ -114,19 +114,19 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<div class="mt-4 flex flex-wrap justify-end gap-3">
<Button :disabled="riskSaving === flag.id" variant="secondary" @click="resolveRiskFlag(flag.id, 'dismissed')">
{{ riskSaving === flag.id ? 'Speichert ...' : 'Dismiss' }}
{{ riskSaving === flag.id ? 'Speichert ...' : 'Verwerfen' }}
</Button>
<Button :disabled="riskSaving === flag.id" @click="resolveRiskFlag(flag.id, 'resolved')">
{{ riskSaving === flag.id ? 'Speichert ...' : 'Resolve' }}
{{ riskSaving === flag.id ? 'Speichert ...' : 'Erledigt markieren' }}
</Button>
</div>
</div>
<p v-if="riskFlags.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine offenen Risk Flags vorhanden.
Keine offenen Risikohinweise vorhanden.
</p>
<p v-else-if="filteredRiskFlags.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine Risk Flags passen zum aktuellen Filter.
Keine Risikohinweise passen zum aktuellen Filter.
</p>
</div>
</Card>
@@ -134,7 +134,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Audit Log</h2>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Audit-Protokoll</h2>
<p class="mt-2 text-sm text-slate-500">Nachvollziehbare Admin-Aktionen fuer Kategorie-, Kandidaten- und Review-Aenderungen.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
@@ -147,7 +147,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
v-model="auditFilter"
type="text"
class="w-full rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Audit-Eintraege nach Aktion, Admin oder Entitaet filtern"
placeholder="Audit-Eintraege nach Aktion, Admin oder Objekt filtern"
/>
</div>
@@ -147,8 +147,8 @@ async function createCategory() {
<Card class="p-7">
<div class="flex flex-col gap-6">
<div>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Jahres-Setup</h2>
<p class="mt-2 text-sm text-slate-500">Phase, Current-Status und Basiskontext fuer das aktive Award-Jahr.</p>
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Jahres-Einrichtung</h2>
<p class="mt-2 text-sm text-slate-500">Phase, aktueller Status und Basiskontext fuer das aktive Award-Jahr.</p>
</div>
<div class="space-y-3">
@@ -162,7 +162,7 @@ async function createCategory() {
<label class="flex items-center gap-3 rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-4 text-sm text-slate-700">
<input v-model="seasonForm.isCurrent" type="checkbox" class="h-4 w-4 accent-violet-600" />
Dieses Jahr ist das aktuelle Public-Jahr
Dieses Jahr ist das aktuell oeffentliche Award-Jahr
</label>
<div class="flex flex-wrap items-center gap-4">
@@ -186,13 +186,13 @@ async function createCategory() {
<Card class="p-7">
<h2 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Neue Kategorie</h2>
<div class="mt-6 space-y-4">
<input v-model="newCategoryForm.groupName" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Group Name" />
<input v-model="newCategoryForm.groupName" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Gruppenname" />
<input v-model="newCategoryForm.name" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Kategorie-Name" />
<input v-model="newCategoryForm.slug" type="text" class="w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Slug" />
<textarea v-model="newCategoryForm.description" class="min-h-28 w-full rounded-2xl border border-violet-200 px-4 py-3" placeholder="Beschreibung" />
<div class="grid gap-4 sm:grid-cols-2">
<input v-model="newCategoryForm.sortOrder" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Sortierung" />
<input v-model="newCategoryForm.maxNomineesPerUser" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Max Nominees" />
<input v-model="newCategoryForm.maxNomineesPerUser" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Max. Nominierungen" />
</div>
<Button :disabled="categorySaving === 'new' || !selectedSeasonId" @click="createCategory">
{{ categorySaving === 'new' ? 'Erstellt ...' : 'Kategorie anlegen' }}
@@ -231,7 +231,7 @@ async function createCategory() {
class="rounded-[26px] border border-violet-100 bg-white/90 p-5"
>
<div class="grid gap-4 md:grid-cols-2">
<input v-model="editForms[category.id].groupName" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Group" />
<input v-model="editForms[category.id].groupName" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Gruppe" />
<input v-model="editForms[category.id].name" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Name" />
<input v-model="editForms[category.id].slug" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Slug" />
<input v-model="editForms[category.id].sortOrder" type="number" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Sortierung" />