i18n(fitness): migrate inline ternaries across pages and components
Replace lang === 'en' string ternaries on the check-in, stats, workout,
exercises, history, and stats history detail pages, plus TemplateCard,
with t.<key> lookups against the fitness dictionary. Added new keys for
toast messages, body-part counts, body-fat label, clear/measure short
labels, "edit all fields", BF chart delta prefix, calorie balance and
adherence tooltips, actual/target legend labels, daily expenditure
prefix, height/birth/weight setup hint, exercise/workout/recent labels,
"starts with", and a {n}-template "X days ago" string.
URL slug ternaries (e.g. 'check-in' / 'erfassung') remain inline since
they encode route data, not UI text.
Bump site version to 1.56.2.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.56.1",
|
||||
"version": "1.56.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
if (diffDays === 0) return t.today;
|
||||
if (diffDays === 1) return t.yesterday;
|
||||
if (diffDays < 7) return lang === 'en' ? `${diffDays} days ago` : `vor ${diffDays} Tagen`;
|
||||
return d.toLocaleDateString(lang === 'en' ? 'en' : 'de', { month: 'short', day: 'numeric' });
|
||||
if (diffDays < 7) return t.days_ago_template.replace('{n}', String(diffDays));
|
||||
return d.toLocaleDateString(lang, { month: 'short', day: 'numeric' });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -363,4 +363,30 @@ export const de = {
|
||||
sugars: "Zucker",
|
||||
source_db: "Quelle",
|
||||
initializing_gps: "GPS wird initialisiert…",
|
||||
|
||||
// Check-in page
|
||||
failed_to_load_more: "Laden fehlgeschlagen",
|
||||
body_part_count_one: "1 Körperteil",
|
||||
body_parts_count_other: "Körperteile",
|
||||
updated_toast: "Aktualisiert",
|
||||
body_fat_label: "Körperfett",
|
||||
clear_action: "Leeren",
|
||||
measure_short: "Messen",
|
||||
edit_all_fields: "Alle Felder bearbeiten",
|
||||
measurement_saved: "Messung gespeichert",
|
||||
|
||||
// Stats page
|
||||
bf_delta_from_prefix: "Δ von",
|
||||
set_height_birthyear_weight: "Größe, Geburtsjahr & Gewicht eintragen",
|
||||
actual_label: "Ist",
|
||||
target_label: "Ziel",
|
||||
calorie_balance_tooltip: "Durchschnittlich gegessene Kalorien minus geschätzter Verbrauch (TDEE + erfasste Trainingskilokalorien) der letzten 7 Tage. Negativ = Defizit, positiv = Überschuss.",
|
||||
daily_expenditure_estimate_prefix: "Geschätzter Tagesverbrauch:",
|
||||
diet_adherence_tooltip: "Prozent der Tage, an denen die gegessenen Kalorien innerhalb von ±10 % deines Ziels lagen (bereinigt um verbrannte Trainingskalorien). Nicht erfasste Tage zählen als verfehlt.",
|
||||
|
||||
// Misc page titles / labels
|
||||
exercise_title: "Übung",
|
||||
recent_label: "Aktuell",
|
||||
starts_with: "beginnt mit",
|
||||
days_ago_template: "vor {n} Tagen"
|
||||
} as const;
|
||||
|
||||
@@ -363,4 +363,30 @@ export const en = {
|
||||
sugars: "Sugars",
|
||||
source_db: "Source",
|
||||
initializing_gps: "Initializing GPS…",
|
||||
|
||||
// Check-in page
|
||||
failed_to_load_more: "Failed to load more",
|
||||
body_part_count_one: "1 body part",
|
||||
body_parts_count_other: "body parts",
|
||||
updated_toast: "Updated",
|
||||
body_fat_label: "Body Fat",
|
||||
clear_action: "Clear",
|
||||
measure_short: "Measure",
|
||||
edit_all_fields: "Edit all fields",
|
||||
measurement_saved: "Measurement saved",
|
||||
|
||||
// Stats page
|
||||
bf_delta_from_prefix: "Δ from",
|
||||
set_height_birthyear_weight: "Set height, birth year & weight",
|
||||
actual_label: "Actual",
|
||||
target_label: "Target",
|
||||
calorie_balance_tooltip: "Average daily calories eaten minus estimated expenditure (TDEE + tracked workout calories) over the last 7 days. Negative = deficit, positive = surplus.",
|
||||
daily_expenditure_estimate_prefix: "Est. daily expenditure:",
|
||||
diet_adherence_tooltip: "Percentage of days where calories eaten were within ±10% of your goal (adjusted for exercise calories burned). Days without tracking count as misses.",
|
||||
|
||||
// Misc page titles / labels
|
||||
exercise_title: "Exercise",
|
||||
recent_label: "Recent",
|
||||
starts_with: "starts with",
|
||||
days_ago_template: "{n} days ago"
|
||||
} as const satisfies Record<keyof typeof de, string>;
|
||||
|
||||
@@ -136,10 +136,10 @@
|
||||
measurements = [...measurements, ...fresh];
|
||||
if (typeof body?.total === 'number') measurementsTotal = body.total;
|
||||
} else {
|
||||
toast.error(lang === 'en' ? 'Failed to load more' : 'Laden fehlgeschlagen');
|
||||
toast.error(t.failed_to_load_more);
|
||||
}
|
||||
} catch {
|
||||
toast.error(lang === 'en' ? 'Failed to load more' : 'Laden fehlgeschlagen');
|
||||
toast.error(t.failed_to_load_more);
|
||||
}
|
||||
loadingMore = false;
|
||||
}
|
||||
@@ -179,9 +179,7 @@
|
||||
? Object.values(m.measurements).filter((v) => v != null).length
|
||||
: 0;
|
||||
if (bpCount > 0) {
|
||||
const en = bpCount === 1 ? '1 body part' : `${bpCount} body parts`;
|
||||
const de = bpCount === 1 ? '1 Körperteil' : `${bpCount} Körperteile`;
|
||||
parts.push(lang === 'en' ? en : de);
|
||||
parts.push(bpCount === 1 ? t.body_part_count_one : `${bpCount} ${t.body_parts_count_other}`);
|
||||
}
|
||||
return parts.join(' · ') || t.no_measurements_yet;
|
||||
}
|
||||
@@ -293,7 +291,7 @@
|
||||
const latestRes = await fetch('/api/fitness/measurements/latest');
|
||||
if (latestRes.ok) latest = await latestRes.json();
|
||||
} catch {}
|
||||
toast.success(lang === 'en' ? 'Updated' : 'Aktualisiert');
|
||||
toast.success(t.updated_toast);
|
||||
editingId = null;
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
@@ -356,10 +354,10 @@
|
||||
return conflicts.map((c) => {
|
||||
let label = c.key;
|
||||
let unit = '';
|
||||
if (c.key === 'weight') { label = lang === 'en' ? 'Weight' : 'Gewicht'; unit = ' kg'; }
|
||||
else if (c.key === 'bodyFatPercent') { label = lang === 'en' ? 'Body Fat' : 'Körperfett'; unit = ' %'; }
|
||||
else if (c.key === 'caloricIntake') { label = lang === 'en' ? 'Calories' : 'Kalorien'; unit = ' kcal'; }
|
||||
else if (c.key === 'notes') { label = lang === 'en' ? 'Notes' : 'Notizen'; }
|
||||
if (c.key === 'weight') { label = t.weight; unit = ' kg'; }
|
||||
else if (c.key === 'bodyFatPercent') { label = t.body_fat_label; unit = ' %'; }
|
||||
else if (c.key === 'caloricIntake') { label = t.calories; unit = ' kcal'; }
|
||||
else if (c.key === 'notes') { label = t.notes; }
|
||||
else if (c.key.startsWith('measurements.')) {
|
||||
const part = c.key.slice('measurements.'.length);
|
||||
label = t[/** @type {FitnessKey} */ (partKeyMap[part] ?? part)];
|
||||
@@ -408,7 +406,7 @@
|
||||
measurementsTotal = measurementsTotal + 1;
|
||||
}
|
||||
resetForm();
|
||||
toast.success(lang === 'en' ? 'Measurement saved' : 'Messung gespeichert');
|
||||
toast.success(t.measurement_saved);
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to save measurement');
|
||||
@@ -418,7 +416,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{lang === 'en' ? 'Measure' : 'Messen'} - Bocken</title></svelte:head>
|
||||
<svelte:head><title>{t.measure_short} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="measure-page">
|
||||
<h1 class="sr-only">{t.measure_title}</h1>
|
||||
@@ -504,10 +502,10 @@
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<label for="m-weight" class="metric-label">{lang === 'en' ? 'Weight' : 'Gewicht'}</label>
|
||||
<label for="m-weight" class="metric-label">{t.weight}</label>
|
||||
{#if formWeight}
|
||||
<button type="button" class="metric-clear" onclick={() => formWeight = ''}>
|
||||
<X size={12} /> {lang === 'en' ? 'Clear' : 'Leeren'}
|
||||
<X size={12} /> {t.clear_action}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -535,10 +533,10 @@
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<label for="m-bf" class="metric-label">{lang === 'en' ? 'Body Fat' : 'Körperfett'}</label>
|
||||
<label for="m-bf" class="metric-label">{t.body_fat_label}</label>
|
||||
{#if formBodyFat}
|
||||
<button type="button" class="metric-clear" onclick={() => formBodyFat = ''}>
|
||||
<X size={12} /> {lang === 'en' ? 'Clear' : 'Leeren'}
|
||||
<X size={12} /> {t.clear_action}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -638,7 +636,7 @@
|
||||
<div class="edit-actions">
|
||||
<a class="edit-more" href={resolve('/fitness/[checkin=fitnessCheckIn]/edit/[id]', { checkin: checkinSlug, id: m._id })} aria-label={t.edit_measurement}>
|
||||
<Pencil size={11} />
|
||||
<span class="edit-more-label">{lang === 'en' ? 'Edit all fields' : 'Alle Felder bearbeiten'}</span>
|
||||
<span class="edit-more-label">{t.edit_all_fields}</span>
|
||||
<ChevronRight size={11} />
|
||||
</a>
|
||||
<button type="button" class="edit-btn cancel" onclick={cancelEdit} aria-label={t.cancel}>
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
res = await doPost(true);
|
||||
}
|
||||
if (res.ok) {
|
||||
toast.success(lang === 'en' ? 'Measurement saved' : 'Messung gespeichert');
|
||||
toast.success(t.measurement_saved);
|
||||
await goto(`/fitness/${checkinSlug}`);
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
}));
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{lang === 'en' ? 'Exercises' : 'Übungen'} - Bocken</title></svelte:head>
|
||||
<svelte:head><title>{t.exercises_title} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="exercises-page">
|
||||
<h1 class="sr-only">{t.exercises_title}</h1>
|
||||
|
||||
@@ -130,10 +130,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{exercise?.localName ?? (lang === 'en' ? 'Exercise' : 'Übung')} - Bocken</title></svelte:head>
|
||||
<svelte:head><title>{exercise?.localName ?? t.exercise_title} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="exercise-detail">
|
||||
<h1>{exercise?.localName ?? 'Exercise'}</h1>
|
||||
<h1>{exercise?.localName ?? t.exercise_title}</h1>
|
||||
|
||||
<div class="tabs">
|
||||
{#each tabs as tab}
|
||||
@@ -197,7 +197,7 @@
|
||||
<!-- Similar exercises -->
|
||||
{#if similar.length > 0}
|
||||
<div class="similar-section">
|
||||
<h3>{lang === 'en' ? 'Similar Exercises' : 'Ähnliche Übungen'}</h3>
|
||||
<h3>{t.similar_exercises}</h3>
|
||||
<div class="similar-scroll">
|
||||
{#each similar as sim}
|
||||
<a class="similar-card" href={resolve('/fitness/[exercises=fitnessExercises]/[id]', { exercises: s.exercises, id: sim.id })}>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
{/if}
|
||||
{#if viewMonth}
|
||||
<a class="month-link" href={recentHref}>
|
||||
{lang === 'en' ? 'Recent' : 'Aktuell'}
|
||||
{t.recent_label}
|
||||
</a>
|
||||
{/if}
|
||||
<a class="month-link" href={prevHref}>
|
||||
|
||||
@@ -583,7 +583,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{session?.name ?? (lang === 'en' ? 'Workout' : 'Training')} - Bocken</title>
|
||||
<title>{session?.name ?? t.workout_singular} - Bocken</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -229,9 +229,7 @@
|
||||
const baseline = stats.bfChart?.baseline;
|
||||
const label = t.body_fat.replace(' %', '').replace(' (%)', '');
|
||||
if (baseline == null) return label;
|
||||
const suffix = lang === 'en'
|
||||
? `Δ from ${baseline.toFixed(1)}%`
|
||||
: `Δ von ${baseline.toFixed(1)}%`;
|
||||
const suffix = `${t.bf_delta_from_prefix} ${baseline.toFixed(1)}%`;
|
||||
return `${label} · ${suffix}`;
|
||||
});
|
||||
|
||||
@@ -421,13 +419,9 @@
|
||||
<button class="card-info-trigger" onclick={() => showBalanceInfo = !showBalanceInfo} aria-label="Info"><Info size={12} /></button>
|
||||
{#if showBalanceInfo}
|
||||
<div class="card-info-tooltip">
|
||||
{lang === 'en'
|
||||
? 'Average daily calories eaten minus estimated expenditure (TDEE + tracked workout calories) over the last 7 days. Negative = deficit, positive = surplus.'
|
||||
: 'Durchschnittlich gegessene Kalorien minus geschätzter Verbrauch (TDEE + erfasste Trainingskilokalorien) der letzten 7 Tage. Negativ = Defizit, positiv = Überschuss.'}
|
||||
{t.calorie_balance_tooltip}
|
||||
{#if ns.avgDailyExpenditure}
|
||||
{lang === 'en'
|
||||
? `Est. daily expenditure: ~${ns.avgDailyExpenditure} kcal`
|
||||
: `Geschätzter Tagesverbrauch: ~${ns.avgDailyExpenditure} kcal`}
|
||||
{t.daily_expenditure_estimate_prefix} ~{ns.avgDailyExpenditure} kcal
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -436,7 +430,7 @@
|
||||
{#if ns.avgCalorieBalance != null}
|
||||
{t.seven_day_avg}
|
||||
{:else if !hasDemographics || !ns.trendWeight}
|
||||
{lang === 'en' ? 'Set height, birth year & weight' : 'Größe, Geburtsjahr & Gewicht eintragen'}
|
||||
{t.set_height_birthyear_weight}
|
||||
{:else}
|
||||
{t.no_nutrition_data}
|
||||
{/if}
|
||||
@@ -455,9 +449,7 @@
|
||||
<button class="card-info-trigger" onclick={() => showAdherenceInfo = !showAdherenceInfo} aria-label="Info"><Info size={12} /></button>
|
||||
{#if showAdherenceInfo}
|
||||
<div class="card-info-tooltip">
|
||||
{lang === 'en'
|
||||
? 'Percentage of days where calories eaten were within ±10% of your goal (adjusted for exercise calories burned). Days without tracking count as misses.'
|
||||
: 'Prozent der Tage, an denen die gegessenen Kalorien innerhalb von ±10 % deines Ziels lagen (bereinigt um verbrannte Trainings\u00ADkalorien). Nicht erfasste Tage zählen als verfehlt.'}
|
||||
{t.diet_adherence_tooltip}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -476,11 +468,11 @@
|
||||
<div class="macro-legend">
|
||||
<span class="macro-legend-item">
|
||||
<svg viewBox="0 0 12 12" width="12" height="12"><path d="M3,9.5 A4,4 0 1,1 9,9.5" fill="none" stroke="var(--color-text-secondary)" stroke-width="2" stroke-linecap="round"/></svg>
|
||||
{lang === 'en' ? 'Actual' : 'Ist'}
|
||||
{t.actual_label}
|
||||
</span>
|
||||
<span class="macro-legend-item">
|
||||
<svg viewBox="0 0 12 12" width="12" height="12"><path d="M6,10 L10,2 L2,2 Z" fill="var(--color-text-secondary)" stroke="var(--color-text-secondary)" stroke-width="1.5" stroke-linejoin="round"/></svg>
|
||||
{lang === 'en' ? 'Target' : 'Ziel'}
|
||||
{t.target_label}
|
||||
</span>
|
||||
</div>
|
||||
{#if !ns.macroSplit}
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@
|
||||
const hasData = $derived(series.dates.length > 0);
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{t[card.labelKey]} · {lang === 'en' ? 'History' : 'Verlauf'} - Bocken</title></svelte:head>
|
||||
<svelte:head><title>{t[card.labelKey]} · {t.history_title} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="detail-page">
|
||||
<header class="detail-header" style="--accent: {bodyPartAccent(card.key)}">
|
||||
|
||||
@@ -401,7 +401,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{lang === 'en' ? 'Workout' : 'Training'} - Bocken</title></svelte:head>
|
||||
<svelte:head><title>{t.workout_singular} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="template-view">
|
||||
{#if hasSchedule && nextTemplate}
|
||||
@@ -431,7 +431,7 @@
|
||||
<span class="next-exercises">
|
||||
{nextTemplate.exercises.length} {nextTemplate.exercises.length !== 1 ? t.exercises_word : t.exercise}
|
||||
{#if firstExData}
|
||||
· {lang === 'en' ? 'starts with' : 'beginnt mit'} {firstExData.localName} {#if firstExWeight != null}({firstExWeight} kg){/if}
|
||||
· {t.starts_with} {firstExData.localName} {#if firstExWeight != null}({firstExWeight} kg){/if}
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -976,7 +976,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{workout.name || (lang === 'en' ? 'Workout' : 'Training')} - Bocken</title>
|
||||
<title>{workout.name || t.workout_singular} - Bocken</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user