diff --git a/package.json b/package.json index 57cd1e3b..47aef5ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.54.0", + "version": "1.54.1", "private": true, "type": "module", "scripts": { diff --git a/scripts/codemod-fitness-t-to-m.ts b/scripts/codemod-fitness-t-to-m.ts new file mode 100644 index 00000000..082ac01d --- /dev/null +++ b/scripts/codemod-fitness-t-to-m.ts @@ -0,0 +1,104 @@ +/** + * Migrate fitness call sites from t('key', lang) to t.key (or t[expr] for + * dynamic keys), where t = m[lang] derived once per file. + * + * Run: pnpm exec vite-node scripts/codemod-fitness-t-to-m.ts [--dry] + */ + +import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs'; +import { join, extname } from 'node:path'; + +const DRY = process.argv.includes('--dry'); +const ROOTS = ['src/routes/fitness', 'src/lib/components/fitness']; + +const IMPORT_RE = + /import\s*\{([^}]+)\}\s*from\s*['"]\$lib\/js\/fitnessI18n['"]\s*;?/; + +function walk(dir: string, out: string[] = []): string[] { + for (const name of readdirSync(dir)) { + const p = join(dir, name); + const s = statSync(p); + if (s.isDirectory()) walk(p, out); + else if (extname(p) === '.svelte' || extname(p) === '.ts') out.push(p); + } + return out; +} + +function migrate(src: string): { code: string; changed: boolean } { + const m0 = IMPORT_RE.exec(src); + if (!m0) return { code: src, changed: false }; + + const items = m0[1].split(',').map((s) => s.trim()).filter(Boolean); + if (!items.includes('t')) return { code: src, changed: false }; + + // 1. Rewrite import: drop `t`, ensure `m` present. + const tIdx = items.indexOf('t'); + items.splice(tIdx, 1); + if (!items.includes('m')) items.push('m'); + let out = src.replace(IMPORT_RE, `import { ${items.join(', ')} } from '$lib/js/fitnessI18n';`); + + // 2. Insert `const t = $derived(m[lang]);` at the right spot, depending + // on how `lang` enters scope. + // Pattern A: `const lang = $derived(...)` — derived from URL + // Pattern B: `let { ... lang ... } = $props()` — passed as prop (single or multi-line) + let inserted = false; + + // Pattern A: derived. Allow up to two levels of nested parens inside + // $derived(...) so detectFitnessLang(page.url.pathname) matches. + const langDerivedRe = + /^([ \t]*)(const\s+lang\s*=\s*\$derived\((?:[^()]|\([^()]*\))+\)\s*;?)([ \t]*\n)/m; + if (langDerivedRe.test(out)) { + out = out.replace(langDerivedRe, (_, indent, decl, nl) => { + inserted = true; + return `${indent}${decl}${nl}${indent}const t = $derived(m[lang]);${nl}`; + }); + } + + // Pattern B: $props() destructure, possibly spanning multiple lines. + // Match any `let { ... } = $props()` and only insert if `lang` is in it. + if (!inserted) { + const propsRe = + /^([ \t]*)(let\s*\{[\s\S]*?\}\s*=\s*\$props\(\)\s*;?)([ \t]*\n)/m; + out = out.replace(propsRe, (full, indent, decl, nl) => { + if (!/\blang\b/.test(decl)) return full; + inserted = true; + return `${indent}${decl}${nl}${indent}const t = $derived(m[lang]);${nl}`; + }); + } + + if (!inserted) { + console.warn(` WARN: could not auto-insert \`const t = $derived(m[lang])\` — manual fix needed`); + } + + // 3. Replace t('static_key', lang) → t.static_key + out = out.replace( + /\bt\(\s*['"]([a-z_][a-z0-9_]*)['"]\s*,\s*lang\s*\)/g, + 't.$1' + ); + + // 4. Replace t(, lang) → t[] for any remaining call. + // Expression captured allows up to single-level nested parens, which + // covers our /** @type {FitnessKey} */ (expr) patterns. + out = out.replace( + /\bt\(((?:[^()]|\([^()]*\))+?)\s*,\s*lang\s*\)/g, + (match, expr) => { + const trimmed = expr.trim(); + return `t[${trimmed}]`; + } + ); + + return { code: out, changed: out !== src }; +} + +let total = 0; +for (const root of ROOTS) { + for (const f of walk(root)) { + const orig = readFileSync(f, 'utf8'); + const { code, changed } = migrate(orig); + if (!changed) continue; + if (!DRY) writeFileSync(f, code); + total++; + console.log(` ${f}`); + } +} +console.log(`\n${DRY ? '[dry] ' : ''}${total} files migrated`); diff --git a/src/lib/components/fitness/ExercisePicker.svelte b/src/lib/components/fitness/ExercisePicker.svelte index 628e7b6f..4293d746 100644 --- a/src/lib/components/fitness/ExercisePicker.svelte +++ b/src/lib/components/fitness/ExercisePicker.svelte @@ -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 @@
-

{t('picker_title', lang)}

+

{t.picker_title}

@@ -91,7 +92,7 @@
@@ -154,7 +155,7 @@ {/each} {#if filtered.length === 0} -
  • {t('no_exercises_found', lang)}
  • +
  • {t.no_exercises_found}
  • {/if}
    diff --git a/src/lib/components/fitness/FoodSearch.svelte b/src/lib/components/fitness/FoodSearch.svelte index 52f0feb8..b3356e84 100644 --- a/src/lib/components/fitness/FoodSearch.svelte +++ b/src/lib/components/fitness/FoodSearch.svelte @@ -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 @@ {scanError}

    {/if} {#if loading} -

    {t('loading', lang)}

    +

    {t.loading}

    {/if} {#if displayResults.length > 0}
    @@ -487,7 +488,7 @@
    {/if} {#if oncancel} - + {/if} {:else} @@ -546,7 +547,7 @@ {/if}
    - +
    diff --git a/src/lib/components/fitness/MealTypePicker.svelte b/src/lib/components/fitness/MealTypePicker.svelte index 3628a378..d1794331 100644 --- a/src/lib/components/fitness/MealTypePicker.svelte +++ b/src/lib/components/fitness/MealTypePicker.svelte @@ -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]} > diff --git a/src/lib/components/fitness/PeriodTracker.svelte b/src/lib/components/fitness/PeriodTracker.svelte index a0a14779..7013c44d 100644 --- a/src/lib/components/fitness/PeriodTracker.svelte +++ b/src/lib/components/fitness/PeriodTracker.svelte @@ -1,5 +1,5 @@ -{t('body_parts', lang)} - Bocken +{t.body_parts} - Bocken
    -
    - @@ -442,9 +443,9 @@
    - {fmt(t('step_n_of_m', lang), { n: idx + 1, m: total })} + {fmt(t.step_n_of_m, { n: idx + 1, m: total })}

    {stepLabel(step)}

    -

    {t(step.tipKey, lang)}

    +

    {t[step.tipKey]}

    {#if step.paired} @@ -488,20 +489,20 @@ {/if} {#if lastForStep?.left != null || lastForStep?.right != null} {/if}
    - +
    {:else}
    onWheel(e, step.key, null)}> @@ -519,7 +520,7 @@ {#if lastForStep?.value != null} {/if} {/if} @@ -530,8 +531,8 @@
    -

    {t('ready_to_save', lang)}

    -

    {t('review_numbers', lang)}

    +

    {t.ready_to_save}

    +

    {t.review_numbers}

    @@ -548,7 +549,7 @@
    @@ -561,15 +562,15 @@
    -

    {fmt(t('over_time', lang), { label: stepLabel(step) })}

    +

    {fmt(t.over_time, { label: stepLabel(step) })}

    {#if chart.empty}
    - {t('first_measurement_hint', lang)} + {t.first_measurement_hint}
    {:else} - + @@ -598,7 +599,7 @@ {#if chart.axis.lastDate} {shortDate(chart.axis.lastDate)} {/if} - {t('today_short', lang)} + {t.today_short} {/if} {#if chart.series.length > 1} @@ -618,7 +619,7 @@ {chart.series[0].pending.value.toFixed(1)} cm - {t('today_short', lang)} + {t.today_short}
    {/if} @@ -631,31 +632,31 @@
    {#if !done} - + {#if showShortcuts} {:else} {/if} @@ -665,7 +666,7 @@
    {#if done} - + {/if}
    diff --git a/src/routes/fitness/[checkin=fitnessCheckIn]/edit/[id]/+page.svelte b/src/routes/fitness/[checkin=fitnessCheckIn]/edit/[id]/+page.svelte index ad185d23..202cd6c5 100644 --- a/src/routes/fitness/[checkin=fitnessCheckIn]/edit/[id]/+page.svelte +++ b/src/routes/fitness/[checkin=fitnessCheckIn]/edit/[id]/+page.svelte @@ -1,7 +1,7 @@ -{t('edit_measurement', lang)} - Bocken +{t.edit_measurement} - Bocken
    -

    {t('edit_measurement', lang)}

    +

    {t.edit_measurement}

    {#if !m}

    Measurement not found.

    {:else}
    { e.preventDefault(); saveMeasurement(); }}>
    - +
    -

    {t('general', lang)}

    +

    {t.general}

    - +
    - +
    -

    {t('body_parts_cm', lang)}

    +

    {t.body_parts_cm}

    -
    -
    -
    +
    +
    +
    -
    -
    +
    +
    -
    -
    +
    +
    -
    -
    +
    +
    -
    -
    +
    +
    -
    -
    +
    +
    - + {/if}
    diff --git a/src/routes/fitness/[exercises=fitnessExercises]/+page.svelte b/src/routes/fitness/[exercises=fitnessExercises]/+page.svelte index a73e529d..b734d6aa 100644 --- a/src/routes/fitness/[exercises=fitnessExercises]/+page.svelte +++ b/src/routes/fitness/[exercises=fitnessExercises]/+page.svelte @@ -12,11 +12,12 @@ import Layers from '@lucide/svelte/icons/layers'; import { getFilterOptionsAll, searchAllExercises, isStretchType } from '$lib/data/exercisedb'; import { translateTerm } from '$lib/data/exercises'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import { MUSCLE_GROUPS, MUSCLE_GROUP_DE } from '$lib/data/muscleMap'; import MuscleFilter from '$lib/components/fitness/MuscleFilter.svelte'; const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const isEn = $derived(lang === 'en'); const sl = $derived(fitnessSlugs(lang)); @@ -117,7 +118,7 @@ {lang === 'en' ? 'Exercises' : 'Übungen'} - Bocken
    -

    {t('exercises_title', lang)}

    +

    {t.exercises_title}

    @@ -185,7 +186,7 @@ {#if exercise?.localInstructions?.length} -

    {t('instructions', lang)}

    +

    {t.instructions}

      {#each exercise.localInstructions as step}
    1. {step}
    2. @@ -234,7 +235,7 @@ {:else if activeTab === 'history'}
      {#if history.length === 0} -

      {t('no_history_yet', lang)}

      +

      {t.no_history_yet}

      {:else} {#each history as entry (entry.sessionId)}
      @@ -244,7 +245,7 @@
      - + {#each entry.sets as set, i (i)} @@ -264,11 +265,11 @@ {:else if activeTab === 'charts'}
      {#if (charts.est1rmOverTime?.length ?? 0) > 0} - - - + + + {:else} -

      {t('not_enough_data', lang)}

      +

      {t.not_enough_data}

      {/if}
      {:else if activeTab === 'records'} @@ -276,29 +277,29 @@
      {#if prs.estimatedOneRepMax}
      - {t('estimated_1rm', lang)} + {t.estimated_1rm} {prs.estimatedOneRepMax} kg
      {/if} {#if prs.maxVolume}
      - {t('max_volume', lang)} + {t.max_volume} {prs.maxVolume} kg
      {/if} {#if prs.maxWeight}
      - {t('max_weight', lang)} + {t.max_weight} {prs.maxWeight} kg
      {/if}
      {#if records.length} -

      {t('rep_records', lang)}

      +

      {t.rep_records}

      {t('set', lang)}{t('kg', lang)}{t('reps', lang)}{t('est_1rm', lang)}
      {t.set}{t.kg}{t.reps}{t.est_1rm}
      - + {#each records as rec (rec.reps)} diff --git a/src/routes/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]/+page.svelte b/src/routes/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]/+page.svelte index a46dd194..8bb81d76 100644 --- a/src/routes/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]/+page.svelte +++ b/src/routes/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]/+page.svelte @@ -4,9 +4,10 @@ import ChevronLeft from '@lucide/svelte/icons/chevron-left'; import ChevronRight from '@lucide/svelte/icons/chevron-right'; import SessionCard from '$lib/components/fitness/SessionCard.svelte'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; const lang = $derived(detectFitnessLang(appPage.url.pathname)); + const t = $derived(m[lang]); const s = $derived(fitnessSlugs(lang)); let { data } = $props(); @@ -60,17 +61,17 @@ const recentHref = $derived(resolve('/fitness/[history=fitnessHistory]', { history: s.history })); -{t('history_title', lang)} - Bocken +{t.history_title} - Bocken
      -

      {t('history_title', lang)}

      +

      {t.history_title}

      {#if sessions.length === 0} -

      {t('no_workouts_yet', lang)}

      +

      {t.no_workouts_yet}

      {:else} {#each Object.entries(grouped) as [month, monthSessions] (month)}
      -

      {month} — {monthSessions.length} {monthSessions.length !== 1 ? t('workouts_plural', lang) : t('workout_singular', lang)}

      +

      {month} — {monthSessions.length} {monthSessions.length !== 1 ? t.workouts_plural : t.workout_singular}

      {#each monthSessions as session (session._id)} diff --git a/src/routes/fitness/[history=fitnessHistory]/[id]/+page.svelte b/src/routes/fitness/[history=fitnessHistory]/[id]/+page.svelte index fb192cdc..dc2d5464 100644 --- a/src/routes/fitness/[history=fitnessHistory]/[id]/+page.svelte +++ b/src/routes/fitness/[history=fitnessHistory]/[id]/+page.svelte @@ -16,11 +16,12 @@ import Flame from '@lucide/svelte/icons/flame'; import Info from '@lucide/svelte/icons/info'; import Mountain from '@lucide/svelte/icons/mountain'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import { confirm } from '$lib/js/confirmDialog.svelte'; import { toast } from '$lib/js/toast.svelte'; const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const sl = $derived(fitnessSlugs(lang)); import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises'; import { formatPaceRangeLabel, formatPaceValue } from '$lib/data/cardioPrRanges'; @@ -232,7 +233,7 @@ } async function deleteSession() { - if (!await confirm(t('delete_session_confirm', lang))) return; + if (!await confirm(t.delete_session_confirm)) return; deleting = true; try { const res = await fetch(`/api/fitness/sessions/${session._id}`, { method: 'DELETE' }); @@ -475,7 +476,7 @@ return { labels: filtered.map(s => s.dist.toFixed(2)), datasets: [{ - label: t('elevation', lang), + label: t.elevation, data: filtered.map(s => Math.round(s.altitude)), borderColor: color, backgroundColor: fill, @@ -516,7 +517,7 @@ return { labels: filtered.map(s => s.dist.toFixed(2)), datasets: [{ - label: t('cadence', lang), + label: t.cadence, data: filtered.map(s => s.cadence), borderColor: color, backgroundColor: fill, @@ -564,7 +565,7 @@ /** @param {number} exIdx */ async function removeGpx(exIdx) { - if (!await confirm(t('remove_gps_confirm', lang))) return; + if (!await confirm(t.remove_gps_confirm)) return; try { const res = await fetch(`/api/fitness/sessions/${session._id}/gpx`, { method: 'DELETE', @@ -598,13 +599,13 @@
      {#if editing} - - + {:else} @@ -723,13 +724,13 @@ />
      {/each} {:else} {#each session.exercises as ex, exIdx (ex.exerciseId + '-' + exIdx)} @@ -744,13 +745,13 @@
      {t('reps', lang)}{t('best_performance', lang)}{t('est_1rm', lang)}
      {t.reps}{t.best_performance}{t.est_1rm}
      - + {#each mainMetrics as metric (metric)} {/each} {#if showEst1rm} - + {/if} @@ -783,8 +784,8 @@ {formatPace(pace)} {/if} {#if elevStats} - +{elevStats.gain}{t('elevation_unit', lang)} - -{elevStats.loss}{t('elevation_unit', lang)} + +{elevStats.gain}{t.elevation_unit} + -{elevStats.loss}{t.elevation_unit} {/if}
      @@ -797,7 +798,7 @@
      @@ -820,7 +821,7 @@
      @@ -830,12 +831,12 @@ {#if splits.length > 1} {@const avgPace = splits.reduce((a, s) => a + s.pace, 0) / splits.length}
      -

      {t('splits', lang)}

      +

      {t.splits}

      {t('set_header', lang)}{t.set_header}{METRIC_LABELS[metric]}RPE{t('est_1rm', lang)}{t.est_1rm}
      - + @@ -857,13 +858,13 @@ {/if} {:else if isCardio(ex.exerciseId)} {/if} @@ -872,7 +873,7 @@ {#if !editing && session.prs?.length > 0}
      -

      {t('personal_records', lang)}

      +

      {t.personal_records}

      {#each session.prs as pr (pr.exerciseId + pr.type)} {@const exercise = getExerciseById(pr.exerciseId, lang)} @@ -901,7 +902,7 @@ {#if !editing && session.notes}
      -

      {t('notes', lang)}

      +

      {t.notes}

      {session.notes}

      {/if} diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]/+page.svelte index ac078d01..5d0d2712 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]/+page.svelte @@ -25,7 +25,7 @@ import Beef from '@lucide/svelte/icons/beef'; import Droplet from '@lucide/svelte/icons/droplet'; import Wheat from '@lucide/svelte/icons/wheat'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import AddButton from '$lib/components/AddButton.svelte'; import FoodSearch from '$lib/components/fitness/FoodSearch.svelte'; import MacroBreakdown from '$lib/components/fitness/MacroBreakdown.svelte'; @@ -71,6 +71,7 @@ */ const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const s = $derived(fitnessSlugs(lang)); const isEn = $derived(lang === 'en'); @@ -1054,7 +1055,7 @@ /** @param {string} id */ async function deleteEntry(id) { - if (!await confirm(t('delete_entry_confirm', lang))) return; + if (!await confirm(t.delete_entry_confirm)) return; try { const res = await fetch(`/api/fitness/food-log/${id}`, { method: 'DELETE' }); if (res.ok) { @@ -1262,7 +1263,7 @@ - {t('nutrition_title', lang)} — Fitness + {t.nutrition_title} — Fitness {#snippet cmDetailScreen(/** @type {CustomMeal} */ meal, /** @type {(m: CustomMeal, grams?: number | null) => void} */ logFn)} @@ -1270,7 +1271,7 @@
      {meal.name} - {meal.ingredients.length} {t('ingredients', lang)} · {Math.round(preview.totalGrams)}g {isEn ? 'base' : 'Basis'} + {meal.ingredients.length} {t.ingredients} · {Math.round(preview.totalGrams)}g {isEn ? 'base' : 'Basis'}
      @@ -1314,8 +1315,8 @@
      - - + +
      {/snippet} @@ -1323,7 +1324,7 @@ {#snippet favoritesTab(/** @type {(food: FoodSelection) => void} */ logFn)}
      {#if !favTabLoaded} -

      {t('loading', lang)}

      +

      {t.loading}

      {:else if favTabItems.length === 0}

      {isEn ? 'No favorites yet. Tap the heart on foods to add them here.' : 'Noch keine Favoriten. Tippe auf das Herz bei Lebensmitteln.'}

      {:else} @@ -1342,13 +1343,13 @@ bind:value={cmFilter} /> {/if} {#if customMeals.length === 0} -

      {t('no_custom_meals', lang)}

      +

      {t.no_custom_meals}

      {/if} {#each filteredCustomMeals as cm}
      selectCmMeal(cm)} onkeydown={e => e.key === 'Enter' && selectCmMeal(cm)}>
      {cm.name} - {cm.ingredients.length} {t('ingredients', lang)} · {fmtCal(mealTotalCal(cm))} kcal · {Math.round(mealTotalGrams(cm))}g + {cm.ingredients.length} {t.ingredients} · {fmtCal(mealTotalCal(cm))} kcal · {Math.round(mealTotalGrams(cm))}g
      {/each} @@ -1404,13 +1405,13 @@ {displayDate} - {#if isToday}{t('today', lang)}{/if} + {#if isToday}{t.today}{/if} {#if !isToday} - {t('today', lang)} + {t.today} {/if}
      @@ -1482,7 +1483,7 @@ {/if} {#if !hasBmrData} -
      {isEn ? 'Set profile in' : 'Profil unter'} {t('measure_title', lang)}
      +
      {isEn ? 'Set profile in' : 'Profil unter'} {t.measure_title}
      {/if}
      @@ -1490,9 +1491,9 @@
      {#each [ - { value: dayTotals.protein, goal: proteinGoalGrams, label: t('protein', lang), color: 'var(--nord14)', icon: Beef }, - { value: dayTotals.fat, goal: fatGoalGrams, label: t('fat', lang), color: 'var(--nord12)', icon: Droplet }, - { value: dayTotals.carbs, goal: carbGoalGrams, label: t('carbs', lang), color: 'var(--nord9)', icon: Wheat }, + { value: dayTotals.protein, goal: proteinGoalGrams, label: t.protein, color: 'var(--nord14)', icon: Beef }, + { value: dayTotals.fat, goal: fatGoalGrams, label: t.fat, color: 'var(--nord12)', icon: Droplet }, + { value: dayTotals.carbs, goal: carbGoalGrams, label: t.carbs, color: 'var(--nord9)', icon: Wheat }, ] as macro} {@const pct = macro.goal ? macro.value / macro.goal * 100 : 0} {@const over = pct > 100} @@ -1509,7 +1510,7 @@
      {#if macro.goal} - {remaining >= 0 ? `${fmt(remaining)}/${fmt(macro.goal)}g ${t('remaining', lang)}` : `${fmt(-remaining)}g ${t('over', lang)} ${fmt(macro.goal)}g`} + {remaining >= 0 ? `${fmt(remaining)}/${fmt(macro.goal)}g ${t.remaining}` : `${fmt(-remaining)}g ${t.over} ${fmt(macro.goal)}g`} {:else} {fmt(macro.value)}g @@ -1523,7 +1524,7 @@
      {@render microPanel()} @@ -1533,8 +1534,8 @@ {:else}
      -

      {t('set_goal_prompt', lang)}

      - +

      {t.set_goal_prompt}

      +
      {/if} @@ -1542,14 +1543,14 @@ {#if goalCalories && !showGoalEditor} {/if} {#if showGoalEditor}
      -

      {t('daily_goal', lang)}

      +

      {t.daily_goal}

      @@ -1610,14 +1611,14 @@ {isEn ? 'Your TDEE (Total Daily Energy Expenditure) is the calories you burn per day. Set weight, height, and birth year under' : 'Dein TDEE (Gesamtenergieumsatz) sind die Kalorien, die du pro Tag verbrauchst. Gewicht, Größe und Geburtsjahr einstellen unter'} - {t('measure_title', lang)} + {t.measure_title}
      {/if}
      - +
      {#if hasBmrData} @@ -1715,19 +1716,19 @@ kcal
      - {t('protein', lang)} {editMacroRing.prot}% - {t('fat', lang)} {editMacroRing.fat}% - {t('carbs', lang)} {editMacroRing.carb}% + {t.protein} {editMacroRing.prot}% + {t.fat} {editMacroRing.fat}% + {t.carbs} {editMacroRing.carb}%
      {/if}
      - +
      @@ -1735,11 +1736,11 @@
      - +
      - +
      @@ -1747,9 +1748,9 @@
      - +
      @@ -1855,7 +1856,7 @@ {#if goalCalories}
      -

      {t('micro_details', lang)}

      +

      {t.micro_details}

      {@render microPanel()}
      {/if} @@ -1883,17 +1884,17 @@ ondragleave={(ev) => onMealDragLeave(ev, meal)} ondrop={(ev) => onMealDrop(ev, meal)} role="region" - aria-label={t(meal, lang)} + aria-label={t[meal]} >
      -

      {t(meal, lang)}

      +

      {t[meal]}

      {#if mealEntries.length > 0} - {fmtCal(mealCal)} {t('kcal', lang)} + {fmtCal(mealCal)} {t.kcal} {/if}
      @@ -1940,7 +1941,7 @@ onclick={() => { editingMeal = m; }} > - {t(m, lang)} + {t[m]} {/each}
      @@ -1959,7 +1960,7 @@ {/if} -
      @@ -1973,7 +1974,7 @@
      @@ -1998,7 +1999,7 @@ {:else} {/if} @@ -2071,7 +2072,7 @@
      e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
      -

      {t('add_food', lang)}

      +

      {t.add_food}

      @@ -2087,7 +2088,7 @@ onclick={() => fabMealType = meal} > - {t(meal, lang)} + {t[meal]} {/each}
      @@ -2096,7 +2097,7 @@
      diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte index d4ca65a0..a012cfb8 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte @@ -8,13 +8,14 @@ import Droplet from '@lucide/svelte/icons/droplet'; import Wheat from '@lucide/svelte/icons/wheat'; import { toast } from '$lib/js/toast.svelte'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import { NUTRIENT_META } from '$lib/data/dailyReferenceIntake'; import RingGraph from '$lib/components/fitness/RingGraph.svelte'; let { data } = $props(); const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const s = $derived(fitnessSlugs(lang)); const isEn = $derived(lang === 'en'); diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/meals/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/meals/+page.svelte index 5b107a32..06ec9310 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/meals/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/meals/+page.svelte @@ -6,7 +6,7 @@ import Pencil from '@lucide/svelte/icons/pencil'; import UtensilsCrossed from '@lucide/svelte/icons/utensils-crossed'; import X from '@lucide/svelte/icons/x'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import { toast } from '$lib/js/toast.svelte'; import { confirm } from '$lib/js/confirmDialog.svelte'; import FoodSearch from '$lib/components/fitness/FoodSearch.svelte'; @@ -15,6 +15,7 @@ /** @typedef {import('$models/CustomMeal').ICustomMealIngredient} MealIngredient */ const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const s = $derived(fitnessSlugs(lang)); const isEn = $derived(lang === 'en'); @@ -148,7 +149,7 @@ /** @param {Meal} meal */ async function deleteMeal(meal) { - if (!await confirm(t('delete_meal_confirm', lang))) return; + if (!await confirm(t.delete_meal_confirm)) return; try { const res = await fetch(`/api/fitness/custom-meals/${meal._id}`, { method: 'DELETE' }); if (res.ok) { @@ -167,42 +168,42 @@ - {t('custom_meals', lang)} — Fitness + {t.custom_meals} — Fitness
      -

      {t('custom_meals', lang)}

      +

      {t.custom_meals}

      {#if !editing} {/if}
      {#if loading}
      -

      {t('loading', lang)}

      +

      {t.loading}

      {:else if editing}
      -

      {editingId ? t('edit', lang) : t('new_meal', lang)}

      +

      {editingId ? t.edit : t.new_meal}

      - {t('ingredients', lang)} ({ingredients.length}) + {t.ingredients} ({ingredients.length}) {#if ingredients.length > 0}
      {#each ingredients as ing, i} @@ -256,7 +257,7 @@ {/if} {#if sp}{ing.amountGrams}g ·{/if} - {fmt((ing.per100g?.calories ?? 0) * ing.amountGrams / 100)} {t('kcal', lang)} + {fmt((ing.per100g?.calories ?? 0) * ing.amountGrams / 100)} {t.kcal}
      @@ -287,7 +288,7 @@ {#if !showSearch} {:else}
      @@ -295,20 +296,20 @@ onselect={addIngredient} oncancel={() => { showSearch = false; }} showDetailLinks={false} - confirmLabel={t('add_ingredient', lang)} + confirmLabel={t.add_ingredient} />
      {/if}
      - +
      @@ -316,8 +317,8 @@
      -

      {t('no_custom_meals', lang)}

      -

      {t('create_meal_hint', lang)}

      +

      {t.no_custom_meals}

      +

      {t.create_meal_hint}

      {:else} @@ -328,14 +329,14 @@

      {meal.name}

      - {meal.ingredients.length} {t('ingredients', lang)} — {Math.round(mealTotalCal(meal))} {t('kcal', lang)} + {meal.ingredients.length} {t.ingredients} — {Math.round(mealTotalCal(meal))} {t.kcal}
      - -
      diff --git a/src/routes/fitness/[stats=fitnessStats]/+page.svelte b/src/routes/fitness/[stats=fitnessStats]/+page.svelte index 46ad6db4..ddcc9c7b 100644 --- a/src/routes/fitness/[stats=fitnessStats]/+page.svelte +++ b/src/routes/fitness/[stats=fitnessStats]/+page.svelte @@ -18,12 +18,13 @@ import FitnessStreakAura from '$lib/components/fitness/FitnessStreakAura.svelte'; import PeriodTracker from '$lib/components/fitness/PeriodTracker.svelte'; import { onMount } from 'svelte'; - import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, fitnessSlugs, m } from '$lib/js/fitnessI18n'; import { toast } from '$lib/js/toast.svelte'; import StatsRingGraph from '$lib/components/fitness/StatsRingGraph.svelte'; import { BODY_PART_CARDS, bodyPartSlug, bodyPartAccent } from '$lib/js/fitnessBodyParts'; const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik'); const historySlug = $derived(lang === 'en' ? 'history' : 'verlauf'); @@ -226,7 +227,7 @@ const bfChartTitle = $derived.by(() => { const baseline = stats.bfChart?.baseline; - const label = t('body_fat', lang).replace(' %', '').replace(' (%)', ''); + const label = t.body_fat.replace(' %', '').replace(' (%)', ''); if (baseline == null) return label; const suffix = lang === 'en' ? `Δ from ${baseline.toFixed(1)}%` @@ -284,36 +285,36 @@ -{t('stats_title', lang)} - Bocken +{t.stats_title} - Bocken
      -

      {t('stats_title', lang)}

      +

      {t.stats_title}

      {stats.totalWorkouts ?? 0}
      -
      {(stats.totalWorkouts ?? 0) === 1 ? t('workout_singular', lang) : t('workouts_plural', lang)}
      +
      {(stats.totalWorkouts ?? 0) === 1 ? t.workout_singular : t.workouts_plural}
      {stats.totalTonnage ?? 0}t
      -
      {t('lifted', lang)}
      +
      {t.lifted}
      {#if stats.kcalEstimate}
      ~{stats.kcalEstimate.kcal.toLocaleString()}kcal
      -
      {t('burned', lang)}
      +
      {t.burned}
      {#if !hasDemographics} -
      {t('kcal_set_profile', lang)} {t('measure_title', lang)}
      +
      {t.kcal_set_profile} {t.measure_title}
      {/if}
      {/if}
      {stats.totalCardioKm ?? 0}km
      -
      {t('covered', lang)}
      +
      {t.covered}
      @@ -322,18 +323,18 @@
      { if (e.key === 'Escape') goalEditing = false; }} role="dialog" tabindex="-1">
      goalEditing = false} onkeydown={(e) => { if (e.key === 'Escape') goalEditing = false; }} role="presentation">
      -

      {t('weekly_goal', lang)}

      +

      {t.weekly_goal}

      {goalInput}
      - {t('workouts_per_week_goal', lang)} + {t.workouts_per_week_goal}
      - +
      @@ -345,23 +346,23 @@ {:else} -

      {t('no_workout_data', lang)}

      +

      {t.no_workout_data}

      {/if}
      @@ -370,7 +371,7 @@ {#if (stats.weightChart?.data?.length ?? 0) > 1} @@ -390,18 +391,18 @@
      {#if ns.avgProteinPerKg != null} -
      {ns.avgProteinPerKg.toFixed(1)}{t('protein_per_kg_unit', lang)}
      +
      {ns.avgProteinPerKg.toFixed(1)}{t.protein_per_kg_unit}
      {:else}
      {/if} -
      {t('protein', lang)}
      +
      {t.protein}
      {#if ns.avgProteinPerKg != null} - {t('seven_day_avg', lang)} + {t.seven_day_avg} {:else if !ns.trendWeight} - {t('no_weight_data', lang)} + {t.no_weight_data} {:else} - {t('no_nutrition_data', lang)} + {t.no_nutrition_data} {/if}
      @@ -410,13 +411,13 @@
      {#if ns.avgCalorieBalance != null}
      0} class:negative={ns.avgCalorieBalance < 0}> - {ns.avgCalorieBalance > 0 ? '+' : ''}{ns.avgCalorieBalance}{t('calorie_balance_unit', lang)} + {ns.avgCalorieBalance > 0 ? '+' : ''}{ns.avgCalorieBalance}{t.calorie_balance_unit}
      {:else}
      {/if}
      - {t('calorie_balance', lang)} + {t.calorie_balance} {#if showBalanceInfo}
      @@ -433,11 +434,11 @@
      {#if ns.avgCalorieBalance != null} - {t('seven_day_avg', lang)} + {t.seven_day_avg} {:else if !hasDemographics || !ns.trendWeight} {lang === 'en' ? 'Set height, birth year & weight' : 'Größe, Geburtsjahr & Gewicht eintragen'} {:else} - {t('no_nutrition_data', lang)} + {t.no_nutrition_data} {/if}
      @@ -450,7 +451,7 @@
      {/if}
      - {t('diet_adherence', lang)} + {t.diet_adherence} {#if showAdherenceInfo}
      @@ -462,16 +463,16 @@
      {#if ns.adherencePercent != null} - {t('since_start', lang)} ({ns.adherenceDays} {t('days', lang)}) + {t.since_start} ({ns.adherenceDays} {t.days}) {:else} - {t('no_calorie_goal', lang)} + {t.no_calorie_goal} {/if}
      -
      {t('macro_split', lang)} ({t('seven_day_avg', lang)})
      +
      {t.macro_split} ({t.seven_day_avg})
      @@ -483,14 +484,14 @@
      {#if !ns.macroSplit} -
      {t('no_nutrition_data', lang)}
      +
      {t.no_nutrition_data}
      {/if}
      {#each [ - { pct: ns.macroSplit?.protein ?? 0, target: ns.macroTargets?.protein, label: t('protein', lang), color: 'var(--nord14)', fill: '#a3be8c', icon: Beef }, - { pct: ns.macroSplit?.fat ?? 0, target: ns.macroTargets?.fat, label: t('fat', lang), color: 'var(--nord12)', fill: '#d08770', icon: Droplet }, - { pct: ns.macroSplit?.carbs ?? 0, target: ns.macroTargets?.carbs, label: t('carbs', lang), color: 'var(--nord9)', fill: '#81a1c1', icon: Wheat }, + { pct: ns.macroSplit?.protein ?? 0, target: ns.macroTargets?.protein, label: t.protein, color: 'var(--nord14)', fill: '#a3be8c', icon: Beef }, + { pct: ns.macroSplit?.fat ?? 0, target: ns.macroTargets?.fat, label: t.fat, color: 'var(--nord12)', fill: '#d08770', icon: Droplet }, + { pct: ns.macroSplit?.carbs ?? 0, target: ns.macroTargets?.carbs, label: t.carbs, color: 'var(--nord9)', fill: '#81a1c1', icon: Wheat }, ] as macro (macro.color)} {@const MacroIcon = macro.icon}
      @@ -509,7 +510,7 @@
      -

      {t('muscle_balance', lang)}

      +

      {t.muscle_balance}

      {#await data.muscleHeatmap} {:then muscleHeatmap} @@ -522,7 +523,7 @@ {#if cardsWithData.length > 0}
      -

      {t('body_parts', lang)}

      +

      {t.body_parts}

      {#each cardsWithData as card (card.key)} {@const cv = currentValue(card)} @@ -542,7 +543,7 @@ {/if}
      - {t(card.labelKey, lang)} + {t[card.labelKey]} {#if card.paired} {#if cv.left != null && cv.right != null && cv.left === cv.right} {cv.left.toFixed(1)}cm diff --git a/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte b/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte index cc12c520..a3de945f 100644 --- a/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte +++ b/src/routes/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]/+page.svelte @@ -6,13 +6,14 @@ import TrendingUp from '@lucide/svelte/icons/trending-up'; import TrendingDown from '@lucide/svelte/icons/trending-down'; import MinusIcon from '@lucide/svelte/icons/minus'; - import { detectFitnessLang, t } from '$lib/js/fitnessI18n'; + import { detectFitnessLang, m } from '$lib/js/fitnessI18n'; import FitnessChart from '$lib/components/fitness/FitnessChart.svelte'; import { bodyPartAccent } from '$lib/js/fitnessBodyParts'; let { data } = $props(); const lang = $derived(detectFitnessLang(page.url.pathname)); + const t = $derived(m[lang]); const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik'); const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung'); const card = $derived(data.card); @@ -80,7 +81,7 @@ labels: series.dates, datasets: [ { - label: t(card.labelKey, lang), + label: t[card.labelKey], data: series.values, borderColor: '#88C0D0', pointBackgroundColor: '#88C0D0' @@ -148,16 +149,16 @@ const hasData = $derived(series.dates.length > 0); -{t(card.labelKey, lang)} · {lang === 'en' ? 'History' : 'Verlauf'} - Bocken +{t[card.labelKey]} · {lang === 'en' ? 'History' : 'Verlauf'} - Bocken
      - +
      - {t('body_parts', lang)} -

      {t(card.labelKey, lang)}

      + {t.body_parts} +

      {t[card.labelKey]}

      @@ -538,7 +539,7 @@
    3. {exercise?.localName ?? ex.exerciseId} - {ex.sets.length} {ex.sets.length !== 1 ? t('sets', lang) : t('set', lang)}{#if firstWeight != null} · {firstWeight} kg{/if} + {ex.sets.length} {ex.sets.length !== 1 ? t.sets : t.set}{#if firstWeight != null} · {firstWeight} kg{/if}
    4. {/each} @@ -553,14 +554,14 @@ {#if selectedTemplate.mode === 'gps'} Start GPS Workout {:else} - {t('start_workout', lang)} + {t.start_workout} {/if}
      @@ -575,14 +576,14 @@
      {/each} {/if} @@ -706,11 +707,11 @@ {:else} -

      {t('no_intervals', lang)}

      +

      {t.no_intervals}

      {/if} {/if} @@ -1335,7 +1336,7 @@ {/if} @@ -1344,7 +1345,7 @@
      -

      {editingIntervalId ? t('edit_interval', lang) : t('new_interval', lang)}

      +

      {editingIntervalId ? t.edit_interval : t.new_interval}

      @@ -1353,7 +1354,7 @@ @@ -1393,14 +1394,14 @@ class:selected={step.customLabel} type="button" onclick={() => { step.customLabel = true; }} - >{t('custom', lang)} + >{t.custom}
      {#if step.customLabel} {/if} @@ -1418,13 +1419,13 @@ class:active={step.durationType === 'distance'} type="button" onclick={() => { step.durationType = 'distance'; }} - >{t('meters', lang)} + >{t.meters} + >{t.seconds}
      @@ -1436,7 +1437,7 @@
      - {t('group_label', lang)} + {t.group_label} - × {t('repeat_times', lang)} + × {t.repeat_times}
      - + @@ -1469,7 +1470,7 @@ {/each}
      @@ -1491,11 +1492,11 @@
      @@ -1505,7 +1506,7 @@ onclick={saveInterval} disabled={intervalSaving || !intervalEditorName.trim() || intervalEditorSteps.length === 0} > - {intervalSaving ? t('saving', lang) : t('save_interval', lang)} + {intervalSaving ? t.saving : t.save_interval}
      @@ -1522,7 +1523,7 @@ onfocus={() => { nameEditing = true; }} onblur={() => { nameEditing = false; workout.name = nameInput; }} onkeydown={(e) => { if (e.key === 'Enter' && e.target instanceof HTMLElement) e.target.blur(); }} - placeholder={t('workout_name_placeholder', lang)} + placeholder={t.workout_name_placeholder} /> {/snippet} @@ -1535,7 +1536,7 @@ {#if gpsToggling}
      - {t('initializing_gps', lang)} + {t.initializing_gps}
      {/if} @@ -1646,7 +1647,7 @@ syncStatus={sync.status} setsDone={workoutSetsDone} setsTotal={workoutSetsTotal} - addLabel={t('add_exercise', lang)} + addLabel={t.add_exercise} pauseLabel={isEn ? 'Pause' : 'Pause'} resumeLabel={isEn ? 'Resume' : 'Fortsetzen'} removeLabel={isEn ? 'Remove exercise' : 'Übung entfernen'} @@ -1718,7 +1719,7 @@ /> {:else} @@ -1729,9 +1730,9 @@
      - +
      KM{t('pace', lang)}{t.pace} TIME