mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
i18n: fix review findings and polish ru/es strings
Fixes the 51 P0–P2 findings from the full review of the i18n branch and the issues found while checking every new string on screen in ru and es. - Translate errors and statuses that still reached ru/es in English: feed and tile errors, provider statuses, the ALPR chip, the traffic mode chip, voice plan step labels, fire feed values, receiver bands, scene save reasons and the Cesium attribution link. - Show preset city names in the UI language (src/ui/placeNames.js); data and everything sent to the model stay English. - Share layer names between the layer panel, HUD and voice card (src/ui/layerNames.js). - Keep long ru/es strings whole: voice card error lines, analyst titles, the voice tier status, the Nepal evidence button, the ALPR retry chip. English layout is unchanged. - Ask before a language switch drops unsaved work. - Catalog fixes: glossary terms (пуск, пресет), gender agreement and consistent "peticiones" in es, fewer calques in both languages. - Fix English text that was itself wrong (e.g. "Exit cockpit to fly to a weather", raw tool ids in voice plan steps). Catalogs: es/ru 2535 messages each, complete. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8029747b7e
commit
00704ddc89
212 files changed
+9871
-909
No files matched your search
@@ -0,0 +1,62 @@
|
||||
// K-21: an empty or blank translation must not count as done.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
checkCatalog,
|
||||
extractMessages,
|
||||
readCatalog,
|
||||
} from '../../scripts/i18n.mjs';
|
||||
import { getCatalog, setLocale, t, tn, translateKnown } from './index.js';
|
||||
|
||||
const messages = extractMessages();
|
||||
const EMPTY = 'DISCONNECT';
|
||||
const BLANK = 'CONTACT LOST · LAST KNOWN READOUT · NOT AN ALL-CLEAR';
|
||||
|
||||
for (const locale of ['es', 'ru']) {
|
||||
test(`${locale}: checkCatalog rejects an empty or blank translation`, () => {
|
||||
const catalog = { ...readCatalog(locale), [EMPTY]: '', [BLANK]: ' ' };
|
||||
const { errors } = checkCatalog(locale, catalog, messages);
|
||||
assert.ok(
|
||||
errors.some((e) => e.includes(`"${EMPTY}"`)),
|
||||
'empty string passes the check',
|
||||
);
|
||||
assert.ok(
|
||||
errors.some((e) => e.includes(`"${BLANK}"`)),
|
||||
'blank string passes the check',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test('t() and translateKnown() fall back to English for an empty entry', () => {
|
||||
const es = getCatalog('es');
|
||||
const saved = es[EMPTY];
|
||||
es[EMPTY] = '';
|
||||
setLocale('es');
|
||||
try {
|
||||
assert.equal(t(EMPTY), EMPTY);
|
||||
assert.equal(translateKnown(EMPTY), EMPTY);
|
||||
} finally {
|
||||
es[EMPTY] = saved;
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
|
||||
test('tn() skips an empty plural form', () => {
|
||||
const es = getCatalog('es');
|
||||
const KEY = '{count} INPUTS UNKNOWN';
|
||||
const saved = es[KEY];
|
||||
setLocale('es');
|
||||
try {
|
||||
es[KEY] = { ...saved, one: ' ' };
|
||||
assert.equal(
|
||||
tn(1, '{count} INPUT UNKNOWN', KEY),
|
||||
saved.other.replace('{count}', '1'),
|
||||
);
|
||||
es[KEY] = { one: '', many: '', other: '' };
|
||||
assert.equal(tn(1, '{count} INPUT UNKNOWN', KEY), '1 INPUT UNKNOWN');
|
||||
assert.equal(tn(3, '{count} INPUT UNKNOWN', KEY), '3 INPUTS UNKNOWN');
|
||||
} finally {
|
||||
es[KEY] = saved;
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
+46
-8
@@ -9,7 +9,7 @@
|
||||
//
|
||||
// English needs no catalog. Every other locale is one flat JSON file,
|
||||
// locales/<code>.json, keyed by the English text (for tn, by the English
|
||||
// plural form). A missing entry falls back to English.
|
||||
// plural form). A missing or empty entry falls back to English.
|
||||
// `npm run i18n` keeps the catalogs in step with the code. Guide: docs/I18N.md.
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
@@ -66,10 +66,14 @@ function interpolate(pattern, values) {
|
||||
);
|
||||
}
|
||||
|
||||
/** A catalog string with something to show; an empty one counts as missing. */
|
||||
const filled = (value) => typeof value === 'string' && value.trim() !== '';
|
||||
|
||||
function lookup(text) {
|
||||
const catalog = CATALOGS[currentLocale];
|
||||
if (!catalog) return undefined;
|
||||
if (Object.hasOwn(catalog, text)) return catalog[text];
|
||||
const entry = Object.hasOwn(catalog, text) ? catalog[text] : undefined;
|
||||
if (filled(entry) || (entry && typeof entry === 'object')) return entry;
|
||||
warnMissing(text);
|
||||
return undefined;
|
||||
}
|
||||
@@ -101,8 +105,9 @@ export function tn(count, one, other, values) {
|
||||
const entry = lookup(other);
|
||||
const category = pluralRules(currentLocale).select(count);
|
||||
let pattern;
|
||||
if (entry && typeof entry === 'object') {
|
||||
pattern = entry[category] ?? entry.other ?? other;
|
||||
const forms = entry && typeof entry === 'object' ? entry : null;
|
||||
if (forms && (filled(forms[category]) || filled(forms.other))) {
|
||||
pattern = filled(forms[category]) ? forms[category] : forms.other;
|
||||
} else {
|
||||
pattern = pluralRules(DEFAULT_LOCALE).select(count) === 'one' ? one : other;
|
||||
}
|
||||
@@ -117,8 +122,8 @@ export function tn(count, one, other, values) {
|
||||
export function translateKnown(text) {
|
||||
const catalog = CATALOGS[currentLocale];
|
||||
if (!catalog || typeof text !== 'string') return text;
|
||||
const entry = catalog[text];
|
||||
return typeof entry === 'string' ? entry : text;
|
||||
const entry = Object.hasOwn(catalog, text) ? catalog[text] : undefined;
|
||||
return filled(entry) ? entry : text;
|
||||
}
|
||||
|
||||
/** Mark English text that is stored in data and translated later by t(). */
|
||||
@@ -247,10 +252,43 @@ export function applyDocumentTranslations(doc) {
|
||||
return applied;
|
||||
}
|
||||
|
||||
const unsavedWorkChecks = new Set();
|
||||
|
||||
/**
|
||||
* Name work that switching language would lose: the switch reloads the page,
|
||||
* and the language switch asks first while any check reports something.
|
||||
* @param {() => string|null|undefined} check Returns a msg() label (e.g.
|
||||
* msg('unsaved CCTV calibration')) while there is such work, else nothing.
|
||||
* @returns {() => void} Removes the check.
|
||||
*/
|
||||
export function registerUnsavedWork(check) {
|
||||
if (typeof check !== 'function') return () => {};
|
||||
unsavedWorkChecks.add(check);
|
||||
return () => unsavedWorkChecks.delete(check);
|
||||
}
|
||||
|
||||
/**
|
||||
* What a reload would lose right now, translated, without repeats.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function describeUnsavedWork() {
|
||||
const labels = new Set();
|
||||
for (const check of unsavedWorkChecks) {
|
||||
try {
|
||||
const label = check();
|
||||
if (typeof label === 'string' && label) labels.add(t(label));
|
||||
} catch {
|
||||
// A failing check must not block the switch.
|
||||
}
|
||||
}
|
||||
return [...labels];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a locale choice, then reload preserving window.location.hash exactly
|
||||
* and stripping ?lang from the search string, so the override is one-shot.
|
||||
* Where storage is blocked, ?lang carries the choice instead.
|
||||
* and stripping ?lang from the search string, so the stored choice applies.
|
||||
* Where storage is blocked, ?lang carries the choice instead (share links
|
||||
* drop it: ShareLinkManager.copyLink).
|
||||
* @returns {boolean} true when a reload was triggered.
|
||||
*/
|
||||
export function persistLocaleAndReload(
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// K-194: the KartaView provider's failure and state texts are English data
|
||||
// (msg() in kartaview/{policy,source,viewer,index}.js) that the Street Level
|
||||
// panel and the chip tooltip show through translateKnown, so every one needs
|
||||
// an entry in each language's catalog.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { setLocale, translateKnown } from './index.js';
|
||||
|
||||
const MESSAGES = [
|
||||
// kartaview/policy.js
|
||||
'KartaView is rate-limiting requests — they resume on their own',
|
||||
'KartaView’s photos here are archived and cannot be shown',
|
||||
'KartaView did not filter this area in time — move the map to ask again',
|
||||
'None of KartaView’s photos here pass the imagery filter',
|
||||
// kartaview/source.js
|
||||
'KartaView did not answer in time — try again',
|
||||
'KartaView could not be reached — check the network',
|
||||
'KartaView sent an answer that could not be read',
|
||||
'KartaView is rate-limiting requests',
|
||||
'KartaView is rate-limiting requests — try again in a moment',
|
||||
'KartaView sent too much data',
|
||||
'KartaView has no such photo',
|
||||
// kartaview/viewer.js, index.js
|
||||
'The KartaView photo could not be loaded',
|
||||
'KartaView has no position for this photo',
|
||||
];
|
||||
|
||||
for (const locale of ['es', 'ru']) {
|
||||
test(`KartaView provider messages are translated in ${locale}`, () => {
|
||||
setLocale(locale);
|
||||
try {
|
||||
const untranslated = MESSAGES.filter((m) => translateKnown(m) === m);
|
||||
assert.deepEqual(untranslated, []);
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// kartaview/source.js refusals with a number in them: English data, shown
|
||||
// through placeholder entries by the Street Level panel and the layer panel.
|
||||
test('KartaView HTTP and refusal codes keep their number in every language', async () => {
|
||||
const { translateStreetLevelError } =
|
||||
await import('../ui/streetLevelPresentation.js');
|
||||
const http = 'KartaView answered HTTP 503';
|
||||
const refused = 'KartaView refused the request (code 610)';
|
||||
assert.equal(translateStreetLevelError(http), http);
|
||||
assert.equal(translateStreetLevelError(refused), refused);
|
||||
for (const locale of ['es', 'ru']) {
|
||||
setLocale(locale);
|
||||
try {
|
||||
const shownHttp = translateStreetLevelError(http);
|
||||
const shownRefused = translateStreetLevelError(refused);
|
||||
assert.notEqual(shownHttp, http);
|
||||
assert.match(shownHttp, /503/);
|
||||
assert.notEqual(shownRefused, refused);
|
||||
assert.match(shownRefused, /610/);
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { setLocale, translateKnown } from './index.js';
|
||||
|
||||
// Status and error texts the layers write straight into their state
|
||||
// (loadingLabel / _error / street.error) as English data; the layer panel and
|
||||
// the Street Level panel show them through translateKnown, so each needs a
|
||||
// catalog entry in every language (K-36).
|
||||
const SITES = [
|
||||
['../layers/streetLevel/index.js', 'loading coverage...'],
|
||||
['../layers/firms/ingestion.js', 'live feed unavailable'],
|
||||
['../layers/bikeshare/viewport.js', 'GBFS fetch error'],
|
||||
['../layers/bikeshare/ingestion.js', 'GBFS status update failed'],
|
||||
[
|
||||
'../layers/streetLevel/viewerHost.js',
|
||||
'Open the Street Level panel to view imagery',
|
||||
],
|
||||
['../layers/streetLevel/viewerHost.js', 'Image could not be opened'],
|
||||
['../layers/satellites/ingestion.js', 'CelesTrak unreachable'],
|
||||
[
|
||||
'../layers/streetLevel/providers/mapillary/policy.js',
|
||||
'Coverage requests are being rate-limited — they resume on their own',
|
||||
],
|
||||
['../layers/alpr/index.js', 'Camera source temporarily unavailable'],
|
||||
['../layers/perimeters/index.js', 'Perimeter source unavailable'],
|
||||
['../layers/recentImagery/index.js', 'Imagery catalog unavailable'],
|
||||
['../layers/installations/ingestion.js', 'Installation context unavailable'],
|
||||
['../layers/earthquakes/index.js', 'Earthquake source unavailable'],
|
||||
['../layers/submarineCables/ingestion.js', 'TeleGeography load failed'],
|
||||
['../layers/vessels/ingestion.js', 'AIS live load failed'],
|
||||
['../layers/vessels/lifecycle.js', 'AIS live load failed'],
|
||||
['../layers/wind/index.js', 'Wind unavailable'],
|
||||
['../layers/wind/source.js', 'Wind request timed out'],
|
||||
['../layers/wind/source.js', 'Malformed wind manifest'],
|
||||
['../layers/wind/source.js', 'Malformed wind grid'],
|
||||
['../layers/wind/source.js', 'Temperature field unavailable'],
|
||||
['../layers/wind/source.js', 'Mean sea level pressure field unavailable'],
|
||||
];
|
||||
|
||||
// A fallback that reaches the panel must be marked, or `npm run i18n` cannot
|
||||
// see it and the catalog entry would be pruned as unused.
|
||||
for (const [file, text] of SITES) {
|
||||
test(`"${text}" in ${file} is marked`, () => {
|
||||
const source = readFileSync(new URL(file, import.meta.url), 'utf8');
|
||||
assert.ok(
|
||||
new RegExp(
|
||||
String.raw`\b(?:msg|t)\(\s*'${text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}'`,
|
||||
).test(source),
|
||||
`${text} is not marked in ${file}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const locale of ['es', 'ru']) {
|
||||
for (const [file, text] of SITES) {
|
||||
test(`${locale}: "${text}" from ${file} is translated`, () => {
|
||||
const source = readFileSync(new URL(file, import.meta.url), 'utf8');
|
||||
assert.ok(source.includes(`'${text}'`), `literal still in ${file}`);
|
||||
setLocale(locale);
|
||||
try {
|
||||
assert.notEqual(translateKnown(text), text);
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+558
-5
@@ -10,7 +10,7 @@
|
||||
"LOAD FAILED": "CARGA FALLIDA",
|
||||
"LOAD CANCELLED": "CARGA CANCELADA",
|
||||
"syncing road network": "sincronizando la red vial",
|
||||
"retrying in {seconds}s": "reintentando en {seconds} s",
|
||||
"retrying in {seconds}s": "reintento en {seconds} s",
|
||||
"retry pending": "reintento pendiente",
|
||||
"Clear selected data layers": "Limpiar las capas de datos seleccionadas",
|
||||
"Turn off all selected data layers": "Desactivar todas las capas de datos seleccionadas",
|
||||
@@ -812,7 +812,7 @@
|
||||
"Check microphone permission and network access, then try again.": "Revisa el permiso del micrófono y el acceso a la red, y vuelve a intentarlo.",
|
||||
"AI AGENT": "AGENTE DE IA",
|
||||
"VOICE CONTROL": "CONTROL DE VOZ",
|
||||
"{tier} applies next session": "{tier} se aplica en la próxima sesión",
|
||||
"{tier} applies next session": "{tier} desde la próxima sesión",
|
||||
"Voice model tier — applies next session": "Nivel del modelo de voz — se aplica en la próxima sesión",
|
||||
"Estimated session cost": "Costo estimado de la sesión",
|
||||
"Voice control — activate to toggle voice; hold Space to speak": "Control de voz — púlsalo para activar o desactivar la voz; mantén pulsado Space para hablar",
|
||||
@@ -989,7 +989,7 @@
|
||||
"Coverage colours": "Colores de cobertura",
|
||||
"That shape already has {count} points — finish it or press Backspace.": "Esa forma ya tiene {count} puntos — termínala o pulsa Backspace.",
|
||||
"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.",
|
||||
"{owner} is using the pointer — close it first.": "Puntero en uso: {owner}. Ciérralo primero.",
|
||||
"{label} · Detailed outline unavailable": "{label} · Contorno detallado no disponible",
|
||||
"Witness source": "Fuente de testigo",
|
||||
"{provider} · EMBEDDED SOURCE": "{provider} · FUENTE INSERTADA",
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"Mark {targets} +{more}": "Marcar {targets} +{more}",
|
||||
"Mark {targets}": "Marcar: {targets}",
|
||||
"Fly to {place}": "Volar a {place}",
|
||||
"Nearest aircraft to {place}": "Aeronaves más cercanas a {place}",
|
||||
"Nearest aircraft to {place}": "Aeronave más cercana a {place}",
|
||||
"Read the view": "Consultar la vista",
|
||||
"Check view state": "Comprobar el estado de la vista",
|
||||
"Frame {target}": "Encuadrar {target}",
|
||||
@@ -2208,5 +2208,558 @@
|
||||
"Show your own GeoJSON, KML, KMZ or GPX files on the globe (or drop them onto it). File bytes stay on this device; overlays can appear in map screenshots shared with voice.": "Muestra en el globo tus propios archivos GeoJSON, KML, KMZ o GPX (o suéltalos sobre él). Los archivos no salen de este dispositivo; las capas pueden aparecer en capturas del mapa compartidas con la voz.",
|
||||
"Import": "Importar",
|
||||
"Choose GeoJSON, KML, KMZ or GPX files": "Elige archivos GeoJSON, KML, KMZ o GPX",
|
||||
"Imported files": "Archivos importados"
|
||||
"Imported files": "Archivos importados",
|
||||
"DOCKED · {name}": "ACOPLE · {name}",
|
||||
"DOCKED · {name} · +{extra}": "ACOPLE · {name} · +{extra}",
|
||||
"loading coverage...": "cargando cobertura...",
|
||||
"live feed unavailable": "fuente en vivo no disponible",
|
||||
"GBFS fetch error": "error al obtener datos GBFS",
|
||||
"GBFS status update failed": "falló la actualización del estado GBFS",
|
||||
"Open the Street Level panel to view imagery": "Abre el panel \"A pie de calle\" para ver las imágenes",
|
||||
"Image could not be opened": "No se pudo abrir la imagen",
|
||||
"Mapillary rejected MAPILLARY_CLIENT_TOKEN — replace it in Provider Settings": "Mapillary rechazó MAPILLARY_CLIENT_TOKEN — sustitúyelo en Ajustes de proveedores",
|
||||
"Coverage requests are being rate-limited — they resume on their own": "Las peticiones de cobertura están limitadas por frecuencia — se reanudan solas",
|
||||
"No street-level imagery within {radius} m of that point": "No hay imágenes a pie de calle a menos de {radius} m de ese punto",
|
||||
"No street-level imagery within {radius} m of the view centre": "No hay imágenes a pie de calle a menos de {radius} m del centro de la vista",
|
||||
"Aim the middle of the view at a street to look for imagery there": "Apunta el centro de la vista a una calle para buscar imágenes allí",
|
||||
"Street View has no imagery for this place": "Street View no tiene imágenes de este lugar",
|
||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY no está configurada",
|
||||
"The Google Maps JavaScript API could not load": "No se pudo cargar la API de Maps JavaScript de Google",
|
||||
"Weather request timed out": "La petición del clima agotó el tiempo de espera",
|
||||
"Malformed weather manifest": "Manifiesto del clima mal formado",
|
||||
"Weather HTTP {status}": "Clima: HTTP {status}",
|
||||
"Weather image HTTP {status}": "Imagen del clima: HTTP {status}",
|
||||
"Weather prefetch unavailable": "Precarga del clima no disponible",
|
||||
"Infrared decode failed": "Falló la decodificación del infrarrojo",
|
||||
"rate limited": "límite de peticiones superado",
|
||||
"routing service is rate limited": "el servicio de rutas superó el límite de peticiones",
|
||||
"route leg too long": "tramo de la ruta demasiado largo",
|
||||
"route too long": "ruta demasiado larga",
|
||||
"Routing is rate limited — try again in a moment": "Se alcanzó el límite de peticiones de rutas — inténtalo de nuevo en un momento",
|
||||
"{error} — try again in a moment": "{error} — inténtalo de nuevo en un momento",
|
||||
"Routing unavailable (HTTP {status})": "Rutas no disponibles (HTTP {status})",
|
||||
"CelesTrak unreachable": "CelesTrak inaccesible",
|
||||
"KartaView is rate-limiting requests — they resume on their own": "KartaView está limitando las peticiones — se reanudan solas",
|
||||
"KartaView’s photos here are archived and cannot be shown": "Las fotos de KartaView de aquí están archivadas y no se pueden mostrar",
|
||||
"KartaView did not filter this area in time — move the map to ask again": "KartaView no filtró esta zona a tiempo — mueve el mapa para pedirlo de nuevo",
|
||||
"None of KartaView’s photos here pass the imagery filter": "Ninguna foto de KartaView de aquí pasa el filtro de imágenes",
|
||||
"KartaView did not answer in time — try again": "KartaView no respondió a tiempo — inténtalo de nuevo",
|
||||
"KartaView could not be reached — check the network": "No se pudo conectar con KartaView — comprueba la red",
|
||||
"KartaView sent an answer that could not be read": "KartaView envió una respuesta que no se pudo leer",
|
||||
"KartaView is rate-limiting requests": "KartaView está limitando las peticiones",
|
||||
"KartaView is rate-limiting requests — try again in a moment": "KartaView está limitando las peticiones — inténtalo de nuevo en un momento",
|
||||
"KartaView sent too much data": "KartaView envió demasiados datos",
|
||||
"KartaView has no such photo": "KartaView no tiene esa foto",
|
||||
"The KartaView photo could not be loaded": "No se pudo cargar la foto de KartaView",
|
||||
"KartaView has no position for this photo": "KartaView no tiene la posición de esta foto",
|
||||
"Map tiles temporarily unavailable": "Teselas del mapa temporalmente no disponibles",
|
||||
"Mapped names temporarily unavailable": "Nombres mapeados temporalmente no disponibles",
|
||||
"No mapped sites in view": "No hay sitios mapeados a la vista",
|
||||
"No mapped sites within {km} km of the contact": "No hay sitios mapeados a menos de {km} km del contacto",
|
||||
"{count} mapped sites in view": {
|
||||
"one": "{count} sitio mapeado a la vista",
|
||||
"many": "{count} de sitios mapeados a la vista",
|
||||
"other": "{count} sitios mapeados a la vista"
|
||||
},
|
||||
"{count} mapped sites within {km} km of the contact": {
|
||||
"one": "{count} sitio mapeado a menos de {km} km del contacto",
|
||||
"many": "{count} de sitios mapeados a menos de {km} km del contacto",
|
||||
"other": "{count} sitios mapeados a menos de {km} km del contacto"
|
||||
},
|
||||
"Too many mapped sites in view to list them all": "Hay demasiados sitios mapeados a la vista para listarlos todos",
|
||||
"Installation context unavailable": "Contexto de instalaciones no disponible",
|
||||
"Focus vessel {name}, MMSI {mmsi}": "Enfocar el buque {name}, MMSI {mmsi}",
|
||||
"Open the USGS initial report for the 26 August 2026 Nepal landslide": "Abrir el informe inicial del USGS sobre el deslizamiento del 26 de agosto de 2026 en Nepal",
|
||||
"YouTube source": "Fuente de YouTube",
|
||||
"Facebook source": "Fuente de Facebook",
|
||||
"Military airfield": "Aeródromo militar",
|
||||
"Naval base": "Base naval",
|
||||
"Firing range": "Campo de tiro",
|
||||
"Military land": "Terreno militar",
|
||||
"Mapped installation": "Instalación mapeada",
|
||||
"Military area": "Zona militar",
|
||||
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
||||
"TomTom key unavailable": "Clave de TomTom no disponible",
|
||||
"TomTom daily budget reached": "Se alcanzó el límite diario de TomTom",
|
||||
"TomTom upstream unreachable": "Servidor de TomTom inaccesible",
|
||||
"TomTom flow unavailable": "Flujo de TomTom no disponible",
|
||||
"API key rejected — check AISSTREAM_API_KEY": "Clave de API rechazada — revisa AISSTREAM_API_KEY",
|
||||
"feed silent {seconds}s — no AIS data": "fuente en silencio {seconds} s — sin datos AIS",
|
||||
"feed silent — no AIS data": "fuente en silencio — sin datos AIS",
|
||||
"feed down — retrying slowly (attempt {attempt})": "fuente caída — reintentando despacio (intento {attempt})",
|
||||
"feed down — retrying slowly": "fuente caída — reintentando despacio",
|
||||
"reconnecting to feed… (attempt {attempt})": "reconectando con la fuente… (intento {attempt})",
|
||||
"reconnecting to feed…": "reconectando con la fuente…",
|
||||
"AISSTREAM_API_KEY not set": "AISSTREAM_API_KEY no está configurada",
|
||||
"live feed unsupported": "fuente en vivo no compatible",
|
||||
"connecting to feed…": "conectando con la fuente…",
|
||||
"feed disconnected": "fuente desconectada",
|
||||
"feed down": "fuente caída",
|
||||
"feed idle": "fuente inactiva",
|
||||
"AIS live load failed": "Falló la carga de AIS en vivo",
|
||||
"age|{count}m": "{count} min",
|
||||
"{acres} ac": {
|
||||
"one": "{acres} acre",
|
||||
"many": "{acres} de acres",
|
||||
"other": "{acres} acres"
|
||||
},
|
||||
"{percent}% contained": "contenido al {percent}%",
|
||||
"containment unknown": "contención desconocida",
|
||||
"{cause} cause": "causa: {cause}",
|
||||
"{personnel} personnel": {
|
||||
"one": "{personnel} efectivo",
|
||||
"many": "{personnel} de efectivos",
|
||||
"other": "{personnel} efectivos"
|
||||
},
|
||||
"{county} County": "condado de {county}",
|
||||
"{cost} to date": "{cost} hasta la fecha",
|
||||
"discovered {age} ago": "detectado hace {age}",
|
||||
"updated {age} ago": "actualizado hace {age}",
|
||||
"part of {complex}": "parte de {complex}",
|
||||
"InciWeb ↗ · click card to open": "InciWeb ↗ · haz clic en la tarjeta para abrir",
|
||||
"FIRE · {name}": "INCENDIO · {name}",
|
||||
"Unnamed incident": "Sin nombre",
|
||||
"Open {title} on InciWeb": "Abrir {title} en InciWeb",
|
||||
"Vehicle {vehicle}": "Vehículo {vehicle}",
|
||||
"Bus": "Autobús",
|
||||
"Tram": "Tranvía",
|
||||
"Subway": "Metro",
|
||||
"Train": "Tren",
|
||||
"Ferry": "Ferri",
|
||||
"Transit vehicle": "Vehículo de transporte público",
|
||||
"No recent fix": "Sin posición reciente",
|
||||
"Waiting for update": "Esperando actualización",
|
||||
"Moving": "En movimiento",
|
||||
"reported {speed} km/h": "velocidad notificada: {speed} km/h",
|
||||
"reported hdg {heading}°": "rumbo notificado: {heading}°",
|
||||
"Last report: stopped at stop {stop}": "Último informe: detenido en la parada {stop}",
|
||||
"Last report: arriving at stop {stop}": "Último informe: llegando a la parada {stop}",
|
||||
"Last report: next stop {stop}": "Último informe: próxima parada {stop}",
|
||||
"occupancy|empty": "vacío",
|
||||
"occupancy|many seats available": "muchos asientos libres",
|
||||
"occupancy|few seats available": "pocos asientos libres",
|
||||
"occupancy|standing room only": "solo de pie",
|
||||
"occupancy|crushed standing room only": "abarrotado, solo de pie",
|
||||
"occupancy|full": "lleno",
|
||||
"occupancy|not accepting passengers": "no admite pasajeros",
|
||||
"occupancy|not boardable": "no se puede subir",
|
||||
"Shown at street level · depth not in the feed": "Se muestra a nivel de calle · la fuente no indica la profundidad",
|
||||
"Reported {seconds} s ago": "Notificado hace {seconds} s",
|
||||
"Reported {minutes} min ago": "Notificado hace {minutes} min",
|
||||
"{reported} (feed time)": "{reported} (hora de la fuente)",
|
||||
"shown {seconds} s behind": "se muestra con {seconds} s de retraso",
|
||||
"Available trail: {minutes} min": "Rastro disponible: {minutes} min",
|
||||
"Available trail: {minutes} min (limited)": "Rastro disponible: {minutes} min (limitado)",
|
||||
"UNAVAILABLE · {source} · Roads unavailable": "NO DISPONIBLE · {source} · Vías no disponibles",
|
||||
"Roads: {source}": "Vías: {source}",
|
||||
"TomTom roads unavailable while the traffic service is unreachable": "Vías de TomTom no disponibles mientras el servicio de tráfico esté inaccesible",
|
||||
"Hybrid unavailable while the traffic service is unreachable": "Modo híbrido no disponible mientras el servicio de tráfico esté inaccesible",
|
||||
"TomTom roads need a TomTom key": "Las vías de TomTom necesitan una clave de TomTom",
|
||||
"Hybrid needs a TomTom key": "El modo híbrido necesita una clave de TomTom",
|
||||
"Local surface still loading": "La superficie bajo las vías aún se está cargando",
|
||||
"Partial coverage": "Cobertura parcial",
|
||||
"Detailed roads unavailable": "Vías detalladas no disponibles",
|
||||
"Reduced detail coverage": "Cobertura con menos detalle",
|
||||
"OpenFreeMap tiles rate-limited": "Teselas de OpenFreeMap con límite de peticiones",
|
||||
"OpenFreeMap tiles timed out": "Las teselas de OpenFreeMap agotaron el tiempo de espera",
|
||||
"OpenFreeMap tiles unavailable": "Teselas de OpenFreeMap no disponibles",
|
||||
"Heard by your receiver": "Captado por tu receptor",
|
||||
"browser SDR": "SDR del navegador",
|
||||
"decoder feed": "fuente del decodificador",
|
||||
"Your RTL-SDR receiver and decoder feed": "Tu receptor RTL-SDR y la fuente del decodificador",
|
||||
"Your decoder feed": "Tu fuente del decodificador",
|
||||
"Your RTL-SDR receiver": "Tu receptor RTL-SDR",
|
||||
"NO CALLSIGN": "SIN INDICATIVO",
|
||||
"ALT {altitude} FT · GS {speed} KT · TRK {track}": "ALT {altitude} FT · VEL. SUELO {speed} KN · TRK {track}",
|
||||
"V/S {rate}": "VEL. VERT. {rate}",
|
||||
"V/S {rate} FPM": "VEL. VERT. {rate} FT/MIN",
|
||||
"POSITION {age}": "POSICIÓN {age}",
|
||||
"POSITION {seconds} S AGO": "POSICIÓN HACE {seconds} S",
|
||||
"{count} MSGS": {
|
||||
"one": "{count} MENSAJE",
|
||||
"many": "{count} DE MENSAJES",
|
||||
"other": "{count} MENSAJES"
|
||||
},
|
||||
"installation class|BARRACKS": "CUARTEL",
|
||||
"installation class|TRAINING AREA": "ZONA DE INSTRUCCIÓN",
|
||||
"Seeking up…": "Buscando hacia arriba…",
|
||||
"Seeking down…": "Buscando hacia abajo…",
|
||||
"Scanning {mhz} MHz…": "Escaneando {mhz} MHz…",
|
||||
"Found {mhz} MHz · {snr} dB": "Encontrado {mhz} MHz · {snr} dB",
|
||||
"Broadcaster stream is unavailable or blocked by the browser.": "El flujo de la emisora no está disponible o el navegador lo ha bloqueado.",
|
||||
"Playback requires a direct click or tap.": "La reproducción requiere un clic o un toque directo.",
|
||||
"Broadcaster stream could not be started.": "No se pudo iniciar el flujo de la emisora.",
|
||||
"Directory refresh degraded; showing the previous station catalog.": "La actualización del directorio fue parcial; se muestra el catálogo de emisoras anterior.",
|
||||
"Radio directory coverage is degraded.": "El directorio de radio funciona con interrupciones.",
|
||||
"Directory refresh failed; showing the previous station catalog.": "La actualización del directorio falló; se muestra el catálogo de emisoras anterior.",
|
||||
"Radio directory is temporarily unavailable.": "El directorio de radio no está disponible temporalmente.",
|
||||
"Decoder feeds: {feeds}": "Fuentes del decodificador: {feeds}",
|
||||
" · read while Local ADS-B is on": " · se leen mientras ADS-B local está activo",
|
||||
"idle": "en espera",
|
||||
"running": "activo",
|
||||
"suspended": "suspendido",
|
||||
"closed": "cerrado",
|
||||
"Local FM stopped: internet radio started": "FM local detenida: se inició la radio por Internet",
|
||||
"ALPR cameras": "cámaras ALPR",
|
||||
"OVERPASS RATE-LIMITED": "OVERPASS CON LÍMITE DE PETICIONES",
|
||||
"OVERPASS TIMED OUT": "OVERPASS AGOTÓ EL TIEMPO DE ESPERA",
|
||||
"OVERPASS COULD NOT COMPLETE THE QUERY": "OVERPASS NO PUDO COMPLETAR LA CONSULTA",
|
||||
"MAP TILES TEMPORARILY UNAVAILABLE": "TESELAS DEL MAPA TEMPORALMENTE NO DISPONIBLES",
|
||||
"MAPPED NAMES TEMPORARILY UNAVAILABLE": "NOMBRES MAPEADOS TEMPORALMENTE NO DISPONIBLES",
|
||||
"Shared flight follow expired": "El seguimiento del vuelo compartido ha caducado",
|
||||
"Shared military flight follow expired": "El seguimiento del vuelo militar compartido ha caducado",
|
||||
"Shared satellite follow expired": "El seguimiento del satélite compartido ha caducado",
|
||||
"Shared flight could not be restored — feed unavailable": "No se pudo restaurar el vuelo compartido — fuente no disponible",
|
||||
"Shared military flight could not be restored — feed unavailable": "No se pudo restaurar el vuelo militar compartido — fuente no disponible",
|
||||
"Shared satellite could not be restored — feed unavailable": "No se pudo restaurar el satélite compartido — fuente no disponible",
|
||||
"Shared flight is unavailable": "El vuelo compartido no está disponible",
|
||||
"Shared military flight is unavailable": "El vuelo militar compartido no está disponible",
|
||||
"Shared satellite is unavailable": "El satélite compartido no está disponible",
|
||||
"Scene not saved — browser storage unavailable": "Escena no guardada — almacenamiento del navegador no disponible",
|
||||
"Scene not saved — existing saved project could not be read. Export edits or import a valid file.": "Escena no guardada — no se pudo leer el proyecto guardado. Exporta los cambios o importa un archivo válido.",
|
||||
"Saved project could not be read; storage preserved. Import a valid file to resume saving.": "No se pudo leer el proyecto guardado; el almacenamiento se conserva. Importa un archivo válido para volver a guardar.",
|
||||
"Scene not saved — {reason}": "Escena no guardada — {reason}",
|
||||
"Stopped": "Detenido",
|
||||
"Stopped (Esc)": "Detenido (Esc)",
|
||||
"Camera ownership changed": "Cambió el control de la cámara",
|
||||
"Scene layer turned off": "Capa de la escena desactivada",
|
||||
"Seeking scene clock": "Desplazando el reloj de la escena",
|
||||
"Importing project": "Importando proyecto",
|
||||
"Camera move interrupted": "Movimiento de cámara interrumpido",
|
||||
"Scene media timed out": "El contenido multimedia de la escena agotó el tiempo de espera",
|
||||
"Space Missions isolates replay data. Exit the mode to enable {layer}.": "El modo Misiones espaciales aísla los datos de repetición. Sal del modo para activar {layer}.",
|
||||
"Tilted view": "Vista inclinada",
|
||||
"Straight-down view": "Vista cenital",
|
||||
"North up": "Norte arriba",
|
||||
"Exit cockpit to fly to a camera": "Sal de la cabina para volar a una cámara",
|
||||
"Exit cockpit to fly to a location": "Sal de la cabina para volar a una ubicación",
|
||||
"Exit cockpit to fly to a photo": "Sal de la cabina para volar a una foto",
|
||||
"Exit cockpit to fly to a shared view": "Sal de la cabina para volar a una vista compartida",
|
||||
"Exit cockpit to fly to a target": "Sal de la cabina para volar a un objetivo",
|
||||
"Exit cockpit to fly to a vessel": "Sal de la cabina para volar a un buque",
|
||||
"Exit cockpit to fly to a fire": "Sal de la cabina para volar a un incendio",
|
||||
"Exit cockpit to fly to a route": "Sal de la cabina para volar a una ruta",
|
||||
"Exit cockpit to fly to a file": "Sal de la cabina para volar a un archivo",
|
||||
"Exit cockpit to fly to a scene": "Sal de la cabina para volar a una escena",
|
||||
"Exit cockpit to fly to a {noun}": "Sal de la cabina para volar a {noun}",
|
||||
"Go to expanded Radio section": "Ir a la sección de Radio expandida",
|
||||
"Expand Radio section in Context": "Expandir la sección de Radio en Contexto",
|
||||
"Double-click the header to snap the panel back": "Haz doble clic en el encabezado para devolver el panel a su sitio",
|
||||
"New scene name": "Nombre de la nueva escena",
|
||||
"Delete scene \"{scene}\" and all shots?": "¿Eliminar la escena \"{scene}\" y todos sus planos?",
|
||||
"Detailed outline unavailable": "Contorno detallado no disponible",
|
||||
"Provider Settings does not answer proxied requests": "Ajustes de proveedores no responde a peticiones a través de un proxy",
|
||||
"Provider Settings is disabled while sharing is enabled": "El panel \"Ajustes de proveedores\" está desactivado mientras el uso compartido esté activado",
|
||||
"Provider Settings answers only the machine running the server": "Ajustes de proveedores solo responde al equipo que ejecuta el servidor",
|
||||
"Provider Settings answers only local hostnames": "Ajustes de proveedores solo responde a nombres de host locales",
|
||||
"Provider Settings requires an exact local Origin": "Ajustes de proveedores requiere un Origin local exacto",
|
||||
"Unrecognized Origin refused": "Origin no reconocido: petición rechazada",
|
||||
"Cross-origin requests are refused": "Se rechazan las peticiones de otro origen",
|
||||
"Content-Type must be application/json": "Content-Type debe ser application/json",
|
||||
"Method not allowed": "Método no permitido",
|
||||
"Request too large": "Petición demasiado grande",
|
||||
"Invalid JSON": "JSON no válido",
|
||||
"Body must be a JSON object of {ENV_VAR: value}": "El cuerpo debe ser un objeto JSON de {ENV_VAR: value}",
|
||||
"No keys provided": "No se envió ninguna clave",
|
||||
"At most {limit} keys per save": "Como máximo {limit} claves por guardado",
|
||||
"Unknown key: {name}": "Clave desconocida: {name}",
|
||||
"{name} must be a string": "{name}: el valor debe ser una cadena de texto",
|
||||
"{name} is empty": "{name}: el valor está vacío",
|
||||
"{name} is longer than any real key ({limit} max)": "{name}: el valor es más largo que cualquier clave real (máximo {limit})",
|
||||
"{name} may only contain printable characters with no spaces": "{name}: solo se admiten caracteres imprimibles, sin espacios",
|
||||
"{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)": "{name}: hay un carácter no válido en una clave (#, comillas, $, \\ o acento grave)",
|
||||
"{name} is configured outside Provider Settings and can only be changed where it was set": "{name} se configuró fuera de Ajustes de proveedores y solo se puede cambiar donde se definió",
|
||||
"The key was not saved: could not restrict the credential file to your account; nothing was saved": "La clave no se guardó: no se pudo restringir el archivo de credenciales a tu cuenta; no se guardó nada",
|
||||
"The key was not saved: the existing configuration could not be read, so nothing was changed": "La clave no se guardó: no se pudo leer la configuración existente, así que no se cambió nada",
|
||||
"Could not write the {store} store": "No se pudo escribir en el almacén {store}",
|
||||
"Could not check ChatGPT sign-in. Try again.": "No se pudo comprobar el inicio de sesión de ChatGPT. Inténtalo de nuevo.",
|
||||
"Cloud voice will use OPENAI_API_KEY on the next session.": "La voz en la nube usará OPENAI_API_KEY en la próxima sesión.",
|
||||
"Checking local ChatGPT OAuth sign-in…": "Comprobando el inicio de sesión OAuth local de ChatGPT…",
|
||||
"Could not check ChatGPT sign-in.": "No se pudo comprobar el inicio de sesión de ChatGPT.",
|
||||
"ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.": "OAuth de ChatGPT seleccionado para la voz en la nube. Tu clave de API sigue guardada y disponible.",
|
||||
"ChatGPT sign-in expired. Opening sign-in again…": "El inicio de sesión de ChatGPT caducó. Abriéndolo de nuevo…",
|
||||
"Opening ChatGPT sign-in in your browser…": "Abriendo el inicio de sesión de ChatGPT en tu navegador…",
|
||||
"Could not start ChatGPT sign-in on this machine.": "No se pudo abrir el inicio de sesión de ChatGPT en este equipo.",
|
||||
"Finish ChatGPT sign-in in the browser. Waiting for it to complete…": "Termina de iniciar sesión en ChatGPT en el navegador. Esperando a que se complete…",
|
||||
"ChatGPT sign-in timed out. Click {button} to try again.": "Se agotó el tiempo para iniciar sesión en ChatGPT. Haz clic en \"{button}\" para intentarlo de nuevo.",
|
||||
"ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.": "Sesión de ChatGPT iniciada. Se usará OAuth en la próxima sesión de voz en la nube.",
|
||||
"OAuth check failed: {detail}": "Error al comprobar OAuth: {detail}",
|
||||
"unsaved CCTV calibration": "la calibración de CCTV sin guardar",
|
||||
"the open cockpit": "la cabina abierta",
|
||||
"Switching language reloads the page. This will be lost: {items}. Switch anyway?": "Cambiar de idioma recarga la página. Se perderá lo siguiente: {items}. ¿Cambiar de todos modos?",
|
||||
"Wind upstream unavailable": "Origen de datos de viento no disponible",
|
||||
"WebUSB needs desktop Chrome or Edge": "WebUSB requiere Chrome o Edge de escritorio",
|
||||
"connect a receiver in Radio": "conecta un receptor en Radio",
|
||||
"receiver is in FM mode": "el receptor está en modo FM",
|
||||
"Cyclone advisories unavailable": "Avisos de ciclones no disponibles",
|
||||
"Cyclone data unavailable": "Datos de ciclones no disponibles",
|
||||
"Drag to size the box": "Arrastra para dar tamaño al recuadro",
|
||||
"Press on the ground, not the sky": "Pulsa sobre el suelo, no sobre el cielo",
|
||||
"Point the camera at the ground to use the view": "Apunta la cámara al suelo para usar la vista",
|
||||
"That point cannot anchor a box": "Ese punto no puede anclar un recuadro",
|
||||
"Box edges must be finite": "Coordenadas del recuadro no válidas",
|
||||
"Longitudes must be within ±180°": "Las longitudes deben estar dentro de ±180°",
|
||||
"Select one side of the dateline": "No cruces la línea de cambio de fecha",
|
||||
"Box must enclose an area": "El recuadro debe abarcar un área",
|
||||
"Imagery stops at ±{limit}° latitude": "Las imágenes llegan hasta ±{limit}° de latitud",
|
||||
"{owner} is using the pointer — close it first": "Puntero en uso: {owner}. Ciérralo primero",
|
||||
"OpenSky OAuth client missing/invalid": "Cliente OAuth de OpenSky ausente o no válido",
|
||||
"OpenSky OAuth rejected credentials": "OpenSky OAuth rechazó las credenciales",
|
||||
"OpenSky username/password rejected": "OpenSky rechazó el usuario o la contraseña",
|
||||
"OpenSky auth missing": "Falta la autenticación de OpenSky",
|
||||
"OpenSky auth required": "OpenSky requiere autenticación",
|
||||
"OpenSky auth invalid": "Autenticación de OpenSky no válida",
|
||||
"OpenSky auth failed": "Falló la autenticación de OpenSky",
|
||||
"Source snapshot {minutes} min old": "La instantánea de la fuente tiene {minutes} min",
|
||||
"{source} rate limited (anonymous)": "{source}: límite de peticiones (anónimo)",
|
||||
"{source} rate limited": "{source}: límite de peticiones",
|
||||
"{source} HTTP {status}": "{source}: HTTP {status}",
|
||||
"{source} network error": "{source}: error de red",
|
||||
"Malformed {source} aircraft rows": "{source}: filas de aeronaves mal formadas",
|
||||
"Malformed {source} response": "{source}: respuesta mal formada",
|
||||
"Source snapshot time unavailable": "Hora de la instantánea de la fuente no disponible",
|
||||
"Live data unavailable": "Datos en vivo no disponibles",
|
||||
"{n} aircraft": {
|
||||
"one": "{n} aeronave",
|
||||
"many": "{n} de aeronaves",
|
||||
"other": "{n} aeronaves"
|
||||
},
|
||||
"{n} military aircraft": {
|
||||
"one": "{n} aeronave militar",
|
||||
"many": "{n} de aeronaves militares",
|
||||
"other": "{n} aeronaves militares"
|
||||
},
|
||||
"{n} ships": {
|
||||
"one": "{n} buque",
|
||||
"many": "{n} de buques",
|
||||
"other": "{n} buques"
|
||||
},
|
||||
"{n} satellites": {
|
||||
"one": "{n} satélite",
|
||||
"many": "{n} de satélites",
|
||||
"other": "{n} satélites"
|
||||
},
|
||||
"{n} launches": {
|
||||
"one": "{n} lanzamiento",
|
||||
"many": "{n} de lanzamientos",
|
||||
"other": "{n} lanzamientos"
|
||||
},
|
||||
"{n} earthquakes": {
|
||||
"one": "{n} terremoto",
|
||||
"many": "{n} de terremotos",
|
||||
"other": "{n} terremotos"
|
||||
},
|
||||
"{n} fires": {
|
||||
"one": "{n} incendio",
|
||||
"many": "{n} de incendios",
|
||||
"other": "{n} incendios"
|
||||
},
|
||||
"{n} fire perimeters": {
|
||||
"one": "{n} perímetro de incendio",
|
||||
"many": "{n} de perímetros de incendio",
|
||||
"other": "{n} perímetros de incendio"
|
||||
},
|
||||
"{n} cyclones": {
|
||||
"one": "{n} ciclón",
|
||||
"many": "{n} de ciclones",
|
||||
"other": "{n} ciclones"
|
||||
},
|
||||
"{n} buses": {
|
||||
"one": "{n} autobús",
|
||||
"many": "{n} de autobuses",
|
||||
"other": "{n} autobuses"
|
||||
},
|
||||
"{n} bike share": {
|
||||
"one": "{n} estación de bicicletas compartidas",
|
||||
"many": "{n} de estaciones de bicicletas compartidas",
|
||||
"other": "{n} estaciones de bicicletas compartidas"
|
||||
},
|
||||
"{n} flock cameras": {
|
||||
"one": "{n} cámara ALPR",
|
||||
"many": "{n} de cámaras ALPR",
|
||||
"other": "{n} cámaras ALPR"
|
||||
},
|
||||
"{n} military bases": {
|
||||
"one": "{n} base militar",
|
||||
"many": "{n} de bases militares",
|
||||
"other": "{n} bases militares"
|
||||
},
|
||||
"{n} datacenters": {
|
||||
"one": "{n} centro de datos",
|
||||
"many": "{n} de centros de datos",
|
||||
"other": "{n} centros de datos"
|
||||
},
|
||||
"{n} dams": {
|
||||
"one": "{n} presa",
|
||||
"many": "{n} de presas",
|
||||
"other": "{n} presas"
|
||||
},
|
||||
"{n} results": {
|
||||
"one": "{n} resultado",
|
||||
"many": "{n} de resultados",
|
||||
"other": "{n} resultados"
|
||||
},
|
||||
"anywhere in the loaded data": "en todos los datos cargados",
|
||||
"in view": "a la vista",
|
||||
"within {radius} of the view centre": "en un radio de {radius} del centro de la vista",
|
||||
"within {radius} of {place} (Contacts window)": "en un radio de {radius} de {place} (ventana de Contactos)",
|
||||
"within {radius} of {place}": "en un radio de {radius} de {place}",
|
||||
"within {radius}": "en un radio de {radius}",
|
||||
"over {region}": "en {region}",
|
||||
"At least {items}": "Al menos {items}",
|
||||
"{items} {scope}": "{items} {scope}",
|
||||
"last 24 h": "últimas 24 h",
|
||||
"past 30 d and upcoming": "últimos 30 d y próximos",
|
||||
"the flights layer loads by viewport": "la capa de vuelos se carga según la vista",
|
||||
"distance is ground distance, not overhead": "la distancia es sobre el terreno hasta el punto subsatélite, no hasta el satélite",
|
||||
"the count is a lower bound because at least one layer reached its retained limit.": "el recuento es un mínimo porque al menos una capa alcanzó su límite de retención.",
|
||||
"{label} loading": "{label}: cargando",
|
||||
"{label} error": "{label}: error",
|
||||
"counted the first {returned} loaded records": "se contaron los primeros {returned} registros cargados",
|
||||
"counted {returned} of {total} loaded records": "se contaron {returned} de {total} registros cargados",
|
||||
"{layers} just turned on — still loading, counts will rise": "{layers}: recién activado — aún se está cargando, los recuentos subirán",
|
||||
"Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "Instantánea del panel Contactos ({time}) — el mismo conjunto retenido alimenta la tarjeta, la voz y la memoria de seguimiento",
|
||||
"Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "Instantánea del panel Contactos — el mismo conjunto retenido alimenta la tarjeta, la voz y la memoria de seguimiento",
|
||||
"Not answered: {layers}": "Sin respuesta: {layers}",
|
||||
"The flights layer is not enabled": "La capa de vuelos no está activada",
|
||||
"The military layer is not enabled": "La capa de vuelos militares no está activada",
|
||||
"The satellites layer is not enabled": "La capa de satélites no está activada",
|
||||
"The ais-live-vessels layer is not enabled": "La capa de buques AIS no está activada",
|
||||
"Could not enable the requested layer": "No se pudo activar la capa solicitada",
|
||||
"Could not disable the requested layer": "No se pudo desactivar la capa solicitada",
|
||||
"Radio layer unavailable": "La capa de radio no está disponible",
|
||||
"Camera navigation is unavailable in the current view": "La navegación de la cámara no está disponible en la vista actual",
|
||||
"Camera navigation policy unavailable": "La política de navegación de la cámara no está disponible",
|
||||
"Nearest-aircraft selection supports Flights or Military Flights only": "La selección de la aeronave más cercana solo funciona con vuelos o vuelos militares",
|
||||
"Nearest-aircraft selection needs a preset, place name, or latitude and longitude": "Para seleccionar la aeronave más cercana se necesita un preajuste, un nombre de lugar o la latitud y la longitud",
|
||||
"Could not arrive at the requested place": "No se pudo llegar al lugar solicitado",
|
||||
"The nearest airborne aircraft could not be selected": "No se pudo seleccionar la aeronave en vuelo más cercana",
|
||||
"No flights within {radius} of the current view": "No hay vuelos en un radio de {radius} de la vista actual",
|
||||
"No military within {radius} of the current view": "No hay vuelos militares en un radio de {radius} de la vista actual",
|
||||
"No satellites within {radius} of the current view": "No hay satélites en un radio de {radius} de la vista actual",
|
||||
"No vessels within {radius} of the current view": "No hay buques en un radio de {radius} de la vista actual",
|
||||
"{layer} is enabled on the {source} feed, but no airborne aircraft is loaded in the {place} view yet": "La capa {layer} está activada con la fuente {source}, pero aún no hay aeronaves en vuelo cargadas en la vista de {place}",
|
||||
"{layer} is enabled, but no airborne aircraft is loaded in the {place} view yet": "La capa {layer} está activada, pero aún no hay aeronaves en vuelo cargadas en la vista de {place}",
|
||||
"{layer} is enabled, but its aircraft feed is unavailable": "La capa {layer} está activada, pero su fuente de aeronaves no está disponible",
|
||||
"{layer} is enabled, but its destination refresh did not complete": "La capa {layer} está activada, pero la actualización en el destino no terminó",
|
||||
"{layer} is enabled, but {source} is unavailable": "La capa {layer} está activada, pero {source} no está disponible",
|
||||
"Could not arrive at {place}": "No se pudo llegar a {place}",
|
||||
"{name} is no longer loaded": "{name} ya no está cargado",
|
||||
"Unknown target layer: {target}": "Capa de destino desconocida: {target}",
|
||||
"Layer": "Capa",
|
||||
"there": "allí",
|
||||
"Frame flights": "Encuadrar aeronaves",
|
||||
"Frame military": "Encuadrar aeronaves militares",
|
||||
"Frame vessels": "Encuadrar buques",
|
||||
"Fly to Location": "Volar al lugar",
|
||||
"Nearest aircraft to Here": "Aeronave más cercana aquí",
|
||||
"Estimate is incomplete — a response was still in flight when the session ended, so its usage was never reported.": "La estimación está incompleta: una respuesta seguía en curso cuando terminó la sesión, así que su uso nunca se informó.",
|
||||
"STANDARD": "ESTÁNDAR",
|
||||
"vehicle motion|Stopped": "Detenido",
|
||||
"ACQUIRING FRAME": "OBTENIENDO FOTOGRAMA",
|
||||
"Camera source temporarily unavailable": "Fuente de cámaras no disponible temporalmente",
|
||||
"Perimeter source unavailable": "Fuente de perímetros no disponible",
|
||||
"Imagery catalog unavailable": "Catálogo de imágenes no disponible",
|
||||
"Earthquake source unavailable": "Fuente de terremotos no disponible",
|
||||
"TeleGeography load failed": "Falló la carga de TeleGeography",
|
||||
"Wind request timed out": "La petición del viento agotó el tiempo de espera",
|
||||
"Malformed wind manifest": "Manifiesto del viento mal formado",
|
||||
"Malformed wind grid": "Malla del viento mal formada",
|
||||
"Unknown wind model": "Modelo de viento desconocido",
|
||||
"Unknown weather overlay": "Capa de clima desconocida",
|
||||
"Temperature field unavailable": "Campo de temperatura no disponible",
|
||||
"Mean sea level pressure field unavailable": "Campo de presión a nivel del mar no disponible",
|
||||
"KartaView answered HTTP {status}": "KartaView respondió HTTP {status}",
|
||||
"KartaView refused the request (code {code})": "KartaView rechazó la petición (código {code})",
|
||||
"Stopped by voice": "Detenido por voz",
|
||||
"drawings on the map": "los dibujos del mapa",
|
||||
"imported map files": "los archivos importados en el mapa",
|
||||
"the active voice session": "la sesión de voz activa",
|
||||
"Light aircraft": "Avioneta",
|
||||
"Glider": "Planeador",
|
||||
"Turboprop": "Turbohélice",
|
||||
"Airliner": "Avión comercial",
|
||||
"Widebody": "Avión de fuselaje ancho",
|
||||
"Four-engine jet": "Reactor cuatrimotor",
|
||||
"Helicopter": "Helicóptero",
|
||||
"Fast jet": "Avión de alto rendimiento",
|
||||
"Business jet": "Reactor ejecutivo",
|
||||
"Drone": "Dron",
|
||||
"fire cause|Human": "humana",
|
||||
"fire cause|Natural": "natural",
|
||||
"fire cause|Undetermined": "indeterminada",
|
||||
"fire cause|Unknown": "desconocida",
|
||||
"fire behavior|Minimal": "actividad mínima",
|
||||
"fire behavior|Moderate": "actividad moderada",
|
||||
"fire behavior|Active": "actividad alta",
|
||||
"fire behavior|Extreme": "actividad extrema",
|
||||
"Type {level} Incident": "incidente de tipo {level}",
|
||||
"${amount}K": "{amount} mil $",
|
||||
"${amount}M": "{amount} M$",
|
||||
"${amount}B": "{amount} mil M$",
|
||||
"{speed} kn": "{speed} kn",
|
||||
"1090 MHz": "1090 MHz",
|
||||
"978 MHz UAT": "978 MHz UAT",
|
||||
"feed status|live": "en vivo",
|
||||
"feed status|stale": "desactualizada",
|
||||
"feed status|unreachable": "inaccesible",
|
||||
"feed status|invalid": "no válida",
|
||||
"Vector tile metadata unavailable": "Metadatos de teselas vectoriales no disponibles",
|
||||
"Vector tiles unavailable": "Teselas vectoriales no disponibles",
|
||||
"Vector tiles unavailable (HTTP {status})": "Teselas vectoriales no disponibles (HTTP {status})",
|
||||
"Vector tile request timed out": "Las teselas vectoriales agotaron el tiempo de espera",
|
||||
"Zoom in for vector tile coverage": "Acércate para cargar las teselas vectoriales",
|
||||
"Camera coverage unavailable": "Cobertura de cámaras no disponible",
|
||||
"Hybrid": "Híbrido",
|
||||
"TomTom roads only, each with its live flow": "Solo vías de TomTom, cada una con su flujo en vivo",
|
||||
"TomTom roads only, each with its live flow (needs a TomTom key)": "Solo vías de TomTom, cada una con su flujo en vivo (necesita una clave de TomTom)",
|
||||
"OpenStreetMap roads, with TomTom flow matched when available": "Vías de OpenStreetMap, con el flujo de TomTom emparejado cuando está disponible",
|
||||
"TomTom roads first; OpenStreetMap fills the rest, simulated": "Primero las vías de TomTom; OpenStreetMap completa el resto con flujo simulado",
|
||||
"TomTom roads first; OpenStreetMap fills the rest, simulated (needs a TomTom key)": "Primero las vías de TomTom; OpenStreetMap completa el resto con flujo simulado (necesita una clave de TomTom)",
|
||||
"Austin": "Austin",
|
||||
"San Francisco": "San Francisco",
|
||||
"New York": "Nueva York",
|
||||
"Tokyo": "Tokio",
|
||||
"London": "Londres",
|
||||
"Paris": "París",
|
||||
"Dubai": "Dubái",
|
||||
"Washington DC": "Washington D. C.",
|
||||
"Tallinn": "Tallin",
|
||||
"Los Angeles": "Los Ángeles",
|
||||
"Data attribution": "Atribución de datos",
|
||||
"Data provided by:": "Datos proporcionados por:",
|
||||
"Exit cockpit to view weather coverage": "Sal de la cabina para ver la cobertura de la capa meteorológica",
|
||||
"Exit cockpit to fly to a cyclone": "Sal de la cabina para volar a un ciclón",
|
||||
"the title of scene {scene} is empty or longer than {max} characters": "el título de la escena {scene} está vacío o supera los {max} caracteres",
|
||||
"the title of shot {shot} in scene {scene} is empty or longer than {max} characters": "el título del plano {shot} de la escena {scene} está vacío o supera los {max} caracteres",
|
||||
"the project is larger than 5 MiB": "el proyecto pesa más de 5 MiB",
|
||||
"the project has too many shots": "el proyecto tiene demasiados planos",
|
||||
"the scene data failed validation": "los datos de la escena no superaron la validación",
|
||||
"Layer on": "Capa: activada",
|
||||
"Layer off": "Capa: desactivada",
|
||||
"Zoom in": "Acercar",
|
||||
"Zoom out": "Alejar",
|
||||
"Zoom out to globe": "Alejar hasta el globo",
|
||||
"Open data layers": "Abrir las capas de datos",
|
||||
"Open or close a panel": "Abrir o cerrar un panel",
|
||||
"Change context mode": "Cambiar el modo de contexto",
|
||||
"Control cockpit": "Controlar la cabina",
|
||||
"Change visual style": "Cambiar el estilo visual",
|
||||
"Adjust HUD": "Ajustar el HUD",
|
||||
"Adjust cyber sonar": "Ajustar el sonar Ciber",
|
||||
"Adjust detection overlay": "Ajustar la superposición de detección",
|
||||
"Change map stack": "Cambiar el conjunto de mapas",
|
||||
"Adjust post-processing": "Ajustar el posprocesado",
|
||||
"Control scene": "Controlar la escena",
|
||||
"Control CCTV": "Controlar las cámaras CCTV",
|
||||
"Control radio": "Controlar la radio",
|
||||
"Track contact": "Rastrear el contacto",
|
||||
"Stop tracking": "Detener el rastreo",
|
||||
"Clear marks": "Borrar las marcas",
|
||||
"Move camera": "Mover la cámara",
|
||||
"Fly route": "Recorrer la ruta",
|
||||
"Analyst query": "Consulta analítica",
|
||||
"Next ISS pass": "Próximo paso de la EEI",
|
||||
"Next satellite pass": "Próximo paso del satélite",
|
||||
"Action": "Acción"
|
||||
}
|
||||
+578
-4
@@ -857,7 +857,7 @@
|
||||
"Voice session could not be started.": "Не удалось запустить голосовую сессию.",
|
||||
"CONNECTING": "ПОДКЛЮЧЕНИЕ",
|
||||
"ERROR": "ОШИБКА",
|
||||
"{tier} applies next session": "{tier} вступит в силу со следующей сессии",
|
||||
"{tier} applies next session": "{tier} — со следующей сессии",
|
||||
"Session ended — cost cap {cost}": "Сессия завершена — лимит стоимости {cost}",
|
||||
"Next session: {pendingId} — this session stays on {modelId}": "Следующая сессия: {pendingId} — текущая остаётся на {modelId}",
|
||||
"Voice model: {pendingId} — click to switch to {target}; applies next session": "Голосовая модель: {pendingId} — нажмите, чтобы переключиться на {target}; вступит в силу со следующей сессии",
|
||||
@@ -1001,7 +1001,7 @@
|
||||
"Coverage colours": "Цвета покрытия",
|
||||
"That shape already has {count} points — finish it or press Backspace.": "В этой фигуре уже точек: {count} — завершите её или нажмите Backspace.",
|
||||
"Could not place the shape: {error}": "Не удалось разместить фигуру: {error}",
|
||||
"{owner} is using the pointer — close it first.": "Указатель занят инструментом {owner} — сначала закройте его.",
|
||||
"{owner} is using the pointer — close it first.": "Указатель занят: «{owner}». Закройте его.",
|
||||
"{label} · Detailed outline unavailable": "{label} · Подробный контур недоступен",
|
||||
"Witness source": "Свидетельство очевидца",
|
||||
"{provider} · EMBEDDED SOURCE": "{provider} · ВСТРОЕННЫЙ ИСТОЧНИК",
|
||||
@@ -1790,7 +1790,7 @@
|
||||
"Mark {targets} +{more}": "Отметить {targets} +{more}",
|
||||
"Mark {targets}": "Отметить {targets}",
|
||||
"Fly to {place}": "Перелёт: {place}",
|
||||
"Nearest aircraft to {place}": "Ближайшие самолёты: {place}",
|
||||
"Nearest aircraft to {place}": "Ближайший самолёт: {place}",
|
||||
"Read the view": "Осмотреть видимую область",
|
||||
"Check view state": "Проверить состояние вида",
|
||||
"Frame {target}": "Показать в кадре: {target}",
|
||||
@@ -2244,5 +2244,579 @@
|
||||
"Show your own GeoJSON, KML, KMZ or GPX files on the globe (or drop them onto it). File bytes stay on this device; overlays can appear in map screenshots shared with voice.": "Показать на глобусе свои файлы GeoJSON, KML, KMZ или GPX (или перетащите их на глобус). Содержимое файлов остаётся на этом устройстве; наложения могут попасть в снимки карты, которые передаются голосовому ассистенту.",
|
||||
"Import": "Импорт",
|
||||
"Choose GeoJSON, KML, KMZ or GPX files": "Выберите файлы GeoJSON, KML, KMZ или GPX",
|
||||
"Imported files": "Импортированные файлы"
|
||||
"Imported files": "Импортированные файлы",
|
||||
"DOCKED · {name}": "СТЫКОВКА · {name}",
|
||||
"DOCKED · {name} · +{extra}": "СТЫКОВКА · {name} · +{extra}",
|
||||
"loading coverage...": "загрузка покрытия...",
|
||||
"live feed unavailable": "поток в реальном времени недоступен",
|
||||
"GBFS fetch error": "ошибка загрузки GBFS",
|
||||
"GBFS status update failed": "не удалось обновить статус GBFS",
|
||||
"Open the Street Level panel to view imagery": "Откройте панель «Уличные снимки», чтобы их посмотреть",
|
||||
"Image could not be opened": "Не удалось открыть снимок",
|
||||
"Mapillary rejected MAPILLARY_CLIENT_TOKEN — replace it in Provider Settings": "Mapillary отклонил MAPILLARY_CLIENT_TOKEN — замените его в «Настройках провайдеров»",
|
||||
"Coverage requests are being rate-limited — they resume on their own": "Запросы покрытия ограничены по частоте — они возобновятся сами",
|
||||
"No street-level imagery within {radius} m of that point": "В радиусе {radius} м от этой точки уличных снимков нет",
|
||||
"No street-level imagery within {radius} m of the view centre": "В радиусе {radius} м от центра вида уличных снимков нет",
|
||||
"Aim the middle of the view at a street to look for imagery there": "Наведите центр вида на улицу, чтобы поискать там снимки",
|
||||
"Street View has no imagery for this place": "В Просмотре улиц нет снимков этого места",
|
||||
"GOOGLE_MAPS_API_KEY is not set": "GOOGLE_MAPS_API_KEY не задан",
|
||||
"The Google Maps JavaScript API could not load": "Не удалось загрузить Google Maps JavaScript API",
|
||||
"Weather request timed out": "Истекло время ожидания запроса погоды",
|
||||
"Malformed weather manifest": "Некорректный манифест погоды",
|
||||
"Weather HTTP {status}": "Погода: HTTP {status}",
|
||||
"Weather image HTTP {status}": "Снимок погоды: HTTP {status}",
|
||||
"Weather prefetch unavailable": "Предзагрузка погоды недоступна",
|
||||
"Infrared decode failed": "Не удалось декодировать инфракрасный снимок",
|
||||
"rate limited": "превышен лимит запросов",
|
||||
"routing service is rate limited": "сервис маршрутизации превысил лимит запросов",
|
||||
"route leg too long": "участок маршрута слишком длинный",
|
||||
"route too long": "маршрут слишком длинный",
|
||||
"Routing is rate limited — try again in a moment": "Построение маршрута: превышен лимит запросов — попробуйте ещё раз чуть позже",
|
||||
"{error} — try again in a moment": "{error} — попробуйте ещё раз чуть позже",
|
||||
"Routing unavailable (HTTP {status})": "Построение маршрута недоступно (HTTP {status})",
|
||||
"CelesTrak unreachable": "CelesTrak недоступен",
|
||||
"KartaView is rate-limiting requests — they resume on their own": "KartaView ограничивает частоту запросов — они возобновятся сами",
|
||||
"KartaView’s photos here are archived and cannot be shown": "Фотографии KartaView здесь в архиве, показать их нельзя",
|
||||
"KartaView did not filter this area in time — move the map to ask again": "KartaView не успел отфильтровать эту область — сдвиньте карту, чтобы запросить снова",
|
||||
"None of KartaView’s photos here pass the imagery filter": "Ни одна фотография KartaView здесь не проходит фильтр снимков",
|
||||
"KartaView did not answer in time — try again": "KartaView не ответил вовремя — попробуйте снова",
|
||||
"KartaView could not be reached — check the network": "Не удалось связаться с KartaView — проверьте сеть",
|
||||
"KartaView sent an answer that could not be read": "KartaView прислал ответ, который не удалось прочитать",
|
||||
"KartaView is rate-limiting requests": "KartaView ограничивает частоту запросов",
|
||||
"KartaView is rate-limiting requests — try again in a moment": "KartaView ограничивает частоту запросов — попробуйте ещё раз чуть позже",
|
||||
"KartaView sent too much data": "KartaView прислал слишком много данных",
|
||||
"KartaView has no such photo": "У KartaView нет такой фотографии",
|
||||
"The KartaView photo could not be loaded": "Не удалось загрузить фотографию KartaView",
|
||||
"KartaView has no position for this photo": "У KartaView нет координат этой фотографии",
|
||||
"Map tiles temporarily unavailable": "Тайлы карты временно недоступны",
|
||||
"Mapped names temporarily unavailable": "Названия объектов временно недоступны",
|
||||
"No mapped sites in view": "В кадре нет объектов",
|
||||
"No mapped sites within {km} km of the contact": "Нет объектов в радиусе {km} км от контакта",
|
||||
"{count} mapped sites in view": {
|
||||
"one": "{count} объект в кадре",
|
||||
"few": "{count} объекта в кадре",
|
||||
"many": "{count} объектов в кадре",
|
||||
"other": "{count} объекта в кадре"
|
||||
},
|
||||
"{count} mapped sites within {km} km of the contact": {
|
||||
"one": "{count} объект в радиусе {km} км от контакта",
|
||||
"few": "{count} объекта в радиусе {km} км от контакта",
|
||||
"many": "{count} объектов в радиусе {km} км от контакта",
|
||||
"other": "{count} объекта в радиусе {km} км от контакта"
|
||||
},
|
||||
"Too many mapped sites in view to list them all": "В кадре слишком много объектов, чтобы перечислить все",
|
||||
"Installation context unavailable": "Контекст объектов недоступен",
|
||||
"Focus vessel {name}, MMSI {mmsi}": "Фокус на судне {name}, MMSI {mmsi}",
|
||||
"Open the USGS initial report for the 26 August 2026 Nepal landslide": "Открыть первичное сообщение USGS об оползне в Непале 26 августа 2026 года",
|
||||
"YouTube source": "Источник YouTube",
|
||||
"Facebook source": "Источник Facebook",
|
||||
"Military airfield": "Военный аэродром",
|
||||
"Naval base": "Военно-морская база",
|
||||
"Firing range": "Стрельбище",
|
||||
"Military land": "Военная территория",
|
||||
"Mapped installation": "Объект на карте",
|
||||
"Military area": "Военная зона",
|
||||
"SIMULATED — {reason}": "СИМУЛЯЦИЯ — {reason}",
|
||||
"TomTom key unavailable": "Ключ TomTom недоступен",
|
||||
"TomTom daily budget reached": "Исчерпан дневной лимит TomTom",
|
||||
"TomTom upstream unreachable": "Сервер TomTom не отвечает",
|
||||
"TomTom flow unavailable": "Поток TomTom недоступен",
|
||||
"API key rejected — check AISSTREAM_API_KEY": "Ключ API отклонён — проверьте AISSTREAM_API_KEY",
|
||||
"feed silent {seconds}s — no AIS data": "нет данных AIS от потока {seconds} с",
|
||||
"feed silent — no AIS data": "нет данных AIS от потока",
|
||||
"feed down — retrying slowly (attempt {attempt})": "поток недоступен — повторяем реже (попытка {attempt})",
|
||||
"feed down — retrying slowly": "поток недоступен — повторяем реже",
|
||||
"reconnecting to feed… (attempt {attempt})": "переподключение к потоку… (попытка {attempt})",
|
||||
"reconnecting to feed…": "переподключение к потоку…",
|
||||
"AISSTREAM_API_KEY not set": "AISSTREAM_API_KEY не задан",
|
||||
"live feed unsupported": "поток в реальном времени не поддерживается",
|
||||
"connecting to feed…": "подключение к потоку…",
|
||||
"feed disconnected": "поток отключён",
|
||||
"feed down": "поток недоступен",
|
||||
"feed idle": "поток простаивает",
|
||||
"AIS live load failed": "Не удалось загрузить AIS в реальном времени",
|
||||
"age|{count}m": "{count} мин",
|
||||
"{acres} ac": {
|
||||
"one": "{acres} акр",
|
||||
"few": "{acres} акра",
|
||||
"many": "{acres} акров",
|
||||
"other": "{acres} акра"
|
||||
},
|
||||
"{percent}% contained": "локализован на {percent}%",
|
||||
"containment unknown": "локализация неизвестна",
|
||||
"{cause} cause": "причина: {cause}",
|
||||
"{personnel} personnel": {
|
||||
"one": "{personnel} человек",
|
||||
"few": "{personnel} человека",
|
||||
"many": "{personnel} человек",
|
||||
"other": "{personnel} человека"
|
||||
},
|
||||
"{county} County": "округ {county}",
|
||||
"{cost} to date": "{cost} на сегодня",
|
||||
"discovered {age} ago": "обнаружен {age} назад",
|
||||
"updated {age} ago": "обновлён {age} назад",
|
||||
"part of {complex}": "входит в {complex}",
|
||||
"InciWeb ↗ · click card to open": "InciWeb ↗ · нажмите на карточку, чтобы открыть",
|
||||
"FIRE · {name}": "ПОЖАР · {name}",
|
||||
"Unnamed incident": "Без названия",
|
||||
"Open {title} on InciWeb": "Открыть {title} в InciWeb",
|
||||
"Vehicle {vehicle}": "Борт {vehicle}",
|
||||
"Bus": "Автобус",
|
||||
"Tram": "Трамвай",
|
||||
"Subway": "Метро",
|
||||
"Train": "Поезд",
|
||||
"Ferry": "Паром",
|
||||
"Transit vehicle": "Общественный транспорт",
|
||||
"No recent fix": "Нет свежих координат",
|
||||
"Waiting for update": "Ожидание обновления",
|
||||
"Moving": "В движении",
|
||||
"reported {speed} km/h": "по отчёту {speed} км/ч",
|
||||
"reported hdg {heading}°": "по отчёту курс {heading}°",
|
||||
"Last report: stopped at stop {stop}": "Последний отчёт: стоит на остановке {stop}",
|
||||
"Last report: arriving at stop {stop}": "Последний отчёт: прибывает на остановку {stop}",
|
||||
"Last report: next stop {stop}": "Последний отчёт: следующая остановка {stop}",
|
||||
"occupancy|empty": "пусто",
|
||||
"occupancy|many seats available": "много свободных мест",
|
||||
"occupancy|few seats available": "мало свободных мест",
|
||||
"occupancy|standing room only": "только стоячие места",
|
||||
"occupancy|crushed standing room only": "переполнен, только стоя",
|
||||
"occupancy|full": "мест нет",
|
||||
"occupancy|not accepting passengers": "посадки нет",
|
||||
"occupancy|not boardable": "посадка невозможна",
|
||||
"Shown at street level · depth not in the feed": "Показан на уровне улицы · глубины в потоке нет",
|
||||
"Reported {seconds} s ago": "Отчёт {seconds} с назад",
|
||||
"Reported {minutes} min ago": "Отчёт {minutes} мин назад",
|
||||
"{reported} (feed time)": "{reported} (время потока)",
|
||||
"shown {seconds} s behind": "показ отстаёт на {seconds} с",
|
||||
"Available trail: {minutes} min": "Доступный след: {minutes} мин",
|
||||
"Available trail: {minutes} min (limited)": "Доступный след: {minutes} мин (ограничен)",
|
||||
"UNAVAILABLE · {source} · Roads unavailable": "НЕДОСТУПНО · {source} · Дороги недоступны",
|
||||
"Roads: {source}": "Дороги: {source}",
|
||||
"TomTom roads unavailable while the traffic service is unreachable": "Дороги TomTom недоступны, пока сервис трафика не отвечает",
|
||||
"Hybrid unavailable while the traffic service is unreachable": "Гибридный режим недоступен, пока сервис трафика не отвечает",
|
||||
"TomTom roads need a TomTom key": "Для дорог TomTom нужен ключ TomTom",
|
||||
"Hybrid needs a TomTom key": "Для гибридного режима нужен ключ TomTom",
|
||||
"Local surface still loading": "Поверхность под дорогами ещё загружается",
|
||||
"Partial coverage": "Частичное покрытие",
|
||||
"Detailed roads unavailable": "Нет детальных данных о дорогах",
|
||||
"Reduced detail coverage": "Покрытие с меньшей детализацией",
|
||||
"OpenFreeMap tiles rate-limited": "Тайлы OpenFreeMap: превышен лимит запросов",
|
||||
"OpenFreeMap tiles timed out": "Тайлы OpenFreeMap: истекло время ожидания",
|
||||
"OpenFreeMap tiles unavailable": "Тайлы OpenFreeMap недоступны",
|
||||
"Heard by your receiver": "Принято вашим приёмником",
|
||||
"browser SDR": "SDR в браузере",
|
||||
"decoder feed": "поток декодера",
|
||||
"Your RTL-SDR receiver and decoder feed": "Ваш приёмник RTL-SDR и поток декодера",
|
||||
"Your decoder feed": "Ваш поток декодера",
|
||||
"Your RTL-SDR receiver": "Ваш приёмник RTL-SDR",
|
||||
"NO CALLSIGN": "БЕЗ ПОЗЫВНОГО",
|
||||
"ALT {altitude} FT · GS {speed} KT · TRK {track}": "ВЫС. {altitude} ФТ · ПУТ. СКОР. {speed} УЗ · ПУТЬ {track}",
|
||||
"V/S {rate}": "ВЕРТ. СКОР. {rate}",
|
||||
"V/S {rate} FPM": "ВЕРТ. СКОР. {rate} ФТ/МИН",
|
||||
"POSITION {age}": "ПОЗИЦИЯ {age}",
|
||||
"POSITION {seconds} S AGO": "ПОЗИЦИЯ {seconds} С НАЗАД",
|
||||
"{count} MSGS": {
|
||||
"one": "{count} СООБЩЕНИЕ",
|
||||
"few": "{count} СООБЩЕНИЯ",
|
||||
"many": "{count} СООБЩЕНИЙ",
|
||||
"other": "{count} СООБЩЕНИЯ"
|
||||
},
|
||||
"installation class|BARRACKS": "КАЗАРМЫ",
|
||||
"installation class|TRAINING AREA": "УЧЕБНЫЙ ПОЛИГОН",
|
||||
"Seeking up…": "Поиск вверх…",
|
||||
"Seeking down…": "Поиск вниз…",
|
||||
"Scanning {mhz} MHz…": "Сканирование {mhz} МГц…",
|
||||
"Found {mhz} MHz · {snr} dB": "Найдено {mhz} МГц · {snr} дБ",
|
||||
"Broadcaster stream is unavailable or blocked by the browser.": "Поток вещателя недоступен или заблокирован браузером.",
|
||||
"Playback requires a direct click or tap.": "Чтобы начать воспроизведение, нажмите «Пуск».",
|
||||
"Broadcaster stream could not be started.": "Не удалось запустить поток вещателя.",
|
||||
"Directory refresh degraded; showing the previous station catalog.": "Обновление каталога прошло с перебоями; показан предыдущий каталог станций.",
|
||||
"Radio directory coverage is degraded.": "Каталог радиостанций работает с перебоями.",
|
||||
"Directory refresh failed; showing the previous station catalog.": "Не удалось обновить каталог; показан предыдущий каталог станций.",
|
||||
"Radio directory is temporarily unavailable.": "Каталог радиостанций временно недоступен.",
|
||||
"Decoder feeds: {feeds}": "Потоки декодера: {feeds}",
|
||||
" · read while Local ADS-B is on": " · читаются при включённом слое «Локальный ADS-B»",
|
||||
"idle": "ожидание",
|
||||
"running": "работает",
|
||||
"suspended": "приостановлено",
|
||||
"closed": "закрыто",
|
||||
"Local FM stopped: internet radio started": "Локальный FM остановлен: запущено интернет-радио",
|
||||
"ALPR cameras": "камеры ALPR",
|
||||
"OVERPASS RATE-LIMITED": "OVERPASS: ПРЕВЫШЕН ЛИМИТ ЗАПРОСОВ",
|
||||
"OVERPASS TIMED OUT": "OVERPASS: ИСТЕКЛО ВРЕМЯ ОЖИДАНИЯ",
|
||||
"OVERPASS COULD NOT COMPLETE THE QUERY": "OVERPASS: НЕ УДАЛОСЬ ЗАВЕРШИТЬ ЗАПРОС",
|
||||
"MAP TILES TEMPORARILY UNAVAILABLE": "ТАЙЛЫ КАРТЫ ВРЕМЕННО НЕДОСТУПНЫ",
|
||||
"MAPPED NAMES TEMPORARILY UNAVAILABLE": "НАЗВАНИЯ ОБЪЕКТОВ ВРЕМЕННО НЕДОСТУПНЫ",
|
||||
"Shared flight follow expired": "Срок отслеживания рейса по ссылке истёк",
|
||||
"Shared military flight follow expired": "Срок отслеживания военного рейса по ссылке истёк",
|
||||
"Shared satellite follow expired": "Срок отслеживания спутника по ссылке истёк",
|
||||
"Shared flight could not be restored — feed unavailable": "Не удалось восстановить рейс по ссылке — поток данных недоступен",
|
||||
"Shared military flight could not be restored — feed unavailable": "Не удалось восстановить военный рейс по ссылке — поток данных недоступен",
|
||||
"Shared satellite could not be restored — feed unavailable": "Не удалось восстановить спутник по ссылке — поток данных недоступен",
|
||||
"Shared flight is unavailable": "Рейс по ссылке недоступен",
|
||||
"Shared military flight is unavailable": "Военный рейс по ссылке недоступен",
|
||||
"Shared satellite is unavailable": "Спутник по ссылке недоступен",
|
||||
"Scene not saved — browser storage unavailable": "Сцена не сохранена — хранилище браузера недоступно",
|
||||
"Scene not saved — existing saved project could not be read. Export edits or import a valid file.": "Сцена не сохранена — не удалось прочитать сохранённый проект. Экспортируйте правки или импортируйте корректный файл.",
|
||||
"Saved project could not be read; storage preserved. Import a valid file to resume saving.": "Не удалось прочитать сохранённый проект; хранилище не тронуто. Импортируйте корректный файл, чтобы снова сохранять.",
|
||||
"Scene not saved — {reason}": "Сцена не сохранена — {reason}",
|
||||
"Stopped": "Остановлено",
|
||||
"Stopped (Esc)": "Остановлено (Esc)",
|
||||
"Camera ownership changed": "Управление камерой перехвачено",
|
||||
"Scene layer turned off": "Слой сцены выключен",
|
||||
"Seeking scene clock": "Перемотка сцены",
|
||||
"Importing project": "Импорт проекта",
|
||||
"Camera move interrupted": "Перелёт камеры прерван",
|
||||
"Scene media timed out": "Истекло время ожидания медиа сцены",
|
||||
"Space Missions isolates replay data. Exit the mode to enable {layer}.": "Режим «Космические миссии» изолирует данные воспроизведения. Выйдите из режима, чтобы включить слой «{layer}».",
|
||||
"Tilted view": "Вид с наклоном",
|
||||
"Straight-down view": "Вид сверху",
|
||||
"North up": "Север вверху",
|
||||
"Exit cockpit to fly to a camera": "Выйдите из кабины, чтобы перелететь к камере",
|
||||
"Exit cockpit to fly to a location": "Выйдите из кабины, чтобы перелететь к месту",
|
||||
"Exit cockpit to fly to a photo": "Выйдите из кабины, чтобы перелететь к фото",
|
||||
"Exit cockpit to fly to a shared view": "Выйдите из кабины, чтобы перелететь к виду по ссылке",
|
||||
"Exit cockpit to fly to a target": "Выйдите из кабины, чтобы перелететь к цели",
|
||||
"Exit cockpit to fly to a vessel": "Выйдите из кабины, чтобы перелететь к судну",
|
||||
"Exit cockpit to fly to a fire": "Выйдите из кабины, чтобы перелететь к пожару",
|
||||
"Exit cockpit to fly to a route": "Выйдите из кабины, чтобы перелететь к маршруту",
|
||||
"Exit cockpit to fly to a file": "Выйдите из кабины, чтобы перелететь к файлу",
|
||||
"Exit cockpit to fly to a scene": "Выйдите из кабины, чтобы перелететь к сцене",
|
||||
"Exit cockpit to fly to a {noun}": "Выйдите из кабины, чтобы перелететь: {noun}",
|
||||
"Go to expanded Radio section": "Перейти к развёрнутому разделу радио",
|
||||
"Expand Radio section in Context": "Развернуть раздел «Радио» в панели «Контекст»",
|
||||
"Double-click the header to snap the panel back": "Двойной щелчок по заголовку — вернуть панель на место",
|
||||
"New scene name": "Название новой сцены",
|
||||
"Delete scene \"{scene}\" and all shots?": "Удалить сцену «{scene}» и все кадры?",
|
||||
"Detailed outline unavailable": "Подробный контур недоступен",
|
||||
"Provider Settings does not answer proxied requests": "«Настройки провайдеров» не отвечают на запросы через прокси",
|
||||
"Provider Settings is disabled while sharing is enabled": "«Настройки провайдеров» отключены, пока включён общий доступ",
|
||||
"Provider Settings answers only the machine running the server": "«Настройки провайдеров» отвечают только компьютеру, на котором запущен сервер",
|
||||
"Provider Settings answers only local hostnames": "«Настройки провайдеров» отвечают только на локальные имена хостов",
|
||||
"Provider Settings requires an exact local Origin": "«Настройкам провайдеров» нужен точный локальный Origin",
|
||||
"Unrecognized Origin refused": "Нераспознанный Origin отклонён",
|
||||
"Cross-origin requests are refused": "Запросы с другого источника отклоняются",
|
||||
"Content-Type must be application/json": "Content-Type должен быть application/json",
|
||||
"Method not allowed": "Метод не разрешён",
|
||||
"Request too large": "Слишком большой запрос",
|
||||
"Invalid JSON": "Некорректный JSON",
|
||||
"Body must be a JSON object of {ENV_VAR: value}": "Тело запроса должно быть JSON-объектом вида {ENV_VAR: value}",
|
||||
"No keys provided": "Ключи не переданы",
|
||||
"At most {limit} keys per save": "Ключей за одно сохранение — не больше {limit}",
|
||||
"Unknown key: {name}": "Неизвестный ключ: {name}",
|
||||
"{name} must be a string": "{name}: значение должно быть строкой",
|
||||
"{name} is empty": "{name}: пустое значение",
|
||||
"{name} is longer than any real key ({limit} max)": "{name}: значение длиннее любого настоящего ключа (максимум {limit})",
|
||||
"{name} may only contain printable characters with no spaces": "{name}: допустимы только печатные символы без пробелов",
|
||||
"{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)": "{name}: недопустимый для ключа символ (#, кавычки, $, \\ или обратный апостроф)",
|
||||
"{name} is configured outside Provider Settings and can only be changed where it was set": "{name}: задан вне «Настроек провайдеров», изменить его можно только там, где его задали",
|
||||
"The key was not saved: could not restrict the credential file to your account; nothing was saved": "Ключ не сохранён: не удалось закрыть файл учётных данных от всех, кроме вашей учётной записи; ничего не сохранено",
|
||||
"The key was not saved: the existing configuration could not be read, so nothing was changed": "Ключ не сохранён: не удалось прочитать текущую конфигурацию, поэтому ничего не изменено",
|
||||
"Could not write the {store} store": "Не удалось записать хранилище {store}",
|
||||
"Could not check ChatGPT sign-in. Try again.": "Не удалось проверить вход в ChatGPT. Попробуйте ещё раз.",
|
||||
"Cloud voice will use OPENAI_API_KEY on the next session.": "Следующая облачная голосовая сессия будет использовать OPENAI_API_KEY.",
|
||||
"Checking local ChatGPT OAuth sign-in…": "Проверка локального входа в ChatGPT через OAuth…",
|
||||
"Could not check ChatGPT sign-in.": "Не удалось проверить вход в ChatGPT.",
|
||||
"ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.": "Для облачного голоса выбран ChatGPT OAuth. Ваш ключ API остаётся сохранённым и доступным.",
|
||||
"ChatGPT sign-in expired. Opening sign-in again…": "Сеанс ChatGPT истёк. Открываем вход заново…",
|
||||
"Opening ChatGPT sign-in in your browser…": "Открываем вход в ChatGPT в браузере…",
|
||||
"Could not start ChatGPT sign-in on this machine.": "Не удалось начать вход в ChatGPT на этом компьютере.",
|
||||
"Finish ChatGPT sign-in in the browser. Waiting for it to complete…": "Завершите вход в ChatGPT в браузере. Ожидание завершения…",
|
||||
"ChatGPT sign-in timed out. Click {button} to try again.": "Время на вход в ChatGPT истекло. Нажмите «{button}», чтобы попробовать ещё раз.",
|
||||
"ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.": "Вход в ChatGPT выполнен. В следующей облачной голосовой сессии будет использоваться OAuth.",
|
||||
"OAuth check failed: {detail}": "Не удалось проверить OAuth: {detail}",
|
||||
"unsaved CCTV calibration": "несохранённая калибровка CCTV",
|
||||
"the open cockpit": "открытая кабина",
|
||||
"Switching language reloads the page. This will be lost: {items}. Switch anyway?": "Смена языка перезагрузит страницу. Будет потеряно: {items}. Всё равно сменить язык?",
|
||||
"Wind upstream unavailable": "Источник данных о ветре недоступен",
|
||||
"WebUSB needs desktop Chrome or Edge": "Для WebUSB нужен настольный Chrome или Edge",
|
||||
"connect a receiver in Radio": "подключите приёмник в разделе «Радио»",
|
||||
"receiver is in FM mode": "приёмник в режиме FM",
|
||||
"Cyclone advisories unavailable": "Сводки о циклонах недоступны",
|
||||
"Cyclone data unavailable": "Данные о циклонах недоступны",
|
||||
"Drag to size the box": "Потяните, чтобы задать размер области",
|
||||
"Press on the ground, not the sky": "Нажмите на землю, а не на небо",
|
||||
"Point the camera at the ground to use the view": "Наведите камеру на землю, чтобы использовать вид",
|
||||
"That point cannot anchor a box": "От этой точки нельзя построить область",
|
||||
"Box edges must be finite": "Некорректные координаты области",
|
||||
"Longitudes must be within ±180°": "Долгота должна быть в пределах ±180°",
|
||||
"Select one side of the dateline": "Не пересекайте линию перемены дат",
|
||||
"Box must enclose an area": "Область должна иметь площадь",
|
||||
"Imagery stops at ±{limit}° latitude": "Снимки есть только до ±{limit}° широты",
|
||||
"{owner} is using the pointer — close it first": "Указатель занят: «{owner}». Закройте его",
|
||||
"OpenSky OAuth client missing/invalid": "Клиент OAuth OpenSky отсутствует или недействителен",
|
||||
"OpenSky OAuth rejected credentials": "OpenSky OAuth отклонил учётные данные",
|
||||
"OpenSky username/password rejected": "OpenSky отклонил имя пользователя или пароль",
|
||||
"OpenSky auth missing": "Нет данных для входа в OpenSky",
|
||||
"OpenSky auth required": "OpenSky требует входа",
|
||||
"OpenSky auth invalid": "Неверные данные для входа в OpenSky",
|
||||
"OpenSky auth failed": "Не удалось войти в OpenSky",
|
||||
"Source snapshot {minutes} min old": "Возраст снимка источника: {minutes} мин",
|
||||
"{source} rate limited (anonymous)": "{source}: лимит запросов (анонимно)",
|
||||
"{source} rate limited": "{source}: лимит запросов",
|
||||
"{source} HTTP {status}": "{source}: HTTP {status}",
|
||||
"{source} network error": "{source}: ошибка сети",
|
||||
"Malformed {source} aircraft rows": "{source}: некорректные записи о самолётах",
|
||||
"Malformed {source} response": "{source}: некорректный ответ",
|
||||
"Source snapshot time unavailable": "Время снимка источника неизвестно",
|
||||
"Live data unavailable": "Данные в реальном времени недоступны",
|
||||
"{n} aircraft": {
|
||||
"one": "{n} самолёт",
|
||||
"few": "{n} самолёта",
|
||||
"many": "{n} самолётов",
|
||||
"other": "{n} самолёта"
|
||||
},
|
||||
"{n} military aircraft": {
|
||||
"one": "{n} военный самолёт",
|
||||
"few": "{n} военных самолёта",
|
||||
"many": "{n} военных самолётов",
|
||||
"other": "{n} военного самолёта"
|
||||
},
|
||||
"{n} ships": {
|
||||
"one": "{n} судно",
|
||||
"few": "{n} судна",
|
||||
"many": "{n} судов",
|
||||
"other": "{n} судна"
|
||||
},
|
||||
"{n} satellites": {
|
||||
"one": "{n} спутник",
|
||||
"few": "{n} спутника",
|
||||
"many": "{n} спутников",
|
||||
"other": "{n} спутника"
|
||||
},
|
||||
"{n} launches": {
|
||||
"one": "{n} пуск",
|
||||
"few": "{n} пуска",
|
||||
"many": "{n} пусков",
|
||||
"other": "{n} пуска"
|
||||
},
|
||||
"{n} earthquakes": {
|
||||
"one": "{n} землетрясение",
|
||||
"few": "{n} землетрясения",
|
||||
"many": "{n} землетрясений",
|
||||
"other": "{n} землетрясения"
|
||||
},
|
||||
"{n} fires": {
|
||||
"one": "{n} пожар",
|
||||
"few": "{n} пожара",
|
||||
"many": "{n} пожаров",
|
||||
"other": "{n} пожара"
|
||||
},
|
||||
"{n} fire perimeters": {
|
||||
"one": "{n} контур пожара",
|
||||
"few": "{n} контура пожаров",
|
||||
"many": "{n} контуров пожаров",
|
||||
"other": "{n} контура пожаров"
|
||||
},
|
||||
"{n} cyclones": {
|
||||
"one": "{n} циклон",
|
||||
"few": "{n} циклона",
|
||||
"many": "{n} циклонов",
|
||||
"other": "{n} циклона"
|
||||
},
|
||||
"{n} buses": {
|
||||
"one": "{n} автобус",
|
||||
"few": "{n} автобуса",
|
||||
"many": "{n} автобусов",
|
||||
"other": "{n} автобуса"
|
||||
},
|
||||
"{n} bike share": {
|
||||
"one": "{n} станция проката велосипедов",
|
||||
"few": "{n} станции проката велосипедов",
|
||||
"many": "{n} станций проката велосипедов",
|
||||
"other": "{n} станции проката велосипедов"
|
||||
},
|
||||
"{n} flock cameras": {
|
||||
"one": "{n} камера ALPR",
|
||||
"few": "{n} камеры ALPR",
|
||||
"many": "{n} камер ALPR",
|
||||
"other": "{n} камеры ALPR"
|
||||
},
|
||||
"{n} military bases": {
|
||||
"one": "{n} военная база",
|
||||
"few": "{n} военные базы",
|
||||
"many": "{n} военных баз",
|
||||
"other": "{n} военной базы"
|
||||
},
|
||||
"{n} datacenters": {
|
||||
"one": "{n} дата-центр",
|
||||
"few": "{n} дата-центра",
|
||||
"many": "{n} дата-центров",
|
||||
"other": "{n} дата-центра"
|
||||
},
|
||||
"{n} dams": {
|
||||
"one": "{n} плотина",
|
||||
"few": "{n} плотины",
|
||||
"many": "{n} плотин",
|
||||
"other": "{n} плотины"
|
||||
},
|
||||
"{n} results": {
|
||||
"one": "{n} результат",
|
||||
"few": "{n} результата",
|
||||
"many": "{n} результатов",
|
||||
"other": "{n} результата"
|
||||
},
|
||||
"anywhere in the loaded data": "во всех загруженных данных",
|
||||
"in view": "в кадре",
|
||||
"within {radius} of the view centre": "в радиусе {radius} от центра вида",
|
||||
"within {radius} of {place} (Contacts window)": "в радиусе {radius} — {place} (окно «Контактов»)",
|
||||
"within {radius} of {place}": "в радиусе {radius} — {place}",
|
||||
"within {radius}": "в радиусе {radius}",
|
||||
"over {region}": "в регионе {region}",
|
||||
"At least {items}": "{items} или больше",
|
||||
"{items} {scope}": "{items} {scope}",
|
||||
"last 24 h": "за последние 24 ч",
|
||||
"past 30 d and upcoming": "за последние 30 дн. и предстоящие",
|
||||
"the flights layer loads by viewport": "слой рейсов загружается по области вида",
|
||||
"distance is ground distance, not overhead": "расстояние — по поверхности земли до подспутниковой точки, а не до самого аппарата",
|
||||
"the count is a lower bound because at least one layer reached its retained limit.": "это нижняя граница: хотя бы один слой достиг предела хранимых записей.",
|
||||
"{label} loading": "{label}: загрузка",
|
||||
"{label} error": "{label}: ошибка",
|
||||
"counted the first {returned} loaded records": "учтены только первые загруженные записи: {returned}",
|
||||
"counted {returned} of {total} loaded records": "учтено загруженных записей: {returned} из {total}",
|
||||
"{layers} just turned on — still loading, counts will rise": "{layers}: только что включено — данные ещё загружаются, значения вырастут",
|
||||
"Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "Снимок панели «Контакты» ({time}) — та же сохранённая выборка используется для карточки, речи и памяти уточняющих вопросов",
|
||||
"Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "Снимок панели «Контакты» — та же сохранённая выборка используется для карточки, речи и памяти уточняющих вопросов",
|
||||
"Not answered: {layers}": "Нет ответа: {layers}",
|
||||
"The flights layer is not enabled": "Слой рейсов не включён",
|
||||
"The military layer is not enabled": "Слой военных рейсов не включён",
|
||||
"The satellites layer is not enabled": "Слой спутников не включён",
|
||||
"The ais-live-vessels layer is not enabled": "Слой судов AIS не включён",
|
||||
"Could not enable the requested layer": "Не удалось включить запрошенный слой",
|
||||
"Could not disable the requested layer": "Не удалось выключить запрошенный слой",
|
||||
"Radio layer unavailable": "Слой радио недоступен",
|
||||
"Camera navigation is unavailable in the current view": "Управление камерой недоступно в текущем виде",
|
||||
"Camera navigation policy unavailable": "Правила навигации камеры недоступны",
|
||||
"Nearest-aircraft selection supports Flights or Military Flights only": "Выбор ближайшего самолёта работает только для рейсов и военных рейсов",
|
||||
"Nearest-aircraft selection needs a preset, place name, or latitude and longitude": "Для выбора ближайшего самолёта нужен пресет, название места или широта и долгота",
|
||||
"Could not arrive at the requested place": "Не удалось долететь до запрошенного места",
|
||||
"The nearest airborne aircraft could not be selected": "Не удалось выбрать ближайший самолёт в воздухе",
|
||||
"No flights within {radius} of the current view": "В радиусе {radius} от текущего вида нет рейсов",
|
||||
"No military within {radius} of the current view": "В радиусе {radius} от текущего вида нет военных рейсов",
|
||||
"No satellites within {radius} of the current view": "В радиусе {radius} от текущего вида нет спутников",
|
||||
"No vessels within {radius} of the current view": "В радиусе {radius} от текущего вида нет судов",
|
||||
"{layer} is enabled on the {source} feed, but no airborne aircraft is loaded in the {place} view yet": "{layer}: включено (поток {source}), но в виде «{place}» пока не загружено ни одного самолёта в воздухе",
|
||||
"{layer} is enabled, but no airborne aircraft is loaded in the {place} view yet": "{layer}: включено, но в виде «{place}» пока не загружено ни одного самолёта в воздухе",
|
||||
"{layer} is enabled, but its aircraft feed is unavailable": "{layer}: включено, но поток данных о самолётах недоступен",
|
||||
"{layer} is enabled, but its destination refresh did not complete": "{layer}: включено, но обновление в точке назначения не завершилось",
|
||||
"{layer} is enabled, but {source} is unavailable": "{layer}: включено, но источник {source} недоступен",
|
||||
"Could not arrive at {place}": "Не удалось долететь: {place}",
|
||||
"{name} is no longer loaded": "{name}: объект больше не загружен",
|
||||
"Unknown target layer: {target}": "Неизвестный слой цели: {target}",
|
||||
"Layer": "Слой",
|
||||
"there": "там",
|
||||
"Frame flights": "Показать самолёты в кадре",
|
||||
"Frame military": "Показать военные самолёты в кадре",
|
||||
"Frame vessels": "Показать суда в кадре",
|
||||
"Fly to Location": "Перелёт к месту",
|
||||
"Nearest aircraft to Here": "Ближайший самолёт здесь",
|
||||
"Estimate is incomplete — a response was still in flight when the session ended, so its usage was never reported.": "Оценка неполная: к моменту завершения сессии один из ответов ещё не был получен, поэтому его использование не учтено.",
|
||||
"STANDARD": "СТАНДАРТ",
|
||||
"vehicle motion|Stopped": "Стоит",
|
||||
"ACQUIRING FRAME": "ПОЛУЧЕНИЕ КАДРА",
|
||||
"Camera source temporarily unavailable": "Источник камер временно недоступен",
|
||||
"Perimeter source unavailable": "Источник контуров пожаров недоступен",
|
||||
"Imagery catalog unavailable": "Каталог снимков недоступен",
|
||||
"Earthquake source unavailable": "Источник данных о землетрясениях недоступен",
|
||||
"TeleGeography load failed": "Не удалось загрузить данные TeleGeography",
|
||||
"Wind request timed out": "Истекло время ожидания запроса ветра",
|
||||
"Malformed wind manifest": "Некорректный манифест ветра",
|
||||
"Malformed wind grid": "Некорректная сетка ветра",
|
||||
"Unknown wind model": "Неизвестная модель ветра",
|
||||
"Unknown weather overlay": "Неизвестный погодный слой",
|
||||
"Temperature field unavailable": "Поле температуры недоступно",
|
||||
"Mean sea level pressure field unavailable": "Поле давления на уровне моря недоступно",
|
||||
"KartaView answered HTTP {status}": "KartaView ответил: HTTP {status}",
|
||||
"KartaView refused the request (code {code})": "KartaView отклонил запрос (код {code})",
|
||||
"Stopped by voice": "Остановлено голосовой командой",
|
||||
"drawings on the map": "рисунки на карте",
|
||||
"imported map files": "импортированные файлы на карте",
|
||||
"the active voice session": "активная голосовая сессия",
|
||||
"Light aircraft": "Лёгкий самолёт",
|
||||
"Glider": "Планёр",
|
||||
"Turboprop": "Турбовинтовой самолёт",
|
||||
"Airliner": "Авиалайнер",
|
||||
"Widebody": "Широкофюзеляжный самолёт",
|
||||
"Four-engine jet": "Четырёхмоторный реактивный самолёт",
|
||||
"Helicopter": "Вертолёт",
|
||||
"Fast jet": "Высокоманёвренный самолёт",
|
||||
"Business jet": "Бизнес-джет",
|
||||
"Drone": "Беспилотник",
|
||||
"fire cause|Human": "деятельность человека",
|
||||
"fire cause|Natural": "природная",
|
||||
"fire cause|Undetermined": "не установлена",
|
||||
"fire cause|Unknown": "неизвестна",
|
||||
"fire behavior|Minimal": "минимальная активность",
|
||||
"fire behavior|Moderate": "умеренная активность",
|
||||
"fire behavior|Active": "высокая активность",
|
||||
"fire behavior|Extreme": "экстремальная активность",
|
||||
"Type {level} Incident": "инцидент {level}-го типа",
|
||||
"${amount}K": "{amount} тыс. $",
|
||||
"${amount}M": "{amount} млн $",
|
||||
"${amount}B": "{amount} млрд $",
|
||||
"{speed} kn": "{speed} уз",
|
||||
"1090 MHz": "1090 МГц",
|
||||
"978 MHz UAT": "978 МГц UAT",
|
||||
"feed status|live": "онлайн",
|
||||
"feed status|stale": "устарел",
|
||||
"feed status|unreachable": "недоступен",
|
||||
"feed status|invalid": "некорректный",
|
||||
"Vector tile metadata unavailable": "Метаданные векторных тайлов недоступны",
|
||||
"Vector tiles unavailable": "Векторные тайлы недоступны",
|
||||
"Vector tiles unavailable (HTTP {status})": "Векторные тайлы недоступны (HTTP {status})",
|
||||
"Vector tile request timed out": "Векторные тайлы: истекло время ожидания",
|
||||
"Zoom in for vector tile coverage": "Приблизьте карту, чтобы загрузить векторные тайлы",
|
||||
"Camera coverage unavailable": "Покрытие камер недоступно",
|
||||
"Hybrid": "Гибрид",
|
||||
"TomTom roads only, each with its live flow": "Только дороги TomTom, у каждой — свой поток онлайн",
|
||||
"TomTom roads only, each with its live flow (needs a TomTom key)": "Только дороги TomTom, у каждой — свой поток онлайн (нужен ключ TomTom)",
|
||||
"OpenStreetMap roads, with TomTom flow matched when available": "Дороги OpenStreetMap; поток TomTom сопоставляется, где он есть",
|
||||
"TomTom roads first; OpenStreetMap fills the rest, simulated": "Сначала дороги TomTom; остальное дополняет OpenStreetMap с симуляцией потока",
|
||||
"TomTom roads first; OpenStreetMap fills the rest, simulated (needs a TomTom key)": "Сначала дороги TomTom; остальное дополняет OpenStreetMap с симуляцией потока (нужен ключ TomTom)",
|
||||
"Austin": "Остин",
|
||||
"San Francisco": "Сан-Франциско",
|
||||
"New York": "Нью-Йорк",
|
||||
"Tokyo": "Токио",
|
||||
"London": "Лондон",
|
||||
"Paris": "Париж",
|
||||
"Dubai": "Дубай",
|
||||
"Washington DC": "Вашингтон",
|
||||
"Tallinn": "Таллин",
|
||||
"Los Angeles": "Лос-Анджелес",
|
||||
"Data attribution": "Источники данных",
|
||||
"Data provided by:": "Данные предоставлены:",
|
||||
"Exit cockpit to view weather coverage": "Выйдите из кабины, чтобы показать зону покрытия погодного слоя",
|
||||
"Exit cockpit to fly to a cyclone": "Выйдите из кабины, чтобы перелететь к циклону",
|
||||
"the title of scene {scene} is empty or longer than {max} characters": "название сцены {scene} пустое или длиннее {max} символов",
|
||||
"the title of shot {shot} in scene {scene} is empty or longer than {max} characters": "название кадра {shot} в сцене {scene} пустое или длиннее {max} символов",
|
||||
"the project is larger than 5 MiB": "проект больше 5 МиБ",
|
||||
"the project has too many shots": "в проекте слишком много кадров",
|
||||
"the scene data failed validation": "данные сцены не прошли проверку",
|
||||
"Layer on": "Слой: вкл.",
|
||||
"Layer off": "Слой: выкл.",
|
||||
"Zoom in": "Приблизить",
|
||||
"Zoom out": "Отдалить",
|
||||
"Zoom out to globe": "Отдалить до глобуса",
|
||||
"Open data layers": "Открыть слои данных",
|
||||
"Open or close a panel": "Открыть или закрыть панель",
|
||||
"Change context mode": "Сменить режим контекста",
|
||||
"Control cockpit": "Управлять кабиной",
|
||||
"Change visual style": "Сменить визуальный стиль",
|
||||
"Adjust HUD": "Настроить HUD",
|
||||
"Adjust cyber sonar": "Настроить кибер-сонар",
|
||||
"Adjust detection overlay": "Настроить слой детекции",
|
||||
"Change map stack": "Сменить набор карт",
|
||||
"Adjust post-processing": "Настроить постобработку",
|
||||
"Control scene": "Управлять сценой",
|
||||
"Control CCTV": "Управлять камерами CCTV",
|
||||
"Control radio": "Управлять радио",
|
||||
"Track contact": "Отслеживать контакт",
|
||||
"Stop tracking": "Остановить отслеживание",
|
||||
"Clear marks": "Убрать отметки",
|
||||
"Move camera": "Переместить камеру",
|
||||
"Fly route": "Пролететь по маршруту",
|
||||
"Analyst query": "Аналитический запрос",
|
||||
"Next ISS pass": "Следующий пролёт МКС",
|
||||
"Next satellite pass": "Следующий пролёт спутника",
|
||||
"Action": "Действие"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
checkCatalog,
|
||||
extractMessages,
|
||||
missingTemplate,
|
||||
} from '../../scripts/i18n.mjs';
|
||||
|
||||
// A plural entry whose every form is still the English text must be reported
|
||||
// the same way a string entry is ("is still English"). Today the plural
|
||||
// branch of checkCatalog never compares forms with the English.
|
||||
const fixture = new Map([
|
||||
['{count} cameras', { plural: true, one: '{count} camera' }],
|
||||
['Save keys', { plural: false }],
|
||||
]);
|
||||
|
||||
test('string entry left in English is reported (control)', () => {
|
||||
const { errors } = checkCatalog(
|
||||
'de',
|
||||
{
|
||||
'Save keys': 'Save keys',
|
||||
'{count} cameras': { one: 'x {count}', other: 'y {count}' },
|
||||
},
|
||||
fixture,
|
||||
new Set(),
|
||||
);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0], /is still English/);
|
||||
});
|
||||
|
||||
for (const [locale, entry] of [
|
||||
['de', { one: '{count} camera', other: '{count} cameras' }],
|
||||
['ja', { other: '{count} cameras' }],
|
||||
['zh-CN', { other: '{count} cameras' }],
|
||||
[
|
||||
'es',
|
||||
{
|
||||
one: '{count} camera',
|
||||
many: '{count} cámaras',
|
||||
other: '{count} cameras',
|
||||
},
|
||||
],
|
||||
[
|
||||
'ru',
|
||||
{
|
||||
one: '{count} camera',
|
||||
few: '{count} камеры',
|
||||
many: '{count} камер',
|
||||
other: '{count} cameras',
|
||||
},
|
||||
],
|
||||
]) {
|
||||
test(`${locale}: plural entry with English forms is reported`, () => {
|
||||
const { errors } = checkCatalog(
|
||||
locale,
|
||||
{ 'Save keys': 'Schlüssel', '{count} cameras': entry },
|
||||
fixture,
|
||||
new Set(),
|
||||
);
|
||||
assert.ok(
|
||||
errors.some((e) => /is still English/.test(e)),
|
||||
`expected an "is still English" error, got ${JSON.stringify(errors)}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// `npm run i18n -- --missing <code>` prints a template; pasted without edits,
|
||||
// none of its entries may pass as a translation (K-21, K-52), and a plural
|
||||
// entry carries every form the language needs (K-97).
|
||||
const messages = extractMessages();
|
||||
for (const locale of ['de', 'ja', 'es', 'ru']) {
|
||||
test(`${locale}: an untouched --missing template fails the check entry by entry`, () => {
|
||||
const template = missingTemplate(locale, {}, messages);
|
||||
assert.equal(Object.keys(template).length, messages.size);
|
||||
const categories = new Intl.PluralRules(locale).resolvedOptions()
|
||||
.pluralCategories;
|
||||
for (const [text, info] of messages)
|
||||
if (info.plural)
|
||||
assert.deepEqual(Object.keys(template[text]), categories, text);
|
||||
const { missing, errors } = checkCatalog(
|
||||
locale,
|
||||
template,
|
||||
messages,
|
||||
new Set(),
|
||||
);
|
||||
assert.deepEqual(missing, []);
|
||||
const accepted = [...messages.keys()].filter(
|
||||
(text) => !errors.some((error) => error.startsWith(`"${text}" `)),
|
||||
);
|
||||
assert.deepEqual(accepted, []);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"es": [
|
||||
"1090 MHz",
|
||||
"978 MHz UAT",
|
||||
"AIS: --",
|
||||
"AIS: {name}",
|
||||
"ANIME",
|
||||
@@ -7,6 +9,7 @@
|
||||
"AUTO",
|
||||
"Ambient",
|
||||
"Anime",
|
||||
"Austin",
|
||||
"BRG {value}°",
|
||||
"BRG —",
|
||||
"Blues",
|
||||
@@ -79,6 +82,7 @@
|
||||
"SRC {value}",
|
||||
"SSE",
|
||||
"STD",
|
||||
"San Francisco",
|
||||
"Sector",
|
||||
"Sonar",
|
||||
"Soul",
|
||||
@@ -103,6 +107,7 @@
|
||||
"panel",
|
||||
"view band|GLOBAL",
|
||||
"voice|ERROR",
|
||||
"{speed} kn",
|
||||
"{type} SPD: {speed} HDG: {heading}",
|
||||
"{value} KM",
|
||||
"{value} KM/H",
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// K-23: scripts/i18n.mjs must skip index entries that are gone from the
|
||||
// working tree (plain `mv`/`rm` without `git mv`/`git rm`).
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import {
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
writeFileSync,
|
||||
copyFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function sandbox() {
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'k23-'));
|
||||
mkdirSync(path.join(root, 'scripts'));
|
||||
mkdirSync(path.join(root, 'src/i18n/locales'), { recursive: true });
|
||||
copyFileSync(
|
||||
path.join(HERE, '../../scripts/i18n.mjs'),
|
||||
path.join(root, 'scripts/i18n.mjs'),
|
||||
);
|
||||
writeFileSync(path.join(root, 'src/a.js'), "export const x = t('Hello');\n");
|
||||
writeFileSync(path.join(root, 'index.html'), '<p data-i18n>Hello</p>\n');
|
||||
const git = (...args) =>
|
||||
execFileSync('git', args, { cwd: root, encoding: 'utf8' });
|
||||
git('init', '-q');
|
||||
git('add', '-A');
|
||||
git('-c', 'user.email=k@23', '-c', 'user.name=k23', 'commit', '-qm', 'init');
|
||||
return root;
|
||||
}
|
||||
|
||||
test('extractMessages survives a module renamed with plain mv', async () => {
|
||||
const root = sandbox();
|
||||
try {
|
||||
renameSync(path.join(root, 'src/a.js'), path.join(root, 'src/b.js'));
|
||||
const mod = await import(
|
||||
pathToFileURL(path.join(root, 'scripts/i18n.mjs')).href
|
||||
);
|
||||
const messages = mod.extractMessages();
|
||||
assert.ok(messages.has('Hello'));
|
||||
assert.doesNotThrow(() => mod.findUnmarked());
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user