more i18n

This commit is contained in:
Graham McIntire 2025-07-06 10:28:27 -05:00
parent cba86fb082
commit 524c8208c3
No known key found for this signature in database
8 changed files with 331 additions and 62 deletions

View file

@ -9,4 +9,47 @@ defmodule Aprsme.Convert do
@spec speed(number(), :knots, :mph) :: float()
def speed(value, :knots, :mph), do: Float.round(value * 1.15077945, 2)
# Weather unit conversions
@doc """
Converts Fahrenheit to Celsius
"""
def f_to_c(fahrenheit) when is_number(fahrenheit) do
(fahrenheit - 32) * 5 / 9
end
@doc """
Converts Celsius to Fahrenheit
"""
def c_to_f(celsius) when is_number(celsius) do
celsius * 9 / 5 + 32
end
@doc """
Converts mph to km/h
"""
def mph_to_kph(mph) when is_number(mph) do
mph * 1.60934
end
@doc """
Converts km/h to mph
"""
def kph_to_mph(kph) when is_number(kph) do
kph / 1.60934
end
@doc """
Converts inches to mm
"""
def inches_to_mm(inches) when is_number(inches) do
inches * 25.4
end
@doc """
Converts mm to inches
"""
def mm_to_inches(mm) when is_number(mm) do
mm / 25.4
end
end

View file

@ -329,7 +329,8 @@ defmodule AprsmeWeb.MapLive.CallsignView do
socket = assign(socket, visible_packets: updated_visible_packets)
# Live packets are always the most recent for their callsign
packet_data = PacketUtils.build_packet_data(packet, true)
locale = Map.get(socket.assigns, :locale, "en")
packet_data = PacketUtils.build_packet_data(packet, true, locale)
socket =
if packet_data,
@ -355,7 +356,9 @@ defmodule AprsmeWeb.MapLive.CallsignView do
end
defp handle_replay_packet(packet, socket) do
case PacketUtils.build_packet_data(packet) do
locale = Map.get(socket.assigns, :locale, "en")
case PacketUtils.build_packet_data(packet, false, locale) do
nil -> handle_invalid_replay_packet(socket)
packet_data -> handle_valid_replay_packet(packet, packet_data, socket)
end
@ -814,7 +817,8 @@ defmodule AprsmeWeb.MapLive.CallsignView do
end
defp build_single_historical_packet_data(packet, index, callsign) do
case PacketUtils.build_packet_data(packet) do
# Note: We don't have access to locale here, so we'll use default "en"
case PacketUtils.build_packet_data(packet, false, "en") do
nil ->
nil
@ -847,7 +851,8 @@ defmodule AprsmeWeb.MapLive.CallsignView do
defp push_latest_marker(socket, latest_packet) do
if latest_packet do
packet_data = PacketUtils.build_packet_data(latest_packet, true)
locale = Map.get(socket.assigns, :locale, "en")
packet_data = PacketUtils.build_packet_data(latest_packet, true, locale)
if packet_data,
do: {push_event(socket, "new_packet", packet_data), true},
@ -992,7 +997,8 @@ defmodule AprsmeWeb.MapLive.CallsignView do
if Map.get(socket.assigns, :latest_marker_pushed, false) do
socket
else
packet_data = PacketUtils.build_packet_data(packet, true)
locale = Map.get(socket.assigns, :locale, "en")
packet_data = PacketUtils.build_packet_data(packet, true, locale)
if packet_data, do: push_event(socket, "new_packet", packet_data), else: socket
end
end

View file

@ -144,9 +144,11 @@ defmodule AprsmeWeb.MapLive.Index do
end)
|> Map.new()
locale = Map.get(socket.assigns, :locale, "en")
visible_packets_list =
filtered_packets
|> Enum.map(fn {_callsign, packet} -> PacketUtils.build_packet_data(packet) end)
|> Enum.map(fn {_callsign, packet} -> PacketUtils.build_packet_data(packet, false, locale) end)
|> Enum.filter(& &1)
socket = assign(socket, visible_packets: filtered_packets)
@ -398,7 +400,8 @@ defmodule AprsmeWeb.MapLive.Index do
new_visible_packets = Map.put(socket.assigns.visible_packets, callsign_key, packet)
# Live packets are always the most recent for their callsign
marker_data = PacketUtils.build_packet_data(packet, true)
locale = Map.get(socket.assigns, :locale, "en")
marker_data = PacketUtils.build_packet_data(packet, true, locale)
socket =
if marker_data do
@ -1046,7 +1049,8 @@ defmodule AprsmeWeb.MapLive.Index do
# Only show as red dot if it's not the most recent position
is_most_recent = index == 0
packet_data = PacketUtils.build_packet_data(packet, is_most_recent)
# Note: We don't have access to locale here, so we'll use default "en"
packet_data = PacketUtils.build_packet_data(packet, is_most_recent, "en")
if packet_data do
packet_data

View file

@ -137,24 +137,25 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
Map.get(data_extended, to_string(key)) || "N/A"
end
def build_packet_data(packet, is_most_recent_for_callsign) when is_boolean(is_most_recent_for_callsign) do
def build_packet_data(packet, is_most_recent_for_callsign, locale \\ "en")
when is_boolean(is_most_recent_for_callsign) do
{lat, lon, data_extended} = MapHelpers.get_coordinates(packet)
callsign = generate_callsign(packet)
if lat && lon && callsign != "" && callsign != nil do
packet_data = build_packet_map(packet, lat, lon, data_extended)
packet_data = build_packet_map(packet, lat, lon, data_extended, locale)
Map.put(packet_data, "is_most_recent_for_callsign", is_most_recent_for_callsign)
end
end
def build_packet_data(packet) do
build_packet_data(packet, false)
build_packet_data(packet, false, "en")
end
defp build_packet_map(packet, lat, lon, data_extended) do
defp build_packet_map(packet, lat, lon, data_extended, locale) do
data_extended = data_extended || %{}
packet_info = extract_packet_info(packet, data_extended)
popup = build_popup_content(packet, packet_info, lat, lon)
popup = build_popup_content(packet, packet_info, lat, lon, locale)
build_packet_result(packet, packet_info, lat, lon, popup)
end
@ -171,9 +172,9 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
}
end
defp build_popup_content(packet, packet_info, lat, lon) do
defp build_popup_content(packet, packet_info, lat, lon, locale) do
if packet_info.is_weather_packet do
build_weather_popup_html(packet, packet_info.callsign)
build_weather_popup_html(packet, packet_info.callsign, locale)
else
build_standard_popup_html(packet_info, lat, lon)
end
@ -197,11 +198,27 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
|> IO.iodata_to_binary()
end
defp build_weather_popup_html(packet, sender) do
defp build_weather_popup_html(packet, sender, locale) do
received_at = get_received_at(packet)
timestamp_dt = TimeHelpers.to_datetime(received_at)
cache_buster = System.system_time(:millisecond)
# Get weather data and convert units based on locale
temperature_raw = get_weather_field(packet, :temperature)
wind_speed_raw = get_weather_field(packet, :wind_speed)
wind_gust_raw = get_weather_field(packet, :wind_gust)
rain_1h_raw = get_weather_field(packet, :rain_1h)
rain_24h_raw = get_weather_field(packet, :rain_24h)
rain_since_midnight_raw = get_weather_field(packet, :rain_since_midnight)
# Convert units if the values are numbers
{temperature, temp_unit} = convert_temperature(temperature_raw, locale)
{wind_speed, wind_unit} = convert_wind_speed(wind_speed_raw, locale)
{wind_gust, gust_unit} = convert_wind_speed(wind_gust_raw, locale)
{rain_1h, rain_unit} = convert_rain(rain_1h_raw, locale)
{rain_24h, rain_unit} = convert_rain(rain_24h_raw, locale)
{rain_since_midnight, rain_unit} = convert_rain(rain_since_midnight_raw, locale)
%{
callsign: sender,
comment: nil,
@ -209,15 +226,19 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
cache_buster: cache_buster,
weather: true,
weather_link: true,
temperature: get_weather_field(packet, :temperature),
temperature: temperature,
temp_unit: temp_unit,
humidity: get_weather_field(packet, :humidity),
wind_direction: get_weather_field(packet, :wind_direction),
wind_speed: get_weather_field(packet, :wind_speed),
wind_gust: get_weather_field(packet, :wind_gust),
wind_speed: wind_speed,
wind_unit: wind_unit,
wind_gust: wind_gust,
gust_unit: gust_unit,
pressure: get_weather_field(packet, :pressure),
rain_1h: get_weather_field(packet, :rain_1h),
rain_24h: get_weather_field(packet, :rain_24h),
rain_since_midnight: get_weather_field(packet, :rain_since_midnight)
rain_1h: rain_1h,
rain_24h: rain_24h,
rain_since_midnight: rain_since_midnight,
rain_unit: rain_unit
}
|> PopupComponent.popup()
|> Safe.to_iodata()
@ -259,4 +280,23 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
true -> nil
end
end
# Weather unit conversion helpers
defp convert_temperature(value, locale) when is_number(value) do
AprsmeWeb.WeatherUnits.format_temperature(value, locale)
end
defp convert_temperature(value, _locale), do: {value, "°F"}
defp convert_wind_speed(value, locale) when is_number(value) do
AprsmeWeb.WeatherUnits.format_wind_speed(value, locale)
end
defp convert_wind_speed(value, _locale), do: {value, "mph"}
defp convert_rain(value, locale) when is_number(value) do
AprsmeWeb.WeatherUnits.format_rain(value, locale)
end
defp convert_rain(value, _locale), do: {value, "in"}
end

View file

@ -5,6 +5,8 @@ defmodule AprsmeWeb.MapLive.PopupComponent do
import AprsmeWeb.TimeHelpers
import Gettext
alias AprsmeWeb.WeatherUnits
def popup(assigns) do
~H"""
<div class="aprs-popup" data-timestamp={@cache_buster}>
@ -36,16 +38,16 @@ defmodule AprsmeWeb.MapLive.PopupComponent do
<% end %>
<%= if @weather do %>
<hr style="margin-top: 4px; margin-bottom: 4px;" />
{gettext(AprsmeWeb.Gettext, "Temperature:")} {@temperature}°F<br />
{gettext(AprsmeWeb.Gettext, "Temperature:")} {@temperature}{@temp_unit}<br />
{gettext(AprsmeWeb.Gettext, "Humidity:")} {@humidity}%<br />
{gettext(AprsmeWeb.Gettext, "Wind:")} {@wind_direction}° {gettext(AprsmeWeb.Gettext, "at")} {@wind_speed} mph, {gettext(
{gettext(AprsmeWeb.Gettext, "Wind:")} {@wind_direction}° {gettext(AprsmeWeb.Gettext, "at")} {@wind_speed} {@wind_unit}, {gettext(
AprsmeWeb.Gettext,
"gusts to"
)} {@wind_gust} mph<br />
)} {@wind_gust} {@gust_unit}<br />
{gettext(AprsmeWeb.Gettext, "Pressure:")} {@pressure} hPa<br />
{gettext(AprsmeWeb.Gettext, "Rain (1h):")} {@rain_1h} in.<br />
{gettext(AprsmeWeb.Gettext, "Rain (24h):")} {@rain_24h} in.<br />
{gettext(AprsmeWeb.Gettext, "Rain (since midnight):")} {@rain_since_midnight} in.<br />
{gettext(AprsmeWeb.Gettext, "Rain (1h):")} {@rain_1h} {@rain_unit}<br />
{gettext(AprsmeWeb.Gettext, "Rain (24h):")} {@rain_24h} {@rain_unit}<br />
{gettext(AprsmeWeb.Gettext, "Rain (since midnight):")} {@rain_since_midnight} {@rain_unit}<br />
<% end %>
</div>
"""

View file

@ -7,6 +7,7 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
alias Aprsme.Packets
alias AprsmeWeb.MapLive.PacketUtils
alias AprsmeWeb.WeatherUnits
@impl true
def mount(%{"callsign" => callsign}, _session, socket) do
@ -22,6 +23,10 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
{start_time, end_time} = default_time_range()
weather_history = get_weather_history(normalized_callsign, start_time, end_time)
# Get locale from socket assigns (set by LocaleHook)
locale = Map.get(socket.assigns, :locale, "en")
unit_labels = WeatherUnits.unit_labels(locale)
weather_history_json =
weather_history
|> Enum.map(fn pkt ->
@ -30,36 +35,44 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
calc_dew_point(pkt.temperature, pkt.humidity)
end
# Convert units based on locale
{temp_value, _} = WeatherUnits.format_temperature(pkt.temperature, locale)
{dew_value, _} = if dew_point, do: WeatherUnits.format_temperature(dew_point, locale), else: {nil, nil}
{wind_speed_value, _} = WeatherUnits.format_wind_speed(pkt.wind_speed, locale)
{rain_1h_value, _} = WeatherUnits.format_rain(pkt.rain_1h, locale)
{rain_24h_value, _} = WeatherUnits.format_rain(pkt.rain_24h, locale)
{rain_since_midnight_value, _} = WeatherUnits.format_rain(pkt.rain_since_midnight, locale)
%{
timestamp: pkt.received_at,
temperature: pkt.temperature,
dew_point: dew_point,
temperature: temp_value,
dew_point: dew_value,
humidity: pkt.humidity,
pressure: pkt.pressure,
wind_direction: pkt.wind_direction,
wind_speed: pkt.wind_speed,
rain_1h: pkt.rain_1h,
rain_24h: pkt.rain_24h,
rain_since_midnight: pkt.rain_since_midnight,
wind_speed: wind_speed_value,
rain_1h: rain_1h_value,
rain_24h: rain_24h_value,
rain_since_midnight: rain_since_midnight_value,
luminosity: pkt.luminosity
}
end)
|> Jason.encode!()
# Add chart labels for translation
# Add chart labels for translation with locale-aware units
chart_labels = %{
temp_title: gettext("Temperature & Dew Point (°F)"),
temp_label: gettext("Temperature (°F)"),
dew_label: gettext("Dew Point (°F)"),
temp_title: gettext("Temperature & Dew Point (%{unit})", unit: unit_labels.temperature),
temp_label: gettext("Temperature (%{unit})", unit: unit_labels.temperature),
dew_label: gettext("Dew Point (%{unit})", unit: unit_labels.temperature),
humidity_title: gettext("Humidity (%)"),
humidity_label: gettext("Humidity (%)"),
pressure_title: gettext("Pressure (mb)"),
pressure_label: gettext("Pressure (mb)"),
wind_title: gettext("Wind (mph)"),
wind_label: gettext("Wind Speed (mph)"),
gust_label: gettext("Wind Gust (mph)"),
wind_title: gettext("Wind (%{unit})", unit: unit_labels.wind_speed),
wind_label: gettext("Wind Speed (%{unit})", unit: unit_labels.wind_speed),
gust_label: gettext("Wind Gust (%{unit})", unit: unit_labels.wind_speed),
time: gettext("Time"),
degf: gettext("°F"),
degf: unit_labels.temperature,
percent: gettext("%"),
mb: gettext("mb")
}
@ -83,6 +96,9 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
{start_time, end_time} = default_time_range()
weather_history = get_weather_history(socket.assigns.callsign, start_time, end_time)
# Get locale from socket assigns
locale = Map.get(socket.assigns, :locale, "en")
weather_history_json =
weather_history
|> Enum.map(fn pkt ->
@ -91,17 +107,25 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
calc_dew_point(pkt.temperature, pkt.humidity)
end
# Convert units based on locale
{temp_value, _} = WeatherUnits.format_temperature(pkt.temperature, locale)
{dew_value, _} = if dew_point, do: WeatherUnits.format_temperature(dew_point, locale), else: {nil, nil}
{wind_speed_value, _} = WeatherUnits.format_wind_speed(pkt.wind_speed, locale)
{rain_1h_value, _} = WeatherUnits.format_rain(pkt.rain_1h, locale)
{rain_24h_value, _} = WeatherUnits.format_rain(pkt.rain_24h, locale)
{rain_since_midnight_value, _} = WeatherUnits.format_rain(pkt.rain_since_midnight, locale)
%{
timestamp: pkt.received_at,
temperature: pkt.temperature,
dew_point: dew_point,
temperature: temp_value,
dew_point: dew_value,
humidity: pkt.humidity,
pressure: pkt.pressure,
wind_direction: pkt.wind_direction,
wind_speed: pkt.wind_speed,
rain_1h: pkt.rain_1h,
rain_24h: pkt.rain_24h,
rain_since_midnight: pkt.rain_since_midnight,
wind_speed: wind_speed_value,
rain_1h: rain_1h_value,
rain_24h: rain_24h_value,
rain_since_midnight: rain_since_midnight_value,
luminosity: pkt.luminosity
}
end)
@ -128,6 +152,9 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
{start_time, end_time} = default_time_range()
weather_history = get_weather_history(socket.assigns.callsign, start_time, end_time)
# Get locale from socket assigns
locale = Map.get(socket.assigns, :locale, "en")
weather_history_json =
weather_history
|> Enum.map(fn pkt ->
@ -136,17 +163,25 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
calc_dew_point(pkt.temperature, pkt.humidity)
end
# Convert units based on locale
{temp_value, _} = WeatherUnits.format_temperature(pkt.temperature, locale)
{dew_value, _} = if dew_point, do: WeatherUnits.format_temperature(dew_point, locale), else: {nil, nil}
{wind_speed_value, _} = WeatherUnits.format_wind_speed(pkt.wind_speed, locale)
{rain_1h_value, _} = WeatherUnits.format_rain(pkt.rain_1h, locale)
{rain_24h_value, _} = WeatherUnits.format_rain(pkt.rain_24h, locale)
{rain_since_midnight_value, _} = WeatherUnits.format_rain(pkt.rain_since_midnight, locale)
%{
timestamp: pkt.received_at,
temperature: pkt.temperature,
dew_point: dew_point,
temperature: temp_value,
dew_point: dew_value,
humidity: pkt.humidity,
pressure: pkt.pressure,
wind_direction: pkt.wind_direction,
wind_speed: pkt.wind_speed,
rain_1h: pkt.rain_1h,
rain_24h: pkt.rain_24h,
rain_since_midnight: pkt.rain_since_midnight,
wind_speed: wind_speed_value,
rain_1h: rain_1h_value,
rain_24h: rain_24h_value,
rain_since_midnight: rain_since_midnight_value,
luminosity: pkt.luminosity
}
end)
@ -220,4 +255,44 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
_ -> value
end
end
@doc """
Formats weather values with appropriate units based on locale.
"""
def format_weather_value(packet, key, locale) do
value = PacketUtils.get_weather_field(packet, key)
case {key, value} do
{:temperature, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_temperature(value, locale)
"#{converted_value}#{unit}"
{:wind_speed, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_wind_speed(value, locale)
"#{converted_value} #{unit}"
{:wind_gust, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_wind_speed(value, locale)
"#{converted_value} #{unit}"
{:rain_1h, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_rain(value, locale)
"#{converted_value} #{unit}"
{:rain_24h, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_rain(value, locale)
"#{converted_value} #{unit}"
{:rain_since_midnight, value} when is_number(value) ->
{converted_value, unit} = WeatherUnits.format_rain(value, locale)
"#{converted_value} #{unit}"
_ ->
case value do
"N/A" -> "N/A"
nil -> "N/A"
_ -> "#{value}"
end
end
end
end

View file

@ -96,7 +96,7 @@
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Temperature")}</dt>
<dd class="mt-1 text-sm font-semibold">
{PacketUtils.get_weather_field(@weather_packet, :temperature)}°F
{format_weather_value(@weather_packet, :temperature, @locale)}
</dd>
</div>
<div>
@ -108,16 +108,17 @@
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Wind")}</dt>
<dd class="mt-1 text-sm font-semibold">
{PacketUtils.get_weather_field(@weather_packet, :wind_direction)}° @ {PacketUtils.get_weather_field(
{PacketUtils.get_weather_field(@weather_packet, :wind_direction)}° @ {format_weather_value(
@weather_packet,
:wind_speed
)} mph
:wind_speed,
@locale
)}
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Gusts")}</dt>
<dd class="mt-1 text-sm font-semibold">
{PacketUtils.get_weather_field(@weather_packet, :wind_gust)} mph
{format_weather_value(@weather_packet, :wind_gust, @locale)}
</dd>
</div>
<div>
@ -129,19 +130,19 @@
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Rain (1h)")}</dt>
<dd class="mt-1 text-sm font-semibold">
{get_weather_field_zero(@weather_packet, :rain_1h)} in
{format_weather_value(@weather_packet, :rain_1h, @locale)}
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Rain (24h)")}</dt>
<dd class="mt-1 text-sm font-semibold">
{get_weather_field_zero(@weather_packet, :rain_24h)} in
{format_weather_value(@weather_packet, :rain_24h, @locale)}
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">{gettext("Rain (since midnight)")}</dt>
<dd class="mt-1 text-sm font-semibold">
{get_weather_field_zero(@weather_packet, :rain_since_midnight)} in
{format_weather_value(@weather_packet, :rain_since_midnight, @locale)}
</dd>
</div>
<div class="col-span-2">

View file

@ -0,0 +1,98 @@
defmodule AprsmeWeb.WeatherUnits do
@moduledoc """
Handles weather unit conversions and formatting based on locale.
Supports imperial (US) and metric (international) units.
"""
alias Aprsme.Convert
@doc """
Determines the unit system based on locale.
Returns :imperial for US, :metric for most other countries.
"""
def unit_system(locale) when is_binary(locale) do
case locale do
# Default to imperial for English
"en" -> :imperial
# Spanish-speaking countries mostly use metric
"es" -> :metric
# Germany uses metric
"de" -> :metric
# France uses metric
"fr" -> :metric
# Default to metric for unknown locales
_ -> :metric
end
end
@doc """
Converts temperature from Fahrenheit to Celsius if needed.
Returns the temperature in the appropriate unit for the locale.
"""
def format_temperature(temp, locale) when is_number(temp) do
case unit_system(locale) do
:imperial -> {temp, "°F"}
:metric -> {Convert.f_to_c(temp), "°C"}
end
end
def format_temperature(temp, _locale), do: {temp, "°F"}
@doc """
Converts wind speed from mph to km/h if needed.
Returns the wind speed in the appropriate unit for the locale.
"""
def format_wind_speed(speed, locale) when is_number(speed) do
case unit_system(locale) do
:imperial -> {speed, "mph"}
:metric -> {Convert.mph_to_kph(speed), "km/h"}
end
end
def format_wind_speed(speed, _locale), do: {speed, "mph"}
@doc """
Converts rain from inches to mm if needed.
Returns the rain amount in the appropriate unit for the locale.
"""
def format_rain(rain, locale) when is_number(rain) do
case unit_system(locale) do
:imperial -> {rain, "in"}
:metric -> {Convert.inches_to_mm(rain), "mm"}
end
end
def format_rain(rain, _locale), do: {rain, "in"}
@doc """
Formats pressure (keeps hPa for all locales as it's standard).
"""
def format_pressure(pressure, _locale) when is_number(pressure) do
{pressure, "hPa"}
end
def format_pressure(pressure, _locale), do: {pressure, "hPa"}
@doc """
Gets the appropriate unit labels for the locale.
"""
def unit_labels(locale) do
case unit_system(locale) do
:imperial ->
%{
temperature: "°F",
wind_speed: "mph",
rain: "in",
pressure: "hPa"
}
:metric ->
%{
temperature: "°C",
wind_speed: "km/h",
rain: "mm",
pressure: "hPa"
}
end
end
end