diff --git a/scripts/generate-translations.js b/scripts/generate-translations.js index 0daabdb4..80d7d256 100644 --- a/scripts/generate-translations.js +++ b/scripts/generate-translations.js @@ -31,22 +31,10 @@ const universal = { 'weather.unit.mi': 'mi', 'weather.unit.mph': 'mph', // Wind directions are universal abbreviations - 'weather.wind.N': 'N', - 'weather.wind.NNE': 'NNE', - 'weather.wind.NE': 'NE', - 'weather.wind.ENE': 'ENE', - 'weather.wind.E': 'E', - 'weather.wind.ESE': 'ESE', - 'weather.wind.SE': 'SE', - 'weather.wind.SSE': 'SSE', - 'weather.wind.S': 'S', - 'weather.wind.SSW': 'SSW', - 'weather.wind.SW': 'SW', - 'weather.wind.WSW': 'WSW', - 'weather.wind.W': 'W', - 'weather.wind.WNW': 'WNW', - 'weather.wind.NW': 'NW', - 'weather.wind.NNW': 'NNW', + 'weather.wind.N': 'N', 'weather.wind.NNE': 'NNE', 'weather.wind.NE': 'NE', 'weather.wind.ENE': 'ENE', + 'weather.wind.E': 'E', 'weather.wind.ESE': 'ESE', 'weather.wind.SE': 'SE', 'weather.wind.SSE': 'SSE', + 'weather.wind.S': 'S', 'weather.wind.SSW': 'SSW', 'weather.wind.SW': 'SW', 'weather.wind.WSW': 'WSW', + 'weather.wind.W': 'W', 'weather.wind.WNW': 'WNW', 'weather.wind.NW': 'NW', 'weather.wind.NNW': 'NNW', // Plugin layer names that are proper nouns / brand names 'plugins.layers.wspr.name': 'WSPR', 'plugins.layers.rbn.title': 'RBN', @@ -238,8 +226,7 @@ const translations = { 'station.settings.dx.custom.port': 'Port', 'station.settings.dx.custom.port.placeholder': '7300', 'station.settings.dx.custom.title': '📡 Eigener Telnet-Server', - 'station.settings.dx.custom.warning': - '⚠️ Eigener Telnet erfordert Selbsthosting (Pi/lokal). Cloud-Hosting (Railway/openhamclock.app) blockiert ausgehende Telnet-Verbindungen.', + 'station.settings.dx.custom.warning': '⚠️ Eigener Telnet erfordert Selbsthosting (Pi/lokal). Cloud-Hosting (Railway/openhamclock.app) blockiert ausgehende Telnet-Verbindungen.', 'station.settings.headerSize': 'Rufzeichengröße', 'station.settings.layers.noLayers': 'Keine Kartenebenen verfügbar', 'station.settings.layers.opacity': 'Deckkraft', @@ -262,16 +249,14 @@ const translations = { 'station.settings.tab3.title': '⛊ Satelliten', 'station.settings.timezone.auto': 'Auto (Browser-Standard)', 'station.settings.timezone.currentDefault': ' Aktuell wird der Browser-Standard verwendet.', - 'station.settings.timezone.describe': - 'Setzen Sie dies, wenn Ihre Ortszeit falsch angezeigt wird (z.B. gleich wie UTC). Datenschutzbrowser wie Librewolf können Ihre Zeitzone verschleiern.', + 'station.settings.timezone.describe': 'Setzen Sie dies, wenn Ihre Ortszeit falsch angezeigt wird (z.B. gleich wie UTC). Datenschutzbrowser wie Librewolf können Ihre Zeitzone verschleiern.', 'station.settings.timezone.group.africa': 'Afrika', 'station.settings.timezone.group.asiaPacific': 'Asien & Pazifik', 'station.settings.timezone.group.europe': 'Europa', 'station.settings.timezone.group.northAmerica': 'Nordamerika', 'station.settings.timezone.group.other': 'Sonstige', 'station.settings.timezone.group.southAmerica': 'Südamerika', - 'station.settings.tip.env': - '💡 Tipp: Für permanente Konfiguration .env.example nach .env kopieren und CALLSIGN und LOCATOR setzen', + 'station.settings.tip.env': '💡 Tipp: Für permanente Konfiguration .env.example nach .env kopieren und CALLSIGN und LOCATOR setzen', 'weather.clouds': '☁️ Wolken', 'weather.condition.0': 'Klarer Himmel', 'weather.condition.1': 'Überwiegend klar', @@ -322,8 +307,7 @@ const translations = { es: { 'plugins.layers.floods.description': 'Inundaciones y tormentas severas activas en todo el mundo vía NASA EONET', 'plugins.layers.floods.name': 'Inundaciones y Tormentas', - 'plugins.layers.wildfires.description': - 'Incendios forestales activos en todo el mundo vía detección satelital NASA EONET', + 'plugins.layers.wildfires.description': 'Incendios forestales activos en todo el mundo vía detección satelital NASA EONET', 'plugins.layers.wildfires.name': 'Incendios Forestales', }, @@ -405,7 +389,7 @@ const translations = { 'plugins.layers.wspr.veryWeak': 'Très faible (< -20 dB)', 'plugins.layers.wspr.weak': 'Faible (-20 à -10 dB)', 'plugins.layers.wxradar.attribution': 'Données météo © Iowa State University Mesonet', - 'plugins.layers.wxradar.description': "Superposition radar météo NEXRAD pour l'Amérique du Nord", + 'plugins.layers.wxradar.description': 'Superposition radar météo NEXRAD pour l\'Amérique du Nord', 'plugins.layers.wxradar.name': 'Radar météo', 'propagation.day': 'Jour', 'propagation.estimated': 'estimé', @@ -431,8 +415,7 @@ const translations = { 'station.settings.dx.custom.port': 'Port', 'station.settings.dx.custom.port.placeholder': '7300', 'station.settings.dx.custom.title': '📡 Serveur Telnet personnalisé', - 'station.settings.dx.custom.warning': - "⚠️ Le telnet personnalisé nécessite un hébergement local (Pi/local). L'hébergement cloud (Railway/openhamclock.app) bloque les connexions telnet sortantes.", + 'station.settings.dx.custom.warning': '⚠️ Le telnet personnalisé nécessite un hébergement local (Pi/local). L\'hébergement cloud (Railway/openhamclock.app) bloque les connexions telnet sortantes.', 'station.settings.layers.noLayers': 'Aucune couche disponible', 'station.settings.layers.opacity': 'Opacité', 'station.settings.layers.title': 'Couches de carte', @@ -476,7 +459,7 @@ const translations = { 'weather.humidity': '💧 Humidité', 'weather.pressure': '🔵 Pression', 'weather.switchUnit': 'Passer en °{{unit}}', - 'weather.today': "Aujourd'hui", + 'weather.today': 'Aujourd\'hui', 'weather.uv': '☀️ UV', 'weather.visibility': '👁️ Visibilité', 'weather.wind': '💨 Vent', @@ -517,11 +500,7 @@ function applyTranslations(langCode, newTranslations) { // Sort keys alphabetically for consistency const sorted = {}; - Object.keys(merged) - .sort() - .forEach((k) => { - sorted[k] = merged[k]; - }); + Object.keys(merged).sort().forEach(k => { sorted[k] = merged[k]; }); fs.writeFileSync(filePath, JSON.stringify(sorted, null, 2) + '\n', 'utf8'); return added; @@ -546,9 +525,9 @@ for (const lang of universalOnly) { // Final report console.log('\n--- Coverage After ---'); -for (const lang of ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ms', 'nl', 'pt', 'sl']) { +for (const lang of ['de','es','fr','it','ja','ko','ms','nl','pt','sl']) { const data = JSON.parse(fs.readFileSync(path.join(LANG_DIR, lang + '.json'), 'utf8')); const count = Object.keys(data).length; - const pct = Math.round((count / enKeys.length) * 100); + const pct = Math.round(count / enKeys.length * 100); console.log(`${lang.toUpperCase().padEnd(4)} ${count}/${enKeys.length} = ${pct}%`); } diff --git a/src/DockableApp.jsx b/src/DockableApp.jsx index 294952c9..c6587d56 100644 --- a/src/DockableApp.jsx +++ b/src/DockableApp.jsx @@ -122,6 +122,7 @@ export const DockableApp = ({ toggleWWFF, toggleWWFFLabels, toggleSOTA, + toggleSOTALabels, toggleWWBOTA, toggleWWBOTALabels, toggleSatellites, @@ -192,6 +193,7 @@ export const DockableApp = ({ const toggleWWFFEff = useInternalMapLayers ? internalMap.toggleWWFF : toggleWWFF; const toggleWWFFLabelsEff = useInternalMapLayers ? internalMap.toggleWWFFLabels : toggleWWFFLabels; const toggleSOTAEff = useInternalMapLayers ? internalMap.toggleSOTA : toggleSOTA; + const toggleSOTALabelsEff = useInternalMapLayers ? internalMap.toggleSOTALabels : toggleSOTALabels; const toggleWWBOTAEff = useInternalMapLayers ? internalMap.toggleWWBOTA : toggleWWBOTA; const toggleWWBOTALabelsEff = useInternalMapLayers ? internalMap.toggleWWBOTALabels : toggleWWBOTALabels; const toggleSatellitesEff = useInternalMapLayers ? internalMap.toggleSatellites : toggleSatellites; @@ -1273,4 +1275,4 @@ export const DockableApp = ({ ); }; -export default DockableApp; +export default DockableApp; \ No newline at end of file diff --git a/src/components/APRSPanel.jsx b/src/components/APRSPanel.jsx index 53a98856..122ea776 100644 --- a/src/components/APRSPanel.jsx +++ b/src/components/APRSPanel.jsx @@ -7,7 +7,13 @@ import React, { useState, useMemo, useCallback } from 'react'; import CallsignLink from './CallsignLink.jsx'; import { getBandColor } from '../utils/bandColors.js'; -const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot }) => { +const APRSPanel = ({ + aprsData, + showOnMap, + onToggleMap, + onSpotClick, + onHoverSpot, +}) => { const { filteredStations = [], stations = [], @@ -35,7 +41,8 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot if (!search.trim()) return filteredStations; const q = search.toUpperCase(); return filteredStations.filter( - (s) => s.call?.includes(q) || s.ssid?.includes(q) || s.comment?.toUpperCase().includes(q), + (s) => + s.call?.includes(q) || s.ssid?.includes(q) || s.comment?.toUpperCase().includes(q), ); }, [filteredStations, search]); @@ -55,26 +62,17 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot } }, [addCallInput, addCallTarget, addCallToGroup]); - const formatAge = (minutes) => (minutes < 1 ? 'now' : minutes < 60 ? `${minutes}m` : `${Math.floor(minutes / 60)}h`); + const formatAge = (minutes) => + minutes < 1 ? 'now' : minutes < 60 ? `${minutes}m` : `${Math.floor(minutes / 60)}h`; if (!aprsEnabled) { return (
📍
APRS Not Enabled
-
- Add{' '} - - APRS_ENABLED=true - {' '} - to your .env file and restart the server. -
+
Add APRS_ENABLED=true to your .env file and restart the server.
- Optional: Set{' '} - - APRS_FILTER=r/{'{lat}'}/{'{lon}'}/500 - {' '} - to limit to 500km radius around your station. + Optional: Set APRS_FILTER=r/{'{lat}'}/{'{lon}'}/500 to limit to 500km radius around your station.
); @@ -83,27 +81,18 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot return (
{/* Header bar */} -
+
📍 APRS - + {displayStations.length}/{stations.length} @@ -115,12 +104,9 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot style={{ background: showGroupManager ? 'var(--accent-amber)' : 'var(--bg-tertiary)', border: '1px solid var(--border-color)', - borderRadius: '4px', - padding: '3px 8px', - fontSize: '11px', + borderRadius: '4px', padding: '3px 8px', fontSize: '11px', color: showGroupManager ? '#000' : 'var(--text-secondary)', - cursor: 'pointer', - fontFamily: 'inherit', + cursor: 'pointer', fontFamily: 'inherit', }} > 👥 Groups @@ -130,12 +116,9 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot style={{ background: showOnMap ? 'var(--accent-cyan)' : 'var(--bg-tertiary)', border: '1px solid var(--border-color)', - borderRadius: '4px', - padding: '3px 8px', - fontSize: '11px', + borderRadius: '4px', padding: '3px 8px', fontSize: '11px', color: showOnMap ? '#000' : 'var(--text-muted)', - cursor: 'pointer', - fontFamily: 'inherit', + cursor: 'pointer', fontFamily: 'inherit', }} > {showOnMap ? 'ON' : 'OFF'} @@ -144,16 +127,10 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot
{/* Group filter tabs */} -
+
{[ { key: 'all', label: `All (${stations.length})` }, ...(allWatchlistCalls.size > 0 ? [{ key: 'watchlist', label: `★ Watchlist` }] : []), @@ -163,16 +140,11 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot key={tab.key} onClick={() => setActiveGroup(tab.key)} style={{ - padding: '3px 8px', - fontSize: '10px', - borderRadius: '3px', - border: - watchlist.activeGroup === tab.key ? '1px solid var(--accent-amber)' : '1px solid var(--border-color)', + padding: '3px 8px', fontSize: '10px', borderRadius: '3px', + border: watchlist.activeGroup === tab.key ? '1px solid var(--accent-amber)' : '1px solid var(--border-color)', background: watchlist.activeGroup === tab.key ? 'var(--accent-amber)' : 'transparent', color: watchlist.activeGroup === tab.key ? '#000' : 'var(--text-muted)', - cursor: 'pointer', - fontFamily: 'inherit', - fontWeight: watchlist.activeGroup === tab.key ? '600' : '400', + cursor: 'pointer', fontFamily: 'inherit', fontWeight: watchlist.activeGroup === tab.key ? '600' : '400', }} > {tab.label} @@ -182,13 +154,10 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot {/* Group manager */} {showGroupManager && ( -
+
Watchlist Groups
@@ -204,28 +173,17 @@ const APRSPanel = ({ aprsData, showOnMap, onToggleMap, onSpotClick, onHoverSpot onKeyDown={(e) => e.key === 'Enter' && handleAddGroup()} placeholder="New group name..." style={{ - flex: 1, - padding: '4px 6px', - fontSize: '11px', - background: 'var(--bg-primary)', - border: '1px solid var(--border-color)', - borderRadius: '3px', - color: 'var(--text-primary)', - fontFamily: 'inherit', + flex: 1, padding: '4px 6px', fontSize: '11px', + background: 'var(--bg-primary)', border: '1px solid var(--border-color)', + borderRadius: '3px', color: 'var(--text-primary)', fontFamily: 'inherit', }} />
{station.comment && ( -
+
{station.comment}
)}
-
+
{formatAge(station.age)} {station.speed > 0 && {station.speed} kt}
diff --git a/src/components/PSKFilterManager.jsx b/src/components/PSKFilterManager.jsx index db089b37..6ecec5d4 100644 --- a/src/components/PSKFilterManager.jsx +++ b/src/components/PSKFilterManager.jsx @@ -138,9 +138,7 @@ export const PSKFilterManager = ({ filters, onFilterChange, isOpen, onClose }) = return (
- - Map Direction Filter - + Map Direction Filter
Useful when WSJT-X is also enabled — avoids duplicate plots for the same stations.
@@ -163,30 +161,14 @@ export const PSKFilterManager = ({ filters, onFilterChange, isOpen, onClose }) = textAlign: 'left', }} > - - {direction === opt.value ? '● ' : '○ '} - {opt.label} + + {direction === opt.value ? '● ' : '○ '}{opt.label} {opt.desc} ))}
-
+
Map Legend:
━━ TX (solid line, ● circle) diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index 3616972c..1a046b74 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -2285,6 +2285,7 @@ export const SettingsPanel = ({ > Map Overlays
+