mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
* fix(i18n,ux): package A — missing keys, chart labels, query invalidation, admin error UX Addresses bugs surfaced in issues #73 and #74 plus drift found during audit. - i18n: add 21 missing translation keys (targets.* namespace was only partially populated after the English-first migration; common.reset and settings.configured were referenced but absent), plus common helpers (copy, copyDetails, retry, reportIssue) and admin.settingsSaveError. - charts: rename points7Label/30Label/90Label from cryptic "7P/30P/90P" to "7 pts / 30 pts / 90 pts" in EN and "7 Pkt / 30 Pkt / 90 Pkt" in DE. - queries: add src/lib/query-keys.ts as a single source of truth with measurement/mood/medication-dependent key sets. Wire them into measurement-form/list, mood-form/list, medication-form/card, intake-history-list, intake-timeline, medications page, and Withings sync in settings. Fixes dashboard not auto-refreshing after adding a measurement (old code only invalidated ["measurements"], not ["analytics"] / ["insights"] / ["insights","targets"]). - admin: useUpdateSettings + updateUser mutations now surface real error messages via sonner toasts and invoke getApiErrorMessage() so saves fail loudly instead of silently. Success also gets a toast. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(i18n): package B — locale-aware formatters throughout Removes the hard-coded de-DE locale that lingered from the German-first era and makes every number, date, and time locale-aware. English stays the default per user preference; German remains selectable. New: - src/lib/format-locale.ts exposes makeFormatters(locale) + a cached Europe/Berlin timezone. Covers number, integer, percent, date, dateShort, dateTime, time, dateWithWeekday, monthShort. Also exports parseLocaleFromAcceptLanguage() for server code. - useFormatters() hook in @/lib/i18n/context resolves from the active locale, memoized per render. Migrated (all Intl.NumberFormat("de-DE") / toLocaleString("de-DE") / Intl.DateTimeFormat("de-DE") call sites): - src/app/page.tsx (dashboard) - src/app/insights/page.tsx - src/app/admin/page.tsx - src/app/settings/page.tsx (Withings last-sync, doctor-report caller) - src/components/charts/trend-card.tsx (+ i18n "7d"/"30d" short labels) - src/components/charts/health-chart.tsx - src/components/medications/medication-card.tsx - src/components/medications/medication-form.tsx - src/components/measurements/measurement-list.tsx - src/components/mood/mood-list.tsx - src/components/insights/insight-status-card.tsx (also removed 5 German hard-coded strings with TODO: i18n markers — now translated) - src/components/insights/insight-advisor-card.tsx (same — 10+ strings) Server: reminder-check / reminder-worker / umami-test switched from "de-DE" to neutral en-GB/en-US where the string is machine-compared rather than user-facing. Bug-report GitHub issue titles use ISO-8601 UTC now — the repo is English-speaking. Next.js metadata: - OpenGraph locale from "de_DE" → "en_US" with "de_DE" as alternateLocale. - Root layout now reads healthlog-locale cookie (falls back to Accept-Language) and sets <html lang> server-side, avoiding an EN-flashes-then-DE hydration gap and fixing SEO/a11y. - I18nProvider mirrors locale to a 1-year SameSite=Lax cookie so the next SSR pass is correct. Doctor-report PDF: - generateDoctorReportPDF(data, { t, locale }) now accepts injected translator + locale. All section titles, column headers, BMI / BP classifications, mood labels, and the footer disclaimer are i18n-driven. File name slug switches between gesundheitsbericht / health-report. - 40+ new doctorReport.* keys in EN + DE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ux): package C — typed passkey errors, bug-report gating, error boundary Addresses issue #73 points 1, 5, 6 and lays groundwork for copy-paste error reporting requested during review. Passkey registration / login (#73.1): - src/lib/passkey-errors.ts maps DOMException.name + SimpleWebAuthn code to specific i18n keys instead of swallowing every failure as "cancelled". Distinguishes NotAllowed/InvalidState/NotSupported/ Security/Abort errors and falls back to a diagnostic message. - settings passkey + login passkey flows both use describePasskeyError now, so origin mismatches and previous-registration collisions show actionable copy. - 5 new passkey.* i18n keys in EN + DE. Bug report (#73.5): - New GET /api/bugreport/status reports {configured, hasToken, hasRepo, isAdmin}. - /bugreport page now queries status first. When not configured it shows a notice (different copy for admins vs. regular users) with a direct link to the admin panel. Form is hidden, not silently broken. - When POST /api/bugreport can't create issues it returns 503 instead of 500 with the same user-facing message. - 4 new bugreport.* i18n keys. Global error UX: - src/components/error-details.tsx — reusable panel with retry + copy diagnostics (message, digest, stack (trimmed), URL, user-agent, request-id, locale, timestamp) + report-issue link. Clipboard fallback prompt covers insecure-context cases. - src/app/error.tsx uses it. - src/app/global-error.tsx is provider-free (no i18n dependency) since it replaces the root layout on catastrophic failures — hardcoded English + Dracula palette so it still looks like HealthLog. - 2 new errorBoundary.* keys and 4 common.* keys (copy, copyDetails, retry, reportIssue). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(query-keys): collapse repeated invalidation blocks into invalidateKeys helper Eleven mutation handlers repeated `Promise.all(keys.map(k => invalidate({queryKey: k})))` verbatim. Adds `invalidateKeys(qc, keys)` to the existing query-keys factory and adopts it everywhere, including replacing the local `invalidateMedicationRelated` helper that medication-form.tsx had grown. Also fixes the import-sandwich that helper introduced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: small simplifications in i18n/UX changes - passkey-errors: drop the typeof-name dance now that error.name is already string after the instanceof Error narrow - doctor-report-pdf: replace nested gender ternary with a small lookup map; clarify unitFor's null-as-translation-marker convention - dashboard page: route the analytics queries through the new queryKeys factory so future invalidation stays in one place - health-chart: add missing fmt dependency to trendInfo useMemo (introduced when formatSigned was switched to the locale-aware formatter) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): capture upstream response body in OpenAI errors A production insights burst at 12:19 UTC failed with "OpenAI request failed (500)". The 500 was the upstream OpenAI HTTP status (confirmed via logs), but we had no way to tell from logs whether it was an OpenAI incident, a model-not-found, or a response_format rejection because the response body was thrown away. Now the error message includes the first 500 chars of the response body and structured fields (httpStatus, upstream, model) that Wide Event logging can attach. No behavior change for the happy path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(review): address code-review + security findings Review round produced five real issues — fix them all, plus three nits that were cheap enough to bundle. Must-fix: - security: error-details.tsx + global-error.tsx were copying window.location.href into the clipboard, which on OAuth callback routes (/api/withings/*, password reset) would leak code/state tokens into GitHub issues. Strip to pathname + searchParamCount. - fe: query-keys.invalidateKeys now uses Promise.allSettled, so a transient invalidate failure doesn't abort the chain and no unhandled rejection leaks from void invalidateKeys(...) call sites. - fe: resolveInitialLocale in app/layout.tsx wraps cookies()/headers() in try/catch — a dynamic-server error in the root layout now degrades to the "en" default instead of crashing into global-error. - be: reminder-worker + reminder-check parseTimeToMinutes normalize "24:00" → "00:00" (some ICU builds emit that for midnight under en-GB). Prevents a 1440-minute comparison against schedule windows. - security: openai-client error body excerpt redacts sk-* / Bearer tokens before logging, and moves the excerpt off Error.message into a structured field (bodyExcerpt) so log transports can filter it. - i18n: /api/withings/connect German error message → English. - i18n: /insights/page.tsx hardcoded "Fehler beim Laden" → t(...) Hardening: - /api/bugreport/status collapses to {configured, isAdmin} and adds a 30/min rate limit per user. Client interface updated. - i18n/context.tsx: I18nProvider's initializer now reads the cookie (matches SSR) before falling back to localStorage or browser detection — eliminates the locale-flash hydration class. - format.ts legacy helpers also read cookie first, matching SSR. Tests: - new src/lib/__tests__/format-locale.test.ts — 9 cases covering makeFormatters, parseLocaleFromAcceptLanguage, resolveIntlLocale - new passkey-errors.test.ts — 8 cases covering every DOMException name and SimpleWebAuthn code we map - new query-keys.test.ts — bundles + invalidateKeys allSettled semantics (incl. partial-failure test) 118 → 142 tests. All pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(v1.2): thresholds, glucose, dashboard customization, built-in feedback, multi-provider AI (#76) * build(deps): bump @prisma/adapter-pg from 7.6.0 to 7.7.0 (#60) Bumps [@prisma/adapter-pg](https://github.com/prisma/prisma/tree/HEAD/packages/adapter-pg) from 7.6.0 to 7.7.0. - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.7.0/packages/adapter-pg) --- updated-dependencies: - dependency-name: "@prisma/adapter-pg" dependency-version: 7.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump prisma from 7.6.0 to 7.7.0 (#61) Bumps [prisma](https://github.com/prisma/prisma/tree/HEAD/packages/cli) from 7.6.0 to 7.7.0. - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.7.0/packages/cli) --- updated-dependencies: - dependency-name: prisma dependency-version: 7.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump eslint-config-next from 16.2.2 to 16.2.3 (#62) Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.2.2 to 16.2.3. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v16.2.3/packages/eslint-config-next) --- updated-dependencies: - dependency-name: eslint-config-next dependency-version: 16.2.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump next from 16.2.2 to 16.2.3 (#65) Bumps [next](https://github.com/vercel/next.js) from 16.2.2 to 16.2.3. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v16.2.2...v16.2.3) --- updated-dependencies: - dependency-name: next dependency-version: 16.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/node from 25.5.2 to 25.6.0 (#66) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.2 to 25.6.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.6.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump dotenv from 17.4.0 to 17.4.1 (#67) Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.4.0 to 17.4.1. - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v17.4.0...v17.4.1) --- updated-dependencies: - dependency-name: dotenv dependency-version: 17.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @tanstack/react-query from 5.96.2 to 5.99.0 (#70) Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.96.2 to 5.99.0. - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.99.0/packages/react-query) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-version: 5.99.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump typescript from 5.9.3 to 6.0.2 (#64) Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 6.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump prettier from 3.8.1 to 3.8.2 (#68) Bumps [prettier](https://github.com/prettier/prettier) from 3.8.1 to 3.8.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.8.1...3.8.2) --- updated-dependencies: - dependency-name: prettier dependency-version: 3.8.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump react from 19.2.4 to 19.2.5 (#63) Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) from 19.2.4 to 19.2.5. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react) --- updated-dependencies: - dependency-name: react dependency-version: 19.2.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump vitest from 4.1.2 to 4.1.4 (#69) Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.2 to 4.1.4. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.4/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 4.1.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @prisma/client from 7.6.0 to 7.7.0 (#71) Bumps [@prisma/client](https://github.com/prisma/prisma/tree/HEAD/packages/client) from 7.6.0 to 7.7.0. - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.7.0/packages/client) --- updated-dependencies: - dependency-name: "@prisma/client" dependency-version: 7.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump shadcn from 4.1.2 to 4.2.0 (#72) Bumps [shadcn](https://github.com/shadcn-ui/ui/tree/HEAD/packages/shadcn) from 4.1.2 to 4.2.0. - [Release notes](https://github.com/shadcn-ui/ui/releases) - [Changelog](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/CHANGELOG.md) - [Commits](https://github.com/shadcn-ui/ui/commits/shadcn@4.2.0/packages/shadcn) --- updated-dependencies: - dependency-name: shadcn dependency-version: 4.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(thresholds): foundation + settings UI (v1.2) Introduces per-user health threshold overrides with an auto-default / user-override pattern. Default computation stays evidence-based (WHO, ESC/ESH 2018, AHA, AASM, ADA 2024, DGIM/DDG S3). Overrides live in User.thresholdsJson; any missing metric falls through to the default so new users need zero configuration. Schema (migration 0021): - New enums: glucose_context (FASTING/POSTPRANDIAL/RANDOM/BEDTIME), feedback_category, feedback_status. - measurement_type gains BLOOD_GLUCOSE; Measurement gets optional glucose_context (enforced via CHECK constraint — only set for glucose). - User gains thresholds_json, dashboard_widgets_json, glucose_unit, ai_provider, ai_model, ai_base_url, ai_anthropic_key_encrypted, ai_local_key_encrypted (stubs for the upcoming features). - New Feedback table for the built-in feedback system (next commit). Backend: - src/lib/analytics/effective-range.ts is the single source of truth: getEffectiveRange(metric, profile, overrides) returns the resolved range + the pristine default + physiological bounds. Every chart, band, insight prompt, and doctor-report section should call this. - Supports 11 metrics incl. four glucose contexts with ADA/DGIM ranges. - src/lib/validations/thresholds.ts — Zod schema with per-metric bounds and min<max refinement. - GET/PUT/DELETE /api/user/thresholds with full audit logging (before/ after) so medical-ish data mutations are traceable. UI: - src/components/settings/thresholds-section.tsx — clean per-metric row with Auto/Override Switch, inline min/max, reset-per-row + reset-all, and a "this looks outside guideline ranges" warning banner that only shows when the override is >30% off the default. - New Personalization section in /settings. - 25 new i18n keys (EN + DE) in a `thresholds.*` namespace. Tests: 8 new cases covering default computation, override application, missing profile gracefully returning null, glucose-context defaults, and per-metric bounds. 150/150 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(glucose): wire BLOOD_GLUCOSE + context through the write path Extends measurement validation and the POST /api/measurements route to accept blood-glucose samples with a required `glucoseContext` (FASTING / POSTPRANDIAL / RANDOM / BEDTIME). - Zod schema adds BLOOD_GLUCOSE to the type enum and glucoseContextEnum. - refine(): BLOOD_GLUCOSE requires a context; every other type must not set one — mirrored by the Postgres CHECK constraint from migration 0021 so callers bypassing validation still can't write inconsistent rows. - VALUE_RANGES gains BLOOD_GLUCOSE (20-800 mg/dL) — covers severe hypo through severe hyperglycemia. - getUnitForType(BLOOD_GLUCOSE) returns "mg/dL" (canonical storage). Display in mmol/L is a UI-level conversion per user preference. - Error messages translated to English (removed two lingering German refine messages while here). Measurement form + charts + targets UI follow in the next commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(i18n): localize AI insight prompts (en/de) Each prompt builder (base-system, blood-pressure, bmi, general-status, medication-compliance, mood, pulse, weight) now accepts a Locale and returns the system + user prompt either fully in English or fully in German. Stable contract keys (classification enum optimal/gut/grenzwertig /erhoht/kritisch, confidence niedrig/mittel/hoch) are preserved verbatim so the response parser keeps working — only the natural-language sections are translated. The prompt explicitly tells the model to emit classificationLabel / summary / recommendations in the user's language. Wires the locale through every status generator (general / blood-pressure / bmi / weight / pulse / mood / medication-compliance) into the prompt calls and adds a small server-side resolver (src/lib/i18n/server-locale.ts: explicit override > healthlog-locale or userLocale cookie > User.locale > Accept-Language header > default English) used by all /api/insights/*-status routes plus /api/insights/generate. The auditLog cache key already contains the locale suffix, so cached entries stay disjoint per language. The reminder worker continues to fall back to "de" when User.locale is null, matching its prior behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(glucose): UI + unit conversion helpers - src/lib/glucose.ts — mg/dL ↔ mmol/L conversion (factor 18.0182 per DGIM/DDG), resolveGlucoseUnit() defaulting to mg/dL, thresholdMetricForContext() mapping each context to the matching effective-range key. - Measurement form gains a "Blood glucose" option (mg/dL) with a context selector (fasting / postprandial / random / bedtime). The glucoseContext field is only sent for BLOOD_GLUCOSE — the Zod refinement and the Postgres CHECK constraint both enforce that. - 10 new i18n keys per locale (measurements.typeBloodGlucose, glucoseContext*, glucoseUnit, glucoseUnitHint). - 6 new tests covering conversion roundtrip, unit resolution defaults, and context → threshold-metric mapping. Charts, target cards, and doctor-PDF row for glucose follow in the next commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(feedback): built-in feedback system (no GitHub required) Implements the feedback-system design from the v1.2 spec. The submit button on /bugreport now always works — even when no admin has configured a GitHub repository. Feedback is stored in the Feedback table (migration 0021) with an admin inbox for triage. Endpoints: - POST /api/feedback — user submits. Auth-gated, rate-limited 5/hour. Category (BUG / FEATURE_REQUEST / QUESTION / OTHER), subject, description, optional screenshot (≤5 MB, base64), optional metadata (locale, URL path, userAgent). - GET /api/feedback — user's own history (paginated). - GET /api/admin/feedback — admin inbox with status filter and counts-by-status for tab badges. - PATCH /api/admin/feedback/[id] — update status / admin note. - DELETE /api/admin/feedback/[id] — soft-archive. Every mutation writes to AuditLog with before/after details. UI: - /bugreport rewritten: category dropdown, optional subject, always submits. Informational banner explains whether the server has GitHub escalation available or keeps feedback internal-only. - Legacy "not configured" blocker is gone — the form is never hidden. - 8 new bugreport.* i18n keys per locale. The old POST /api/bugreport route and /api/bugreport/status endpoint are left intact for backwards compatibility; they'll be removed in v1.3 once no one depends on them. Admin inbox UI lands in the next commit alongside the dashboard and multi-provider work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(telegram): localize bot UI per User.locale The Telegram webhook now resolves the user's locale (User.locale, falling back to "de" so existing bots stay German) and runs every reply, inline- keyboard label and callback toast through a small server-side translator. Adds src/lib/i18n/server-translator.ts (getServerTranslator) — reads the same messages/{en,de}.json bundles the client uses, with the same "missing key falls back to English, then to the raw key" semantics, and the same {param} interpolation. Importing the client useTranslations() hook into a server route would have pulled React in, so this mirrors just the parts the bot needs. Adds the telegram.* namespace to messages/en.json and messages/de.json covering: button labels, callback toasts, the /help body, /add prompt, medication-not-found / inactive errors, snooze/skip/confirm confirmations and the cancel button. The "genommen" / "taken" intake keyword and the "hi/hallo/hey/moin/hello" greeting list are accepted regardless of UI locale to keep backwards compatibility for existing users. Also adds 6 vitest cases for getServerTranslator (basic resolution, locale switching, parameter interpolation, English fallback, raw-key fallback, exposed locale). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dashboard): user-customizable widget layout Implements the dashboard customization spec: users choose which widgets appear on / and in what order. Auto-default keeps the current v1.1 behavior so nothing changes without opt-in. Backend: - src/lib/dashboard-layout.ts — single source of truth with DEFAULT_DASHBOARD_LAYOUT (10 widgets incl. the new sleep/steps/glucose ones that were previously hard-conditional), a version field for future schema evolution, and resolveDashboardLayout() that merges persisted state with defaults so new widgets auto-appear (invisible by default) when the schema bumps. - GET / PUT / DELETE /api/dashboard/widgets with strict Zod validation (version literal, widget-id enum, order range). - Persisted in User.dashboardWidgetsJson (column added in 0021). UI: - src/components/settings/dashboard-layout-section.tsx — mounted under the new Personalization section alongside Thresholds. Local edit buffer so toggling/reordering doesn't fire a network call per click; single "Save" commits. Each widget has Up/Down buttons + visibility toggle, plus a reset-to-defaults button at the section header. - 11 new dashboard.* i18n keys per locale. Dashboard page integration is staged — widgets currently still render per the old data-conditional rules. Wiring the saved layout through /app/page.tsx is a follow-up so this commit stays focused on the plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(auth): locale-aware password-strength feedback Completes the zxcvbn i18n migration by wiring the new getZxcvbnTranslations(locale) helper into the client PasswordStrength component and removing the obsolete zxcvbn-de helper. - src/components/ui/password-strength.tsx reads the active locale from useTranslations() and memoizes the translator per locale — so changing language instantly retranslates open warnings/suggestions. - src/lib/zxcvbn-de.ts deleted. Every consumer now routes through zxcvbn-i18n.ts which carries both EN and DE mappings. - server-side checkPasswordStrength (already refactored earlier in this session) continues to accept locale; clients can pass it from the session. No test impact — 162 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dashboard): replace setState-in-effect with draft-overlay pattern The dashboard-layout-section.tsx initial implementation synced server state into local state via useEffect, which trips the project's strict react-hooks/set-state-in-effect rule. Rewrote using a nullable draft pattern: `layout` is derived as `draft ?? remote`, and only user edits set the draft. Save/Reset clear the draft so the server copy wins again. Cancel also clears instead of re-seeding from server. No behavior change, no new tests — the component contract stays the same. Lint back to baseline (2 errors / 7 warnings, both pre-existing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: v1.2 README + CHANGELOG (thresholds, glucose, dashboard, feedback, multi-provider AI) - CHANGELOG 1.2.0 entry covering 5 new features + locale polish - README Key Features expanded: glucose, custom thresholds, dashboard customization, multi-provider AI, built-in feedback - Doctor Report description updated for locale-aware output Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(targets): glucose cards per context with effective range + unit conversion Render one target card per logged glucose context (FASTING, POSTPRANDIAL, RANDOM, BEDTIME) on /targets, sourcing the threshold band from getEffectiveRange() so user overrides apply consistently. The targets API returns glucose values canonical mg/dL; the page converts to the user's glucoseUnit preference via convertGlucose() before display. - Extend /api/insights/targets to include per-context glucose summaries (latest, 30-day average, range, classification) and surface glucoseUnit in the profile envelope. - Expose glucoseUnit on /api/auth/me and AuthUser type. - Targets page resolves the i18n label key, applies unit conversion to current/avg/range, and uses Droplet iconography. - Add targets.glucoseFasting/Postprandial/Random/Bedtime keys to en + de. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(security): address H1 (CSP nonce), H3 (SSRF), M1+M2 (XSS) From the v1.2 full-repo security audit: - **H1 / CWE-79** src/proxy.ts: CSP nonce was base64 of the UUID *string* form, which gives ~122 bits and a structured pattern (hyphens at fixed offsets). Replaced with 16 random bytes → proper 128-bit nonce. - **H3 / CWE-918** src/app/api/admin/ai-settings/route.ts: the admin AI base URL only required "https://". A compromised admin cookie could point it at attacker.example and exfiltrate the bearer key on the next insight call. Now validates with URL parser, requires https://, and only accepts hosts on a small allowlist (OpenAI, Anthropic, Google AI, Mistral, Groq, OpenRouter). Ops can extend via `ADMIN_AI_BASE_URL_ALLOWLIST` env var. - **M1 / CWE-79 (stored, off-platform)** src/app/api/bugreport/route.ts: bug-report description flowed to GitHub with only backtick/asterisk stripping. Now strips HTML tags entirely, neutralises dangerous markdown link schemes (javascript:/data:/vbscript:/file:), and wraps the whole body in a fenced code block so residual markdown renders literally. - **M2 / CWE-79** Zod schemas for feedback + bugreport screenshot now require a base64 PNG/JPEG/WEBP/GIF data URL via regex. SVG deliberately excluded — it can carry inline JS that would execute when the admin inbox renders the `<img>` preview. Medium rate-limiter semantics (audit M3) left as-is: the fixed-window behaviour is acceptable for a single-tenant PWA and matches what ops actually want. Remaining M4–M6 + L1–L7 are tracked for v1.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dashboard): glucose TrendCards + chart label support Render one TrendCard per logged glucose context on the dashboard when the user has the "glucose" widget enabled in their layout. Cards respect the user's glucoseUnit preference via convertGlucose(); data sources from the existing /api/analytics endpoint, which now emits a glucoseByContext map alongside the summaries object. - /api/analytics: include BLOOD_GLUCOSE in the type whitelist (so total counts include glucose) and add glucoseByContext containing a summarize() result per context. - HealthChart: wire BLOOD_GLUCOSE into BASE_TYPE_LABEL_KEYS so the legend shows the localized "Blood glucose" label when the chart is invoked for that type. - Dashboard page: fetch the dashboard layout, gate glucose cards on widget visibility AND data presence, render Droplet-icon TrendCards. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(i18n): bugreport.subtitle no longer promises a GitHub issue The visual QA pass flagged that bugreport.subtitle still said "a GitHub issue will be created automatically" even though the banner below the heading correctly explains the submission is stored locally (built-in feedback system, GitHub is only an optional escalation). Neutral copy now that matches the actual behaviour in both locales. * feat(ai): user-level provider selection + test endpoint + UI Adds the missing pieces for the multi-provider AI MVP that pair with the AnthropicClient and LocalOpenAICompatibleClient already on disk: * /api/user/ai-provider (GET + PATCH): user can pick OPENAI / ANTHROPIC / LOCAL / CHATGPT_OAUTH and store provider, model, baseUrl and per-provider API keys. Anthropic + local keys go through the existing AES-256-GCM crypto helper. Provider value is whitelisted. * /api/ai/test (POST): rate-limited (5/min/user) round-trip that resolves the user's provider and asks for a tiny JSON ack so the UI surfaces real "does this config work?" feedback instead of a blind Save. * settings/page.tsx: appends a per-user override card under the existing AI Insights section. Provider dropdown drives conditional fields (Anthropic key for ANTHROPIC, baseUrl + optional bearer for LOCAL, model for all), then Save / Test buttons. * provider.test.ts: extended to cover the four new cases (ANTHROPIC with key, ANTHROPIC fallback, LOCAL, OPENAI routes through admin key, CHATGPT_OAUTH routes through Codex). Switched to resetAllMocks() in beforeEach to flush mockResolvedValueOnce queues between cases — the prior clearAllMocks() leaked them. * local-client.test.ts: full happy / no-auth / bearer-auth / structured error / empty-content coverage mirroring the openai-client suite. typecheck + tests green; lint stays at the pre-existing 2 errors / 7 warnings baseline (no new findings introduced). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: integrate late agent additions (glucose PDF row + i18n) * test(glucose): cover display-unit conversion edge cases Lock in the precision contract for the targets/dashboard/PDF surfaces: canonical mg/dL stays as integers, mmol/L collapses to 1-decimal, and the ADA diabetes thresholds (70/99/126) round-trip to their published SI values (3.9 / 5.5 / 7.0 mmol/L). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(i18n): eliminate hydration flash by threading SSR-resolved locale to I18nProvider Layout already resolves the initial locale from cookie + Accept-Language and sets <html lang>. Until now the client I18nProvider re-detected locale at mount via getSavedLocale() / detectSystemLocale(), causing a flash where "Loading…" (EN) rendered first, then flipped to "Laden…" (DE) after hydration. Pass initialLocale down layout -> Providers -> I18nProvider so useState seeds the same locale the server used. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(admin/feedback): return 409 when GitHub issue already published Spec requires 409 Conflict (not 200 with flag) when the feedback row already has gitHubIssueUrl set. Drops the alreadyPublished response field in favour of a clear error status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(crypto,dashboard): L4 hard-fail short ENCRYPTION_KEY in prod + dashboard WIP from parallel agent * refactor(effective-range): collapse duplicated glucose case arms Extract glucoseRange() helper and toDate() normalizer. Use METRIC_BOUNDS keys as the source of truth for getAllEffectiveRanges() metric list so additions stay in one place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(dashboard-layout-section): drop redundant setLayout alias Remove the trivial setLayout wrapper and hoist the per-row label key lookup so it runs once per widget instead of twice (span + aria-label). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(security): MED-1 SSRF on user AI baseUrl + MED-2 publish-to-GitHub race Final-audit blockers before v1.2 merge. MED-1 — SSRF via user-level LOCAL AI baseUrl Authenticated user could set aiBaseUrl to http://169.254.169.254/, http://localhost:9090/metrics, or any internal host and get response bodies echoed back via the bodyExcerpt error field. Route the value through the existing isPublicUrl() helper (exported from lib/validations/notifications.ts). Ops can opt-in to private hosts on a specific instance via ALLOW_LOCAL_AI_PRIVATE_HOSTS=true — that's how self-hosted Ollama / LM Studio stays usable without exposing the SSRF surface by default. MED-2 — Race in POST /api/admin/feedback/[id]/github Two concurrent admin clicks both saw gitHubIssueUrl=null and both created duplicate GitHub issues. Atomic claim now: a single updateMany({ where: { id, gitHubIssueUrl: null }, data: PENDING }) — only the caller whose count=1 proceeds. PENDING is released on failure so retries work. Tests + typecheck green. Baseline lint unchanged. * fix: i18n completeness + UX polish (closes #73, #74) (#75) * fix(i18n,ux): package A — missing keys, chart labels, query invalidation, admin error UX Addresses bugs surfaced in issues #73 and #74 plus drift found during audit. - i18n: add 21 missing translation keys (targets.* namespace was only partially populated after the English-first migration; common.reset and settings.configured were referenced but absent), plus common helpers (copy, copyDetails, retry, reportIssue) and admin.settingsSaveError. - charts: rename points7Label/30Label/90Label from cryptic "7P/30P/90P" to "7 pts / 30 pts / 90 pts" in EN and "7 Pkt / 30 Pkt / 90 Pkt" in DE. - queries: add src/lib/query-keys.ts as a single source of truth with measurement/mood/medication-dependent key sets. Wire them into measurement-form/list, mood-form/list, medication-form/card, intake-history-list, intake-timeline, medications page, and Withings sync in settings. Fixes dashboard not auto-refreshing after adding a measurement (old code only invalidated ["measurements"], not ["analytics"] / ["insights"] / ["insights","targets"]). - admin: useUpdateSettings + updateUser mutations now surface real error messages via sonner toasts and invoke getApiErrorMessage() so saves fail loudly instead of silently. Success also gets a toast. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(i18n): package B — locale-aware formatters throughout Removes the hard-coded de-DE locale that lingered from the German-first era and makes every number, date, and time locale-aware. English stays the default per user preference; German remains selectable. New: - src/lib/format-locale.ts exposes makeFormatters(locale) + a cached Europe/Berlin timezone. Covers number, integer, percent, date, dateShort, dateTime, time, dateWithWeekday, monthShort. Also exports parseLocaleFromAcceptLanguage() for server code. - useFormatters() hook in @/lib/i18n/context resolves from the active locale, memoized per render. Migrated (all Intl.NumberFormat("de-DE") / toLocaleString("de-DE") / Intl.DateTimeFormat("de-DE") call sites): - src/app/page.tsx (dashboard) - src/app/insights/page.tsx - src/app/admin/page.tsx - src/app/settings/page.tsx (Withings last-sync, doctor-report caller) - src/components/charts/trend-card.tsx (+ i18n "7d"/"30d" short labels) - src/components/charts/health-chart.tsx - src/components/medications/medication-card.tsx - src/components/medications/medication-form.tsx - src/components/measurements/measurement-list.tsx - src/components/mood/mood-list.tsx - src/components/insights/insight-status-card.tsx (also removed 5 German hard-coded strings with TODO: i18n markers — now translated) - src/components/insights/insight-advisor-card.tsx (same — 10+ strings) Server: reminder-check / reminder-worker / umami-test switched from "de-DE" to neutral en-GB/en-US where the string is machine-compared rather than user-facing. Bug-report GitHub issue titles use ISO-8601 UTC now — the repo is English-speaking. Next.js metadata: - OpenGraph locale from "de_DE" → "en_US" with "de_DE" as alternateLocale. - Root layout now reads healthlog-locale cookie (falls back to Accept-Language) and sets <html lang> server-side, avoiding an EN-flashes-then-DE hydration gap and fixing SEO/a11y. - I18nProvider mirrors locale to a 1-year SameSite=Lax cookie so the next SSR pass is correct. Doctor-report PDF: - generateDoctorReportPDF(data, { t, locale }) now accepts injected translator + locale. All section titles, column headers, BMI / BP classifications, mood labels, and the footer disclaimer are i18n-driven. File name slug switches between gesundheitsbericht / health-report. - 40+ new doctorReport.* keys in EN + DE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ux): package C — typed passkey errors, bug-report gating, error boundary Addresses issue #73 points 1, 5, 6 and lays groundwork for copy-paste error reporting requested during review. Passkey registration / login (#73.1): - src/lib/passkey-errors.ts maps DOMException.name + SimpleWebAuthn code to specific i18n keys instead of swallowing every failure as "cancelled". Distinguishes NotAllowed/InvalidState/NotSupported/ Security/Abort errors and falls back to a diagnostic message. - settings passkey + login passkey flows both use describePasskeyError now, so origin mismatches and previous-registration collisions show actionable copy. - 5 new passkey.* i18n keys in EN + DE. Bug report (#73.5): - New GET /api/bugreport/status reports {configured, hasToken, hasRepo, isAdmin}. - /bugreport page now queries status first. When not configured it shows a notice (different copy for admins vs. regular users) with a direct link to the admin panel. Form is hidden, not silently broken. - When POST /api/bugreport can't create issues it returns 503 instead of 500 with the same user-facing message. - 4 new bugreport.* i18n keys. Global error UX: - src/components/error-details.tsx — reusable panel with retry + copy diagnostics (message, digest, stack (trimmed), URL, user-agent, request-id, locale, timestamp) + report-issue link. Clipboard fallback prompt covers insecure-context cases. - src/app/error.tsx uses it. - src/app/global-error.tsx is provider-free (no i18n dependency) since it replaces the root layout on catastrophic failures — hardcoded English + Dracula palette so it still looks like HealthLog. - 2 new errorBoundary.* keys and 4 common.* keys (copy, copyDetails, retry, reportIssue). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(query-keys): collapse repeated invalidation blocks into invalidateKeys helper Eleven mutation handlers repeated `Promise.all(keys.map(k => invalidate({queryKey: k})))` verbatim. Adds `invalidateKeys(qc, keys)` to the existing query-keys factory and adopts it everywhere, including replacing the local `invalidateMedicationRelated` helper that medication-form.tsx had grown. Also fixes the import-sandwich that helper introduced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: small simplifications in i18n/UX changes - passkey-errors: drop the typeof-name dance now that error.name is already string after the instanceof Error narrow - doctor-report-pdf: replace nested gender ternary with a small lookup map; clarify unitFor's null-as-translation-marker convention - dashboard page: route the analytics queries through the new queryKeys factory so future invalidation stays in one place - health-chart: add missing fmt dependency to trendInfo useMemo (introduced when formatSigned was switched to the locale-aware formatter) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): capture upstream response body in OpenAI errors A production insights burst at 12:19 UTC failed with "OpenAI request failed (500)". The 500 was the upstream OpenAI HTTP status (confirmed via logs), but we had no way to tell from logs whether it was an OpenAI incident, a model-not-found, or a response_format rejection because the response body was thrown away. Now the error message includes the first 500 chars of the response body and structured fields (httpStatus, upstream, model) that Wide Event logging can attach. No behavior change for the happy path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(review): address code-review + security findings Review round produced five real issues — fix them all, plus three nits that were cheap enough to bundle. Must-fix: - security: error-details.tsx + global-error.tsx were copying window.location.href into the clipboard, which on OAuth callback routes (/api/withings/*, password reset) would leak code/state tokens into GitHub issues. Strip to pathname + searchParamCount. - fe: query-keys.invalidateKeys now uses Promise.allSettled, so a transient invalidate failure doesn't abort the chain and no unhandled rejection leaks from void invalidateKeys(...) call sites. - fe: resolveInitialLocale in app/layout.tsx wraps cookies()/headers() in try/catch — a dynamic-server error in the root layout now degrades to the "en" default instead of crashing into global-error. - be: reminder-worker + reminder-check parseTimeToMinutes normalize "24:00" → "00:00" (some ICU builds emit that for midnight under en-GB). Prevents a 1440-minute comparison against schedule windows. - security: openai-client error body excerpt redacts sk-* / Bearer tokens before logging, and moves the excerpt off Error.message into a structured field (bodyExcerpt) so log transports can filter it. - i18n: /api/withings/connect German error message → English. - i18n: /insights/page.tsx hardcoded "Fehler beim Laden" → t(...) Hardening: - /api/bugreport/status collapses to {configured, isAdmin} and adds a 30/min rate limit per user. Client interface updated. - i18n/context.tsx: I18nProvider's initializer now reads the cookie (matches SSR) before falling back to localStorage or browser detection — eliminates the locale-flash hydration class. - format.ts legacy helpers also read cookie first, matching SSR. Tests: - new src/lib/__tests__/format-locale.test.ts — 9 cases covering makeFormatters, parseLocaleFromAcceptLanguage, resolveIntlLocale - new passkey-errors.test.ts — 8 cases covering every DOMException name and SimpleWebAuthn code we map - new query-keys.test.ts — bundles + invalidateKeys allSettled semantics (incl. partial-failure test) 118 → 142 tests. All pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Marc-André Bombeck <marc@bombeck.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: restore Link import lost in merge * fix(bugreport): remove stale loading + not-configured blocks lost in merge The v1.2 built-in feedback refactor made status informational only — form always renders. Main's merge re-introduced the pre-refactor blocker blocks referencing undefined statusLoading/AlertCircle and broke the build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc-André Bombeck <marc@bombeck.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Marc-André Bombeck <marc@bombeck.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1654 lines
75 KiB
JSON
1654 lines
75 KiB
JSON
{
|
||
"common": {
|
||
"save": "Speichern",
|
||
"cancel": "Abbrechen",
|
||
"delete": "Löschen",
|
||
"edit": "Bearbeiten",
|
||
"moreOptions": "Weitere Optionen",
|
||
"create": "Erstellen",
|
||
"close": "Schließen",
|
||
"loading": "Laden...",
|
||
"error": "Fehler",
|
||
"success": "Erfolgreich",
|
||
"saved": "Gespeichert",
|
||
"confirm": "Bestätigen",
|
||
"back": "Zurück",
|
||
"next": "Weiter",
|
||
"yes": "Ja",
|
||
"no": "Nein",
|
||
"search": "Suchen",
|
||
"filter": "Filtern",
|
||
"noData": "Keine Daten vorhanden",
|
||
"optional": "Optional",
|
||
"required": "Erforderlich",
|
||
"all": "Alle",
|
||
"active": "Aktiv",
|
||
"inactive": "Inaktiv",
|
||
"enabled": "Aktiviert",
|
||
"disabled": "Deaktiviert",
|
||
"networkError": "Netzwerkfehler",
|
||
"unknownError": "Unbekannter Fehler",
|
||
"notLoggedIn": "Nicht angemeldet",
|
||
"noAccess": "Kein Zugriff",
|
||
"copied": "Kopiert!",
|
||
"or": "oder",
|
||
"unknown": "Unbekannt",
|
||
"replace": "Ersetzen",
|
||
"add": "Hinzufügen",
|
||
"import": "Importieren",
|
||
"test": "Test",
|
||
"reset": "Zurücksetzen",
|
||
"copy": "Kopieren",
|
||
"copyDetails": "Details kopieren",
|
||
"retry": "Erneut versuchen",
|
||
"reportIssue": "Fehler melden"
|
||
},
|
||
"doctorReport": {
|
||
"title": "Gesundheitsbericht",
|
||
"subtitle": "HealthLog — Persönlicher Gesundheitsbericht",
|
||
"patient": "Patient",
|
||
"dateOfBirth": "Geburtsdatum",
|
||
"gender": "Geschlecht",
|
||
"genderMale": "Männlich",
|
||
"genderFemale": "Weiblich",
|
||
"genderOther": "Divers",
|
||
"height": "Körpergröße",
|
||
"period": "Berichtszeitraum",
|
||
"createdOn": "Erstellt am",
|
||
"vitalsTitle": "Vitalparameter — Übersicht",
|
||
"colParameter": "Parameter",
|
||
"colCurrent": "Aktuell",
|
||
"colAvgPeriod": "Ø Zeitraum",
|
||
"colMin": "Min",
|
||
"colMax": "Max",
|
||
"colN": "n",
|
||
"bpClassificationTitle": "Blutdruck — ESC/ESH-Klassifikation (2018)",
|
||
"avgBp": "Durchschnittlicher Blutdruck",
|
||
"classification": "Klassifikation",
|
||
"bmiTitle": "Body-Mass-Index (BMI)",
|
||
"bmiRow": "BMI: {bmi} kg/m² — Klassifikation: {class} (WHO)",
|
||
"complianceTitle": "Medikamenten-Compliance",
|
||
"colMedication": "Medikament",
|
||
"colTaken": "Eingenommen",
|
||
"colSkipped": "Übersprungen",
|
||
"colMissed": "Verpasst",
|
||
"colTotal": "Gesamt",
|
||
"colComplianceRate": "Compliance-Rate",
|
||
"moodTitle": "Stimmungsverlauf",
|
||
"moodSummary": "Durchschnitt: {avg} / 5,0 · Einträge: {count} · Bereich: {min}–{max}",
|
||
"colMood": "Stimmung",
|
||
"colCount": "Anzahl",
|
||
"colShare": "Anteil",
|
||
"footerDisclaimer1": "Dieser Bericht wurde automatisch aus selbst erfassten Daten generiert und dient ausschließlich zur Unterstützung",
|
||
"footerDisclaimer2": "des Arzt-Patienten-Gesprächs. Er ersetzt keine ärztliche Diagnose. Korrelationen implizieren keine Kausalität.",
|
||
"footerSource": "Quelle: HealthLog | Zeitzone: Europe/Berlin | Erstellt: {timestamp}",
|
||
"typeWeight": "Körpergewicht",
|
||
"typeBpSys": "Systolischer Blutdruck",
|
||
"typeBpDia": "Diastolischer Blutdruck",
|
||
"typePulse": "Ruhepuls",
|
||
"typeBodyFat": "Körperfettanteil",
|
||
"typeSleep": "Schlafdauer",
|
||
"typeSteps": "Aktivität (Schritte)",
|
||
"unitSteps": "Schritte",
|
||
"bmiUnderweight": "Untergewicht",
|
||
"bmiNormal": "Normalgewicht",
|
||
"bmiOverweight": "Übergewicht (Präadipositas)",
|
||
"bmiObeseGrade1": "Adipositas Grad I",
|
||
"bmiObeseGrade2": "Adipositas Grad II",
|
||
"bmiObeseGrade3": "Adipositas Grad III",
|
||
"bpOptimal": "Optimal",
|
||
"bpNormal": "Normal",
|
||
"bpHighNormal": "Hochnormal",
|
||
"bpHypertensionGrade1": "Hypertonie Grad 1",
|
||
"bpHypertensionGrade2": "Hypertonie Grad 2",
|
||
"bpHypertensionGrade3": "Hypertonie Grad 3",
|
||
"moodAwful": "Sehr schlecht",
|
||
"moodBad": "Schlecht",
|
||
"moodNeutral": "Neutral",
|
||
"moodGood": "Gut",
|
||
"moodGreat": "Sehr gut",
|
||
"typeGlucoseFasting": "Glukose (nüchtern)",
|
||
"typeGlucosePostprandial": "Glukose (postprandial)",
|
||
"typeGlucoseRandom": "Glukose (beliebig)",
|
||
"typeGlucoseBedtime": "Glukose (vor dem Schlafen)",
|
||
"glucoseClassificationTitle": "Blutzucker — Referenzbereiche (ADA 2024 / DDG)",
|
||
"glucoseRow": "{label}: Ø {avg} {unit} (Ziel {rangeMin}–{rangeMax}) — {class}",
|
||
"glucoseInTarget": "im Ziel",
|
||
"glucoseBelowTarget": "unter Ziel",
|
||
"glucoseAboveTarget": "über Ziel"
|
||
},
|
||
"errorBoundary": {
|
||
"title": "Da ist etwas schiefgelaufen",
|
||
"description": "Ein unerwarteter Fehler ist aufgetreten. Du kannst es erneut versuchen, die technischen Details kopieren oder einen Bug-Report öffnen."
|
||
},
|
||
"nav": {
|
||
"home": "Home",
|
||
"dashboard": "Dashboard",
|
||
"measurements": "Messungen",
|
||
"medications": "Medikamente",
|
||
"charts": "Verlauf",
|
||
"insights": "Insights",
|
||
"targets": "Zielwerte",
|
||
"achievements": "Erfolge",
|
||
"settings": "Einstellungen",
|
||
"bugreport": "Bugreport",
|
||
"admin": "Admin-Konsole",
|
||
"logout": "Abmelden",
|
||
"login": "Anmelden",
|
||
"notifications": "Benachrichtigungen",
|
||
"theme": "Darstellung",
|
||
"themeSystem": "System",
|
||
"themeDark": "Dunkel",
|
||
"themeLight": "Hell",
|
||
"collapseSidebar": "Einklappen",
|
||
"userMenu": "Benutzermenü",
|
||
"mood": "Stimmung",
|
||
"skipToContent": "Zum Inhalt springen",
|
||
"sidebar": "Sidebar",
|
||
"mainNavigation": "Hauptnavigation",
|
||
"mobileNavigation": "Mobile Navigation",
|
||
"loadingScreen": "Laden…"
|
||
},
|
||
"auth": {
|
||
"login": "Anmelden",
|
||
"register": "Registrieren",
|
||
"loginTitle": "Anmeldung",
|
||
"registerTitle": "Registrierung",
|
||
"username": "Benutzername",
|
||
"password": "Passwort",
|
||
"email": "E-Mail-Adresse",
|
||
"emailPlaceholder": "name@beispiel.de",
|
||
"emailOrUsername": "E-Mail-Adresse oder Benutzername",
|
||
"emailOrUsernamePlaceholder": "name@beispiel.de oder benutzername",
|
||
"loginWithPasskey": "Mit Passkey anmelden",
|
||
"loginWithPassword": "Mit Passwort anmelden",
|
||
"noAccount": "Noch kein Konto?",
|
||
"hasAccount": "Bereits ein Konto?",
|
||
"registerDisabled": "Registrierung ist deaktiviert.",
|
||
"loginFailed": "Anmeldung fehlgeschlagen",
|
||
"passkeyFailed": "Passkey-Anmeldung abgebrochen oder fehlgeschlagen",
|
||
"registerFailed": "Registrierung fehlgeschlagen",
|
||
"createAccount": "Konto erstellen",
|
||
"newAccount": "Neues Konto erstellen",
|
||
"backToPasskey": "Zurück zu Passkey",
|
||
"passkeySetupLater": "Passkeys kannst du jederzeit in den Einstellungen einrichten.",
|
||
"passwordMinLength": "Min. 12 Zeichen",
|
||
"passwordTooShort": "Passwort muss mindestens {minLength} Zeichen lang sein.",
|
||
"passwordStrength": "Passwortstärke",
|
||
"strengthWeak": "Sehr schwach",
|
||
"strengthFair": "Schwach",
|
||
"strengthGood": "Mittel",
|
||
"strengthStrong": "Stark",
|
||
"strengthVeryStrong": "Sehr stark"
|
||
},
|
||
"dashboard": {
|
||
"title": "Dashboard",
|
||
"subtitle": "Dein Gesundheitsüberblick",
|
||
"greeting": {
|
||
"morning": "Guten Morgen",
|
||
"day": "Hallo",
|
||
"evening": "Guten Abend"
|
||
},
|
||
"welcomeBack": "{greeting}, willkommen zurück.",
|
||
"welcomeBackWithName": "{greeting} {name}, willkommen zurück.",
|
||
"weight": "Gewicht",
|
||
"bloodPressure": "Blutdruck",
|
||
"bloodPressureSys": "Blutdruck (Sys)",
|
||
"bloodPressureDia": "Blutdruck (Dia)",
|
||
"pulse": "Puls",
|
||
"bodyFat": "Körperfett",
|
||
"bpInTarget": "BD im Zielbereich",
|
||
"days30Short": "30T",
|
||
"medications": "Medikamente",
|
||
"lastMeasurement": "Letzter Messwert",
|
||
"noMeasurements": "Noch keine Messwerte",
|
||
"trend": "Trend",
|
||
"trendUp": "steigend",
|
||
"trendDown": "fallend",
|
||
"trendStable": "stabil",
|
||
"quickEntry": "Schnelleingabe",
|
||
"addMeasurement": "Messwert hinzufügen",
|
||
"compliance7d": "Einnahmetreue (7 Tage)",
|
||
"complianceGood": "Gut",
|
||
"complianceLow": "Niedrig",
|
||
"mood": "Stimmung",
|
||
"customizeTitle": "Dashboard-Layout",
|
||
"customizeSubtitle": "Wähle, welche Karten auf dem Dashboard angezeigt werden und in welcher Reihenfolge. Defaults funktionieren sofort.",
|
||
"layoutReset": "Auf Defaults zurücksetzen",
|
||
"layoutSaveSuccess": "Dashboard-Layout gespeichert",
|
||
"layoutSaveError": "Layout konnte nicht gespeichert werden",
|
||
"layoutResetSuccess": "Auf Standard-Layout zurückgesetzt",
|
||
"layoutUsingDefaults": "Standard-Layout aktiv",
|
||
"layoutCustomized": "Eigenes Layout aktiv",
|
||
"moveUp": "Nach oben",
|
||
"moveDown": "Nach unten"
|
||
},
|
||
"measurements": {
|
||
"title": "Messungen",
|
||
"subtitle": "Gewicht, Blutdruck, Puls & mehr",
|
||
"addMeasurement": "Hinzufügen",
|
||
"editMeasurement": "Messwert bearbeiten",
|
||
"type": "Typ",
|
||
"value": "Wert",
|
||
"unit": "Einheit",
|
||
"date": "Datum",
|
||
"source": "Quelle",
|
||
"notes": "Notizen",
|
||
"typeWeight": "Gewicht",
|
||
"typeBloodPressure": "Blutdruck",
|
||
"typeBpSys": "Sys",
|
||
"typeBpDia": "Dia",
|
||
"typePulse": "Puls",
|
||
"typeBodyFat": "Körperfett",
|
||
"typeSleep": "Schlaf",
|
||
"typeSteps": "Schritte",
|
||
"typeBloodGlucose": "Blutzucker",
|
||
"glucoseContext": "Kontext",
|
||
"glucoseContextFasting": "Nüchtern (≥8 h ohne Nahrung)",
|
||
"glucoseContextPostprandial": "Postprandial (2 h nach dem Essen)",
|
||
"glucoseContextRandom": "Beliebig / ohne Kontext",
|
||
"glucoseContextBedtime": "Vor dem Schlafen",
|
||
"glucoseUnit": "Blutzucker-Einheit",
|
||
"glucoseUnitHint": "Gespeichert in mg/dL; Anzeige-Einheit ist deine Präferenz.",
|
||
"unitHours": "Stunden",
|
||
"unitSteps": "Schritte",
|
||
"sourceManual": "Manuell",
|
||
"sourceWithings": "Withings",
|
||
"sourceImport": "Import",
|
||
"systolicLabel": "Systolisch (mmHg)",
|
||
"diastolicLabel": "Diastolisch (mmHg)",
|
||
"pulseLabel": "Puls (bpm)",
|
||
"valueWithUnit": "Wert ({unit})",
|
||
"timestamp": "Zeitpunkt",
|
||
"notesPlaceholder": "z.B. nach dem Essen",
|
||
"formReset": "Formular zurücksetzen",
|
||
"saveError": "Fehler beim Speichern",
|
||
"deleteError": "Fehler beim Löschen",
|
||
"invalidValue": "Bitte einen gültigen Wert eingeben.",
|
||
"invalidTimestamp": "Bitte einen gültigen Zeitpunkt wählen.",
|
||
"loginRequired": "Bitte anmelden, um Messwerte zu sehen.",
|
||
"allTypes": "Alle Typen",
|
||
"measurementCount": "{count} Messwerte",
|
||
"noMeasurements": "Keine Messwerte vorhanden",
|
||
"comment": "Kommentar",
|
||
"actions": "Aktionen",
|
||
"deleteConfirmTitle": "Messwert löschen?",
|
||
"deleteConfirmDescription": "Dieser Messwert wird unwiderruflich gelöscht.",
|
||
"pageInfo": "Seite {page} von {total}",
|
||
"showing": "Zeigt",
|
||
"of": "von",
|
||
"entries": "Einträgen"
|
||
},
|
||
"mood": {
|
||
"title": "Stimmung",
|
||
"subtitle": "Stimmungsverlauf & Einträge",
|
||
"addEntry": "Hinzufügen",
|
||
"editEntry": "Eintrag bearbeiten",
|
||
"moodLevel": "Stimmung",
|
||
"tags": "Tags",
|
||
"tagsPlaceholder": "z.B. Sport, Arbeit, Familie",
|
||
"tagsHelp": "Kommagetrennt eingeben",
|
||
"timestamp": "Zeitpunkt",
|
||
"date": "Datum",
|
||
"source": "Quelle",
|
||
"levelSuperGut": "Super gut",
|
||
"levelGut": "Gut",
|
||
"levelOkay": "Okay",
|
||
"levelSchlecht": "Schlecht",
|
||
"levelLausig": "Lausig",
|
||
"sourceManual": "Manuell",
|
||
"sourceMoodlog": "moodLog",
|
||
"sourceWeb": "Web",
|
||
"sourceTelegram": "Telegram",
|
||
"sourceDaylio": "Daylio",
|
||
"allMoods": "Alle Stimmungen",
|
||
"entryCount": "{count} Einträge",
|
||
"noEntries": "Keine Stimmungseinträge vorhanden",
|
||
"actions": "Aktionen",
|
||
"formReset": "Formular zurücksetzen",
|
||
"saveError": "Fehler beim Speichern",
|
||
"deleteError": "Fehler beim Löschen",
|
||
"invalidTimestamp": "Bitte einen gültigen Zeitpunkt wählen.",
|
||
"deleteConfirmTitle": "Eintrag löschen?",
|
||
"deleteConfirmDescription": "Dieser Stimmungseintrag wird unwiderruflich gelöscht.",
|
||
"pageInfo": "Seite {page} von {total}",
|
||
"loginRequired": "Bitte anmelden, um Stimmungseinträge zu sehen."
|
||
},
|
||
"medications": {
|
||
"title": "Medikamente",
|
||
"subtitle": "Medikation, Zeitpläne & Einnahmen",
|
||
"loginRequired": "Bitte anmelden, um Medikamente zu verwalten.",
|
||
"addMedication": "Hinzufügen",
|
||
"editMedication": "Medikament bearbeiten",
|
||
"newMedication": "Neues Medikament",
|
||
"firstMedication": "Erstes Medikament anlegen",
|
||
"noMedicationsYet": "Noch keine Medikamente angelegt",
|
||
"loadFailed": "Medikamente konnten nicht geladen werden",
|
||
"retryLoad": "Erneut laden",
|
||
"name": "Name",
|
||
"dose": "Dosis",
|
||
"doseAmount": "Menge",
|
||
"doseUnit": "Einheit",
|
||
"category": "Kategorie",
|
||
"schedule": "Zeitfenster",
|
||
"scheduleFrom": "Von",
|
||
"scheduleTo": "Bis",
|
||
"scheduleLabel": "Bezeichnung",
|
||
"scheduleDays": "Tage",
|
||
"scheduleDaily": "Täglich",
|
||
"active": "Aktiv",
|
||
"inactive": "Pausiert",
|
||
"intakeHistory": "Einnahme-Verlauf",
|
||
"showIntakeHistory": "Einnahme-Verlauf anzeigen",
|
||
"hideIntakeHistory": "Einnahme-Verlauf ausblenden",
|
||
"markTaken": "Eingenommen",
|
||
"markSkipped": "Übersprungen",
|
||
"deleteConfirm": "Medikament löschen?",
|
||
"deleteDescription": "Das Medikament und alle Einnahmen werden unwiderruflich gelöscht.",
|
||
"noMedications": "Keine Medikamente vorhanden.",
|
||
"compliance": "Einnahmetreue",
|
||
"import": "Import",
|
||
"importIntakes": "Einnahmen importieren",
|
||
"importDescription": "JSON-Array mit Einnahme-Daten einfügen. Erwartetes Format:",
|
||
"importUploadFile": "JSON-Datei hochladen",
|
||
"importPaste": "JSON hier einfügen...",
|
||
"importSelected": "Ausgewählt: {name}",
|
||
"importFileLoaded": "Datei \"{name}\" geladen",
|
||
"importInvalidJson": "Datei enthält kein gültiges JSON",
|
||
"importNoArray": "Kein Array gefunden",
|
||
"importResult": "{imported} Einnahmen importiert",
|
||
"importDuplicatesSkipped": "{count} Duplikate übersprungen",
|
||
"importInvalidSkipped": "{count} ungültige Einträge übersprungen",
|
||
"importFailed": "Import fehlgeschlagen",
|
||
"importInvalidFormat": "Ungültiges JSON-Format",
|
||
"apiAccess": "API-Zugang",
|
||
"apiEndpointTitle": "API-Endpoint: {name}",
|
||
"apiEndpointDescription": "Nutze diesen Endpoint, um Einnahmen per API zu erfassen (z.B. via iPhone-Kurzbefehl oder cron-Job).",
|
||
"apiEndpointActive": "API-Endpoint aktiv",
|
||
"apiEndpointActivated": "API-Endpoint aktiviert",
|
||
"apiEndpointDeactivated": "API-Endpoint deaktiviert",
|
||
"apiTokenCount": "Aktiv ({count} Token)",
|
||
"apiToken": "API-Token",
|
||
"apiTokenActiveHint": "Endpoint ist aktiv. Vorhandene Tokens bleiben gültig, können aber nicht erneut im Klartext angezeigt werden.",
|
||
"apiTokenActivateHint": "Aktiviere den Endpoint, um einen neuen Token zu erstellen.",
|
||
"apiTokenOnceHint": "Der Token wird nur bei der Erstellung einmal im Klartext angezeigt.",
|
||
"requestExample": "Request-Beispiel",
|
||
"statusLoadFailed": "Status konnte nicht geladen werden",
|
||
"changeFailed": "Ändern fehlgeschlagen",
|
||
"categoryBloodPressure": "Blutdrucksenker",
|
||
"categoryCholesterol": "Cholesterin",
|
||
"categoryDiabetes": "Diabetes",
|
||
"categoryVitamin": "Vitamine",
|
||
"categoryThyroid": "Schilddrüse",
|
||
"categoryHormone": "Hormone",
|
||
"categorySupplement": "Nahrungsergänzung",
|
||
"categoryPainRelief": "Schmerzmittel",
|
||
"categoryAllergy": "Allergie",
|
||
"categoryDigestive": "Magen/Darm",
|
||
"categorySkin": "Hautpflege",
|
||
"categorySleepAid": "Schlafmittel",
|
||
"categoryOther": "Sonstiges",
|
||
"daysSun": "So",
|
||
"daysMon": "Mo",
|
||
"daysTue": "Di",
|
||
"daysWed": "Mi",
|
||
"daysThu": "Do",
|
||
"daysFri": "Fr",
|
||
"daysSat": "Sa",
|
||
"intakeHistoryTitle": "Einnahme-Verlauf",
|
||
"newIntake": "Hinzufügen",
|
||
"editIntake": "Einnahme bearbeiten",
|
||
"intakeScheduledFor": "Geplant",
|
||
"intakeTakenAt": "Eingenommen um",
|
||
"intakeStatus": "Status",
|
||
"intakeSource": "Quelle",
|
||
"intakeStatusTaken": "Eingenommen",
|
||
"intakeStatusSkipped": "Übersprungen",
|
||
"intakeStatusMissed": "Verpasst",
|
||
"intakeDeleteConfirm": "Einnahme-Eintrag löschen?",
|
||
"intakeDeleteDescription": "Dieser Einnahme-Eintrag wird unwiderruflich gelöscht.",
|
||
"intakeCount": "{count} Einnahmen",
|
||
"viewHistory": "Verlauf",
|
||
"today": "Heute",
|
||
"yesterday": "Gestern",
|
||
"tomorrow": "Morgen",
|
||
"withoutNotification": "Ohne Benachrichtigung",
|
||
"pausedSince": "Pausiert seit",
|
||
"takeNow": "Jetzt einnehmen",
|
||
"overdue": "Überfällig",
|
||
"veryOverdue": "Stark überfällig",
|
||
"compliance7d": "7-Tage-Compliance",
|
||
"compliance30d": "30-Tage-Compliance",
|
||
"lastIntake": "Letzte Einnahme:",
|
||
"nextIntake": "Nächste Einnahme:",
|
||
"dayStreak": "Tage Serie",
|
||
"taken": "Genommen",
|
||
"skipped": "Übersprungen",
|
||
"weekdaySunday": "Sonntag",
|
||
"weekdayMonday": "Montag",
|
||
"weekdayTuesday": "Dienstag",
|
||
"weekdayWednesday": "Mittwoch",
|
||
"weekdayThursday": "Donnerstag",
|
||
"weekdayFriday": "Freitag",
|
||
"weekdaySaturday": "Samstag",
|
||
"weekdaySundayPlural": "Sonntags",
|
||
"weekdayMondayPlural": "Montags",
|
||
"weekdayTuesdayPlural": "Dienstags",
|
||
"weekdayWednesdayPlural": "Mittwochs",
|
||
"weekdayThursdayPlural": "Donnerstags",
|
||
"weekdayFridayPlural": "Freitags",
|
||
"weekdaySaturdayPlural": "Samstags",
|
||
"formType": "Typ",
|
||
"formDose": "Dosis",
|
||
"formUnit": "Einheit",
|
||
"formSchedule": "Zeitfenster",
|
||
"newSchedule": "Neues Zeitfenster",
|
||
"removeSchedule": "Zeitfenster entfernen",
|
||
"advancedEditing": "Erweiterte Bearbeitung",
|
||
"advancedEditingHide": "Erweiterte Bearbeitung ausblenden",
|
||
"scheduleInterval": "Intervall",
|
||
"scheduleEveryWeek": "jede Woche",
|
||
"scheduleEveryNWeeks": "alle {n} Wochen",
|
||
"scheduleIntervalPrefix": "Intervall:",
|
||
"nextSchedulePrefix": "Nächstes Zeitfenster:",
|
||
"nextScheduleDaily": "täglich",
|
||
"importIntakesAction": "Einnahmen importieren",
|
||
"apiEndpointAction": "API-Endpunkt",
|
||
"pause": "Pausieren",
|
||
"activate": "Aktivieren",
|
||
"disableNotifications": "Benachrichtigungen deaktivieren",
|
||
"enableNotifications": "Benachrichtigungen aktivieren",
|
||
"purgeRecords": "Alle Aufzeichnungen löschen",
|
||
"formReset": "Formular zurücksetzen",
|
||
"createMedication": "Medikament anlegen",
|
||
"deleteConfirmDescription": "Das Medikament und die gesamte Einnahme-Historie werden unwiderruflich gelöscht.",
|
||
"purgeConfirmTitle": "Alle Aufzeichnungen löschen?",
|
||
"purgeConfirmDescription": "Alle Einnahme-Aufzeichnungen für dieses Medikament werden unwiderruflich gelöscht. Das Medikament selbst bleibt bestehen und startet ohne Verlaufsdaten.",
|
||
"saveError": "Fehler beim Speichern",
|
||
"deleteError": "Fehler beim Löschen",
|
||
"purgeError": "Fehler beim Löschen der Aufzeichnungen",
|
||
"invalidTimestamp": "Bitte einen gültigen Zeitpunkt wählen.",
|
||
"noIntakesYet": "Noch keine Einnahmen vorhanden",
|
||
"noIntakesForMedication": "Noch keine Einnahmen für",
|
||
"deleteIntakeAriaLabel": "Einnahme löschen",
|
||
"actions": "Aktionen",
|
||
"pageInfo": "Seite {page} von {total}",
|
||
"loginRequiredIntakes": "Bitte anmelden, um Einnahmen zu sehen.",
|
||
"clear": "Löschen",
|
||
"phaseConfig": "Erinnerungsphasen",
|
||
"phaseConfigDescription": "Konfiguriere die Zeitpunkte für Erinnerungen.",
|
||
"phaseGreen": "Grün",
|
||
"phaseYellow": "Gelb",
|
||
"phaseOrange": "Orange",
|
||
"phaseRed": "Rot",
|
||
"phaseMinutes": "Min",
|
||
"phasePercent": "%",
|
||
"phaseBeforeEnd": "vor Ende",
|
||
"phaseAfterEnd": "nach Ende",
|
||
"phaseResetDefaults": "Auf Defaults zurücksetzen",
|
||
"phaseSaved": "Erinnerungsphasen gespeichert",
|
||
"phaseReset": "Auf Defaults zurückgesetzt",
|
||
"sourceWeb": "Webseite",
|
||
"sourceApi": "Externe API",
|
||
"sourceReminder": "Telegram / Erinnerung",
|
||
"sourceImport": "CSV-Import",
|
||
"unitDrops": "Tropfen",
|
||
"unitTablets": "Tablette(n)",
|
||
"unitCapsules": "Kapsel(n)",
|
||
"unitPieces": "Stück",
|
||
"namePlaceholder": "z.B. Ibuprofen",
|
||
"dosePlaceholder": "z.B. 400",
|
||
"labelPlaceholder": "z.B. Morgens",
|
||
"defaultDose": "Standard",
|
||
"dayDeactivate": "{day} deaktivieren",
|
||
"dayActivate": "{day} aktivieren"
|
||
},
|
||
"charts": {
|
||
"title": "Verlauf",
|
||
"subtitle": "Visualisierung deiner Gesundheitsdaten",
|
||
"timeRange": "Zeitraum",
|
||
"days7": "7 Tage",
|
||
"days30": "30 Tage",
|
||
"days90": "90 Tage",
|
||
"days365": "1 Jahr",
|
||
"daysAll": "Alle",
|
||
"weight": "Gewicht",
|
||
"bloodPressure": "Blutdruck",
|
||
"pulse": "Puls",
|
||
"bodyFat": "Körperfett",
|
||
"sleep": "Schlaf",
|
||
"steps": "Schritte",
|
||
"compliance": "Einnahmetreue",
|
||
"noData": "Keine Daten im gewählten Zeitraum",
|
||
"movingAverage": "Gleitender Durchschnitt",
|
||
"target": "Zielbereich",
|
||
"points7Label": "7 Pkt",
|
||
"points7Title": "Letzte 7 Messpunkte",
|
||
"points30Label": "30 Pkt",
|
||
"points30Title": "Letzte 30 Messpunkte",
|
||
"points90Label": "90 Pkt",
|
||
"points90Title": "Letzte 90 Messpunkte",
|
||
"pointsAllLabel": "Alle",
|
||
"pointsAllTitle": "Alle verfügbaren Messpunkte",
|
||
"systolic": "Systolisch",
|
||
"diastolic": "Diastolisch",
|
||
"movingAverage7d": "7T-Schnitt",
|
||
"avg7dShort": "7T",
|
||
"avg30dShort": "30T",
|
||
"trend": "Trend",
|
||
"targetRanges": "Zielbereiche",
|
||
"perWeek": "/Woche",
|
||
"selectMedication": "Medikament wählen",
|
||
"noComplianceData": "Keine Compliance-Daten verfügbar",
|
||
"history": "Verlauf",
|
||
"complianceDays90": "Einnahmetreue (90 Tage)",
|
||
"colorGreen": "Grün:",
|
||
"colorOrange": "Orange:",
|
||
"colorRed": "Rot:",
|
||
"mood": "Stimmung",
|
||
"moodScore": "Stimmungswert",
|
||
"moodMA": "7-Tage-Schnitt",
|
||
"moodLabel1": "Lausig",
|
||
"moodLabel2": "Schlecht",
|
||
"moodLabel3": "Okay",
|
||
"moodLabel4": "Gut",
|
||
"moodLabel5": "Super",
|
||
"weekdays": {
|
||
"mon": "Mo",
|
||
"wed": "Mi",
|
||
"fri": "Fr",
|
||
"sun": "So"
|
||
},
|
||
"months": {
|
||
"jan": "Jan",
|
||
"feb": "Feb",
|
||
"mar": "Mär",
|
||
"apr": "Apr",
|
||
"may": "Mai",
|
||
"jun": "Jun",
|
||
"jul": "Jul",
|
||
"aug": "Aug",
|
||
"sep": "Sep",
|
||
"oct": "Okt",
|
||
"nov": "Nov",
|
||
"dec": "Dez"
|
||
},
|
||
"heatmapOnTime": "pünktlich",
|
||
"heatmapLate": "spät",
|
||
"heatmapVeryLate": "sehr spät",
|
||
"legendOnTime": "Pünktlich",
|
||
"legendLate": "Spät",
|
||
"legendVeryLate": "Sehr spät",
|
||
"legendMissed": "Verpasst"
|
||
},
|
||
"insights": {
|
||
"aiAnalysisTitle": "KI-Gesundheitsanalyse",
|
||
"noAnalysisYet": "Noch keine Analyse vorhanden.",
|
||
"noProviderConfigured": "KI-Provider nicht konfiguriert.",
|
||
"startAnalysis": "Analyse starten",
|
||
"refreshAnalysis": "Analyse aktualisieren",
|
||
"keyTakeaway": "Das Wichtigste",
|
||
"findingsTitle": "Befunde",
|
||
"correlationsTitle": "Zusammenhänge",
|
||
"recommendationsTitle": "Empfehlungen",
|
||
"dataFoundation": "Datengrundlage",
|
||
"dataGaps": "Datenlücken:",
|
||
"lastUpdated": "Zuletzt aktualisiert",
|
||
"confidenceHoch": "Hoch",
|
||
"confidenceMittel": "Mittel",
|
||
"confidenceGering": "Gering",
|
||
"loadError": "Auswertungen konnten nicht geladen werden",
|
||
"generationFailed": "Die Analyse konnte nicht erstellt werden. Bitte erneut versuchen.",
|
||
"title": "Insights",
|
||
"subtitle": "KI-basierte Analyse deiner Gesundheitsdaten",
|
||
"overviewSubtitle": "Trends, Risiken, Fortschritt auf einen Blick",
|
||
"navGeneral": "Allgemein",
|
||
"navBloodPressure": "Blutdruck",
|
||
"navWeight": "Gewicht",
|
||
"navPulse": "Puls",
|
||
"navMood": "Stimmung",
|
||
"navMedication": "Medikamente",
|
||
"navBmi": "BMI",
|
||
"measurementsOverDays": "{count} Messungen über {days} Tage",
|
||
"healthStatus": "Gesundheitsstatus",
|
||
"noWeightData": "Keine Gewichtsdaten oder Größe nicht hinterlegt.",
|
||
"notEnoughBpData": "Nicht genügend Blutdruckdaten.",
|
||
"bpTargetRange": "Zielbereich Blutdruck",
|
||
"inTargetRange": "im Zielbereich",
|
||
"bpTarget": "Ziel: {range}",
|
||
"noDobSet": "Geburtsdatum nicht hinterlegt.",
|
||
"avg30dEsc": "30T-Durchschnitt · ESC/ESH 2018",
|
||
"whoClassification": "WHO-Klassifikation",
|
||
"correlations": "Korrelationen",
|
||
"weightVsBp": "Gewicht vs. Blutdruck (Systolisch)",
|
||
"strengthStrong": "Starke Korrelation",
|
||
"strengthModerate": "Moderate Korrelation",
|
||
"strengthWeak": "Schwache Korrelation",
|
||
"strengthNone": "Keine Korrelation",
|
||
"weightBpStrong": "Es besteht ein starker Zusammenhang zwischen Gewicht und Blutdruck.",
|
||
"weightBpModerate": "Es besteht ein moderater Zusammenhang zwischen Gewicht und Blutdruck.",
|
||
"weightBpWeak": "Es besteht ein schwacher Zusammenhang zwischen Gewicht und Blutdruck.",
|
||
"weightBpNone": "Kein erkennbarer Zusammenhang zwischen Gewicht und Blutdruck.",
|
||
"notEnoughCorrelationData": "Zu wenig Daten für Korrelationsanalyse.",
|
||
"minCorrelationData": "Mindestens 5 zeitlich übereinstimmende Gewichts- und Blutdruckmessungen nötig.",
|
||
"bpMedContinuity": "Blutdrucksenker-Kontinuität vs. Blutdruck (Systolisch)",
|
||
"continuityLabel": "Kontinuität",
|
||
"continuityBpLabel": "Kontinuität Blutdrucksenker (%)",
|
||
"weightKgLabel": "Gewicht (kg)",
|
||
"bpMedNegative": "Negativer Zusammenhang: höhere Kontinuität geht eher mit niedrigerem systolischen Blutdruck einher.",
|
||
"bpMedPositive": "Positiver Zusammenhang: höhere Kontinuität geht in deinen Daten eher mit höherem systolischen Blutdruck einher.",
|
||
"bpMedNone": "Kein erkennbarer Zusammenhang in deinen Daten.",
|
||
"bpMedCount": "{count} Blutdrucksenker",
|
||
"notEnoughMedData": "Zu wenig Daten für diese Analyse.",
|
||
"notEnoughMedDataHint": "Markiere Medikamente als Blutdrucksenker und erfasse ausreichend Einnahmen + Blutdruckmessungen.",
|
||
"medicationCompliance": "Medikamenten-Compliance",
|
||
"dayStreak": "{count} Tage Serie",
|
||
"compliance7d": "7-Tage-Compliance",
|
||
"compliance30d": "30-Tage-Compliance",
|
||
"taken": "genommen",
|
||
"skipped": "übersprungen",
|
||
"missed": "verpasst",
|
||
"complianceTrend": "Compliance-Verlauf",
|
||
"generalStatusTitle": "Allgemeiner Gesundheitszustand",
|
||
"generalStatusLoading": "Allgemeiner Zustand wird erstellt...",
|
||
"generalStatusNoKey": "Der allgemeine Zustand kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"generalStatusUnavailable": "Der allgemeine Zustand konnte aktuell nicht geladen werden.",
|
||
"generalStatusBadge": {
|
||
"good": "Gut",
|
||
"watch": "Beobachten",
|
||
"critical": "Kritisch"
|
||
},
|
||
"assessmentTitle": "Einschätzung",
|
||
"bloodPressureSectionTitle": "Blutdruck",
|
||
"bloodPressureMedicationListTitle": "Blutdrucksenker & Compliance",
|
||
"bloodPressureMedicationListEmpty": "Keine aktiven Blutdrucksenker hinterlegt.",
|
||
"bloodPressureStatusLoading": "Blutdruck-Zusammenfassung wird erstellt...",
|
||
"bloodPressureStatusNoKey": "Die Blutdruck-Zusammenfassung kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"bloodPressureStatusUnavailable": "Die Blutdruck-Zusammenfassung konnte aktuell nicht geladen werden.",
|
||
"weightSectionTitle": "Gewicht",
|
||
"weightStatusLoading": "Gewichts-Zusammenfassung wird erstellt...",
|
||
"weightStatusNoKey": "Die Gewichts-Zusammenfassung kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"weightStatusUnavailable": "Die Gewichts-Zusammenfassung konnte aktuell nicht geladen werden.",
|
||
"pulseSectionTitle": "Puls",
|
||
"pulseStatusLoading": "Puls-Zusammenfassung wird erstellt...",
|
||
"pulseStatusNoKey": "Die Puls-Zusammenfassung kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"pulseStatusUnavailable": "Die Puls-Zusammenfassung konnte aktuell nicht geladen werden.",
|
||
"bmiSectionTitle": "BMI",
|
||
"bmiStatusLoading": "BMI-Zusammenfassung wird erstellt...",
|
||
"bmiStatusNoKey": "Die BMI-Zusammenfassung kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"bmiStatusUnavailable": "Die BMI-Zusammenfassung konnte aktuell nicht geladen werden.",
|
||
"medicationComplianceStatusLoading": "Text zur Medikamentencompliance wird erstellt...",
|
||
"medicationComplianceStatusNoKey": "Der Text zur Medikamentencompliance kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"medicationComplianceStatusUnavailable": "Der Text zur Medikamentencompliance konnte aktuell nicht geladen werden.",
|
||
"complianceNoData": "Keine Compliance-Daten verfügbar",
|
||
"aiInsights": "KI-Insights",
|
||
"configureApiKey": "Konfiguriere einen OpenAI API-Key in den Einstellungen, um KI-basierte Insights zu erhalten.",
|
||
"alertsTitle": "Hinweise & Empfehlungen",
|
||
"showLess": "Weniger anzeigen",
|
||
"showMore": "+{count} weitere",
|
||
"generate": "Neue Analyse erstellen",
|
||
"generating": "Analyse wird erstellt...",
|
||
"lastGenerated": "Letzte Analyse",
|
||
"noInsights": "Noch keine Analyse vorhanden.",
|
||
"rateLimit": "Bitte warte, bevor du eine neue Analyse erstellst.",
|
||
"noApiKey": "Bitte hinterlege zuerst einen OpenAI API-Key in den Einstellungen.",
|
||
"sendRawData": "Rohdaten mitsenden",
|
||
"minMoodCorrelationData": "Mindestens 5 zeitlich übereinstimmende Stimmungs- und Messdaten nötig.",
|
||
"moodBpModerate": "Es besteht ein moderater Zusammenhang zwischen Stimmung und Blutdruck.",
|
||
"moodBpNone": "Kein erkennbarer Zusammenhang zwischen Stimmung und Blutdruck.",
|
||
"moodBpStrong": "Es besteht ein starker Zusammenhang zwischen Stimmung und Blutdruck.",
|
||
"moodBpWeak": "Es besteht ein schwacher Zusammenhang zwischen Stimmung und Blutdruck.",
|
||
"moodScoreLabel": "Stimmung (Ø)",
|
||
"moodSectionTitle": "Stimmung",
|
||
"moodStatusLoading": "Stimmungsanalyse wird erstellt...",
|
||
"moodStatusNoKey": "Die Stimmungs-Zusammenfassung kann nicht erstellt werden, da kein OpenAI-KI-Schlüssel hinterlegt ist.",
|
||
"moodStatusUnavailable": "Stimmungsanalyse nicht verfügbar",
|
||
"moodVsBp": "Stimmung vs. Blutdruck (Systolisch)",
|
||
"moodVsPulse": "Stimmung vs. Puls",
|
||
"moodVsWeight": "Stimmung vs. Gewicht",
|
||
"moodWeightModerate": "Es besteht ein moderater Zusammenhang zwischen Stimmung und Gewicht.",
|
||
"moodWeightNone": "Kein erkennbarer Zusammenhang zwischen Stimmung und Gewicht.",
|
||
"moodWeightStrong": "Es besteht ein starker Zusammenhang zwischen Stimmung und Gewicht.",
|
||
"moodWeightWeak": "Es besteht ein schwacher Zusammenhang zwischen Stimmung und Gewicht.",
|
||
"notEnoughMoodCorrelationData": "Zu wenig Stimmungsdaten für Korrelationsanalyse.",
|
||
"refreshButton": "Aktualisieren",
|
||
"generateButton": "Generieren",
|
||
"analyzing": "Analysiere Gesundheitsdaten...",
|
||
"confidence": "Konfidenz",
|
||
"confidence_niedrig": "niedrig",
|
||
"confidence_mittel": "mittel",
|
||
"confidence_hoch": "hoch",
|
||
"cached": "Gecacht",
|
||
"sectionChanged": "Was hat sich verändert",
|
||
"sectionStable": "Was ist stabil",
|
||
"sectionDrivers": "Mögliche Zusammenhänge",
|
||
"sectionNextSteps": "Nächste Schritte",
|
||
"generatePrompt": "Klicke auf \"Generieren\" für KI-basierte Insights deiner Gesundheitsdaten."
|
||
},
|
||
"thresholds": {
|
||
"title": "Persönliche Zielwerte",
|
||
"subtitle": "Überschreibe den berechneten Zielbereich für jede Metrik. Ohne Eintrag gilt der Default.",
|
||
"defaultLabel": "Default",
|
||
"overrideLabel": "Dein Wert",
|
||
"autoModeLabel": "Auto",
|
||
"overrideModeLabel": "Überschrieben",
|
||
"resetAction": "Auf Default zurücksetzen",
|
||
"resetAllAction": "Alle auf Default zurücksetzen",
|
||
"minLabel": "Min",
|
||
"maxLabel": "Max",
|
||
"unitSuffix": "({unit})",
|
||
"outOfBoundsHint": "Erlaubter Bereich: {min}–{max} {unit}",
|
||
"overrideWarning": "Werte außerhalb der Leitlinien können zu ungesunden Zielen führen. Nur nach ärztlicher Rücksprache überschreiben.",
|
||
"saveSuccess": "Zielwerte aktualisiert",
|
||
"saveError": "Zielwerte konnten nicht gespeichert werden",
|
||
"resetSuccess": "Auf Default zurückgesetzt",
|
||
"sourceOverride": "Eigener Wert",
|
||
"metricWeight": "Gewicht",
|
||
"metricBpSys": "Blutdruck (systolisch)",
|
||
"metricBpDia": "Blutdruck (diastolisch)",
|
||
"metricPulse": "Ruhepuls",
|
||
"metricBodyFat": "Körperfett",
|
||
"metricSleep": "Schlafdauer",
|
||
"metricSteps": "Schritte/Tag",
|
||
"metricGlucoseFasting": "Glukose — nüchtern",
|
||
"metricGlucosePostprandial": "Glukose — postprandial",
|
||
"metricGlucoseRandom": "Glukose — beliebig",
|
||
"metricGlucoseBedtime": "Glukose — vor dem Schlafen",
|
||
"unsetExplanation": "Kein eigener Wert — berechneter Default aktiv.",
|
||
"loadError": "Zielwerte konnten nicht geladen werden"
|
||
},
|
||
"targets": {
|
||
"title": "Zielwerte",
|
||
"subtitle": "Persönliche Referenzbereiche basierend auf deinem Profil",
|
||
"introText": "Diese Zielwerte sind persönliche Referenzbereiche, die aus deinem Profil und anerkannten medizinischen Richtwerten abgeleitet werden. Sie helfen dir, deine wichtigsten Gesundheitswerte schnell einzuordnen und Entwicklungen früh zu erkennen. Die Bereiche werden für dich individuell berechnet, zum Beispiel anhand von Alter, Geschlecht und Körpergröße. Zu jedem Wert siehst du direkt, ob du im Zielbereich liegst oder darüber beziehungsweise darunter. So bekommst du eine kompakte Übersicht darüber, welche Zielwerte du aktuell gut einhältst und wo noch Potenzial besteht.",
|
||
"current": "Aktuell",
|
||
"average30d": "30-Tage-Durchschnitt:",
|
||
"targetRange": "Zielbereich",
|
||
"classification": "Bewertung",
|
||
"source": "Quelle",
|
||
"sourceLabel": "Quelle: {source}",
|
||
"trend": "Trend",
|
||
"weight": "Gewicht",
|
||
"bloodPressure": "Blutdruck",
|
||
"pulse": "Ruhepuls",
|
||
"sleep": "Schlafdauer",
|
||
"bmi": "BMI",
|
||
"bodyFat": "Körperfett",
|
||
"steps": "Schritte/Tag",
|
||
"compliance": "Einnahmetreue",
|
||
"compliancePerMedication": "Pro Medikament",
|
||
"diastolic": "Diastolisch",
|
||
"noProfile": "Bitte fülle dein Profil aus, um personalisierte Zielwerte zu erhalten.",
|
||
"profileIncomplete": "Profil unvollständig",
|
||
"profileIncompleteHeightAge": "Hinterlege deine Größe und dein Geburtsdatum im Profil, um personalisierte Zielbereiche zu erhalten.",
|
||
"profileIncompleteHeight": "Hinterlege deine Größe im Profil, um den Gewichts-Zielbereich zu berechnen.",
|
||
"profileIncompleteAge": "Hinterlege dein Geburtsdatum im Profil, um altersbasierte Zielbereiche zu erhalten.",
|
||
"noMeasurementData": "Noch keine Messdaten vorhanden.",
|
||
"noMeasurementYet": "Noch keine Messung vorhanden",
|
||
"currentValue": "Aktuell: {value} {unit}",
|
||
"targetRangeValue": "Zielbereich: {min}–{max} {unit}",
|
||
"inTarget": "Im Zielbereich",
|
||
"belowTarget": "{delta} {unit} unter Ziel",
|
||
"aboveTarget": "{delta} {unit} über Ziel",
|
||
"loadError": "Fehler beim Laden",
|
||
"classificationOptimal": "Optimal",
|
||
"classificationNormal": "Normal",
|
||
"classificationElevated": "Erhöht",
|
||
"classificationHigh": "Hoch",
|
||
"classificationLow": "Niedrig",
|
||
"glucoseFasting": "Glukose — nüchtern",
|
||
"glucosePostprandial": "Glukose — postprandial",
|
||
"glucoseRandom": "Glukose — beliebig",
|
||
"glucoseBedtime": "Glukose — vor dem Schlafen"
|
||
},
|
||
"onboarding": {
|
||
"welcome": "Willkommen bei HealthLog!",
|
||
"setupDescription": "Richte dein Profil ein, um loszulegen. Du kannst das auch später in den Einstellungen ändern.",
|
||
"completeSetup": "Einrichtung abschließen",
|
||
"skip": "Überspringen",
|
||
"medicationsTitle": "Nimmst du Medikamente?",
|
||
"medicationsDescription": "Füge deine Medikamente hinzu, um Erinnerungen und Compliance-Tracking zu nutzen.",
|
||
"medNamePlaceholder": "Name",
|
||
"medDosePlaceholder": "Dosis",
|
||
"notificationsTitle": "Erinnerungen",
|
||
"notificationsDescription": "HealthLog kann dich an deine Medikamente erinnern. Konfiguriere dies später in den Einstellungen.",
|
||
"telegramHint": "Erinnerungen per Telegram-Bot mit Snooze-Buttons",
|
||
"webPushHint": "Browser-Benachrichtigungen direkt auf dein Gerät",
|
||
"notificationsLater": "Du kannst dies jederzeit in den Einstellungen einrichten.",
|
||
"targetsTitle": "Zielwerte",
|
||
"targetsDescription": "Setze optionale Zielwerte für Blutdruck und Gewicht.",
|
||
"targetSys": "Systolisch (mmHg)",
|
||
"targetDia": "Diastolisch (mmHg)",
|
||
"targetWeight": "Zielgewicht (kg)"
|
||
},
|
||
"settings": {
|
||
"title": "Einstellungen",
|
||
"subtitle": "Profil, Sicherheit & Verbindungen",
|
||
"sectionsTitle": "Settings",
|
||
"categoryGeneral": "Allgemein",
|
||
"categoryNotifications": "Benachrichtigungen",
|
||
"categoryAi": "KI",
|
||
"categoryPersonalization": "Personalisierung",
|
||
"categoryIntegration": "Integration",
|
||
"categoryApi": "API",
|
||
"profile": "Profil",
|
||
"security": "Sicherheit",
|
||
"language": "Sprache",
|
||
"languageDescription": "Wähle die Anzeigesprache der Anwendung.",
|
||
"languageSystem": "System (automatisch)",
|
||
"username": "Benutzername",
|
||
"height": "Körpergröße (cm)",
|
||
"dateOfBirth": "Geburtsdatum",
|
||
"dateOfBirthHint": "Wird für die automatische Berechnung der Blutdruck-Zielwerte benötigt.",
|
||
"gender": "Geschlecht",
|
||
"genderNone": "Keine Angabe",
|
||
"genderMale": "Männlich",
|
||
"genderFemale": "Weiblich",
|
||
"genderHint": "Wird für geschlechtsspezifische Zielwerte verwendet.",
|
||
"profileSaved": "Profil gespeichert",
|
||
"changePassword": "Passwort ändern",
|
||
"changePasswordDescription": "Ersetze dein aktuelles Passwort durch ein neues.",
|
||
"passwordReset": "Passwort-Reset",
|
||
"currentPassword": "Aktuelles Passwort",
|
||
"newPassword": "Neues Passwort",
|
||
"confirmNewPassword": "Neues Passwort bestätigen",
|
||
"passwordMismatch": "Neue Passwörter stimmen nicht überein",
|
||
"passwordUpdated": "Passwort erfolgreich geändert",
|
||
"passkeys": "Passkeys",
|
||
"registeredPasskeys": "Registrierte Passkeys",
|
||
"passkeysDescription": "Passkeys ermöglichen sicheres Anmelden ohne Passwort.",
|
||
"passkeyName": "Name",
|
||
"passkeyDevice": "Gerät",
|
||
"passkeyBackup": "Backup",
|
||
"passkeyCreated": "Erstellt",
|
||
"passkeyActions": "Aktionen",
|
||
"noPasskeys": "Keine Passkeys registriert.",
|
||
"addPasskey": "Passkey hinzufügen",
|
||
"passkeyAdded": "Passkey erfolgreich hinzugefügt!",
|
||
"passkeyOptionsError": "Konnte Passkey-Optionen nicht laden",
|
||
"passkeyRegistrationFailed": "Fehler bei Passkey-Registrierung",
|
||
"passkeyRegistrationCancelled": "Passkey-Registrierung abgebrochen",
|
||
"passkeyNotSupported": "Dein Browser oder Gerät unterstützt Passkeys noch nicht. Bitte einen anderen Browser oder ein anderes Gerät verwenden.",
|
||
"passkeyAlreadyRegistered": "Dieses Gerät ist bereits als Passkey für dein Konto registriert.",
|
||
"passkeySecurityBlocked": "Dein Browser hat die Passkey-Anfrage aus Sicherheitsgründen blockiert. Bitte auf einer vertrauenswürdigen HTTPS-URL erneut versuchen.",
|
||
"passkeyTimeout": "Die Passkey-Aufforderung ist abgelaufen. Bitte erneut versuchen.",
|
||
"passkeyUnknownError": "Passkey-Registrierung fehlgeschlagen: {message}",
|
||
"deletePasskey": "Passkey löschen?",
|
||
"deletePasskeyDescription": "Der Passkey wird unwiderruflich gelöscht.",
|
||
"singleDevice": "Einzelgerät",
|
||
"multiDevice": "Multi-Gerät",
|
||
"backedUp": "gesichert",
|
||
"telegram": "Telegram-Benachrichtigungen",
|
||
"telegramDescription": "Erhalte Erinnerungen bei vergessenen Medikamenten-Einnahmen per Telegram.",
|
||
"telegramSaved": "Telegram-Einstellungen gespeichert",
|
||
"botToken": "Bot-Token",
|
||
"chatId": "Chat-ID",
|
||
"enableNotifications": "Benachrichtigungen aktivieren",
|
||
"testMessage": "Test-Nachricht",
|
||
"testSent": "Test-Nachricht gesendet!",
|
||
"telegramStep1": "1. Erstelle einen Bot über @BotFather in Telegram und kopiere den Token.",
|
||
"telegramStep2": "2. Sende /start an deinen Bot, um den Chat zu aktivieren.",
|
||
"telegramStep3": "3. Finde deine Chat-ID über @userinfobot oder die Bot-API.",
|
||
"ntfy": "ntfy",
|
||
"ntfyDescription": "Benachrichtigungen über ntfy (self-hosted oder ntfy.sh).",
|
||
"ntfyEnable": "ntfy aktivieren",
|
||
"ntfyServer": "Server-URL",
|
||
"ntfyTopic": "Topic",
|
||
"ntfyAuthToken": "Auth-Token (optional)",
|
||
"ntfyAuthTokenHint": "Nur nötig bei privaten Topics mit Zugangsschutz.",
|
||
"webPush": "Browser-Push",
|
||
"webPushDescription": "Erhalte Benachrichtigungen direkt im Browser, auch wenn HealthLog nicht geöffnet ist.",
|
||
"webPushNotSupported": "Dein Browser unterstützt keine Push-Benachrichtigungen.",
|
||
"webPushDenied": "Push-Benachrichtigungen wurden blockiert. Erlaube sie in den Browser-Einstellungen.",
|
||
"webPushNotConfigured": "Web Push ist serverseitig nicht konfiguriert.",
|
||
"webPushSubscribe": "Push aktivieren",
|
||
"webPushUnsubscribe": "Push deaktivieren",
|
||
"webPushSubscribed": "Push-Benachrichtigungen aktiviert!",
|
||
"webPushUnsubscribed": "Push-Benachrichtigungen deaktiviert.",
|
||
"webPushSubscribeFailed": "Aktivierung fehlgeschlagen",
|
||
"webPushActive": "Aktiv",
|
||
"kiInsights": "KI-Insights (OpenAI)",
|
||
"kiInsightsDescription": "Optional: Hinterlege deinen OpenAI-Key für tägliche, automatische KI-Auswertungen in den Insights.",
|
||
"kiInsightsBenefitsTitle": "Vorteile mit OpenAI-Key",
|
||
"kiInsightsBenefit1": "Tägliche Zusammenfassungen für allgemeinen Zustand, Blutdruck, Gewicht, Puls, BMI und Medikamenten-Compliance.",
|
||
"kiInsightsBenefit2": "Bessere Einordnung von Trends und Korrelationen auf Basis deiner letzten 30 Tagesmesspunkte pro Bereich.",
|
||
"kiInsightsBenefit3": "Automatische Aktualisierung im Hintergrund, damit die Insights-Seite immer aktuelle Bewertungen zeigt.",
|
||
"createApiKeyHint": "API-Key erstellen unter:",
|
||
"createApiKeyLinkLabel": "platform.openai.com/api-keys",
|
||
"apiKey": "API-Key",
|
||
"apiKeyStored": "Key hinterlegt — neuen eingeben zum Ersetzen",
|
||
"apiKeySaved": "API-Key gespeichert",
|
||
"apiKeyRemoved": "API-Key entfernt",
|
||
"codexConnected": "ChatGPT erfolgreich verbunden! KI-Insights sind jetzt aktiv.",
|
||
"codexDisconnected": "ChatGPT-Verbindung getrennt.",
|
||
"codexConnectionFailed": "ChatGPT-Verbindung fehlgeschlagen. Bitte versuche es erneut.",
|
||
"rawData": "Rohdaten mitsenden",
|
||
"rawDataOnDescription": "Es werden aggregierte Kennzahlen plus anonymisierte Rohdaten der letzten 30 Tage gesendet. Dazu zählen einzelne Messpunkte je Bereich (z. B. Gewicht, Blutdruck, Puls) mit Zeitbezug, damit die KI zeitliche Muster, Ausreißer und Zusammenhänge zuverlässiger erkennen kann. Nicht übertragen werden Name, E-Mail oder direkte Kontodaten.",
|
||
"rawDataOffDescription": "Es werden nur zusammengefasste Werte gesendet (z. B. Durchschnitt, Trend, Minimum/Maximum). Keine einzelnen Messpunkte und keine exakten Zeitbezüge. Das ist datensparsamer, die KI kann dafür aber weniger fein auf Muster und kurzfristige Schwankungen eingehen.",
|
||
"rawDataWarning": "Rohdaten aktiv: Die KI erhält zusätzlich anonymisierte Messpunkte der letzten 30 Tage für eine präzisere Auswertung.",
|
||
"regenerateInsights": "Reports neu generieren",
|
||
"regenerateSuccess": "Reports wurden neu generiert",
|
||
"regenerateRateLimit": "Bitte warte — maximal 2 Analysen pro Stunde.",
|
||
"lastGeneratedAt": "Zuletzt generiert",
|
||
"withings": "Withings",
|
||
"withingsDescription": "Verbinde deine Withings-Waage und Blutdruckmessgeräte.",
|
||
"withingsCredentials": "API-Zugangsdaten",
|
||
"withingsClientId": "Client-ID",
|
||
"withingsClientSecret": "Client-Secret",
|
||
"withingsCredentialsSaved": "Zugangsdaten gespeichert",
|
||
"withingsCredentialsSavedPlaceholder": "Gespeichert — neue eingeben zum Ersetzen",
|
||
"withingsCredentialsSavedPlaceholderSecret": "Gespeichert — neues eingeben zum Ersetzen",
|
||
"withingsSaveCredentials": "Zugangsdaten speichern",
|
||
"withingsConnected": "Verbunden",
|
||
"configured": "Konfiguriert",
|
||
"withingsTokenExpired": "Token abgelaufen",
|
||
"withingsLastSync": "Letzte Sync",
|
||
"withingsSync": "Jetzt synchronisieren",
|
||
"withingsFullSync": "Alle Daten synchronisieren",
|
||
"withingsFullSyncTitle": "Vollsynchronisierung?",
|
||
"withingsFullSyncDescription": "Alle verfügbaren Withings-Daten werden vollständig synchronisiert. Das kann je nach Historie etwas dauern.",
|
||
"withingsSyncResult": "{count} Messwerte synchronisiert",
|
||
"withingsFullSyncResult": "{count} Messwerte vollständig synchronisiert",
|
||
"withingsSyncFailed": "Sync fehlgeschlagen",
|
||
"withingsSynchronize": "Synchronisieren",
|
||
"withingsDisconnect": "Trennen",
|
||
"withingsDisconnectTitle": "Withings-Verbindung trennen?",
|
||
"withingsDisconnectDescription": "Die Verbindung zu Withings wird getrennt. Bereits synchronisierte Daten bleiben erhalten.",
|
||
"withingsConnect": "Mit Withings verbinden",
|
||
"withingsNoCredentials": "Bitte zuerst die API-Zugangsdaten oben eingeben, um Withings zu verbinden.",
|
||
"apiTokens": "API-Tokens",
|
||
"apiTokensDescription": "Erstelle API-Tokens für die externe Medikamenten-Einnahme (z.B. Shortcuts, Automationen).",
|
||
"tokenName": "Token-Name",
|
||
"tokenNamePlaceholder": "Token-Name (z.B. iPhone Shortcut)",
|
||
"tokenCreated": "Token erstellt — kopiere ihn jetzt! Er wird nicht erneut angezeigt.",
|
||
"tokenRevoke": "Token widerrufen?",
|
||
"tokenRevokeDescription": "Der Token wird unwiderruflich deaktiviert. Anwendungen, die ihn nutzen, verlieren den Zugriff.",
|
||
"tokenRevoked": "Widerrufen",
|
||
"tokenExpired": "Abgelaufen",
|
||
"tokenActive": "Aktiv",
|
||
"activeTokensTitle": "Aktive Tokens",
|
||
"revokedTokensTitle": "Widerrufene Tokens ({count})",
|
||
"tokenTableName": "Name",
|
||
"tokenTablePermissions": "Berechtigungen",
|
||
"tokenTableStatus": "Status",
|
||
"tokenTableCreated": "Erstellt",
|
||
"tokenTableLastUsed": "Zuletzt genutzt",
|
||
"tokenTableActions": "Aktionen",
|
||
"noActiveTokens": "Keine aktiven Tokens vorhanden.",
|
||
"tokenNeverUsed": "Nie",
|
||
"tokenLastUsed": "Zuletzt:",
|
||
"tokenCreatedAt": "Erstellt:",
|
||
"apiEndpoint": "API-Endpoint für Medikamenten-Einnahme:",
|
||
"apiEndpointsTitle": "API-Endpoints",
|
||
"apiEndpointsDescription": "Übersicht der derzeit verfügbaren externen Endpoint(s) für Token-basierte Eingaben.",
|
||
"apiEndpointMethod": "Methode",
|
||
"apiEndpointPath": "Pfad",
|
||
"apiEndpointAuth": "Authentifizierung",
|
||
"apiEndpointExample": "Body-Beispiel",
|
||
"export": "Daten-Export",
|
||
"exportDescription": "Exportiere alle deine Gesundheitsdaten.",
|
||
"exportJson": "JSON-Export",
|
||
"exportCsv": "CSV-Export",
|
||
"doctorReport": "Arztreport (PDF)",
|
||
"auditLog": "Aktivitätsprotokoll",
|
||
"auditLogHidden": "Protokoll ausgeblendet.",
|
||
"auditLogShowing": "Zeigt die letzten {count} von {total} Einträgen",
|
||
"auditNoActivities": "Keine Aktivitäten vorhanden.",
|
||
"auditAction": "Aktion",
|
||
"auditCategory": "Kategorie",
|
||
"auditIp": "IP",
|
||
"auditLocation": "Standort",
|
||
"auditTimestamp": "Zeitpunkt",
|
||
"auditOther": "Sonstig",
|
||
"auditCategoryAuth": "Auth",
|
||
"auditCategoryData": "Daten",
|
||
"auditCategoryIntegration": "Integration",
|
||
"auditCategoryAI": "KI",
|
||
"auditCategoryAdmin": "Admin",
|
||
"auditActionAuthRegister": "Registrierung",
|
||
"auditActionAuthLogin": "Anmeldung",
|
||
"auditActionAuthLoginPasskey": "Passkey-Anmeldung",
|
||
"auditActionAuthLoginPassword": "Passwort-Anmeldung",
|
||
"auditActionAuthLoginFailed": "Fehlgeschlagene Anmeldung",
|
||
"auditActionAuthLogout": "Abmeldung",
|
||
"auditActionAuthPasskeyRegister": "Passkey hinzugefügt",
|
||
"auditActionAuthPasskeyDelete": "Passkey gelöscht",
|
||
"auditActionAuthPasswordChange": "Passwort geändert",
|
||
"auditActionMeasurementCreate": "Messwert erstellt",
|
||
"auditActionMeasurementUpdate": "Messwert aktualisiert",
|
||
"auditActionMeasurementDelete": "Messwert gelöscht",
|
||
"auditActionMedicationCreate": "Medikament erstellt",
|
||
"auditActionMedicationUpdate": "Medikament aktualisiert",
|
||
"auditActionMedicationDelete": "Medikament gelöscht",
|
||
"auditActionMedicationIntake": "Einnahme erfasst",
|
||
"auditActionMedicationIngestExternal": "Externe Einnahme",
|
||
"auditActionWithingsConnect": "Withings verbunden",
|
||
"auditActionWithingsDisconnect": "Withings getrennt",
|
||
"auditActionExportDownload": "Export heruntergeladen",
|
||
"auditActionInsightsGenerate": "KI-Insight generiert",
|
||
"auditActionAdminUserUpdate": "Admin: Benutzer aktualisiert",
|
||
"auditActionAdminUserResetPassword": "Admin: Passwort zurückgesetzt",
|
||
"auditActionAdminSettingsUpdate": "Admin: Einstellungen geändert",
|
||
"collapse": "Einklappen",
|
||
"expand": "Ausklappen",
|
||
"dangerZoneTitle": "Danger Zone",
|
||
"dangerZone": "Alle Daten löschen",
|
||
"dangerZoneDescription": "Löscht alle deine Gesundheitsdaten und Integrationen. Dein Benutzerkonto bleibt erhalten.",
|
||
"dangerZoneConfirm": "Alle Daten löschen?",
|
||
"dangerZoneConfirmDescription": "Dieser Vorgang löscht alle deine Gesundheitsdaten und Integrationen unwiderruflich. Dein Benutzerkonto bleibt erhalten.",
|
||
"dangerZoneSuccess": "Alle persönlichen Daten wurden gelöscht",
|
||
"dangerZoneDeleteFailed": "Löschen fehlgeschlagen",
|
||
"finalDelete": "Endgültig löschen",
|
||
"saved": "Gespeichert",
|
||
"savingError": "Fehler beim Speichern",
|
||
"adminAreaTitle": "Admin-Bereich",
|
||
"adminAreaDescription": "Systemweite Einstellungen und Benutzerverwaltung findest du in der Admin-Konsole.",
|
||
"openAdminConsole": "Admin-Konsole öffnen",
|
||
"moodLogTitle": "moodLog",
|
||
"moodLogDescription": "Stimmungsdaten aus moodLog importieren",
|
||
"moodLogUrl": "moodLog URL",
|
||
"moodLogUrlPlaceholder": "https://mood.example.com",
|
||
"moodLogApiKey": "API-Key",
|
||
"moodLogApiKeyPlaceholder": "ml_...",
|
||
"moodLogWebhookSecret": "Webhook-Secret",
|
||
"moodLogWebhookSecretHelp": "Dieses Secret in moodLog als Webhook-Secret eintragen",
|
||
"moodLogSync": "Sync starten",
|
||
"moodLogFullSync": "Voll-Sync",
|
||
"moodLogFullSyncTitle": "Vollsynchronisierung?",
|
||
"moodLogFullSyncDescription": "Alle verfügbaren Stimmungsdaten werden vollständig synchronisiert.",
|
||
"moodLogDisconnect": "Trennen",
|
||
"moodLogDisconnectTitle": "moodLog trennen?",
|
||
"moodLogDisconnectDescription": "Die Verbindung wird getrennt und alle importierten Stimmungsdaten werden gelöscht.",
|
||
"moodLogLastSync": "Letzter Sync",
|
||
"moodLogEntries": "Einträge",
|
||
"moodLogSyncResult": "{count} Einträge synchronisiert",
|
||
"moodLogSyncFailed": "Sync fehlgeschlagen",
|
||
"moodLogSaved": "moodLog-Verbindung gespeichert",
|
||
"moodLogDisconnected": "moodLog getrennt"
|
||
},
|
||
"admin": {
|
||
"title": "Administration",
|
||
"subtitle": "Systemstatus, Benutzer und Einstellungen",
|
||
"settingsSaveError": "Einstellungen konnten nicht gespeichert werden",
|
||
"settingsSaveValidationError": "Bitte markierte Felder prüfen",
|
||
"sectionsTitle": "Admin",
|
||
"systemStatus": "Systemstatus",
|
||
"version": "Version",
|
||
"database": "Datenbank",
|
||
"databaseConnected": "Verbunden",
|
||
"databaseError": "Fehler",
|
||
"gitCommit": "Git Commit",
|
||
"startedAt": "Gestartet",
|
||
"users": "Benutzer",
|
||
"measurementsCount": "Messwerte",
|
||
"activeTokens": "Aktive API-Tokens",
|
||
"activeSessions": "Aktive Sessions",
|
||
"loadingStatus": "Lade Status...",
|
||
"workerStatus": "Job-Worker",
|
||
"workerRunning": "Aktiv",
|
||
"workerStopped": "Gestoppt",
|
||
"lastReminderCheck": "Letzter Reminder-Check",
|
||
"appSettings": "Anwendungseinstellungen",
|
||
"registrationEnabled": "Registrierung erlauben",
|
||
"registrationEnabledDescription": "Wenn deaktiviert, können sich keine neuen Benutzer registrieren.",
|
||
"defaultLanguage": "Standard-Sprache",
|
||
"defaultLanguageDescription": "Wird für neue Benutzer und als Fallback verwendet.",
|
||
"servicesGlobal": "Dienste (global)",
|
||
"servicesGlobalDescription": "Deaktivierte Dienste stehen keinem Benutzer zur Verfügung.",
|
||
"telegramGlobal": "Telegram-Benachrichtigungen systemweit erlauben.",
|
||
"ntfyGlobal": "ntfy-Benachrichtigungen systemweit erlauben.",
|
||
"webPushGlobal": "Browser-Push-Benachrichtigungen systemweit erlauben.",
|
||
"apiGlobal": "API-Endpunkte und API-Tokens systemweit erlauben.",
|
||
"moodLogGlobal": "moodLog-Integration systemweit erlauben.",
|
||
"sectionGeneral": "Allgemein",
|
||
"sectionIntegrations": "Integrationen",
|
||
"sectionMedication": "Medikamente",
|
||
"sectionManagement": "Verwaltung",
|
||
"umamiTitle": "Umami",
|
||
"umamiDescription": "Web-Analyse mit Umami",
|
||
"glitchtipTitle": "GlitchTip",
|
||
"glitchtipDescription": "Fehler-Tracking mit GlitchTip",
|
||
"webPushVapidTitle": "Web Push VAPID",
|
||
"monitoring": "Monitoring (Umami/Glitchtip)",
|
||
"monitoringDescription": "Konfiguration für Website-Analytics und Fehler-Tracking.",
|
||
"umamiEnabled": "Umami-Tracking aktivieren.",
|
||
"umamiScriptUrl": "Umami Script-URL",
|
||
"umamiScriptUrlPlaceholder": "https://analytics.example.com/script.js",
|
||
"umamiWebsiteId": "Umami Website-ID",
|
||
"umamiWebsiteIdPlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||
"glitchtipEnabled": "Glitchtip Fehler-Tracking aktivieren.",
|
||
"glitchtipDsn": "Glitchtip DSN",
|
||
"glitchtipDsnPlaceholder": "https://<public-key>@glitchtip.example.com/<project-id>",
|
||
"glitchtipEnvironment": "Glitchtip Environment",
|
||
"glitchtipEnvironmentPlaceholder": "production",
|
||
"webPushVapid": "Web Push (VAPID)",
|
||
"webPushVapidDescription": "Konfiguriere VAPID-Schlüssel und Subject direkt hier, damit Browser-Push ohne ENV-Variablen funktioniert.",
|
||
"webPushVapidPublicKey": "VAPID Public Key",
|
||
"webPushVapidPublicKeyPlaceholder": "BElx... (Base64URL)",
|
||
"webPushVapidPrivateKey": "VAPID Private Key",
|
||
"webPushVapidPrivateKeyPlaceholder": "Optional: neuen Private Key eintragen",
|
||
"webPushVapidSubject": "VAPID Subject",
|
||
"webPushVapidSubjectPlaceholder": "mailto:deine-adresse@example.com",
|
||
"configured": "Konfiguriert",
|
||
"notConfigured": "Nicht konfiguriert",
|
||
"monitoringTestUmami": "Umami-Test senden",
|
||
"monitoringTestGlitchtip": "Glitchtip-Test senden",
|
||
"monitoringTestSuccess": "Testevent wurde gesendet",
|
||
"monitoringTestFailed": "Monitoring-Test fehlgeschlagen",
|
||
"monitoringSave": "Monitoring speichern",
|
||
"bugReportGithub": "Bugreport (GitHub Issues)",
|
||
"bugReportGithubDescription": "Hinterlege GitHub-Issue-Token und Repository für eingehende Bugreports.",
|
||
"bugReportRepo": "Repository",
|
||
"bugReportRepoPlaceholder": "owner/repo",
|
||
"bugReportToken": "Issue-Token",
|
||
"bugReportTokenPlaceholder": "ghp_...",
|
||
"bugReportConfigured": "Bugreport ist konfiguriert.",
|
||
"bugReportNotConfigured": "Bugreport ist noch nicht vollständig konfiguriert.",
|
||
"bugReportSave": "Bugreport speichern",
|
||
"userManagement": "Benutzerverwaltung",
|
||
"userEmail": "E-Mail",
|
||
"userRole": "Rolle",
|
||
"userPasskeys": "Passkeys",
|
||
"userCreated": "Erstellt",
|
||
"userActions": "Aktionen",
|
||
"editUser": "Benutzer bearbeiten",
|
||
"editUserTitle": "Benutzer bearbeiten: {name}",
|
||
"demoteToUser": "Zu User degradieren",
|
||
"promoteToAdmin": "Zum Admin befördern",
|
||
"toUser": "→ User",
|
||
"toAdmin": "→ Admin",
|
||
"ownRoleUnchangeable": "Eigene Rolle nicht änderbar",
|
||
"lastAdminError": "Der letzte Admin kann nicht degradiert werden",
|
||
"resetPassword": "Passwort zurücksetzen",
|
||
"resetPasswordTitle": "Passwort zurücksetzen: {name}",
|
||
"newPassword": "Neues Passwort",
|
||
"newPasswordPlaceholder": "Mindestens 12 Zeichen",
|
||
"passwordReset": "Passwort wurde zurückgesetzt",
|
||
"reset": "Zurücksetzen",
|
||
"loadingUsers": "Lade Benutzer...",
|
||
"apiTokens": "API-Tokens",
|
||
"tokenUser": "Benutzer",
|
||
"tokenName": "Name",
|
||
"tokenPermissions": "Berechtigungen",
|
||
"tokenStatus": "Status",
|
||
"tokenLastUsed": "Zuletzt benutzt",
|
||
"tokenCreated": "Erstellt",
|
||
"tokenNeverUsed": "Nie",
|
||
"noTokens": "Keine API-Tokens vorhanden.",
|
||
"loginOverview": "Anmeldeübersicht",
|
||
"allAuthEvents": "Alle Auth-Ereignisse",
|
||
"failedOnly": "Nur fehlgeschlagene",
|
||
"noEntries": "Keine Einträge vorhanden.",
|
||
"showingEntries": "Zeigt {count} von {total} Auth-Einträgen",
|
||
"status": "Status",
|
||
"action": "Aktion",
|
||
"ip": "IP",
|
||
"location": "Standort",
|
||
"timestamp": "Zeitpunkt",
|
||
"dangerZone": "Gefahrenzone",
|
||
"deleteAllData": "Alle Daten global löschen",
|
||
"deleteAllDescription": "Löscht alle Mess-, Medikamenten-, Integrations- und Protokoll-Daten aller Benutzer. Benutzerkonten bleiben bestehen.",
|
||
"deleteAllConfirm": "Alle Daten wirklich löschen?",
|
||
"deleteAllConfirmDescription": "Dieser Vorgang löscht alle Messwerte, Medikamente, Einnahmen und Integrationsdaten aller Benutzer unwiderruflich. Benutzerkonten bleiben erhalten.",
|
||
"deleteButton": "Alle Daten löschen",
|
||
"finalDelete": "Endgültig löschen",
|
||
"deletedResult": "Gelöscht: {measurements} Messwerte, {medications} Medikamente, {intakeEvents} Einnahmen",
|
||
"authRegister": "Registrierung",
|
||
"authLogin": "Anmeldung",
|
||
"authLoginPasskey": "Passkey-Anmeldung",
|
||
"authLoginPassword": "Passwort-Anmeldung",
|
||
"authLoginFailed": "Fehlgeschlagen",
|
||
"authLogout": "Abmeldung",
|
||
"authPasskeyRegister": "Passkey hinzugefügt",
|
||
"authPasskeyDelete": "Passkey gelöscht",
|
||
"medicationReminders": "Medikamentenerinnerungen",
|
||
"medicationRemindersDescription": "Schwellenwerte für die Farbübergänge der Erinnerungs-Badges auf den Medikamentenkarten.",
|
||
"reminderLateMinutes": "Spät-Schwellenwert (Minuten)",
|
||
"reminderLateMinutesDescription": "Minuten nach Fenster-Ende bis der Badge von Gelb auf Orange wechselt.",
|
||
"reminderMissedMinutes": "Verpasst-Schwellenwert (Minuten)",
|
||
"reminderMissedMinutesDescription": "Minuten nach Fenster-Ende bis die Einnahme als verpasst gilt.",
|
||
"reminderThresholdsSave": "Schwellenwerte speichern",
|
||
"notificationTest": "Benachrichtigungen testen",
|
||
"notificationTestSend": "Test-Notification senden",
|
||
"notificationTestSuccess": "Test-Notification wurde gesendet",
|
||
"notificationTestFailed": "Test-Notification konnte nicht gesendet werden",
|
||
"reminderCheckRun": "Reminder-Check ausführen",
|
||
"reminderCheckSuccess": "Reminder-Check abgeschlossen",
|
||
"reminderCheckFailed": "Reminder-Check konnte nicht ausgeführt werden",
|
||
"reminderCheckResults": "Ergebnisse",
|
||
"reminderCheckNotifOn": "Benachrichtigungen an",
|
||
"reminderCheckNotifOff": "Benachrichtigungen aus",
|
||
"reminderCheckEventsToday": "Heutige Events",
|
||
"reminderCheckNotifSent": "Gesendet",
|
||
"feedback": {
|
||
"title": "Feedback-Postfach",
|
||
"description": "Eingegangene Rückmeldungen, Fehlerberichte und Feature-Wünsche.",
|
||
"tabOpen": "Offen",
|
||
"tabAcknowledged": "Bestätigt",
|
||
"tabResolved": "Erledigt",
|
||
"tabArchived": "Archiviert",
|
||
"subject": "Betreff",
|
||
"category": "Kategorie",
|
||
"user": "Benutzer",
|
||
"createdAt": "Eingegangen",
|
||
"actions": "Aktionen",
|
||
"details": "Details",
|
||
"noEntries": "Keine Feedback-Einträge in dieser Kategorie.",
|
||
"loading": "Lade Feedback...",
|
||
"anonymous": "anonym",
|
||
"categoryBug": "Bug",
|
||
"categoryFeature": "Feature",
|
||
"categoryQuestion": "Frage",
|
||
"categoryOther": "Sonstiges",
|
||
"statusOpen": "Offen",
|
||
"statusAcknowledged": "Bestätigt",
|
||
"statusResolved": "Erledigt",
|
||
"statusArchived": "Archiviert",
|
||
"dialogTitle": "Feedback-Details",
|
||
"metadataHeading": "Metadaten",
|
||
"metaUrl": "URL",
|
||
"metaLocale": "Sprache",
|
||
"metaUserAgent": "User-Agent",
|
||
"metaAppVersion": "App-Version",
|
||
"screenshotHeading": "Screenshot",
|
||
"adminNote": "Admin-Notiz",
|
||
"adminNotePlaceholder": "Interne Notiz (nicht für Benutzer sichtbar)",
|
||
"saveNote": "Notiz speichern",
|
||
"noteSaved": "Notiz gespeichert",
|
||
"actionAcknowledge": "Bestätigen",
|
||
"actionResolve": "Erledigen",
|
||
"actionArchive": "Archivieren",
|
||
"actionPublishGithub": "Auf GitHub veröffentlichen",
|
||
"publishedToGithub": "Auf GitHub veröffentlicht",
|
||
"viewIssue": "Issue ansehen",
|
||
"publishSuccess": "Issue erstellt",
|
||
"publishFailed": "GitHub-Veröffentlichung fehlgeschlagen",
|
||
"updateFailed": "Aktualisierung fehlgeschlagen",
|
||
"archiveConfirm": "Diesen Feedback-Eintrag archivieren?",
|
||
"submittedBy": "Eingereicht von",
|
||
"sectionTitle": "Feedback"
|
||
}
|
||
},
|
||
"achievements": {
|
||
"title": "Erfolge",
|
||
"subtitle": "Sammle Erfolge ab Feature-Start durch konsequente Gesundheitstrends und Medikamentenroutinen.",
|
||
"loginRequired": "Bitte anmelden, um Erfolge zu sehen.",
|
||
"points": "Punkte",
|
||
"unlocked": "Freigeschaltet",
|
||
"nextGoal": "Nächstes Ziel",
|
||
"allCompleted": "Alle Erfolge freigeschaltet!",
|
||
"completed": "Abgeschlossen",
|
||
"inProgress": "In Arbeit",
|
||
"progress": "Fortschritt",
|
||
"goalReached": "Ziel erreicht",
|
||
"remaining": "Noch {count} bis zum Ziel",
|
||
"remainingUnlocks": "Noch {count} freischaltbar",
|
||
"noneUnlockedYet": "Noch keine Erfolge freigeschaltet.",
|
||
"pointsValue": "{points} Punkte",
|
||
"metricCount": "{count}",
|
||
"metricDays": "{count} Tage",
|
||
"metricPercent": "{count}%",
|
||
"badges": {
|
||
"intakeTotal1": {
|
||
"title": "Erste Einnahme",
|
||
"description": "Dokumentiere dein erstes eingenommenes Medikament."
|
||
},
|
||
"intakeTotal10": {
|
||
"title": "Einnahme-Starter",
|
||
"description": "Dokumentiere 10 eingenommene Medikamente."
|
||
},
|
||
"intakeTotal50": {
|
||
"title": "Einnahme-Routine",
|
||
"description": "Dokumentiere 50 eingenommene Medikamente."
|
||
},
|
||
"intakeTotal150": {
|
||
"title": "Einnahme-Profi",
|
||
"description": "Dokumentiere 150 eingenommene Medikamente."
|
||
},
|
||
"intakeTotal300": {
|
||
"title": "Einnahme-Legende",
|
||
"description": "Dokumentiere 300 eingenommene Medikamente."
|
||
},
|
||
"overIntake1": {
|
||
"title": "Idiot",
|
||
"description": "Nimm mindestens einmal mehr ein als vorgesehen."
|
||
},
|
||
"skippedIntake1": {
|
||
"title": "Lazy Boy",
|
||
"description": "Überspringe mindestens eine geplante Einnahme."
|
||
},
|
||
"passkeyCreated1": {
|
||
"title": "Passkey gesetzt",
|
||
"description": "Erstelle deinen ersten Passkey."
|
||
},
|
||
"passkeyLogin1": {
|
||
"title": "Passkey genutzt",
|
||
"description": "Melde dich mindestens einmal per Passkey an."
|
||
},
|
||
"passwordLogin1": {
|
||
"title": "Oldschool",
|
||
"description": "Melde dich mindestens einmal mit Username und Passwort an."
|
||
},
|
||
"bugReport1": {
|
||
"title": "Fehlerjäger",
|
||
"description": "Reiche einen Bugreport ein."
|
||
},
|
||
"loginStreak7": {
|
||
"title": "Login-Serie 7T",
|
||
"description": "Logge dich an 7 Tagen in Folge ein."
|
||
},
|
||
"loginStreak30": {
|
||
"title": "Login-Serie 30T",
|
||
"description": "Logge dich an 30 Tagen in Folge ein."
|
||
},
|
||
"onTimePerfect1": {
|
||
"title": "Zeitfenster 1T",
|
||
"description": "Nimm 1 Tage in Folge alle Medikamente im Zeitfenster ein."
|
||
},
|
||
"compliance801": {
|
||
"title": "Treue 80% · 1T",
|
||
"description": "Erreiche 1 Tage in Folge eine 30-Tage-Einnahmetreue von mindestens 80%."
|
||
},
|
||
"bmiGreen1": {
|
||
"title": "BMI im Grün · 1T",
|
||
"description": "Halte deinen BMI 1 Tage in Folge im Normalbereich."
|
||
},
|
||
"bpGreen1": {
|
||
"title": "Blutdruck im Grün · 1T",
|
||
"description": "Halte deinen Blutdruck 1 Tage in Folge im Normalbereich."
|
||
},
|
||
"pulseGreen1": {
|
||
"title": "Puls im Grün · 1T",
|
||
"description": "Halte deinen Ruhepuls 1 Tage in Folge im Normalbereich."
|
||
},
|
||
"onTimePerfect7": {
|
||
"title": "Zeitfenster 7T",
|
||
"description": "Nimm 7 Tage in Folge alle Medikamente im Zeitfenster ein."
|
||
},
|
||
"compliance807": {
|
||
"title": "Treue 80% · 7T",
|
||
"description": "Erreiche 7 Tage in Folge eine 30-Tage-Einnahmetreue von mindestens 80%."
|
||
},
|
||
"bmiGreen7": {
|
||
"title": "BMI im Grün · 7T",
|
||
"description": "Halte deinen BMI 7 Tage in Folge im Normalbereich."
|
||
},
|
||
"bpGreen7": {
|
||
"title": "Blutdruck im Grün · 7T",
|
||
"description": "Halte deinen Blutdruck 7 Tage in Folge im Normalbereich."
|
||
},
|
||
"pulseGreen7": {
|
||
"title": "Puls im Grün · 7T",
|
||
"description": "Halte deinen Ruhepuls 7 Tage in Folge im Normalbereich."
|
||
},
|
||
"onTimePerfect30": {
|
||
"title": "Zeitfenster 30T",
|
||
"description": "Nimm 30 Tage in Folge alle Medikamente im Zeitfenster ein."
|
||
},
|
||
"compliance8030": {
|
||
"title": "Treue 80% · 30T",
|
||
"description": "Erreiche 30 Tage in Folge eine 30-Tage-Einnahmetreue von mindestens 80%."
|
||
},
|
||
"bmiGreen30": {
|
||
"title": "BMI im Grün · 30T",
|
||
"description": "Halte deinen BMI 30 Tage in Folge im Normalbereich."
|
||
},
|
||
"bpGreen30": {
|
||
"title": "Blutdruck im Grün · 30T",
|
||
"description": "Halte deinen Blutdruck 30 Tage in Folge im Normalbereich."
|
||
},
|
||
"pulseGreen30": {
|
||
"title": "Puls im Grün · 30T",
|
||
"description": "Halte deinen Ruhepuls 30 Tage in Folge im Normalbereich."
|
||
},
|
||
"onTimePerfect180": {
|
||
"title": "Zeitfenster 180T",
|
||
"description": "Nimm 180 Tage in Folge alle Medikamente im Zeitfenster ein."
|
||
},
|
||
"compliance80180": {
|
||
"title": "Treue 80% · 180T",
|
||
"description": "Erreiche 180 Tage in Folge eine 30-Tage-Einnahmetreue von mindestens 80%."
|
||
},
|
||
"bmiGreen180": {
|
||
"title": "BMI im Grün · 180T",
|
||
"description": "Halte deinen BMI 180 Tage in Folge im Normalbereich."
|
||
},
|
||
"bpGreen180": {
|
||
"title": "Blutdruck im Grün · 180T",
|
||
"description": "Halte deinen Blutdruck 180 Tage in Folge im Normalbereich."
|
||
},
|
||
"pulseGreen180": {
|
||
"title": "Puls im Grün · 180T",
|
||
"description": "Halte deinen Ruhepuls 180 Tage in Folge im Normalbereich."
|
||
},
|
||
"onTimePerfect360": {
|
||
"title": "Zeitfenster 360T",
|
||
"description": "Nimm 360 Tage in Folge alle Medikamente im Zeitfenster ein."
|
||
},
|
||
"compliance80360": {
|
||
"title": "Treue 80% · 360T",
|
||
"description": "Erreiche 360 Tage in Folge eine 30-Tage-Einnahmetreue von mindestens 80%."
|
||
},
|
||
"bmiGreen360": {
|
||
"title": "BMI im Grün · 360T",
|
||
"description": "Halte deinen BMI 360 Tage in Folge im Normalbereich."
|
||
},
|
||
"bpGreen360": {
|
||
"title": "Blutdruck im Grün · 360T",
|
||
"description": "Halte deinen Blutdruck 360 Tage in Folge im Normalbereich."
|
||
},
|
||
"pulseGreen360": {
|
||
"title": "Puls im Grün · 360T",
|
||
"description": "Halte deinen Ruhepuls 360 Tage in Folge im Normalbereich."
|
||
}
|
||
},
|
||
"nextProgressLabel": "Fortschritt bis Freischaltung",
|
||
"completedOn": "Abgeschlossen am {date}"
|
||
},
|
||
"bugreport": {
|
||
"title": "Bugreport",
|
||
"subtitle": "Sende eine Fehlermeldung, einen Feature-Wunsch oder eine Frage. Der Server-Administrator sieht deine Einreichung.",
|
||
"loginRequired": "Bitte anmelden, um einen Bug zu melden.",
|
||
"bugTitle": "Titel",
|
||
"bugTitlePlaceholder": "Kurze Beschreibung des Problems",
|
||
"description": "Beschreibung",
|
||
"descriptionPlaceholder": "Beschreibe den Fehler möglichst genau. Was hast du getan? Was ist passiert? Was hättest du erwartet?",
|
||
"screenshot": "Screenshot (optional)",
|
||
"screenshotPreview": "Screenshot-Vorschau",
|
||
"attachScreenshot": "Screenshot anhängen",
|
||
"imageOnly": "Nur Bilddateien erlaubt",
|
||
"tooLarge": "Screenshot ist zu groß (max. 5 MB)",
|
||
"submit": "Senden",
|
||
"success": "Vielen Dank! Dein Bug Report wurde erfolgreich übermittelt.",
|
||
"viewOnGithub": "Auf GitHub ansehen",
|
||
"errorCreating": "Fehler beim Erstellen",
|
||
"notConfiguredTitle": "Bug-Reports noch nicht aktiviert",
|
||
"notConfiguredUser": "Bug-Reports sind auf diesem Server nicht aktiv. Bitte wende dich an den Administrator.",
|
||
"notConfiguredAdmin": "Für Bug-Reports wird eine GitHub-Integration benötigt (Issue-Token + Repository). Im Admin-Panel unter Einstellungen → Bug-Reports einrichten.",
|
||
"openAdminSettings": "Admin-Einstellungen öffnen",
|
||
"category": "Art",
|
||
"categoryBug": "Fehlermeldung",
|
||
"categoryFeature": "Feature-Wunsch",
|
||
"categoryQuestion": "Frage",
|
||
"categoryOther": "Sonstiges Feedback",
|
||
"bugTitlePlaceholder": "Kurze Zusammenfassung (optional)",
|
||
"githubEscalationNote": "Dein Feedback wird lokal gespeichert. Wenn der Administrator die GitHub-Integration aktiviert, kann es auf Wunsch als öffentliches Issue veröffentlicht werden.",
|
||
"internalOnlyNote": "Dein Feedback wird lokal gespeichert und ist nur für den Administrator dieses Servers sichtbar."
|
||
},
|
||
"notifications": {
|
||
"title": "Benachrichtigungen",
|
||
"subtitle": "Wähle aus, welche Benachrichtigungen du über welchen Kanal erhalten möchtest.",
|
||
"noChannels": "Konfiguriere zuerst einen Benachrichtigungskanal in den Einstellungen.",
|
||
"goToSettings": "Zu den Einstellungen",
|
||
"channelDisabled": "Deaktiviert",
|
||
"eventMedicationReminder": "Medikamenten-Erinnerung",
|
||
"eventMedicationReminderDesc": "Erinnerung, wenn eine Einnahme aussteht.",
|
||
"eventMeasurementAnomaly": "Anomale Messwerte",
|
||
"eventMeasurementAnomalyDesc": "Warnung bei ungewöhnlichen Messungen (z.B. hoher Blutdruck).",
|
||
"eventComplianceLow": "Niedrige Einnahmetreue",
|
||
"eventComplianceLowDesc": "Benachrichtigung, wenn die Einnahmetreue unter einen Schwellwert fällt.",
|
||
"eventWithingsSyncFailed": "Withings-Sync-Fehler",
|
||
"eventWithingsSyncFailedDesc": "Meldung bei Fehlern beim automatischen Withings-Datenabgleich.",
|
||
"eventSystemAlert": "System-Hinweise",
|
||
"eventSystemAlertDesc": "Wichtige Systemnachrichten und administrative Hinweise."
|
||
},
|
||
"format": {
|
||
"dateShort": "{day}.{month}.{year}",
|
||
"timeShort": "{hours}:{minutes}",
|
||
"dateTime": "{day}.{month}.{year}, {hours}:{minutes}"
|
||
},
|
||
"classifications": {
|
||
"bmi": {
|
||
"Underweight": "Untergewicht",
|
||
"Normal": "Normalgewicht",
|
||
"Overweight": "Übergewicht",
|
||
"ObesityGradeI": "Adipositas Grad I",
|
||
"ObesityGradeII": "Adipositas Grad II",
|
||
"ObesityGradeIII": "Adipositas Grad III"
|
||
},
|
||
"bp": {
|
||
"Optimal": "Optimal",
|
||
"Normal": "Normal",
|
||
"HighNormal": "Hoch-normal",
|
||
"HypertensionGrade1": "Hypertonie Grad 1",
|
||
"HypertensionGrade2": "Hypertonie Grad 2",
|
||
"HypertensionGrade3": "Hypertonie Grad 3"
|
||
},
|
||
"pulse": {
|
||
"Bradycardia": "Bradykardie",
|
||
"Normal": "Normal",
|
||
"Elevated": "Erhöht",
|
||
"Tachycardia": "Tachykardie"
|
||
},
|
||
"pulseTarget": {
|
||
"SignificantlyLow": "Deutlich niedrig",
|
||
"SlightlyLow": "Leicht niedrig",
|
||
"OnTarget": "Zielbereich",
|
||
"SlightlyElevated": "Leicht erhöht",
|
||
"SignificantlyElevated": "Deutlich erhöht"
|
||
},
|
||
"sleep": {
|
||
"FarTooShort": "Deutlich zu kurz",
|
||
"TooShort": "Zu kurz",
|
||
"OnTarget": "Zielbereich",
|
||
"SlightlyLong": "Etwas lang",
|
||
"FarTooLong": "Deutlich zu lang"
|
||
},
|
||
"bodyFat": {
|
||
"Essential": "Essenziell",
|
||
"Athletic": "Athletisch",
|
||
"Fitness": "Fitness",
|
||
"Average": "Durchschnitt",
|
||
"Obese": "Adipös"
|
||
},
|
||
"steps": {
|
||
"VeryLow": "Sehr wenig",
|
||
"LowActive": "Wenig aktiv",
|
||
"ModeratelyActive": "Moderat aktiv",
|
||
"Active": "Aktiv",
|
||
"VeryActive": "Sehr aktiv"
|
||
},
|
||
"general": {
|
||
"Good": "Gut",
|
||
"Moderate": "Moderat",
|
||
"Low": "Niedrig",
|
||
"VeryGood": "Sehr gut",
|
||
"VeryStable": "Sehr stabil",
|
||
"Stable": "Stabil",
|
||
"Fluctuating": "Schwankend"
|
||
},
|
||
"alerts": {
|
||
"bmiBelowNormalTitle": "BMI unter Normalbereich",
|
||
"bmiBelowNormalMessage": "Ihr BMI von {bmi} liegt unter dem Normalbereich (18,5–24,9).",
|
||
"bmiOverweightTitle": "BMI im Bereich Übergewicht",
|
||
"bmiOverweightMessage": "Ihr BMI von {bmi} liegt im Bereich Übergewicht. Regelmäßige Bewegung und ausgewogene Ernährung können helfen.",
|
||
"bmiObesityTitle": "BMI im Bereich Adipositas",
|
||
"bmiObesityMessage": "Ihr BMI von {bmi} liegt im Bereich Adipositas.",
|
||
"bmiNormalTitle": "BMI im Normalbereich",
|
||
"bmiNormalMessage": "Ihr BMI von {bmi} liegt im gesunden Bereich.",
|
||
"bpDangerTitle": "Blutdruck deutlich erhöht",
|
||
"bpDangerMessage": "Ihr durchschnittlicher Blutdruck ({sys}/{dia}) wird als \"{category}\" eingestuft.",
|
||
"bpWarningTitle": "Blutdruck leicht erhöht",
|
||
"bpWarningMessage": "Ihr durchschnittlicher Blutdruck ({sys}/{dia}) liegt im Bereich \"{category}\".",
|
||
"bpTargetGoodTitle": "Gute Blutdruck-Zielbereichstreue",
|
||
"bpTargetGoodMessage": "{pct}% Ihrer Messungen liegen im Zielbereich.",
|
||
"bpTargetBadTitle": "Blutdruck oft außerhalb des Zielbereichs",
|
||
"bpTargetBadMessage": "Nur {pct}% Ihrer Messungen liegen im Zielbereich.",
|
||
"weightIncreasingTitle": "Gewicht steigend",
|
||
"weightIncreasingMessage": "Ihr Gewicht steigt um ca. {change} kg pro Woche in den letzten 30 Tagen.",
|
||
"weightDecreasingTitle": "Gewicht fallend",
|
||
"weightDecreasingMessage": "Ihr Gewicht sinkt um ca. {change} kg pro Woche in den letzten 30 Tagen.",
|
||
"pulseOutliersTitle": "Puls-Ausreißer erkannt",
|
||
"pulseOutliersMessage": "{count} ungewöhnliche Pulsmessungen in den letzten 30 Tagen.",
|
||
"complianceLowTitle": "Niedrige Einnahmetreue: {name}",
|
||
"complianceLowMessage": "Ihre 7-Tage-Compliance für {name} liegt bei nur {pct}%. Regelmäßige Einnahme ist wichtig.",
|
||
"complianceGoodTitle": "Sehr gute Einnahmetreue: {name}",
|
||
"complianceGoodMessage": "{pct}% Compliance in den letzten 7 Tagen. Weiter so!"
|
||
}
|
||
},
|
||
"targets": {
|
||
"title": "Zielwerte",
|
||
"introText": "Deine aktuellen Werte im Vergleich zu empfohlenen Zielbereichen. Quellen der Leitlinien sind auf jeder Karte verlinkt.",
|
||
"weight": "Gewicht",
|
||
"bloodPressure": "Blutdruck",
|
||
"bloodPressureOnTarget": "Blutdruck im Zielbereich",
|
||
"restingPulse": "Ruhepuls",
|
||
"sleepDuration": "Schlafdauer",
|
||
"bmi": "BMI",
|
||
"bodyFat": "Körperfett",
|
||
"stepsPerDay": "Schritte/Tag",
|
||
"medicationCompliance": "Einnahmetreue",
|
||
"mood": "Stimmung",
|
||
"moodStability": "Stimmungsstabilität",
|
||
"steps": "Schritte",
|
||
"average30d": "30-Tage-Schnitt:",
|
||
"currentValue": "Aktuell: {value} {unit}",
|
||
"targetRangeValue": "Zielbereich: {min}–{max} {unit}",
|
||
"sourceLabel": "Quelle: {source}",
|
||
"inTarget": "Im Zielbereich",
|
||
"aboveTarget": "{delta} {unit} über Zielbereich",
|
||
"belowTarget": "{delta} {unit} unter Zielbereich",
|
||
"diastolic": "Diastolisch",
|
||
"noMeasurementYet": "Noch keine Messungen",
|
||
"noMeasurementData": "Keine Messdaten verfügbar. Füge Messwerte hinzu, um deine Ziele zu sehen.",
|
||
"profileIncomplete": "Profil unvollständig",
|
||
"profileIncompleteHeight": "Trage deine Körpergröße im Profil ein, um BMI-Ziele zu aktivieren.",
|
||
"profileIncompleteAge": "Trage dein Geburtsdatum im Profil ein, um altersspezifische Ziele zu aktivieren.",
|
||
"profileIncompleteHeightAge": "Trage Körpergröße und Geburtsdatum im Profil ein, um personalisierte Ziele zu sehen.",
|
||
"compliancePerMedication": "Einnahmetreue pro Medikament",
|
||
"loadError": "Zielwerte konnten nicht geladen werden"
|
||
},
|
||
"passwordStrength": {
|
||
"veryWeak": "Sehr schwach",
|
||
"weak": "Schwach",
|
||
"acceptable": "Akzeptabel",
|
||
"strong": "Stark",
|
||
"veryStrong": "Sehr stark",
|
||
"tooShort": "Zu kurz",
|
||
"characters": "Zeichen",
|
||
"minLength": "Mindestens {count} Zeichen erforderlich."
|
||
},
|
||
"telegram": {
|
||
"errorInvalidAction": "Ungültige Aktion.",
|
||
"errorMedicationNotFound": "Medikament nicht gefunden.",
|
||
"errorMedicationInactive": "Medikament nicht gefunden oder inaktiv.",
|
||
"errorUnknownAction": "Unbekannte Aktion.",
|
||
"snoozeOneHour": "1 Stunde",
|
||
"snoozeThreeHours": "3 Stunden",
|
||
"snoozedFor": "{name} für {duration} zurückgestellt.",
|
||
"skipped": "{name} übersprungen.",
|
||
"alreadyRecorded": "{name} war bereits erfasst.",
|
||
"recordedAsTaken": "{name} als eingenommen erfasst.",
|
||
"confirmed": "{name} bestätigt.",
|
||
"genericConfirmed": "Bestätigt.",
|
||
"cancelled": "Abgebrochen.",
|
||
"noActiveMedications": "Keine aktiven Medikamente gefunden.",
|
||
"whichMedication": "<b>Welches Medikament eingenommen?</b>",
|
||
"cancelButton": "Abbrechen",
|
||
"askMedicationName": "Bitte Medikament angeben, z.B. <b>genommen Ramipril</b>.",
|
||
"medicationNotFoundExact": "Medikament nicht gefunden. Bitte Namen exakt wie in HealthLog senden.",
|
||
"helpHeader": "<b>Verfügbare Befehle:</b>",
|
||
"helpBody": "/help — Diese Hilfe anzeigen\n/add — Einnahme erfassen\n/start — Bot starten\n\n<b>Textbefehle:</b>\ngenommen <Name> — Einnahme bestätigen\n\n<b>Über die Buttons in Erinnerungen:</b>\n• Genommen — Einnahme bestätigen\n• 🕐 1h / 🕐 3h — Erinnerung verschieben\n• ⏭ Überspringen — Einnahme überspringen\n• ✓ Bestätigen — Verpasste Einnahme bestätigen"
|
||
}
|
||
}
|