diff --git a/src/components/insights/mood/mood-better-days.tsx b/src/components/insights/mood/mood-better-days.tsx index 07b507214..55738c513 100644 --- a/src/components/insights/mood/mood-better-days.tsx +++ b/src/components/insights/mood/mood-better-days.tsx @@ -2,7 +2,7 @@ import { ArrowDownRight, ArrowUpRight } from "lucide-react"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { moodTagIcon } from "@/components/mood/mood-tag-icons"; import { cn } from "@/lib/utils"; import type { MoodInfluenceConfidence } from "./mood-tag-influence"; @@ -70,6 +70,7 @@ export function MoodBetterDays({ factors: MoodBetterDayFactor[]; }) { const { t } = useTranslations(); + const nf = useFormatters(); if (factors.length === 0) return null; return ( @@ -108,9 +109,9 @@ export function MoodBetterDays({ // metric factors show the correlation coefficient. const effectText = factor.source === "tag" && factor.delta != null - ? `${factor.delta >= 0 ? "+" : ""}${factor.delta.toFixed(1)}` + ? `${factor.delta >= 0 ? "+" : ""}${nf.number(factor.delta, 1)}` : factor.r != null - ? `r ${factor.r.toFixed(2)}` + ? `r ${nf.number(factor.r, 2)}` : ""; return ( diff --git a/src/components/insights/mood/mood-context-comparison.tsx b/src/components/insights/mood/mood-context-comparison.tsx index 3df6ff9ba..4bcf8a63e 100644 --- a/src/components/insights/mood/mood-context-comparison.tsx +++ b/src/components/insights/mood/mood-context-comparison.tsx @@ -16,7 +16,7 @@ * them; a second threshold in the presentation layer would be a second place * to get the statistics wrong. */ -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { contextValueLabelKey } from "@/lib/mood/context-vocabulary"; import type { ContextComparisonRow } from "@/lib/insights/mood-context-crosstab"; @@ -26,6 +26,7 @@ export function MoodContextComparison({ rows: ContextComparisonRow[]; }) { const { t } = useTranslations(); + const nf = useFormatters(); if (rows.length === 0) return null; return ( @@ -50,8 +51,8 @@ export function MoodContextComparison({ {t("insights.mood.contextComparison.statement", { label, direction, - withAvg: row.withAvg.toFixed(1), - withoutAvg: row.withoutAvg.toFixed(1), + withAvg: nf.number(row.withAvg, 1), + withoutAvg: nf.number(row.withoutAvg, 1), })}

{/* The counts ride with the statement, in the same block a diff --git a/src/components/insights/mood/mood-correlation-cards.tsx b/src/components/insights/mood/mood-correlation-cards.tsx index 804bf52ff..36c07db54 100644 --- a/src/components/insights/mood/mood-correlation-cards.tsx +++ b/src/components/insights/mood/mood-correlation-cards.tsx @@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { TileHeader } from "@/components/insights/tile-header"; import { EmptyState } from "@/components/ui/empty-state"; import { Skeleton } from "@/components/ui/skeleton"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import type { CorrelationResult } from "@/lib/analytics/correlations"; import { MoodExplainerIcon } from "./mood-explainer-icon"; @@ -106,6 +106,7 @@ function MoodCorrelationCard({ className?: string; }) { const { t } = useTranslations(); + const nf = useFormatters(); const hasResult = data.result != null; // Three different silences, and the reader deserves to know which one they @@ -164,7 +165,7 @@ function MoodCorrelationCard({ label={t("insights.mood.correlation.sourceLabel")} detail={t("insights.mood.correlation.source", { n: data.n, - r: data.result.r.toFixed(2), + r: nf.number(data.result.r, 2), })} /> @@ -179,7 +180,7 @@ function MoodCorrelationCard({ label={t("insights.mood.correlation.sourceLabel")} detail={t("insights.mood.correlation.source", { n: data.n, - r: data.result.r.toFixed(2), + r: nf.number(data.result.r, 2), })} /> ) : null} diff --git a/src/components/insights/mood/mood-discovered-relations.tsx b/src/components/insights/mood/mood-discovered-relations.tsx index 53bca5e81..4a9adf235 100644 --- a/src/components/insights/mood/mood-discovered-relations.tsx +++ b/src/components/insights/mood/mood-discovered-relations.tsx @@ -1,6 +1,6 @@ "use client"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { MoodExplainerIcon } from "./mood-explainer-icon"; import { PatternDismissButton, @@ -94,6 +94,7 @@ export function MoodDiscoveredRelations({ pairsTested: number; }) { const { t } = useTranslations(); + const nf = useFormatters(); const dismissal = usePatternDismissalOverrides(); if (pairs.length === 0) return null; @@ -169,8 +170,8 @@ export function MoodDiscoveredRelations({ label={t("insights.mood.discovery.statLabel")} detail={t("insights.mood.discovery.stat", { n: pair.n, - r: pair.r.toFixed(2), - q: pair.qValue.toFixed(3), + r: nf.number(pair.r, 2), + q: nf.number(pair.qValue, 3), })} /> diff --git a/src/components/insights/mood/mood-factor-metric-crosstab.tsx b/src/components/insights/mood/mood-factor-metric-crosstab.tsx index 65d1b5a4e..6cd598af6 100644 --- a/src/components/insights/mood/mood-factor-metric-crosstab.tsx +++ b/src/components/insights/mood/mood-factor-metric-crosstab.tsx @@ -1,6 +1,7 @@ "use client"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; +import type { Formatters } from "@/lib/format-locale"; import { useUnitDisplay } from "@/hooks/use-unit-display"; import { moodTagIcon } from "@/components/mood/mood-tag-icons"; import { cn } from "@/lib/utils"; @@ -84,8 +85,12 @@ const CONFIDENCE_CLASS: Record = { }; /** Whole numbers for steps; one decimal otherwise. */ -function fmt(value: number, display: MoodFactorCrosstabDisplay): string { - return display === "steps" ? Math.round(value).toString() : value.toFixed(1); +function formatValue( + value: number, + display: MoodFactorCrosstabDisplay, + nf: Formatters, +): string { + return display === "steps" ? nf.integer(value) : nf.number(value, 1); } export function MoodFactorMetricCrosstab({ @@ -94,6 +99,7 @@ export function MoodFactorMetricCrosstab({ rows: MoodFactorMetricCrosstabRow[]; }) { const { t } = useTranslations(); + const nf = useFormatters(); const unitDisplay = useUnitDisplay(); const dismissal = usePatternDismissalOverrides(); if (rows.length === 0) return null; @@ -143,7 +149,7 @@ export function MoodFactorMetricCrosstab({ // green/red would imply a health verdict the data doesn't support. // The sign prefix + `data-direction` carry the direction. const up = row.delta >= 0; - const deltaText = `${up ? "+" : ""}${fmt(conv(row.delta), row.display)} ${unit}`; + const deltaText = `${up ? "+" : ""}${formatValue(conv(row.delta), row.display, nf)} ${unit}`; return (
  • Math.max(m, row.count), 0) || 1; return ( @@ -74,7 +75,7 @@ export function MoodStructuredTagBreakdown({ style={{ color: colorForScore(row.avgScore) }} title={t("insights.mood.tagAvgMood")} > - {row.avgScore.toFixed(1)} + {nf.number(row.avgScore, 1)}
  • ); diff --git a/src/components/insights/mood/mood-tag-breakdown.tsx b/src/components/insights/mood/mood-tag-breakdown.tsx index 2e4c3d5bc..53e63793e 100644 --- a/src/components/insights/mood/mood-tag-breakdown.tsx +++ b/src/components/insights/mood/mood-tag-breakdown.tsx @@ -1,6 +1,6 @@ "use client"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; /** * v1.8.5 — tag / trigger breakdown. @@ -30,6 +30,7 @@ function colorForScore(score: number): string { export function MoodTagBreakdown({ tags }: { tags: MoodTagRow[] }) { const { t } = useTranslations(); + const nf = useFormatters(); const maxCount = tags.reduce((m, row) => Math.max(m, row.count), 0) || 1; return ( @@ -61,7 +62,7 @@ export function MoodTagBreakdown({ tags }: { tags: MoodTagRow[] }) { style={{ color: colorForScore(row.avgScore) }} title={t("insights.mood.tagAvgMood")} > - {row.avgScore.toFixed(1)} + {nf.number(row.avgScore, 1)} ))} diff --git a/src/components/insights/mood/mood-tag-influence.tsx b/src/components/insights/mood/mood-tag-influence.tsx index 619c2d699..4db56866a 100644 --- a/src/components/insights/mood/mood-tag-influence.tsx +++ b/src/components/insights/mood/mood-tag-influence.tsx @@ -1,6 +1,6 @@ "use client"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { moodTagIcon } from "@/components/mood/mood-tag-icons"; import { cn } from "@/lib/utils"; @@ -62,6 +62,7 @@ function deltaColor(delta: number): string { export function MoodTagInfluence({ rows }: { rows: MoodTagInfluenceRow[] }) { const { t } = useTranslations(); + const nf = useFormatters(); if (rows.length === 0) return null; return ( @@ -71,7 +72,7 @@ export function MoodTagInfluence({ rows }: { rows: MoodTagInfluenceRow[] }) { const Icon = row.labelKey ? moodTagIcon(row.icon) : null; const label = row.label ?? (row.labelKey ? t(row.labelKey) : row.tag); const up = row.delta >= 0; - const deltaText = `${up ? "+" : ""}${row.delta.toFixed(1)}`; + const deltaText = `${up ? "+" : ""}${nf.number(row.delta, 1)}`; return (
  • {t("insights.mood.influence.detail", { - withAvg: row.withAvg.toFixed(1), - withoutAvg: row.withoutAvg.toFixed(1), + withAvg: nf.number(row.withAvg, 1), + withoutAvg: nf.number(row.withoutAvg, 1), withDays: row.withDays, })}

    diff --git a/src/components/insights/mood/mood-tag-metric-crosstab.tsx b/src/components/insights/mood/mood-tag-metric-crosstab.tsx index 9263fbaee..60d926a38 100644 --- a/src/components/insights/mood/mood-tag-metric-crosstab.tsx +++ b/src/components/insights/mood/mood-tag-metric-crosstab.tsx @@ -1,6 +1,7 @@ "use client"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; +import type { Formatters } from "@/lib/format-locale"; import { moodTagIcon } from "@/components/mood/mood-tag-icons"; import { cn } from "@/lib/utils"; import type { MoodInfluenceConfidence } from "./mood-tag-influence"; @@ -92,8 +93,12 @@ const CONFIDENCE_CLASS: Record = { }; /** One decimal for hours/score, whole numbers for kcal. */ -function fmt(value: number, display: MoodCrosstabDisplay): string { - return display === "kcal" ? Math.round(value).toString() : value.toFixed(1); +function formatValue( + value: number, + display: MoodCrosstabDisplay, + nf: Formatters, +): string { + return display === "kcal" ? nf.integer(value) : nf.number(value, 1); } export function MoodTagMetricCrosstab({ @@ -102,6 +107,7 @@ export function MoodTagMetricCrosstab({ rows: MoodTagMetricCrosstabRow[]; }) { const { t } = useTranslations(); + const nf = useFormatters(); const dismissal = usePatternDismissalOverrides(); if (rows.length === 0) return null; @@ -135,7 +141,7 @@ export function MoodTagMetricCrosstab({ ); const unit = t(UNIT_KEY[row.display]); const up = row.delta >= 0; - const deltaText = `${up ? "+" : ""}${fmt(row.delta, row.display)} ${unit}`; + const deltaText = `${up ? "+" : ""}${formatValue(row.delta, row.display, nf)} ${unit}`; return (
  • ({ bucket: row.bucket, @@ -125,7 +126,7 @@ export function MoodTimeOfDayChart({ return [t("insights.mood.weekdayNoData"), ""]; } return [ - `${payload.avgScore.toFixed(1)} (${payload.count})`, + `${nf.number(payload.avgScore, 1)} (${payload.count})`, t("insights.mood.timeOfDay.title"), ]; }} diff --git a/src/components/insights/mood/mood-weekday-chart.tsx b/src/components/insights/mood/mood-weekday-chart.tsx index a0d69225f..ef53ee877 100644 --- a/src/components/insights/mood/mood-weekday-chart.tsx +++ b/src/components/insights/mood/mood-weekday-chart.tsx @@ -12,7 +12,7 @@ import { YAxis, } from "recharts"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; /** * v1.8.5 — average mood per weekday (Monday-aligned). @@ -50,6 +50,7 @@ function colorForScore(score: number): string { export function MoodWeekdayChart({ weekday }: { weekday: MoodWeekdayRow[] }) { const { t } = useTranslations(); + const nf = useFormatters(); const data = weekday.map((row) => ({ weekday: row.weekday, @@ -134,7 +135,7 @@ export function MoodWeekdayChart({ weekday }: { weekday: MoodWeekdayRow[] }) { return [t("insights.mood.weekdayNoData"), ""]; } return [ - `${payload.avgScore.toFixed(1)} (${payload.count})`, + `${nf.number(payload.avgScore, 1)} (${payload.count})`, t("insights.mood.weekdayTitle"), ]; }}