en español

This commit is contained in:
Graham McIntire 2025-07-05 15:51:39 -05:00
parent 1406ae97f0
commit fc449ed5f9
No known key found for this signature in database
31 changed files with 3087 additions and 206 deletions

View file

@ -59,6 +59,16 @@ function unregisterChartInstance(el: HTMLElement) {
}
}
function getLabels(el: HTMLElement) {
const raw = el.getAttribute('data-chart-labels');
if (!raw) return {};
try {
return JSON.parse(raw);
} catch {
return {};
}
}
// All hooks are typed as 'any' for LiveView context compatibility
export const WeatherChartHooks: Record<string, Hook> = {
ChartJSTempChart: {
@ -83,6 +93,7 @@ export const WeatherChartHooks: Record<string, Hook> = {
const self = this as any;
if (self.chart) self.chart.destroy();
const data: WeatherHistoryDatum[] = JSON.parse(self.el.dataset.weatherHistory!);
const labels = getLabels(self.el);
const times = data.map(d => new Date(d.timestamp));
const temps = data.map(d => d.temperature);
const dews = data.map(d => d.dew_point);
@ -94,20 +105,20 @@ export const WeatherChartHooks: Record<string, Hook> = {
data: {
labels: times,
datasets: [
{ label: 'Temperature (°F)', data: temps, borderColor: 'red', backgroundColor: 'rgba(255, 0, 0, 0.1)', tension: 0.1, pointRadius: 0 },
{ label: 'Dew Point (°F)', data: dews, borderColor: 'blue', backgroundColor: 'rgba(0, 0, 255, 0.1)', tension: 0.1, pointRadius: 0 }
{ label: labels.temp_label || 'Temperature (°F)', data: temps, borderColor: 'red', backgroundColor: 'rgba(255, 0, 0, 0.1)', tension: 0.1, pointRadius: 0 },
{ label: labels.dew_label || 'Dew Point (°F)', data: dews, borderColor: 'blue', backgroundColor: 'rgba(0, 0, 255, 0.1)', tension: 0.1, pointRadius: 0 }
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: { display: true, text: 'Temperature & Dew Point (°F)', color: colors.text },
title: { display: true, text: labels.temp_title || 'Temperature & Dew Point (°F)', color: colors.text },
legend: { labels: { color: colors.text } }
},
scales: {
x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } },
y: { title: { display: true, text: '°F', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }
x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: labels.time || 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } },
y: { title: { display: true, text: labels.degf || '°F', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }
}
}
});
@ -135,6 +146,7 @@ export const WeatherChartHooks: Record<string, Hook> = {
const self = this as any;
if (self.chart) self.chart.destroy();
const data: WeatherHistoryDatum[] = JSON.parse(self.el.dataset.weatherHistory!);
const labels = getLabels(self.el);
const times = data.map(d => new Date(d.timestamp));
const humidity = data.map(d => d.humidity);
const colors = getThemeColors();
@ -142,12 +154,12 @@ export const WeatherChartHooks: Record<string, Hook> = {
if (!canvas) return;
self.chart = new window.Chart(canvas, {
type: 'line',
data: { labels: times, datasets: [{ label: 'Humidity (%)', data: humidity, borderColor: 'green', backgroundColor: 'rgba(0, 255, 0, 0.1)', tension: 0.1, pointRadius: 0 }] },
data: { labels: times, datasets: [{ label: labels.humidity_label || 'Humidity (%)', data: humidity, borderColor: 'green', backgroundColor: 'rgba(0, 255, 0, 0.1)', tension: 0.1, pointRadius: 0 }] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { title: { display: true, text: 'Humidity (%)', color: colors.text }, legend: { labels: { color: colors.text } } },
scales: { x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }, y: { title: { display: true, text: '%', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } } }
plugins: { title: { display: true, text: labels.humidity_title || 'Humidity (%)', color: colors.text }, legend: { labels: { color: colors.text } } },
scales: { x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: labels.time || 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }, y: { title: { display: true, text: labels.percent || '%', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } } }
}
});
}
@ -174,6 +186,7 @@ export const WeatherChartHooks: Record<string, Hook> = {
const self = this as any;
if (self.chart) self.chart.destroy();
const data: WeatherHistoryDatum[] = JSON.parse(self.el.dataset.weatherHistory!);
const labels = getLabels(self.el);
const times = data.map(d => new Date(d.timestamp));
const pressure = data.map(d => d.pressure);
const colors = getThemeColors();
@ -181,12 +194,12 @@ export const WeatherChartHooks: Record<string, Hook> = {
if (!canvas) return;
self.chart = new window.Chart(canvas, {
type: 'line',
data: { labels: times, datasets: [{ label: 'Pressure (mb)', data: pressure, borderColor: 'purple', backgroundColor: 'rgba(128, 0, 128, 0.1)', tension: 0.1, pointRadius: 0 }] },
data: { labels: times, datasets: [{ label: labels.pressure_label || 'Pressure (mb)', data: pressure, borderColor: 'purple', backgroundColor: 'rgba(128, 0, 128, 0.1)', tension: 0.1, pointRadius: 0 }] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { title: { display: true, text: 'Pressure (mb)', color: colors.text }, legend: { labels: { color: colors.text } } },
scales: { x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }, y: { title: { display: true, text: 'mb', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } } }
plugins: { title: { display: true, text: labels.pressure_title || 'Pressure (mb)', color: colors.text }, legend: { labels: { color: colors.text } } },
scales: { x: { type: 'time', time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' } }, title: { display: true, text: labels.time || 'Time', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } }, y: { title: { display: true, text: labels.mb || 'mb', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid } } }
}
});
}

View file

@ -26,6 +26,11 @@ config :aprsme, AprsmeWeb.Endpoint,
pubsub_server: Aprsme.PubSub,
live_view: [signing_salt: "ees098qG"]
# Configure Gettext
config :aprsme, AprsmeWeb.Gettext,
locales: ~w(en es),
default_locale: "en"
# Configure Oban for background jobs
config :aprsme, Oban,
repo: Aprsme.Repo,

View file

@ -68,6 +68,8 @@ defmodule Aprsme.Application do
defp migrate do
auto_migrate = Application.get_env(:aprsme, :auto_migrate, true)
do_migrate(auto_migrate)
# Gettext translations are automatically compiled during Mix compilation
rescue
error ->
require Logger

View file

@ -2,6 +2,7 @@ defmodule Aprsme.DeviceIdentification do
@moduledoc """
Handles APRS device identification based on the APRS device identification database.
"""
use Gettext, backend: AprsmeWeb.Gettext
import Ecto.Query
@ -52,7 +53,7 @@ defmodule Aprsme.DeviceIdentification do
"""
@spec identify_device(String.t()) :: String.t()
def identify_device(symbols) do
Enum.find_value(@device_patterns, "Unknown", fn {regex, name} ->
Enum.find_value(@device_patterns, gettext("Unknown"), fn {regex, name} ->
if Regex.match?(regex, symbols) do
name
end

View file

@ -11,6 +11,8 @@ defmodule Aprsme.Release do
deployed_at = init()
Logger.info("Deployment timestamp: #{deployed_at}")
# Gettext translations are automatically compiled during Mix compilation
# Run migrations
{:ok, _, _} = Ecto.Migrator.with_repo(Aprsme.Repo, &Ecto.Migrator.run(&1, :up, all: true))
end

View file

@ -5,6 +5,7 @@ defmodule AprsmeWeb.Api.V1.FallbackController do
See `Phoenix.Controller.action_fallback/1` for more details.
"""
use AprsmeWeb, :controller
use Gettext, backend: AprsmeWeb.Gettext
alias AprsmeWeb.Api.V1.ErrorJSON
@ -53,7 +54,7 @@ defmodule AprsmeWeb.Api.V1.FallbackController do
conn
|> put_status(:request_timeout)
|> put_view(json: ErrorJSON)
|> render(:error, message: "Request timeout")
|> render(:error, message: gettext("Request timeout"))
end
# Default fallback for unexpected errors
@ -64,9 +65,9 @@ defmodule AprsmeWeb.Api.V1.FallbackController do
|> render(:"500")
end
defp format_error_message(:not_found), do: "Resource not found"
defp format_error_message(:unauthorized), do: "Unauthorized access"
defp format_error_message(:forbidden), do: "Access forbidden"
defp format_error_message(:bad_request), do: "Bad request"
defp format_error_message(reason), do: "An error occurred: #{reason}"
defp format_error_message(:not_found), do: gettext("Resource not found")
defp format_error_message(:unauthorized), do: gettext("Unauthorized access")
defp format_error_message(:forbidden), do: gettext("Access forbidden")
defp format_error_message(:bad_request), do: gettext("Bad request")
defp format_error_message(reason), do: gettext("An error occurred: %{reason}", reason: reason)
end

View file

@ -2,6 +2,7 @@ defmodule AprsmeWeb.Api.V1.ChangesetJSON do
@moduledoc """
Renders changeset errors for API v1.
"""
use Gettext, backend: AprsmeWeb.Gettext
@doc """
Renders changeset errors.
@ -9,7 +10,7 @@ defmodule AprsmeWeb.Api.V1.ChangesetJSON do
def render("error.json", %{changeset: changeset}) do
%{
error: %{
message: "Validation failed",
message: gettext("Validation failed"),
code: "validation_error",
details: translate_errors(changeset)
}

View file

@ -2,6 +2,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
@moduledoc """
Renders error responses for API v1.
"""
use Gettext, backend: AprsmeWeb.Gettext
def render("error.json", %{message: message}) do
%{
@ -15,7 +16,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("404.json", _assigns) do
%{
error: %{
message: "Resource not found",
message: gettext("Resource not found"),
code: "not_found"
}
}
@ -24,7 +25,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("500.json", _assigns) do
%{
error: %{
message: "Internal server error",
message: gettext("Internal server error"),
code: "internal_server_error"
}
}
@ -33,7 +34,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("422.json", _assigns) do
%{
error: %{
message: "Unprocessable entity",
message: gettext("Unprocessable entity"),
code: "unprocessable_entity"
}
}
@ -42,7 +43,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("400.json", _assigns) do
%{
error: %{
message: "Bad request",
message: gettext("Bad request"),
code: "bad_request"
}
}
@ -51,7 +52,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("401.json", _assigns) do
%{
error: %{
message: "Unauthorized",
message: gettext("Unauthorized"),
code: "unauthorized"
}
}
@ -60,7 +61,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("403.json", _assigns) do
%{
error: %{
message: "Forbidden",
message: gettext("Forbidden"),
code: "forbidden"
}
}
@ -69,7 +70,7 @@ defmodule AprsmeWeb.Api.V1.ErrorJSON do
def render("408.json", _assigns) do
%{
error: %{
message: "Request timeout",
message: gettext("Request timeout"),
code: "request_timeout"
}
}

View file

@ -1,6 +1,7 @@
defmodule AprsmeWeb.AboutLive do
@moduledoc false
use AprsmeWeb, :live_view
use Gettext, backend: AprsmeWeb.Gettext
@impl true
def mount(_params, _session, socket) do

View file

@ -1,7 +1,7 @@
<div class="container mx-auto max-w-2xl py-12">
<div class="card bg-base-100 shadow-xl mb-8">
<div class="card-body prose max-w-none">
<h2 class="card-title text-3xl">Under heavy construction</h2>
<h2 class="card-title text-3xl">{gettext("Under heavy construction")}</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</p>
@ -9,7 +9,7 @@
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
</p>
<div class="bg-base-200 p-6 rounded-lg mb-8">
<h4 class="text-lg font-semibold mb-3">Built with Modern Technologies</h4>
<h4 class="text-lg font-semibold mb-3">{gettext("Built with Modern Technologies")}</h4>
<div class="flex flex-wrap gap-2">
<span class="badge badge-primary">Elixir</span>
<span class="badge badge-success">Phoenix</span>
@ -18,7 +18,7 @@
<span class="badge badge-error">Tailwind CSS</span>
</div>
</div>
<h3 class="text-2xl font-semibold mb-4">Join Our Community</h3>
<h3 class="text-2xl font-semibold mb-4">{gettext("Join Our Community")}</h3>
<p>
Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.
</p>
@ -32,7 +32,7 @@
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m-6 3l6-3"
/>
</svg>
Explore the Map
{gettext("Explore the Map")}
</a>
</div>
</div>

View file

@ -6,7 +6,7 @@
<div class="mt-6 bg-white shadow-sm rounded-lg overflow-hidden">
<div class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
<span class="text-xs text-gray-500">
Auto-refreshes every 5 seconds
{gettext("Auto-refreshes every 5 seconds")}
</span>
<%= if @loading do %>
<span class="flex items-center text-xs text-blue-600">
@ -32,12 +32,12 @@
>
</path>
</svg>
Updating...
{gettext("Updating...")}
</span>
<% end %>
</div>
<.table id="bad_packets" rows={@bad_packets}>
<:col :let={bad_packet} label="Attempted At">
<:col :let={bad_packet} label={gettext("Attempted At")}>
<span class="text-sm text-gray-900">
{Calendar.strftime(
bad_packet.attempted_at || bad_packet.inserted_at,
@ -45,17 +45,17 @@
)}
</span>
</:col>
<:col :let={bad_packet} label="Error Type">
<:col :let={bad_packet} label={gettext("Error Type")}>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
{bad_packet.error_type || "Unknown"}
{bad_packet.error_type || gettext("Unknown")}
</span>
</:col>
<:col :let={bad_packet} label="Error Message">
<:col :let={bad_packet} label={gettext("Error Message")}>
<span class="text-sm text-red-600 font-mono">
{bad_packet.error_message || "No error message"}
{bad_packet.error_message || gettext("No error message")}
</span>
</:col>
<:col :let={bad_packet} label="Raw Packet">
<:col :let={bad_packet} label={gettext("Raw Packet")}>
<div class="max-w-md">
<div class="text-xs font-mono text-gray-700 break-all">
<%= if is_binary(bad_packet.raw_packet) do %>
@ -81,8 +81,8 @@
/>
</svg>
</div>
<h3 class="mt-2 text-sm font-medium text-gray-900">No bad packets</h3>
<p class="mt-1 text-sm text-gray-500">All packets are parsing successfully!</p>
<h3 class="mt-2 text-sm font-medium text-gray-900">{gettext("No bad packets")}</h3>
<p class="mt-1 text-sm text-gray-500">{gettext("All packets are parsing successfully!")}</p>
</div>
<% end %>
</div>

View file

@ -1,6 +1,7 @@
defmodule AprsmeWeb.InfoLive.Show do
@moduledoc false
use AprsmeWeb, :live_view
use Gettext, backend: AprsmeWeb.Gettext
alias Aprsme.Packets
alias AprsmeWeb.MapLive.PacketUtils
@ -273,38 +274,38 @@ defmodule AprsmeWeb.InfoLive.Show do
# WIDE digipeaters
String.starts_with?(element, "WIDE") ->
case element do
"WIDE1-1" -> "WIDE1-1 (Wide area digipeater, 1 hop)"
"WIDE2-1" -> "WIDE2-1 (Wide area digipeater, 2 hops)"
"WIDE3-1" -> "WIDE3-1 (Wide area digipeater, 3 hops)"
"WIDE4-1" -> "WIDE4-1 (Wide area digipeater, 4 hops)"
"WIDE5-1" -> "WIDE5-1 (Wide area digipeater, 5 hops)"
"WIDE6-1" -> "WIDE6-1 (Wide area digipeater, 6 hops)"
"WIDE7-1" -> "WIDE7-1 (WIDE area digipeater, 7 hops)"
"WIDE1-2" -> "WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)"
"WIDE2-2" -> "WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)"
_ -> "WIDE digipeater (#{element})"
"WIDE1-1" -> gettext("WIDE1-1 (Wide area digipeater, 1 hop)")
"WIDE2-1" -> gettext("WIDE2-1 (Wide area digipeater, 2 hops)")
"WIDE3-1" -> gettext("WIDE3-1 (Wide area digipeater, 3 hops)")
"WIDE4-1" -> gettext("WIDE4-1 (Wide area digipeater, 4 hops)")
"WIDE5-1" -> gettext("WIDE5-1 (Wide area digipeater, 5 hops)")
"WIDE6-1" -> gettext("WIDE6-1 (Wide area digipeater, 6 hops)")
"WIDE7-1" -> gettext("WIDE7-1 (WIDE area digipeater, 7 hops)")
"WIDE1-2" -> gettext("WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)")
"WIDE2-2" -> gettext("WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)")
_ -> gettext("WIDE digipeater (%{element})", element: element)
end
# TRACE digipeaters
String.starts_with?(element, "TRACE") ->
case element do
"TRACE1-1" -> "TRACE1-1 (Trace digipeater, 1 hop)"
"TRACE2-1" -> "TRACE2-1 (Trace digipeater, 2 hops)"
"TRACE3-1" -> "TRACE3-1 (Trace digipeater, 3 hops)"
"TRACE4-1" -> "TRACE4-1 (Trace digipeater, 4 hops)"
"TRACE5-1" -> "TRACE5-1 (Trace digipeater, 5 hops)"
"TRACE6-1" -> "TRACE6-1 (Trace digipeater, 6 hops)"
"TRACE7-1" -> "TRACE7-1 (Trace digipeater, 7 hops)"
_ -> "TRACE digipeater (#{element})"
"TRACE1-1" -> gettext("TRACE1-1 (Trace digipeater, 1 hop)")
"TRACE2-1" -> gettext("TRACE2-1 (Trace digipeater, 2 hops)")
"TRACE3-1" -> gettext("TRACE3-1 (Trace digipeater, 3 hops)")
"TRACE4-1" -> gettext("TRACE4-1 (Trace digipeater, 4 hops)")
"TRACE5-1" -> gettext("TRACE5-1 (Trace digipeater, 5 hops)")
"TRACE6-1" -> gettext("TRACE6-1 (Trace digipeater, 6 hops)")
"TRACE7-1" -> gettext("TRACE7-1 (Trace digipeater, 7 hops)")
_ -> gettext("TRACE digipeater (%{element})", element: element)
end
# RELAY digipeaters
String.starts_with?(element, "RELAY") ->
case element do
"RELAY" -> "RELAY (Relay digipeater)"
"RELAY-1" -> "RELAY-1 (Relay digipeater, 1 hop)"
"RELAY-2" -> "RELAY-2 (Relay digipeater, 2 hops)"
_ -> "RELAY digipeater (#{element})"
"RELAY" -> gettext("RELAY (Relay digipeater)")
"RELAY-1" -> gettext("RELAY-1 (Relay digipeater, 1 hop)")
"RELAY-2" -> gettext("RELAY-2 (Relay digipeater, 2 hops)")
_ -> gettext("RELAY digipeater (%{element})", element: element)
end
# qAC (APRS-IS connection)
@ -466,22 +467,22 @@ defmodule AprsmeWeb.InfoLive.Show do
# TCPIP digipeaters
String.starts_with?(element, "TCPIP") ->
case element do
"TCPIP" -> "TCPIP (Internet gateway)"
"TCPIP*" -> "TCPIP* (Internet gateway, no forward)"
_ -> "TCPIP gateway (#{element})"
"TCPIP" -> gettext("TCPIP (Internet gateway)")
"TCPIP*" -> gettext("TCPIP* (Internet gateway, no forward)")
_ -> gettext("TCPIP gateway (%{element})", element: element)
end
# Generic callsign with SSID (likely a digipeater)
Regex.match?(~r/^[A-Z0-9]+-\d+$/, element) ->
"#{element} (Digipeater)"
gettext("%{element} (Digipeater)", element: element)
# Generic callsign without SSID
Regex.match?(~r/^[A-Z0-9]+$/, element) ->
"#{element} (Station)"
gettext("%{element} (Station)", element: element)
# Default case
true ->
"#{element} (Unknown)"
gettext("%{element} (Unknown)", element: element)
end
end
end

View file

@ -24,7 +24,7 @@
<div class="min-w-0 flex-1">
<div class="flex items-center">
<h1 class="text-xl font-bold leading-7 sm:truncate sm:text-2xl">
APRS Station
{gettext("APRS Station")}
</h1>
<div class="ml-3 flex items-center space-x-3">
<span class="badge badge-primary">
@ -64,11 +64,11 @@
<div class="mt-3 flex md:ml-4 md:mt-0">
<div class="flex space-x-2">
<.link navigate={~p"/packets/#{@callsign}"} class="btn btn-outline btn-sm">
View packets
{gettext("View packets")}
</.link>
<%= if @has_weather_packets do %>
<.link navigate={~p"/weather/#{@callsign}"} class="btn btn-primary btn-sm">
Weather charts
{gettext("Weather charts")}
</.link>
<% end %>
</div>
@ -106,19 +106,19 @@
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Position Information</h3>
<h3 class="text-sm font-medium">{gettext("Position Information")}</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium opacity-70">Location</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Location")}</dt>
<dd class="mt-1 text-sm font-semibold">
{@packet.lat || ""}, {@packet.lon || ""}
</dd>
</div>
<%= if @packet.lat && @packet.lon do %>
<div>
<dt class="text-xs font-medium opacity-70">Grid Square</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Grid Square")}</dt>
<dd class="mt-1 text-sm font-semibold">
<%= case Gridsquare.encode(@packet.lon, @packet.lat) do %>
<% %Gridsquare.EncodeResult{grid_reference: grid_ref} -> %>
@ -130,7 +130,7 @@
</div>
<% end %>
<div>
<dt class="text-xs font-medium opacity-70">Last Position</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Last Position")}</dt>
<dd class="mt-1 text-sm">
<%= if @packet.received_at do %>
<div class="font-semibold">
@ -140,25 +140,25 @@
{Calendar.strftime(@packet.received_at, "%Y-%m-%d %H:%M:%S UTC")}
</div>
<% else %>
<span class="opacity-70">Unknown</span>
<span class="opacity-70">{gettext("Unknown")}</span>
<% end %>
</dd>
</div>
<%= if @packet.altitude do %>
<div>
<dt class="text-xs font-medium opacity-70">Altitude</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Altitude")}</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.altitude} ft</dd>
</div>
<% end %>
<%= if @packet.course do %>
<div>
<dt class="text-xs font-medium opacity-70">Course</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Course")}</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.course}°</dd>
</div>
<% end %>
<%= if @packet.speed do %>
<div>
<dt class="text-xs font-medium opacity-70">Speed</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Speed")}</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.speed} MPH</dd>
</div>
<% end %>
@ -186,12 +186,12 @@
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Device Information</h3>
<h3 class="text-sm font-medium">{gettext("Device Information")}</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium opacity-70">Device</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Device")}</dt>
<dd class="mt-1 text-sm font-semibold">
<%= if @packet.device_model || @packet.device_vendor do %>
{[
@ -206,25 +206,25 @@
<% end %>
<% else %>
<span class="opacity-70 font-mono">
{@packet.device_identifier || "Unknown"}
{@packet.device_identifier || gettext("Unknown")}
</span>
<% end %>
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Path</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Path")}</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.path || ""}</dd>
</div>
<%= if @packet.path && @packet.path != "" do %>
<div class="col-span-2">
<dt class="text-xs font-medium opacity-70">Decoded Path</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Decoded Path")}</dt>
<dd class="mt-1 text-sm">
{decode_aprs_path(@packet.path)}
</dd>
</div>
<% end %>
<div class="col-span-2">
<dt class="text-xs font-medium opacity-70">Raw Packet</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Raw Packet")}</dt>
<dd class="mt-1 text-xs font-mono break-all">
<%= if is_binary(@packet.raw_packet) do %>
{Aprsme.EncodingUtils.sanitize_string(@packet.raw_packet)}
@ -259,7 +259,7 @@
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Other SSIDs</h3>
<h3 class="text-sm font-medium">{gettext("Other SSIDs")}</h3>
</div>
</div>
<div class="overflow-x-auto">
@ -267,13 +267,13 @@
<thead>
<tr>
<th class="text-xs font-medium uppercase tracking-wider">
Callsign
{gettext("Callsign")}
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Distance & Direction
{gettext("Distance & Direction")}
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Last Heard
{gettext("Last Heard")}
</th>
</tr>
</thead>
@ -377,7 +377,7 @@
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Stations Near Current Position</h3>
<h3 class="text-sm font-medium">{gettext("Stations Near Current Position")}</h3>
</div>
</div>
<div class="overflow-x-auto">
@ -385,13 +385,13 @@
<thead>
<tr>
<th class="text-xs font-medium uppercase tracking-wider">
Callsign
{gettext("Callsign")}
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Distance & Course
{gettext("Distance & Course")}
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Last Heard
{gettext("Last Heard")}
</th>
</tr>
</thead>
@ -466,9 +466,9 @@
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423L16.5 15.75l.394 1.183a2.25 2.25 0 001.423 1.423L19.5 18.75l-1.183.394a2.25 2.25 0 00-1.423 1.423z"
/>
</svg>
<h3 class="mt-2 text-sm font-semibold">No data available</h3>
<h3 class="mt-2 text-sm font-semibold">{gettext("No data available")}</h3>
<p class="mt-1 text-sm opacity-70">
No recent packet data available for this callsign.
{gettext("No recent packet data available for this callsign.")}
</p>
</div>
<% end %>

View file

@ -0,0 +1,25 @@
defmodule AprsmeWeb.LocaleHook do
@moduledoc """
LiveView hook for setting locale based on Accept-Language header.
This ensures that LiveView updates maintain the correct locale.
"""
import Phoenix.Component
@supported_locales ["en", "es"]
def on_mount(:set_locale, _params, session, socket) do
locale = get_locale_from_session(session) || "en"
Gettext.put_locale(AprsmeWeb.Gettext, locale)
{:cont, assign(socket, locale: locale)}
end
defp get_locale_from_session(session) do
case session do
%{"locale" => locale} when locale in @supported_locales ->
locale
_ ->
nil
end
end
end

View file

@ -607,7 +607,11 @@ defmodule AprsmeWeb.MapLive.Index do
>
</div>
<button class="locate-button" phx-click="locate_me" title="Find my location">
<button
class="locate-button"
phx-click="locate_me"
title={Gettext.gettext(AprsmeWeb.Gettext, "Find my location")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
@ -629,7 +633,11 @@ defmodule AprsmeWeb.MapLive.Index do
<button
class={["slideover-toggle", if(@slideover_open, do: "slideover-open", else: "slideover-closed")]}
phx-click="toggle_slideover"
title={if @slideover_open, do: "Hide controls", else: "Show controls"}
title={
if @slideover_open,
do: Gettext.gettext(AprsmeWeb.Gettext, "Hide controls"),
else: Gettext.gettext(AprsmeWeb.Gettext, "Show controls")
}
>
<%= if @slideover_open do %>
<svg
@ -704,7 +712,7 @@ defmodule AprsmeWeb.MapLive.Index do
<button
class="lg:hidden text-white hover:text-slate-200 transition-colors"
phx-click="toggle_slideover"
title="Close controls"
title={Gettext.gettext(AprsmeWeb.Gettext, "Close controls")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -736,7 +744,7 @@ defmodule AprsmeWeb.MapLive.Index do
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<span>Search Callsign</span>
<span>{Gettext.gettext(AprsmeWeb.Gettext, "Search Callsign")}</span>
</label>
<form phx-submit="search_callsign" class="flex space-x-2">
<input
@ -744,14 +752,14 @@ defmodule AprsmeWeb.MapLive.Index do
name="callsign"
value={@overlay_callsign}
phx-change="update_callsign"
placeholder="Enter callsign..."
placeholder={Gettext.gettext(AprsmeWeb.Gettext, "Enter callsign...")}
class="flex-1 px-4 py-3 border border-slate-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm uppercase placeholder-slate-400 transition-all duration-200 hover:border-slate-400"
/>
<button
type="submit"
class="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-xl hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-200 text-sm font-semibold shadow-lg hover:shadow-xl"
>
Search
{Gettext.gettext(AprsmeWeb.Gettext, "Search")}
</button>
</form>
</div>
@ -772,19 +780,31 @@ defmodule AprsmeWeb.MapLive.Index do
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Trail Duration</span>
<span>{Gettext.gettext(AprsmeWeb.Gettext, "Trail Duration")}</span>
</label>
<form phx-change="update_trail_duration" class="relative">
<select
name="trail_duration"
class="w-full px-4 py-3 border border-slate-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm bg-white text-gray-900 appearance-none transition-all duration-200 hover:border-slate-400"
>
<option value="1" selected={@trail_duration == "1"}>1 Hour</option>
<option value="6" selected={@trail_duration == "6"}>6 Hours</option>
<option value="12" selected={@trail_duration == "12"}>12 Hours</option>
<option value="24" selected={@trail_duration == "24"}>24 Hours</option>
<option value="48" selected={@trail_duration == "48"}>48 Hours</option>
<option value="168" selected={@trail_duration == "168"}>1 Week</option>
<option value="1" selected={@trail_duration == "1"}>
{Gettext.gettext(AprsmeWeb.Gettext, "1 Hour")}
</option>
<option value="6" selected={@trail_duration == "6"}>
{Gettext.gettext(AprsmeWeb.Gettext, "6 Hours")}
</option>
<option value="12" selected={@trail_duration == "12"}>
{Gettext.gettext(AprsmeWeb.Gettext, "12 Hours")}
</option>
<option value="24" selected={@trail_duration == "24"}>
{Gettext.gettext(AprsmeWeb.Gettext, "24 Hours")}
</option>
<option value="48" selected={@trail_duration == "48"}>
{Gettext.gettext(AprsmeWeb.Gettext, "48 Hours")}
</option>
<option value="168" selected={@trail_duration == "168"}>
{Gettext.gettext(AprsmeWeb.Gettext, "1 Week")}
</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg
@ -815,18 +835,28 @@ defmodule AprsmeWeb.MapLive.Index do
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Historical Data</span>
<span>{Gettext.gettext(AprsmeWeb.Gettext, "Historical Data")}</span>
</label>
<form phx-change="update_historical_hours" class="relative">
<select
name="historical_hours"
class="w-full px-4 py-3 border border-slate-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm bg-white text-gray-900 appearance-none transition-all duration-200 hover:border-slate-400"
>
<option value="1" selected={@historical_hours == "1"}>1 Hour</option>
<option value="3" selected={@historical_hours == "3"}>3 Hours</option>
<option value="6" selected={@historical_hours == "6"}>6 Hours</option>
<option value="12" selected={@historical_hours == "12"}>12 Hours</option>
<option value="24" selected={@historical_hours == "24"}>24 Hours</option>
<option value="1" selected={@historical_hours == "1"}>
{Gettext.gettext(AprsmeWeb.Gettext, "1 Hour")}
</option>
<option value="3" selected={@historical_hours == "3"}>
{Gettext.gettext(AprsmeWeb.Gettext, "3 Hours")}
</option>
<option value="6" selected={@historical_hours == "6"}>
{Gettext.gettext(AprsmeWeb.Gettext, "6 Hours")}
</option>
<option value="12" selected={@historical_hours == "12"}>
{Gettext.gettext(AprsmeWeb.Gettext, "12 Hours")}
</option>
<option value="24" selected={@historical_hours == "24"}>
{Gettext.gettext(AprsmeWeb.Gettext, "24 Hours")}
</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg
@ -857,7 +887,7 @@ defmodule AprsmeWeb.MapLive.Index do
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"
/>
</svg>
<span class="font-medium">Navigation</span>
<span class="font-medium">{Gettext.gettext(AprsmeWeb.Gettext, "Navigation")}</span>
</div>
<.navigation variant={:vertical} class="text-sm" />
</div>
@ -873,7 +903,7 @@ defmodule AprsmeWeb.MapLive.Index do
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="font-medium">Last Deploy</span>
<span class="font-medium">{Gettext.gettext(AprsmeWeb.Gettext, "Last Deploy")}</span>
</div>
<div class="text-xs text-slate-500">
<div class="font-mono">{time_ago_in_words(@deployed_at)}</div>

View file

@ -3,19 +3,24 @@ defmodule AprsmeWeb.MapLive.PopupComponent do
use Phoenix.Component
import AprsmeWeb.TimeHelpers
import Gettext
def popup(assigns) do
~H"""
<div class="aprs-popup" data-timestamp={@cache_buster}>
<div class="aprs-callsign">
<strong><.link navigate={"/#{@callsign}"}>{@callsign}</.link></strong>
<.link navigate={"/info/#{@callsign}"} class="aprs-info-link">info</.link>
<.link navigate={"/info/#{@callsign}"} class="aprs-info-link">
{gettext(AprsmeWeb.Gettext, "info")}
</.link>
<%= if @weather_link do %>
<.link navigate={"/weather/#{@callsign}"} class="aprs-info-link">weather charts</.link>
<.link navigate={"/weather/#{@callsign}"} class="aprs-info-link">
{gettext(AprsmeWeb.Gettext, "weather charts")}
</.link>
<% end %>
</div>
<%= if @weather do %>
<div class="aprs-comment">Weather Report</div>
<div class="aprs-comment">{gettext(AprsmeWeb.Gettext, "Weather Report")}</div>
<% else %>
<%= if @comment != "" do %>
<div class="aprs-comment">{@comment}</div>
@ -30,11 +35,17 @@ defmodule AprsmeWeb.MapLive.PopupComponent do
</div>
<% end %>
<%= if @weather do %>
<hr style="margin-top: 4px; margin-bottom: 4px;" /> Temperature: {@temperature}°F<br />
Humidity: {@humidity}%<br />
Wind: {@wind_direction}° at {@wind_speed} mph, gusts to {@wind_gust} mph<br />
Pressure: {@pressure} hPa<br /> Rain (1h): {@rain_1h} in.<br />
Rain (24h): {@rain_24h} in.<br /> Rain (since midnight): {@rain_since_midnight} in.<br />
<hr style="margin-top: 4px; margin-bottom: 4px;" />
{gettext(AprsmeWeb.Gettext, "Temperature:")} {@temperature}°F<br />
{gettext(AprsmeWeb.Gettext, "Humidity:")} {@humidity}%<br />
{gettext(AprsmeWeb.Gettext, "Wind:")} {@wind_direction}° {gettext(AprsmeWeb.Gettext, "at")} {@wind_speed} mph, {gettext(
AprsmeWeb.Gettext,
"gusts to"
)} {@wind_gust} mph<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 />
<% end %>
</div>
"""

View file

@ -1,6 +1,7 @@
defmodule AprsmeWeb.PacketsLive.Index do
@moduledoc false
use AprsmeWeb, :live_view
use Gettext, backend: AprsmeWeb.Gettext
alias Aprsme.EncodingUtils
alias AprsmeWeb.Endpoint

View file

@ -5,15 +5,15 @@
<table class="table table-zebra">
<thead>
<tr>
<th>Sender</th>
<th>SSID</th>
<th>Base Callsign</th>
<th>Data Type</th>
<th>Symbol</th>
<th>Path</th>
<th>Latitude</th>
<th>Longitude</th>
<th>Device</th>
<th>{gettext("Sender")}</th>
<th>{gettext("SSID")}</th>
<th>{gettext("Base Callsign")}</th>
<th>{gettext("Data Type")}</th>
<th>{gettext("Symbol")}</th>
<th>{gettext("Path")}</th>
<th>{gettext("Latitude")}</th>
<th>{gettext("Longitude")}</th>
<th>{gettext("Device")}</th>
</tr>
</thead>
<tbody>
@ -161,8 +161,8 @@
/>
</svg>
</div>
<h3 class="text-lg font-medium mb-2">No packets</h3>
<p class="opacity-70">Packets will appear here as they are received.</p>
<h3 class="text-lg font-medium mb-2">{gettext("No packets")}</h3>
<p class="opacity-70">{gettext("Packets will appear here as they are received.")}</p>
</div>
<% end %>
</div>

View file

@ -41,9 +41,9 @@ defmodule AprsmeWeb.StatusLive.Index do
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex justify-between items-center mb-6">
<h1 class="card-title text-3xl">APRS.me System Status</h1>
<h1 class="card-title text-3xl">{gettext("APRS.me System Status")}</h1>
<div class="text-sm opacity-70">
Last updated:
{gettext("Last updated:")}
<span class="font-mono">{Calendar.strftime(@current_time, "%H:%M:%S UTC")}</span>
</div>
</div>
@ -65,9 +65,11 @@ defmodule AprsmeWeb.StatusLive.Index do
/>
</svg>
<div>
<h3 class="font-bold">APRS-IS Connection Issue</h3>
<h3 class="font-bold">{gettext("APRS-IS Connection Issue")}</h3>
<div class="text-xs">
The system is currently disconnected from the APRS-IS network. This may be due to network issues or server maintenance.
{gettext(
"The system is currently disconnected from the APRS-IS network. This may be due to network issues or server maintenance."
)}
</div>
</div>
</div>
@ -75,50 +77,52 @@ defmodule AprsmeWeb.StatusLive.Index do
<!-- APRS-IS Connection Status -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-4">APRS-IS Connection</h2>
<h2 class="text-xl font-semibold mb-4">{gettext("APRS-IS Connection")}</h2>
<div class="card bg-base-200">
<div class="card-body">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Status:</span>
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Status:")}</span>
<%= if @aprs_status.connected do %>
<div class="badge badge-success gap-1">
<div class="w-2 h-2 bg-current rounded-full"></div>
Connected
{gettext("Connected")}
</div>
<% else %>
<div class="badge badge-error gap-1">
<div class="w-2 h-2 bg-current rounded-full"></div>
Disconnected
{gettext("Disconnected")}
</div>
<% end %>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Server:</span>
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Server:")}</span>
<span class="text-sm font-mono">
{@aprs_status.server}:{@aprs_status.port}
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Login ID:</span>
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Login ID:")}</span>
<span class="text-sm font-mono">{@aprs_status.login_id}</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Connected Since:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Connected Since:")}
</span>
<span class="text-sm">
<%= if @aprs_status.connected_at do %>
{Calendar.strftime(@aprs_status.connected_at, "%Y-%m-%d %H:%M:%S UTC")}
<% else %>
<span class="opacity-50">Not connected</span>
<span class="opacity-50">{gettext("Not connected")}</span>
<% end %>
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Uptime:</span>
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Uptime:")}</span>
<span class={[
"text-sm font-medium",
if(@aprs_status.connected, do: "text-success", else: "text-error")
@ -128,7 +132,7 @@ defmodule AprsmeWeb.StatusLive.Index do
</div>
<div class="flex items-center col-span-2">
<span class="text-sm font-medium opacity-70 mr-2">Filter:</span>
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Filter:")}</span>
<div class="badge badge-outline font-mono">
{@aprs_status.filter}
</div>
@ -138,42 +142,52 @@ defmodule AprsmeWeb.StatusLive.Index do
<!-- Packet Statistics -->
<div class="divider"></div>
<div>
<h3 class="text-sm font-medium mb-3">Packet Statistics</h3>
<h3 class="text-sm font-medium mb-3">{gettext("Packet Statistics")}</h3>
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Total Packets:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Total Packets:")}
</span>
<span class="text-sm font-mono">
{format_number(@aprs_status.packet_stats.total_packets)}
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Packets/Sec:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Packets/Sec:")}
</span>
<span class="text-sm font-mono">
{@aprs_status.packet_stats.packets_per_second}
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Packets/Min:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Packets/Min:")}
</span>
<span class="text-sm font-mono">
{@aprs_status.packet_stats.packets_per_second * 60}
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Last Packet:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Last Packet:")}
</span>
<span class="text-sm">
<%= if @aprs_status.packet_stats.last_packet_at do %>
{format_time_ago(@aprs_status.packet_stats.last_packet_at)}
<% else %>
<span class="opacity-50">None</span>
<span class="opacity-50">{gettext("None")}</span>
<% end %>
</span>
</div>
<div class="flex items-center">
<span class="text-sm font-medium opacity-70 mr-2">Stored Packets:</span>
<span class="text-sm font-medium opacity-70 mr-2">
{gettext("Stored Packets:")}
</span>
<span class="text-sm font-mono">
{format_number(@aprs_status.stored_packet_count)}
</span>
@ -184,7 +198,7 @@ defmodule AprsmeWeb.StatusLive.Index do
<!-- Health Score -->
<div class="divider"></div>
<div class="flex items-center justify-between">
<span class="text-sm font-medium opacity-70">Connection Health:</span>
<span class="text-sm font-medium opacity-70">{gettext("Connection Health:")}</span>
<div class="flex items-center">
<div class="rating rating-sm mr-2">
<%= for i <- 1..5 do %>
@ -259,7 +273,7 @@ defmodule AprsmeWeb.StatusLive.Index do
Process.send_after(self(), :refresh_status, @refresh_interval)
end
def format_uptime(seconds) when seconds <= 0, do: "Not connected"
def format_uptime(seconds) when seconds <= 0, do: gettext("Not connected")
def format_uptime(seconds) do
days = div(seconds, 86_400)
@ -291,12 +305,12 @@ defmodule AprsmeWeb.StatusLive.Index do
def get_health_description(score, connected) do
case {score, connected} do
{1, false} -> "Disconnected - Connection issues detected"
{2, true} -> "Recently connected - Monitoring stability"
{3, true} -> "Good - Connection stable for less than 1 hour"
{4, true} -> "Very good - Connection stable for less than 1 day"
{5, true} -> "Excellent - Long-term stable connection"
_ -> "Unknown status"
{1, false} -> gettext("Disconnected - Connection issues detected")
{2, true} -> gettext("Recently connected - Monitoring stability")
{3, true} -> gettext("Good - Connection stable for less than 1 hour")
{4, true} -> gettext("Very good - Connection stable for less than 1 day")
{5, true} -> gettext("Excellent - Long-term stable connection")
_ -> gettext("Unknown status")
end
end
@ -304,10 +318,10 @@ defmodule AprsmeWeb.StatusLive.Index do
diff_seconds = DateTime.diff(DateTime.utc_now(), datetime)
cond do
diff_seconds < 60 -> "#{diff_seconds} seconds ago"
diff_seconds < 3600 -> "#{div(diff_seconds, 60)} minutes ago"
diff_seconds < 86_400 -> "#{div(diff_seconds, 3600)} hours ago"
true -> "#{div(diff_seconds, 86_400)} days ago"
diff_seconds < 60 -> gettext("%{count} seconds ago", count: diff_seconds)
diff_seconds < 3600 -> gettext("%{count} minutes ago", count: div(diff_seconds, 60))
diff_seconds < 86_400 -> gettext("%{count} hours ago", count: div(diff_seconds, 3600))
true -> gettext("%{count} days ago", count: div(diff_seconds, 86_400))
end
end

View file

@ -1,6 +1,7 @@
defmodule AprsmeWeb.WeatherLive.CallsignView do
@moduledoc false
use AprsmeWeb, :live_view
use Gettext, backend: AprsmeWeb.Gettext
import Phoenix.LiveView, only: [push_event: 3, connected?: 1]
@ -45,6 +46,24 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
end)
|> Jason.encode!()
# Add chart labels for translation
chart_labels = %{
temp_title: gettext("Temperature & Dew Point (°F)"),
temp_label: gettext("Temperature (°F)"),
dew_label: gettext("Dew Point (°F)"),
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)"),
time: gettext("Time"),
degf: gettext("°F"),
percent: gettext("%"),
mb: gettext("mb")
}
socket =
socket
|> assign(:callsign, normalized_callsign)
@ -52,6 +71,7 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|> assign(:page_title, "Weather for #{normalized_callsign}")
|> assign(:weather_history, weather_history)
|> assign(:weather_history_json, weather_history_json)
|> assign(:chart_labels, chart_labels)
{:ok, socket}
end

View file

@ -5,7 +5,7 @@
<div class="py-4 md:flex md:items-center md:justify-between">
<div class="flex items-center">
<h1 class="text-xl font-bold leading-7 sm:truncate sm:text-2xl">
Weather Station
{gettext("Weather Station")}
</h1>
<div class="ml-3 flex items-center space-x-3">
<span class="badge badge-primary">
@ -40,13 +40,13 @@
<div class="mt-3 flex md:ml-4 md:mt-0">
<div class="flex space-x-2">
<.link navigate={~p"/info/#{@callsign}"} class="btn btn-outline btn-sm">
View info
{gettext("View info")}
</.link>
<.link navigate={~p"/packets/#{@callsign}"} class="btn btn-outline btn-sm">
View packets
{gettext("View packets")}
</.link>
<.link navigate={~p"/#{@callsign}"} class="btn btn-primary btn-sm">
View on map
{gettext("View on map")}
</.link>
</div>
</div>
@ -76,12 +76,12 @@
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Weather Details</h3>
<h3 class="text-sm font-medium">{gettext("Weather Details")}</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium opacity-70">Last WX report</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Last WX report")}</dt>
<dd class="mt-1 text-sm font-semibold">
<%= if dt do %>
{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S UTC")} ({AprsmeWeb.TimeHelpers.time_ago_in_words(
@ -89,24 +89,24 @@
)})<br />
<%!-- <span class="text-xs text-gray-500">{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S %Z")} local time at {Map.get(@weather_packet, :region) || Map.get(@weather_packet, "region") || "Unknown location"} [?]</span> --%>
<% else %>
Unknown
{gettext("Unknown")}
<% end %>
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Temperature</dt>
<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
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Humidity</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Humidity")}</dt>
<dd class="mt-1 text-sm font-semibold">
{PacketUtils.get_weather_field(@weather_packet, :humidity)}%
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Wind</dt>
<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(
@weather_packet,
@ -115,37 +115,37 @@
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Gusts</dt>
<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
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Pressure</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Pressure")}</dt>
<dd class="mt-1 text-sm font-semibold">
{PacketUtils.get_weather_field(@weather_packet, :pressure)} hPa
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Rain (1h)</dt>
<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
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Rain (24h)</dt>
<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
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Rain (since midnight)</dt>
<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
</dd>
</div>
<div class="col-span-2">
<dt class="text-xs font-medium opacity-70">Raw Packet</dt>
<dt class="text-xs font-medium opacity-70">{gettext("Raw Packet")}</dt>
<dd class="mt-1 text-xs font-mono break-all">
<%= if is_binary(@weather_packet.raw_packet) do %>
{Aprsme.EncodingUtils.sanitize_string(@weather_packet.raw_packet)}
@ -156,18 +156,20 @@
</div>
</dl>
<div class="mt-2 text-xs opacity-70">
<strong>Raw comment:</strong> {@weather_packet.comment || @weather_packet["comment"]}
<strong>{gettext("Raw comment")}:</strong> {@weather_packet.comment ||
@weather_packet["comment"]}
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h3 class="text-sm font-medium mb-2">Weather History Graphs</h3>
<h3 class="text-sm font-medium mb-2">{gettext("Weather History Graphs")}</h3>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :temperature, nil)))) do %>
<div
id="temp-chart"
phx-hook="ChartJSTempChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>
@ -179,6 +181,7 @@
id="humidity-chart"
phx-hook="ChartJSHumidityChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>
@ -190,6 +193,7 @@
id="pressure-chart"
phx-hook="ChartJSPressureChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>
@ -201,6 +205,7 @@
id="wind-chart"
phx-hook="ChartJSWindChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>
@ -212,6 +217,7 @@
id="rain-chart"
phx-hook="ChartJSRainChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>
@ -223,6 +229,7 @@
id="luminosity-chart"
phx-hook="ChartJSLuminosityChart"
data-weather-history={@weather_history_json}
data-chart-labels={Jason.encode!(@chart_labels)}
class="bg-base-200 rounded-lg p-4 mb-4"
style="height:250px;"
>

View file

@ -0,0 +1,53 @@
defmodule AprsmeWeb.Plugs.SetLocale do
@moduledoc """
A plug that sets the locale based on the Accept-Language header.
Falls back to English if the requested locale is not available.
"""
import Plug.Conn
@supported_locales ["en", "es"]
def init(opts), do: opts
def call(conn, _opts) do
locale = get_locale_from_header(conn) || "en"
IO.puts("DEBUG: SetLocale - Final locale: #{locale}")
Gettext.put_locale(AprsmeWeb.Gettext, locale)
# Store locale in session for LiveView to access
conn = put_session(conn, :locale, locale)
conn
end
defp get_locale_from_header(conn) do
case get_req_header(conn, "accept-language") do
[accept_language | _] ->
IO.puts("DEBUG: SetLocale - Accept-Language header: #{inspect(accept_language)}")
parse_accept_language(accept_language)
_ ->
IO.puts("DEBUG: SetLocale - No Accept-Language header found")
nil
end
end
defp parse_accept_language(accept_language) do
accept_language
|> String.split(",")
|> Enum.map(&parse_language_tag/1)
|> Enum.find(&supported_locale?/1)
end
defp parse_language_tag(tag) do
tag
|> String.trim()
|> String.split(";")
|> List.first()
|> String.split("-")
|> List.first()
|> String.downcase()
end
defp supported_locale?(locale) do
locale in @supported_locales
end
end

View file

@ -12,6 +12,7 @@ defmodule AprsmeWeb.Router do
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_user
plug AprsmeWeb.Plugs.SetLocale
end
pipeline :api do
@ -30,7 +31,7 @@ defmodule AprsmeWeb.Router do
live_dashboard "/dashboard", metrics: AprsmeWeb.Telemetry
live_session :regular_pages do
live_session :regular_pages, on_mount: [{AprsmeWeb.LocaleHook, :set_locale}] do
live "/status", StatusLive.Index, :index
live "/packets", PacketsLive.Index, :index
live "/packets/:callsign", PacketsLive.CallsignView, :index
@ -73,7 +74,7 @@ defmodule AprsmeWeb.Router do
pipe_through [:browser, :redirect_if_user_is_authenticated]
live_session :redirect_if_user_is_authenticated,
on_mount: [{AprsmeWeb.UserAuth, :redirect_if_user_is_authenticated}] do
on_mount: [{AprsmeWeb.UserAuth, :redirect_if_user_is_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/register", UserRegistrationLive, :new
live "/users/log_in", UserLoginLive, :new
live "/users/reset_password", UserForgotPasswordLive, :new
@ -87,7 +88,7 @@ defmodule AprsmeWeb.Router do
pipe_through [:browser, :require_authenticated_user]
live_session :require_authenticated_user,
on_mount: [{AprsmeWeb.UserAuth, :ensure_authenticated}] do
on_mount: [{AprsmeWeb.UserAuth, :ensure_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
end
@ -99,7 +100,7 @@ defmodule AprsmeWeb.Router do
delete "/users/log_out", UserSessionController, :delete
live_session :current_user,
on_mount: [{AprsmeWeb.UserAuth, :mount_current_user}] do
on_mount: [{AprsmeWeb.UserAuth, :mount_current_user}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new
end

View file

@ -2,6 +2,7 @@ defmodule AprsmeWeb.TimeHelpers do
@moduledoc """
Shared helpers for formatting time and dates in the web layer.
"""
use Gettext, backend: AprsmeWeb.Gettext
@doc """
Returns a human-readable string for how long ago the given DateTime was.
@ -10,22 +11,22 @@ defmodule AprsmeWeb.TimeHelpers do
now = DateTime.utc_now()
diff_seconds = DateTime.diff(now, datetime, :second)
format_time_diff(diff_seconds) <> " ago"
format_time_diff(diff_seconds) <> " " <> gettext("ago")
end
defp format_time_diff(seconds) when seconds < 60, do: "less than a minute"
defp format_time_diff(seconds) when seconds < 120, do: "1 minute"
defp format_time_diff(seconds) when seconds < 3600, do: "#{div(seconds, 60)} minutes"
defp format_time_diff(seconds) when seconds < 7200, do: "1 hour"
defp format_time_diff(seconds) when seconds < 86_400, do: "#{div(seconds, 3600)} hours"
defp format_time_diff(seconds) when seconds < 172_800, do: "1 day"
defp format_time_diff(seconds) when seconds < 2_592_000, do: "#{div(seconds, 86_400)} days"
defp format_time_diff(seconds) when seconds < 5_184_000, do: "1 month"
defp format_time_diff(seconds) when seconds < 60, do: gettext("less than a minute")
defp format_time_diff(seconds) when seconds < 120, do: gettext("1 minute")
defp format_time_diff(seconds) when seconds < 3600, do: gettext("%{count} minutes", count: div(seconds, 60))
defp format_time_diff(seconds) when seconds < 7200, do: gettext("1 hour")
defp format_time_diff(seconds) when seconds < 86_400, do: gettext("%{count} hours", count: div(seconds, 3600))
defp format_time_diff(seconds) when seconds < 172_800, do: gettext("1 day")
defp format_time_diff(seconds) when seconds < 2_592_000, do: gettext("%{count} days", count: div(seconds, 86_400))
defp format_time_diff(seconds) when seconds < 5_184_000, do: gettext("1 month")
defp format_time_diff(seconds) when seconds < 31_536_000, do: "#{div(seconds, 2_592_000)} months"
defp format_time_diff(seconds) when seconds < 31_536_000, do: gettext("%{count} months", count: div(seconds, 2_592_000))
defp format_time_diff(seconds) when seconds < 63_072_000, do: "1 year"
defp format_time_diff(seconds), do: "#{div(seconds, 31_536_000)} years"
defp format_time_diff(seconds) when seconds < 63_072_000, do: gettext("1 year")
defp format_time_diff(seconds), do: gettext("%{count} years", count: div(seconds, 31_536_000))
@doc """
Converts various timestamp formats into a standard DateTime object.

815
priv/gettext/default.pot Normal file
View file

@ -0,0 +1,815 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
#: lib/aprsme_web/components/core_components.ex:570
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/aprsme_web/components/core_components.ex:117
#: lib/aprsme_web/components/core_components.ex:202
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:23
#, elixir-autogen, elixir-format
msgid "%{count} days"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:310
#, elixir-autogen, elixir-format
msgid "%{count} days ago"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:21
#, elixir-autogen, elixir-format
msgid "%{count} hours"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:309
#, elixir-autogen, elixir-format
msgid "%{count} hours ago"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:19
#, elixir-autogen, elixir-format
msgid "%{count} minutes"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:308
#, elixir-autogen, elixir-format
msgid "%{count} minutes ago"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:26
#, elixir-autogen, elixir-format
msgid "%{count} months"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:307
#, elixir-autogen, elixir-format
msgid "%{count} seconds ago"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:29
#, elixir-autogen, elixir-format
msgid "%{count} years"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:477
#, elixir-autogen, elixir-format
msgid "%{element} (Digipeater)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:481
#, elixir-autogen, elixir-format
msgid "%{element} (Station)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:485
#, elixir-autogen, elixir-format
msgid "%{element} (Unknown)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:22
#, elixir-autogen, elixir-format
msgid "1 day"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:20
#, elixir-autogen, elixir-format
msgid "1 hour"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:18
#, elixir-autogen, elixir-format
msgid "1 minute"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:24
#, elixir-autogen, elixir-format
msgid "1 month"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:28
#, elixir-autogen, elixir-format
msgid "1 year"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:78
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:68
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection Issue"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:44
#, elixir-autogen, elixir-format
msgid "APRS.me System Status"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:70
#, elixir-autogen, elixir-format
msgid "Access forbidden"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "All packets are parsing successfully!"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:72
#, elixir-autogen, elixir-format
msgid "An error occurred: %{reason}"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:40
#, elixir-autogen, elixir-format
msgid "Attempted At"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "Auto-refreshes every 5 seconds"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:71
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:46
#, elixir-autogen, elixir-format
msgid "Bad request"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:87
#, elixir-autogen, elixir-format
msgid "Connected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:110
#, elixir-autogen, elixir-format
msgid "Connected Since:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:187
#, elixir-autogen, elixir-format
msgid "Connection Health:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:92
#, elixir-autogen, elixir-format
msgid "Disconnected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:294
#, elixir-autogen, elixir-format
msgid "Disconnected - Connection issues detected"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:53
#, elixir-autogen, elixir-format
msgid "Error Message"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:48
#, elixir-autogen, elixir-format
msgid "Error Type"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:298
#, elixir-autogen, elixir-format
msgid "Excellent - Long-term stable connection"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:131
#, elixir-autogen, elixir-format
msgid "Filter:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:64
#, elixir-autogen, elixir-format
msgid "Forbidden"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:296
#, elixir-autogen, elixir-format
msgid "Good - Connection stable for less than 1 hour"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:28
#, elixir-autogen, elixir-format
msgid "Internal server error"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:165
#, elixir-autogen, elixir-format
msgid "Last Packet:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:46
#, elixir-autogen, elixir-format
msgid "Last updated:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:105
#, elixir-autogen, elixir-format
msgid "Login ID:"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "No bad packets"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:55
#, elixir-autogen, elixir-format
msgid "No error message"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:170
#, elixir-autogen, elixir-format
msgid "None"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:115
#: lib/aprsme_web/live/status_live/index.ex:262
#, elixir-autogen, elixir-format
msgid "Not connected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:141
#, elixir-autogen, elixir-format
msgid "Packet Statistics"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:158
#, elixir-autogen, elixir-format
msgid "Packets/Min:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:151
#, elixir-autogen, elixir-format
msgid "Packets/Sec:"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:305
#, elixir-autogen, elixir-format
msgid "RELAY (Relay digipeater)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:308
#, elixir-autogen, elixir-format
msgid "RELAY digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:306
#, elixir-autogen, elixir-format
msgid "RELAY-1 (Relay digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:307
#, elixir-autogen, elixir-format
msgid "RELAY-2 (Relay digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:58
#: lib/aprsme_web/live/info_live/show.html.heex:227
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:148
#, elixir-autogen, elixir-format
msgid "Raw Packet"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:295
#, elixir-autogen, elixir-format
msgid "Recently connected - Monitoring stability"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:57
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:73
#, elixir-autogen, elixir-format
msgid "Request timeout"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:68
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:19
#, elixir-autogen, elixir-format
msgid "Resource not found"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:98
#, elixir-autogen, elixir-format
msgid "Server:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:83
#, elixir-autogen, elixir-format
msgid "Status:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:176
#, elixir-autogen, elixir-format
msgid "Stored Packets:"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:470
#, elixir-autogen, elixir-format
msgid "TCPIP (Internet gateway)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:472
#, elixir-autogen, elixir-format
msgid "TCPIP gateway (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:471
#, elixir-autogen, elixir-format
msgid "TCPIP* (Internet gateway, no forward)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:299
#, elixir-autogen, elixir-format
msgid "TRACE digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:292
#, elixir-autogen, elixir-format
msgid "TRACE1-1 (Trace digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:293
#, elixir-autogen, elixir-format
msgid "TRACE2-1 (Trace digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:294
#, elixir-autogen, elixir-format
msgid "TRACE3-1 (Trace digipeater, 3 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:295
#, elixir-autogen, elixir-format
msgid "TRACE4-1 (Trace digipeater, 4 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:296
#, elixir-autogen, elixir-format
msgid "TRACE5-1 (Trace digipeater, 5 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:297
#, elixir-autogen, elixir-format
msgid "TRACE6-1 (Trace digipeater, 6 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:298
#, elixir-autogen, elixir-format
msgid "TRACE7-1 (Trace digipeater, 7 hops)"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:70
#, elixir-autogen, elixir-format
msgid "The system is currently disconnected from the APRS-IS network. This may be due to network issues or server maintenance."
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:144
#, elixir-autogen, elixir-format
msgid "Total Packets:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:55
#, elixir-autogen, elixir-format
msgid "Unauthorized"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Unauthorized access"
msgstr ""
#: lib/aprsme/device_identification.ex:56
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:50
#: lib/aprsme_web/live/info_live/show.html.heex:143
#: lib/aprsme_web/live/info_live/show.html.heex:209
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:92
#, elixir-autogen, elixir-format
msgid "Unknown"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:299
#, elixir-autogen, elixir-format
msgid "Unknown status"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:37
#, elixir-autogen, elixir-format
msgid "Unprocessable entity"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Updating..."
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:121
#, elixir-autogen, elixir-format
msgid "Uptime:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/changeset_json.ex:13
#, elixir-autogen, elixir-format
msgid "Validation failed"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:297
#, elixir-autogen, elixir-format
msgid "Very good - Connection stable for less than 1 day"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:286
#, elixir-autogen, elixir-format
msgid "WIDE digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:277
#, elixir-autogen, elixir-format
msgid "WIDE1-1 (Wide area digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:284
#, elixir-autogen, elixir-format
msgid "WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:278
#, elixir-autogen, elixir-format
msgid "WIDE2-1 (Wide area digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:285
#, elixir-autogen, elixir-format
msgid "WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:279
#, elixir-autogen, elixir-format
msgid "WIDE3-1 (Wide area digipeater, 3 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:280
#, elixir-autogen, elixir-format
msgid "WIDE4-1 (Wide area digipeater, 4 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:281
#, elixir-autogen, elixir-format
msgid "WIDE5-1 (Wide area digipeater, 5 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:282
#, elixir-autogen, elixir-format
msgid "WIDE6-1 (Wide area digipeater, 6 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:283
#, elixir-autogen, elixir-format
msgid "WIDE7-1 (WIDE area digipeater, 7 hops)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:17
#, elixir-autogen, elixir-format
msgid "less than a minute"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:118
#, elixir-autogen, elixir-format
msgid "Gusts"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:103
#, elixir-autogen, elixir-format
msgid "Humidity"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:84
#, elixir-autogen, elixir-format
msgid "Last WX report"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:124
#, elixir-autogen, elixir-format
msgid "Pressure"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:130
#, elixir-autogen, elixir-format
msgid "Rain (1h)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:136
#, elixir-autogen, elixir-format
msgid "Rain (24h)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:142
#, elixir-autogen, elixir-format
msgid "Rain (since midnight)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:159
#, elixir-autogen, elixir-format
msgid "Raw comment"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:97
#, elixir-autogen, elixir-format
msgid "Temperature"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:43
#, elixir-autogen, elixir-format
msgid "View info"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:49
#, elixir-autogen, elixir-format
msgid "View on map"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:67
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:46
#, elixir-autogen, elixir-format
msgid "View packets"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:79
#, elixir-autogen, elixir-format
msgid "Weather Details"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:165
#, elixir-autogen, elixir-format
msgid "Weather History Graphs"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:8
#, elixir-autogen, elixir-format
msgid "Weather Station"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:109
#, elixir-autogen, elixir-format
msgid "Wind"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:27
#, elixir-autogen, elixir-format
msgid "APRS Station"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:149
#, elixir-autogen, elixir-format
msgid "Altitude"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:10
#, elixir-autogen, elixir-format
msgid "Base Callsign"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:12
#, elixir-autogen, elixir-format
msgid "Built with Modern Technologies"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:270
#: lib/aprsme_web/live/info_live/show.html.heex:388
#, elixir-autogen, elixir-format
msgid "Callsign"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:155
#, elixir-autogen, elixir-format
msgid "Course"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:11
#, elixir-autogen, elixir-format
msgid "Data Type"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:220
#, elixir-autogen, elixir-format
msgid "Decoded Path"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:194
#: lib/aprsme_web/live/packets_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "Device"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:189
#, elixir-autogen, elixir-format
msgid "Device Information"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:391
#, elixir-autogen, elixir-format
msgid "Distance & Course"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:273
#, elixir-autogen, elixir-format
msgid "Distance & Direction"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:35
#, elixir-autogen, elixir-format
msgid "Explore the Map"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:121
#, elixir-autogen, elixir-format
msgid "Grid Square"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:21
#, elixir-autogen, elixir-format
msgid "Join Our Community"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:276
#: lib/aprsme_web/live/info_live/show.html.heex:394
#, elixir-autogen, elixir-format
msgid "Last Heard"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:133
#, elixir-autogen, elixir-format
msgid "Last Position"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:14
#, elixir-autogen, elixir-format
msgid "Latitude"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:114
#, elixir-autogen, elixir-format
msgid "Location"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:15
#, elixir-autogen, elixir-format
msgid "Longitude"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:469
#, elixir-autogen, elixir-format
msgid "No data available"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:164
#, elixir-autogen, elixir-format
msgid "No packets"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:471
#, elixir-autogen, elixir-format
msgid "No recent packet data available for this callsign."
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:262
#, elixir-autogen, elixir-format
msgid "Other SSIDs"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:165
#, elixir-autogen, elixir-format
msgid "Packets will appear here as they are received."
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:215
#: lib/aprsme_web/live/packets_live/index.html.heex:13
#, elixir-autogen, elixir-format
msgid "Path"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:109
#, elixir-autogen, elixir-format
msgid "Position Information"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "SSID"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:8
#, elixir-autogen, elixir-format
msgid "Sender"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:161
#, elixir-autogen, elixir-format
msgid "Speed"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:380
#, elixir-autogen, elixir-format
msgid "Stations Near Current Position"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:12
#, elixir-autogen, elixir-format
msgid "Symbol"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:4
#, elixir-autogen, elixir-format
msgid "Under heavy construction"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:71
#, elixir-autogen, elixir-format
msgid "Weather charts"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:63
#, elixir-autogen, elixir-format
msgid "%"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:53
#, elixir-autogen, elixir-format
msgid "Dew Point (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:54
#: lib/aprsme_web/live/weather_live/callsign_view.ex:55
#, elixir-autogen, elixir-format
msgid "Humidity (%)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:56
#: lib/aprsme_web/live/weather_live/callsign_view.ex:57
#, elixir-autogen, elixir-format
msgid "Pressure (mb)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:51
#, elixir-autogen, elixir-format
msgid "Temperature & Dew Point (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:52
#, elixir-autogen, elixir-format
msgid "Temperature (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:61
#, elixir-autogen, elixir-format
msgid "Time"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:58
#, elixir-autogen, elixir-format
msgid "Wind (mph)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:60
#, elixir-autogen, elixir-format
msgid "Wind Gust (mph)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:59
#, elixir-autogen, elixir-format
msgid "Wind Speed (mph)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:14
#, elixir-autogen, elixir-format
msgid "ago"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:64
#, elixir-autogen, elixir-format
msgid "mb"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:62
#, elixir-autogen, elixir-format
msgid "°F"
msgstr ""

View file

@ -0,0 +1,815 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/aprsme_web/components/core_components.ex:570
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr "Actions"
#: lib/aprsme_web/components/core_components.ex:117
#: lib/aprsme_web/components/core_components.ex:202
#, elixir-autogen, elixir-format
msgid "close"
msgstr "close"
#: lib/aprsme_web/time_helpers.ex:23
#, elixir-autogen, elixir-format
msgid "%{count} days"
msgstr "%{count} days"
#: lib/aprsme_web/live/status_live/index.ex:310
#, elixir-autogen, elixir-format
msgid "%{count} days ago"
msgstr "%{count} days ago"
#: lib/aprsme_web/time_helpers.ex:21
#, elixir-autogen, elixir-format
msgid "%{count} hours"
msgstr "%{count} hours"
#: lib/aprsme_web/live/status_live/index.ex:309
#, elixir-autogen, elixir-format
msgid "%{count} hours ago"
msgstr "%{count} hours ago"
#: lib/aprsme_web/time_helpers.ex:19
#, elixir-autogen, elixir-format
msgid "%{count} minutes"
msgstr "%{count} minutes"
#: lib/aprsme_web/live/status_live/index.ex:308
#, elixir-autogen, elixir-format
msgid "%{count} minutes ago"
msgstr "%{count} minutes ago"
#: lib/aprsme_web/time_helpers.ex:26
#, elixir-autogen, elixir-format
msgid "%{count} months"
msgstr "%{count} months"
#: lib/aprsme_web/live/status_live/index.ex:307
#, elixir-autogen, elixir-format
msgid "%{count} seconds ago"
msgstr "%{count} seconds ago"
#: lib/aprsme_web/time_helpers.ex:29
#, elixir-autogen, elixir-format
msgid "%{count} years"
msgstr "%{count} years"
#: lib/aprsme_web/live/info_live/show.ex:477
#, elixir-autogen, elixir-format
msgid "%{element} (Digipeater)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:481
#, elixir-autogen, elixir-format
msgid "%{element} (Station)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:485
#, elixir-autogen, elixir-format
msgid "%{element} (Unknown)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:22
#, elixir-autogen, elixir-format
msgid "1 day"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:20
#, elixir-autogen, elixir-format
msgid "1 hour"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:18
#, elixir-autogen, elixir-format
msgid "1 minute"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:24
#, elixir-autogen, elixir-format
msgid "1 month"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:28
#, elixir-autogen, elixir-format
msgid "1 year"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:78
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:68
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection Issue"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:44
#, elixir-autogen, elixir-format
msgid "APRS.me System Status"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:70
#, elixir-autogen, elixir-format
msgid "Access forbidden"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "All packets are parsing successfully!"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:72
#, elixir-autogen, elixir-format
msgid "An error occurred: %{reason}"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:40
#, elixir-autogen, elixir-format
msgid "Attempted At"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "Auto-refreshes every 5 seconds"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:71
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:46
#, elixir-autogen, elixir-format
msgid "Bad request"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:87
#, elixir-autogen, elixir-format
msgid "Connected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:110
#, elixir-autogen, elixir-format
msgid "Connected Since:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:187
#, elixir-autogen, elixir-format
msgid "Connection Health:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:92
#, elixir-autogen, elixir-format
msgid "Disconnected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:294
#, elixir-autogen, elixir-format
msgid "Disconnected - Connection issues detected"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:53
#, elixir-autogen, elixir-format
msgid "Error Message"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:48
#, elixir-autogen, elixir-format
msgid "Error Type"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:298
#, elixir-autogen, elixir-format
msgid "Excellent - Long-term stable connection"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:131
#, elixir-autogen, elixir-format
msgid "Filter:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:64
#, elixir-autogen, elixir-format
msgid "Forbidden"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:296
#, elixir-autogen, elixir-format
msgid "Good - Connection stable for less than 1 hour"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:28
#, elixir-autogen, elixir-format
msgid "Internal server error"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:165
#, elixir-autogen, elixir-format
msgid "Last Packet:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:46
#, elixir-autogen, elixir-format
msgid "Last updated:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:105
#, elixir-autogen, elixir-format
msgid "Login ID:"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "No bad packets"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:55
#, elixir-autogen, elixir-format
msgid "No error message"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:170
#, elixir-autogen, elixir-format
msgid "None"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:115
#: lib/aprsme_web/live/status_live/index.ex:262
#, elixir-autogen, elixir-format
msgid "Not connected"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:141
#, elixir-autogen, elixir-format
msgid "Packet Statistics"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:158
#, elixir-autogen, elixir-format
msgid "Packets/Min:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:151
#, elixir-autogen, elixir-format
msgid "Packets/Sec:"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:305
#, elixir-autogen, elixir-format
msgid "RELAY (Relay digipeater)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:308
#, elixir-autogen, elixir-format
msgid "RELAY digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:306
#, elixir-autogen, elixir-format
msgid "RELAY-1 (Relay digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:307
#, elixir-autogen, elixir-format
msgid "RELAY-2 (Relay digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:58
#: lib/aprsme_web/live/info_live/show.html.heex:227
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:148
#, elixir-autogen, elixir-format
msgid "Raw Packet"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:295
#, elixir-autogen, elixir-format
msgid "Recently connected - Monitoring stability"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:57
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:73
#, elixir-autogen, elixir-format
msgid "Request timeout"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:68
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:19
#, elixir-autogen, elixir-format
msgid "Resource not found"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:98
#, elixir-autogen, elixir-format
msgid "Server:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:83
#, elixir-autogen, elixir-format
msgid "Status:"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:176
#, elixir-autogen, elixir-format
msgid "Stored Packets:"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:470
#, elixir-autogen, elixir-format
msgid "TCPIP (Internet gateway)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:472
#, elixir-autogen, elixir-format
msgid "TCPIP gateway (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:471
#, elixir-autogen, elixir-format
msgid "TCPIP* (Internet gateway, no forward)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:299
#, elixir-autogen, elixir-format
msgid "TRACE digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:292
#, elixir-autogen, elixir-format
msgid "TRACE1-1 (Trace digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:293
#, elixir-autogen, elixir-format
msgid "TRACE2-1 (Trace digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:294
#, elixir-autogen, elixir-format
msgid "TRACE3-1 (Trace digipeater, 3 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:295
#, elixir-autogen, elixir-format
msgid "TRACE4-1 (Trace digipeater, 4 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:296
#, elixir-autogen, elixir-format
msgid "TRACE5-1 (Trace digipeater, 5 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:297
#, elixir-autogen, elixir-format
msgid "TRACE6-1 (Trace digipeater, 6 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:298
#, elixir-autogen, elixir-format
msgid "TRACE7-1 (Trace digipeater, 7 hops)"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:70
#, elixir-autogen, elixir-format
msgid "The system is currently disconnected from the APRS-IS network. This may be due to network issues or server maintenance."
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:144
#, elixir-autogen, elixir-format
msgid "Total Packets:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:55
#, elixir-autogen, elixir-format
msgid "Unauthorized"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Unauthorized access"
msgstr ""
#: lib/aprsme/device_identification.ex:56
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:50
#: lib/aprsme_web/live/info_live/show.html.heex:143
#: lib/aprsme_web/live/info_live/show.html.heex:209
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:92
#, elixir-autogen, elixir-format
msgid "Unknown"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:299
#, elixir-autogen, elixir-format
msgid "Unknown status"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:37
#, elixir-autogen, elixir-format
msgid "Unprocessable entity"
msgstr ""
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Updating..."
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:121
#, elixir-autogen, elixir-format
msgid "Uptime:"
msgstr ""
#: lib/aprsme_web/controllers/api/v1/json/changeset_json.ex:13
#, elixir-autogen, elixir-format
msgid "Validation failed"
msgstr ""
#: lib/aprsme_web/live/status_live/index.ex:297
#, elixir-autogen, elixir-format
msgid "Very good - Connection stable for less than 1 day"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:286
#, elixir-autogen, elixir-format
msgid "WIDE digipeater (%{element})"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:277
#, elixir-autogen, elixir-format
msgid "WIDE1-1 (Wide area digipeater, 1 hop)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:284
#, elixir-autogen, elixir-format
msgid "WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:278
#, elixir-autogen, elixir-format
msgid "WIDE2-1 (Wide area digipeater, 2 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:285
#, elixir-autogen, elixir-format
msgid "WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:279
#, elixir-autogen, elixir-format
msgid "WIDE3-1 (Wide area digipeater, 3 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:280
#, elixir-autogen, elixir-format
msgid "WIDE4-1 (Wide area digipeater, 4 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:281
#, elixir-autogen, elixir-format
msgid "WIDE5-1 (Wide area digipeater, 5 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:282
#, elixir-autogen, elixir-format
msgid "WIDE6-1 (Wide area digipeater, 6 hops)"
msgstr ""
#: lib/aprsme_web/live/info_live/show.ex:283
#, elixir-autogen, elixir-format
msgid "WIDE7-1 (WIDE area digipeater, 7 hops)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:17
#, elixir-autogen, elixir-format
msgid "less than a minute"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:118
#, elixir-autogen, elixir-format
msgid "Gusts"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:103
#, elixir-autogen, elixir-format
msgid "Humidity"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:84
#, elixir-autogen, elixir-format
msgid "Last WX report"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:124
#, elixir-autogen, elixir-format
msgid "Pressure"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:130
#, elixir-autogen, elixir-format
msgid "Rain (1h)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:136
#, elixir-autogen, elixir-format
msgid "Rain (24h)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:142
#, elixir-autogen, elixir-format
msgid "Rain (since midnight)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:159
#, elixir-autogen, elixir-format
msgid "Raw comment"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:97
#, elixir-autogen, elixir-format
msgid "Temperature"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:43
#, elixir-autogen, elixir-format
msgid "View info"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:49
#, elixir-autogen, elixir-format
msgid "View on map"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:67
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:46
#, elixir-autogen, elixir-format
msgid "View packets"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:79
#, elixir-autogen, elixir-format
msgid "Weather Details"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:165
#, elixir-autogen, elixir-format
msgid "Weather History Graphs"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:8
#, elixir-autogen, elixir-format
msgid "Weather Station"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:109
#, elixir-autogen, elixir-format
msgid "Wind"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:27
#, elixir-autogen, elixir-format
msgid "APRS Station"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:149
#, elixir-autogen, elixir-format
msgid "Altitude"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:10
#, elixir-autogen, elixir-format
msgid "Base Callsign"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:12
#, elixir-autogen, elixir-format
msgid "Built with Modern Technologies"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:270
#: lib/aprsme_web/live/info_live/show.html.heex:388
#, elixir-autogen, elixir-format
msgid "Callsign"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:155
#, elixir-autogen, elixir-format
msgid "Course"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:11
#, elixir-autogen, elixir-format
msgid "Data Type"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:220
#, elixir-autogen, elixir-format
msgid "Decoded Path"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:194
#: lib/aprsme_web/live/packets_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "Device"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:189
#, elixir-autogen, elixir-format
msgid "Device Information"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:391
#, elixir-autogen, elixir-format
msgid "Distance & Course"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:273
#, elixir-autogen, elixir-format
msgid "Distance & Direction"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:35
#, elixir-autogen, elixir-format
msgid "Explore the Map"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:121
#, elixir-autogen, elixir-format
msgid "Grid Square"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:21
#, elixir-autogen, elixir-format
msgid "Join Our Community"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:276
#: lib/aprsme_web/live/info_live/show.html.heex:394
#, elixir-autogen, elixir-format
msgid "Last Heard"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:133
#, elixir-autogen, elixir-format
msgid "Last Position"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:14
#, elixir-autogen, elixir-format
msgid "Latitude"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:114
#, elixir-autogen, elixir-format
msgid "Location"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:15
#, elixir-autogen, elixir-format
msgid "Longitude"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:469
#, elixir-autogen, elixir-format
msgid "No data available"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:164
#, elixir-autogen, elixir-format, fuzzy
msgid "No packets"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:471
#, elixir-autogen, elixir-format
msgid "No recent packet data available for this callsign."
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:262
#, elixir-autogen, elixir-format
msgid "Other SSIDs"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:165
#, elixir-autogen, elixir-format
msgid "Packets will appear here as they are received."
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:215
#: lib/aprsme_web/live/packets_live/index.html.heex:13
#, elixir-autogen, elixir-format
msgid "Path"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:109
#, elixir-autogen, elixir-format
msgid "Position Information"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "SSID"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:8
#, elixir-autogen, elixir-format
msgid "Sender"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:161
#, elixir-autogen, elixir-format
msgid "Speed"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:380
#, elixir-autogen, elixir-format
msgid "Stations Near Current Position"
msgstr ""
#: lib/aprsme_web/live/packets_live/index.html.heex:12
#, elixir-autogen, elixir-format
msgid "Symbol"
msgstr ""
#: lib/aprsme_web/live/about_live.html.heex:4
#, elixir-autogen, elixir-format
msgid "Under heavy construction"
msgstr ""
#: lib/aprsme_web/live/info_live/show.html.heex:71
#, elixir-autogen, elixir-format, fuzzy
msgid "Weather charts"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:63
#, elixir-autogen, elixir-format
msgid "%"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:53
#, elixir-autogen, elixir-format
msgid "Dew Point (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:54
#: lib/aprsme_web/live/weather_live/callsign_view.ex:55
#, elixir-autogen, elixir-format, fuzzy
msgid "Humidity (%)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:56
#: lib/aprsme_web/live/weather_live/callsign_view.ex:57
#, elixir-autogen, elixir-format, fuzzy
msgid "Pressure (mb)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:51
#, elixir-autogen, elixir-format
msgid "Temperature & Dew Point (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:52
#, elixir-autogen, elixir-format, fuzzy
msgid "Temperature (°F)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:61
#, elixir-autogen, elixir-format
msgid "Time"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:58
#, elixir-autogen, elixir-format
msgid "Wind (mph)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:60
#, elixir-autogen, elixir-format
msgid "Wind Gust (mph)"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:59
#, elixir-autogen, elixir-format
msgid "Wind Speed (mph)"
msgstr ""
#: lib/aprsme_web/time_helpers.ex:14
#, elixir-autogen, elixir-format
msgid "ago"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:64
#, elixir-autogen, elixir-format
msgid "mb"
msgstr ""
#: lib/aprsme_web/live/weather_live/callsign_view.ex:62
#, elixir-autogen, elixir-format
msgid "°F"
msgstr ""

View file

@ -12,15 +12,15 @@ msgstr ""
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
msgstr "can't be blank"
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
msgstr "has already been taken"
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
msgstr "is invalid"
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"

View file

@ -7,7 +7,6 @@
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""

View file

@ -0,0 +1,905 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/aprsme_web/components/core_components.ex:570
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr "Acciones"
#: lib/aprsme_web/components/core_components.ex:117
#: lib/aprsme_web/components/core_components.ex:202
#, elixir-autogen, elixir-format
msgid "close"
msgstr "cerrar"
#: lib/aprsme_web/time_helpers.ex:23
#, elixir-autogen, elixir-format
msgid "%{count} days"
msgstr "%{count} días"
#: lib/aprsme_web/live/status_live/index.ex:310
#, elixir-autogen, elixir-format
msgid "%{count} days ago"
msgstr "hace %{count} días"
#: lib/aprsme_web/time_helpers.ex:21
#, elixir-autogen, elixir-format
msgid "%{count} hours"
msgstr "%{count} horas"
#: lib/aprsme_web/live/status_live/index.ex:309
#, elixir-autogen, elixir-format
msgid "%{count} hours ago"
msgstr "hace %{count} horas"
#: lib/aprsme_web/time_helpers.ex:19
#, elixir-autogen, elixir-format
msgid "%{count} minutes"
msgstr "%{count} minutos"
#: lib/aprsme_web/live/status_live/index.ex:308
#, elixir-autogen, elixir-format
msgid "%{count} minutes ago"
msgstr "hace %{count} minutos"
#: lib/aprsme_web/time_helpers.ex:26
#, elixir-autogen, elixir-format
msgid "%{count} months"
msgstr "%{count} meses"
#: lib/aprsme_web/live/status_live/index.ex:307
#, elixir-autogen, elixir-format
msgid "%{count} seconds ago"
msgstr "hace %{count} segundos"
#: lib/aprsme_web/time_helpers.ex:29
#, elixir-autogen, elixir-format
msgid "%{count} years"
msgstr "%{count} años"
#: lib/aprsme_web/live/info_live/show.ex:477
#, elixir-autogen, elixir-format
msgid "%{element} (Digipeater)"
msgstr "%{element} (Digipeater)"
#: lib/aprsme_web/live/info_live/show.ex:481
#, elixir-autogen, elixir-format
msgid "%{element} (Station)"
msgstr "%{element} (Estación)"
#: lib/aprsme_web/live/info_live/show.ex:485
#, elixir-autogen, elixir-format
msgid "%{element} (Unknown)"
msgstr "%{element} (Desconocido)"
#: lib/aprsme_web/time_helpers.ex:22
#, elixir-autogen, elixir-format
msgid "1 day"
msgstr "1 día"
#: lib/aprsme_web/time_helpers.ex:20
#, elixir-autogen, elixir-format
msgid "1 hour"
msgstr "1 hora"
#: lib/aprsme_web/time_helpers.ex:18
#, elixir-autogen, elixir-format
msgid "1 minute"
msgstr "1 minuto"
#: lib/aprsme_web/time_helpers.ex:24
#, elixir-autogen, elixir-format
msgid "1 month"
msgstr "1 mes"
#: lib/aprsme_web/time_helpers.ex:28
#, elixir-autogen, elixir-format
msgid "1 year"
msgstr "1 año"
#: lib/aprsme_web/live/status_live/index.ex:78
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection"
msgstr "Conexión APRS-IS"
#: lib/aprsme_web/live/status_live/index.ex:68
#, elixir-autogen, elixir-format
msgid "APRS-IS Connection Issue"
msgstr "Problema de Conexión APRS-IS"
#: lib/aprsme_web/live/status_live/index.ex:44
#, elixir-autogen, elixir-format
msgid "APRS.me System Status"
msgstr "Estado del Sistema APRS.me"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:70
#, elixir-autogen, elixir-format
msgid "Access forbidden"
msgstr "Acceso prohibido"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:85
#, elixir-autogen, elixir-format
msgid "All packets are parsing successfully!"
msgstr "¡Todos los paquetes se están analizando correctamente!"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:72
#, elixir-autogen, elixir-format
msgid "An error occurred: %{reason}"
msgstr "Ocurrió un error: %{reason}"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:40
#, elixir-autogen, elixir-format
msgid "Attempted At"
msgstr "Intentado En"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "Auto-refreshes every 5 seconds"
msgstr "Se actualiza automáticamente cada 5 segundos"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:71
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:46
#, elixir-autogen, elixir-format
msgid "Bad request"
msgstr "Solicitud incorrecta"
#: lib/aprsme_web/live/status_live/index.ex:87
#, elixir-autogen, elixir-format
msgid "Connected"
msgstr "Conectado"
#: lib/aprsme_web/live/status_live/index.ex:110
#, elixir-autogen, elixir-format
msgid "Connected Since:"
msgstr "Conectado Desde:"
#: lib/aprsme_web/live/status_live/index.ex:187
#, elixir-autogen, elixir-format
msgid "Connection Health:"
msgstr "Estado de la Conexión:"
#: lib/aprsme_web/live/status_live/index.ex:92
#, elixir-autogen, elixir-format
msgid "Disconnected"
msgstr "Desconectado"
#: lib/aprsme_web/live/status_live/index.ex:294
#, elixir-autogen, elixir-format
msgid "Disconnected - Connection issues detected"
msgstr "Desconectado - Problemas de conexión detectados"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:53
#, elixir-autogen, elixir-format
msgid "Error Message"
msgstr "Mensaje de Error"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:48
#, elixir-autogen, elixir-format
msgid "Error Type"
msgstr "Tipo de Error"
#: lib/aprsme_web/live/status_live/index.ex:298
#, elixir-autogen, elixir-format
msgid "Excellent - Long-term stable connection"
msgstr "Excelente - Conexión estable a largo plazo"
#: lib/aprsme_web/live/status_live/index.ex:131
#, elixir-autogen, elixir-format
msgid "Filter:"
msgstr "Filtro:"
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:64
#, elixir-autogen, elixir-format
msgid "Forbidden"
msgstr "Prohibido"
#: lib/aprsme_web/live/status_live/index.ex:296
#, elixir-autogen, elixir-format
msgid "Good - Connection stable for less than 1 hour"
msgstr "Bueno - Conexión estable por menos de 1 hora"
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:28
#, elixir-autogen, elixir-format
msgid "Internal server error"
msgstr "Error interno del servidor"
#: lib/aprsme_web/live/status_live/index.ex:165
#, elixir-autogen, elixir-format
msgid "Last Packet:"
msgstr "Último Paquete:"
#: lib/aprsme_web/live/status_live/index.ex:46
#, elixir-autogen, elixir-format
msgid "Last updated:"
msgstr "Última actualización:"
#: lib/aprsme_web/live/status_live/index.ex:105
#, elixir-autogen, elixir-format
msgid "Login ID:"
msgstr "ID de Inicio de Sesión:"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "No bad packets"
msgstr "No hay paquetes malos"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:55
#, elixir-autogen, elixir-format
msgid "No error message"
msgstr "Sin mensaje de error"
#: lib/aprsme_web/live/status_live/index.ex:170
#, elixir-autogen, elixir-format
msgid "None"
msgstr "Ninguno"
#: lib/aprsme_web/live/status_live/index.ex:115
#: lib/aprsme_web/live/status_live/index.ex:262
#, elixir-autogen, elixir-format
msgid "Not connected"
msgstr "No conectado"
#: lib/aprsme_web/live/status_live/index.ex:141
#, elixir-autogen, elixir-format
msgid "Packet Statistics"
msgstr "Estadísticas de Paquetes"
#: lib/aprsme_web/live/status_live/index.ex:158
#, elixir-autogen, elixir-format
msgid "Packets/Min:"
msgstr "Paquetes/Min:"
#: lib/aprsme_web/live/status_live/index.ex:151
#, elixir-autogen, elixir-format
msgid "Packets/Sec:"
msgstr "Paquetes/Seg:"
#: lib/aprsme_web/live/info_live/show.ex:305
#, elixir-autogen, elixir-format
msgid "RELAY (Relay digipeater)"
msgstr "RELAY (Digipeater de retransmisión)"
#: lib/aprsme_web/live/info_live/show.ex:308
#, elixir-autogen, elixir-format
msgid "RELAY digipeater (%{element})"
msgstr "Digipeater RELAY (%{element})"
#: lib/aprsme_web/live/info_live/show.ex:306
#, elixir-autogen, elixir-format
msgid "RELAY-1 (Relay digipeater, 1 hop)"
msgstr "RELAY-1 (Digipeater de retransmisión, 1 salto)"
#: lib/aprsme_web/live/info_live/show.ex:307
#, elixir-autogen, elixir-format
msgid "RELAY-2 (Relay digipeater, 2 hops)"
msgstr "RELAY-2 (Digipeater de retransmisión, 2 saltos)"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:58
#: lib/aprsme_web/live/info_live/show.html.heex:227
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:148
#, elixir-autogen, elixir-format
msgid "Raw Packet"
msgstr "Paquete Sin Procesar"
#: lib/aprsme_web/live/status_live/index.ex:295
#, elixir-autogen, elixir-format
msgid "Recently connected - Monitoring stability"
msgstr "Recientemente conectado - Monitoreando estabilidad"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:57
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:73
#, elixir-autogen, elixir-format
msgid "Request timeout"
msgstr "Tiempo de espera de la solicitud"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:68
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:19
#, elixir-autogen, elixir-format
msgid "Resource not found"
msgstr "Recurso no encontrado"
#: lib/aprsme_web/live/status_live/index.ex:98
#, elixir-autogen, elixir-format
msgid "Server:"
msgstr "Servidor:"
#: lib/aprsme_web/live/status_live/index.ex:83
#, elixir-autogen, elixir-format
msgid "Status:"
msgstr "Estado:"
#: lib/aprsme_web/live/status_live/index.ex:176
#, elixir-autogen, elixir-format
msgid "Stored Packets:"
msgstr "Paquetes Almacenados:"
#: lib/aprsme_web/live/info_live/show.ex:470
#, elixir-autogen, elixir-format
msgid "TCPIP (Internet gateway)"
msgstr "TCPIP (Puerta de enlace de Internet)"
#: lib/aprsme_web/live/info_live/show.ex:472
#, elixir-autogen, elixir-format
msgid "TCPIP gateway (%{element})"
msgstr "Puerta de enlace TCPIP (%{element})"
#: lib/aprsme_web/live/info_live/show.ex:471
#, elixir-autogen, elixir-format
msgid "TCPIP* (Internet gateway, no forward)"
msgstr "TCPIP* (Puerta de enlace de Internet, sin reenvío)"
#: lib/aprsme_web/live/info_live/show.ex:299
#, elixir-autogen, elixir-format
msgid "TRACE digipeater (%{element})"
msgstr "Digipeater TRACE (%{element})"
#: lib/aprsme_web/live/info_live/show.ex:292
#, elixir-autogen, elixir-format
msgid "TRACE1-1 (Trace digipeater, 1 hop)"
msgstr "TRACE1-1 (Digipeater de rastreo, 1 salto)"
#: lib/aprsme_web/live/info_live/show.ex:293
#, elixir-autogen, elixir-format
msgid "TRACE2-1 (Trace digipeater, 2 hops)"
msgstr "TRACE2-1 (Digipeater de rastreo, 2 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:294
#, elixir-autogen, elixir-format
msgid "TRACE3-1 (Trace digipeater, 3 hops)"
msgstr "TRACE3-1 (Digipeater de rastreo, 3 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:295
#, elixir-autogen, elixir-format
msgid "TRACE4-1 (Trace digipeater, 4 hops)"
msgstr "TRACE4-1 (Digipeater de rastreo, 4 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:296
#, elixir-autogen, elixir-format
msgid "TRACE5-1 (Trace digipeater, 5 hops)"
msgstr "TRACE5-1 (Digipeater de rastreo, 5 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:297
#, elixir-autogen, elixir-format
msgid "TRACE6-1 (Trace digipeater, 6 hops)"
msgstr "TRACE6-1 (Digipeater de rastreo, 6 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:298
#, elixir-autogen, elixir-format
msgid "TRACE7-1 (Trace digipeater, 7 hops)"
msgstr "TRACE7-1 (Digipeater de rastreo, 7 saltos)"
#: lib/aprsme_web/live/status_live/index.ex:70
#, elixir-autogen, elixir-format
msgid "The system is currently disconnected from the APRS-IS network. This may be due to network issues or server maintenance."
msgstr "El sistema está actualmente desconectado de la red APRS-IS. Esto puede deberse a problemas de red o mantenimiento del servidor."
#: lib/aprsme_web/live/status_live/index.ex:144
#, elixir-autogen, elixir-format
msgid "Total Packets:"
msgstr "Total de Paquetes:"
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:55
#, elixir-autogen, elixir-format
msgid "Unauthorized"
msgstr "No autorizado"
#: lib/aprsme_web/controllers/api/v1/fallback_controller.ex:69
#, elixir-autogen, elixir-format
msgid "Unauthorized access"
msgstr "Acceso no autorizado"
#: lib/aprsme/device_identification.ex:56
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:50
#: lib/aprsme_web/live/info_live/show.html.heex:143
#: lib/aprsme_web/live/info_live/show.html.heex:209
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:92
#, elixir-autogen, elixir-format
msgid "Unknown"
msgstr "Desconocido"
#: lib/aprsme_web/live/status_live/index.ex:299
#, elixir-autogen, elixir-format
msgid "Unknown status"
msgstr "Estado desconocido"
#: lib/aprsme_web/controllers/api/v1/json/error_json.ex:37
#, elixir-autogen, elixir-format
msgid "Unprocessable entity"
msgstr "Entidad no procesable"
#: lib/aprsme_web/live/bad_packets_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Updating..."
msgstr "Actualizando..."
#: lib/aprsme_web/live/status_live/index.ex:121
#, elixir-autogen, elixir-format
msgid "Uptime:"
msgstr "Tiempo de Actividad:"
#: lib/aprsme_web/controllers/api/v1/json/changeset_json.ex:13
#, elixir-autogen, elixir-format
msgid "Validation failed"
msgstr "Validación fallida"
#: lib/aprsme_web/live/status_live/index.ex:297
#, elixir-autogen, elixir-format
msgid "Very good - Connection stable for less than 1 day"
msgstr "Muy bueno - Conexión estable por menos de 1 día"
#: lib/aprsme_web/live/info_live/show.ex:286
#, elixir-autogen, elixir-format
msgid "WIDE digipeater (%{element})"
msgstr "Digipeater WIDE (%{element})"
#: lib/aprsme_web/live/info_live/show.ex:277
#, elixir-autogen, elixir-format
msgid "WIDE1-1 (Wide area digipeater, 1 hop)"
msgstr "WIDE1-1 (Digipeater de área amplia, 1 salto)"
#: lib/aprsme_web/live/info_live/show.ex:284
#, elixir-autogen, elixir-format
msgid "WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)"
msgstr "WIDE1-2 (Digipeater de área amplia, 1 salto, 2º intento)"
#: lib/aprsme_web/live/info_live/show.ex:278
#, elixir-autogen, elixir-format
msgid "WIDE2-1 (Wide area digipeater, 2 hops)"
msgstr "WIDE2-1 (Digipeater de área amplia, 2 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:285
#, elixir-autogen, elixir-format
msgid "WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)"
msgstr "WIDE2-2 (Digipeater de área amplia, 2 saltos, 2º intento)"
#: lib/aprsme_web/live/info_live/show.ex:279
#, elixir-autogen, elixir-format
msgid "WIDE3-1 (Wide area digipeater, 3 hops)"
msgstr "WIDE3-1 (Digipeater de área amplia, 3 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:280
#, elixir-autogen, elixir-format
msgid "WIDE4-1 (Wide area digipeater, 4 hops)"
msgstr "WIDE4-1 (Digipeater de área amplia, 4 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:281
#, elixir-autogen, elixir-format
msgid "WIDE5-1 (Wide area digipeater, 5 hops)"
msgstr "WIDE5-1 (Digipeater de área amplia, 5 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:282
#, elixir-autogen, elixir-format
msgid "WIDE6-1 (Wide area digipeater, 6 hops)"
msgstr "WIDE6-1 (Digipeater de área amplia, 6 saltos)"
#: lib/aprsme_web/live/info_live/show.ex:283
#, elixir-autogen, elixir-format
msgid "WIDE7-1 (WIDE area digipeater, 7 hops)"
msgstr "WIDE7-1 (Digipeater de área amplia, 7 saltos)"
#: lib/aprsme_web/time_helpers.ex:17
#, elixir-autogen, elixir-format
msgid "less than a minute"
msgstr "menos de un minuto"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:118
#, elixir-autogen, elixir-format
msgid "Gusts"
msgstr "Ráfagas"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:103
#, elixir-autogen, elixir-format
msgid "Humidity"
msgstr "Humedad"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:84
#, elixir-autogen, elixir-format
msgid "Last WX report"
msgstr "Último reporte WX"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:124
#, elixir-autogen, elixir-format
msgid "Pressure"
msgstr "Presión"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:130
#, elixir-autogen, elixir-format
msgid "Rain (1h)"
msgstr "Lluvia (1h)"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:136
#, elixir-autogen, elixir-format
msgid "Rain (24h)"
msgstr "Lluvia (24h)"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:142
#, elixir-autogen, elixir-format
msgid "Rain (since midnight)"
msgstr "Lluvia (desde medianoche)"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:159
#, elixir-autogen, elixir-format
msgid "Raw comment"
msgstr "Comentario crudo"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:97
#, elixir-autogen, elixir-format
msgid "Temperature"
msgstr "Temperatura"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:43
#, elixir-autogen, elixir-format
msgid "View info"
msgstr "Ver información"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:49
#, elixir-autogen, elixir-format
msgid "View on map"
msgstr "Ver en mapa"
#: lib/aprsme_web/live/info_live/show.html.heex:67
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:46
#, elixir-autogen, elixir-format
msgid "View packets"
msgstr "Ver paquetes"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:79
#, elixir-autogen, elixir-format
msgid "Weather Details"
msgstr "Detalles del Clima"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:165
#, elixir-autogen, elixir-format
msgid "Weather History Graphs"
msgstr "Gráficos del Historial del Clima"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:8
#, elixir-autogen, elixir-format
msgid "Weather Station"
msgstr "Estación Meteorológica"
#: lib/aprsme_web/live/weather_live/callsign_view.html.heex:109
#, elixir-autogen, elixir-format
msgid "Wind"
msgstr "Viento"
#: lib/aprsme_web/live/info_live/show.html.heex:27
#, elixir-autogen, elixir-format
msgid "APRS Station"
msgstr "Estación APRS"
#: lib/aprsme_web/live/info_live/show.html.heex:149
#, elixir-autogen, elixir-format
msgid "Altitude"
msgstr "Altitud"
#: lib/aprsme_web/live/packets_live/index.html.heex:10
#, elixir-autogen, elixir-format
msgid "Base Callsign"
msgstr "Indicativo Base"
#: lib/aprsme_web/live/about_live.html.heex:12
#, elixir-autogen, elixir-format
msgid "Built with Modern Technologies"
msgstr "Construido con Tecnologías Modernas"
#: lib/aprsme_web/live/info_live/show.html.heex:270
#: lib/aprsme_web/live/info_live/show.html.heex:388
#, elixir-autogen, elixir-format
msgid "Callsign"
msgstr "Indicativo"
#: lib/aprsme_web/live/info_live/show.html.heex:155
#, elixir-autogen, elixir-format
msgid "Course"
msgstr "Rumbo"
#: lib/aprsme_web/live/packets_live/index.html.heex:11
#, elixir-autogen, elixir-format
msgid "Data Type"
msgstr "Tipo de Datos"
#: lib/aprsme_web/live/info_live/show.html.heex:220
#, elixir-autogen, elixir-format
msgid "Decoded Path"
msgstr "Ruta Decodificada"
#: lib/aprsme_web/live/info_live/show.html.heex:194
#: lib/aprsme_web/live/packets_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "Device"
msgstr "Dispositivo"
#: lib/aprsme_web/live/info_live/show.html.heex:189
#, elixir-autogen, elixir-format
msgid "Device Information"
msgstr "Información del Dispositivo"
#: lib/aprsme_web/live/info_live/show.html.heex:391
#, elixir-autogen, elixir-format
msgid "Distance & Course"
msgstr "Distancia y Rumbo"
#: lib/aprsme_web/live/info_live/show.html.heex:273
#, elixir-autogen, elixir-format
msgid "Distance & Direction"
msgstr "Distancia y Dirección"
#: lib/aprsme_web/live/about_live.html.heex:35
#, elixir-autogen, elixir-format
msgid "Explore the Map"
msgstr "Explorar el Mapa"
#: lib/aprsme_web/live/info_live/show.html.heex:121
#, elixir-autogen, elixir-format
msgid "Grid Square"
msgstr "Cuadrícula"
#: lib/aprsme_web/live/about_live.html.heex:21
#, elixir-autogen, elixir-format
msgid "Join Our Community"
msgstr "Únete a Nuestra Comunidad"
#: lib/aprsme_web/live/info_live/show.html.heex:276
#: lib/aprsme_web/live/info_live/show.html.heex:394
#, elixir-autogen, elixir-format
msgid "Last Heard"
msgstr "Último Escuchado"
#: lib/aprsme_web/live/info_live/show.html.heex:133
#, elixir-autogen, elixir-format
msgid "Last Position"
msgstr "Última Posición"
#: lib/aprsme_web/live/packets_live/index.html.heex:14
#, elixir-autogen, elixir-format
msgid "Latitude"
msgstr "Latitud"
#: lib/aprsme_web/live/info_live/show.html.heex:114
#, elixir-autogen, elixir-format
msgid "Location"
msgstr "Ubicación"
#: lib/aprsme_web/live/packets_live/index.html.heex:15
#, elixir-autogen, elixir-format
msgid "Longitude"
msgstr "Longitud"
#: lib/aprsme_web/live/info_live/show.html.heex:469
#, elixir-autogen, elixir-format
msgid "No data available"
msgstr "No hay datos disponibles"
#: lib/aprsme_web/live/packets_live/index.html.heex:164
#, elixir-autogen, elixir-format, fuzzy
msgid "No packets"
msgstr "No hay paquetes"
#: lib/aprsme_web/live/info_live/show.html.heex:471
#, elixir-autogen, elixir-format
msgid "No recent packet data available for this callsign."
msgstr "No hay datos de paquetes recientes disponibles para este indicativo."
#: lib/aprsme_web/live/info_live/show.html.heex:262
#, elixir-autogen, elixir-format
msgid "Other SSIDs"
msgstr "Otros SSIDs"
#: lib/aprsme_web/live/packets_live/index.html.heex:165
#, elixir-autogen, elixir-format
msgid "Packets will appear here as they are received."
msgstr "Los paquetes aparecerán aquí a medida que sean recibidos."
#: lib/aprsme_web/live/info_live/show.html.heex:215
#: lib/aprsme_web/live/packets_live/index.html.heex:13
#, elixir-autogen, elixir-format
msgid "Path"
msgstr "Ruta"
#: lib/aprsme_web/live/info_live/show.html.heex:109
#, elixir-autogen, elixir-format
msgid "Position Information"
msgstr "Información de Posición"
#: lib/aprsme_web/live/packets_live/index.html.heex:9
#, elixir-autogen, elixir-format
msgid "SSID"
msgstr "SSID"
#: lib/aprsme_web/live/packets_live/index.html.heex:8
#, elixir-autogen, elixir-format
msgid "Sender"
msgstr "Remitente"
#: lib/aprsme_web/live/info_live/show.html.heex:161
#, elixir-autogen, elixir-format
msgid "Speed"
msgstr "Velocidad"
#: lib/aprsme_web/live/info_live/show.html.heex:380
#, elixir-autogen, elixir-format
msgid "Stations Near Current Position"
msgstr "Estaciones Cerca de la Posición Actual"
#: lib/aprsme_web/live/packets_live/index.html.heex:12
#, elixir-autogen, elixir-format
msgid "Symbol"
msgstr "Símbolo"
#: lib/aprsme_web/live/about_live.html.heex:4
#, elixir-autogen, elixir-format
msgid "Under heavy construction"
msgstr "En construcción intensiva"
#: lib/aprsme_web/live/info_live/show.html.heex:71
#, elixir-autogen, elixir-format, fuzzy
msgid "Weather charts"
msgstr "Gráficos del clima"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:63
#, elixir-autogen, elixir-format
msgid "%"
msgstr "%"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:53
#, elixir-autogen, elixir-format
msgid "Dew Point (°F)"
msgstr "Punto de Rocío (°F)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:54
#: lib/aprsme_web/live/weather_live/callsign_view.ex:55
#, elixir-autogen, elixir-format
msgid "Humidity (%)"
msgstr "Humedad (%)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:56
#: lib/aprsme_web/live/weather_live/callsign_view.ex:57
#, elixir-autogen, elixir-format
msgid "Pressure (mb)"
msgstr "Presión (mb)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:51
#, elixir-autogen, elixir-format
msgid "Temperature & Dew Point (°F)"
msgstr "Temperatura y Punto de Rocío (°F)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:52
#, elixir-autogen, elixir-format
msgid "Temperature (°F)"
msgstr "Temperatura (°F)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:61
#, elixir-autogen, elixir-format
msgid "Time"
msgstr "Tiempo"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:58
#, elixir-autogen, elixir-format
msgid "Wind (mph)"
msgstr "Viento (mph)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:60
#, elixir-autogen, elixir-format
msgid "Wind Gust (mph)"
msgstr "Ráfaga de Viento (mph)"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:59
#, elixir-autogen, elixir-format
msgid "Wind Speed (mph)"
msgstr "Velocidad del Viento (mph)"
#: lib/aprsme_web/time_helpers.ex:14
#, elixir-autogen, elixir-format
msgid "ago"
msgstr "hace"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:64
#, elixir-autogen, elixir-format
msgid "mb"
msgstr "mb"
#: lib/aprsme_web/live/weather_live/callsign_view.ex:62
#, elixir-autogen, elixir-format
msgid "°F"
msgstr "°F"
#: lib/aprsme_web/live/map_live/index.ex:611
#, elixir-autogen, elixir-format
msgid "Find my location"
msgstr "Encontrar mi ubicación"
#: lib/aprsme_web/live/map_live/index.ex:633
#, elixir-autogen, elixir-format
msgid "Hide controls"
msgstr "Ocultar controles"
#: lib/aprsme_web/live/map_live/index.ex:633
#, elixir-autogen, elixir-format
msgid "Show controls"
msgstr "Mostrar controles"
#: lib/aprsme_web/live/map_live/index.ex:670
#, elixir-autogen, elixir-format
msgid "Close controls"
msgstr "Cerrar controles"
#: lib/aprsme_web/live/map_live/index.ex:690
#, elixir-autogen, elixir-format
msgid "Search Callsign"
msgstr "Buscar Indicativo"
#: lib/aprsme_web/live/map_live/index.ex:695
#, elixir-autogen, elixir-format
msgid "Enter callsign..."
msgstr "Ingrese indicativo..."
#: lib/aprsme_web/live/map_live/index.ex:700
#, elixir-autogen, elixir-format
msgid "Search"
msgstr "Buscar"
#: lib/aprsme_web/live/map_live/index.ex:710
#, elixir-autogen, elixir-format
msgid "Trail Duration"
msgstr "Duración del Rastro"
#: lib/aprsme_web/live/map_live/index.ex:715
#, elixir-autogen, elixir-format
msgid "1 Hour"
msgstr "1 Hora"
#: lib/aprsme_web/live/map_live/index.ex:716
#, elixir-autogen, elixir-format
msgid "6 Hours"
msgstr "6 Horas"
#: lib/aprsme_web/live/map_live/index.ex:717
#, elixir-autogen, elixir-format
msgid "12 Hours"
msgstr "12 Horas"
#: lib/aprsme_web/live/map_live/index.ex:718
#, elixir-autogen, elixir-format
msgid "24 Hours"
msgstr "24 Horas"
#: lib/aprsme_web/live/map_live/index.ex:719
#, elixir-autogen, elixir-format
msgid "48 Hours"
msgstr "48 Horas"
#: lib/aprsme_web/live/map_live/index.ex:720
#, elixir-autogen, elixir-format
msgid "1 Week"
msgstr "1 Semana"
#: lib/aprsme_web/live/map_live/index.ex:730
#, elixir-autogen, elixir-format
msgid "Historical Data"
msgstr "Datos Históricos"
#: lib/aprsme_web/live/map_live/index.ex:735
#, elixir-autogen, elixir-format
msgid "3 Hours"
msgstr "3 Horas"
#: lib/aprsme_web/live/map_live/index.ex:750
#, elixir-autogen, elixir-format
msgid "Navigation"
msgstr "Navegación"
#: lib/aprsme_web/live/map_live/index.ex:760
#, elixir-autogen, elixir-format
msgid "Last Deploy"
msgstr "Último Despliegue"

View file

@ -0,0 +1,102 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "can't be blank"
msgstr "no puede estar en blanco"
msgid "has already been taken"
msgstr "ya ha sido tomado"
msgid "is invalid"
msgstr "no es válido"
msgid "must be accepted"
msgstr "debe ser aceptado"
msgid "has invalid format"
msgstr "tiene formato inválido"
msgid "has an invalid entry"
msgstr "tiene una entrada inválida"
msgid "is reserved"
msgstr "está reservado"
msgid "does not match confirmation"
msgstr "no coincide con la confirmación"
msgid "is still associated with this entry"
msgstr "todavía está asociado con esta entrada"
msgid "are still associated with this entry"
msgstr "todavía están asociados con esta entrada"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "debe tener %{count} elemento"
msgstr[1] "debe tener %{count} elementos"
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "debe ser %{count} carácter"
msgstr[1] "debe ser %{count} caracteres"
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] "debe ser %{count} byte"
msgstr[1] "debe ser %{count} bytes"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "debe tener al menos %{count} elemento"
msgstr[1] "debe tener al menos %{count} elementos"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "debe ser al menos %{count} carácter"
msgstr[1] "debe ser al menos %{count} caracteres"
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] "debe ser al menos %{count} byte"
msgstr[1] "debe ser al menos %{count} bytes"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "debe tener como máximo %{count} elemento"
msgstr[1] "debe tener como máximo %{count} elementos"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "debe ser como máximo %{count} carácter"
msgstr[1] "debe ser como máximo %{count} caracteres"
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] "debe ser como máximo %{count} byte"
msgstr[1] "debe ser como máximo %{count} bytes"
msgid "must be less than %{number}"
msgstr "debe ser menor que %{number}"
msgid "must be greater than %{number}"
msgstr "debe ser mayor que %{number}"
msgid "must be less than or equal to %{number}"
msgstr "debe ser menor o igual a %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "debe ser mayor o igual a %{number}"
msgid "must be equal to %{number}"
msgstr "debe ser igual a %{number}"

View file

@ -0,0 +1,54 @@
defmodule AprsmeWeb.Plugs.SetLocaleTest do
use AprsmeWeb.ConnCase
alias AprsmeWeb.Plugs.SetLocale
test "sets Spanish locale when Accept-Language header contains es", %{conn: conn} do
conn
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|> fetch_session()
|> put_req_header("accept-language", "es-ES,es;q=0.9,en;q=0.8")
|> SetLocale.call([])
assert Gettext.get_locale(AprsmeWeb.Gettext) == "es"
end
test "sets English locale when Accept-Language header contains en", %{conn: conn} do
conn
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|> fetch_session()
|> put_req_header("accept-language", "en-US,en;q=0.9,es;q=0.8")
|> SetLocale.call([])
assert Gettext.get_locale(AprsmeWeb.Gettext) == "en"
end
test "falls back to English when Accept-Language header contains unsupported locale", %{conn: conn} do
conn
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|> fetch_session()
|> put_req_header("accept-language", "fr-FR,fr;q=0.9,en;q=0.8")
|> SetLocale.call([])
assert Gettext.get_locale(AprsmeWeb.Gettext) == "en"
end
test "falls back to English when no Accept-Language header is present", %{conn: conn} do
conn
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|> fetch_session()
|> SetLocale.call([])
assert Gettext.get_locale(AprsmeWeb.Gettext) == "en"
end
test "prefers first supported locale in Accept-Language header", %{conn: conn} do
conn
|> Plug.Session.call(Plug.Session.init(store: :cookie, key: "_aprs_key", signing_salt: "test"))
|> fetch_session()
|> put_req_header("accept-language", "fr-FR,es-ES,en-US;q=0.9")
|> SetLocale.call([])
assert Gettext.get_locale(AprsmeWeb.Gettext) == "es"
end
end