From 1eb9e06c49c6935e99f109d32ee7130083e1e2bb Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 28 Oct 2026 17:00:16 +0400 Subject: [PATCH] i18n(de): apply review round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Meaning fixes: playback connects to the broadcaster when it starts, browser keys are restricted at the provider, clearing data layers switches them off (ausschalten, not löschen), cockpit inputs are Quellen, Electronic is Elektronische Musik, AIS types name vessels (SEGLER, LOTSENBOOT, SANITÄTSDIENST, SCHNELLSCHIFF), contiguous US excludes Alaska/Hawaii, wind and weather readings are model values, nearest frames are nearest in time, cloud imagery and the brightness filter say what they are. - NHC/CPHC advisories are Lageberichte, not Warnungen; cyclone strings use Wirbelsturm throughout. Glossary updated. - Grammar: " oder " keeps its space as a join separator, "{x} konnte nicht sauber beendet werden", Then: no longer doubles Dann, scene save reasons continue the sentence in lower case, neutral "Warten auf". - Spacing between numbers and units in the remaining nine readouts. - Reroute tooltip: German mode nouns keep their capital (Auto, Fahrrad). - Built-in Nepal shot titles localize in every language: the legacy scene upgrade rebuilds its base shots without sourcePackId, so the pack's own shots in the scene now vouch for them. - Layout: the layer status column no longer shrinks under a wide chip, and a mixed-case compound (ALPR-Kameras) may break at its hyphen; only capital codes (ADS-B, RTL-SDR) stay unbroken. WIRD AKTIVIERT overlapped the name. The Bhote step meta line may take a third line. Co-Authored-By: Claude Opus 5.5 --- docs/translations/de.md | 11 +- src/data/directions.test.mjs | 16 ++ src/i18n/locales/de.json | 246 +++++++++++++------------- src/layers/directions/index.js | 20 ++- src/scenes/recipes.js | 23 ++- src/scenes/sceneTitleDisplay.test.mjs | 22 +++ src/ui/layerPanel.js | 8 +- src/ui/layerPanel.test.mjs | 9 + src/ui/styles/bhote-koshi.css | 5 +- src/ui/styles/radio.css | 6 + 10 files changed, 232 insertions(+), 134 deletions(-) diff --git a/docs/translations/de.md b/docs/translations/de.md index a66141a58..7a1984f19 100644 --- a/docs/translations/de.md +++ b/docs/translations/de.md @@ -13,9 +13,10 @@ Translation source: abiyeenzo (#908). - Short labels, chips and HUD readouts stay short. German runs about a third longer than English, so prefer a short word or a standard abbreviation with a period over a wrapped line: REICHW., GESCHW., NIEDERSCHL., SONST., NACHR., VERST., VGL., KAL. - The avionics codes HDG, BRG, CRS, DEST, SPD, ALT, GS, TRK, V/S, FOV and SQUAWK stay in English, as in the cockpit. Words used in a sentence are translated: heading → «Kurs», bearing → «Peilung», range → «Reichweite». - A dash between clauses is a spaced en dash « – »; a colon is fine where it explains: «Feed ausgefallen – langsame Wiederholung», «SIMULIERT: Verkehrsdienst nicht erreichbar». Quotes are „…“. +- Status and waiting messages use the neutral infinitive too: «Warten auf Aktualisierung», not «Warte auf …». Reasons inserted after a dash stay lower case: «Szene nicht gespeichert – das Projekt ist größer als 5 MiB». - Keyboard keys are named as on German keyboards where they differ: Leertaste, Rücktaste, Umschalt; Esc, Enter, Tab and letter keys stay. - Decimal comma in fixed text: «M4,4», «28,2710° N». Thousands separator is a period: «1.000 km». Formatted numbers come from `Intl` and are already German. -- Units: km, m, km/h, m/s, kn (knots, also in the cockpit), ft (feet), hPa, MW, h, min, s, T (days, in short readouts); in UPPERCASE text KM, KM/H, KN, FT, MIN. A space between number and unit: «{value} m», «{count} min», «50 %». `°` sticks to the number. +- Units: km, m, km/h, m/s, kn (knots, also in the cockpit), ft (feet), hPa, MW, h, min, s, T (days, in short readouts); in UPPERCASE text KM, KM/H, KN, FT, MIN. A space between number and unit, in compact readouts too: «{value} m», «{count} min», «50 %», «{speed} KN», «{count} / 30 T». `°` sticks to the number. - "ago" goes before the time: «vor {count} min», «vor {value}». When a placeholder already holds a localized "ago" text (`{age}` in `STALE · cached {age}`, `{ago}`), do not add «vor» again. - Compass points use O for Ost: N, NNO, NO, ONO, O, OSO, SO, SSO, S, SSW, SW, WSW, W, WNW, NW, NNW; hemisphere letters N, S, O, W. - Routing: direction words are lowercase adverbs (links, leicht rechts, scharf links, geradeaus), so a sentence never starts with one: «Auf {road} {direction} abbiegen», «Dann {direction} abbiegen», «Weiter {direction}». Roundabout exits are ordinals with a period: «die {exit} Ausfahrt» → «die 2. Ausfahrt». @@ -36,7 +37,7 @@ Keep the verb in agreement: «{count} Station meldet» / «{count} Stationen mel | English | Deutsch | | ---------------------------------------------- | -------------------------------------------------------------------- | | layer / data layer | Ebene / Datenebene | -| feed / source / data source | Feed / Quelle / Datenquelle | +| feed / source / data source / input (cockpit) | Feed / Quelle / Datenquelle / Quelle | | LIVE (status) / live data | LIVE / Live-Daten | | SIMULATED | SIMULIERT | | stale / degraded / unavailable | veraltet / eingeschränkt / nicht verfügbar | @@ -57,7 +58,7 @@ Keep the verb in agreement: «{count} Station meldet» / «{count} Stationen mel | mapped / observed | kartiert / beobachtet | | camera / CCTV camera / camera badge | Kamera / CCTV-Kamera / Kamera-Markierung | | earthquake / active fire / fire perimeter | Erdbeben / aktiver Brand / Brandgrenze | -| cyclone / advisory (NHC/CPHC) / cone | Wirbelsturm / Warnung / Kegel | +| cyclone / advisory (NHC/CPHC) / cone | Wirbelsturm / Lagebericht (not «Warnung») / Kegel | | radio station | Sender | | broadcast FM | UKW | | road / flow (traffic) | Straße / Verkehrsfluss | @@ -88,3 +89,7 @@ Keep the verb in agreement: «{count} Station meldet» / «{count} Stationen mel | voice (control) / voice session | Sprachsteuerung / Sprachsitzung | | YOU / THIS / HERE (voice card labels) | EINGABE / DIES / HIER | | click / click to … | anklicken / … klicken | +| card (host card) / voice card | Eintrag (in hints), Infokarte / Sprachkarte | +| label (on the map) / clear (switch off layers) | Beschriftung / ausschalten (not «löschen») | +| station tag (radio filter) | Kategorie | +| MIC (dock button) | MIKRO | diff --git a/src/data/directions.test.mjs b/src/data/directions.test.mjs index bf6e2878e..42ed9382f 100644 --- a/src/data/directions.test.mjs +++ b/src/data/directions.test.mjs @@ -919,3 +919,19 @@ test('ramp and lane wording stays localized after choosing the maneuver form', ( ); } }); + +test('the reroute tooltip keeps German mode nouns capitalized', (t) => { + t.after(() => setLocale('en')); + const title = (id) => + directionsRowControls(idle).chips.find((chip) => chip.id === id).title; + assert.equal(title('mode-foot'), 'Walk — reroute for walk'); + setLocale('de'); + assert.equal( + title('mode-foot'), + 'Zu Fuß – Route im Modus „Zu Fuß“ neu berechnen', + ); + assert.equal( + title('mode-bike'), + 'Fahrrad – Route im Modus „Fahrrad“ neu berechnen', + ); +}); diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 8acbc0f6f..6b9800310 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -9,7 +9,7 @@ "Close data attribution": "Datenquellenhinweis schließen", "UNAVAILABLE": "NICHT VERFÜGBAR", "SOURCE": "QUELLE", - "Overpass rate-limited": "Overpass-Ratenlimit erreicht", + "Overpass rate-limited": "Overpass-Anfragelimit erreicht", "Overpass timed out": "Zeitüberschreitung bei Overpass", "Overpass could not complete the query": "Overpass konnte die Abfrage nicht abschließen", "Overpass temporarily unavailable": "Overpass vorübergehend nicht verfügbar", @@ -26,13 +26,13 @@ "observed or mapped nearby context": "beobachteter oder kartierter Kontext in der Nähe", "no observed or mapped objects in current feeds": "keine beobachteten oder kartierten Objekte in den aktuellen Feeds", "STATION": "STATION", - "Crewed stations and their visiting vehicles": "Bemannte Stationen und ihre Besucherfahrzeuge", + "Crewed stations and their visiting vehicles": "Bemannte Stationen und angedockte Raumfahrzeuge", "NAV": "NAV", "GNSS navigation — GPS, GLONASS, Galileo": "GNSS-Navigation: GPS, GLONASS, Galileo", "GEO": "GEO", "Geostationary belt — comms and weather, fixed over the equator": "Geostationärer Gürtel: Kommunikation und Wetter, fest über dem Äquator", "VISUAL": "VISUELL", - "Brightest naked-eye objects — CelesTrak visual group": "Hellste Objekte mit bloßem Auge: CelesTrak-Gruppe visual", + "Brightest naked-eye objects — CelesTrak visual group": "Hellste mit bloßem Auge sichtbare Objekte: CelesTrak-Gruppe „visual“", "COMMS": "KOMM", "Broadband constellation shell — shown only in DENSE mode": "Breitband-Konstellationsschale: nur im Modus DICHT sichtbar", "FISHING": "FISCHEREI", @@ -40,16 +40,16 @@ "DREDGER": "BAGGER", "DIVE OPS": "TAUCHEINSATZ", "MILITARY": "MILITÄR", - "SAILING": "SEGELN", + "SAILING": "SEGLER", "PLEASURE": "SPORTBOOT", - "PILOT": "LOTSE", + "PILOT": "LOTSENBOOT", "SAR": "SAR", "TUG": "SCHLEPPER", "PORT TENDER": "HAFENTENDER", "ANTI-POLLUTION": "UMWELTSCHUTZ", "LAW ENFORCE": "POLIZEI", - "MEDICAL": "MEDIZIN", - "HIGH-SPEED": "SCHNELLFAHRT", + "MEDICAL": "SANITÄTSDIENST", + "HIGH-SPEED": "SCHNELLSCHIFF", "PASSENGER": "PASSAGIER", "CARGO": "FRACHT", "TANKER": "TANKER", @@ -75,7 +75,7 @@ "PARTIAL": "TEILWEISE", "FALLBACK": "FALLBACK", "SUMMARY": "ÜBERSICHT", - "Awaiting telemetry...": "Warte auf Telemetrie...", + "Awaiting telemetry...": "Warten auf Telemetrie...", "REC": "REC", "AIS: --": "AIS: --", "POWER UP · {count} KEYS WAITING": { @@ -103,7 +103,7 @@ "CONTEXT READY": "KONTEXT BEREIT", "GLOBAL CONTEXT OFF": "GLOBALER KONTEXT AUS", "SELECT A FLIGHT, VESSEL, OR MAPPED INSTALLATION": "FLUG, SCHIFF ODER KARTIERTE ANLAGE WÄHLEN", - "ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "AKTIVIEREN, UM BEOBACHTETE / KARTIERTE NÄHE ZU LADEN", + "ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "AKTIVIEREN FÜR BEOBACHTETE / KARTIERTE OBJEKTE IM UMKREIS", "Unavailable": "Nicht verfügbar", "Focus {label}": "{label} fokussieren", "Global Context navigation": "Navigation des globalen Kontexts", @@ -132,11 +132,11 @@ "⚠️ Not renting": "⚠️ Keine Ausleihe", "⚠️ Not returning": "⚠️ Keine Rückgabe", "HDG {value}° (ESTIMATED)": "HDG {value}° (GESCHÄTZT)", - "{count} CAMERAS STANDING BY · NO CAMERA SELECTED · CLICK A CAMERA TO ACTIVATE": "{count} KAMERAS BEREIT · KEINE KAMERA GEWÄHLT · KAMERA ANKLICKEN ZUM AKTIVIEREN", + "{count} CAMERAS STANDING BY · NO CAMERA SELECTED · CLICK A CAMERA TO ACTIVATE": "KAMERAS BEREIT: {count} · KEINE KAMERA GEWÄHLT · KAMERA ANKLICKEN ZUM AKTIVIEREN", "No cameras available in catalog.": "Keine Kameras im Katalog verfügbar.", "{city} CCTV": "{city} CCTV", "FOV {value}°": "FOV {value}°", - "COVERAGE {value}km²": "ABDECKUNG {value}km²", + "COVERAGE {value}km²": "ABDECKUNG {value} km²", "OVERLAP {count} cams": "ÜBERLAPPUNG {count} Kam.", "ISOLATED VIEW": "EINZELANSICHT", "PROJ MONITOR": "PROJ MONITOR", @@ -146,7 +146,7 @@ "SRC {value}": "QUELLE {value}", "{value} CONTEXT": "{value} KONTEXT", "Focus fire detection {title}, {details}": "Branderkennung {title} fokussieren, {details}", - "{value} conf": "{value} Konf.", + "{value} conf": "Konf. {value}", "{value} ago": "vor {value}", "sensor n/a": "Sensor k. A.", "FIRE · {frp} MW": "BRAND · {frp} MW", @@ -185,7 +185,7 @@ "FLIGHT {count}": "FLUG {count}", "REUSED": "WIEDERVERWENDET", "NO STAGE RE-ENTRY / RECOVERY DATA": "KEINE DATEN ZU STUFEN-WIEDEREINTRITT / BERGUNG", - "{count} / 30D": "{count} / 30T", + "{count} / 30D": "{count} / 30 T", "NO MISSIONS AVAILABLE IN THE CURRENT 30-DAY WINDOW": "KEINE MISSIONEN IM AKTUELLEN 30-TAGE-FENSTER", "DATE UNAVAILABLE": "DATUM NICHT VERFÜGBAR", "UNSPECIFIED OPERATOR": "BETREIBER NICHT ANGEGEBEN", @@ -224,7 +224,7 @@ "PLANNED · ": "GEPLANT · ", "SUPPLIED TRAJECTORY POINTS": "GELIEFERTE BAHNPUNKTE", "RECONSTRUCTED ESTIMATE": "REKONSTRUIERTE SCHÄTZUNG", - "Replay the estimated ascent with a following camera": "Geschätzten Aufstieg mit folgender Kamera abspielen", + "Replay the estimated ascent with a following camera": "Geschätzten Aufstieg mit einer mitfliegenden Kamera abspielen", "Resume replay": "Wiedergabe fortsetzen", "T minus {seconds}": "T minus {seconds}", "Preparing launch site": "Startplatz wird vorbereitet", @@ -255,7 +255,7 @@ "Classical": "Klassik", "Country": "Country", "Dance": "Dance", - "Electronic": "Elektronik", + "Electronic": "Elektronische Musik", "Folk": "Folk", "Funk": "Funk", "Hip-Hop": "Hip-Hop", @@ -278,18 +278,18 @@ "Loading the Starlink shell…": "Starlink-Schale wird geladen…", "Starlink {detail} — click to retry": "Starlink {detail}: zum erneuten Versuch klicken", "load failed": "Laden fehlgeschlagen", - "Showing the full Starlink shell — click for the core catalog only": "Komplette Starlink-Schale sichtbar: klicken für nur den Kernkatalog", + "Showing the full Starlink shell — click for the core catalog only": "Komplette Starlink-Schale sichtbar: klicken, um nur den Kernkatalog zu zeigen", "DENSE ···": "DICHT ···", "DENSE ✕": "DICHT ✕", "DENSE": "DICHT", "Syncing flow · Roads: {source}": "Verkehrsfluss wird synchronisiert · Straßen: {source}", "LIVE · Roads: {source} · Roads without flow hidden": "LIVE · Straßen: {source} · Straßen ohne Verkehrsdaten ausgeblendet", "LIVE · Roads: {source} · No flow roads in view": "LIVE · Straßen: {source} · Keine Straßen mit Verkehrsdaten im Bild", - "LIVE · Roads: {source} · Flow {percent}%": "LIVE · Straßen: {source} · Verkehr {percent}%", - "SIMULATED · Roads: {source} · Flow {percent}%": "SIMULIERT · Straßen: {source} · Verkehr {percent}%", + "LIVE · Roads: {source} · Flow {percent}%": "LIVE · Straßen: {source} · Verkehr {percent} %", + "SIMULATED · Roads: {source} · Flow {percent}%": "SIMULIERT · Straßen: {source} · Verkehr {percent} %", "Syncing flow · Roads: OpenStreetMap · Flow: TomTom · Unmatched: simulated": "Verkehrsfluss wird synchronisiert · Straßen: OpenStreetMap · Verkehr: TomTom · Ohne Zuordnung: simuliert", - "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov · Unmatched: simulated": "LIVE · Straßen: OpenStreetMap · Verkehr: TomTom · {percent}% Abd. · Ohne Zuordnung: simuliert", - "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov": "LIVE · Straßen: OpenStreetMap · Verkehr: TomTom · {percent}% Abd.", + "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov · Unmatched: simulated": "LIVE · Straßen: OpenStreetMap · Verkehr: TomTom · {percent} % Abd. · Ohne Zuordnung: simuliert", + "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov": "LIVE · Straßen: OpenStreetMap · Verkehr: TomTom · {percent} % Abd.", "SIMULATED · Roads: OpenStreetMap · Flow: TomTom (no matches)": "SIMULIERT · Straßen: OpenStreetMap · Verkehr: TomTom (keine Treffer)", "SIMULATED — traffic service unreachable": "SIMULIERT: Verkehrsdienst nicht erreichbar", "SIMULATED — add TomTom key for live": "SIMULIERT: TomTom-Schlüssel für Live-Daten hinzufügen", @@ -298,9 +298,9 @@ "VESSEL": "SCHIFF", "POS: LIVE": "POS: LIVE", "POS: {time}Z": "POS: {time}Z", - "awaiting first AIS position…": "warte auf erste AIS-Position…", - "awaiting usable AIS positions…": "warte auf nutzbare AIS-Positionen…", - "awaiting first AIS message…": "warte auf erste AIS-Nachricht…", + "awaiting first AIS position…": "warten auf erste AIS-Position…", + "awaiting usable AIS positions…": "warten auf nutzbare AIS-Positionen…", + "awaiting first AIS message…": "warten auf erste AIS-Nachricht…", "Resume": "Fortsetzen", "Pause": "Pause", "Error: {message}": "Fehler: {message}", @@ -363,7 +363,7 @@ "CAL · {badge}": "KAL · {badge}", "MONITOR": "MONITOR", "OFF": "AUS", - "{city} · HDG {heading} · FOV {fov} · RANGE {range}m · {projection}{calBadge} · {provider}{status}": "{city} · HDG {heading} · FOV {fov} · REICHW. {range}m · {projection}{calBadge} · {provider}{status}", + "{city} · HDG {heading} · FOV {fov} · RANGE {range}m · {projection}{calBadge} · {provider}{status}": "{city} · HDG {heading} · FOV {fov} · REICHW. {range} m · {projection}{calBadge} · {provider}{status}", "{count} cameras loaded · click a camera to activate": { "one": "{count} Kamera geladen · Kamera anklicken zum Aktivieren", "other": "{count} Kameras geladen · Kamera anklicken zum Aktivieren" @@ -377,8 +377,8 @@ "No summary available.": "Keine Zusammenfassung verfügbar.", "loading frames": "Frames werden geladen", "camera grid ready": "Kameraraster bereit", - "Clearing selected data layers": "Ausgewählte Datenebenen werden gelöscht", - "Clear selected data layers": "Ausgewählte Datenebenen löschen", + "Clearing selected data layers": "Ausgewählte Datenebenen werden ausgeschaltet", + "Clear selected data layers": "Ausgewählte Datenebenen ausschalten", "CONDITIONS UNKNOWN": "BEDINGUNGEN UNBEKANNT", "CLEAR": "KLAR", "PARTLY CLOUDY": "TEILS BEWÖLKT", @@ -393,14 +393,14 @@ "CYCLE ON": "WECHSEL EIN", "CYCLE OFF": "WECHSEL AUS", "Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.": "Automatischen Seitenwechsel stoppen. Zurück, Weiter und die Tabs SIG/NEWS/LOKAL bleiben verfügbar.", - "Cycle briefing pages automatically every 9 seconds (Signals → News → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way.": "Briefing-Seiten automatisch alle 9 Sekunden wechseln (Signale → News → Lokal). Pausiert, solange der Bereich überfahren oder fokussiert ist. Live-Signaldaten werden in beiden Fällen laufend aktualisiert.", + "Cycle briefing pages automatically every 9 seconds (Signals → News → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way.": "Briefing-Seiten automatisch alle 9 Sekunden wechseln (Signale → News → Lokal). Pausiert, solange sich der Mauszeiger über dem Bereich befindet oder dieser den Fokus hat. Live-Signaldaten werden in beiden Fällen laufend aktualisiert.", "ACQUIRING REGIONAL NEWS": "REGIONALE NEWS WERDEN ERFASST", "REGIONAL NEWS UNAVAILABLE": "REGIONALE NEWS NICHT VERFÜGBAR", "RESOLVING REGION": "REGION WIRD ERMITTELT", "REGION UNAVAILABLE": "REGION NICHT VERFÜGBAR", "NO RECENT LOCATION MATCHES": "KEINE AKTUELLEN ORTSTREFFER", "{domain} · {age}": "{domain} · {age}", - "CLOUD {pct}%": "WOLKEN {pct}%", + "CLOUD {pct}%": "WOLKEN {pct} %", "CLOUD UNKNOWN": "WOLKEN UNBEKANNT", "{source} · LOCATION QUERY": "{source} · ORTSABFRAGE", "REGIONAL NEWS": "REGIONALE NEWS", @@ -408,7 +408,7 @@ "ENABLE GLOBAL CONTEXT FOR PROXIMITY PINGS": "GLOBALEN KONTEXT FÜR NÄHE-PINGS AKTIVIEREN", "CONTACT LOST · LAST KNOWN READOUT · NOT AN ALL-CLEAR": "KONTAKT VERLOREN · LETZTER STAND · KEINE ENTWARNUNG", "CONTACT LOST · {subject}": "KONTAKT VERLOREN · {subject}", - "SUBJECT LEFT ITS FEED · READOUT HOLDING LAST KNOWN": "OBJEKT HAT SEINEN FEED VERLASSEN · ANZEIGE HÄLT LETZTEN STAND", + "SUBJECT LEFT ITS FEED · READOUT HOLDING LAST KNOWN": "OBJEKT NICHT MEHR IM FEED · ANZEIGE HÄLT LETZTEN STAND", "{cohort} · {contact}": "{cohort} · {contact}", "NO AVAILABLE EXAMPLE": "KEIN BEISPIEL VERFÜGBAR", "{cohort}, Unavailable": "{cohort}, nicht verfügbar", @@ -417,17 +417,17 @@ "L": "L", "R": "R", "{count} INPUTS UNKNOWN · NOT AN ALL-CLEAR": { - "one": "{count} EINGABE UNBEKANNT · KEINE ENTWARNUNG", - "other": "{count} EINGABEN UNBEKANNT · KEINE ENTWARNUNG" + "one": "STATUS UNKLAR: {count} QUELLE · KEINE ENTWARNUNG", + "other": "STATUS UNKLAR: {count} QUELLEN · KEINE ENTWARNUNG" }, - "AVAILABLE INPUTS CURRENT · NOT AN ALL-CLEAR": "VERFÜGBARE EINGABEN AKTUELL · KEINE ENTWARNUNG", + "AVAILABLE INPUTS CURRENT · NOT AN ALL-CLEAR": "VERFÜGBARE QUELLEN AKTUELL · KEINE ENTWARNUNG", "AIRCRAFT": "FLUGZEUG", "ACQUIRING SURFACE": "OBERFLÄCHE WIRD ERFASST", "SURFACE FALLBACK": "OBERFLÄCHEN-FALLBACK", "STALE FEED": "VERALTETER FEED", "LIVE TRACK": "LIVE-TRACK", "COMMERCIAL": "ZIVIL", - "{aircraftClass} · {feedState} · COURSE ALIGNED": "{aircraftClass} · {feedState} · KURS ANGEGLICHEN", + "{aircraftClass} · {feedState} · COURSE ALIGNED": "{aircraftClass} · {feedState} · IN KURSRICHTUNG", "ARROW · ESTIMATED DIRECTION": "PFEIL · GESCHÄTZTE RICHTUNG", "ROUTE DATA UNAVAILABLE": "ROUTENDATEN NICHT VERFÜGBAR", "DEST {bearing}": "DEST {bearing}", @@ -467,8 +467,8 @@ "{aircraftClass} · {distance}": "{aircraftClass} · {distance}", "DISTANCE UNKNOWN": "DISTANZ UNBEKANNT", "{count} INPUTS UNKNOWN": { - "one": "{count} EINGABE UNBEKANNT", - "other": "{count} EINGABEN UNBEKANNT" + "one": "STATUS UNKLAR: {count} QUELLE", + "other": "STATUS UNKLAR: {count} QUELLEN" }, "SOURCE STATUS UNAVAILABLE": "QUELLENSTATUS NICHT VERFÜGBAR", "Restore real aircraft": "Echtes Flugzeug wiederherstellen", @@ -478,14 +478,14 @@ "Context could not restore every layer; try again": "Kontext konnte nicht alle Ebenen wiederherstellen; erneut versuchen", "No selected data layers": "Keine Datenebenen ausgewählt", "{count} data layers could not be cleared": { - "one": "{count} Datenebene konnte nicht gelöscht werden", - "other": "{count} Datenebenen konnten nicht gelöscht werden" + "one": "{count} Datenebene konnte nicht ausgeschaltet werden", + "other": "{count} Datenebenen konnten nicht ausgeschaltet werden" }, "Cleared {count} data layers": { - "one": "{count} Datenebene gelöscht", - "other": "{count} Datenebenen gelöscht" + "one": "{count} Datenebene ausgeschaltet", + "other": "{count} Datenebenen ausgeschaltet" }, - "Selected data layers could not be cleared": "Ausgewählte Datenebenen konnten nicht gelöscht werden", + "Selected data layers could not be cleared": "Ausgewählte Datenebenen konnten nicht ausgeschaltet werden", "Space Missions could not complete the requested transition; try again": "Raummissionen konnten den gewünschten Wechsel nicht abschließen; erneut versuchen", "Nearby installations refreshed": "Anlagen in der Nähe aktualisiert", "Nearby installations could not be refreshed; try again": "Anlagen in der Nähe konnten nicht aktualisiert werden; erneut versuchen", @@ -493,7 +493,7 @@ "That layer is unavailable in the current Context mode": "Diese Ebene ist im aktuellen Kontextmodus nicht verfügbar", "{layerId} could not {action} cleanly": "{layerId} konnte nicht sauber {action}", "start": "starten", - "stop": "beenden", + "stop": "beendet werden", "{mode} could not start because another layer did not stop cleanly": "{mode}: Start nicht möglich, da eine andere Ebene nicht sauber beendet wurde", "Space Missions": "Raummissionen", "Context": "Kontext", @@ -512,7 +512,7 @@ "Datacenters": "Rechenzentren", "Dams": "Staudämme", "Submarine Cables": "Seekabel", - "FIRMS Active Fires": "FIRMS aktive Brände", + "FIRMS Active Fires": "Aktive Brände (FIRMS)", "ENABLING": "WIRD AKTIVIERT", "DISABLING": "WIRD DEAKTIVIERT", "{state} · {source}": "{state} · {source}", @@ -550,7 +550,7 @@ "Collapse Radio": "Radio einklappen", "Expand Radio section": "Radio-Bereich ausklappen", "Collapse Radio section": "Radio-Bereich einklappen", - "Panel layout updated — positions reset to new defaults": "Bereichslayout aktualisiert. Positionen auf neue Standards zurückgesetzt", + "Panel layout updated — positions reset to new defaults": "Bereichslayout aktualisiert – Positionen auf neue Standardwerte zurückgesetzt", "Collapse Cockpit display options": "Cockpit-Anzeigeoptionen einklappen", "Expand Cockpit display options": "Cockpit-Anzeigeoptionen ausklappen", "Collapse Cockpit Radio controls": "Cockpit-Radio-Steuerung einklappen", @@ -593,7 +593,7 @@ " · degraded directory": " · eingeschränktes Verzeichnis", " · stale directory": " · veraltetes Verzeichnis", " · outside current filter": " · außerhalb des aktuellen Filters", - "Ready — playback starts only from your action": "Bereit: Wiedergabe startet nur auf eigene Aktion", + "Ready — playback starts only from your action": "Bereit: Wiedergabe startet nur manuell", "Radio off": "Radio aus", "Connecting directly to broadcaster…": "Direktverbindung zum Sender wird aufgebaut…", "Buffering broadcaster stream…": "Sender-Stream wird gepuffert…", @@ -603,11 +603,11 @@ " · muted during voice interaction": " · stumm bei Sprachinteraktion", " · restoring volume after voice": " · Lautstärke wird nach Sprache wiederhergestellt", " · static indicates no broadcaster audio": " · Rauschen bedeutet: kein Sender-Audio", - " · tuning static until broadcaster starts": " · Rauschen bis der Sender startet", + " · tuning static until broadcaster starts": " · Rauschen, bis der Sender startet", "Station unavailable after directory refresh — choose another channel": "Sender nach Verzeichnisaktualisierung nicht verfügbar: anderen Kanal wählen", "Radio is enabling…": "Radio wird aktiviert…", "Radio is disabling…": "Radio wird deaktiviert…", - "Radio lifecycle is uncertain — use Enable or Disable to reconcile": "Radio-Lebenszyklus unklar: zum Abgleichen Aktivieren oder Deaktivieren nutzen", + "Radio lifecycle is uncertain — use Enable or Disable to reconcile": "Radio-Lebenszyklus unklar: zum Abgleichen EIN oder AUS wählen", "Scene action failed": "Szenenaktion fehlgeschlagen", "Delete shot \"{shot}\"?": "Aufnahme „{shot}“ löschen?", "SPARSE": "DÜNN", @@ -674,7 +674,7 @@ "GROUND SPEED · KTS": "GESCHW. ÜBER GRUND · KN", "ALTITUDE · FT": "HÖHE · FT", "FIRST PERSON": "EGO-PERSPEKTIVE", - "LIVE TRACK · COURSE ALIGNED": "LIVE-TRACK · KURS ANGEGLICHEN", + "LIVE TRACK · COURSE ALIGNED": "LIVE-TRACK · IN KURSRICHTUNG", "Cockpit vision style": "Sichtstil im Cockpit", "Previous cockpit vision style": "Vorheriger Cockpit-Sichtstil", "Previous vision style": "Vorheriger Sichtstil", @@ -692,8 +692,8 @@ "Contact navigation": "Kontaktnavigation", "CONTEXT ONLY": "NUR KONTEXT", "Nearby cohort counts": "Anzahl Kontakte in der Nähe", - "NEAREST OBSERVED / MAPPED": "NÄCHSTER BEOBACHTET / KARTIERT", - "AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR": "NUR VERFÜGBARE EINGABEN · KEINE ENTWARNUNG", + "NEAREST OBSERVED / MAPPED": "NÄCHSTES OBJEKT · BEOBACHTET / KARTIERT", + "AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR": "NUR VERFÜGBARE QUELLEN · KEINE ENTWARNUNG", "Cockpit briefing carousel": "Cockpit-Briefing-Karussell", "Estimated flight plan": "Geschätzter Flugplan", "ESTIMATED FLIGHT PLAN": "GESCHÄTZTER FLUGPLAN", @@ -733,8 +733,8 @@ "Pin visual presets": "Visuelle Presets anheften", "Keep visual presets open": "Visuelle Presets geöffnet lassen", "Show the globe without a visual filter.": "Globus ohne visuellen Filter anzeigen.", - "Emulate a green phosphor CRT with scanlines and screen curvature.": "Grüne Phosphor-Röhre mit Zeilen und Bildschirmkrümmung nachahmen.", - "Simulate night-vision goggles with green intensification and a tube vignette.": "Nachtsichtgerät mit grüner Aufhellung und Röhrenvignette simulieren.", + "Emulate a green phosphor CRT with scanlines and screen curvature.": "Grüne Phosphor-Röhre mit Scanlines und Bildschirmkrümmung nachahmen.", + "Simulate night-vision goggles with green intensification and a tube vignette.": "Nachtsichtgerät mit grüner Restlichtverstärkung und Röhrenvignette simulieren.", "Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "FLIR-artigen Wärmekontrast simulieren. Ironbow aufdrehen für Farbe.", "Apply bright cel-shaded color and illustrated outlines.": "Leuchtende Cel-Shading-Farben und gezeichnete Konturen anwenden.", "Apply high-contrast monochrome film-noir grading.": "Kontrastreiche monochrome Film-noir-Farbgebung anwenden.", @@ -767,15 +767,15 @@ "SEARCH NEARBY SITES": "STANDORTE IN DER NÄHE SUCHEN", "Reclassify tracked contact as TR-3B": "Verfolgten Kontakt als TR-3B umklassifizieren", "CONTACTS CONTEXT OFF": "KONTAKTKONTEXT AUS", - "SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "KONTAKTE WÄHLEN, UM BEOBACHTETE / KARTIERTE NÄHE ZU LADEN", + "SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "KONTAKTE WÄHLEN FÜR BEOBACHTETE / KARTIERTE OBJEKTE IM UMKREIS", "Available Space Missions": "Verfügbare Raummissionen", "AVAILABLE MISSIONS": "VERFÜGBARE MISSIONEN", "SELECT A MISSION TO INSPECT": "MISSION ZUM ANSEHEN WÄHLEN", "LOADING 30-DAY MISSION INDEX": "30-TAGE-MISSIONSINDEX WIRD GELADEN", - "TAB PREVIEWS · ENTER / SPACE SELECTS": "TAB VORSCHAU · ENTER / LEERTASTE WÄHLT", + "TAB PREVIEWS · ENTER / SPACE SELECTS": "TAB: VORSCHAU · ENTER / LEERTASTE: AUSWAHL", "Internet radio and local SDR": "Internetradio und lokales SDR", - "STATION TAG": "SENDER-TAG", - "Filter stations by station tag": "Sender nach Sender-Tag filtern", + "STATION TAG": "KATEGORIE", + "Filter stations by station tag": "Sender nach Kategorie filtern", "All": "Alle", "Enable Radio, then choose a globe marker or use next.": "Radio aktivieren, dann einen Globus-Marker wählen oder Weiter nutzen.", "DRAG TO TUNE": "ZUM ABSTIMMEN ZIEHEN", @@ -789,7 +789,7 @@ "Stop radio playback": "Radiowiedergabe stoppen", "Radio volume": "Radio-Lautstärke", "STATION SITE": "SENDERSTANDORT", - "Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.": "Audio verbindet sich nach dem Abspielen direkt mit dem Sender. Die IP-Adresse ist für diesen Sender sichtbar.", + "Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.": "Beim Starten der Wiedergabe wird eine direkte Verbindung zum Sender hergestellt. Die IP-Adresse ist für diesen Sender sichtbar.", "Intelligence HUD (H)": "Intelligence-HUD (H)", "Layout": "Layout", "HUD layout": "HUD-Layout", @@ -798,9 +798,9 @@ "Minimal": "Minimal", "Detection Overlay (D)": "Erkennungs-Overlay (D)", "Detection overlay": "Erkennungs-Overlay", - "Detection label density": "Dichte der Erkennungslabels", + "Detection label density": "Dichte der Erkennungsbeschriftungen", "Allocation": "Zuteilung", - "Detection label allocation": "Zuteilung der Erkennungslabels", + "Detection label allocation": "Zuteilung der Erkennungsbeschriftungen", "Elastic": "Elastisch", "Weighted": "Gewichtet", "Fade": "Ausblenden", @@ -808,7 +808,7 @@ "World-overlay fade distance outside the keyhole as a percentage of its radius": "Ausblendeabstand des Welt-Overlays außerhalb des Schlüssellochs, in Prozent seines Radius", "Outside": "Außen", "Detection opacity outside the keyhole": "Erkennungsdeckkraft außerhalb des Schlüssellochs", - "World-overlay label and card opacity beyond the fade distance": "Deckkraft von Welt-Overlay-Labels und -Karten jenseits des Ausblendeabstands", + "World-overlay label and card opacity beyond the fade distance": "Deckkraft von Beschriftungen und Infokarten im Welt-Overlay jenseits des Ausblendeabstands", "PARAMETERS": "PARAMETER", "3D aircraft — flat icons zoomed out, 3D models up close": "3D-Flugzeuge: flache Symbole bei Fernsicht, 3D-Modelle aus der Nähe", "Models": "Modelle", @@ -859,7 +859,7 @@ "GROUND STATION · PROVIDER SETTINGS": "BODENSTATION · ANBIETER-EINSTELLUNGEN", "Close key setup": "Schlüssel-Einrichtung schließen", "Power up the globe": "Globus hochfahren", - "The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "Der Globus fliegt bereits ohne Schlüssel. Jeder Schlüssel unten schaltet einen weiteren echten Feed frei: Einfügen genügt, er wird in der lokalen Konfiguration dieser App gespeichert, danach startet der Server selbst neu. Serverseitige Schlüssel bleiben auf diesem Rechner; Google Maps und Cesium ion laufen im Browser und müssen auf den Anbieter beschränkt sein. Anderswo konfigurierte Schlüssel werden angezeigt, aber nie verändert.", + "The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "Der Globus fliegt bereits ohne Schlüssel. Jeder Schlüssel unten schaltet einen weiteren echten Feed frei: Einfügen genügt, er wird in der lokalen Konfiguration dieser App gespeichert, danach startet der Server selbst neu. Serverseitige Schlüssel bleiben auf diesem Rechner; die Schlüssel für Google Maps und Cesium ion werden im Browser verwendet und müssen beim jeweiligen Anbieter eingeschränkt werden. Anderswo konfigurierte Schlüssel werden angezeigt, aber nie verändert.", "SAVE KEYS": "SCHLÜSSEL SPEICHERN", "ESC to close": "ESC zum Schließen", "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "Der Google-Maps-Schlüssel liefert den fotorealistischen Planeten, alles andere kommt obendrauf.", @@ -868,17 +868,17 @@ "Globe actions": "Globus-Aktionen", "Turn off all selected data layers": "Alle ausgewählten Datenebenen ausschalten", "Copy share link": "Link zum Teilen kopieren", - "Reset camera and return to full globe view": "Kamera zurücksetzen und zur Vollglobus-Ansicht", + "Reset camera and return to full globe view": "Kamera zurücksetzen und zur Vollglobus-Ansicht zurückkehren", "MISSION CONTROL · FIRST LAUNCH": "MISSION CONTROL · ERSTER START", "Choose your first view": "Erste Ansicht wählen", "LIVE CONTACTS": "LIVE-KONTAKTE", - "Aircraft, vessels and nearby intelligence": "Flugzeuge, Schiffe und Aufklärung in der Nähe", + "Aircraft, vessels and nearby intelligence": "Flugzeuge, Schiffe und Lagebild der Umgebung", "Launches, spacecraft and orbital context": "Starts, Raumfahrzeuge und Orbit-Kontext", "EXPLORE MANUALLY": "MANUELL ERKUNDEN", "Begin with a clean globe": "Mit einem leeren Globus beginnen", "Don't show this again": "Nicht mehr anzeigen", "ESC to dismiss": "ESC zum Schließen", - "Tip: the GEV MIC button in the dock lets you talk to the map.": "Tipp: Mit der Taste GEV MIC im Dock lässt sich die Karte per Sprache steuern.", + "Tip: the GEV MIC button in the dock lets you talk to the map.": "Tipp: Mit der Mikrofontaste im Dock lässt sich die Karte per Sprache steuern.", "Pick a shape, then click the map.": "Form wählen, dann auf die Karte klicken.", "Enter to place the pin, Esc to cancel.": "Enter setzt den Pin, Esc bricht ab.", "Click where the pin goes.": "Auf die Stelle für den Pin klicken.", @@ -1035,7 +1035,7 @@ "one": "{count} QUELLE", "other": "{count} QUELLEN" }, - "OUTSIDE IMAGERY SWIPE": "AUSSERHALB DES BILDVERGLEICHS", + "OUTSIDE IMAGERY SWIPE": "NICHT IM BILDVERGLEICH", "{scene} / AUTHORED SHOT": "{scene} / GESTALTETE AUFNAHME", "NEPAL FLOOD INCIDENT": "FLUTEREIGNIS NEPAL", "Play {shot}": "{shot} abspielen", @@ -1101,7 +1101,7 @@ "That KMZ holds no KML document.": "Diese KMZ enthält kein KML-Dokument.", "That {format} file could not be read.": "Diese {format}-Datei konnte nicht gelesen werden.", "That file has {count} map objects; the limit is {limit}.": "Diese Datei hat {count} Kartenobjekte; das Limit ist {limit}.", - "Ground heights could not be read. Try that file again.": "Geländehöhen konnten nicht gelesen werden. Die Datei erneut versuchen.", + "Ground heights could not be read. Try that file again.": "Geländehöhen konnten nicht gelesen werden. Die Datei erneut laden.", "That file has invalid point graphics.": "Diese Datei enthält ungültige Punktgrafiken.", "Map tiles temporarily unavailable": "Kartenkacheln vorübergehend nicht verfügbar", "Mapped names temporarily unavailable": "Kartierte Namen vorübergehend nicht verfügbar", @@ -1200,7 +1200,7 @@ "ChatGPT sign-in expired. Opening sign-in again…": "ChatGPT-Anmeldung abgelaufen. Anmeldung wird erneut geöffnet…", "Opening ChatGPT sign-in in your browser…": "ChatGPT-Anmeldung wird im Browser geöffnet…", "Could not start ChatGPT sign-in on this machine.": "ChatGPT-Anmeldung konnte auf diesem Rechner nicht gestartet werden.", - "Finish ChatGPT sign-in in the browser. Waiting for it to complete…": "ChatGPT-Anmeldung im Browser abschließen. Warte auf Abschluss…", + "Finish ChatGPT sign-in in the browser. Waiting for it to complete…": "ChatGPT-Anmeldung im Browser abschließen. Warten auf Abschluss…", "ChatGPT sign-in timed out. Click {button} to try again.": "Zeitüberschreitung bei der ChatGPT-Anmeldung. Zum erneuten Versuch auf {button} klicken.", "ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.": "ChatGPT-Anmeldung abgeschlossen. OAuth wird für die nächste Cloud-Sprachsitzung verwendet.", "The photorealistic 3D planet, place search + Street View": "Der fotorealistische 3D-Planet, Ortssuche + Street View", @@ -1215,7 +1215,7 @@ "one": "{count} Station meldet", "other": "{count} Stationen melden" }, - "Highest air-quality readings in view": "Höchste Luftqualitätswerte im Bild", + "Highest air-quality readings in view": "Schlechteste Luftqualitätswerte im Bild", "Low": "Niedrig", "Low health risk": "Geringes Gesundheitsrisiko", "Moderate": "Mäßig", @@ -1266,11 +1266,11 @@ "viewport feed is not a complete 250 km survey": "Feed des Bildausschnitts ist keine vollständige 250-km-Erfassung", "GBFS fetch error": "GBFS-Abruffehler", "FOV {value}° (DATASHEET)": "FOV {value}° (DATENBLATT)", - "SPEC FOV {span}° (CAPABILITY)": "SPEZ. FOV {span}° (LEISTUNG)", + "SPEC FOV {span}° (CAPABILITY)": "SPEZ. FOV {span}° (BEREICH)", "{spec} · SPECS: CCTV-DATABASE.COM": "{spec} · SPEZ.: CCTV-DATABASE.COM", "Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.": "Atlantik und östlicher/zentraler Nordpazifik; keine weltweite Abdeckung von Wirbelstürmen.", - "Cyclone advisories unavailable": "Sturmwarnungen nicht verfügbar", - "Cyclone data unavailable": "Sturmdaten nicht verfügbar", + "Cyclone advisories unavailable": "Wirbelsturm-Lageberichte nicht verfügbar", + "Cyclone data unavailable": "Wirbelsturmdaten nicht verfügbar", "Potential tropical cyclone": "Potenzieller tropischer Wirbelsturm", "Post-tropical cyclone": "Posttropischer Wirbelsturm", "Hurricane": "Hurrikan", @@ -1278,29 +1278,29 @@ "Tropical depression": "Tropisches Tiefdruckgebiet", "Subtropical storm": "Subtropischer Sturm", "Subtropical depression": "Subtropisches Tiefdruckgebiet", - "Track and cone match this advisory": "Zugbahn und Kegel passen zu dieser Warnung", - "Track/cone awaiting advisory {number}": "Zugbahn/Kegel warten auf Warnung {number}", + "Track and cone match this advisory": "Zugbahn und Kegel passen zu diesem Lagebericht", + "Track/cone awaiting advisory {number}": "Zugbahn/Kegel warten auf Lagebericht {number}", "Track/cone unavailable": "Zugbahn/Kegel nicht verfügbar", - "Cached advisory · stale source": "Warnung aus dem Cache · Quelle veraltet", - "Loading advisories…": "Warnungen werden geladen…", + "Cached advisory · stale source": "Lagebericht aus dem Cache · Quelle veraltet", + "Loading advisories…": "Lageberichte werden geladen…", "{count} active storms": { "one": "{count} aktiver Sturm", "other": "{count} aktive Stürme" }, - "Position as of {time}": "Position mit Stand {time}", + "Position as of {time}": "Position, Stand {time}", "Maximum sustained wind: {wind} · Pressure: {pressure}": "Maximaler Dauerwind: {wind} · Luftdruck: {pressure}", - "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · Warnung {number} · herausgegeben {time}", + "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · Lagebericht {number} · herausgegeben {time}", "No active NHC/CPHC systems": "Keine aktiven NHC/CPHC-Systeme", - "Advisories unavailable": "Warnungen nicht verfügbar", + "Advisories unavailable": "Lageberichte nicht verfügbar", "Cyclones · NHC / CPHC": "Wirbelstürme · NHC / CPHC", "Atlantic · E/C Pacific": "Atlantik · östl./zentr. Pazifik", "{storms} · {name} selected": "{storms} · {name} ausgewählt", - "Official advisory ↗": "Offizielle Warnung ↗", - "Active NHC and CPHC cyclone advisories": "Aktive Wirbelsturmwarnungen von NHC und CPHC", + "Official advisory ↗": "Offizieller Lagebericht ↗", + "Active NHC and CPHC cyclone advisories": "Aktive Wirbelsturm-Lageberichte von NHC und CPHC", "Wind unavailable": "Wind nicht verfügbar", - "Advisory center / forecast track": "Zentrum laut Warnung / Vorhersagebahn", + "Advisory center / forecast track": "Zentrum laut Lagebericht / Vorhersagebahn", "Center-track uncertainty cone": "Unsicherheitskegel der Zentrumsbahn", - "Select a storm on the map, or choose a storm in the list to select it and move the camera. Click empty map space to clear the selection. NOAA NHC/CPHC advisory context. The cone describes forecast center-track uncertainty, not storm size or the full hazard area. Forecast point labels are source lead hours, not times computed from advisory issuance. Geometry follows the surface; height is not weather altitude. Consult the official advisory.": "Einen Sturm auf der Karte auswählen oder in der Liste wählen, um ihn auszuwählen und die Kamera dorthin zu bewegen. Auf eine leere Kartenstelle klicken, um die Auswahl aufzuheben. Kontext aus Warnungen von NOAA NHC/CPHC. Der Kegel beschreibt die Unsicherheit der vorhergesagten Zentrumsbahn, nicht die Sturmgröße oder das gesamte Gefahrengebiet. Die Beschriftungen der Vorhersagepunkte sind Vorlaufstunden der Quelle, keine aus dem Ausgabezeitpunkt berechneten Zeiten. Die Geometrie folgt der Oberfläche; die Höhe ist keine Wetterhöhe. Die offizielle Warnung beachten.", + "Select a storm on the map, or choose a storm in the list to select it and move the camera. Click empty map space to clear the selection. NOAA NHC/CPHC advisory context. The cone describes forecast center-track uncertainty, not storm size or the full hazard area. Forecast point labels are source lead hours, not times computed from advisory issuance. Geometry follows the surface; height is not weather altitude. Consult the official advisory.": "Einen Sturm auf der Karte auswählen oder in der Liste wählen, um ihn auszuwählen und die Kamera dorthin zu bewegen. Auf eine leere Kartenstelle klicken, um die Auswahl aufzuheben. Kontext aus Lageberichten von NOAA NHC/CPHC. Der Kegel beschreibt die Unsicherheit der vorhergesagten Zentrumsbahn, nicht die Sturmgröße oder das gesamte Gefahrengebiet. Die Beschriftungen der Vorhersagepunkte sind Vorlaufstunden der Quelle, keine aus dem Ausgabezeitpunkt berechneten Zeiten. Die Geometrie folgt der Oberfläche; die Höhe ist keine Wetterhöhe. Den offiziellen Lagebericht beachten.", "DRIVE": "AUTO", "Drive": "Auto", "WALK": "ZU FUSS", @@ -1394,7 +1394,7 @@ "Use the {direction} lane": "Die {direction} Spur benutzen", "Continue onto {road}": "Weiter auf {road}", "maneuver|Continue": "Weiter", - "{mode} — reroute for {modeLower}": "{mode} – Route für {modeLower} neu berechnen", + "{mode} — reroute for {modeLower}": "{mode} – Route im Modus „{modeLower}“ neu berechnen", "CLICK MAP": "KARTE ANKLICKEN", "SET A": "A SETZEN", "Click a spot on the globe to place A (click again to cancel)": "Auf eine Stelle des Globus klicken, um A zu setzen (erneut klicken zum Abbrechen)", @@ -1425,7 +1425,7 @@ "SET A, then click the globe": "A SETZEN, dann auf den Globus klicken", "Step {step} of {total} · then {leg}": "Schritt {step} von {total} · dann {leg}", "Step {step} of {total}": "Schritt {step} von {total}", - "Then: {instruction}": "Dann: {instruction}", + "Then: {instruction}": "Nächster Schritt: {instruction}", "No route found between A and B": "Keine Route zwischen A und B gefunden", "Routing failed: {error}": "Routing fehlgeschlagen: {error}", "Routing timed out": "Zeitüberschreitung beim Routing", @@ -1453,8 +1453,8 @@ "Latest Drop": "Neueste Ausgabe", "GhostMaps file could not be read": "GhostMaps-Datei konnte nicht gelesen werden", "{count} source links ↗ · click card to open the first": { - "one": "{count} Quelllink ↗ · Karte anklicken zum Öffnen", - "other": "{count} Quelllinks ↗ · Karte anklicken, um den ersten zu öffnen" + "one": "{count} Quelllink ↗ · Eintrag anklicken zum Öffnen", + "other": "{count} Quelllinks ↗ · Eintrag anklicken, um den ersten zu öffnen" }, "S2 · {name}": "S2 · {name}", "Report": "Bericht", @@ -1562,7 +1562,7 @@ "discovered {age} ago": "entdeckt vor {age}", "updated {age} ago": "aktualisiert vor {age}", "part of {complex}": "Teil von {complex}", - "InciWeb ↗ · click card to open": "InciWeb ↗ · Karte anklicken zum Öffnen", + "InciWeb ↗ · click card to open": "InciWeb ↗ · Eintrag anklicken zum Öffnen", "FIRE · {name}": "BRAND · {name}", "Unnamed incident": "Unbenannter Einsatz", "Open {title} on InciWeb": "{title} auf InciWeb öffnen", @@ -1593,7 +1593,7 @@ "Swipe needs a globe map": "Bildvergleich braucht eine Globuskarte", "Imagery stays on Esri · CLEAR to use Google 3D": "Bilder bleiben auf Esri · LÖSCHEN, um Google 3D zu verwenden", "Imagery catalog unavailable": "Bildkatalog nicht verfügbar", - "Catalog truncated · a newer clear day may exist": "Katalog gekürzt · ein neuerer klarer Tag ist möglich", + "Catalog truncated · a newer clear day may exist": "Katalog gekürzt · ein neuerer wolkenfreier Tag ist möglich", "{label} catalog unavailable · {detail}": "{label}-Katalog nicht verfügbar · {detail}", "Daily overview for today may still be empty until the pass": "Die tägliche Übersicht für heute kann bis zum Überflug noch leer sein", "Box and images cleared": "Bereich und Bilder gelöscht", @@ -1707,7 +1707,7 @@ "Vehicle {vehicle}": "Fahrzeug {vehicle}", "vehicle motion|Stopped": "Steht", "No recent fix": "Keine aktuelle Position", - "Waiting for update": "Warte auf Aktualisierung", + "Waiting for update": "Warten auf Aktualisierung", "Moving": "In Bewegung", "reported {speed} km/h": "gemeldet {speed} km/h", "reported hdg {heading}°": "gemeldeter Kurs {heading}°", @@ -1727,7 +1727,7 @@ "Train": "Zug", "Ferry": "Fähre", "Transit vehicle": "ÖPNV-Fahrzeug", - "{speed}KT": "{speed}KN", + "{speed}KT": "{speed} KN", "{heading}DEG": "{heading}°", "AIS live load failed": "AIS-Live-Daten konnten nicht geladen werden", "AISSTREAM_API_KEY not set": "AISSTREAM_API_KEY nicht gesetzt", @@ -1759,9 +1759,9 @@ "Observed": "Beobachtet", "History": "Verlauf", "Observation unavailable": "Beobachtung nicht verfügbar", - "Waiting for observation": "Warte auf Beobachtung", + "Waiting for observation": "Warten auf Beobachtung", "{detail} · synced": "{detail} · synchronisiert", - "{detail} · nearest": "{detail} · nächstgelegen", + "{detail} · nearest": "{detail} · zeitlich am nächsten", "RADAR REFLECTIVITY · dBZ": "RADARREFLEKTIVITÄT · dBZ", "LIGHTNING DENSITY · 15 min accumulation": "BLITZDICHTE · 15-min-Summe", "GLOBAL INFRARED · hourly": "GLOBALES INFRAROT · stündlich", @@ -1771,7 +1771,7 @@ "frame {index}/{total}": "Frame {index}/{total}", "loading": "lädt", "Observation: unavailable": "Beobachtung: nicht verfügbar", - "Contiguous US · gaps ≠ no rain": "USA (Festland) · Lücken ≠ kein Regen", + "Contiguous US · gaps ≠ no rain": "USA ohne Alaska/Hawaii · Lücken ≠ kein Regen", "Americas + Pacific · not individual strikes": "Amerika + Pazifik · keine Einzelblitze", "Color: strikes/km²/min ×10³": "Farbe: Blitze/km²/min ×10³", "60°S–60°N · typically 2–3 h delayed": "60°S–60°N · meist 2–3 h verzögert", @@ -1780,7 +1780,7 @@ "Reduced motion · manual history available": "Reduzierte Bewegung · manueller Verlauf verfügbar", "Rain radar · US": "Regenradar · USA", "Lightning density · 15 min": "Blitzdichte · 15 min", - "Satellite clouds": "Satellitenwolken", + "Satellite clouds": "Wolken (Satellit)", "Americas + Pacific": "Amerika + Pazifik", "Global · 60°S–60°N": "Global · 60°S–60°N", "North America": "Nordamerika", @@ -1792,9 +1792,9 @@ "N. America": "Nordamerika", "Global": "Global", "Hourly global mosaic; usually 2–3 hours delayed": "Stündliches globales Mosaik; meist 2–3 Stunden verzögert", - "GOES regional clouds; approximately 5-minute updates": "Regionale GOES-Wolken; Aktualisierung etwa alle 5 Minuten", + "GOES regional clouds; approximately 5-minute updates": "Regionale GOES-Wolkenbilder; Aktualisierung etwa alle 5 Minuten", "Clouds only": "Nur Wolken", - "Apply the full cloud brightness key; 100% filter strength": "Volle Wolkenhelligkeitsskala anwenden; Filterstärke 100 %", + "Apply the full cloud brightness key; 100% filter strength": "Helligkeitsfilter für Wolken vollständig anwenden; Filterstärke 100 %", "Full": "Voll", "Show the complete infrared image; 0% filter strength": "Vollständiges Infrarotbild zeigen; Filterstärke 0 %", "Soft": "Weich", @@ -1842,7 +1842,7 @@ "NOAA GFS surface forecast": "NOAA-GFS-Bodenvorhersage", "Reduced motion": "Reduzierte Bewegung", "Pause visual flow; forecast time does not advance with animation": "Visuelle Strömung pausieren; die Vorhersagezeit läuft mit der Animation nicht weiter", - "None": "Keine", + "None": "Ohne", "Speed": "Geschwindigkeit", "Pressure": "Luftdruck", "Temperature": "Temperatur", @@ -1881,7 +1881,7 @@ "WNW": "WNW", "NW": "NW", "NNW": "NNW", - "No surface reading": "Kein Bodenmesswert", + "No surface reading": "Kein bodennaher Wert", "Aim the map center at Earth": "Kartenmitte auf die Erde richten", "A location reading needs a loaded forecast and the Earth at the center of the view.": "Ein Ortswert braucht eine geladene Vorhersage und die Erde in der Bildmitte.", "Interpolated model forecast on an approximately 1° grid. Broad weather patterns, not a street-level measurement.": "Interpolierte Modellvorhersage auf einem Raster von etwa 1°. Großräumige Wetterlagen, keine Messung auf Straßenebene.", @@ -1969,11 +1969,11 @@ "Bidur / Trishuli Consequence": "Folgen in Bidur / Trishuli", "Final view": "Schlussansicht", "Scene {n}": "Szene {n}", - "the title of scene {scene} is empty or longer than {max} characters": "Der Titel von Szene {scene} ist leer oder länger als {max} Zeichen", - "the title of shot {shot} in scene {scene} is empty or longer than {max} characters": "Der Titel von Aufnahme {shot} in Szene {scene} ist leer oder länger als {max} Zeichen", - "the project is larger than 5 MiB": "Das Projekt ist größer als 5 MiB", - "the project has too many shots": "Das Projekt hat zu viele Aufnahmen", - "the scene data failed validation": "Die Szenendaten haben die Validierung nicht bestanden", + "the title of scene {scene} is empty or longer than {max} characters": "der Titel von Szene {scene} ist leer oder länger als {max} Zeichen", + "the title of shot {shot} in scene {scene} is empty or longer than {max} characters": "der Titel von Aufnahme {shot} in Szene {scene} ist leer oder länger als {max} Zeichen", + "the project is larger than 5 MiB": "das Projekt ist größer als 5 MiB", + "the project has too many shots": "das Projekt hat zu viele Aufnahmen", + "the scene data failed validation": "die Szenendaten haben die Validierung nicht bestanden", "Could not complete this action. Check the file, references and selected assets.": "Diese Aktion konnte nicht abgeschlossen werden. Datei, Verweise und ausgewählte Assets prüfen.", "{count} scenes": { "one": "{count} Szene", @@ -2032,7 +2032,7 @@ "SDR decoder stopped responding; receiver stopped": "SDR-Decoder reagiert nicht mehr; Empfänger gestoppt", "SDR decoder fell behind; receiver stopped": "SDR-Decoder kommt nicht hinterher; Empfänger gestoppt", "WebUSB requires desktop Chrome or Edge": "WebUSB braucht Chrome oder Edge auf dem Desktop", - "Waiting for an RTL-SDR device…": "Warte auf ein RTL-SDR-Gerät…", + "Waiting for an RTL-SDR device…": "Warten auf ein RTL-SDR-Gerät…", "Browser audio is suspended; press Connect again to resume it": "Browser-Audio ist angehalten; erneut auf Verbinden drücken, um es fortzusetzen", "RTL-SDR sample stream stopped": "RTL-SDR-Sample-Stream gestoppt", "Switching to {mode}…": "Wechsel zu {mode}…", @@ -2073,7 +2073,7 @@ "Link shared": "Link geteilt", "Return map to straight-down view": "Karte wieder senkrecht von oben zeigen", "Tilt map to oblique view": "Karte in Schrägansicht kippen", - "Reset map to north up. Current heading {heading} degrees": "Karte nach Norden ausrichten. Aktueller Kurs {heading} Grad", + "Reset map to north up. Current heading {heading} degrees": "Karte nach Norden ausrichten. Aktuelle Ausrichtung {heading} Grad", "Tilted view": "Schrägansicht", "Straight-down view": "Senkrechte Ansicht", "North up": "Norden oben", @@ -2117,7 +2117,7 @@ "{source} · LOCATION QUERY · RECENT": "{source} · ORTSABFRAGE · AKTUELL", "Rendered globe frames per second · toggle with `": "Gerenderte Globus-Frames pro Sekunde · umschalten mit `", "imported map files": "importierte Kartendateien", - "Ground heights could not be refreshed. Try the file again when the map has loaded.": "Geländehöhen konnten nicht aktualisiert werden. Die Datei erneut versuchen, wenn die Karte geladen ist.", + "Ground heights could not be refreshed. Try the file again when the map has loaded.": "Geländehöhen konnten nicht aktualisiert werden. Die Datei erneut laden, wenn die Karte geladen ist.", "{count} map objects": { "one": "{count} Kartenobjekt", "other": "{count} Kartenobjekte" @@ -2153,7 +2153,7 @@ "S2 GhostMaps": "S2 GhostMaps", "Rain radar": "Regenradar", "Lightning density": "Blitzdichte", - "Cyclone advisories": "Sturmwarnungen", + "Cyclone advisories": "Wirbelstürme", "ALPR Cameras": "ALPR-Kameras", "Live Vessels": "Live-Schiffe", "Bike Share": "Bikesharing", @@ -2192,7 +2192,7 @@ "feed status|stale": "veraltet", "feed status|unreachable": "nicht erreichbar", "feed status|invalid": "ungültig", - "waiting for IQ": "warte auf IQ", + "waiting for IQ": "warten auf IQ", "{count} messages": { "one": "{count} Nachricht", "other": "{count} Nachrichten" @@ -2245,14 +2245,14 @@ "{category} ({count})": "{category} ({count})", "PAUSE": "PAUSE", "RESUME": "WEITER", - "Clear reading": "Messwert löschen", + "Clear reading": "Wert ausblenden", "Earlier observation": "Frühere Beobachtung", "Observed history": "Beobachteter Verlauf", "Later observation": "Spätere Beobachtung", "Latest": "Neueste", "Newest frame per product": "Neuester Frame je Produkt", "Replay observed history": "Beobachteten Verlauf abspielen", - "Imagery on Esri · Google 3D returns when cleared": "Bilder auf Esri · Google 3D nach dem Löschen", + "Imagery on Esri · Google 3D returns when cleared": "Bilder auf Esri · nach LÖSCHEN wieder Google 3D", "Point the camera at the ground to use the view": "Kamera auf den Boden richten, um die Ansicht zu verwenden", "That point cannot anchor a box": "Dieser Punkt kann keinen Bereich verankern", "Imagery stops at ±{limit}° latitude": "Bilder enden bei ±{limit}° Breite", @@ -2265,7 +2265,7 @@ "Newest day covering this box · cloudier than 20%": "Neuester Tag mit Abdeckung dieses Bereichs · über 20 % Bewölkung", "Newest day with imagery for this box · partial coverage": "Neuester Tag mit Bildern für diesen Bereich · teilweise Abdeckung", "Newest daily overview for this box": "Neueste tägliche Übersicht für diesen Bereich", - "newest clear day": "neuester klarer Tag", + "newest clear day": "neuester wolkenfreier Tag", "newest day (cloudy)": "neuester Tag (bewölkt)", "newest day (partial coverage)": "neuester Tag (teilweise Abdeckung)", "newest overview": "neueste Übersicht", @@ -2377,13 +2377,13 @@ "Shared satellite is unavailable": "Geteilter Satellit ist nicht verfügbar", "{id} isn't reporting now — showing where it was last seen": "{id} sendet gerade nicht – letzte bekannte Position wird gezeigt", "{id} isn't reporting now — showing where it was at {time}": "{id} sendet gerade nicht – Position um {time} wird gezeigt", - "This share link can't be opened": "Dieser Link zum Teilen kann nicht geöffnet werden", + "This share link can't be opened": "Dieser Freigabelink kann nicht geöffnet werden", "Shared vessel is unavailable": "Geteiltes Schiff ist nicht verfügbar", "subject": "Objekt", "RETRYING ALPR CAMERAS": "ALPR-KAMERAS: NEUER VERSUCH", "FETCHING ALPR CAMERAS": "ALPR-KAMERAS WERDEN ABGERUFEN", "OVERPASS TEMPORARILY UNAVAILABLE": "OVERPASS VORÜBERGEHEND NICHT VERFÜGBAR", - "OVERPASS RATE-LIMITED": "OVERPASS-RATENLIMIT ERREICHT", + "OVERPASS RATE-LIMITED": "OVERPASS-ANFRAGELIMIT ERREICHT", "OVERPASS TIMED OUT": "ZEITÜBERSCHREITUNG BEI OVERPASS", "OVERPASS COULD NOT COMPLETE THE QUERY": "OVERPASS KONNTE DIE ABFRAGE NICHT ABSCHLIESSEN", "MAP TILES TEMPORARILY UNAVAILABLE": "KARTENKACHELN VORÜBERGEHEND NICHT VERFÜGBAR", @@ -2453,7 +2453,7 @@ "LAST 3 MONTHS": "LETZTE 3 MONATE", "LAST MONTH": "LETZTER MONAT", "LAST {count} DAYS": { - "one": "LETZTER {count} TAG", + "one": "{count} TAG", "other": "LETZTE {count} TAGE" }, "{window} · SINCE {date}": "{window} · SEIT {date}", @@ -2462,7 +2462,7 @@ "{name} imagery on": "{name}-Bilder an", "{name} imagery off": "{name}-Bilder aus", "Image by {creator}": "Bild von {creator}", - " or ": " oder", + " or ": " oder ", "OPEN {labels}": "{labels} ÖFFNEN", "Open {names} at the centre of the view": "{names} in der Bildmitte öffnen", "Zoom in to a street to open {names} here": "Auf eine Straße hineinzoomen, um hier {names} zu öffnen", @@ -2509,9 +2509,9 @@ "voice|OFF": "AUS", "voice|CONNECTING": "VERBINDET", "voice|ERROR": "FEHLER", - "Voice provider": "Sprachanbieter", + "Voice provider": "Anbieter der Sprachsteuerung", "Changing provider reconnects voice if it is on.": "Ein Anbieterwechsel verbindet die Sprachsteuerung neu, wenn sie an ist.", - "Voice Settings": "Spracheinstellungen", + "Voice Settings": "Einstellungen der Sprachsteuerung", "STD": "STD", "ON/OFF": "EIN/AUS", "VOICE": "SPRACHE", @@ -2592,7 +2592,7 @@ "Usage is incomplete because a response was still in flight when the session ended.": "Die Nutzung ist unvollständig, weil beim Sitzungsende noch eine Antwort unterwegs war.", "Estimate is incomplete — a response was still in flight when the session ended, so its usage was never reported.": "Schätzung unvollständig – beim Sitzungsende war noch eine Antwort unterwegs, ihre Nutzung wurde daher nie gemeldet.", "STANDARD": "STANDARD", - "MIC": "MIK", + "MIC": "MIKRO", "{n} aircraft": { "one": "{n} Luftfahrzeug", "other": "{n} Luftfahrzeuge" @@ -2673,7 +2673,7 @@ "last 24 h": "letzte 24 h", "past 30 d and upcoming": "letzte 30 Tage und anstehende", "the flights layer loads by viewport": "die Flugebene lädt nach Bildausschnitt", - "distance is ground distance, not overhead": "Entfernung ist Bodenentfernung, nicht über Kopf", + "distance is ground distance, not overhead": "Entfernung am Boden, ohne Berücksichtigung der Höhe", "the count is a lower bound because at least one layer reached its retained limit.": "die Anzahl ist eine Untergrenze, weil mindestens eine Ebene ihr Speicherlimit erreicht hat.", "{label} off": "{label} aus", "{label} loading": "{label} lädt", @@ -2681,8 +2681,8 @@ "counted the first {returned} loaded records": "die ersten {returned} geladenen Einträge gezählt", "counted {returned} of {total} loaded records": "{returned} von {total} geladenen Einträgen gezählt", "{layers} just turned on — still loading, counts will rise": "{layers} gerade eingeschaltet – lädt noch, die Zahlen steigen", - "Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "Snapshot des Kontaktbereichs um {time} – dieselbe gespeicherte Auswahl liefert Karte, Sprachausgabe und Gedächtnis für Folgefragen", - "Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "Snapshot des Kontaktbereichs – dieselbe gespeicherte Auswahl liefert Karte, Sprachausgabe und Gedächtnis für Folgefragen", + "Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "Snapshot des Kontaktbereichs um {time} – dieselbe gespeicherte Auswahl liefert die Sprachkarte, die Sprachausgabe und das Gedächtnis für Folgefragen", + "Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "Snapshot des Kontaktbereichs – dieselbe gespeicherte Auswahl liefert die Sprachkarte, die Sprachausgabe und das Gedächtnis für Folgefragen", "Fly below {km} km to a covered region": "Unter {km} km zu einer abgedeckten Region fliegen", "No feed here yet · {n} regions available": { "one": "Hier noch kein Feed · {n} Region verfügbar", @@ -2841,7 +2841,7 @@ "FT": "FT", "FLT": "FLÜGE", "MIL": "MIL", - "SITE": "ORTE", + "SITE": "STANDORTE", "WX": "WETTER", "MM": "MM", "Weather data by Open-Meteo.com": "Wetterdaten von Open-Meteo.com", @@ -2933,6 +2933,6 @@ "Toggle straight-down and tilted map views": "Zwischen senkrechter und geneigter Kartenansicht wechseln", "Reset map to north up": "Karte nach Norden ausrichten", "Reset map bearing to north": "Kartenausrichtung auf Norden zurücksetzen", - "Cyclone request timed out": "Zeitüberschreitung bei der Sturmabfrage", - "Malformed cyclone snapshot": "Fehlerhafter Sturm-Snapshot" + "Cyclone request timed out": "Zeitüberschreitung bei der Wirbelsturm-Abfrage", + "Malformed cyclone snapshot": "Fehlerhafter Wirbelsturm-Snapshot" } diff --git a/src/layers/directions/index.js b/src/layers/directions/index.js index b8cceff02..8b5400cda 100644 --- a/src/layers/directions/index.js +++ b/src/layers/directions/index.js @@ -18,7 +18,13 @@ import * as Cesium from 'cesium'; import { roadLabel } from '../../data/routeSteps.js'; -import { t, msg, formatNumber, translateKnown } from '../../i18n/index.js'; +import { + t, + msg, + formatNumber, + getLocale, + translateKnown, +} from '../../i18n/index.js'; import { pointerOwnerName } from '../../data/pointerOwnerNames.js'; import { formatQuantity } from '../../ui/unitLabels.js'; @@ -406,6 +412,16 @@ export function normalizeDirectionsParams(params = {}) { return out; } +/** + * A travel-mode word inside a sentence: lower case, except in German, where + * the words are nouns and keep their capital ("Auto", "Fahrrad"). + * @param {string} word Translated mode word. + * @returns {string} + */ +function modeWordInSentence(word) { + return getLocale() === 'de' ? word : word.toLowerCase(); +} + /** * Row chips for a given state. Pure so the chip logic is testable without a DOM. * @param {{mode:string, armed:null|'a'|'b', a:object|null, b:object|null, status:string, route:object|null}} state @@ -421,7 +437,7 @@ export function directionsRowControls(state) { state: mode === id ? 'active' : 'idle', title: t('{mode} — reroute for {modeLower}', { mode: t(spec.word), - modeLower: t(spec.word).toLowerCase(), + modeLower: modeWordInSentence(t(spec.word)), }), params: { mode: id }, })); diff --git a/src/scenes/recipes.js b/src/scenes/recipes.js index ba3940a57..081fa1db4 100644 --- a/src/scenes/recipes.js +++ b/src/scenes/recipes.js @@ -1069,13 +1069,34 @@ export function displaySceneTitle(scene) { export function displayShotTitle(title, shot = undefined, scene = undefined) { const text = typeof title === 'string' ? title : ''; const fromRecipe = shot - ? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene?.id, text) + ? Boolean(shot.sourcePackId) || + recipeDeclaresShotTitle(scene?.id, text) || + shotPackDeclaresBaseTitle(scene, text) : true; if (fromRecipe && BUILTIN_SHOT_TITLES.has(text)) return t(text); const numbered = /^Shot ([1-9]\d*)$/.exec(text); return numbered ? t('Shot {n}', { n: numbered[1] }) : text; } +/** + * Whether a shot pack that upgraded this scene declares the title for one of + * its legacy base shots. The upgrade rebuilds those shots without a + * sourcePackId (see SceneDirector._bootstrapLegacyShotPacks), so the pack's + * own shots in the scene vouch for them. + * @param {{shots?: Array<{sourcePackId?: string}>}} [scene] + * @param {string} title + * @returns {boolean} + */ +function shotPackDeclaresBaseTitle(scene, title) { + const shots = Array.isArray(scene?.shots) ? scene.shots : []; + return SCENE_APPEND_RECIPES.some( + (pack) => + pack.legacySceneBootstrap?.cameraPath?.some( + (keyframe) => keyframe?.title === title, + ) && shots.some((item) => item?.sourcePackId === pack.id), + ); +} + /** Whether the built-in recipe behind a scene declares a shot with this title. */ function recipeDeclaresShotTitle(sceneId, title) { const recipe = sceneId ? getSceneRecipeById(sceneId) : null; diff --git a/src/scenes/sceneTitleDisplay.test.mjs b/src/scenes/sceneTitleDisplay.test.mjs index 604e01c49..a5d157e64 100644 --- a/src/scenes/sceneTitleDisplay.test.mjs +++ b/src/scenes/sceneTitleDisplay.test.mjs @@ -4,6 +4,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { + SCENE_APPEND_RECIPES, SCENE_RECIPES, displaySceneTitle, displayShotTitle, @@ -141,3 +142,24 @@ test('the scene context of a shot carries its display title by provenance', asyn title, ); }); + +test('the base shots of a scene the Nepal pack upgraded localize', () => { + const pack = SCENE_APPEND_RECIPES.find( + (recipe) => recipe.legacySceneBootstrap?.cameraPath?.length, + ); + if (!pack) return; + setLocale('de'); + const title = pack.legacySceneBootstrap.cameraPath[0].title; + const base = { id: 'shot-base', title }; + const upgraded = { + id: 'scene-upgraded', + shots: [base, { id: 'shot-pack', title: 'x', sourcePackId: pack.id }], + }; + assert.equal(displayShotTitle(title, base, upgraded), displayShotTitle(title)); + assert.notEqual(displayShotTitle(title), title); + // The same title in a scene no pack touched stays as typed. + assert.equal( + displayShotTitle(title, base, { id: 'scene-user', shots: [base] }), + title, + ); +}); diff --git a/src/ui/layerPanel.js b/src/ui/layerPanel.js index 18b71dabf..afe255733 100644 --- a/src/ui/layerPanel.js +++ b/src/ui/layerPanel.js @@ -136,9 +136,11 @@ export function layerKeyRequirementTooltip(layer = {}) { const ROW_TEXT_SEPARATOR = ' · '; -// A short Latin code joined by hyphens. Longer runs (a URL, a token in an -// error) keep their break opportunities so they cannot overflow the rail. -const HYPHENATED_CODE = /[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+/g; +// A short Latin code joined by hyphens: every part after a hyphen is capitals +// or digits (ADS-B, RTL-SDR, GTFS-RT). A compound such as "ALPR-Kameras" is a +// word, not a code, and may break at its hyphen. Longer runs (a URL, a token +// in an error) keep their break opportunities so they cannot overflow the rail. +const HYPHENATED_CODE = /\b[A-Za-z0-9]+(?:-[A-Z0-9]+)+(?![A-Za-z0-9])/g; const HYPHENATED_CODE_MAX = 16; /** A span for one run of row text; `keep` stops the browser breaking inside it. */ diff --git a/src/ui/layerPanel.test.mjs b/src/ui/layerPanel.test.mjs index b4b771e1a..5a6f78c8f 100644 --- a/src/ui/layerPanel.test.mjs +++ b/src/ui/layerPanel.test.mjs @@ -303,6 +303,15 @@ test('row text keeps a hyphenated code whole and never starts a line with the se assert.equal(plain.textContent, 'Satellites'); assert.equal(plain.children.length, 0); + const compound = element(); + setRowText(compound, 'Kartierte ALPR-Kameras · ADS-B-Flüge'); + assert.equal(text(compound), 'Kartierte ALPR-Kameras · ADS-B-Flüge'); + assert.deepEqual( + kept(compound), + [' ·', 'ADS-B'], + 'a compound may break at its hyphen; the code inside it stays whole', + ); + const long = element(); setRowText(long, 'see https-proxy-endpoint-config-x'); assert.deepEqual(kept(long), [], 'a long hyphenated run may still wrap'); diff --git a/src/ui/styles/bhote-koshi.css b/src/ui/styles/bhote-koshi.css index 21a0348f7..f9e9ea1d1 100644 --- a/src/ui/styles/bhote-koshi.css +++ b/src/ui/styles/bhote-koshi.css @@ -518,14 +518,15 @@ html:not([lang='en']) .bhote-event-story-current strong { -webkit-line-clamp: 2; } -/* Phase / time / sources run long in translation: allow a second line. */ +/* Phase / time / sources / imagery note run long in translation: allow up to + three lines so the last status is not cut off. */ html:not([lang='en']) .bhote-event-story-current small { display: -webkit-box; margin-top: 3px; text-overflow: clip; white-space: normal; -webkit-box-orient: vertical; - -webkit-line-clamp: 2; + -webkit-line-clamp: 3; } .bhote-event-actions { diff --git a/src/ui/styles/radio.css b/src/ui/styles/radio.css index 483321992..e7607c42c 100644 --- a/src/ui/styles/radio.css +++ b/src/ui/styles/radio.css @@ -688,6 +688,12 @@ html:not([lang='en']) .data-count { text-wrap: balance; } +/* The status column keeps its width, so a wide chip (WIRD AKTIVIERT) never + overlaps the name; the name wraps in the room left beside it. */ +html:not([lang='en']) .data-toggle-right { + flex: none; +} + /* Row text pieces that must not break inside: a hyphenated code (ADS-B) and the space before a " · " separator (see setRowText in layerPanel.js). */ html:not([lang='en']) .data-keep {