Let temperature formatting ship without dashboard bulk

Temperature formatting was embedded in the large dashboard utility module, so small scan-terminal views had to import the heavy utility surface for simple labels. This moves the pure temperature helpers into a lightweight module while keeping dashboard-utils re-exports for compatibility.

Constraint: Preserve existing temperature text output and all dashboard-utils import compatibility.

Rejected: Split chart, pace, and model helpers in the same pass | those helpers have wider coupling and should move one boundary at a time.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Tested: TypeScript diagnostics for temperature-utils, dashboard-utils, and OpportunityTable

Tested: npm run build

Not-tested: Bundle analyzer size comparison.
This commit is contained in:
2569718930@qq.com
2026-04-28 11:32:03 +08:00
parent 8d05812a7e
commit be41becb30
8 changed files with 58 additions and 44 deletions
+11 -36
View File
@@ -6,6 +6,17 @@ import {
NearbyStation,
} from "@/lib/dashboard-types";
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
import {
formatTemperatureValue,
normalizeTemperatureLabel,
normalizeTemperatureSymbol,
} from "@/lib/temperature-utils";
export {
formatTemperatureValue,
normalizeTemperatureLabel,
normalizeTemperatureSymbol,
} from "@/lib/temperature-utils";
const METAR_WX_MAP: Record<
string,
@@ -45,42 +56,6 @@ function containsCjk(text: string) {
return /[\u3400-\u9fff]/.test(text);
}
export function normalizeTemperatureSymbol(value?: string | null) {
return String(value || "").toUpperCase().includes("F") ? "°F" : "°C";
}
export function formatTemperatureValue(
value: number | null | undefined,
symbol?: string | null,
options?: { signed?: boolean; digits?: number },
) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return "--";
const digits = options?.digits ?? 0;
const sign = options?.signed && numeric >= 0 ? "+" : "";
return `${sign}${numeric.toFixed(digits)}${normalizeTemperatureSymbol(symbol)}`;
}
export function normalizeTemperatureLabel(
label?: string | null,
fallbackSymbol?: string | null,
) {
const raw = String(label || "").trim();
if (!raw) return "";
const normalizedSymbol = normalizeTemperatureSymbol(fallbackSymbol);
let next = raw.replace(/℃/gi, "°C");
next = next.replace(/°?([CF])\b/gi, (_, unit: string) => `°${unit.toUpperCase()}`);
if (!/[°][CF]/.test(next) && /\d/.test(next)) {
next = `${next}${normalizedSymbol}`;
}
if (normalizedSymbol === "°F") {
next = next.replace(/°C/g, "°F");
} else {
next = next.replace(/°F/g, "°C");
}
return next;
}
function getLocalizedDynamicCommentary(
detail: CityDetail,
locale: Locale,
+35
View File
@@ -0,0 +1,35 @@
export function normalizeTemperatureSymbol(value?: string | null) {
return String(value || "").toUpperCase().includes("F") ? "°F" : "°C";
}
export function formatTemperatureValue(
value: number | null | undefined,
symbol?: string | null,
options?: { signed?: boolean; digits?: number },
) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return "--";
const digits = options?.digits ?? 0;
const sign = options?.signed && numeric >= 0 ? "+" : "";
return `${sign}${numeric.toFixed(digits)}${normalizeTemperatureSymbol(symbol)}`;
}
export function normalizeTemperatureLabel(
label?: string | null,
fallbackSymbol?: string | null,
) {
const raw = String(label || "").trim();
if (!raw) return "";
const normalizedSymbol = normalizeTemperatureSymbol(fallbackSymbol);
let next = raw.replace(/℃/gi, "°C");
next = next.replace(/°?([CF])\b/gi, (_, unit: string) => `°${unit.toUpperCase()}`);
if (!/[°][CF]/.test(next) && /\d/.test(next)) {
next = `${next}${normalizedSymbol}`;
}
if (normalizedSymbol === "°F") {
next = next.replace(/°C/g, "°F");
} else {
next = next.replace(/°F/g, "°C");
}
return next;
}