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