FEAT-1: Tiere list page — Gridify filters (Status/Geschlecht/Farbschlag), sort, paging + German strings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
gerbil-manager-web/src/format/labels.ts
Normal file
19
gerbil-manager-web/src/format/labels.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/** German label helpers for enum values and lookups. */
|
||||||
|
import { de } from '../strings/de'
|
||||||
|
import type { Gender, GerbilStatus } from '../api/types'
|
||||||
|
|
||||||
|
export function genderLabel(gender: Gender): string {
|
||||||
|
return de.pages.gerbils.genderLabels[gender]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statusLabel(status: GerbilStatus): string {
|
||||||
|
return de.pages.gerbils.statusLabels[status]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format an ISO date ("YYYY-MM-DD") as German "TT.MM.JJJJ"; null -> dash. */
|
||||||
|
export function formatDate(iso: string | null | undefined): string {
|
||||||
|
if (!iso) return '—'
|
||||||
|
const [y, m, d] = iso.split('-')
|
||||||
|
if (!y || !m || !d) return iso
|
||||||
|
return `${d}.${m}.${y}`
|
||||||
|
}
|
||||||
@@ -155,3 +155,195 @@ a {
|
|||||||
padding: 1.5rem 2rem;
|
padding: 1.5rem 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Shared UI ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
background: var(--color-accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--primary {
|
||||||
|
background: var(--color-accent);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--primary:hover:not(:disabled) {
|
||||||
|
filter: brightness(0.95);
|
||||||
|
background: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--danger {
|
||||||
|
border-color: #b54a4a;
|
||||||
|
color: #b54a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font: inherit;
|
||||||
|
padding: 0.5rem 0.6rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
min-height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 5rem;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert--error {
|
||||||
|
background: #fbeaea;
|
||||||
|
border: 1px solid #e3b3b3;
|
||||||
|
color: #8a2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Filters ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters .input[type='search'] {
|
||||||
|
flex: 1 1 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field > select,
|
||||||
|
.field > .input {
|
||||||
|
min-width: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Card list (mobile-first); table-like rows on wider screens ── */
|
||||||
|
|
||||||
|
.card-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gerbil-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 0.25rem 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
background: var(--color-surface);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gerbil-card:hover {
|
||||||
|
background: var(--color-accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gerbil-card__name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gerbil-card__meta {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
justify-self: end;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: var(--color-accent-soft);
|
||||||
|
color: var(--color-accent);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge--deceased {
|
||||||
|
background: #ececec;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge--givenaway {
|
||||||
|
background: #e7eef6;
|
||||||
|
color: #3a6ea5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.gerbil-card {
|
||||||
|
grid-template-columns: 2fr 1fr 1.5fr 1fr auto;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,207 @@
|
|||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
import { de } from '../strings/de'
|
import { de } from '../strings/de'
|
||||||
|
import { listGerbils } from '../api/gerbils'
|
||||||
|
import { listColorVarieties } from '../api/lookups'
|
||||||
|
import { andFilter, condition, type GridifyQuery } from '../api/gridify'
|
||||||
|
import { GENDERS, GERBIL_STATUSES, type Gender, type GerbilStatus } from '../api/types'
|
||||||
|
import { useApi } from '../hooks/useApi'
|
||||||
|
import { formatDate, genderLabel, statusLabel } from '../format/labels'
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
|
type SortKey = 'nameAsc' | 'nameDesc' | 'birthDesc' | 'birthAsc'
|
||||||
|
const SORT_ORDER_BY: Record<SortKey, string> = {
|
||||||
|
nameAsc: 'name',
|
||||||
|
nameDesc: 'name desc',
|
||||||
|
birthDesc: 'dateOfBirth desc',
|
||||||
|
birthAsc: 'dateOfBirth',
|
||||||
|
}
|
||||||
|
|
||||||
export default function GerbilsPage() {
|
export default function GerbilsPage() {
|
||||||
|
const t = de.pages.gerbils
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const [status, setStatus] = useState<GerbilStatus | ''>('Active')
|
||||||
|
const [gender, setGender] = useState<Gender | ''>('')
|
||||||
|
const [colorVarietyId, setColorVarietyId] = useState('')
|
||||||
|
const [sort, setSort] = useState<SortKey>('nameAsc')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
|
||||||
|
const colorVarieties = useApi(() => listColorVarieties(), [])
|
||||||
|
const colorNameById = useMemo(() => {
|
||||||
|
const map = new Map<string, string>()
|
||||||
|
for (const cv of colorVarieties.data ?? []) map.set(cv.id, cv.name)
|
||||||
|
return map
|
||||||
|
}, [colorVarieties.data])
|
||||||
|
|
||||||
|
const filter = andFilter(
|
||||||
|
search.trim() && condition({ field: 'name', op: 'contains', value: search.trim() }),
|
||||||
|
status && condition({ field: 'status', op: '==', value: status }),
|
||||||
|
gender && condition({ field: 'gender', op: '==', value: gender }),
|
||||||
|
colorVarietyId && condition({ field: 'colorVarietyId', op: '==', value: colorVarietyId }),
|
||||||
|
)
|
||||||
|
const orderBy = SORT_ORDER_BY[sort]
|
||||||
|
const query: GridifyQuery = { filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE }
|
||||||
|
|
||||||
|
const gerbils = useApi(() => listGerbils(query), [filter, orderBy, page])
|
||||||
|
|
||||||
|
const resetFilters = () => {
|
||||||
|
setSearch('')
|
||||||
|
setStatus('Active')
|
||||||
|
setGender('')
|
||||||
|
setColorVarietyId('')
|
||||||
|
setPage(1)
|
||||||
|
setSort('nameAsc')
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a filter changes, jump back to page 1.
|
||||||
|
const onFilterChange = <T,>(setter: (v: T) => void) => (value: T) => {
|
||||||
|
setter(value)
|
||||||
|
setPage(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = gerbils.data?.totalCount ?? 0
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||||||
|
const items = gerbils.data?.items ?? []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="page">
|
||||||
<h2>{de.pages.gerbils.title}</h2>
|
<header className="page-head">
|
||||||
<p>{de.pages.gerbils.comingSoon}</p>
|
<div>
|
||||||
|
<h2>{t.title}</h2>
|
||||||
|
{gerbils.data && (
|
||||||
|
<p className="muted">
|
||||||
|
{total} {t.countLabel}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Link to="/rennmaeuse/neu" className="btn btn--primary">
|
||||||
|
+ {t.newButton}
|
||||||
|
</Link>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="filters">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="input"
|
||||||
|
placeholder={t.searchPlaceholder}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => onFilterChange(setSearch)(e.target.value)}
|
||||||
|
aria-label={t.fields.name}
|
||||||
|
/>
|
||||||
|
<label className="field">
|
||||||
|
<span>{t.filters.status}</span>
|
||||||
|
<select
|
||||||
|
value={status}
|
||||||
|
onChange={(e) => onFilterChange(setStatus)(e.target.value as GerbilStatus | '')}
|
||||||
|
>
|
||||||
|
<option value="">{t.filters.all}</option>
|
||||||
|
{GERBIL_STATUSES.map((s) => (
|
||||||
|
<option key={s} value={s}>
|
||||||
|
{statusLabel(s)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="field">
|
||||||
|
<span>{t.filters.gender}</span>
|
||||||
|
<select
|
||||||
|
value={gender}
|
||||||
|
onChange={(e) => onFilterChange(setGender)(e.target.value as Gender | '')}
|
||||||
|
>
|
||||||
|
<option value="">{t.filters.all}</option>
|
||||||
|
{GENDERS.map((g) => (
|
||||||
|
<option key={g} value={g}>
|
||||||
|
{genderLabel(g)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="field">
|
||||||
|
<span>{t.filters.colorVariety}</span>
|
||||||
|
<select
|
||||||
|
value={colorVarietyId}
|
||||||
|
onChange={(e) => onFilterChange(setColorVarietyId)(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">{t.filters.all}</option>
|
||||||
|
{(colorVarieties.data ?? []).map((cv) => (
|
||||||
|
<option key={cv.id} value={cv.id}>
|
||||||
|
{cv.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="field">
|
||||||
|
<span>{t.filters.sortBy}</span>
|
||||||
|
<select value={sort} onChange={(e) => setSort(e.target.value as SortKey)}>
|
||||||
|
<option value="nameAsc">{t.sort.nameAsc}</option>
|
||||||
|
<option value="nameDesc">{t.sort.nameDesc}</option>
|
||||||
|
<option value="birthDesc">{t.sort.birthDesc}</option>
|
||||||
|
<option value="birthAsc">{t.sort.birthAsc}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button type="button" className="btn" onClick={resetFilters}>
|
||||||
|
{t.filters.reset}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{gerbils.loading && <p className="muted">{de.common.loading}</p>}
|
||||||
|
{gerbils.error && (
|
||||||
|
<div className="alert alert--error">
|
||||||
|
<span>{gerbils.error}</span>
|
||||||
|
<button type="button" className="btn" onClick={gerbils.reload}>
|
||||||
|
{de.common.retry}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!gerbils.loading && !gerbils.error && items.length === 0 && (
|
||||||
|
<p className="muted">{t.empty}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{items.length > 0 && (
|
||||||
|
<ul className="card-list">
|
||||||
|
{items.map((g) => (
|
||||||
|
<li key={g.id}>
|
||||||
|
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
|
||||||
|
<span className="gerbil-card__name">{g.name}</span>
|
||||||
|
<span className={`badge badge--${g.status.toLowerCase()}`}>
|
||||||
|
{statusLabel(g.status)}
|
||||||
|
</span>
|
||||||
|
<span className="gerbil-card__meta">{genderLabel(g.gender)}</span>
|
||||||
|
<span className="gerbil-card__meta">
|
||||||
|
{g.colorVarietyId ? (colorNameById.get(g.colorVarietyId) ?? '—') : '—'}
|
||||||
|
</span>
|
||||||
|
<span className="gerbil-card__meta">{formatDate(g.dateOfBirth)}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<nav className="pager" aria-label="Seitennavigation">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn"
|
||||||
|
disabled={page <= 1}
|
||||||
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||||
|
>
|
||||||
|
{de.common.previous}
|
||||||
|
</button>
|
||||||
|
<span className="muted">
|
||||||
|
{de.common.page} {page} {de.common.of} {totalPages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn"
|
||||||
|
disabled={page >= totalPages}
|
||||||
|
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||||
|
>
|
||||||
|
{de.common.next}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,90 @@ export const de = {
|
|||||||
},
|
},
|
||||||
gerbils: {
|
gerbils: {
|
||||||
title: 'Rennmäuse',
|
title: 'Rennmäuse',
|
||||||
comingSoon: 'Die Rennmaus-Verwaltung entsteht gerade. Bald kannst du hier deine Tiere anlegen und pflegen.',
|
// Liste
|
||||||
|
newButton: 'Neue Rennmaus',
|
||||||
|
empty: 'Keine Rennmäuse gefunden.',
|
||||||
|
countLabel: 'Tiere',
|
||||||
|
searchPlaceholder: 'Name suchen …',
|
||||||
|
// Filter-Beschriftungen
|
||||||
|
filters: {
|
||||||
|
title: 'Filter',
|
||||||
|
status: 'Status',
|
||||||
|
gender: 'Geschlecht',
|
||||||
|
colorVariety: 'Farbschlag',
|
||||||
|
all: 'Alle',
|
||||||
|
reset: 'Filter zurücksetzen',
|
||||||
|
sortBy: 'Sortieren nach',
|
||||||
|
},
|
||||||
|
// Sortier-Optionen
|
||||||
|
sort: {
|
||||||
|
nameAsc: 'Name (A–Z)',
|
||||||
|
nameDesc: 'Name (Z–A)',
|
||||||
|
birthDesc: 'Geburtsdatum (neueste)',
|
||||||
|
birthAsc: 'Geburtsdatum (älteste)',
|
||||||
|
},
|
||||||
|
// Feld-Beschriftungen (Stammdaten)
|
||||||
|
fields: {
|
||||||
|
name: 'Name',
|
||||||
|
gender: 'Geschlecht',
|
||||||
|
status: 'Status',
|
||||||
|
dateOfBirth: 'Geburtsdatum',
|
||||||
|
dateOfDeath: 'Todesdatum',
|
||||||
|
causeOfDeath: 'Todesursache',
|
||||||
|
goHomeDate: 'Abgabedatum',
|
||||||
|
litter: 'Wurf',
|
||||||
|
enclosure: 'Becken',
|
||||||
|
colorVariety: 'Farbschlag',
|
||||||
|
origin: 'Herkunft',
|
||||||
|
receiver: 'Abnehmer',
|
||||||
|
genotype: 'Genotyp',
|
||||||
|
notes: 'Notizen',
|
||||||
|
},
|
||||||
|
// Detailseite
|
||||||
|
detail: {
|
||||||
|
masterData: 'Stammdaten',
|
||||||
|
genetics: 'Genetik',
|
||||||
|
resolvedFarbschlag: 'Errechneter Farbschlag',
|
||||||
|
genotypeNotSet: 'Kein Genotyp hinterlegt.',
|
||||||
|
edit: 'Bearbeiten',
|
||||||
|
back: 'Zurück zur Liste',
|
||||||
|
tabs: {
|
||||||
|
photos: 'Fotos',
|
||||||
|
health: 'Gesundheit',
|
||||||
|
weight: 'Gewicht',
|
||||||
|
},
|
||||||
|
tabPlaceholder: 'Dieser Bereich entsteht in einem späteren Schritt.',
|
||||||
|
notFound: 'Diese Rennmaus wurde nicht gefunden.',
|
||||||
|
},
|
||||||
|
// Formular (anlegen/bearbeiten)
|
||||||
|
form: {
|
||||||
|
createTitle: 'Neue Rennmaus anlegen',
|
||||||
|
editTitle: 'Rennmaus bearbeiten',
|
||||||
|
none: '— keine Angabe —',
|
||||||
|
genotypeHint:
|
||||||
|
'Optional. Format z. B. „Aa CC Dd EE GG Pp Spsp rere“. Unbekannte Allele als „?“.',
|
||||||
|
save: 'Speichern',
|
||||||
|
cancel: 'Abbrechen',
|
||||||
|
saving: 'Speichern …',
|
||||||
|
},
|
||||||
|
// Validierungsmeldungen
|
||||||
|
validation: {
|
||||||
|
nameRequired: 'Bitte einen Namen eingeben.',
|
||||||
|
genderRequired: 'Bitte ein Geschlecht auswählen.',
|
||||||
|
genotypeInvalid: 'Der Genotyp ist ungültig.',
|
||||||
|
dateOfDeathBeforeBirth: 'Das Todesdatum darf nicht vor dem Geburtsdatum liegen.',
|
||||||
|
},
|
||||||
|
// Aufzählungen
|
||||||
|
genderLabels: {
|
||||||
|
unknown: 'Unbekannt',
|
||||||
|
male: 'Männlich',
|
||||||
|
female: 'Weiblich',
|
||||||
|
},
|
||||||
|
statusLabels: {
|
||||||
|
Active: 'Aktiv',
|
||||||
|
Deceased: 'Verstorben',
|
||||||
|
GivenAway: 'Abgegeben',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
litters: {
|
litters: {
|
||||||
title: 'Würfe',
|
title: 'Würfe',
|
||||||
@@ -51,6 +134,15 @@ export const de = {
|
|||||||
},
|
},
|
||||||
common: {
|
common: {
|
||||||
loading: 'Lädt …',
|
loading: 'Lädt …',
|
||||||
|
retry: 'Erneut versuchen',
|
||||||
|
previous: 'Zurück',
|
||||||
|
next: 'Weiter',
|
||||||
|
page: 'Seite',
|
||||||
|
of: 'von',
|
||||||
|
yes: 'Ja',
|
||||||
|
no: 'Nein',
|
||||||
|
delete: 'Löschen',
|
||||||
|
confirmDelete: 'Wirklich löschen?',
|
||||||
},
|
},
|
||||||
// Genetik-Warnungen: der Engine (src/genetics) liefert nur CODES,
|
// Genetik-Warnungen: der Engine (src/genetics) liefert nur CODES,
|
||||||
// die deutschen Texte stehen hier. Schlüssel = GeneticsWarningCode-Werte.
|
// die deutschen Texte stehen hier. Schlüssel = GeneticsWarningCode-Werte.
|
||||||
|
|||||||
Reference in New Issue
Block a user