From 50a23d76e3dc14f23a27125297b8901a6724d18a Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 10 Oct 2025 19:59:05 +0200 Subject: [PATCH] feat(weather-widget): add imperial windspeed option (#4253) --- packages/common/src/number.ts | 5 +++++ packages/old-import/src/widgets/options.ts | 1 + packages/translation/src/lang/en.json | 15 ++++++++++++--- packages/widgets/src/weather/component.tsx | 15 ++++++++++++++- packages/widgets/src/weather/icon.tsx | 13 +++++++++++-- packages/widgets/src/weather/index.ts | 1 + 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/common/src/number.ts b/packages/common/src/number.ts index bb479f34c..3d9f79bd7 100644 --- a/packages/common/src/number.ts +++ b/packages/common/src/number.ts @@ -47,3 +47,8 @@ export const humanFileSize = (size: number, concat = ""): string => { } return "∞"; }; + +const IMPERIAL_MULTIPLIER = 1.609344; + +export const metricToImperial = (metricValue: number) => metricValue / IMPERIAL_MULTIPLIER; +export const imperialToMetric = (imperialValue: number) => imperialValue * IMPERIAL_MULTIPLIER; diff --git a/packages/old-import/src/widgets/options.ts b/packages/old-import/src/widgets/options.ts index fba538522..60c5302f6 100644 --- a/packages/old-import/src/widgets/options.ts +++ b/packages/old-import/src/widgets/options.ts @@ -88,6 +88,7 @@ const optionMapping: OptionMapping = { location: (oldOptions) => oldOptions.location, showCity: (oldOptions) => oldOptions.displayCityName, dateFormat: (oldOptions) => (oldOptions.dateFormat === "hide" ? undefined : oldOptions.dateFormat), + useImperialSpeed: () => undefined, }, iframe: { embedUrl: (oldOptions) => oldOptions.embedUrl, diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index a7706c3e9..ab8721032 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1145,6 +1145,12 @@ "groupNameTaken": "Group name already taken" } } + }, + "unit": { + "speed": { + "kilometersPerHour": "km/h", + "milesPerHour": "mph" + } } }, "section": { @@ -1744,6 +1750,9 @@ "label": "Show current wind speed", "description": "Only on current weather" }, + "useImperialSpeed": { + "label": "Use mph for windspeed" + }, "location": { "label": "Weather location" }, @@ -1762,12 +1771,12 @@ "description": "How the date should look like" } }, - "currentWindSpeed": "{currentWindSpeed} km/h", + "currentWindSpeed": "{currentWindSpeed} {unit}", "dailyForecast": { "sunrise": "Sunrise", "sunset": "Sunset", - "maxWindSpeed": "Max wind speed: {maxWindSpeed} km/h", - "maxWindGusts": "Max wind gusts: {maxWindGusts} km/h" + "maxWindSpeed": "Max wind speed: {maxWindSpeed} {unit}", + "maxWindGusts": "Max wind gusts: {maxWindGusts} {unit}" }, "kind": { "clear": "Clear", diff --git a/packages/widgets/src/weather/component.tsx b/packages/widgets/src/weather/component.tsx index 300244bd0..f81736c27 100644 --- a/packages/widgets/src/weather/component.tsx +++ b/packages/widgets/src/weather/component.tsx @@ -7,6 +7,7 @@ import dayjs from "dayjs"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; +import { metricToImperial } from "@homarr/common"; import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; @@ -52,6 +53,7 @@ interface WeatherProps extends Pick, "options"> const DailyWeather = ({ options, weather }: WeatherProps) => { const t = useScopedI18n("widget.weather"); + const tCommon = useScopedI18n("common"); return ( <> @@ -78,7 +80,17 @@ const DailyWeather = ({ options, weather }: WeatherProps) => { {options.showCurrentWindSpeed && ( - {t("currentWindSpeed", { currentWindSpeed: String(weather.current.windspeed) })} + + {t("currentWindSpeed", { + currentWindSpeed: (options.useImperialSpeed + ? metricToImperial(weather.current.windspeed) + : weather.current.windspeed + ).toFixed(1), + unit: options.useImperialSpeed + ? tCommon("unit.speed.milesPerHour") + : tCommon("unit.speed.kilometersPerHour"), + })} + )} @@ -180,6 +192,7 @@ function Forecast({ weather, options }: WeatherProps) { { interface WeatherDescriptionProps { weatherOnly?: boolean; + useImperialSpeed?: boolean; dateFormat?: WidgetProps<"weather">["options"]["dateFormat"]; time?: string; weatherCode: number; @@ -66,6 +68,7 @@ interface WeatherDescriptionProps { */ export const WeatherDescription = ({ weatherOnly, + useImperialSpeed, dateFormat, time, weatherCode, @@ -96,12 +99,18 @@ export const WeatherDescription = ({ }>{`${t("dailyForecast.sunset")}: ${sunset}`} {maxWindSpeed !== undefined && ( }> - {t("dailyForecast.maxWindSpeed", { maxWindSpeed: String(maxWindSpeed) })} + {t("dailyForecast.maxWindSpeed", { + maxWindSpeed: (useImperialSpeed ? metricToImperial(maxWindSpeed) : maxWindSpeed).toFixed(1), + unit: useImperialSpeed ? tCommon("unit.speed.milesPerHour") : tCommon("unit.speed.kilometersPerHour"), + })} )} {maxWindGusts !== undefined && ( }> - {t("dailyForecast.maxWindGusts", { maxWindGusts: String(maxWindGusts) })} + {t("dailyForecast.maxWindGusts", { + maxWindGusts: (useImperialSpeed ? metricToImperial(maxWindGusts) : maxWindGusts).toFixed(1), + unit: useImperialSpeed ? tCommon("unit.speed.milesPerHour") : tCommon("unit.speed.kilometersPerHour"), + })} )} diff --git a/packages/widgets/src/weather/index.ts b/packages/widgets/src/weather/index.ts index c8eb3f108..6c30b6df8 100644 --- a/packages/widgets/src/weather/index.ts +++ b/packages/widgets/src/weather/index.ts @@ -13,6 +13,7 @@ export const { definition, componentLoader } = createWidgetDefinition("weather", isFormatFahrenheit: factory.switch(), disableTemperatureDecimals: factory.switch(), showCurrentWindSpeed: factory.switch({ withDescription: true }), + useImperialSpeed: factory.switch(), location: factory.location({ defaultValue: { name: "Paris",