feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
{
"@@locale": "en",
"appTitle": "Halkyon Train",
"@appTitle": {"description": "The app's name: the window's title, and where halkyon_app's texts name the app."},
"launchProduct": "Train",
"@launchProduct": {"description": "The app's word in its wordmark, under \"Halkyon\"."},
"instanceFirstStartSubtitle": "Choose the instance to keep your workouts on. Someone runs each one; you can add others later.",
feat(train): the workouts listed, and each one's page with its map (ADR 0030, D22-D24)
Home lists the person's workouts newest first under their months, each
with its activity's icon, its name (else the time of day's and the
activity's), when it started, its distance, moving time and pace or
speed; those this device can't open come last, locked. A workout's page
shows its track on the instance's map source (flutter_map with
vector_map_tiles, pinned at 9.0.0-beta.13), in pieces between its
pauses, start and end marked; its totals with the elevation gained;
its splits per kilometre on foot, per 5 km cycling; pace or speed and
elevation over the distance, drawn by the app's own painter; and what
the map's source sees. Its name and notes are edited there; deleting
asks first and can be undone for a moment from home (halkyon_app's
DelayedDeletions, TrainPerson.deletions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 14:29:39 +01:00
"homeEmptyTitle": "No workouts yet",
"homeEmptyHint": "Workouts recorded on your phone show here.",
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
"accountTitle": "Account",
"accountDeleteMessage": "Your account is deleted on the server and on all your devices. This can't be undone.",
feat(train): recording a workout on Android (ADR 0030, D13, D16, D19, D21)
RecorderService, a foreground service of the types location and health
started from the open app, takes fixes from LocationManager's GPS and
steps from the step detector, pauses by itself (AutoPause, tested on the
JVM) and appends each sample to the person's recording file, flushed
every five seconds. MainActivity asks for location, notifications and,
for auto-pause on foot, physical activity, never background location
(D19), and bridges the service to the app.
The app follows the file's lines with RecordingBloc and shows the moving
time, the distance, the pace or speed and GPS's state; the notification
shows the time and the distance with Pause or Resume. Stop is held down;
a stopped recording is saved as a workout and its series, or discarded
after asking, and one cut off or left unsaved is offered as the app
opens. Home has the Record button where the device records.
Three choices of the implementation's are recorded under D21 for review:
rowing waits for 5f, auto-pause on foot without steps goes by GPS speed
(below 2 km/h, on above 3 km/h), and Stop's hint shows above the button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 13:24:56 +01:00
"aboutTitle": "About",
feat(train): the workouts listed, and each one's page with its map (ADR 0030, D22-D24)
Home lists the person's workouts newest first under their months, each
with its activity's icon, its name (else the time of day's and the
activity's), when it started, its distance, moving time and pace or
speed; those this device can't open come last, locked. A workout's page
shows its track on the instance's map source (flutter_map with
vector_map_tiles, pinned at 9.0.0-beta.13), in pieces between its
pauses, start and end marked; its totals with the elevation gained;
its splits per kilometre on foot, per 5 km cycling; pace or speed and
elevation over the distance, drawn by the app's own painter; and what
the map's source sees. Its name and notes are edited there; deleting
asks first and can be undone for a moment from home (halkyon_app's
DelayedDeletions, TrainPerson.deletions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 14:29:39 +01:00
"homeRecordHint": "Record one with the button below.",
feat(train): recording a workout on Android (ADR 0030, D13, D16, D19, D21)
RecorderService, a foreground service of the types location and health
started from the open app, takes fixes from LocationManager's GPS and
steps from the step detector, pauses by itself (AutoPause, tested on the
JVM) and appends each sample to the person's recording file, flushed
every five seconds. MainActivity asks for location, notifications and,
for auto-pause on foot, physical activity, never background location
(D19), and bridges the service to the app.
The app follows the file's lines with RecordingBloc and shows the moving
time, the distance, the pace or speed and GPS's state; the notification
shows the time and the distance with Pause or Resume. Stop is held down;
a stopped recording is saved as a workout and its series, or discarded
after asking, and one cut off or left unsaved is offered as the app
opens. Home has the Record button where the device records.
Three choices of the implementation's are recorded under D21 for review:
rowing waits for 5f, auto-pause on foot without steps goes by GPS speed
(below 2 km/h, on above 3 km/h), and Stop's hint shows above the button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 13:24:56 +01:00
"recordAction": "Record",
"recordBack": "Back to the recording",
"recordSheetTitle": "Record a workout",
"activityRunning": "Running",
"activityWalking": "Walking",
"activityHiking": "Hiking",
"activityCycling": "Cycling",
"activityIndoorRowing": "Rowing indoors",
"recordAutoPause": "Pause by itself",
"@recordAutoPause": {"description": "The switch for auto-pause, as the recording starts."},
"recordAutoPauseHint": "When you stand still, and going on as you move again",
"recordStart": "Start",
"recordLocationRefused": "Train needs your location to record this. You can allow it in the system's settings.",
"recordingWaitingForGps": "Waiting for GPS",
"recordingWaitingForGpsAccuracy": "Waiting for GPS · ±{metres} m",
"@recordingWaitingForGpsAccuracy": {"description": "Before a fix precise enough: how far off the last one may be.", "placeholders": {"metres": {"type": "int"}}},
"recordingGpsAccuracy": "GPS ±{metres} m",
"@recordingGpsAccuracy": {"description": "How far off the last fix may be.", "placeholders": {"metres": {"type": "int"}}},
"recordingMovingTime": "moving time",
"@recordingMovingTime": {"description": "Under the time, lower case: the time moved, pauses left out."},
"recordingDistance": "distance",
"recordingPace": "pace",
"recordingSpeed": "speed",
"recordingDistanceValue": "{km} km",
"@recordingDistanceValue": {"placeholders": {"km": {"type": "String", "example": "5.12"}}},
"recordingPaceValue": "{pace} /km",
"@recordingPaceValue": {"description": "Minutes and seconds a kilometre takes.", "placeholders": {"pace": {"type": "String", "example": "6:18"}}},
"recordingSpeedValue": "{kmh} km/h",
"@recordingSpeedValue": {"placeholders": {"kmh": {"type": "String", "example": "24.3"}}},
"recordingPausedAuto": "Paused: not moving",
"@recordingPausedAuto": {"description": "Train paused the recording by itself (auto-pause)."},
"recordingPausedByHand": "Paused",
"recordingPause": "Pause",
"recordingResume": "Resume",
"recordingStop": "Stop",
"recordingHoldToStop": "Hold Stop to end the workout",
"recordingStoppedTitle": "Save this workout?",
"recordingSave": "Save",
"recordingDiscard": "Discard",
"recordingDiscardTitle": "Discard this workout?",
"recordingDiscardMessage": "Its time and track are deleted from this device. This can't be undone.",
"recordingSaveFailed": "It can't be saved on this device, which doesn't hold your account's keys. It stays here until it can be.",
"recordingCutOffTitle": "A recording was cut off",
"recordingCutOffMessage": "A recording from {start} was cut off. Save it up to {end}, or discard it?",
"@recordingCutOffMessage": {"description": "A recording found unfinished as the app opened: when it started and its last sample.", "placeholders": {"start": {"type": "String", "example": "07:30"}, "end": {"type": "String", "example": "08:02"}}},
"recordingNotificationChannel": "Recording",
"@recordingNotificationChannel": {"description": "The name of the recording's notification channel, in the system's settings."},
"recordingNotificationPaused": "{activity} · paused",
feat(train): the workouts listed, and each one's page with its map (ADR 0030, D22-D24)
Home lists the person's workouts newest first under their months, each
with its activity's icon, its name (else the time of day's and the
activity's), when it started, its distance, moving time and pace or
speed; those this device can't open come last, locked. A workout's page
shows its track on the instance's map source (flutter_map with
vector_map_tiles, pinned at 9.0.0-beta.13), in pieces between its
pauses, start and end marked; its totals with the elevation gained;
its splits per kilometre on foot, per 5 km cycling; pace or speed and
elevation over the distance, drawn by the app's own painter; and what
the map's source sees. Its name and notes are edited there; deleting
asks first and can be undone for a moment from home (halkyon_app's
DelayedDeletions, TrainPerson.deletions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 14:29:39 +01:00
"@recordingNotificationPaused": {"description": "The notification's title while paused.", "placeholders": {"activity": {"type": "String", "example": "Running"}}},
"workoutName": "{when, select, morning_running{Morning run} morning_walking{Morning walk} morning_hiking{Morning hike} morning_cycling{Morning ride} morning_indoorRowing{Morning row} afternoon_running{Afternoon run} afternoon_walking{Afternoon walk} afternoon_hiking{Afternoon hike} afternoon_cycling{Afternoon ride} afternoon_indoorRowing{Afternoon row} evening_running{Evening run} evening_walking{Evening walk} evening_hiking{Evening hike} evening_cycling{Evening ride} evening_indoorRowing{Evening row} night_running{Night run} night_walking{Night walk} night_hiking{Night hike} night_cycling{Night ride} night_indoorRowing{Night row} other{Workout}}",
"@workoutName": {"description": "A workout's name where the person gave none: the time of day it started (morning 5– 12, afternoon 12– 17, evening 17– 22, night) and its activity, as Strava names them.", "placeholders": {"when": {"type": "String"}}},
"workoutLocked": "Locked workout",
"workoutLockedHint": "This device doesn't hold the key it's sealed with.",
"@workoutLockedHint": {"description": "Under a workout this device can't open (its key is missing here)."},
"workoutEdit": "Edit",
"workoutEditTitle": "Edit workout",
"workoutNameLabel": "Name",
"workoutNotesLabel": "Notes",
"workoutSave": "Save",
"workoutDelete": "Delete",
"workoutDeleteTitle": "Delete this workout?",
"workoutDeleteMessage": "It's deleted on all your devices.",
"workoutDeleted": "Workout deleted",
"workoutMovingTime": "moving time",
"workoutTotalTime": "whole time",
"@workoutTotalTime": {"description": "Lower case, under a figure: from start to end, pauses included."},
"workoutDistance": "distance",
"workoutPace": "pace",
"workoutSpeed": "speed",
"workoutClimb": "climbed",
"@workoutClimb": {"description": "Lower case, under a figure: the elevation gained."},
"workoutClimbValue": "{metres} m",
"@workoutClimbValue": {"placeholders": {"metres": {"type": "int"}}},
"workoutSplits": "Splits",
"workoutSplitKm": "km {number}",
"@workoutSplitKm": {"description": "A kilometre's split, its number.", "placeholders": {"number": {"type": "int"}}},
"workoutSplitFiveKm": "{from}– {to} km",
"@workoutSplitFiveKm": {"description": "A cycling split of 5 km: where it starts and ends.", "placeholders": {"from": {"type": "int"}, "to": {"type": "int"}}},
"workoutPaceChart": "Pace",
"workoutSpeedChart": "Speed",
"workoutElevationChart": "Elevation",
"workoutMapSource": "Map by {name}: it sees your device's address and the area shown, not your track.",
"@workoutMapSource": {"description": "Under a workout's map: what the map's source sees.", "placeholders": {"name": {"type": "String", "example": "OpenFreeMap"}}},
"workoutMapSourceAbout": "What {name} keeps",
"@workoutMapSourceAbout": {"description": "A link to the map source's page about what it keeps.", "placeholders": {"name": {"type": "String", "example": "OpenFreeMap"}}},
"workoutMapUnavailable": "The map can't be loaded now.",
feat(train): the heart-rate strap, recorded by the service itself (ADR 0030, D25-D27)
The Record sheet gains a Heart rate row: a sheet looks for straps
advertising the Heart Rate Service, the nearest first, once nearby
devices are allowed, and the one chosen is remembered in the person's
folder; it records with every workout unless switched off for one.
The recorder service connects to it with Android's own Bluetooth API,
no package (D26), parses Heart Rate Measurement in Kotlin (tested on
the JVM), writes each rate and the strap's state to the recording
file, and asks for a lost strap again for as long as it records. The
recording screen shows the heart rate, and "Strap lost" while it is
gone; a workout's page shows the heart's average and highest rate,
each split's average and its chart.
iOS's recording waits for a Mac (D25), after the rower.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 15:50:36 +01:00
"workoutNotFound": "This workout is gone.",
"recordHeartRate": "Heart rate",
"recordNoStrap": "No strap",
"@recordNoStrap": {"description": "On the Record sheet, under Heart rate, where no strap is chosen."},
"strapSheetTitle": "Heart-rate strap",
"strapSearching": "Looking for straps nearby…",
"strapSearchingHint": "Put the strap on: most wake up only once they touch skin.",
"strapNear": "close by",
"@strapNear": {"description": "Under a strap found: its signal is strong."},
"strapFar": "further off",
"@strapFar": {"description": "Under a strap found: its signal is weak."},
"strapUnnamed": "Unnamed strap",
"@strapUnnamed": {"description": "A strap that sends no name."},
"strapForget": "Forget strap",
"strapRefused": "Train needs nearby devices to find your strap. You can allow it in the system's settings.",
"strapBluetoothOff": "Bluetooth is off. Turn it on to find your strap.",
"recordingHeartRate": "heart rate",
"@recordingHeartRate": {"description": "Under the heart rate, lower case."},
"recordingStrapLost": "Strap lost",
"@recordingStrapLost": {"description": "While recording: the strap is out of reach or off; Train keeps asking for it."},
"workoutAverageHeartRate": "average heart rate",
"@workoutAverageHeartRate": {"description": "Lower case, under a figure."},
"workoutMaxHeartRate": "highest heart rate",
"@workoutMaxHeartRate": {"description": "Lower case, under a figure."},
"workoutBpmValue": "{bpm} bpm",
"@workoutBpmValue": {"placeholders": {"bpm": {"type": "int"}}},
"workoutHeartRateChart": "Heart rate"
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
}