feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)

Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.

The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 22:35:22 +01:00
commit f406ddcc59
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -241,6 +241,76 @@
"loopFormSave": "Band einrichten",
"homeWorkouts": "Workouts",
"homeDays": "Tage",
"homePlans": "Pläne",
"plansNew": "Neuer Plan",
"plansNewEmpty": "Leer",
"plansEmptyTitle": "Noch keine Pläne",
"plansEmptyHint": "Die Schritte eines Plans führen dich beim Aufzeichnen durch Intervalle, mit Ton, Vibration und Stimme bei jedem Wechsel. Beginne mit einer Vorlage oder leer.",
"planLocked": "Ein Plan, der sich hier nicht öffnet",
"planTemplateFourByFour": "4×4 Minuten",
"planTemplateTabata": "Tabata",
"planTemplateFourHundreds": "10×400 m",
"planTitle": "Plan",
"planNewTitle": "Neuer Plan",
"planName": "Name",
"planActivity": "Für",
"planActivityAny": "Jede Aktivität",
"planOffTarget": "Sagen, wenn ich vom Ziel abweiche",
"planOffTargetHint": "Ein kurzer Hinweis, höchstens alle 30 Sekunden.",
"planSave": "Speichern",
"planNotSaved": "Der Plan wurde nicht gespeichert.",
"planAddStep": "Schritt hinzufügen",
"planAddRepeat": "Wiederholung hinzufügen",
"planRepeat": "{count}×",
"planFewer": "Weniger Runden",
"planMore": "Mehr Runden",
"planBlockMenu": "Mehr",
"planMoveUp": "Nach oben",
"planMoveDown": "Nach unten",
"planRemove": "Entfernen",
"planDelete": "Löschen",
"planDeleteTitle": "Diesen Plan löschen?",
"planDeleteMessage": "Er wird auf all deinen Geräten gelöscht; Trainings, die mit ihm aufgezeichnet wurden, behalten ihre Schritte.",
"planDeleted": "Plan gelöscht",
"planSteps": "{count, plural, =1{1 Schritt} other{{count} Schritte}}",
"stepTitle": "Schritt",
"stepKind": "Art",
"stepWarmUp": "Aufwärmen",
"stepWork": "Belastung",
"stepRecovery": "Erholung",
"stepCoolDown": "Auslaufen",
"stepEnds": "Endet",
"stepEndTime": "Nach einer Zeit",
"stepEndDistance": "Nach einer Strecke",
"stepEndLap": "Mit der Rundentaste",
"stepTimeLabel": "Zeit (min:s)",
"stepMetresLabel": "Meter",
"stepTarget": "Ziel",
"stepZone": "Zone",
"stepDone": "Fertig",
"stepMinutes": "{minutes} min",
"stepSeconds": "{seconds} s",
"stepMinutesSeconds": "{minutes} min {seconds} s",
"stepMetres": "{metres} m",
"stepKilometres": "{km} km",
"stepUntilLap": "bis zur Rundentaste",
"targetNone": "Keins",
"targetFrom": "Von",
"targetTo": "Bis",
"targetKindPace": "Tempo (pro km)",
"targetKindSpeed": "Geschwindigkeit (km/h)",
"targetKindHeartRate": "Herzfrequenz (Schläge/min)",
"targetKindHeartZone": "Herzfrequenzzone",
"targetKindSplit": "Split (pro 500 m)",
"targetKindWatts": "Leistung (W)",
"targetKindStrokeRate": "Schlagzahl (pro min)",
"targetPace": "{from}–{to} pro km",
"targetSpeed": "{from}–{to} km/h",
"targetHeartRate": "{from}–{to} Schläge/min",
"targetZone": "Zone {zone}",
"targetSplit": "{from}–{to} pro 500 m",
"targetWatts": "{from}–{to} W",
"targetStrokeRate": "{from}–{to} Schläge/min",
"daysEmptyTitle": "Noch keine Tage",
"daysEmptyHint": "Die Tage und Nächte, die dein Polar Loop zählt, erscheinen hier.",
"daysSteps": "{steps, plural, =1{1 Schritt} other{{steps} Schritte}}",