From 8c844d7741b86085b0cf9a24540f1053a9871a06 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 14 Oct 2026 18:41:28 +0400 Subject: [PATCH] fix(i18n): map cards and wind readout in the UI language; final review wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A final review of both PDFs found text the earlier checks missed: - Map cards: fire confidence (high/nominal/low) and age (24h), vessel types (TOWING, TANKER) and installation classes (BASE, AIRFIELD) showed in English. They now go through the catalog; the data stays English. - The wind readout kept "10-14 14:00 UTC" and "29.93°N 97.74°W"; it now uses the locale's date, decimal separator and hemisphere letters. - Catalog: one term per concept (label/подпись, basemap/подложка, PROVIDER CONTROLS, HEARD, zoom in, REGIONAL NEWS), calques and broken grammar in the scene editor, provider settings and Bhote Koshi notes, and Spanish wording that read wrong ("restringidas por el proveedor", "Incluye … no se incluyen", "justo ahora", "Sal"). Co-Authored-By: Claude Opus 5.5 --- scripts/package-boundaries.json | 9 +- src/data/bhoteKoshiLocator.test.mjs | 2 +- src/i18n/locales/es.json | 111 ++++++++++-------- src/i18n/locales/ru.json | 139 +++++++++++++---------- src/i18n/same-as-english.json | 5 +- src/layers/firms/model.js | 36 +++++- src/layers/installations/classLabel.js | 29 +++++ src/layers/installations/namedMarkers.js | 5 +- src/layers/installations/rendering.js | 5 +- src/layers/vessels/cards.js | 4 +- src/layers/wind/presentation.js | 40 ++++++- 11 files changed, 251 insertions(+), 134 deletions(-) create mode 100644 src/layers/installations/classLabel.js diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index e5cef57f8..cc811efb4 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -1145,7 +1145,8 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", - "src/i18n/locales/ru.json" + "src/i18n/locales/ru.json", + "src/layers/installations/classLabel.js" ], "external": ["cesium", "@mapbox/vector-tile", "pbf"] }, @@ -2348,7 +2349,8 @@ "src/i18n/locales/ru.json", "src/ui/unitLabels.js", "src/layers/recentImagery/display.js", - "src/ui/keyGuidance.js" + "src/ui/keyGuidance.js", + "src/layers/installations/classLabel.js" ], "external": [ "hls.js", @@ -2881,7 +2883,8 @@ "src/i18n/locales/es.json", "src/i18n/locales/ru.json", "src/ui/unitLabels.js", - "src/layers/recentImagery/display.js" + "src/layers/recentImagery/display.js", + "src/layers/installations/classLabel.js" ], "external": [ "hls.js", diff --git a/src/data/bhoteKoshiLocator.test.mjs b/src/data/bhoteKoshiLocator.test.mjs index 8af4a2a42..81235275c 100644 --- a/src/data/bhoteKoshiLocator.test.mjs +++ b/src/data/bhoteKoshiLocator.test.mjs @@ -435,7 +435,7 @@ test('Bhote Koshi overview pins translate their descriptive titles and keep plac ); assert.equal( titles.get('bhote-koshi-place-immediate-collapse-viewpoint'), - '01 · ОБЗОР ОБРУШЕНИЯ', + '01 · ВИД НА ОБРУШЕНИЕ', ); assert.equal( titles.get('bhote-koshi-place-gyirong-border-gate'), diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 7c9e8add5..92272baba 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -100,7 +100,7 @@ "Feather": "Difuminado", "Scope edge feather as a percentage of the keyhole radius": "Difuminado del borde de la mirilla como porcentaje del radio del ojo de cerradura", "Celestial ring — reveal the full globe": "Anillo celestial — muestra el globo completo", - "Celestial": "Celeste", + "Celestial": "Cielo", "Hide UI chrome": "Ocultar los elementos de la interfaz", "Clean UI": "Interfaz limpia", "Bloom / Glow": "Resplandor", @@ -154,15 +154,15 @@ "Live signals": "Señales en vivo", "Latest regional news": "Últimas noticias regionales", "Location-based information": "Información basada en la ubicación", - "ACQUIRING REGIONAL NEWS": "OBTENIENDO NOTICIAS REGIONALES", + "ACQUIRING REGIONAL NEWS": "OBTENIENDO NOTICIAS DE ZONA", "RESOLVING REGION": "DETERMINANDO REGIÓN", - "TEMP": "TEMP", + "TEMP": "TEMP.", "WIND": "VIENTO", "SKY": "CIELO", - "PRECIP": "PRECIP", + "PRECIP": "PRECIP.", "SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS": "EVENTOS CON FUENTE · SIN NOTICIAS SINTÉTICAS", "Show Live Signals": "Mostrar Señales en vivo", - "Show Regional News": "Mostrar Noticias regionales", + "Show Regional News": "Mostrar noticias de zona", "Show Local Info": "Mostrar Información local", "Contact cockpit summary": "Resumen de contacto de la cabina", "Contact navigation": "Navegación de contactos", @@ -280,10 +280,10 @@ "Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.": "Detén el cambio automático de páginas. Anterior, Siguiente y las pestañas SIG/NOTICIAS/LOCAL siguen disponibles.", "REGIONAL NEWS": "NOTICIAS DE ZONA", "LOCAL INFO": "INFORMACIÓN LOCAL", - "LATEST LOCATION-MATCHED REPORTING": "ÚLTIMOS REPORTES SEGÚN UBICACIÓN", + "LATEST LOCATION-MATCHED REPORTING": "ÚLTIMOS INFORMES SEGÚN UBICACIÓN", "PLACE / CONDITIONS / POSITION": "LUGAR / CLIMA / POSICIÓN", "POSITION UNAVAILABLE": "POSICIÓN NO DISPONIBLE", - "REGIONAL NEWS UNAVAILABLE": "NOTICIAS REGIONALES NO DISPONIBLES", + "REGIONAL NEWS UNAVAILABLE": "NOTICIAS DE ZONA NO DISPONIBLES", "REGION UNAVAILABLE": "REGIÓN NO DISPONIBLE", "NO RECENT LOCATION MATCHES": "SIN COINCIDENCIAS RECIENTES DE UBICACIÓN", "SOURCE": "FUENTE", @@ -317,7 +317,7 @@ "Space Missions could not complete the requested transition; try again": "Misiones espaciales no pudo completar la transición solicitada; inténtalo de nuevo", "Nearby installations could not be refreshed; try again": "No se pudieron actualizar las instalaciones cercanas; inténtalo de nuevo", "Context could not restore every layer; try again": "Contexto no pudo restaurar todas las capas; inténtalo de nuevo", - "Zoom in to search mapped installations": "Haz zoom para buscar instalaciones mapeadas", + "Zoom in to search mapped installations": "Acércate para buscar instalaciones mapeadas", "Nearby installations refreshed": "Instalaciones cercanas actualizadas", "That layer is unavailable in the current Context mode": "Esa capa no está disponible en el modo de contexto actual", "start": "iniciarse", @@ -391,7 +391,7 @@ "Expand LOCATION": "Expandir UBICACIÓN", "UNAVAILABLE": "NO DISPONIBLE", "Live Flights": "Vuelos en vivo", - "Earthquakes (24h)": "Terremotos (24h)", + "Earthquakes (24h)": "Terremotos (24 h)", "No selected data layers": "No hay capas de datos seleccionadas", "Cleared {count} data layers": { "one": "Se limpió {count} capa de datos", @@ -461,7 +461,7 @@ "DISABLING": "APAGANDO", "Military Flights": "Vuelos militares", "Satellites": "Satélites", - "Space Missions (30d)": "Misiones espaciales (30d)", + "Space Missions (30d)": "Misiones espaciales (30 d)", "Street Traffic": "Tráfico urbano", "Radio": "Radio", "Bikeshare": "Bicicletas compartidas", @@ -472,7 +472,7 @@ "Dams": "Presas", "Submarine Cables": "Cables submarinos", "FIRMS Active Fires": "Incendios activos FIRMS", - "just now": "justo ahora", + "just now": "hace un momento", "{count}s ago": "hace {count} s", "{count}m ago": "hace {count} min", "{count}h ago": "hace {count} h", @@ -663,7 +663,7 @@ "▲ {frp} MW": "▲ {frp} MW", "FIRE": "INCENDIO", "FIRES": "INCENDIOS", - "{value} conf": "{value} de confianza", + "{value} conf": "confianza {value}", "{value} ago": "hace {value}", "max {value} MW": "máx {value} MW", "new {value}": "nueva hace {value}", @@ -746,7 +746,7 @@ "ESC to dismiss": "ESC para cerrar", "Tip: the GEV MIC button in the dock lets you talk to the map.": "Consejo: el botón GEV MIC del dock te permite hablar con el mapa.", "Close key setup": "Cerrar la configuración de claves", - "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.": "El globo ya vuela sin claves. Cada clave de abajo activa otra fuente real — pega una y queda guardada en la configuración local de esta app, y luego el servidor se reinicia solo. Las claves del servidor se quedan en esta máquina; Google Maps y Cesium ion funcionan en el navegador y sus claves deben estar restringidas por el proveedor. Las claves que configuraste en otro lugar se muestran, pero nunca se modifican.", + "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.": "El globo ya funciona sin claves. Cada clave de abajo activa otra fuente real — pega una y queda guardada en la configuración local de esta app, y luego el servidor se reinicia solo. Las claves del servidor se quedan en esta máquina; Google Maps y Cesium ion funcionan en el navegador y sus claves deben restringirse en la consola del proveedor. Las claves que configuraste en otro lugar se muestran, pero nunca se modifican.", "ESC to close": "ESC para cerrar", "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "La clave de Google Maps activa el planeta fotorrealista; todo lo demás se superpone.", "SCENES": "ESCENAS", @@ -797,7 +797,7 @@ "VOICE STANDBY": "VOZ EN ESPERA", "VOICE ACTIVE": "VOZ ACTIVA", "VOICE UNAVAILABLE": "VOZ NO DISPONIBLE", - "WebRTC microphone support unavailable": "Compatibilidad con micrófono WebRTC no disponible", + "WebRTC microphone support unavailable": "Este navegador no admite micrófono WebRTC", "Requesting microphone": "Solicitando micrófono", "Hold Space to talk": "Mantén Space y habla", "Release Space to send": "Suelta Space para enviar", @@ -835,7 +835,7 @@ "Places context + Street View fallback; optional separate key": "Contexto de Places + respaldo de Street View; clave separada opcional", "Live ships, worldwide": "Buques en vivo, en todo el mundo", "Live active-fire detections": "Detecciones de incendios activos en vivo", - "Real live traffic (keyless runs a simulation)": "Tráfico real en vivo (sin clave usa una simulación)", + "Real live traffic (keyless runs a simulation)": "Tráfico real en vivo (sin clave se usa una simulación)", "Bing imagery map stacks + world terrain": "Imágenes de Bing + terreno mundial", "More flight-polling credits (anonymous works without)": "Más créditos de consulta de vuelos (sin clave funciona el modo anónimo)", "Higher space-missions request allowance": "Mayor límite de peticiones para misiones espaciales", @@ -844,7 +844,7 @@ "Next session: {pendingId} — this session stays on {modelId}": "Sesión siguiente: {pendingId} — esta sesión sigue en {modelId}", "Voice model: {pendingId} — click to switch to {target}; applies next session": "Modelo de voz: {pendingId} — haz clic para cambiar a {target}; se aplica en la próxima sesión", "Estimated session cost on {modelId} — {responses} response(s). Warns at {warn}, ends the session at {cap}.": "Costo de sesión estimado en {modelId} — {responses} respuesta(s). Avisa a {warn} y cierra la sesión a {cap}.", - "Street-level imagery and coverage in the Street Level layer. Free: register an app in the Mapillary developer dashboard and paste its Client Token": "Imágenes y cobertura a nivel de calle en la capa A pie de calle. Gratis: registra una app en el panel de desarrolladores de Mapillary y pega su token de cliente (Client Token)", + "Street-level imagery and coverage in the Street Level layer. Free: register an app in the Mapillary developer dashboard and paste its Client Token": "Imágenes y cobertura a nivel de calle en la capa \"A pie de calle\". Gratis: registra una app en el panel de desarrolladores de Mapillary y pega su token de cliente (Client Token)", "Voice control — API key or ChatGPT OAuth": "Control de voz — clave de API u OAuth de ChatGPT", "WITHIN {km} KM": "A MENOS DE {km} KM", "Syncing flow · Roads: {source}": "Sincronizando flujo · Vías: {source}", @@ -914,7 +914,7 @@ "Data Centers": "Centros de datos", "Active Fires": "Incendios activos", "Street Level could not start": "No se pudo iniciar A pie de calle", - "Street Level toggle failed": "No se pudo cambiar A pie de calle", + "Street Level toggle failed": "No se pudo activar o desactivar la capa A pie de calle", "Street-level image": "Imagen a pie de calle", "SHRINK": "REDUCIR", "EXPAND": "AMPLIAR", @@ -981,11 +981,11 @@ "Could not place the shape: {error}": "No se pudo colocar la forma: {error}", "{owner} is using the pointer — close it first.": "{owner} está usando el puntero — ciérralo primero.", "{label} · Detailed outline unavailable": "{label} · Contorno detallado no disponible", - "Witness source": "Testimonio de testigo", + "Witness source": "Fuente de testigo", "{provider} · EMBEDDED SOURCE": "{provider} · FUENTE INSERTADA", "OPEN ORIGINAL ↗": "ABRIR ORIGINAL ↗", "{provider} media: {source}": "Contenido de {provider}: {source}", - "{provider} · PROVIDER CONTROLS": "{provider} · CONTROLES NATIVOS", + "{provider} · PROVIDER CONTROLS": "{provider} · CONTROLES DEL PROVEEDOR", "YOUTUBE {state} · OPEN ORIGINAL": "YOUTUBE {state} · ABRIR ORIGINAL", "Facebook player API did not initialize.": "La API del reproductor de Facebook no se inicializó.", "Facebook player API could not load.": "No se pudo cargar la API del reproductor de Facebook.", @@ -995,18 +995,18 @@ "X embed timed out.": "La inserción de X agotó el tiempo de espera.", "TIMEOUT": "TIEMPO AGOTADO", "BLOCKED": "BLOQUEADO", - "Facebook embed unavailable. The post may be private, removed, region-restricted, or blocked.": "Inserción de Facebook no disponible. La publicación puede ser privada, haber sido eliminada, estar restringida por región o bloqueada.", + "Facebook embed unavailable. The post may be private, removed, region-restricted, or blocked.": "Inserción de Facebook no disponible. La publicación puede ser privada, haber sido eliminada, estar restringida por región o estar bloqueada.", "Preloading {provider} media": "Precargando contenido de {provider}", "LOADING X POST…": "CARGANDO PUBLICACIÓN DE X…", "The X post is unavailable or cannot be embedded.": "La publicación de X no está disponible o no se puede insertar.", - "X POST · PROVIDER CONTROLS": "PUBLICACIÓN DE X · CONTROLES NATIVOS", + "X POST · PROVIDER CONTROLS": "PUBLICACIÓN DE X · CONTROLES DEL PROVEEDOR", "{reason} OPEN ORIGINAL.": "{reason} ABRE EL ORIGINAL.", "X embed unavailable": "Inserción de X no disponible", - "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · CONTROLES NATIVOS", + "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · CONTROLES DEL PROVEEDOR", "Facebook embed unavailable": "Inserción de Facebook no disponible", "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "REPRODUCCIÓN AUTOMÁTICA BLOQUEADA · USA LOS CONTROLES DEL PROVEEDOR", "Embedded {provider} media for {source}": "Contenido de {provider} insertado para {source}", - "witness source": "testimonio de testigo", + "witness source": "fuente de testigo", "Rasuwagadhi witness": "Testigo de Rasuwagadhi", "Open original source for {source}; capture time unverified": "Abrir la fuente original de {source}; hora de captura sin verificar", "Historical reference and post-event image divider": "Divisor entre la imagen de referencia histórica y la posterior al evento", @@ -1064,7 +1064,7 @@ "Close event layer": "Cerrar la capa del evento", "OBSERVED IMAGERY": "IMÁGENES OBSERVADAS", "SCHEMATIC CORRIDOR": "CORREDOR ESQUEMÁTICO", - "2021 HISTORICAL REFERENCE / 2026 POST-EVENT": "HISTÓRICA 2021 / TRAS EL EVENTO 2026", + "2021 HISTORICAL REFERENCE / 2026 POST-EVENT": "REF. HISTÓRICA 2021 / POSTERIOR AL EVENTO 2026", "Historical reference and post-event image split": "División entre la imagen de referencia histórica y la posterior al evento", "RECONSTRUCTION CLOCK": "RELOJ DE LA RECONSTRUCCIÓN", "Schematic downstream progression": "Avance esquemático aguas abajo", @@ -1083,7 +1083,7 @@ "FIELD REPORTS · {count}": "INFORMES DE CAMPO · {count}", "GEOLOCATIONS · {credit}": "GEOLOCALIZACIONES · {credit}", "↗ OPEN PUBLIC MAP": "↗ ABRIR MAPA PÚBLICO", - "Clouds are preserved from the source imagery.": "Las nubes se conservan de las imágenes de origen.", + "Clouds are preserved from the source imagery.": "Las nubes provienen de las imágenes de origen.", "Show the Nepal context callout without a border or country fill": "Muestra la etiqueta de contexto de Nepal sin frontera ni relleno del país", "Introduce Nepal, draw its border, then reveal the Bhote Koshi incident": "Presenta Nepal, dibuja su frontera y luego revela el incidente del Bhote Koshi", "CITIES": "CIUDADES", @@ -1142,9 +1142,9 @@ "retrying mapped ALPR cameras": "reintentando cámaras ALPR mapeadas", "loading mapped ALPR cameras": "cargando cámaras ALPR mapeadas", "No ALPR data for this area — US and Canada only": "Sin datos ALPR para esta zona — solo EE. UU. y Canadá", - "Zoom in to load mapped cameras": "Haz zoom para cargar las cámaras mapeadas", + "Zoom in to load mapped cameras": "Acércate para cargar las cámaras mapeadas", "Showing cached locations": "Mostrando ubicaciones en caché", - "Coverage limited — zoom in": "Cobertura limitada — haz zoom", + "Coverage limited — zoom in": "Cobertura limitada: acércate", "No ALPR data for this area": "Sin datos ALPR para esta zona", "None on screen — nearby cameras are outside the view": "Ninguna en pantalla — las cámaras cercanas están fuera de la vista", "ALPR camera": "Cámara ALPR", @@ -1166,7 +1166,7 @@ "many": "{count} de ciclones activos", "other": "{count} ciclones activos" }, - "Position as of {time}": "Posición a fecha de {time}", + "Position as of {time}": "Posición a las {time}", "Maximum sustained wind: {wind} · Pressure: {pressure}": "Viento máximo sostenido: {wind} · Presión: {pressure}", "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · Aviso {number} · emitido {time}", "No active NHC/CPHC systems": "Sin sistemas activos del NHC/CPHC", @@ -1417,7 +1417,7 @@ "Apply details": "Aplicar detalles", "Select a scene first": "Selecciona primero una escena", "Share selected scene": "Compartir la escena seleccionada", - "Scene JSON preserves authored settings and attribution. It does not include asset files. Bundles include only pack files you choose here or previously imported bundle files.": "El JSON de escena conserva los ajustes de autor y la atribución. No incluye archivos de recursos. Los paquetes solo incluyen los archivos de paquete que elijas aquí o los archivos de paquetes importados antes.", + "Scene JSON preserves authored settings and attribution. It does not include asset files. Bundles include only pack files you choose here or previously imported bundle files.": "El JSON de escena conserva los ajustes definidos por el autor y la atribución. No incluye archivos de recursos. Los paquetes de escena solo incluyen los archivos de paquetes de datos que elijas aquí o los que se importaron antes.", "Download scene JSON": "Descargar JSON de escena", "Choose data-pack files": "Elegir archivos de paquete de datos", "Or choose a data-pack folder": "O elige una carpeta de paquete de datos", @@ -1505,7 +1505,7 @@ "Latest": "Último", "Newest frame per product": "Fotograma más reciente por producto", "Replay observed history": "Reproducir el historial observado", - "S on another day replaces it · × unpins": "S en otro día lo reemplaza · × desfija", + "S on another day replaces it · × unpins": "S en otro día lo reemplaza · × quita la fijación", "← → preview · S shows the focused day": "← → vista previa · S muestra el día enfocado", "Recent imagery": "Imágenes recientes", "ZOOM IN": "ACERCAR", @@ -1513,7 +1513,7 @@ "Imagery days, newest first · arrows move, S or A and B pin": "Días de imágenes, el más reciente primero · las flechas mueven, S o A y B fijan", "On the map": "En el mapa", "Mode": "Modo", - "Imagery on Esri · Google 3D returns when cleared": "Imágenes sobre Esri · BORRAR devuelve Google 3D", + "Imagery on Esri · Google 3D returns when cleared": "Imágenes sobre Esri · BORRAR restaura Google 3D", "Imagery opacity": "Opacidad de las imágenes", "SWAP": "INVERTIR", "VS BASEMAP": "VS MAPA BASE", @@ -1553,8 +1553,8 @@ "Not set": "Sin definir", "Basemap": "Mapa base", "Source off": "Fuente desactivada", - "Unpin {slot}": "Desfijar {slot}", - "Unpin the image": "Desfijar la imagen", + "Unpin {slot}": "Quitar {slot}", + "Unpin the image": "Quitar la imagen fijada", "EXPORT {slot}": "EXP. {slot}", "EXPORT": "EXPORTAR", "Download {slot} as a PNG": "Descargar {slot} como PNG", @@ -1633,14 +1633,14 @@ "GAIN": "GANANCIA", "FM MHZ": "FM MHZ", "AUTO": "AUTO", - "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · un sintonizador local compartido por FM y la capa ADS-B local.", + "Chrome/Edge · WebUSB · one local tuner shared by FM and the Local ADS-B layer.": "Chrome/Edge · WebUSB · un sintonizador local compartido entre FM y la capa ADS-B local.", "RTL-SDR tuner gain for the current mode": "Ganancia del sintonizador RTL-SDR para el modo actual", "WEATHER": "CLIMA", "Active weather products": "Productos meteorológicos activos", "RECENT IMAGERY": "IMÁGENES RECIENTES", "Imagery days in the box": "Días de imágenes en el recuadro", "MSG/S": "MSG/S", - "HEARD": "OÍDAS", + "HEARD": "CAPTADAS", "POSITIONED": "POSICIONADAS", "SDR VOL": "VOL. SDR", "LOCAL RTL-SDR": "RTL-SDR LOCAL", @@ -1671,11 +1671,11 @@ "{time} · {minutes} min ago": "{time} · hace {minutes} min", "Active weather": "Capas meteorológicas activas", "MINI": "MINI", - "STD": "EST.", + "STD": "STD", "standard": "estándar", "mini": "mini", "COST UNKNOWN": "COSTO DESCONOCIDO", - "ChatGPT OAuth session: {responses} response(s), {input} input and {output} output tokens, {captions} caption transcription(s) reported.": "Sesión OAuth de ChatGPT: {responses} respuesta(s), {input} tokens de entrada y {output} de salida, {captions} transcripción(es) de subtítulos informadas.", + "ChatGPT OAuth session: {responses} response(s), {input} input and {output} output tokens, {captions} caption transcription(s) reported.": "Sesión OAuth de ChatGPT (datos informados): {responses} respuesta(s), {input} tokens de entrada y {output} de salida, {captions} transcripción(es) de subtítulos.", "USD cost and the API spend cap are unavailable for this auth mode.": "El costo en USD y el límite de gasto de la API no están disponibles con este modo de autenticación.", "Usage is incomplete because a response was still in flight when the session ended.": "El uso está incompleto porque una respuesta seguía en curso cuando terminó la sesión.", "Toggle voice control": "Alternar el control de voz", @@ -1730,7 +1730,7 @@ "Framing uses the nearest 80 loaded contacts": "El encuadre usa los 80 contactos cargados más cercanos", "{distance} from {location}": "A {distance} de {location}", "Altitude {altitude}": "Altitud {altitude}", - "{origin} to {destination}": "{origin} a {destination}", + "{origin} to {destination}": "De {origin} a {destination}", "from {origin}": "desde {origin}", "to {destination}": "a {destination}", "Registration {registration}": "Matrícula {registration}", @@ -1746,7 +1746,7 @@ "Tracing outlines": "Trazando contornos", "Mark the map": "Marcar el mapa", "Mark {targets} +{more}": "Marcar {targets} +{more}", - "Mark {targets}": "Marcar {targets}", + "Mark {targets}": "Marcar: {targets}", "Fly to {place}": "Volar a {place}", "Nearest aircraft to {place}": "Aeronaves más cercanas a {place}", "Read the view": "Consultar la vista", @@ -1838,7 +1838,7 @@ "ordinal|{number}rd": "{number}.ª", "ordinal|{number}th": "{number}.ª", "Head out on {road}": "Sal por {road}", - "Head out": "Sal", + "Head out": "Inicia el recorrido", "Arrive at your destination": "Has llegado a tu destino", "Arrive at your destination, on the left": "Tu destino está a la izquierda", "Arrive at your destination, on the right": "Tu destino está a la derecha", @@ -1898,7 +1898,7 @@ "Outline unavailable": "Contorno no disponible", "in progress": "en curso", "done": "hecho", - "failed": "falló", + "failed": "fallido", "cancelled": "cancelado", "view band|STREET": "CALLE", "view band|CITY": "CIUDAD", @@ -1908,7 +1908,7 @@ "NEAR {poi} ({city}) {distance}": "CERCA DE {poi} ({city}) {distance}", "SECTOR {lat} {lon}": "SECTOR {lat} {lon}", "ARCTIC": "ÁRTICO", - "ANTARCTIC": "ANTÁRTIDA", + "ANTARCTIC": "ANTÁRTICO", "NORTH AMERICA": "NORTEAMÉRICA", "SOUTH AMERICA": "SUDAMÉRICA", "EUROPE": "EUROPA", @@ -1927,7 +1927,7 @@ "RESUME": "REANUDAR", "dB": "dB", "radio|NEWS": "NOTICIAS", - "radio|TALK": "TERTULIA", + "radio|TALK": "RADIO HABLADA", "radio|WEATHER": "CLIMA", "radio|SAFETY": "SEGURIDAD", "radio|AIR / SEA": "AIRE / MAR", @@ -2007,8 +2007,8 @@ "Swipe needs a globe map": "La comparación necesita un mapa de globo", "Imagery stays on Esri · CLEAR to use Google 3D": "Las imágenes siguen en Esri · BORRAR para usar Google 3D", "Box and images cleared": "Recuadro e imágenes borrados", - "Box is {size} wide · limit {limit}": "El recuadro mide {size} · límite {limit}", - "View is {size} wide · limit {limit}": "La vista mide {size} · límite {limit}", + "Box is {size} wide · limit {limit}": "El recuadro mide {size} de ancho · límite {limit}", + "View is {size} wide · limit {limit}": "La vista mide {size} de ancho · límite {limit}", "Frame unavailable; previous observation retained": "Fotograma no disponible; se mantiene la observación anterior", "Weather imagery unavailable": "Imágenes meteorológicas no disponibles", "Weather source unavailable; previous observation retained": "Fuente meteorológica no disponible; se mantiene la observación anterior", @@ -2020,7 +2020,7 @@ "Globe imagery unavailable": "Imágenes del globo no disponibles", "Globe field image unavailable": "Imagen del campo en el globo no disponible", "status chip|UNAVAILABLE": "NO DISP.", - "Uses registered content or linked media. Those external files are not included in a scene bundle. Their original notices still apply.": "Incluye contenido registrado o medios vinculados. Esos archivos externos no se incluyen en un paquete de escena. Sus avisos originales siguen vigentes.", + "Uses registered content or linked media. Those external files are not included in a scene bundle. Their original notices still apply.": "Contiene referencias a contenido registrado o medios vinculados. Esos archivos externos no se incluyen en un paquete de escena. Sus avisos originales siguen vigentes.", "{state} {layers}": "{state}: {layers}", "Selected Space Mission": "Misión espacial seleccionada", "{duration}s + {hold}s": "{duration} s + {hold} s", @@ -2031,7 +2031,7 @@ "Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Necesita {envVars} — se añade en Ajustes de proveedores — o un token de Cesium ion para la ruta alojada en ion", "Comparison in use by another scene": "La comparación está en uso por otra escena", "A location reading needs a loaded forecast and the Earth at the center of the view.": "Una lectura en un punto necesita un pronóstico cargado y la Tierra en el centro de la vista.", - "{mode} {band} {locality} | {region} | ALT {alt} | WINDOW {window} | SUN {sun}° | ONA {ona}° | {zone}": "{mode} · {band} · {locality} | {region} | ALT {alt} | VENTANA {window} | SUN {sun}° | ONA {ona}° | {zone}", + "{mode} {band} {locality} | {region} | ALT {alt} | WINDOW {window} | SUN {sun}° | ONA {ona}° | {zone}": "{mode} · {band} · {locality} | {region} | ALT {alt} | VENTANA {window} | SOL {sun}° | ONA {ona}° | {zone}", "light {source} to use your {name} key": "activa {source} para usar tu clave de {name}", "light {source}": "activa {source}", ", or ": ", o ", @@ -2093,12 +2093,25 @@ "{value}°N": "{value}°N", "{value}°S": "{value}°S", "{value}°E": "{value}°E", - "{value}°W": "{value}°W", + "{value}°W": "{value}°O", "fork direction|left": "a la izquierda", "fork direction|right": "a la derecha", "fork direction|slightly left": "ligeramente a la izquierda", "fork direction|slightly right": "ligeramente a la derecha", "fork direction|sharply left": "bruscamente a la izquierda", "fork direction|sharply right": "bruscamente a la derecha", - "fork direction|straight": "recto" + "fork direction|straight": "recto", + "installation class|AIRFIELD": "AERÓDROMO", + "installation class|NAVAL BASE": "BASE NAVAL", + "installation class|RANGE": "CAMPO DE TIRO", + "installation class|MILITARY LAND": "TERRENO MILITAR", + "installation class|BASE": "BASE", + "installation class|PLACES CANDIDATE": "DE GOOGLE PLACES", + "installation class|INSTALLATION": "INSTALACIÓN", + "fire confidence|high": "alta", + "fire confidence|nominal": "nominal", + "fire confidence|low": "baja", + "age|<1h": "<1 h", + "age|{count}h": "{count} h", + "age|{count}d": "{count} d" } diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index b4636f815..9745ec5b7 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -7,7 +7,7 @@ "Loading the keyless globe...": "Загрузка глобуса без ключа...", "Google 3D Tiles unavailable ({detail}). Loading the keyless globe...": "Google 3D Tiles недоступны ({detail}). Загрузка глобуса без ключа...", "Close data attribution": "Закрыть атрибуцию данных", - "observed or mapped nearby context": "наблюдаемое или картографированное окружение", + "observed or mapped nearby context": "наблюдаемая или картографическая обстановка", "no observed or mapped objects in current feeds": "нет наблюдаемых или картографированных объектов в текущих потоках", "feed unavailable": "поток данных недоступен", "feed stale": "поток данных устарел", @@ -72,8 +72,8 @@ "Saved to {store}. Restarting — this page reloads itself.": "Сохранено: {store}. Перезапуск — страница перезагрузится сама.", "Remove this key from your saved configuration?": "Удалить этот ключ из сохранённой конфигурации?", "Removed from {store}. Restarting — this page reloads itself.": "Удалено: {store}. Перезапуск — страница перезагрузится сама.", - "More flight-polling credits (anonymous works without)": "Больше кредитов на опрос рейсов (анонимно работает и без ключа)", - "Street-level imagery and coverage in the Street Level layer. Free: register an app in the Mapillary developer dashboard and paste its Client Token": "Уличные снимки и покрытие в слое «Уличные снимки». Бесплатно: зарегистрируйте приложение в панели разработчика Mapillary и вставьте его клиентский токен (Client Token)", + "More flight-polling credits (anonymous works without)": "Повышенный лимит запросов рейсов (анонимно работает и без ключа)", + "Street-level imagery and coverage in the Street Level layer. Free: register an app in the Mapillary developer dashboard and paste its Client Token": "Уличные снимки и покрытие в слое «Уличные снимки». Бесплатно: зарегистрируйте приложение в панели разработчика Mapillary и вставьте его клиентский токен (Client Token).", "Higher space-missions request allowance": "Повышенный лимит запросов для космических миссий", "Places context + Street View fallback; optional separate key": "Контекст Places + резерв: Просмотр улиц; отдельный ключ — по желанию", "Voice control — API key or ChatGPT OAuth": "Голосовое управление — ключ API или ChatGPT OAuth", @@ -127,7 +127,7 @@ "SRC {value}": "ИСТ. {value}", "{value} CONTEXT": "КОНТЕКСТ {value}", "Focus fire detection {title}, {details}": "Фокус на очаге пожара {title}, {details}", - "{value} conf": "увер. {value}", + "{value} conf": "увер.: {value}", "{value} ago": "{value} назад", "sensor n/a": "сенсор н/д", "FIRE · {frp} MW": "ПОЖАР · {frp} МВт", @@ -379,15 +379,15 @@ "L": "Л", "R": "П", "{count} INPUTS UNKNOWN · NOT AN ALL-CLEAR": { - "one": "{count} ИСТОЧНИК ДАННЫХ НЕИЗВЕСТЕН · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", - "few": "{count} ИСТОЧНИКА ДАННЫХ НЕИЗВЕСТНЫ · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", - "many": "{count} ИСТОЧНИКОВ ДАННЫХ НЕИЗВЕСТНО · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", - "other": "{count} ИСТОЧНИКА ДАННЫХ НЕИЗВЕСТНО · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА" + "one": "{count} ИСТОЧНИК ДАННЫХ НЕИЗВЕСТЕН · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", + "few": "{count} ИСТОЧНИКА ДАННЫХ НЕИЗВЕСТНЫ · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", + "many": "{count} ИСТОЧНИКОВ ДАННЫХ НЕИЗВЕСТНО · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", + "other": "{count} ИСТОЧНИКА ДАННЫХ НЕИЗВЕСТНО · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО" }, - "AVAILABLE INPUTS CURRENT · NOT AN ALL-CLEAR": "ДОСТУПНЫЕ ДАННЫЕ АКТУАЛЬНЫ · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", + "AVAILABLE INPUTS CURRENT · NOT AN ALL-CLEAR": "ДОСТУПНЫЕ ДАННЫЕ АКТУАЛЬНЫ · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", "CONTEXT STANDBY": "КОНТЕКСТ В ОЖИДАНИИ", "ENABLE GLOBAL CONTEXT FOR PROXIMITY PINGS": "ВКЛЮЧИТЕ ГЛОБАЛЬНЫЙ КОНТЕКСТ ДЛЯ ПИНГОВ ПОБЛИЗОСТИ", - "CONTACT LOST · LAST KNOWN READOUT · NOT AN ALL-CLEAR": "КОНТАКТ ПОТЕРЯН · ПОСЛЕДНИЕ ИЗВЕСТНЫЕ ДАННЫЕ · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", + "CONTACT LOST · LAST KNOWN READOUT · NOT AN ALL-CLEAR": "КОНТАКТ ПОТЕРЯН · ПОСЛЕДНИЕ ИЗВЕСТНЫЕ ДАННЫЕ · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", "CONTACT LOST · {subject}": "КОНТАКТ ПОТЕРЯН · {subject}", "SUBJECT LEFT ITS FEED · READOUT HOLDING LAST KNOWN": "ОБЪЕКТ ВЫПАЛ ИЗ ПОТОКА · ПОКАЗАНЫ ПОСЛЕДНИЕ ДАННЫЕ", "{cohort} · {contact}": "{cohort} · {contact}", @@ -659,7 +659,7 @@ "GROUND SPEED": "ПУТЕВАЯ СКОРОСТЬ", "ALTITUDE": "ВЫСОТА", "CONTEXT ONLY": "ТОЛЬКО КОНТЕКСТ", - "AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR": "ТОЛЬКО ДОСТУПНЫЕ ДАННЫЕ · БЕЗОПАСНОСТЬ НЕ ПОДТВЕРЖДЕНА", + "AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR": "ТОЛЬКО ДОСТУПНЫЕ ДАННЫЕ · ОТСУТСТВИЕ УГРОЗ НЕ ПОДТВЕРЖДЕНО", "ESTIMATED FLIGHT PLAN": "РАСЧЁТНЫЙ ПЛАН ПОЛЁТА", "FROM": "ОТКУДА", "TO": "КУДА", @@ -722,7 +722,7 @@ "STATION TAG": "ТЕГ СТАНЦИИ", "DRAG TO TUNE": "ТЯНИТЕ ДЛЯ НАСТРОЙКИ", "TAB PREVIEWS · ENTER / SPACE SELECTS": "TAB — ПРОСМОТР · ENTER / SPACE — ВЫБОР", - "Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.": "После нажатия «Пуск» аудио подключается напрямую к вещателю. Ваш IP виден этому вещателю.", + "Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.": "После нажатия «Пуск» звук идёт напрямую от вещателя, и ему виден ваш IP-адрес.", "Internet radio and local SDR": "Интернет-радио и локальный SDR", "Filter stations by station tag": "Фильтровать станции по тегу", "CONTEXT": "КОНТЕКСТ", @@ -806,9 +806,9 @@ "SCENES": "СЦЕНЫ", "Close key setup": "Закрыть настройку ключей", "SAVE KEYS": "СОХРАНИТЬ КЛЮЧИ", - "Power up the globe": "Запустите глобус", - "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.": "Глобус уже работает без ключей. Каждый ключ ниже включает ещё один реальный поток данных — вставьте ключ, и он сохранится в локальной конфигурации приложения, после чего сервер перезапустится сам. Серверные ключи остаются на этом компьютере; Google Maps и Cesium ion работают в браузере, поэтому их ключи нужно ограничить на стороне провайдера. Ключи, настроенные в другом месте, отображаются, но не изменяются.", - "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "Ключ Google Maps открывает фотореалистичную планету — всё остальное надстраивается поверх неё.", + "Power up the globe": "Подключите потоки данных", + "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.": "Глобус уже работает без ключей. Каждый ключ ниже включает ещё один поток данных в реальном времени — вставьте ключ, и он сохранится в локальной конфигурации приложения, после чего сервер перезапустится сам. Серверные ключи остаются на этом компьютере; Google Maps и Cesium ion работают в браузере, поэтому их ключи нужно ограничить на стороне провайдера. Ключи, настроенные в другом месте, видны здесь, но изменить их нельзя.", + "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "Ключ Google Maps открывает фотореалистичную планету — остальное накладывается поверх.", "POWER UP": "ЗАПУСК", "GROUND STATION · PROVIDER SETTINGS": "НАЗЕМНАЯ СТАНЦИЯ · НАСТРОЙКИ ПРОВАЙДЕРОВ", "ESC to close": "ESC — закрыть", @@ -880,7 +880,7 @@ "Ironbow": "Ironbow", "Pick a shape, then click the map.": "Выберите фигуру и щёлкните по карте.", "Enter to place the pin, Esc to cancel.": "Enter — поставить метку, Esc — отменить.", - "Click where the pin goes.": "Щёлкните, где поставить метку.", + "Click where the pin goes.": "Щёлкните там, где нужно поставить метку.", "Click {count} more points.": { "one": "Отметьте ещё {count} точку.", "few": "Отметьте ещё {count} точки.", @@ -889,7 +889,7 @@ }, "Those points are in a line — move one off it to enclose an area.": "Точки лежат на одной линии — сдвиньте одну из них, чтобы образовалась замкнутая область.", "That line has no length — click somewhere further away.": "У линии нет длины — щёлкните дальше.", - " · {max}-point limit reached": " · лимит точек: {max}", + " · {max}-point limit reached": " · достигнут лимит точек: {max}", "{measure} · double-click or Enter to finish, Backspace undoes, Esc cancels.{full}": "{measure} · двойной щелчок или Enter — завершить, Backspace — отменить точку, Esc — отменить.{full}", "That point is off the globe — click on the world.": "Эта точка вне глобуса — щёлкните по Земле.", "That shape could not be placed.": "Не удалось разместить фигуру.", @@ -913,7 +913,7 @@ "Remove every mark from the board": "Убрать все пометки с доски", "Point the camera at the globe for street-level coverage": "Наведите камеру на глобус, чтобы увидеть покрытие уличных снимков", "No street-level imagery providers configured": "Источники уличных снимков не настроены", - "Selected": "Выбранная", + "Selected": "Выбрано", "Live Vessels": "Суда онлайн", "Bike Share": "Прокат велосипедов", "Cameras": "Камеры", @@ -996,7 +996,7 @@ "{provider} · EMBEDDED SOURCE": "{provider} · ВСТРОЕННЫЙ ИСТОЧНИК", "OPEN ORIGINAL ↗": "ОТКРЫТЬ ОРИГИНАЛ ↗", "{provider} media: {source}": "Медиа {provider}: {source}", - "{provider} · PROVIDER CONTROLS": "{provider} · УПРАВЛЕНИЕ ПРОВАЙДЕРА", + "{provider} · PROVIDER CONTROLS": "{provider} · ВСТРОЕННЫЙ ПЛЕЕР", "YOUTUBE {state} · OPEN ORIGINAL": "YOUTUBE {state} · ОТКРЫТЬ ОРИГИНАЛ", "Facebook player API did not initialize.": "API плеера Facebook не инициализировался.", "Facebook player API could not load.": "Не удалось загрузить API плеера Facebook.", @@ -1010,16 +1010,16 @@ "Preloading {provider} media": "Предзагрузка медиа {provider}", "LOADING X POST…": "ЗАГРУЗКА ПОСТА X…", "The X post is unavailable or cannot be embedded.": "Пост X недоступен или не может быть встроен.", - "X POST · PROVIDER CONTROLS": "ПОСТ X · УПРАВЛЕНИЕ ПРОВАЙДЕРА", + "X POST · PROVIDER CONTROLS": "ПОСТ X · ВСТРОЕННЫЙ ВИДЖЕТ", "{reason} OPEN ORIGINAL.": "{reason} ОТКРОЙТЕ ОРИГИНАЛ.", "X embed unavailable": "Встраивание X недоступно", - "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · УПРАВЛЕНИЕ ПРОВАЙДЕРА", + "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · ВСТРОЕННЫЙ ПЛЕЕР", "Facebook embed unavailable": "Встраивание Facebook недоступно", - "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "АВТОВОСПРОИЗВЕДЕНИЕ ЗАБЛОКИРОВАНО · ИСПОЛЬЗУЙТЕ УПРАВЛЕНИЕ ПРОВАЙДЕРА", + "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "АВТОВОСПРОИЗВЕДЕНИЕ ЗАБЛОКИРОВАНО · ЗАПУСТИТЕ В ПЛЕЕРЕ", "Embedded {provider} media for {source}": "Встроенное медиа {provider}: {source}", "witness source": "свидетельство очевидца", "Rasuwagadhi witness": "Очевидец в Rasuwagadhi", - "Open original source for {source}; capture time unverified": "Открыть исходный источник: {source}; время съёмки не подтверждено", + "Open original source for {source}; capture time unverified": "Открыть оригинал: {source}; время съёмки не подтверждено", "Historical reference and post-event image divider": "Разделитель исторического снимка и снимка после события", "A historical reference {before} percent, B post-event {after} percent": "A — исторический снимок: {before}%, B — после события: {after}%", "■ RELEASE CAMERA": "■ ОСВОБОДИТЬ КАМЕРУ", @@ -1075,7 +1075,7 @@ "EVENT RECONSTRUCTION · 26 AUG 2026": "РЕКОНСТРУКЦИЯ СОБЫТИЯ · 26 АВГ. 2026", "BHOTE KOSHI OUTBURST FLOOD": "ПРОРЫВНОЙ ПАВОДОК НА BHOTE KOSHI", "Close event layer": "Закрыть слой события", - "OBSERVED IMAGERY": "НАБЛЮДАЕМЫЕ СНИМКИ", + "OBSERVED IMAGERY": "ФАКТИЧЕСКИЕ СНИМКИ", "SCHEMATIC CORRIDOR": "СХЕМАТИЧЕСКИЙ КОРИДОР", "2021 HISTORICAL REFERENCE / 2026 POST-EVENT": "2021 ИСТОРИЧЕСКИЙ СНИМОК / 2026 ПОСЛЕ СОБЫТИЯ", "Historical reference and post-event image split": "Сравнение исторического снимка и снимка после события", @@ -1096,7 +1096,7 @@ "FIELD REPORTS · {count}": "СООБЩЕНИЯ С МЕСТА · {count}", "GEOLOCATIONS · {credit}": "ГЕОЛОКАЦИИ · {credit}", "↗ OPEN PUBLIC MAP": "↗ ОТКРЫТЬ ПУБЛИЧНУЮ КАРТУ", - "Clouds are preserved from the source imagery.": "Облака сохранены из исходных снимков.", + "Clouds are preserved from the source imagery.": "Облака оставлены такими, как на исходных снимках.", "Show the Nepal context callout without a border or country fill": "Показать выноску с контекстом Непала без границы и заливки страны", "Introduce Nepal, draw its border, then reveal the Bhote Koshi incident": "Представить Непал, нарисовать его границу, затем показать инцидент на Bhote Koshi", "CITIES": "ГОРОДА", @@ -1119,8 +1119,8 @@ "flight": "рейс", "military flight": "военный рейс", "satellite": "спутник", - "VOICE AUTH · CHATGPT OAUTH": "АВТОРИЗАЦИЯ ГОЛОСА · CHATGPT OAUTH", - "VOICE AUTH · API KEY": "АВТОРИЗАЦИЯ ГОЛОСА · КЛЮЧ API", + "VOICE AUTH · CHATGPT OAUTH": "ГОЛОС: АВТОРИЗАЦИЯ · CHATGPT OAUTH", + "VOICE AUTH · API KEY": "ГОЛОС: КЛЮЧ API", "USE API KEY": "ИСПОЛЬЗОВАТЬ КЛЮЧ API", "USE CHATGPT OAUTH": "ИСПОЛЬЗОВАТЬ CHATGPT OAUTH", "Use OPENAI_API_KEY for the next cloud voice session": "Использовать OPENAI_API_KEY для следующей облачной голосовой сессии", @@ -1163,7 +1163,7 @@ "No ALPR data for this area": "Нет данных ALPR для этой области", "None on screen — nearby cameras are outside the view": "На экране нет — ближайшие камеры вне поля зрения", "ALPR camera": "Камера ALPR", - "OpenStreetMap · community mapped": "OpenStreetMap · нанесено сообществом", + "OpenStreetMap · community mapped": "OpenStreetMap · данные сообщества", "Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.": "Атлантика и восточная/центральная часть северного Тихого океана; покрытие циклонов не глобальное.", "Potential tropical cyclone": "Потенциальный тропический циклон", "Hurricane": "Ураган", @@ -1230,8 +1230,8 @@ "{distance} · {duration} · {mode} · first {count} turns": "{distance} · {duration} · {mode} · первые повороты: {count}", "{distance} · {duration} · {mode}": "{distance} · {duration} · {mode}", "Click the globe to place {endpoint}": "Щёлкните по глобусу, чтобы поставить {endpoint}", - "SET B, then click the globe": "Нажмите ЗАДАТЬ B, затем щёлкните по глобусу", - "SET A, then click the globe": "Нажмите ЗАДАТЬ A, затем щёлкните по глобусу", + "SET B, then click the globe": "Нажмите «ЗАДАТЬ B», затем щёлкните по глобусу", + "SET A, then click the globe": "Нажмите «ЗАДАТЬ A», затем щёлкните по глобусу", "Step {step} of {total} · then {leg}": "Шаг {step} из {total} · затем {leg}", "Step {step} of {total}": "Шаг {step} из {total}", "Then: {instruction}": "Затем: {instruction}", @@ -1250,7 +1250,7 @@ "Sentinel-2 and Landsat via HLS, 30 m, every 2–6 days": "Sentinel‑2 и Landsat через HLS, 30 м, каждые 2–6 дней", "DAILY OVERVIEW · 250 m": "ЕЖЕДНЕВНЫЙ ОБЗОР · 250 м", "VIIRS daily mosaic, 250 m, follows broad changes; little detail under 25 km": "Ежедневная мозаика VIIRS, 250 м, показывает крупные изменения; мало деталей в масштабе менее 25 км", - "Catalog truncated · a newer clear day may exist": "Каталог усечён · может быть более новый ясный день", + "Catalog truncated · a newer clear day may exist": "Каталог усечён · возможно, есть более поздний ясный день", "{label} catalog unavailable · {detail}": "Каталог {label} недоступен · {detail}", "Daily overview for today may still be empty until the pass": "Ежедневный обзор за сегодня может оставаться пустым до пролёта спутника", "Sentinel-2": "Sentinel‑2", @@ -1313,7 +1313,7 @@ "STALE · cached source metadata": "УСТАРЕЛ · метаданные источника из кэша", "Map center is outside source coverage": "Центр карты вне зоны покрытия источника", "NOAA/NWS 15-minute lightning density derived from Vaisala NLDN/GLD360. Coverage 110°E across the Pacific/Americas to 0°, 25°S–80°N. Not a live strike count, global coverage or a safety warning.": "15-минутная плотность молний NOAA/NWS на основе Vaisala NLDN/GLD360. Покрытие — от 110° в. д. через Тихий океан и Америку до 0°, 25° ю. ш.–80° с. ш. Это не подсчёт разрядов в реальном времени, не глобальное покрытие и не предупреждение об опасности.", - "NOAA MRMS radar echoes indicate precipitation patterns, not rain rate, a storm warning or a future forecast. Native source approximately 1 km; display is limited to level 6. Frames use exact advertised observation times.": "Радиолокационные эхосигналы NOAA MRMS показывают картину осадков, а не их интенсивность, штормовое предупреждение или прогноз. Исходное разрешение около 1 км; отображение ограничено уровнем 6. Кадры используют точное заявленное время наблюдений.", + "NOAA MRMS radar echoes indicate precipitation patterns, not rain rate, a storm warning or a future forecast. Native source approximately 1 km; display is limited to level 6. Frames use exact advertised observation times.": "Радиолокационные эхосигналы NOAA MRMS показывают картину осадков, а не их интенсивность, штормовое предупреждение или прогноз. Исходное разрешение около 1 км; отображение ограничено уровнем 6. Для кадров используется точное заявленное время наблюдений.", "GOES-19/18 longwave infrared Band 14 regional; NESDIS global longwave mosaic. Clouds only dims everything but bright, cold cloud tops; a brightness filter, not a cloud mask. Coverage and freshness differ by region.": "Региональный длинноволновый ИК-канал 14 GOES-19/18; глобальная длинноволновая мозаика NESDIS. Режим «Только облака» затемняет всё, кроме ярких холодных вершин облаков; это фильтр яркости, а не маска облачности. Покрытие и актуальность различаются по регионам.", "REGION": "РЕГИОН", "IMAGE": "СНИМОК", @@ -1326,7 +1326,7 @@ "Global · 1° grid": "Глобально · сетка 1°", "{model} forecast · {time}": "Прогноз {model} · {time}", "Loading forecast": "Загрузка прогноза", - "Preparing flow": "Подготовка потока", + "Preparing flow": "Подготовка анимации ветра", "Selected field unavailable": "Выбранное поле недоступно", "Cached forecast · stale": "Прогноз из кэша · устарел", "ECMWF IFS surface forecast": "Приземный прогноз ECMWF IFS", @@ -1350,7 +1350,7 @@ "Issued: {time}": "Выпущен: {time}", "Selected field unavailable · wind remains visible": "Выбранное поле недоступно · ветер остаётся видимым", "{error} · wind remains visible": "{error} · ветер остаётся видимым", - "Surface wind at 10 m. Approximately 1° global grid. Curves follow the 10 m wind field, lifted 12 km for visibility; display height is not weather altitude. View lighting is for readability. Animation shows flow through one fixed forecast; it does not advance time. Color fields drape the globe basemap or the active photorealistic 3D Tiles.": "Приземный ветер на высоте 10 м. Глобальная сетка около 1°. Линии следуют полю ветра на 10 м и подняты на 12 км для наглядности; высота отображения не является высотой погодных явлений. Освещение вида — для удобства чтения. Анимация показывает поток в одном фиксированном прогнозе и не сдвигает время. Цветовые поля накладываются на базовую карту глобуса или на активные фотореалистичные 3D Tiles.", + "Surface wind at 10 m. Approximately 1° global grid. Curves follow the 10 m wind field, lifted 12 km for visibility; display height is not weather altitude. View lighting is for readability. Animation shows flow through one fixed forecast; it does not advance time. Color fields drape the globe basemap or the active photorealistic 3D Tiles.": "Приземный ветер на высоте 10 м. Глобальная сетка около 1°. Линии следуют полю ветра на 10 м и подняты на 12 км для наглядности; высота отображения не является высотой погодных явлений. Освещение вида — для удобства чтения. Анимация показывает поток в одном фиксированном прогнозе и не сдвигает время. Цветовые поля накладываются на подложку глобуса или на активные фотореалистичные 3D Tiles.", "MODEL": "МОДЕЛЬ", "FIELD": "ПОЛЕ", "UNITS": "ЕДИНИЦЫ", @@ -1367,7 +1367,7 @@ "Source": "Источник", "Could not leave scene: {scene}": "Не удалось выйти из сцены: {scene}", "Seeked: {scene} / {shot}": "Перемотано: {scene} / {shot}", - "Scene transition limit reached — load a shot to reset": "Достигнут лимит переходов сцены — загрузите кадр, чтобы сбросить его", + "Scene transition limit reached — load a shot to reset": "Достигнут лимит переходов сцены — загрузите кадр, чтобы сбросить счётчик", "Export failed: {error}": "Ошибка экспорта: {error}", "Imported {file}": "Импортировано: {file}", "Import failed: {error}": "Ошибка импорта: {error}", @@ -1420,18 +1420,18 @@ "Review scene import": "Проверка импорта сцены", "Nothing is loaded or changed until you apply this file.": "Ничего не загружается и не меняется, пока вы не примените этот файл.", "Ready to import": "Готово к импорту", - "Apply replaces the current project. Export your current project first if you want to keep both.": "Применение заменит текущий проект. Чтобы сохранить оба, сначала экспортируйте текущий проект.", + "Apply replaces the current project. Export your current project first if you want to keep both.": "Применение заменит текущий проект. Чтобы не потерять его, сначала экспортируйте текущий проект.", "Apply import": "Применить импорт", "Import was not applied. The current project may have changed.": "Импорт не применён. Возможно, текущий проект изменился.", "Select a scene and shot first": "Сначала выберите сцену и кадр", "Edit scene details": "Изменить параметры сцены", - "Camera positions use degrees and meters above the ellipsoid. Drafts are validated before they replace your saved scene.": "Положения камеры задаются в градусах и метрах над эллипсоидом. Черновики проверяются, прежде чем заменить сохранённую сцену.", + "Camera positions use degrees and meters above the ellipsoid. Drafts are validated before they replace your saved scene.": "Положения камеры задаются в градусах и метрах над эллипсоидом. Перед заменой сохранённой сцены черновик проверяется.", "Anchors and data packs": "Якоря и пакеты данных", "Shot camera, timing, packs and actions": "Камера кадра, тайминг, пакеты и действия", "New anchor ID": "ID нового якоря", "Capture camera as anchor": "Сохранить камеру как якорь", "Anchor added to draft": "Якорь добавлен в черновик", - "Set move start to current camera": "Начать перелёт с текущей камеры", + "Set move start to current camera": "Начало перелёта — текущая камера", "Move added to draft; destination remains the shot camera": "Перелёт добавлен в черновик; конечной точкой остаётся камера кадра", "Use ordinary flight": "Использовать обычный перелёт", "Invalid shot JSON": "Некорректный JSON кадра", @@ -1440,7 +1440,7 @@ "Share selected scene": "Поделиться выбранной сценой", "Scene JSON preserves authored settings and attribution. It does not include asset files. Bundles include only pack files you choose here or previously imported bundle files.": "JSON сцены сохраняет авторские настройки и атрибуцию, но не содержит файлов ресурсов. Архив включает только файлы пакетов, выбранные здесь, или файлы ранее импортированных архивов.", "Download scene JSON": "Скачать JSON сцены", - "Choose data-pack files": "Выбрать файлы пакетов данных", + "Choose data-pack files": "Выберите файлы пакетов данных", "Or choose a data-pack folder": "Или выберите папку пакета данных", "Download asset bundle": "Скачать архив ресурсов", "Asset bundle downloaded": "Архив ресурсов скачан", @@ -1529,7 +1529,7 @@ "Latest": "Последние", "Newest frame per product": "Самый свежий кадр каждого продукта", "Replay observed history": "Воспроизвести историю наблюдений", - "S on another day replaces it · × unpins": "S на другом дне заменяет его · × открепляет", + "S on another day replaces it · × unpins": "S в другой день заменяет его · × открепляет", "← → preview · S shows the focused day": "← → просмотр · S показывает выбранный день", "Recent imagery": "Свежие снимки", "ZOOM IN": "ПРИБЛИЗИТЬ", @@ -1537,10 +1537,10 @@ "Imagery days, newest first · arrows move, S or A and B pin": "Дни со снимками, сначала новые · стрелки — перемещение, S или A и B — закрепить", "On the map": "На карте", "Mode": "Режим", - "Imagery on Esri · Google 3D returns when cleared": "Снимки на Esri · сброс вернёт Google 3D", + "Imagery on Esri · Google 3D returns when cleared": "Подложка: Esri · сброс вернёт Google 3D", "Imagery opacity": "Непрозрачность снимков", "SWAP": "ПОМЕНЯТЬ", - "VS BASEMAP": "VS ПОДЛОЖКА", + "VS BASEMAP": "С ПОДЛОЖКОЙ", "Trade the two sides of the divider": "Поменять местами стороны разделителя", "A / B": "A / B", "Newest low-cloud day for this box · scene cloud, not box cloud": "Самый свежий малооблачный день для этой области · облачность всей сцены, а не области", @@ -1573,7 +1573,7 @@ "Searching": "Поиск", "Every day is empty here": "Здесь все дни пустые", "Sources off": "Источники выключены", - "VS": "VS", + "VS": "СРАВН.", "Not set": "Не задано", "Basemap": "Подложка", "Source off": "Источник выключен", @@ -1617,8 +1617,8 @@ "Zoom in or draw a smaller box": "Приблизьте карту или выделите область поменьше", "Select a box or use the view": "Выделите область или используйте текущий вид", "Searching the last 30 days": "Поиск за последние 30 дней", - "No days to show for this box": "Для этой области нет дней для показа", - "Drag the divider · SWAP trades sides": "Перетащите разделитель · «ПОМЕНЯТЬ» меняет стороны местами", + "No days to show for this box": "Для этой области нечего показать", + "Drag the divider · SWAP trades sides": "Тяните разделитель · «ПОМЕНЯТЬ» — смена сторон", "Export failed · {error}": "Ошибка экспорта · {error}", "← → preview · A or B pins the focused day": "← → просмотр · A или B закрепляет выбранный день", "← → preview the other side · A or B pins it": "← → просмотр другой стороны · A или B закрепляет её", @@ -1673,8 +1673,8 @@ "Sonar ring count": "Число колец сонара", "Sonar ring range": "Дальность колец сонара", "Sonar intensity": "Интенсивность сонара", - "Cyber contact and label opacity": "Непрозрачность контактов и меток в режиме «Кибер»", - "Minimum opacity for map dots, icons, models, brackets, and labels between sonar passes": "Минимальная непрозрачность точек, значков, моделей, рамок и меток на карте между проходами сонара", + "Cyber contact and label opacity": "Непрозрачность контактов и подписей в режиме «Кибер»", + "Minimum opacity for map dots, icons, models, brackets, and labels between sonar passes": "Минимальная непрозрачность точек, значков, моделей, рамок и подписей на карте между проходами сонара", "Sonar sector width": "Ширина сектора сонара", "Rings": "Кольца", "Range": "Дальность", @@ -1756,7 +1756,7 @@ "{first} and {second}": "{first} и {second}", "Camera range {range}": "Дальность камеры: {range}", "Within {radius} of the view": "В радиусе {radius} от вида", - "Labels on": "Метки включены", + "Labels on": "Подписи включены", "Framing uses the nearest 80 loaded contacts": "В кадр берутся 80 ближайших загруженных контактов", "{distance} from {location}": "{location}: {distance}", "Altitude {altitude}": "Высота {altitude}", @@ -1815,7 +1815,7 @@ "Recent Imagery": "Свежие снимки", "Fire Perimeters": "Контуры пожаров", "Cyclone advisories": "Сводки о циклонах", - "EDIT DETAILS": "ИЗМЕНИТЬ ДАННЫЕ", + "EDIT DETAILS": "ИЗМЕНИТЬ ПАРАМЕТРЫ", "SHARE SCENE": "ПОДЕЛИТЬСЯ СЦЕНОЙ", "ON/OFF": "ВКЛ/ВЫКЛ", "Voice assistant": "Голосовой ассистент", @@ -1876,13 +1876,13 @@ "Make a U-turn": "Развернитесь", "Make a U-turn onto {road}": "Развернитесь на {road}", "Continue straight": "Продолжайте прямо", - "Continue straight onto {road}": "Продолжайте прямо на {road}", + "Continue straight onto {road}": "Продолжайте движение прямо по {road}", "Turn {direction}": "Поверните {direction}", "Turn {direction} onto {road}": "Поверните {direction} на {road}", "maneuver|Turn": "Поверните", "Turn onto {road}": "Поверните на {road}", "Continue {direction}": "Продолжайте движение {direction}", - "Continue {direction} onto {road}": "Продолжайте движение {direction} на {road}", + "Continue {direction} onto {road}": "Продолжайте движение {direction} по {road}", "maneuver|Continue": "Продолжайте движение", "Continue onto {road}": "Продолжайте движение по {road}", "At the end of the road, turn {direction}": "В конце дороги поверните {direction}", @@ -1912,7 +1912,7 @@ "At the roundabout, turn {direction}": "На кольце поверните {direction}", "At the roundabout, turn {direction} onto {road}": "На кольце поверните {direction} на {road}", "At the roundabout, continue": "На кольце продолжайте движение", - "At the roundabout, continue onto {road}": "На кольце продолжайте движение на {road}", + "At the roundabout, continue onto {road}": "На кольце продолжайте движение по {road}", "Exit the roundabout": "Съезжайте с кольца", "Exit the roundabout onto {road}": "Съезжайте с кольца на {road}", "Use the {direction} lane": "Займите {direction} полосу", @@ -1997,14 +1997,14 @@ "Rasuwagadhi witness video": "Видео очевидца в Rasuwagadhi", "Bhote Koshi valley footage": "Съёмка долины Bhote Koshi", "Flood footage": "Съёмка паводка", - "Compilation and casualty claims": "Подборка и заявления о жертвах", - "SOURCE GEOCONFIRMED · WORLD-PINNED": "ИСТОЧНИК ГЕОПОДТВЕРЖДЁН · ОТМЕЧЕН НА КАРТЕ", + "Compilation and casualty claims": "Подборка видео и сообщения о жертвах", + "SOURCE GEOCONFIRMED · WORLD-PINNED": "МЕСТО СЪЁМКИ ПОДТВЕРЖДЕНО · ОТМЕЧЕНО НА КАРТЕ", "SOURCE CREDITED · LOCATION UNRESOLVED": "АВТОР УКАЗАН · МЕСТО НЕ УСТАНОВЛЕНО", "REFERENCE ONLY · CLAIMS UNVERIFIED": "ТОЛЬКО ДЛЯ СПРАВКИ · ЗАЯВЛЕНИЯ НЕ ПРОВЕРЕНЫ", - "SOURCE GEOCONFIRMED": "ИСТОЧНИК ГЕОПОДТВЕРЖДЁН", - "2021 historical reference versus 2026 post-event imagery; changes are cumulative and include the July 2025 Rasuwagadhi flood. Schematic downstream progression, not modeled arrival time; not an official hazard model.": "Исторический снимок 2021 года в сравнении со снимками 2026 года после события; изменения накоплены за весь период и включают паводок в Rasuwagadhi в июле 2025 года. Схематическое продвижение вниз по течению, а не смоделированное время добегания паводка; не официальная модель опасности.", + "SOURCE GEOCONFIRMED": "МЕСТО СЪЁМКИ ПОДТВЕРЖДЕНО", + "2021 historical reference versus 2026 post-event imagery; changes are cumulative and include the July 2025 Rasuwagadhi flood. Schematic downstream progression, not modeled arrival time; not an official hazard model.": "Исторический снимок 2021 года в сравнении со снимками 2026 года после события; изменения показаны суммарно за весь период и включают паводок в Rasuwagadhi в июле 2025 года. Схематическое продвижение вниз по течению, а не смоделированное время добегания паводка; не официальная модель опасности.", "Immediate collapse viewpoint": "Точка обзора у места обрушения", - "Collapse viewpoint": "Обзор обрушения", + "Collapse viewpoint": "Вид на обрушение", "Debris-dammed lake": "Озеро, запруженное обломками", "Dammed lake": "Запрудное озеро", "Second landslide": "Второй оползень", @@ -2030,14 +2030,14 @@ "Debris-Dammed Lake": "Озеро, запруженное обломками", "Nepal-China Border Gate": "Погранпереход Непал — Китай", "Timure Evidence Cluster": "Группа свидетельств у Timure", - "Syabru Besi Passage": "Прохождение Syabru Besi", + "Syabru Besi Passage": "Прохождение через Syabru Besi", "Bidur / Trishuli Consequence": "Последствия в Bidur / Trishuli", "Final view": "Финальный вид", "{year} HISTORICAL REFERENCE": "{year} ИСТОРИЧЕСКИЙ СНИМОК", "{year} POST-EVENT": "{year} ПОСЛЕ СОБЫТИЯ", "Esri map unavailable · no swipe": "Карта Esri недоступна · без сравнения", "Swipe needs a globe map": "Для сравнения нужна карта-глобус", - "Imagery stays on Esri · CLEAR to use Google 3D": "Снимки остаются на Esri · СБРОСИТЬ, чтобы вернуть Google 3D", + "Imagery stays on Esri · CLEAR to use Google 3D": "Снимки остаются на Esri · «СБРОСИТЬ», чтобы вернуть Google 3D", "Box and images cleared": "Область и снимки сброшены", "Box is {size} wide · limit {limit}": "Область шириной {size} · предел {limit}", "View is {size} wide · limit {limit}": "Вид шириной {size} · предел {limit}", @@ -2060,11 +2060,11 @@ "subject": "объект", "{value}m": "{value} м", "Google 3D tiles unavailable — check the key's API restrictions, quota, or network": "Тайлы Google 3D недоступны — проверьте ограничения API ключа, квоту или сеть", - "Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Нужен {envVars} — добавьте его в «Настройках провайдеров» — или токен Cesium ion для маршрута через ion", + "Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Нужен {envVars} — добавьте его в «Настройках провайдеров» — или токен Cesium ion для варианта через ion", "Comparison in use by another scene": "Сравнение занято другой сценой", "A location reading needs a loaded forecast and the Earth at the center of the view.": "Чтобы получить данные в точке, загрузите прогноз и поместите Землю в центр экрана.", "{mode} {band} {locality} | {region} | ALT {alt} | WINDOW {window} | SUN {sun}° | ONA {ona}° | {zone}": "{mode} · {band} · {locality} | {region} | ALT {alt} | ОКНО {window} | SUN {sun}° | ONA {ona}° | {zone}", - "light {source} to use your {name} key": "включите {source}, чтобы использовать ваш ключ {name}", + "light {source} to use your {name} key": "включите {source}, чтобы использовать свой ключ {name}", "light {source}": "включите {source}", ", or ": ", или ", "{requirement}, or {offer}": "{requirement}, или {offer}", @@ -2110,7 +2110,7 @@ "{heading}DEG": "{heading}°", "At least {count}": "Не менее {count}", "Named areas ({n})": "Именованные зоны ({n})", - "{distance} FLIGHT / VESSEL WINDOW": "ОКНО РЕЙСОВ / СУДОВ {distance}", + "{distance} FLIGHT / VESSEL WINDOW": "РАДИУС РЕЙСОВ / СУДОВ {distance}", "mapped matches within {km} km of the subject": "совпадения на карте в радиусе {km} км от объекта", "none mapped within {km} km; not a complete 250 km survey": "на карте нет в радиусе {km} км; это не полный обзор 250 км", "mapped matches from the loaded viewport": "совпадения на карте в загруженной области", @@ -2132,5 +2132,18 @@ "{value}°N": "{value}° с. ш.", "{value}°S": "{value}° ю. ш.", "{value}°E": "{value}° в. д.", - "{value}°W": "{value}° з. д." + "{value}°W": "{value}° з. д.", + "installation class|AIRFIELD": "АЭРОДРОМ", + "installation class|NAVAL BASE": "ВОЕННО-МОРСКАЯ БАЗА", + "installation class|RANGE": "ПОЛИГОН", + "installation class|MILITARY LAND": "ВОЕННАЯ ТЕРРИТОРИЯ", + "installation class|BASE": "БАЗА", + "installation class|PLACES CANDIDATE": "ИЗ GOOGLE PLACES", + "installation class|INSTALLATION": "ОБЪЕКТ", + "fire confidence|high": "высокая", + "fire confidence|nominal": "средняя", + "fire confidence|low": "низкая", + "age|<1h": "<1 ч", + "age|{count}h": "{count} ч", + "age|{count}d": "{count} д" } diff --git a/src/i18n/same-as-english.json b/src/i18n/same-as-english.json index 2fc7b1311..db42f9146 100644 --- a/src/i18n/same-as-english.json +++ b/src/i18n/same-as-english.json @@ -61,7 +61,6 @@ "Noir", "Normal", "POS: {time}Z", - "PRECIP", "Plan", "Pop", "Punk", @@ -78,10 +77,10 @@ "SIG", "SRC {value}", "SSE", + "STD", "Sector", "Sonar", "Soul", - "TEMP", "Techno", "Trance", "VISUAL", @@ -90,8 +89,10 @@ "WX", "audio {state}", "dB", + "fire confidence|nominal", "ft", "hPa", + "installation class|BASE", "km", "km/h", "min", diff --git a/src/layers/firms/model.js b/src/layers/firms/model.js index 40ce61c5a..0e721f198 100644 --- a/src/layers/firms/model.js +++ b/src/layers/firms/model.js @@ -332,11 +332,11 @@ export function createModel({ function buildSelectedFireCard(fire, nowMs) { const meta = [ t('{value} conf', { - value: confidenceBucket(fire.confidence), + value: confidenceLabel(confidenceBucket(fire.confidence)), }), ]; if (fire.acqMs > 0) { - const age = formatAge(nowMs - fire.acqMs); + const age = formatAgeLabel(nowMs - fire.acqMs); if (age) meta.push(t('{value} ago', { value: age })); } const sat = satelliteShortName(fire.satellite); @@ -374,9 +374,9 @@ export function createModel({ function buildFireCard(candidate, nowMs) { const fire = candidate.fire; - const meta = [confidenceBucket(fire.confidence)]; + const meta = [confidenceLabel(confidenceBucket(fire.confidence))]; if (fire.acqMs > 0) { - const age = formatAge(nowMs - fire.acqMs); + const age = formatAgeLabel(nowMs - fire.acqMs); if (age) meta.push(age); } const sat = satelliteShortName(fire.satellite) || fire.sensor; @@ -409,7 +409,7 @@ export function createModel({ const noun = cell.count === 1 ? t('FIRE') : t('FIRES'); const parts = [t('max {value} MW', { value: formatFrp(cell.maxFrp) })]; if (cell.newestAcqMs > 0) { - const age = formatAge(nowMs - cell.newestAcqMs); + const age = formatAgeLabel(nowMs - cell.newestAcqMs); if (age) parts.push(t('new {value}', { value: age })); } return { @@ -503,6 +503,32 @@ export function createModel({ return t('{count}h ago', { count: Math.round(minutes / 60) }); } + /** A confidence bucket as a card shows it, in the UI language. */ + + function confidenceLabel(bucket) { + switch (bucket) { + case 'high': + return t('high', { context: 'fire confidence' }); + case 'nominal': + return t('nominal', { context: 'fire confidence' }); + case 'low': + return t('low', { context: 'fire confidence' }); + default: + return bucket; + } + } + + /** formatAge for a card: "<1h", "24h", "3d" in the UI language ("24 ч"). */ + + function formatAgeLabel(deltaMs) { + if (!Number.isFinite(deltaMs) || deltaMs < 0) return ''; + const hours = deltaMs / 3600000; + if (hours < 1) return t('<1h', { context: 'age' }); + if (hours < 48) + return t('{count}h', { context: 'age', count: Math.round(hours) }); + return t('{count}d', { context: 'age', count: Math.round(hours / 24) }); + } + /** Normalized 0..1 confidence → low/nominal/high display bucket. */ function confidenceBucket(confidence) { diff --git a/src/layers/installations/classLabel.js b/src/layers/installations/classLabel.js new file mode 100644 index 000000000..f3a14fe45 --- /dev/null +++ b/src/layers/installations/classLabel.js @@ -0,0 +1,29 @@ +import { t } from '../../i18n/index.js'; + +/** + * An installation class as a map card shows it: AIRFIELD, NAVAL BASE…, in + * the UI language. The class itself stays an English identifier in data. + * @param {string} klass + * @returns {string} + */ +export function installationClassLabel(klass) { + const key = String(klass || 'installation'); + switch (key) { + case 'airfield': + return t('AIRFIELD', { context: 'installation class' }); + case 'naval_base': + return t('NAVAL BASE', { context: 'installation class' }); + case 'range': + return t('RANGE', { context: 'installation class' }); + case 'military_land': + return t('MILITARY LAND', { context: 'installation class' }); + case 'base': + return t('BASE', { context: 'installation class' }); + case 'places_candidate': + return t('PLACES CANDIDATE', { context: 'installation class' }); + case 'installation': + return t('INSTALLATION', { context: 'installation class' }); + default: + return key.replaceAll('_', ' ').toUpperCase(); + } +} diff --git a/src/layers/installations/namedMarkers.js b/src/layers/installations/namedMarkers.js index e516fb627..d66fd9c3b 100644 --- a/src/layers/installations/namedMarkers.js +++ b/src/layers/installations/namedMarkers.js @@ -1,3 +1,4 @@ +import { installationClassLabel } from './classLabel.js'; import * as Cesium from 'cesium'; import { MILITARY_LABEL_CAP, @@ -177,9 +178,7 @@ export function createNamedMarkers({ state, services, parts, overlayHost }) { marker.entry.selected = record.id === state.selectedId; marker.entry.details = marker.entry.selected ? [ - String(record.class || 'installation') - .replaceAll('_', ' ') - .toUpperCase(), + installationClassLabel(record.class), ...(record.memberNames || []).slice(0, 3), ] : []; diff --git a/src/layers/installations/rendering.js b/src/layers/installations/rendering.js index 5969bb740..24801d82f 100644 --- a/src/layers/installations/rendering.js +++ b/src/layers/installations/rendering.js @@ -1,3 +1,4 @@ +import { installationClassLabel } from './classLabel.js'; import * as Cesium from 'cesium'; import { t } from '../../i18n/index.js'; import { LAYER_ID, MAX_RENDERED, COLOR_BY_CLASS } from './policy.js'; @@ -273,9 +274,7 @@ export function createRendering({ entity.gevLabelModel = { title: record.name || t('MAPPED INSTALLATION'), details: [ - String(record.class || 'installation') - .replaceAll('_', ' ') - .toUpperCase(), + installationClassLabel(record.class), ...(record.memberNames || []).slice(0, 3), ], accent: COLOR_BY_CLASS[record.class] || '#9ca6b0', diff --git a/src/layers/vessels/cards.js b/src/layers/vessels/cards.js index 7fce9d616..9d5b2af87 100644 --- a/src/layers/vessels/cards.js +++ b/src/layers/vessels/cards.js @@ -1,6 +1,6 @@ import { accentForVesselType, - normalizeVesselType, + displayVesselType, } from '../../data/vesselLabels.js'; import { formatNumber, t } from '../../i18n/index.js'; @@ -130,7 +130,7 @@ export function createCards({ /** Uppercased, card-width-bounded AIS type (empty string when unknown). */ function vesselTypeShort(record) { - return normalizeVesselType(record.type).toUpperCase().slice(0, 14); + return displayVesselType(record.type).toUpperCase().slice(0, 14); } /** diff --git a/src/layers/wind/presentation.js b/src/layers/wind/presentation.js index 941cff5e1..8475f8f4c 100644 --- a/src/layers/wind/presentation.js +++ b/src/layers/wind/presentation.js @@ -1,5 +1,5 @@ import { formatWindSpeed, WIND_UNITS } from './inspection.js'; -import { t } from '../../i18n/index.js'; +import { formatNumber, formatUtcDayMonth, t } from '../../i18n/index.js'; import { formatQuantity, unitLabel } from '../../ui/unitLabels.js'; /** Reformat a captured sample; changing units never samples the map again. */ @@ -62,12 +62,46 @@ function windText(reading) { : `${speed}${t(' from {direction}', { direction: t(reading.from) })}`; } +/** "29.93°N · 97.74°W" from the English reading, in the active language. */ +function displayCoordinates(coordinates) { + const match = /^(\d+(?:\.\d+)?)°([NS]) · (\d+(?:\.\d+)?)°([EW])$/.exec( + coordinates, + ); + if (!match) return coordinates.replace(' · ', ' '); + const part = (value, hemisphere) => { + const decimals = value.split('.')[1]?.length || 0; + const amount = formatNumber(Number(value), { + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + useGrouping: false, + }); + switch (hemisphere) { + case 'N': + return t('{value}°N', { value: amount }); + case 'S': + return t('{value}°S', { value: amount }); + case 'E': + return t('{value}°E', { value: amount }); + default: + return t('{value}°W', { value: amount }); + } + }; + return `${part(match[1], match[2])} ${part(match[3], match[4])}`; +} + +/** "2026-10-14 14:00 UTC" from the reading, as "10-14 14:00 UTC" / "14.10 …". */ +function displayValidTime(validTime) { + const match = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) UTC$/.exec(validTime || ''); + if (!match) return validTime?.replace(/^\d{4}-/, ''); + return `${formatUtcDayMonth(match[1])} ${match[2]} UTC`; +} + /** Portable result block consumed by the WEATHER card. */ export function windReadingResult(reading) { const coordinates = reading.coordinates === 'No surface reading' ? t('No surface reading') - : reading.coordinates.replace(' · ', ' '); + : displayCoordinates(reading.coordinates); return { id: 'reading', label: t('WIND AT {coordinates}', { coordinates }), @@ -80,7 +114,7 @@ export function windReadingResult(reading) { id: 'meta', text: t('{model} · valid {time}', { model: reading.model, - time: reading.validTime?.replace(/^\d{4}-/, ''), + time: displayValidTime(reading.validTime), }), }, ...(reading.scalarValue