refactor(fitness): adopt t.key / t[expr] syntax across fitness pages
22 files migrated from t('key', lang) function calls to direct lookups
on a derived dictionary alias: const t = $derived(m[lang]) once per
file, then t.start_period or t[card.labelKey] at the call sites.
Cleaner read at the point of use, one less argument threaded through,
and TypeScript narrows on every key access (so a typo in a literal
key now errors at the call site, not silently falls back to the key
string).
The codemod handles both ways `lang` can enter scope — derived from
the URL via detectFitnessLang, or destructured from $props() (single
or multi-line). One file aliased the i18n table to `messages` to
avoid collision with a local `const m = data.measurement`.
The deprecated `t(key, lang)` function still exists in fitnessI18n.ts
for any remaining out-of-tree call sites — can be deleted once
nothing imports it.
This commit is contained in:
@@ -10,9 +10,10 @@
|
||||
import Shapes from '@lucide/svelte/icons/shapes';
|
||||
import Weight from '@lucide/svelte/icons/weight';
|
||||
import { page } from '$app/state';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
||||
|
||||
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||
const t = $derived(m[lang]);
|
||||
const isEn = $derived(lang === 'en');
|
||||
|
||||
/**
|
||||
@@ -81,7 +82,7 @@
|
||||
<div class="picker-backdrop" onclick={onClose}></div>
|
||||
<div class="picker-panel">
|
||||
<div class="picker-header">
|
||||
<h2>{t('picker_title', lang)}</h2>
|
||||
<h2>{t.picker_title}</h2>
|
||||
<button class="close-btn" onclick={onClose} aria-label="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
@@ -91,7 +92,7 @@
|
||||
<Search size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('search_exercises', lang)}
|
||||
placeholder={t.search_exercises}
|
||||
bind:value={query}
|
||||
/>
|
||||
</div>
|
||||
@@ -154,7 +155,7 @@
|
||||
</li>
|
||||
{/each}
|
||||
{#if filtered.length === 0}
|
||||
<li class="no-results">{t('no_exercises_found', lang)}</li>
|
||||
<li class="no-results">{t.no_exercises_found}</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import ExternalLink from '@lucide/svelte/icons/external-link';
|
||||
import ScanBarcode from '@lucide/svelte/icons/scan-barcode';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
||||
import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n';
|
||||
import MacroBreakdown from './MacroBreakdown.svelte';
|
||||
|
||||
/**
|
||||
@@ -51,9 +51,10 @@
|
||||
} = $props();
|
||||
|
||||
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||
const t = $derived(m[lang]);
|
||||
const s = $derived(fitnessSlugs(lang));
|
||||
const isEn = $derived(lang === 'en');
|
||||
const btnLabel = $derived(confirmLabel ?? t('log_food', lang));
|
||||
const btnLabel = $derived(confirmLabel ?? t.log_food);
|
||||
|
||||
// --- Search state ---
|
||||
let query = $state('');
|
||||
@@ -434,7 +435,7 @@
|
||||
<input
|
||||
type="text"
|
||||
class="fs-search-input"
|
||||
placeholder={t('search_food', lang)}
|
||||
placeholder={t.search_food}
|
||||
bind:value={query}
|
||||
oninput={doSearch}
|
||||
autofocus={autofocus}
|
||||
@@ -455,7 +456,7 @@
|
||||
<p class="fs-scan-error">{scanError}</p>
|
||||
{/if}
|
||||
{#if loading}
|
||||
<p class="fs-status">{t('loading', lang)}</p>
|
||||
<p class="fs-status">{t.loading}</p>
|
||||
{/if}
|
||||
{#if displayResults.length > 0}
|
||||
<div class="fs-results">
|
||||
@@ -487,7 +488,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if oncancel}
|
||||
<button class="fs-btn-cancel" onclick={oncancel}>{t('cancel', lang)}</button>
|
||||
<button class="fs-btn-cancel" onclick={oncancel}>{t.cancel}</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- Selected food — detail & amount -->
|
||||
@@ -546,7 +547,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="fs-actions">
|
||||
<button class="fs-btn-cancel" onclick={() => { selected = null; }}>{t('cancel', lang)}</button>
|
||||
<button class="fs-btn-cancel" onclick={() => { selected = null; }}>{t.cancel}</button>
|
||||
<button class="fs-btn-confirm" onclick={confirm}>{btnLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Sun from '@lucide/svelte/icons/sun';
|
||||
import Moon from '@lucide/svelte/icons/moon';
|
||||
import Cookie from '@lucide/svelte/icons/cookie';
|
||||
import { t } from '$lib/js/fitnessI18n';
|
||||
import { m } from '$lib/js/fitnessI18n';
|
||||
|
||||
/** @type {{ value?: 'breakfast' | 'lunch' | 'dinner' | 'snack', lang?: 'en' | 'de', onchange?: (meal: 'breakfast' | 'lunch' | 'dinner' | 'snack') => void }} */
|
||||
let {
|
||||
@@ -11,6 +11,7 @@
|
||||
lang = 'de',
|
||||
onchange = () => {},
|
||||
} = $props();
|
||||
const t = $derived(m[lang]);
|
||||
|
||||
/** @type {Array<'breakfast' | 'lunch' | 'dinner' | 'snack'>} */
|
||||
const mealTypes = ['breakfast', 'lunch', 'dinner', 'snack'];
|
||||
@@ -32,7 +33,7 @@
|
||||
class:active={value === meal}
|
||||
style="--mc: {meta.color}"
|
||||
onclick={() => { onchange(meal); }}
|
||||
title={t(meal, lang)}
|
||||
title={t[meal]}
|
||||
>
|
||||
<MealIcon size={14} />
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { t } from '$lib/js/fitnessI18n';
|
||||
import { m } from '$lib/js/fitnessI18n';
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
@@ -17,6 +17,7 @@
|
||||
* @type {{ periods: any[], lang: 'en' | 'de', sharedWith?: string[], readOnly?: boolean, ownerName?: string, mode?: 'entry' | 'projection' | 'full' }}
|
||||
*/
|
||||
let { periods: initialPeriods = [], lang = 'en', sharedWith: initialSharedWith = [], readOnly = false, ownerName = '', mode = 'full' } = $props();
|
||||
const t = $derived(m[lang]);
|
||||
const showEntry = $derived(mode !== 'projection');
|
||||
const showProjection = $derived(mode !== 'entry');
|
||||
|
||||
@@ -116,7 +117,7 @@
|
||||
const startDay = start.toLocaleDateString(locale, { weekday: 'long' });
|
||||
const endDay = end.toLocaleDateString(locale, { weekday: 'long' });
|
||||
const diffDays = Math.round((midnight(start) - todayMidnight) / 86400000);
|
||||
const toWord = t('to', lang);
|
||||
const toWord = t.to;
|
||||
|
||||
if (diffDays >= 0 && diffDays < 7) {
|
||||
return lang === 'de'
|
||||
@@ -653,7 +654,7 @@
|
||||
|
||||
/** @param {string} id */
|
||||
async function deletePeriod(id) {
|
||||
if (!await confirm(t('delete_period_confirm', lang))) return;
|
||||
if (!await confirm(t.delete_period_confirm)) return;
|
||||
try {
|
||||
const res = await fetch(`/api/fitness/period/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
@@ -709,7 +710,7 @@
|
||||
{ownerName}
|
||||
</span>
|
||||
{:else}
|
||||
{t('period_tracker', lang)}
|
||||
{t.period_tracker}
|
||||
{/if}
|
||||
</h2>
|
||||
|
||||
@@ -718,29 +719,29 @@
|
||||
{#if ongoing}
|
||||
<div class="status-split">
|
||||
<div class="status-main">
|
||||
<span class="status-pill period-pill">{t('current_period', lang)}</span>
|
||||
<span class="status-hero ongoing-hero">{t('period_day', lang)} {ongoingDay}</span>
|
||||
<span class="status-pill period-pill">{t.current_period}</span>
|
||||
<span class="status-hero ongoing-hero">{t.period_day} {ongoingDay}</span>
|
||||
{#if showProjection && predictions.predictedEndOfOngoing}
|
||||
<span class="status-detail">{t('predicted_end', lang)}</span>
|
||||
<span class="status-detail">{t.predicted_end}</span>
|
||||
<span class="status-relative">{relativeDate(predictions.predictedEndOfOngoing)}</span>
|
||||
<span class="status-date">{formatDate(predictions.predictedEndOfOngoing)}</span>
|
||||
{/if}
|
||||
{#if showEntry && !readOnly}
|
||||
<button class="end-btn" onclick={endPeriod} disabled={loading}>
|
||||
<span class="end-btn-icon"><Check size={18} strokeWidth={2.5} /></span>
|
||||
<span class="end-btn-label">{t('end_period', lang)}</span>
|
||||
<span class="end-btn-label">{t.end_period}</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showProjection && nextCycle}
|
||||
<div class="status-side">
|
||||
<div class="status-side-item ovulation-accent">
|
||||
<span class="status-side-label">{t('ovulation', lang)}</span>
|
||||
<span class="status-side-label">{t.ovulation}</span>
|
||||
<span class="status-side-relative">{relativeDate(nextCycle.fertileEnd)}</span>
|
||||
<span class="status-side-date">{formatDate(nextCycle.fertileEnd)}</span>
|
||||
</div>
|
||||
<div class="status-side-item fertile-accent">
|
||||
<span class="status-side-label">{t('fertile', lang)}</span>
|
||||
<span class="status-side-label">{t.fertile}</span>
|
||||
<span class="status-side-date">{formatDate(nextCycle.fertileStart)} — {formatDate(nextCycle.fertileEnd)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -749,33 +750,33 @@
|
||||
{:else if showProjection && nextCycle}
|
||||
<div class="status-split">
|
||||
<div class="status-main">
|
||||
<span class="status-pill period-pill">{t('next_period', lang)}</span>
|
||||
<span class="status-pill period-pill">{t.next_period}</span>
|
||||
<span class="status-hero">{relativeRange(nextCycle.start, nextCycle.end)}</span>
|
||||
<span class="status-date">{formatDate(nextCycle.start)} — {formatDate(nextCycle.end)}</span>
|
||||
{#if showEntry && !readOnly}
|
||||
<button class="start-btn" onclick={startPeriod} disabled={loading}>
|
||||
{t('start_period', lang)}
|
||||
{t.start_period}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="status-side">
|
||||
<div class="status-side-item ovulation-accent">
|
||||
<span class="status-side-label">{t('ovulation', lang)}</span>
|
||||
<span class="status-side-label">{t.ovulation}</span>
|
||||
<span class="status-side-relative">{relativeDate(nextCycle.fertileEnd)}</span>
|
||||
<span class="status-side-date">{formatDate(nextCycle.fertileEnd)}</span>
|
||||
</div>
|
||||
<div class="status-side-item fertile-accent">
|
||||
<span class="status-side-label">{t('fertile', lang)}</span>
|
||||
<span class="status-side-label">{t.fertile}</span>
|
||||
<span class="status-side-date">{formatDate(nextCycle.fertileStart)} — {formatDate(nextCycle.fertileEnd)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if showEntry}
|
||||
<div class="status-block">
|
||||
<span class="status-empty">{sorted.length === 0 ? t('no_period_data', lang) : t('no_active_period', lang)}</span>
|
||||
<span class="status-empty">{sorted.length === 0 ? t.no_period_data : t.no_active_period}</span>
|
||||
{#if !readOnly}
|
||||
<button class="start-btn" onclick={startPeriod} disabled={loading}>
|
||||
{t('start_period', lang)}
|
||||
{t.start_period}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -817,10 +818,10 @@
|
||||
<div class="cal-legend">
|
||||
<span class="legend-item"><span class="legend-dot period"></span> {lang === 'de' ? 'Periode' : 'Period'}</span>
|
||||
<span class="legend-item"><span class="legend-dot predicted"></span> {lang === 'de' ? 'Prognose' : 'Predicted'}</span>
|
||||
<span class="legend-item"><span class="legend-dot fertile"></span> {t('fertile', lang)}</span>
|
||||
<span class="legend-item"><span class="legend-dot peak-fertile"></span> {t('peak_fertility', lang)}</span>
|
||||
<span class="legend-item"><span class="legend-dot ovulation"></span> {t('ovulation', lang)}</span>
|
||||
<span class="legend-item"><span class="legend-dot luteal"></span> {t('luteal_phase', lang)}</span>
|
||||
<span class="legend-item"><span class="legend-dot fertile"></span> {t.fertile}</span>
|
||||
<span class="legend-item"><span class="legend-dot peak-fertile"></span> {t.peak_fertility}</span>
|
||||
<span class="legend-item"><span class="legend-dot ovulation"></span> {t.ovulation}</span>
|
||||
<span class="legend-item"><span class="legend-dot luteal"></span> {t.luteal_phase}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -829,17 +830,17 @@
|
||||
{#if showProjection && completed.length >= 2}
|
||||
<div class="cycle-stats">
|
||||
<div class="cycle-stat">
|
||||
<span class="cycle-stat-label">{t('cycle_length', lang)}</span>
|
||||
<span class="cycle-stat-value">{Math.round(predictions.avgCycle)} {t('days', lang)}</span>
|
||||
<span class="cycle-stat-label">{t.cycle_length}</span>
|
||||
<span class="cycle-stat-value">{Math.round(predictions.avgCycle)} {t.days}</span>
|
||||
{#if predictions.cycleVariance > 0}
|
||||
<span class="cycle-stat-variance">± {predictions.cycleVariance} {t('days', lang)} (95% CI)</span>
|
||||
<span class="cycle-stat-variance">± {predictions.cycleVariance} {t.days} (95% CI)</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="cycle-stat">
|
||||
<span class="cycle-stat-label">{t('period_length', lang)}</span>
|
||||
<span class="cycle-stat-value">{Math.round(predictions.avgPeriod)} {t('days', lang)}</span>
|
||||
<span class="cycle-stat-label">{t.period_length}</span>
|
||||
<span class="cycle-stat-value">{Math.round(predictions.avgPeriod)} {t.days}</span>
|
||||
{#if predictions.periodVariance > 0}
|
||||
<span class="cycle-stat-variance">± {predictions.periodVariance} {t('days', lang)} (95% CI)</span>
|
||||
<span class="cycle-stat-variance">± {predictions.periodVariance} {t.days} (95% CI)</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -851,13 +852,13 @@
|
||||
<div class="history">
|
||||
<div class="history-share-row">
|
||||
<button class="history-toggle" onclick={() => showHistory = !showHistory}>
|
||||
<h3>{t('history', lang)}</h3>
|
||||
<h3>{t.history}</h3>
|
||||
<ChevronRight size={14} class={showHistory ? 'chevron open' : 'chevron'} />
|
||||
</button>
|
||||
<div class="share-bar">
|
||||
{#if shareList.length > 0}
|
||||
<div class="shared-avatars">
|
||||
<span class="shared-label">{t('shared_with', lang)}</span>
|
||||
<span class="shared-label">{t.shared_with}</span>
|
||||
{#each shareList as user}
|
||||
<div class="shared-avatar" title={user}>
|
||||
<ProfilePicture username={user} size={28} />
|
||||
@@ -865,7 +866,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<button class="share-btn" onclick={() => showShare = true} aria-label={t('share', lang)}>
|
||||
<button class="share-btn" onclick={() => showShare = true} aria-label={t.share}>
|
||||
<UserPlus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -875,7 +876,7 @@
|
||||
<div class="history-header">
|
||||
<button class="add-past-btn" onclick={() => showAddForm = !showAddForm}>
|
||||
<Plus size={14} />
|
||||
{t('add_past_period', lang)}
|
||||
{t.add_past_period}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -883,20 +884,20 @@
|
||||
<div class="add-form">
|
||||
<div class="add-row">
|
||||
<label>
|
||||
{t('period_start', lang)}
|
||||
{t.period_start}
|
||||
<DatePicker bind:value={addStart} max={todayStr} {lang} />
|
||||
</label>
|
||||
<label>
|
||||
{t('period_end', lang)}
|
||||
{t.period_end}
|
||||
<DatePicker bind:value={addEnd} min={addStart} max={todayStr} {lang} />
|
||||
</label>
|
||||
</div>
|
||||
<div class="add-actions">
|
||||
<button class="save-btn" onclick={addPastPeriod} disabled={!addStart || loading}>
|
||||
{t('save', lang)}
|
||||
{t.save}
|
||||
</button>
|
||||
<button class="cancel-btn" onclick={() => { showAddForm = false; addStart = ''; addEnd = ''; }}>
|
||||
{t('cancel', lang)}
|
||||
{t.cancel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -908,20 +909,20 @@
|
||||
<div class="history-item editing">
|
||||
<div class="add-row">
|
||||
<label>
|
||||
{t('period_start', lang)}
|
||||
{t.period_start}
|
||||
<DatePicker bind:value={editStart} {lang} />
|
||||
</label>
|
||||
<label>
|
||||
{t('period_end', lang)}
|
||||
{t.period_end}
|
||||
<DatePicker bind:value={editEnd} min={editStart} {lang} />
|
||||
</label>
|
||||
</div>
|
||||
<div class="add-actions">
|
||||
<button class="save-btn" onclick={saveEdit} disabled={!editStart || loading}>
|
||||
{t('save', lang)}
|
||||
{t.save}
|
||||
</button>
|
||||
<button class="cancel-btn" onclick={cancelEdit}>
|
||||
{t('cancel', lang)}
|
||||
{t.cancel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -933,12 +934,12 @@
|
||||
{#if p.endDate}
|
||||
— {formatDate(p.endDate)}
|
||||
{:else}
|
||||
<span class="ongoing-badge">{t('ongoing', lang)}</span>
|
||||
<span class="ongoing-badge">{t.ongoing}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{#if p.endDate}
|
||||
{@const dur = Math.round((new Date(p.endDate).getTime() - new Date(p.startDate).getTime()) / 86400000) + 1}
|
||||
<span class="history-dur">{dur} {t('days', lang)}</span>
|
||||
<span class="history-dur">{dur} {t.days}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="history-actions">
|
||||
@@ -958,33 +959,33 @@
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
<div class="share-bar">
|
||||
<p>{t('no_period_data', lang)}</p>
|
||||
<button class="share-btn" onclick={() => showShare = true} aria-label={t('share', lang)}>
|
||||
<p>{t.no_period_data}</p>
|
||||
<button class="share-btn" onclick={() => showShare = true} aria-label={t.share}>
|
||||
<UserPlus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<button class="add-past-btn" onclick={() => showAddForm = !showAddForm}>
|
||||
<Plus size={14} />
|
||||
{t('add_past_period', lang)}
|
||||
{t.add_past_period}
|
||||
</button>
|
||||
{#if showAddForm}
|
||||
<div class="add-form">
|
||||
<div class="add-row">
|
||||
<label>
|
||||
{t('period_start', lang)}
|
||||
{t.period_start}
|
||||
<DatePicker bind:value={addStart} max={todayStr} {lang} />
|
||||
</label>
|
||||
<label>
|
||||
{t('period_end', lang)}
|
||||
{t.period_end}
|
||||
<DatePicker bind:value={addEnd} min={addStart} max={todayStr} {lang} />
|
||||
</label>
|
||||
</div>
|
||||
<div class="add-actions">
|
||||
<button class="save-btn" onclick={addPastPeriod} disabled={!addStart || loading}>
|
||||
{t('save', lang)}
|
||||
{t.save}
|
||||
</button>
|
||||
<button class="cancel-btn" onclick={() => { showAddForm = false; addStart = ''; addEnd = ''; }}>
|
||||
{t('cancel', lang)}
|
||||
{t.cancel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1002,7 +1003,7 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||
<div class="share-modal" role="presentation" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="share-modal-header">
|
||||
<h3>{t('share', lang)}</h3>
|
||||
<h3>{t.share}</h3>
|
||||
<button class="share-modal-close" onclick={() => showShare = false}>
|
||||
<X size={16} />
|
||||
</button>
|
||||
@@ -1020,13 +1021,13 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<span class="share-empty">{t('no_shared', lang)}</span>
|
||||
<span class="share-empty">{t.no_shared}</span>
|
||||
{/if}
|
||||
<form class="share-add" onsubmit={(e) => { e.preventDefault(); addShareUser(); }}>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={shareInput}
|
||||
placeholder={t('add_user', lang)}
|
||||
placeholder={t.add_user}
|
||||
disabled={shareSaving}
|
||||
/>
|
||||
<button type="submit" class="share-add-btn" disabled={!shareInput.trim() || shareSaving}>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import Wheat from '@lucide/svelte/icons/wheat';
|
||||
import { untrack } from 'svelte';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import { t } from '$lib/js/fitnessI18n';
|
||||
import { m } from '$lib/js/fitnessI18n';
|
||||
import MealTypePicker from '$lib/components/fitness/MealTypePicker.svelte';
|
||||
/** @typedef {import('$lib/server/roundOffScoring').ComboSuggestion} ComboSuggestion */
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
initialSuggestions = null,
|
||||
onlogged = () => {},
|
||||
} = $props();
|
||||
const t = $derived(m[lang]);
|
||||
|
||||
const isEn = $derived(lang === 'en');
|
||||
|
||||
@@ -157,7 +158,7 @@
|
||||
<div class="round-off-header">
|
||||
<Sparkles size={16} />
|
||||
<h3>{isEn ? 'Round off this day' : 'Tag abrunden'}</h3>
|
||||
<span class="round-off-remaining">{Math.round(remainingKcal)} kcal {t('remaining', lang)}</span>
|
||||
<span class="round-off-remaining">{Math.round(remainingKcal)} kcal {t.remaining}</span>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
import { METRIC_LABELS } from '$lib/data/exercises';
|
||||
import RestTimer from './RestTimer.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
||||
|
||||
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||
const t = $derived(m[lang]);
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
@@ -97,16 +98,16 @@
|
||||
{#if editable && onRemove}
|
||||
<th class="col-remove"></th>
|
||||
{/if}
|
||||
<th class="col-set">{t('set_header', lang)}</th>
|
||||
<th class="col-set">{t.set_header}</th>
|
||||
{#if previousSets}
|
||||
<th class="col-prev">{t('prev_header', lang)}</th>
|
||||
<th class="col-prev">{t.prev_header}</th>
|
||||
{/if}
|
||||
{#each mainMetrics as metric (metric)}
|
||||
<th class="col-metric">{timedHold && metric === 'duration' ? 'SEC' : METRIC_LABELS[metric]}</th>
|
||||
{/each}
|
||||
{#if editable && hasRpe}
|
||||
<th class="col-at"></th>
|
||||
<th class="col-rpe">{t('rpe', lang)}</th>
|
||||
<th class="col-rpe">{t.rpe}</th>
|
||||
{/if}
|
||||
{#if editable}
|
||||
<th class="col-check"></th>
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import EllipsisVertical from '@lucide/svelte/icons/ellipsis-vertical';
|
||||
import MapPin from '@lucide/svelte/icons/map-pin';
|
||||
import { page } from '$app/state';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
||||
|
||||
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||
const t = $derived(m[lang]);
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
@@ -23,8 +24,8 @@
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - d.getTime();
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
if (diffDays === 0) return t('today', lang);
|
||||
if (diffDays === 1) return t('yesterday', lang);
|
||||
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' });
|
||||
}
|
||||
@@ -52,12 +53,12 @@
|
||||
<li>{ex.sets.length} × {exercise?.localName ?? ex.exerciseId}</li>
|
||||
{/each}
|
||||
{#if template.exercises.length > 4}
|
||||
<li class="more">+{template.exercises.length - 4} {t('more', lang)}</li>
|
||||
<li class="more">+{template.exercises.length - 4} {t.more}</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if lastUsed}
|
||||
<p class="last-used">{t('last_performed', lang)} {formatDate(lastUsed)}</p>
|
||||
<p class="last-used">{t.last_performed} {formatDate(lastUsed)}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import Pause from '@lucide/svelte/icons/pause';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
||||
|
||||
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||
const t = $derived(m[lang]);
|
||||
|
||||
let { href, elapsed = '0:00', paused = false, syncStatus = 'idle', onPauseToggle,
|
||||
restSeconds = 0, restTotal = 0, onRestAdjust = null, onRestSkip = null } = $props();
|
||||
@@ -28,7 +29,7 @@ const restProgress = $derived(restTotal > 0 ? restSeconds / restTotal : 0);
|
||||
class:rest-active={restActive}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label={t('active_workout', lang)}
|
||||
aria-label={t.active_workout}
|
||||
onclick={() => goto(href)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); goto(href); } }}
|
||||
>
|
||||
@@ -54,7 +55,7 @@ const restProgress = $derived(restTotal > 0 ? restSeconds / restTotal : 0);
|
||||
<button class="rest-adj" onclick={(e) => { e.stopPropagation(); onRestAdjust?.(30); }} aria-label="Add 30 seconds">+30s</button>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="fab-label">{t('active_workout', lang)}</span>
|
||||
<span class="fab-label">{t.active_workout}</span>
|
||||
<ChevronRight size={14} strokeWidth={2.4} class="fab-chevron" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user