mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-13 23:02:22 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3953421aad | ||
|
|
209b135ff1 |
No files matched your search
@@ -1,6 +0,0 @@
|
|||||||
- When the globe stops drawing after a render error or a lost WebGL context,
|
|
||||||
an alert dialog says so instead of leaving a frozen or black globe behind
|
|
||||||
working controls. Retry restarts drawing (Reload after a lost context), and
|
|
||||||
Reset view reopens the app without the view restored from the address bar.
|
|
||||||
Cesium's "Rendering has stopped" panel no longer appears in the app
|
|
||||||
(German, @gekh, #1579).
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
- Give counts real plural forms in every language: the wildlife count, the
|
||||||
|
Data Library list, the News time window, the GhostMaps event age and the
|
||||||
|
bridge radar range now read "1 Tag" and "7 Tage", «0,75 МОР. МИЛИ» and
|
||||||
|
«12 МОР. МИЛЬ». Street level words "the view centre" in each language's
|
||||||
|
grammar, and Latest, FOCUS, CONFIRMED and the course readout each have
|
||||||
|
their own translation where one word served two places (German, @gekh,
|
||||||
|
#1700).
|
||||||
@@ -2442,51 +2442,6 @@ future driver fix restores the effect with no code change; iOS/iPadOS platform
|
|||||||
detection is the backstop for when no probe context can be created. Applied
|
detection is the backstop for when no probe context can be created. Applied
|
||||||
during viewer construction, before any tile builds a draw command.
|
during viewer construction, before any tile builds a draw command.
|
||||||
|
|
||||||
## Render failure notice
|
|
||||||
|
|
||||||
When the globe stops drawing, the scene chrome says so instead of leaving a
|
|
||||||
black globe behind working controls. `installRenderFailureNotice()`
|
|
||||||
(`src/ui/renderFailure.js`) listens for Cesium's `renderError` and the canvas'
|
|
||||||
`webglcontextlost`, logs the error, and publishes a notice that
|
|
||||||
`RenderFailure.vue` shows as an alert dialog with focus on its first action.
|
|
||||||
The app's viewer uses `showRenderLoopErrors: false`, so Cesium's dead-end
|
|
||||||
"Rendering has stopped" panel no longer appears; `applicationViewerOptions()`
|
|
||||||
keeps it by default for other callers.
|
|
||||||
|
|
||||||
- **Retry** restarts the render loop. The notice stays until a frame starts
|
|
||||||
and ends without an error: Cesium raises `postRender` after a failed frame
|
|
||||||
too.
|
|
||||||
- **Reload**, after a lost context, reloads the page; the address keeps the
|
|
||||||
view.
|
|
||||||
- **Reset view** opens `appRootUrl()`, without the hash or `/share/<code>`
|
|
||||||
path; the query, such as `?lang=`, is kept.
|
|
||||||
|
|
||||||
Pitfalls:
|
|
||||||
|
|
||||||
- An error thrown outside Scene's error handling (a frame listener, `onTick`,
|
|
||||||
a resize) stops the loop without a `renderError`. The notice wraps the
|
|
||||||
widget's `render()` and `resize()` and raises it as one, once even with
|
|
||||||
`rethrowRenderErrors`.
|
|
||||||
- A throwing listener leaves its Cesium event mid-raise, with listener changes
|
|
||||||
only queued. The wrapper finishes those raises first (`finishRaise()` over
|
|
||||||
`frameEvents()`), so a layer turned off or on meanwhile behaves.
|
|
||||||
- A render error marks the loop stopped while its next frame is still queued.
|
|
||||||
The wrapper keeps the loop marked running, so a Retry or a shown tab
|
|
||||||
(`src/app/tools.js`) before that frame starts no second loop.
|
|
||||||
- After a lost context, later render errors are only logged, so the notice
|
|
||||||
keeps saying why. The listener neither prevents nor restores the loss.
|
|
||||||
- When the globe draws again, focus goes back where it was, or to the globe's
|
|
||||||
canvas, which the map's keys read as the map; the canvas is focusable only
|
|
||||||
until focus leaves or the globe is pressed. Focus the user moved stays put.
|
|
||||||
- The notice sits above floating panels such as the Québec camera viewer, and
|
|
||||||
its buttons are 44 px tall on touch screens.
|
|
||||||
|
|
||||||
An inline conversation panel never reloads or navigates its host's page: it
|
|
||||||
offers Retry after a render error only, and nothing after a lost context, where
|
|
||||||
the panel runtime's own notice applies. Its timer (`keepPanelRendering()`)
|
|
||||||
draws only while the render loop is on. An error a timer frame throws is caught
|
|
||||||
there: it has already been raised as a render error.
|
|
||||||
|
|
||||||
## Map Source control ownership
|
## Map Source control ownership
|
||||||
|
|
||||||
Map Source controls own chip listeners, source-state subscriptions and selection
|
Map Source controls own chip listeners, source-state subscriptions and selection
|
||||||
|
|||||||
+2
-1
@@ -87,7 +87,8 @@ menu.onChoose = (code) => switchLocale(code); // switched in place and stored (i
|
|||||||
|
|
||||||
- **Pass literals.** `t('Close')`, not `t(label)` (unless `label` holds a `msg()` value) and never a template literal with `${…}`.
|
- **Pass literals.** `t('Close')`, not `t(label)` (unless `label` holds a `msg()` value) and never a template literal with `${…}`.
|
||||||
- **Use placeholders, not concatenation.** `t('Flying to {place}…', { place })`, not `t('Flying to') + ' ' + place`. Word order differs between languages.
|
- **Use placeholders, not concatenation.** `t('Flying to {place}…', { place })`, not `t('Flying to') + ' ' + place`. Word order differs between languages.
|
||||||
- **Use `tn()` for counts**, not `count === 1 ? … : …`. Russian has three forms.
|
- **A placeholder takes a name, not a phrase.** When code has no name to pass and would fall back to a phrase ("the view centre", "this spot"), give that case a message of its own: `t('{source} at the view centre', { source })`. Other languages put a phrase in a different case after a preposition, which a name in its nominative form cannot follow.
|
||||||
|
- **Use `tn()` for counts**, not `count === 1 ? … : …`. Russian has three forms. That includes a number the English writes with a symbol or abbreviation, so both English forms read the same (`tn(hours, 'Last {hours} h', 'Last {hours} h', { hours })`): other languages inflect the words around it («Última 1 h», «Últimas 24 h»). Fractions have a form of their own too (ru «0,75 МОР. МИЛИ»).
|
||||||
- **Translate the label, not the value.** Layer ids, enums, share-link parameters, API fields, voice tool schemas and feed data (callsigns, vessel and place names) stay as they are; translate only the text shown for them.
|
- **Translate the label, not the value.** Layer ids, enums, share-link parameters, API fields, voice tool schemas and feed data (callsigns, vessel and place names) stay as they are; translate only the text shown for them.
|
||||||
- **No `t()` at module top level.** Keep the English in `msg()` and call `t()` when rendering.
|
- **No `t()` at module top level.** Keep the English in `msg()` and call `t()` when rendering.
|
||||||
- **Numbers and dates** go through `formatNumber`, `formatDate`, `formatRelativeTime` and `formatUtcDayMonth` from `src/i18n/index.js`, which follow the active language.
|
- **Numbers and dates** go through `formatNumber`, `formatDate`, `formatRelativeTime` and `formatUtcDayMonth` from `src/i18n/index.js`, which follow the active language.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Catalog: `src/i18n/locales/ru.json`. General rules: [TRANSLATORS.md](../TRANSLAT
|
|||||||
- Sentence case, not Title Case: "Live Flights" → «Рейсы в реальном времени».
|
- Sentence case, not Title Case: "Live Flights" → «Рейсы в реальном времени».
|
||||||
- Quotes inside Russian text are «ёлочки». Write ё where it belongs: её, ещё, всё.
|
- Quotes inside Russian text are «ёлочки». Write ё where it belongs: её, ещё, всё.
|
||||||
- In short labels, a standard abbreviation with a period beats a wrapped line: НЕОПРЕД., СЛЕД.
|
- In short labels, a standard abbreviation with a period beats a wrapped line: НЕОПРЕД., СЛЕД.
|
||||||
- Units: км, м, км/ч, м/с, уз (knots), фт (feet), ч, мин, с; in UPPERCASE text КМ, М, КМ/С, УЗ, ФТ. MW is МВт in any case. `°` and `%` stay.
|
- Units: км, м, км/ч, м/с, уз (knots), фт (feet), ч, мин, с; in UPPERCASE text КМ, М, КМ/С, УЗ, ФТ. MW is МВт in any case. `°` and `%` stay. Nautical miles on the bridge radar are «мор. миля», a noun that follows the count: «3 МОР. МИЛИ», «12 МОР. МИЛЬ», «0,75 МОР. МИЛИ».
|
||||||
|
|
||||||
## Plurals
|
## Plurals
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Translation source: meirm (#312).
|
|||||||
- The apostrophe is always ʼ (U+02BC): обʼєкт, звʼязок, памʼять. Not ' or ’.
|
- The apostrophe is always ʼ (U+02BC): обʼєкт, звʼязок, памʼять. Not ' or ’.
|
||||||
- Quotes inside Ukrainian text are «ялинки»; a button or layer name inside a sentence goes in them: Увімкніть «Вуличні знімки».
|
- Quotes inside Ukrainian text are «ялинки»; a button or layer name inside a sentence goes in them: Увімкніть «Вуличні знімки».
|
||||||
- In short labels, a standard abbreviation with a period beats a wrapped line: ПОПЕР., ДАЛЬН., КАЛІБР.
|
- In short labels, a standard abbreviation with a period beats a wrapped line: ПОПЕР., ДАЛЬН., КАЛІБР.
|
||||||
- Units: км, м, км/год, м/с, вуз (knots), фт (feet), год, хв, с, МГц, дБ, гПа; in UPPERCASE text КМ, М, КМ/С, ВУЗ, ФТ. MW is МВт in any case. `°` and `%` stay. Decimal comma in coordinates written as text: 28,2710° пн. ш.
|
- Units: км, м, км/год, м/с, вуз (knots), фт (feet), год, хв, с, МГц, дБ, гПа; in UPPERCASE text КМ, М, КМ/С, ВУЗ, ФТ. Nautical miles on the bridge radar are «мор. миля», a noun that follows the count: «3 МОР. МИЛІ», «12 МОР. МИЛЬ», «0,75 МОР. МИЛІ». MW is МВт in any case. `°` and `%` stay. Decimal comma in coordinates written as text: 28,2710° пн. ш.
|
||||||
- Coordinates: пн. ш. / пд. ш. / сх. д. / зх. д.; the HUD hemisphere letters are Пн, Пд, Сх, Зх.
|
- Coordinates: пн. ш. / пд. ш. / сх. д. / зх. д.; the HUD hemisphere letters are Пн, Пд, Сх, Зх.
|
||||||
|
|
||||||
## Plurals
|
## Plurals
|
||||||
|
|||||||
@@ -557,13 +557,6 @@
|
|||||||
"src/ui/frameRateMonitor.js",
|
"src/ui/frameRateMonitor.js",
|
||||||
"src/ui/frameRateMonitor.test.mjs",
|
"src/ui/frameRateMonitor.test.mjs",
|
||||||
"src/ui/frameRateView.test.mjs",
|
"src/ui/frameRateView.test.mjs",
|
||||||
"src/ui/renderFailure.test.mjs",
|
|
||||||
"src/ui/renderFailureView.test.mjs",
|
|
||||||
"src/ui/renderFailureLoopErrors.test.mjs",
|
|
||||||
"src/ui/renderFailureEventRetry.test.mjs",
|
|
||||||
"src/app/renderFailureInlineRetry.test.mjs",
|
|
||||||
"src/ui/renderFailureSecondLoop.test.mjs",
|
|
||||||
"src/ui/renderLoopFixture.mjs",
|
|
||||||
"src/layers/cctv/lifecycle.js",
|
"src/layers/cctv/lifecycle.js",
|
||||||
"src/layers/cctv/controls.js",
|
"src/layers/cctv/controls.js",
|
||||||
"src/layers/cctv/geometry.js",
|
"src/layers/cctv/geometry.js",
|
||||||
|
|||||||
@@ -163,7 +163,6 @@
|
|||||||
"src/ui/kit/UiBadge.vue",
|
"src/ui/kit/UiBadge.vue",
|
||||||
"src/ui/stores/globeActions.js",
|
"src/ui/stores/globeActions.js",
|
||||||
"src/ui/views/panels/GlobeActions.vue",
|
"src/ui/views/panels/GlobeActions.vue",
|
||||||
"src/ui/views/panels/RenderFailure.vue",
|
|
||||||
"src/ui/views/panels/GlobeCompass.vue",
|
"src/ui/views/panels/GlobeCompass.vue",
|
||||||
"src/ui/stores/recording.js",
|
"src/ui/stores/recording.js",
|
||||||
"src/ui/styleParameters.js",
|
"src/ui/styleParameters.js",
|
||||||
@@ -3114,7 +3113,6 @@
|
|||||||
"src/ui/displayControls.js",
|
"src/ui/displayControls.js",
|
||||||
"src/ui/effects.js",
|
"src/ui/effects.js",
|
||||||
"src/ui/frameRateMonitor.js",
|
"src/ui/frameRateMonitor.js",
|
||||||
"src/ui/renderFailure.js",
|
|
||||||
"src/ui/layerPanel.js",
|
"src/ui/layerPanel.js",
|
||||||
"src/ui/weatherPanel.js",
|
"src/ui/weatherPanel.js",
|
||||||
"src/ui/railCards.js",
|
"src/ui/railCards.js",
|
||||||
|
|||||||
@@ -400,27 +400,6 @@ test('Enter finishes from the canvas and the label field, never from a focused b
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Enter on the globe canvas finishes, as after the render failure notice hands focus back', async () => {
|
|
||||||
const h = harness();
|
|
||||||
try {
|
|
||||||
h.act('toggleDraw');
|
|
||||||
h.tool.setShape('area');
|
|
||||||
h.tool.addVertex(-97.75, 30.26);
|
|
||||||
h.tool.addVertex(-97.74, 30.26);
|
|
||||||
h.tool.addVertex(-97.74, 30.27);
|
|
||||||
|
|
||||||
// src/ui/views/panels/RenderFailure.vue focuses the canvas when the globe
|
|
||||||
// draws again and the control that had focus is gone.
|
|
||||||
h.documentDouble.emit('keydown', { key: 'Enter', target: h.viewer.scene.canvas });
|
|
||||||
await Promise.resolve();
|
|
||||||
await Promise.resolve();
|
|
||||||
assert.equal(h.annotations.calls.annotate.length, 1, 'Enter on the canvas finishes');
|
|
||||||
assert.equal(h.annotations.calls.annotate[0].specs[0].type, 'area');
|
|
||||||
} finally {
|
|
||||||
h.restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('the live preview hangs at the height the click landed on', async () => {
|
test('the live preview hangs at the height the click landed on', async () => {
|
||||||
const h = harness();
|
const h = harness();
|
||||||
try {
|
try {
|
||||||
|
|||||||
+11
-13
@@ -122,30 +122,28 @@ export function keepPanelRendering(
|
|||||||
frameRequest = windowRef.requestAnimationFrame(onFrame);
|
frameRequest = windowRef.requestAnimationFrame(onFrame);
|
||||||
};
|
};
|
||||||
frameRequest = windowRef.requestAnimationFrame(onFrame);
|
frameRequest = windowRef.requestAnimationFrame(onFrame);
|
||||||
// Cesium stops its render loop after a render error, and the timer stops
|
// Cesium stops drawing after a render error, and so does the timer.
|
||||||
// with it; Retry restarts both. Report the error in full: workers report
|
// Report the error in full: workers report plain objects, which Cesium
|
||||||
// plain objects, which Cesium prints as [object Object].
|
// prints as [object Object].
|
||||||
|
let failed = false;
|
||||||
const removeErrorListener = viewer.scene?.renderError?.addEventListener(
|
const removeErrorListener = viewer.scene?.renderError?.addEventListener(
|
||||||
(_scene, error) =>
|
(_scene, error) => {
|
||||||
|
failed = true;
|
||||||
console.error(
|
console.error(
|
||||||
"[God's Eye View panel] render error:",
|
"[God's Eye View panel] render error:",
|
||||||
describeError(error),
|
describeError(error),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const timer = windowRef.setInterval(() => {
|
const timer = windowRef.setInterval(() => {
|
||||||
if (
|
if (
|
||||||
viewer.useDefaultRenderLoop === false ||
|
failed ||
|
||||||
now() - lastFrame < MISSED_FRAMES_MS ||
|
now() - lastFrame < MISSED_FRAMES_MS ||
|
||||||
viewer.isDestroyed?.()
|
viewer.isDestroyed?.()
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
try {
|
viewer.resize();
|
||||||
viewer.resize();
|
viewer.render();
|
||||||
viewer.render();
|
|
||||||
} catch {
|
|
||||||
// Raised as a render error by now (src/ui/renderFailure.js), which
|
|
||||||
// stops this timer; thrown on, it would only reach the host page.
|
|
||||||
}
|
|
||||||
}, PANEL_FRAME_MS);
|
}, PANEL_FRAME_MS);
|
||||||
return () => {
|
return () => {
|
||||||
windowRef.clearInterval(timer);
|
windowRef.clearInterval(timer);
|
||||||
|
|||||||
@@ -482,8 +482,6 @@ test('a panel reports a render error in full and stops drawing', () => {
|
|||||||
try {
|
try {
|
||||||
let clock = 0;
|
let clock = 0;
|
||||||
const stop = keepPanelRendering(viewer, { windowRef, now: () => clock });
|
const stop = keepPanelRendering(viewer, { windowRef, now: () => clock });
|
||||||
// Cesium stops its render loop after a render error.
|
|
||||||
viewer.useDefaultRenderLoop = false;
|
|
||||||
listener(viewer.scene, { name: 'RuntimeError', message: 'worker failed' });
|
listener(viewer.scene, { name: 'RuntimeError', message: 'worker failed' });
|
||||||
assert.match(logged[0], /render error: .*"message":"worker failed"/);
|
assert.match(logged[0], /render error: .*"message":"worker failed"/);
|
||||||
clock = 1000;
|
clock = 1000;
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
// Retry in an inline panel whose host stops animation frames, so the panel
|
|
||||||
// draws from keepPanelRendering's timer. Drives Cesium's real startRenderLoop
|
|
||||||
// and useDefaultRenderLoop setter (CesiumWidget.prototype).
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { keepPanelRendering } from './embed.js';
|
|
||||||
import { renderLoopFixture } from '../ui/renderLoopFixture.mjs';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let tick = null;
|
|
||||||
let clock = 0;
|
|
||||||
// The host reports the panel hidden: its own animation frames never arrive.
|
|
||||||
const windowRef = {
|
|
||||||
requestAnimationFrame: () => 1,
|
|
||||||
cancelAnimationFrame() {},
|
|
||||||
setInterval: (fn) => ((tick = fn), 1),
|
|
||||||
clearInterval() {},
|
|
||||||
};
|
|
||||||
const previousError = console.error;
|
|
||||||
console.error = () => {};
|
|
||||||
const state = { failure: null, drawn: 0 };
|
|
||||||
const scene = {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
requestRender() {},
|
|
||||||
initializeFrame() {},
|
|
||||||
// Scene.render: a failing primitive update raises renderError
|
|
||||||
// (tryAndCatchError) and returns; a failing listener throws.
|
|
||||||
render() {
|
|
||||||
state.drawn += 1;
|
|
||||||
this.preUpdate.raiseEvent(this);
|
|
||||||
if (state.failure === 'caught')
|
|
||||||
this.renderError.raiseEvent(this, new Error('boom'));
|
|
||||||
if (state.failure === 'thrown') throw new Error('boom');
|
|
||||||
this.postRender.raiseEvent(this);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const { frames, widget, viewer, store, runFrames, restore } =
|
|
||||||
renderLoopFixture(scene, {
|
|
||||||
viewer: {
|
|
||||||
isDestroyed: () => false,
|
|
||||||
render: () => widget.render(),
|
|
||||||
resize: () => widget.resize(),
|
|
||||||
},
|
|
||||||
notice: { allowReload: false },
|
|
||||||
});
|
|
||||||
const stop = keepPanelRendering(viewer, { windowRef, now: () => clock });
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
const timerFrame = () => {
|
|
||||||
clock += 400;
|
|
||||||
tick();
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
frames,
|
|
||||||
state,
|
|
||||||
store,
|
|
||||||
timerFrame,
|
|
||||||
animationFrames: runFrames,
|
|
||||||
restore: () => {
|
|
||||||
stop();
|
|
||||||
restore();
|
|
||||||
console.error = previousError;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const failure of ['caught', 'thrown']) {
|
|
||||||
test(`an error ${failure} in a frame the timer draws: Retry draws again with one render loop`, () => {
|
|
||||||
const { frames, state, store, timerFrame, animationFrames, restore } =
|
|
||||||
setup();
|
|
||||||
try {
|
|
||||||
// The host stops animation frames with the loop's next one queued.
|
|
||||||
assert.equal(frames.length, 1);
|
|
||||||
state.failure = failure;
|
|
||||||
timerFrame(); // an error thrown here does not reach the host page
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
assert.equal(typeof store.renderFailure.retry, 'function');
|
|
||||||
state.failure = null;
|
|
||||||
store.renderFailure.retry();
|
|
||||||
const before = state.drawn;
|
|
||||||
timerFrame();
|
|
||||||
assert.ok(state.drawn > before, 'the panel draws again after Retry');
|
|
||||||
assert.equal(
|
|
||||||
store.renderFailure,
|
|
||||||
null,
|
|
||||||
'a clean frame clears the notice',
|
|
||||||
);
|
|
||||||
assert.equal(frames.length, 1, 'Retry starts no second loop');
|
|
||||||
// The host resumes frames: the globe draws once a frame.
|
|
||||||
state.drawn = 0;
|
|
||||||
animationFrames(3);
|
|
||||||
assert.equal(state.drawn, 3);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('an error Scene caught in an animation frame right before the host stopped frames: Retry keeps one render loop', () => {
|
|
||||||
const { frames, state, store, timerFrame, animationFrames, restore } =
|
|
||||||
setup();
|
|
||||||
try {
|
|
||||||
state.failure = 'caught';
|
|
||||||
animationFrames(1);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
assert.equal(frames.length, 1);
|
|
||||||
state.failure = null;
|
|
||||||
store.renderFailure.retry();
|
|
||||||
timerFrame();
|
|
||||||
assert.equal(store.renderFailure, null);
|
|
||||||
assert.equal(frames.length, 1, 'Retry starts no second loop');
|
|
||||||
state.drawn = 0;
|
|
||||||
animationFrames(3);
|
|
||||||
assert.equal(state.drawn, 3);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -15,8 +15,6 @@ import {
|
|||||||
governorRequestRender,
|
governorRequestRender,
|
||||||
} from '../renderGovernor.js';
|
} from '../renderGovernor.js';
|
||||||
import { describeError } from './errors.js';
|
import { describeError } from './errors.js';
|
||||||
import { isEmbeddedInline } from './embed.js';
|
|
||||||
import { installRenderFailureNotice } from '../ui/renderFailure.js';
|
|
||||||
import { worldClock, worldTimeFor } from '../time/worldClock.js';
|
import { worldClock, worldTimeFor } from '../time/worldClock.js';
|
||||||
import { followWorldTime } from '../time/viewerClock.js';
|
import { followWorldTime } from '../time/viewerClock.js';
|
||||||
import { onLocaleChange, t } from '../i18n/index.js';
|
import { onLocaleChange, t } from '../i18n/index.js';
|
||||||
@@ -57,17 +55,11 @@ export async function createApplicationScene({
|
|||||||
const viewer = createApplicationViewer({
|
const viewer = createApplicationViewer({
|
||||||
container: 'cesiumContainer',
|
container: 'cesiumContainer',
|
||||||
creditContainer,
|
creditContainer,
|
||||||
// The render failure notice below replaces Cesium's dead-end panel.
|
|
||||||
showRenderLoopErrors: false,
|
|
||||||
});
|
});
|
||||||
defer(() => {
|
defer(() => {
|
||||||
uninstallRenderGovernor(viewer);
|
uninstallRenderGovernor(viewer);
|
||||||
if (!viewer.isDestroyed()) viewer.destroy();
|
if (!viewer.isDestroyed()) viewer.destroy();
|
||||||
});
|
});
|
||||||
// An inline panel must not reload or navigate its host's page.
|
|
||||||
defer(
|
|
||||||
installRenderFailureNotice(viewer, { allowReload: !isEmbeddedInline() }),
|
|
||||||
);
|
|
||||||
defer(installTrackpadPinchZoom(viewer));
|
defer(installTrackpadPinchZoom(viewer));
|
||||||
defer(
|
defer(
|
||||||
followWorldTime(viewer, worldTimeFor('sky'), {
|
followWorldTime(viewer, worldTimeFor('sky'), {
|
||||||
|
|||||||
+4
-16
@@ -375,15 +375,8 @@ export function installGlobeInViewRecovery(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Cesium.Viewer options of the standard globe viewer. */
|
||||||
* Cesium.Viewer options of the standard globe viewer. `showRenderLoopErrors`
|
export function applicationViewerOptions(creditContainer) {
|
||||||
* false leaves render errors to the caller's own notice instead of Cesium's
|
|
||||||
* panel.
|
|
||||||
*/
|
|
||||||
export function applicationViewerOptions(
|
|
||||||
creditContainer,
|
|
||||||
{ showRenderLoopErrors = true } = {},
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
timeline: false,
|
timeline: false,
|
||||||
animation: false,
|
animation: false,
|
||||||
@@ -403,21 +396,16 @@ export function applicationViewerOptions(
|
|||||||
creditContainer,
|
creditContainer,
|
||||||
msaaSamples: 4,
|
msaaSamples: 4,
|
||||||
contextOptions: { webgl: { preserveDrawingBuffer: true } },
|
contextOptions: { webgl: { preserveDrawingBuffer: true } },
|
||||||
showRenderLoopErrors,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create the standard globe viewer in caller-owned, visible containers. */
|
/** Create the standard globe viewer in caller-owned, visible containers. */
|
||||||
export function createApplicationViewer({
|
export function createApplicationViewer({ container, creditContainer }) {
|
||||||
container,
|
|
||||||
creditContainer,
|
|
||||||
showRenderLoopErrors,
|
|
||||||
}) {
|
|
||||||
if (!container || !creditContainer)
|
if (!container || !creditContainer)
|
||||||
throw new TypeError('Viewer and credit containers are required');
|
throw new TypeError('Viewer and credit containers are required');
|
||||||
const viewer = new Cesium.Viewer(
|
const viewer = new Cesium.Viewer(
|
||||||
container,
|
container,
|
||||||
applicationViewerOptions(creditContainer, { showRenderLoopErrors }),
|
applicationViewerOptions(creditContainer),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
viewer.targetFrameRate = 60;
|
viewer.targetFrameRate = 60;
|
||||||
|
|||||||
+90
-19
@@ -77,8 +77,22 @@
|
|||||||
"{severity} only": "{severity} فقط",
|
"{severity} only": "{severity} فقط",
|
||||||
"All categories": "كل الفئات",
|
"All categories": "كل الفئات",
|
||||||
"Reading the news…": "جارٍ قراءة الأخبار…",
|
"Reading the news…": "جارٍ قراءة الأخبار…",
|
||||||
"News, last {hours} hours": "الأخبار، الساعات الأخيرة: {hours}",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "آخر {hours} س",
|
"zero": "الأخبار، آخر {hours} ساعة",
|
||||||
|
"one": "الأخبار، آخر ساعة ({hours})",
|
||||||
|
"two": "الأخبار، آخر ساعتين ({hours})",
|
||||||
|
"few": "الأخبار، آخر {hours} ساعات",
|
||||||
|
"many": "الأخبار، آخر {hours} ساعة",
|
||||||
|
"other": "الأخبار، آخر {hours} ساعة"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"zero": "آخر {hours} ساعة",
|
||||||
|
"one": "آخر ساعة ({hours})",
|
||||||
|
"two": "آخر ساعتين ({hours})",
|
||||||
|
"few": "آخر {hours} ساعات",
|
||||||
|
"many": "آخر {hours} ساعة",
|
||||||
|
"other": "آخر {hours} ساعة"
|
||||||
|
},
|
||||||
"Time window": "الفترة الزمنية",
|
"Time window": "الفترة الزمنية",
|
||||||
"Conflicts": "النزاعات",
|
"Conflicts": "النزاعات",
|
||||||
"Local": "محلي",
|
"Local": "محلي",
|
||||||
@@ -87,9 +101,23 @@
|
|||||||
"Hotspots": "بؤر ساخنة",
|
"Hotspots": "بؤر ساخنة",
|
||||||
"Severity": "الخطورة",
|
"Severity": "الخطورة",
|
||||||
"Category": "الفئة",
|
"Category": "الفئة",
|
||||||
"Nothing reported near here in the last {hours} h": "لم يُبلَّغ عن شيء قريب من هنا في آخر {hours} س",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"zero": "لم يُبلَّغ عن شيء قريب من هنا في آخر {hours} ساعة",
|
||||||
|
"one": "لم يُبلَّغ عن شيء قريب من هنا في آخر ساعة ({hours})",
|
||||||
|
"two": "لم يُبلَّغ عن شيء قريب من هنا في آخر ساعتين ({hours})",
|
||||||
|
"few": "لم يُبلَّغ عن شيء قريب من هنا في آخر {hours} ساعات",
|
||||||
|
"many": "لم يُبلَّغ عن شيء قريب من هنا في آخر {hours} ساعة",
|
||||||
|
"other": "لم يُبلَّغ عن شيء قريب من هنا في آخر {hours} ساعة"
|
||||||
|
},
|
||||||
"Latest news": "آخر الأخبار",
|
"Latest news": "آخر الأخبار",
|
||||||
"No news matches these filters in the last {hours} h": "لا أخبار تطابق هذه المرشحات في آخر {hours} س",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"zero": "لا أخبار تطابق هذه المرشحات في آخر {hours} ساعة",
|
||||||
|
"one": "لا أخبار تطابق هذه المرشحات في آخر ساعة ({hours})",
|
||||||
|
"two": "لا أخبار تطابق هذه المرشحات في آخر ساعتين ({hours})",
|
||||||
|
"few": "لا أخبار تطابق هذه المرشحات في آخر {hours} ساعات",
|
||||||
|
"many": "لا أخبار تطابق هذه المرشحات في آخر {hours} ساعة",
|
||||||
|
"other": "لا أخبار تطابق هذه المرشحات في آخر {hours} ساعة"
|
||||||
|
},
|
||||||
"Load older news": "تحميل أخبار أقدم",
|
"Load older news": "تحميل أخبار أقدم",
|
||||||
"How to read the map": "كيف تقرأ الخريطة",
|
"How to read the map": "كيف تقرأ الخريطة",
|
||||||
"Colour: severity · icon: type of event": "اللون: الخطورة · الرمز: نوع الحدث",
|
"Colour: severity · icon: type of event": "اللون: الخطورة · الرمز: نوع الحدث",
|
||||||
@@ -259,7 +287,14 @@
|
|||||||
"ALTITUDE": "الارتفاع",
|
"ALTITUDE": "الارتفاع",
|
||||||
"CONTACT": "الهدف",
|
"CONTACT": "الهدف",
|
||||||
"CONTACTS · 250 KM": "الأهداف · 250 كم",
|
"CONTACTS · 250 KM": "الأهداف · 250 كم",
|
||||||
"RINGS {spacing} NM": "الحلقات كل {spacing} ميل بحري",
|
"RINGS {spacing} NM": {
|
||||||
|
"zero": "الحلقات كل {spacing} ميل بحري",
|
||||||
|
"one": "الحلقات كل ميل بحري ({spacing})",
|
||||||
|
"two": "الحلقات كل ميلين بحريين ({spacing})",
|
||||||
|
"few": "الحلقات كل {spacing} أميال بحرية",
|
||||||
|
"many": "الحلقات كل {spacing} ميلًا بحريًا",
|
||||||
|
"other": "الحلقات كل {spacing} ميل بحري"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"zero": "لا أهداف ({count})",
|
"zero": "لا أهداف ({count})",
|
||||||
"one": "هدف واحد ({count})",
|
"one": "هدف واحد ({count})",
|
||||||
@@ -388,7 +423,14 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "بيانات المسار غير متاحة",
|
"ROUTE DATA UNAVAILABLE": "بيانات المسار غير متاحة",
|
||||||
"CONTACT NAVIGATION OFF": "التنقل بين الأهداف متوقف",
|
"CONTACT NAVIGATION OFF": "التنقل بين الأهداف متوقف",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "تبقى غرفة القيادة مع السفينة المحددة",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "تبقى غرفة القيادة مع السفينة المحددة",
|
||||||
"{range} NM": "{range} ميل بحري",
|
"{range} NM": {
|
||||||
|
"zero": "{range} ميل بحري",
|
||||||
|
"one": "ميل بحري واحد ({range})",
|
||||||
|
"two": "ميلان بحريان ({range})",
|
||||||
|
"few": "{range} أميال بحرية",
|
||||||
|
"many": "{range} ميلًا بحريًا",
|
||||||
|
"other": "{range} ميل بحري"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "وجهة AIS · أبلغ عنها الطاقم",
|
"AIS DESTINATION · CREW REPORTED": "وجهة AIS · أبلغ عنها الطاقم",
|
||||||
"FROM": "من",
|
"FROM": "من",
|
||||||
"TO": "إلى",
|
"TO": "إلى",
|
||||||
@@ -472,6 +514,7 @@
|
|||||||
"Current style: {style} — click for next": "النمط الحالي: {style} — انقر للتالي",
|
"Current style: {style} — click for next": "النمط الحالي: {style} — انقر للتالي",
|
||||||
"TRACK ACQUIRED": "تم التقاط الهدف",
|
"TRACK ACQUIRED": "تم التقاط الهدف",
|
||||||
"{label} · COURSE {heading}°": "{label} · المسار {heading}°",
|
"{label} · COURSE {heading}°": "{label} · المسار {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · المسار {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · الاتجاه {heading}°",
|
"{label} · HEADING {heading}°": "{label} · الاتجاه {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · لا اتجاه",
|
"{label} · NO HEADING": "{label} · لا اتجاه",
|
||||||
"{label} · NADIR VIEW": "{label} · عرض باتجاه النظير",
|
"{label} · NADIR VIEW": "{label} · عرض باتجاه النظير",
|
||||||
@@ -673,6 +716,7 @@
|
|||||||
"NEAREST": "الأقرب",
|
"NEAREST": "الأقرب",
|
||||||
"CCTV camera": "كاميرا CCTV",
|
"CCTV camera": "كاميرا CCTV",
|
||||||
"FOCUS": "تركيز",
|
"FOCUS": "تركيز",
|
||||||
|
"site status|FOCUS": "تركيز",
|
||||||
"COVERAGE OFF": "التغطية متوقفة",
|
"COVERAGE OFF": "التغطية متوقفة",
|
||||||
"AUTO HOP OFF": "التنقل التلقائي متوقف",
|
"AUTO HOP OFF": "التنقل التلقائي متوقف",
|
||||||
"PROJECTION ON": "الإسقاط مفعّل",
|
"PROJECTION ON": "الإسقاط مفعّل",
|
||||||
@@ -1063,6 +1107,7 @@
|
|||||||
"awaiting first AIS message…": "بانتظار أول رسالة AIS…",
|
"awaiting first AIS message…": "بانتظار أول رسالة AIS…",
|
||||||
"EST. DOWNRANGE": "تقدير على امتداد المسار",
|
"EST. DOWNRANGE": "تقدير على امتداد المسار",
|
||||||
"CONFIRMED": "مؤكَّد",
|
"CONFIRMED": "مؤكَّد",
|
||||||
|
"sightings filter|CONFIRMED": "مؤكَّدة",
|
||||||
"PAD / RTLS": "منصة الإطلاق / RTLS",
|
"PAD / RTLS": "منصة الإطلاق / RTLS",
|
||||||
"Pick a shape, then click the map.": "اختر شكلًا، ثم انقر على الخريطة.",
|
"Pick a shape, then click the map.": "اختر شكلًا، ثم انقر على الخريطة.",
|
||||||
"Enter to place the pin, Esc to cancel.": "Enter لوضع الدبوس، وEsc للإلغاء.",
|
"Enter to place the pin, Esc to cancel.": "Enter لوضع الدبوس، وEsc للإلغاء.",
|
||||||
@@ -2544,6 +2589,7 @@
|
|||||||
"Observed history": "سجل الأرصاد",
|
"Observed history": "سجل الأرصاد",
|
||||||
"Later observation": "رصد لاحق",
|
"Later observation": "رصد لاحق",
|
||||||
"Latest": "الأحدث",
|
"Latest": "الأحدث",
|
||||||
|
"news list|Latest": "الأحدث",
|
||||||
"Newest frame per product": "أحدث إطار لكل منتج",
|
"Newest frame per product": "أحدث إطار لكل منتج",
|
||||||
"Replay observed history": "إعادة تشغيل سجل الأرصاد",
|
"Replay observed history": "إعادة تشغيل سجل الأرصاد",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "الصور على Esri · تعود Google 3D عند المسح",
|
"Imagery on Esri · Google 3D returns when cleared": "الصور على Esri · تعود Google 3D عند المسح",
|
||||||
@@ -3716,7 +3762,14 @@
|
|||||||
"Photo of {name}": "صورة {name}",
|
"Photo of {name}": "صورة {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "المشاهدات في نطاق العرض: {count}",
|
"{count} sightings in view": {
|
||||||
|
"zero": "{count} مشاهدة في نطاق العرض",
|
||||||
|
"one": "مشاهدة واحدة ({count}) في نطاق العرض",
|
||||||
|
"two": "مشاهدتان ({count}) في نطاق العرض",
|
||||||
|
"few": "{count} مشاهدات في نطاق العرض",
|
||||||
|
"many": "{count} مشاهدة في نطاق العرض",
|
||||||
|
"other": "{count} مشاهدة في نطاق العرض"
|
||||||
|
},
|
||||||
"Close sighting": "إغلاق المشاهدة",
|
"Close sighting": "إغلاق المشاهدة",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "حياة برية شوهدت خلال 48 ساعة (الحيتان 7 أيام). يوضح اللون ورمز الشارة المجموعة التصنيفية. تُعرض الأنواع الحساسة في خلايا بحجم 0.2° فقط؛ وتُقرّب مشاهدات الثدييات البحرية في بحر ساليش عبر Acartia إلى 0.01°. ليست إحصاءً شاملًا.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "حياة برية شوهدت خلال 48 ساعة (الحيتان 7 أيام). يوضح اللون ورمز الشارة المجموعة التصنيفية. تُعرض الأنواع الحساسة في خلايا بحجم 0.2° فقط؛ وتُقرّب مشاهدات الثدييات البحرية في بحر ساليش عبر Acartia إلى 0.01°. ليست إحصاءً شاملًا.",
|
||||||
"Open {name} on iNaturalist": "فتح {name} على iNaturalist",
|
"Open {name} on iNaturalist": "فتح {name} على iNaturalist",
|
||||||
@@ -3727,20 +3780,37 @@
|
|||||||
"{days}d": "{days} ي",
|
"{days}d": "{days} ي",
|
||||||
"All markers": "كل العلامات",
|
"All markers": "كل العلامات",
|
||||||
"Show markers of every age": "عرض العلامات من جميع الأعمار",
|
"Show markers of every age": "عرض العلامات من جميع الأعمار",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "الحد الأقصى لعمر الحدث: {days} ي؛ تبقى العلامات غير المؤرخة ظاهرة",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"zero": "الحد الأقصى لعمر الحدث: {days} يوم؛ تبقى العلامات غير المؤرخة ظاهرة",
|
||||||
|
"one": "الحد الأقصى لعمر الحدث: يوم واحد ({days})؛ تبقى العلامات غير المؤرخة ظاهرة",
|
||||||
|
"two": "الحد الأقصى لعمر الحدث: يومان ({days})؛ تبقى العلامات غير المؤرخة ظاهرة",
|
||||||
|
"few": "الحد الأقصى لعمر الحدث: {days} أيام؛ تبقى العلامات غير المؤرخة ظاهرة",
|
||||||
|
"many": "الحد الأقصى لعمر الحدث: {days} يومًا؛ تبقى العلامات غير المؤرخة ظاهرة",
|
||||||
|
"other": "الحد الأقصى لعمر الحدث: {days} يوم؛ تبقى العلامات غير المؤرخة ظاهرة"
|
||||||
|
},
|
||||||
"event age filter|All": "الكل",
|
"event age filter|All": "الكل",
|
||||||
"Event age": "عمر الحدث",
|
"Event age": "عمر الحدث",
|
||||||
"Event age: {days}d · undated kept": "عمر الحدث: {days} ي · غير المؤرخة تبقى ظاهرة",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"zero": "عمر الحدث: {days} يوم · غير المؤرخة تبقى ظاهرة",
|
||||||
|
"one": "عمر الحدث: يوم واحد ({days}) · غير المؤرخة تبقى ظاهرة",
|
||||||
|
"two": "عمر الحدث: يومان ({days}) · غير المؤرخة تبقى ظاهرة",
|
||||||
|
"few": "عمر الحدث: {days} أيام · غير المؤرخة تبقى ظاهرة",
|
||||||
|
"many": "عمر الحدث: {days} يومًا · غير المؤرخة تبقى ظاهرة",
|
||||||
|
"other": "عمر الحدث: {days} يوم · غير المؤرخة تبقى ظاهرة"
|
||||||
|
},
|
||||||
"Close camera preview": "إغلاق معاينة الكاميرا",
|
"Close camera preview": "إغلاق معاينة الكاميرا",
|
||||||
"Refresh map still": "تحديث صورة الخريطة الثابتة",
|
"Refresh map still": "تحديث صورة الخريطة الثابتة",
|
||||||
"The map still uses a separate request and may show a different moment.": "تستخدم صورة الخريطة طلبًا منفصلًا وقد تعرض لحظة مختلفة.",
|
"The map still uses a separate request and may show a different moment.": "تستخدم صورة الخريطة طلبًا منفصلًا وقد تعرض لحظة مختلفة.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "يُشغَّل الفيديو من Québec 511، الذي يرى عنوان IP الخاص بك.",
|
"Video plays from Québec 511, which sees your IP address.": "يُشغَّل الفيديو من Québec 511، الذي يرى عنوان IP الخاص بك.",
|
||||||
"Press OPEN to load the photo": "اضغط «فتح» لتحميل الصورة",
|
"Press OPEN to load the photo": "اضغط «فتح» لتحميل الصورة",
|
||||||
"Captured {month}": "التُقطت في {month}",
|
"Captured {month}": "التُقطت في {month}",
|
||||||
"this spot": "هذا الموضع",
|
|
||||||
"the view centre": "مركز العرض",
|
"the view centre": "مركز العرض",
|
||||||
"{source} at {place}": "{source} في {place}",
|
"{source} at {place}": "{source} في {place}",
|
||||||
|
"{source} at this spot": "{source} في هذا الموضع",
|
||||||
|
"{source} at the view centre": "{source} في مركز العرض",
|
||||||
"Street level at {place}": "مستوى الشارع في {place}",
|
"Street level at {place}": "مستوى الشارع في {place}",
|
||||||
|
"Street level at this spot": "مستوى الشارع في هذا الموضع",
|
||||||
|
"Street level at the view centre": "مستوى الشارع في مركز العرض",
|
||||||
"Switching map": "جارٍ تبديل الخريطة",
|
"Switching map": "جارٍ تبديل الخريطة",
|
||||||
"Opening imagery": "جارٍ فتح الصور",
|
"Opening imagery": "جارٍ فتح الصور",
|
||||||
"Street View is ready: press {open} to load it": "Street View جاهز: اضغط «{open}» لتحميله",
|
"Street View is ready: press {open} to load it": "Street View جاهز: اضغط «{open}» لتحميله",
|
||||||
@@ -3832,6 +3902,7 @@
|
|||||||
"{source} imagery could not be opened": "تعذّر فتح صور {source}",
|
"{source} imagery could not be opened": "تعذّر فتح صور {source}",
|
||||||
"{place} has no position to look for imagery at": "{place}: لا موضع للبحث عن الصور فيه",
|
"{place} has no position to look for imagery at": "{place}: لا موضع للبحث عن الصور فيه",
|
||||||
"No {source} imagery near {place}": "لا توجد صور {source} قرب {place}",
|
"No {source} imagery near {place}": "لا توجد صور {source} قرب {place}",
|
||||||
|
"No {source} imagery near the view centre": "لا توجد صور {source} قرب مركز العرض",
|
||||||
"{reason} (filters on: {filters})": "{reason} (المرشحات المفعّلة: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (المرشحات المفعّلة: {filters})",
|
||||||
"tiles unavailable": "البلاطات غير متاحة",
|
"tiles unavailable": "البلاطات غير متاحة",
|
||||||
"zoom in to load this layer": "قرّب لتحميل هذه الطبقة",
|
"zoom in to load this layer": "قرّب لتحميل هذه الطبقة",
|
||||||
@@ -3952,13 +4023,13 @@
|
|||||||
"REMOVE ALL": "إزالة الكل",
|
"REMOVE ALL": "إزالة الكل",
|
||||||
"Confirm: remove every added entry from Data Layers": "تأكيد: إزالة كل الإدخالات المُضافة من طبقات البيانات",
|
"Confirm: remove every added entry from Data Layers": "تأكيد: إزالة كل الإدخالات المُضافة من طبقات البيانات",
|
||||||
"Remove every added entry from Data Layers": "إزالة كل الإدخالات المُضافة من طبقات البيانات",
|
"Remove every added entry from Data Layers": "إزالة كل الإدخالات المُضافة من طبقات البيانات",
|
||||||
"Showing {shown} of {total}": "عرض {shown} من {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "تصفّح مكتبة البيانات",
|
"zero": "عرض {shown} من {total}",
|
||||||
"The globe stopped drawing": "توقفت الكرة الأرضية عن الرسم",
|
"one": "عرض {shown} من {total}",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "استعاد المتصفح رسومات الكرة الأرضية ثلاثية الأبعاد. لا تزال الطبقات وعناصر التحكم تعمل، لكن الخريطة لن تُرسم حتى تُعاد تحميل الصفحة.",
|
"two": "عرض {shown} من {total}",
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "توقف العرض بعد خطأ. لا تزال الطبقات وعناصر التحكم تعمل، لكن الخريطة لا يتم تحديثها.",
|
"few": "عرض {shown} من {total}",
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "تعيد «إعادة ضبط العرض» تحميل التطبيق دون العرض المستعاد من شريط العنوان.",
|
"many": "عرض {shown} من {total}",
|
||||||
"Reload": "إعادة التحميل",
|
"other": "عرض {shown} من {total}"
|
||||||
"Retry": "إعادة المحاولة",
|
},
|
||||||
"Reset view": "إعادة ضبط العرض"
|
"BROWSE DATA LIBRARY": "تصفّح مكتبة البيانات"
|
||||||
}
|
}
|
||||||
+51
-20
@@ -65,8 +65,14 @@
|
|||||||
"{severity} only": "Nur {severity}",
|
"{severity} only": "Nur {severity}",
|
||||||
"All categories": "Alle Kategorien",
|
"All categories": "Alle Kategorien",
|
||||||
"Reading the news…": "Nachrichten werden gelesen…",
|
"Reading the news…": "Nachrichten werden gelesen…",
|
||||||
"News, last {hours} hours": "Nachrichten, letzte {hours} h",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "Letzte {hours} h",
|
"one": "Nachrichten der letzten {hours} Stunde",
|
||||||
|
"other": "Nachrichten der letzten {hours} Stunden"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "Letzte {hours} h",
|
||||||
|
"other": "Letzte {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Zeitraum",
|
"Time window": "Zeitraum",
|
||||||
"Conflicts": "Konflikte",
|
"Conflicts": "Konflikte",
|
||||||
"Local": "Lokal",
|
"Local": "Lokal",
|
||||||
@@ -75,9 +81,15 @@
|
|||||||
"Hotspots": "Brennpunkte",
|
"Hotspots": "Brennpunkte",
|
||||||
"Severity": "Schweregrad",
|
"Severity": "Schweregrad",
|
||||||
"Category": "Kategorie",
|
"Category": "Kategorie",
|
||||||
"Nothing reported near here in the last {hours} h": "In den letzten {hours} h nichts in der Nähe gemeldet",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "In der letzten {hours} h nichts in der Nähe gemeldet",
|
||||||
|
"other": "In den letzten {hours} h nichts in der Nähe gemeldet"
|
||||||
|
},
|
||||||
"Latest news": "Neueste Nachrichten",
|
"Latest news": "Neueste Nachrichten",
|
||||||
"No news matches these filters in the last {hours} h": "Keine Nachrichten der letzten {hours} h passen zu diesen Filtern",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "Keine Nachrichten der letzten {hours} h passen zu diesen Filtern",
|
||||||
|
"other": "Keine Nachrichten der letzten {hours} h passen zu diesen Filtern"
|
||||||
|
},
|
||||||
"Load older news": "Ältere Nachrichten laden",
|
"Load older news": "Ältere Nachrichten laden",
|
||||||
"How to read the map": "So liest man die Karte",
|
"How to read the map": "So liest man die Karte",
|
||||||
"Colour: severity · icon: type of event": "Farbe: Schweregrad · Symbol: Art des Ereignisses",
|
"Colour: severity · icon: type of event": "Farbe: Schweregrad · Symbol: Art des Ereignisses",
|
||||||
@@ -198,6 +210,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Zurück: zuvor besuchter Kontakt im 250-km-Fenster",
|
"Previous — prior visited contact in the 250 km window": "Zurück: zuvor besuchter Kontakt im 250-km-Fenster",
|
||||||
"PREVIOUS": "ZURÜCK",
|
"PREVIOUS": "ZURÜCK",
|
||||||
"FOCUS": "FOKUS",
|
"FOCUS": "FOKUS",
|
||||||
|
"site status|FOCUS": "FOKUS",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Weiter: nächstgelegener unbesuchter Kontakt im 250-km-Fenster",
|
"Next — nearest unvisited contact in the 250 km window": "Weiter: nächstgelegener unbesuchter Kontakt im 250-km-Fenster",
|
||||||
"NEXT": "WEITER",
|
"NEXT": "WEITER",
|
||||||
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Kontext aus offenen Quellen, kartiert/beobachtet. Fehlende Funksignale, nicht geladene Kartenbereiche oder nicht kartierte Standorte sind kein Beleg für Abwesenheit.",
|
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Kontext aus offenen Quellen, kartiert/beobachtet. Fehlende Funksignale, nicht geladene Kartenbereiche oder nicht kartierte Standorte sind kein Beleg für Abwesenheit.",
|
||||||
@@ -302,7 +315,7 @@
|
|||||||
"ORBITAL VELOCITY": "BAHNGESCHWINDIGKEIT",
|
"ORBITAL VELOCITY": "BAHNGESCHWINDIGKEIT",
|
||||||
"SUB-SATELLITE POINT": "SUBSATELLITENPUNKT",
|
"SUB-SATELLITE POINT": "SUBSATELLITENPUNKT",
|
||||||
"NORTH UP": "NORDEN OBEN",
|
"NORTH UP": "NORDEN OBEN",
|
||||||
"CURRENTLY OVER": "AKTUELL ÜBER",
|
"CURRENTLY OVER": "UNTER DEM SATELLITEN",
|
||||||
"{division}, {country}": "{division}, {country}",
|
"{division}, {country}": "{division}, {country}",
|
||||||
"{region} · near {place}": "{region} · nahe {place}",
|
"{region} · near {place}": "{region} · nahe {place}",
|
||||||
"NORAD {id}": "NORAD {id}",
|
"NORAD {id}": "NORAD {id}",
|
||||||
@@ -559,7 +572,10 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "ROUTENDATEN NICHT VERFÜGBAR",
|
"ROUTE DATA UNAVAILABLE": "ROUTENDATEN NICHT VERFÜGBAR",
|
||||||
"CONTACT NAVIGATION OFF": "KONTAKTNAVIGATION AUS",
|
"CONTACT NAVIGATION OFF": "KONTAKTNAVIGATION AUS",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "DIE BRÜCKE BLEIBT BEIM GEWÄHLTEN SCHIFF",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "DIE BRÜCKE BLEIBT BEIM GEWÄHLTEN SCHIFF",
|
||||||
"{range} NM": "{range} SM",
|
"{range} NM": {
|
||||||
|
"one": "{range} SM",
|
||||||
|
"other": "{range} SM"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "AIS-ZIEL · VON DER BESATZUNG GEMELDET",
|
"AIS DESTINATION · CREW REPORTED": "AIS-ZIEL · VON DER BESATZUNG GEMELDET",
|
||||||
"DEST {bearing}": "DEST {bearing}",
|
"DEST {bearing}": "DEST {bearing}",
|
||||||
"Disable cockpit weather effects": "Cockpit-Wettereffekte deaktivieren",
|
"Disable cockpit weather effects": "Cockpit-Wettereffekte deaktivieren",
|
||||||
@@ -605,6 +621,7 @@
|
|||||||
"Reclassify as TR-3B": "Als TR-3B umklassifizieren",
|
"Reclassify as TR-3B": "Als TR-3B umklassifizieren",
|
||||||
"TRACK ACQUIRED": "TRACK ERFASST",
|
"TRACK ACQUIRED": "TRACK ERFASST",
|
||||||
"{label} · COURSE {heading}°": "{label} · KURS {heading}°",
|
"{label} · COURSE {heading}°": "{label} · KURS {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · KURS {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · STEUERKURS {heading}°",
|
"{label} · HEADING {heading}°": "{label} · STEUERKURS {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · KEIN STEUERKURS",
|
"{label} · NO HEADING": "{label} · KEIN STEUERKURS",
|
||||||
"{label} · NADIR VIEW": "{label} · NADIRANSICHT",
|
"{label} · NADIR VIEW": "{label} · NADIRANSICHT",
|
||||||
@@ -836,7 +853,10 @@
|
|||||||
"Contact cockpit summary": "Cockpit-Zusammenfassung des Kontakts",
|
"Contact cockpit summary": "Cockpit-Zusammenfassung des Kontakts",
|
||||||
"CONTACT": "KONTAKT",
|
"CONTACT": "KONTAKT",
|
||||||
"CONTACTS · 250 KM": "KONTAKTE · 250 KM",
|
"CONTACTS · 250 KM": "KONTAKTE · 250 KM",
|
||||||
"RINGS {spacing} NM": "RINGE {spacing} SM",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "RINGE {spacing} SM",
|
||||||
|
"other": "RINGE {spacing} SM"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} KONTAKT",
|
"one": "{count} KONTAKT",
|
||||||
"other": "{count} KONTAKTE"
|
"other": "{count} KONTAKTE"
|
||||||
@@ -2404,6 +2424,7 @@
|
|||||||
"Observed history": "Beobachteter Verlauf",
|
"Observed history": "Beobachteter Verlauf",
|
||||||
"Later observation": "Spätere Beobachtung",
|
"Later observation": "Spätere Beobachtung",
|
||||||
"Latest": "Neueste",
|
"Latest": "Neueste",
|
||||||
|
"news list|Latest": "Neueste",
|
||||||
"Newest frame per product": "Neuester Frame je Produkt",
|
"Newest frame per product": "Neuester Frame je Produkt",
|
||||||
"Replay observed history": "Beobachteten Verlauf abspielen",
|
"Replay observed history": "Beobachteten Verlauf abspielen",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Bilder auf Esri · nach LÖSCHEN wieder Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "Bilder auf Esri · nach LÖSCHEN wieder Google 3D",
|
||||||
@@ -3132,6 +3153,7 @@
|
|||||||
"Left panels": "Linke Bereiche",
|
"Left panels": "Linke Bereiche",
|
||||||
"Right panels": "Rechte Bereiche",
|
"Right panels": "Rechte Bereiche",
|
||||||
"CONFIRMED": "BESTÄTIGT",
|
"CONFIRMED": "BESTÄTIGT",
|
||||||
|
"sightings filter|CONFIRMED": "BESTÄTIGT",
|
||||||
"PAD / RTLS": "STARTPLATZ / RTLS",
|
"PAD / RTLS": "STARTPLATZ / RTLS",
|
||||||
"fire confidence filter|All": "Alle",
|
"fire confidence filter|All": "Alle",
|
||||||
"fire confidence filter|Nominal+": "Mittel+",
|
"fire confidence filter|Nominal+": "Mittel+",
|
||||||
@@ -3392,7 +3414,10 @@
|
|||||||
"Photo of {name}": "Foto von {name}",
|
"Photo of {name}": "Foto von {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Beobachtungen im Bild: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} Beobachtung im Bild",
|
||||||
|
"other": "{count} Beobachtungen im Bild"
|
||||||
|
},
|
||||||
"Close sighting": "Beobachtung schließen",
|
"Close sighting": "Beobachtung schließen",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "In den letzten 48 h beobachtete Wildtiere (Wale 7 Tage). Farbe und Symbol der Markierung zeigen die Artengruppe. Empfindliche Arten erscheinen nur als 0,2°-Zellen; Acartia-Meldungen zu Meeressäugern aus der Salish-See werden auf 0,01° gerundet. Keine Bestandszählung.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "In den letzten 48 h beobachtete Wildtiere (Wale 7 Tage). Farbe und Symbol der Markierung zeigen die Artengruppe. Empfindliche Arten erscheinen nur als 0,2°-Zellen; Acartia-Meldungen zu Meeressäugern aus der Salish-See werden auf 0,01° gerundet. Keine Bestandszählung.",
|
||||||
"Open {name} on iNaturalist": "{name} auf iNaturalist öffnen",
|
"Open {name} on iNaturalist": "{name} auf iNaturalist öffnen",
|
||||||
@@ -3403,20 +3428,29 @@
|
|||||||
"{days}d": "{days} T",
|
"{days}d": "{days} T",
|
||||||
"All markers": "Alle Markierungen",
|
"All markers": "Alle Markierungen",
|
||||||
"Show markers of every age": "Markierungen jeden Alters anzeigen",
|
"Show markers of every age": "Markierungen jeden Alters anzeigen",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Maximales Ereignisalter: {days} T; undatierte Markierungen bleiben erhalten",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Maximales Ereignisalter: {days} Tag; undatierte Markierungen bleiben erhalten",
|
||||||
|
"other": "Maximales Ereignisalter: {days} Tage; undatierte Markierungen bleiben erhalten"
|
||||||
|
},
|
||||||
"event age filter|All": "Alle",
|
"event age filter|All": "Alle",
|
||||||
"Event age": "Ereignisalter",
|
"Event age": "Ereignisalter",
|
||||||
"Event age: {days}d · undated kept": "Ereignisalter: {days} T · undatierte bleiben",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Ereignisalter: {days} Tag · undatierte bleiben",
|
||||||
|
"other": "Ereignisalter: {days} Tage · undatierte bleiben"
|
||||||
|
},
|
||||||
"Close camera preview": "Kameravorschau schließen",
|
"Close camera preview": "Kameravorschau schließen",
|
||||||
"Refresh map still": "Kartenstandbild aktualisieren",
|
"Refresh map still": "Kartenstandbild aktualisieren",
|
||||||
"The map still uses a separate request and may show a different moment.": "Das Kartenstandbild wird separat abgerufen und kann einen anderen Moment zeigen.",
|
"The map still uses a separate request and may show a different moment.": "Das Kartenstandbild wird separat abgerufen und kann einen anderen Moment zeigen.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "Das Video wird von Québec 511 abgespielt; die IP-Adresse ist für diesen Anbieter sichtbar.",
|
"Video plays from Québec 511, which sees your IP address.": "Das Video wird von Québec 511 abgespielt; die IP-Adresse ist für diesen Anbieter sichtbar.",
|
||||||
"Press OPEN to load the photo": "Auf ÖFFNEN klicken, um das Foto zu laden",
|
"Press OPEN to load the photo": "Auf ÖFFNEN klicken, um das Foto zu laden",
|
||||||
"Captured {month}": "Aufgenommen im {month}",
|
"Captured {month}": "Aufgenommen im {month}",
|
||||||
"this spot": "diese Stelle",
|
|
||||||
"the view centre": "die Bildmitte",
|
"the view centre": "die Bildmitte",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} an dieser Stelle",
|
||||||
|
"{source} at the view centre": "{source} in der Bildmitte",
|
||||||
"Street level at {place}": "Straßenebene: {place}",
|
"Street level at {place}": "Straßenebene: {place}",
|
||||||
|
"Street level at this spot": "Straßenebene an dieser Stelle",
|
||||||
|
"Street level at the view centre": "Straßenebene in der Bildmitte",
|
||||||
"Switching map": "Karte wird gewechselt",
|
"Switching map": "Karte wird gewechselt",
|
||||||
"Opening imagery": "Bilder werden geöffnet",
|
"Opening imagery": "Bilder werden geöffnet",
|
||||||
"Street View is ready: press {open} to load it": "Street View ist bereit: Auf {open} klicken, um es zu laden",
|
"Street View is ready: press {open} to load it": "Street View ist bereit: Auf {open} klicken, um es zu laden",
|
||||||
@@ -3508,6 +3542,7 @@
|
|||||||
"{source} imagery could not be opened": "Bilder von {source} konnten nicht geöffnet werden",
|
"{source} imagery could not be opened": "Bilder von {source} konnten nicht geöffnet werden",
|
||||||
"{place} has no position to look for imagery at": "{place} hat keine Position, an der nach Bildern gesucht werden kann",
|
"{place} has no position to look for imagery at": "{place} hat keine Position, an der nach Bildern gesucht werden kann",
|
||||||
"No {source} imagery near {place}": "Keine Bilder von {source} in der Nähe von {place}",
|
"No {source} imagery near {place}": "Keine Bilder von {source} in der Nähe von {place}",
|
||||||
|
"No {source} imagery near the view centre": "Keine Bilder von {source} in der Nähe der Bildmitte",
|
||||||
"{reason} (filters on: {filters})": "{reason} (aktive Filter: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (aktive Filter: {filters})",
|
||||||
"tiles unavailable": "Kacheln nicht verfügbar",
|
"tiles unavailable": "Kacheln nicht verfügbar",
|
||||||
"zoom in to load this layer": "hineinzoomen, um diese Ebene zu laden",
|
"zoom in to load this layer": "hineinzoomen, um diese Ebene zu laden",
|
||||||
@@ -3604,13 +3639,9 @@
|
|||||||
"REMOVE ALL": "ALLE ENTFERNEN",
|
"REMOVE ALL": "ALLE ENTFERNEN",
|
||||||
"Confirm: remove every added entry from Data Layers": "Bestätigen: alle hinzugefügten Einträge aus Datenebenen entfernen",
|
"Confirm: remove every added entry from Data Layers": "Bestätigen: alle hinzugefügten Einträge aus Datenebenen entfernen",
|
||||||
"Remove every added entry from Data Layers": "Alle hinzugefügten Einträge aus Datenebenen entfernen",
|
"Remove every added entry from Data Layers": "Alle hinzugefügten Einträge aus Datenebenen entfernen",
|
||||||
"Showing {shown} of {total}": "{shown} von {total} angezeigt",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "DATENBIBLIOTHEK DURCHSUCHEN",
|
"one": "{shown} von {total} angezeigt",
|
||||||
"The globe stopped drawing": "Der Globus zeichnet nicht mehr",
|
"other": "{shown} von {total} angezeigt"
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Der Browser hat dem Globus die 3D-Grafik entzogen. Ebenen und Bedienelemente funktionieren weiter, aber die Karte kann erst nach dem Neuladen der Seite wieder zeichnen.",
|
},
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Das Rendern wurde nach einem Fehler angehalten. Ebenen und Bedienelemente funktionieren weiter, aber die Karte wird nicht aktualisiert.",
|
"BROWSE DATA LIBRARY": "DATENBIBLIOTHEK DURCHSUCHEN"
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "Ansicht zurücksetzen lädt die App ohne die aus der Adressleiste wiederhergestellte Ansicht neu.",
|
|
||||||
"Reload": "Neu laden",
|
|
||||||
"Retry": "Erneut versuchen",
|
|
||||||
"Reset view": "Ansicht zurücksetzen"
|
|
||||||
}
|
}
|
||||||
+60
-19
@@ -68,8 +68,16 @@
|
|||||||
"{severity} only": "Solo {severity}",
|
"{severity} only": "Solo {severity}",
|
||||||
"All categories": "Todas las categorías",
|
"All categories": "Todas las categorías",
|
||||||
"Reading the news…": "Leyendo las noticias…",
|
"Reading the news…": "Leyendo las noticias…",
|
||||||
"News, last {hours} hours": "Noticias desde hace {hours} h",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "Desde hace {hours} h",
|
"one": "Noticias de la última {hours} hora",
|
||||||
|
"many": "Noticias de las últimas {hours} de horas",
|
||||||
|
"other": "Noticias de las últimas {hours} horas"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "Última {hours} h",
|
||||||
|
"many": "Últimas {hours} h",
|
||||||
|
"other": "Últimas {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Periodo",
|
"Time window": "Periodo",
|
||||||
"Conflicts": "Conflictos",
|
"Conflicts": "Conflictos",
|
||||||
"Local": "Local",
|
"Local": "Local",
|
||||||
@@ -78,9 +86,17 @@
|
|||||||
"Hotspots": "Focos",
|
"Hotspots": "Focos",
|
||||||
"Severity": "Gravedad",
|
"Severity": "Gravedad",
|
||||||
"Category": "Categoría",
|
"Category": "Categoría",
|
||||||
"Nothing reported near here in the last {hours} h": "No hay noticias cerca de aquí desde hace {hours} h",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "No hay noticias cerca de aquí en la última {hours} h",
|
||||||
|
"many": "No hay noticias cerca de aquí en las últimas {hours} h",
|
||||||
|
"other": "No hay noticias cerca de aquí en las últimas {hours} h"
|
||||||
|
},
|
||||||
"Latest news": "Últimas noticias",
|
"Latest news": "Últimas noticias",
|
||||||
"No news matches these filters in the last {hours} h": "Desde hace {hours} h no hay noticias que coincidan con estos filtros",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "En la última {hours} h no hay noticias que coincidan con estos filtros",
|
||||||
|
"many": "En las últimas {hours} h no hay noticias que coincidan con estos filtros",
|
||||||
|
"other": "En las últimas {hours} h no hay noticias que coincidan con estos filtros"
|
||||||
|
},
|
||||||
"Load older news": "Cargar noticias anteriores",
|
"Load older news": "Cargar noticias anteriores",
|
||||||
"How to read the map": "Cómo leer el mapa",
|
"How to read the map": "Cómo leer el mapa",
|
||||||
"Colour: severity · icon: type of event": "Color: gravedad · icono: tipo de suceso",
|
"Colour: severity · icon: type of event": "Color: gravedad · icono: tipo de suceso",
|
||||||
@@ -139,7 +155,11 @@
|
|||||||
"ALTITUDE": "ALTITUD",
|
"ALTITUDE": "ALTITUD",
|
||||||
"CONTACT": "CONTACTO",
|
"CONTACT": "CONTACTO",
|
||||||
"CONTACTS · 250 KM": "CONTACTOS · 250 KM",
|
"CONTACTS · 250 KM": "CONTACTOS · 250 KM",
|
||||||
"RINGS {spacing} NM": "ANILLOS {spacing} MN",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "ANILLOS {spacing} MN",
|
||||||
|
"many": "ANILLOS {spacing} MN",
|
||||||
|
"other": "ANILLOS {spacing} MN"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} CONTACTO",
|
"one": "{count} CONTACTO",
|
||||||
"many": "{count} DE CONTACTOS",
|
"many": "{count} DE CONTACTOS",
|
||||||
@@ -266,7 +286,11 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "DATOS DE RUTA NO DISPONIBLES",
|
"ROUTE DATA UNAVAILABLE": "DATOS DE RUTA NO DISPONIBLES",
|
||||||
"CONTACT NAVIGATION OFF": "NAVEGACIÓN DE CONTACTOS DESACTIVADA",
|
"CONTACT NAVIGATION OFF": "NAVEGACIÓN DE CONTACTOS DESACTIVADA",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "EL PUENTE PERMANECE EN EL BUQUE SELECCIONADO",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "EL PUENTE PERMANECE EN EL BUQUE SELECCIONADO",
|
||||||
"{range} NM": "{range} MN",
|
"{range} NM": {
|
||||||
|
"one": "{range} MN",
|
||||||
|
"many": "{range} MN",
|
||||||
|
"other": "{range} MN"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "DESTINO AIS · NOTIFICADO POR LA TRIPULACIÓN",
|
"AIS DESTINATION · CREW REPORTED": "DESTINO AIS · NOTIFICADO POR LA TRIPULACIÓN",
|
||||||
"FROM": "DESDE",
|
"FROM": "DESDE",
|
||||||
"TO": "HACIA",
|
"TO": "HACIA",
|
||||||
@@ -352,6 +376,7 @@
|
|||||||
"Current style: {style} — click for next": "Estilo actual: {style} — haz clic para el siguiente",
|
"Current style: {style} — click for next": "Estilo actual: {style} — haz clic para el siguiente",
|
||||||
"TRACK ACQUIRED": "RASTREO ADQUIRIDO",
|
"TRACK ACQUIRED": "RASTREO ADQUIRIDO",
|
||||||
"{label} · COURSE {heading}°": "{label} · RUMBO {heading}°",
|
"{label} · COURSE {heading}°": "{label} · RUMBO {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · RUMBO {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · PROA {heading}°",
|
"{label} · HEADING {heading}°": "{label} · PROA {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · PROA N/D",
|
"{label} · NO HEADING": "{label} · PROA N/D",
|
||||||
"{label} · NADIR VIEW": "{label} · VISTA AL NADIR",
|
"{label} · NADIR VIEW": "{label} · VISTA AL NADIR",
|
||||||
@@ -546,6 +571,7 @@
|
|||||||
"NEAREST": "MÁS CERCANA",
|
"NEAREST": "MÁS CERCANA",
|
||||||
"CCTV camera": "Cámara CCTV",
|
"CCTV camera": "Cámara CCTV",
|
||||||
"FOCUS": "ENFOCAR",
|
"FOCUS": "ENFOCAR",
|
||||||
|
"site status|FOCUS": "ENFOCADO",
|
||||||
"COVERAGE OFF": "COBERTURA DESACTIVADA",
|
"COVERAGE OFF": "COBERTURA DESACTIVADA",
|
||||||
"AUTO HOP OFF": "SALTO AUTO: NO",
|
"AUTO HOP OFF": "SALTO AUTO: NO",
|
||||||
"PROJECTION ON": "PROYECCIÓN ACTIVADA",
|
"PROJECTION ON": "PROYECCIÓN ACTIVADA",
|
||||||
@@ -899,6 +925,7 @@
|
|||||||
"awaiting first AIS message…": "esperando el primer mensaje AIS…",
|
"awaiting first AIS message…": "esperando el primer mensaje AIS…",
|
||||||
"EST. DOWNRANGE": "DIST. ESTIMADA",
|
"EST. DOWNRANGE": "DIST. ESTIMADA",
|
||||||
"CONFIRMED": "CONFIRMADA",
|
"CONFIRMED": "CONFIRMADA",
|
||||||
|
"sightings filter|CONFIRMED": "CONFIRMADAS",
|
||||||
"PAD / RTLS": "PLATAFORMA / RTLS",
|
"PAD / RTLS": "PLATAFORMA / RTLS",
|
||||||
"MISSION CONTROL · FIRST LAUNCH": "CENTRO DE MANDO · PRIMER INICIO",
|
"MISSION CONTROL · FIRST LAUNCH": "CENTRO DE MANDO · PRIMER INICIO",
|
||||||
"Choose your first view": "Elige tu primera vista",
|
"Choose your first view": "Elige tu primera vista",
|
||||||
@@ -1669,6 +1696,7 @@
|
|||||||
"Observed history": "Historial observado",
|
"Observed history": "Historial observado",
|
||||||
"Later observation": "Observación posterior",
|
"Later observation": "Observación posterior",
|
||||||
"Latest": "Último",
|
"Latest": "Último",
|
||||||
|
"news list|Latest": "Últimas",
|
||||||
"Newest frame per product": "Fotograma más reciente por producto",
|
"Newest frame per product": "Fotograma más reciente por producto",
|
||||||
"Replay observed history": "Reproducir el historial observado",
|
"Replay observed history": "Reproducir el historial observado",
|
||||||
"S on another day replaces it · × unpins": "S en otro día lo reemplaza · × quita la fijación",
|
"S on another day replaces it · × unpins": "S en otro día lo reemplaza · × quita la fijación",
|
||||||
@@ -3473,7 +3501,11 @@
|
|||||||
"Photo of {name}": "Foto de {name}",
|
"Photo of {name}": "Foto de {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Avistamientos a la vista: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} avistamiento a la vista",
|
||||||
|
"many": "{count} de avistamientos a la vista",
|
||||||
|
"other": "{count} avistamientos a la vista"
|
||||||
|
},
|
||||||
"Close sighting": "Cerrar avistamiento",
|
"Close sighting": "Cerrar avistamiento",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Fauna y flora observadas en las últimas 48 h (cetáceos 7 días). El color y el símbolo de la insignia indican el grupo taxonómico. Las especies sensibles solo se muestran en celdas de 0,2°; los informes de mamíferos marinos de Acartia en el mar de Salish se redondean a 0,01°. No es un censo.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Fauna y flora observadas en las últimas 48 h (cetáceos 7 días). El color y el símbolo de la insignia indican el grupo taxonómico. Las especies sensibles solo se muestran en celdas de 0,2°; los informes de mamíferos marinos de Acartia en el mar de Salish se redondean a 0,01°. No es un censo.",
|
||||||
"Open {name} on iNaturalist": "Abrir {name} en iNaturalist",
|
"Open {name} on iNaturalist": "Abrir {name} en iNaturalist",
|
||||||
@@ -3484,20 +3516,31 @@
|
|||||||
"{days}d": "{days} d",
|
"{days}d": "{days} d",
|
||||||
"All markers": "Todos los marcadores",
|
"All markers": "Todos los marcadores",
|
||||||
"Show markers of every age": "Mostrar marcadores de cualquier antigüedad",
|
"Show markers of every age": "Mostrar marcadores de cualquier antigüedad",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Antigüedad máxima: {days} d; se conservan los marcadores sin fecha",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Antigüedad máxima: {days} día; se conservan los marcadores sin fecha",
|
||||||
|
"many": "Antigüedad máxima: {days} de días; se conservan los marcadores sin fecha",
|
||||||
|
"other": "Antigüedad máxima: {days} días; se conservan los marcadores sin fecha"
|
||||||
|
},
|
||||||
"event age filter|All": "Todos",
|
"event age filter|All": "Todos",
|
||||||
"Event age": "Antigüedad",
|
"Event age": "Antigüedad",
|
||||||
"Event age: {days}d · undated kept": "Antigüedad: {days} d · se conservan los marcadores sin fecha",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Antigüedad: {days} día · se conservan los marcadores sin fecha",
|
||||||
|
"many": "Antigüedad: {days} de días · se conservan los marcadores sin fecha",
|
||||||
|
"other": "Antigüedad: {days} días · se conservan los marcadores sin fecha"
|
||||||
|
},
|
||||||
"Close camera preview": "Cerrar vista previa de la cámara",
|
"Close camera preview": "Cerrar vista previa de la cámara",
|
||||||
"Refresh map still": "Actualizar imagen fija del mapa",
|
"Refresh map still": "Actualizar imagen fija del mapa",
|
||||||
"The map still uses a separate request and may show a different moment.": "La imagen del mapa usa una solicitud independiente y puede mostrar otro momento.",
|
"The map still uses a separate request and may show a different moment.": "La imagen del mapa usa una solicitud independiente y puede mostrar otro momento.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "El vídeo se reproduce desde Québec 511, que ve tu dirección IP.",
|
"Video plays from Québec 511, which sees your IP address.": "El vídeo se reproduce desde Québec 511, que ve tu dirección IP.",
|
||||||
"Press OPEN to load the photo": "Pulsa ABRIR para cargar la foto",
|
"Press OPEN to load the photo": "Pulsa ABRIR para cargar la foto",
|
||||||
"Captured {month}": "Capturada en {month}",
|
"Captured {month}": "Capturada en {month}",
|
||||||
"this spot": "este punto",
|
|
||||||
"the view centre": "el centro de la vista",
|
"the view centre": "el centro de la vista",
|
||||||
"{source} at {place}": "{source} en {place}",
|
"{source} at {place}": "{source} en {place}",
|
||||||
|
"{source} at this spot": "{source} en este punto",
|
||||||
|
"{source} at the view centre": "{source} en el centro de la vista",
|
||||||
"Street level at {place}": "A pie de calle en {place}",
|
"Street level at {place}": "A pie de calle en {place}",
|
||||||
|
"Street level at this spot": "A pie de calle en este punto",
|
||||||
|
"Street level at the view centre": "A pie de calle en el centro de la vista",
|
||||||
"Switching map": "Cambiando de mapa",
|
"Switching map": "Cambiando de mapa",
|
||||||
"Opening imagery": "Abriendo imágenes",
|
"Opening imagery": "Abriendo imágenes",
|
||||||
"Street View is ready: press {open} to load it": "Street View está listo: pulsa {open} para cargarlo",
|
"Street View is ready: press {open} to load it": "Street View está listo: pulsa {open} para cargarlo",
|
||||||
@@ -3589,6 +3632,7 @@
|
|||||||
"{source} imagery could not be opened": "No se pudieron abrir las imágenes de {source}",
|
"{source} imagery could not be opened": "No se pudieron abrir las imágenes de {source}",
|
||||||
"{place} has no position to look for imagery at": "{place} no tiene una posición donde buscar imágenes",
|
"{place} has no position to look for imagery at": "{place} no tiene una posición donde buscar imágenes",
|
||||||
"No {source} imagery near {place}": "No hay imágenes de {source} cerca de {place}",
|
"No {source} imagery near {place}": "No hay imágenes de {source} cerca de {place}",
|
||||||
|
"No {source} imagery near the view centre": "No hay imágenes de {source} cerca del centro de la vista",
|
||||||
"{reason} (filters on: {filters})": "{reason} (filtros activos: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (filtros activos: {filters})",
|
||||||
"tiles unavailable": "teselas no disponibles",
|
"tiles unavailable": "teselas no disponibles",
|
||||||
"Environment": "Medio ambiente",
|
"Environment": "Medio ambiente",
|
||||||
@@ -3671,7 +3715,11 @@
|
|||||||
"switch on a dataset to load features": "activa un conjunto de datos para cargar elementos",
|
"switch on a dataset to load features": "activa un conjunto de datos para cargar elementos",
|
||||||
"Data Library at a glance": "La biblioteca de datos de un vistazo",
|
"Data Library at a glance": "La biblioteca de datos de un vistazo",
|
||||||
"Map features": "Elementos del mapa",
|
"Map features": "Elementos del mapa",
|
||||||
"Showing {shown} of {total}": "Mostrando {shown} de {total}",
|
"Showing {shown} of {total}": {
|
||||||
|
"one": "Mostrando {shown} de {total}",
|
||||||
|
"many": "Mostrando {shown} de {total}",
|
||||||
|
"other": "Mostrando {shown} de {total}"
|
||||||
|
},
|
||||||
"Datasets": "Conjuntos de datos",
|
"Datasets": "Conjuntos de datos",
|
||||||
"Themes": "Temas",
|
"Themes": "Temas",
|
||||||
"zoom in to load this layer": "acércate para cargar esta capa",
|
"zoom in to load this layer": "acércate para cargar esta capa",
|
||||||
@@ -3692,12 +3740,5 @@
|
|||||||
"500–1,000 m": "500–1000 m",
|
"500–1,000 m": "500–1000 m",
|
||||||
"1,000–2,000 m": "1000–2000 m",
|
"1,000–2,000 m": "1000–2000 m",
|
||||||
"2,000–3,000 m": "2000–3000 m",
|
"2,000–3,000 m": "2000–3000 m",
|
||||||
"3,000 m and above": "3000 m o más",
|
"3,000 m and above": "3000 m o más"
|
||||||
"The globe stopped drawing": "El globo dejó de dibujarse",
|
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "El navegador retiró los gráficos 3D del globo. Las capas y los controles siguen funcionando, pero el mapa no se dibujará hasta que se recargue la página.",
|
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "El renderizado se detuvo tras un error. Las capas y los controles siguen funcionando, pero el mapa no se actualiza.",
|
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "Restablecer la vista recarga la app sin la vista restaurada desde la barra de direcciones.",
|
|
||||||
"Reload": "Recargar",
|
|
||||||
"Retry": "Reintentar",
|
|
||||||
"Reset view": "Restablecer la vista"
|
|
||||||
}
|
}
|
||||||
+60
-19
@@ -68,8 +68,16 @@
|
|||||||
"{severity} only": "{severity} uniquement",
|
"{severity} only": "{severity} uniquement",
|
||||||
"All categories": "Toutes les catégories",
|
"All categories": "Toutes les catégories",
|
||||||
"Reading the news…": "Lecture des actualités…",
|
"Reading the news…": "Lecture des actualités…",
|
||||||
"News, last {hours} hours": "Actualités sur {hours} h",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "Sur {hours} h",
|
"one": "Actualités de la dernière {hours} heure",
|
||||||
|
"many": "Actualités des dernières {hours} d'heures",
|
||||||
|
"other": "Actualités des dernières {hours} heures"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "Dernière {hours} h",
|
||||||
|
"many": "Dernières {hours} h",
|
||||||
|
"other": "Dernières {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Période",
|
"Time window": "Période",
|
||||||
"Conflicts": "Conflits",
|
"Conflicts": "Conflits",
|
||||||
"Local": "Local",
|
"Local": "Local",
|
||||||
@@ -78,9 +86,17 @@
|
|||||||
"Hotspots": "Foyers",
|
"Hotspots": "Foyers",
|
||||||
"Severity": "Gravité",
|
"Severity": "Gravité",
|
||||||
"Category": "Catégorie",
|
"Category": "Catégorie",
|
||||||
"Nothing reported near here in the last {hours} h": "Rien de signalé près d'ici en {hours} h",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "Rien de signalé près d'ici cette dernière {hours} h",
|
||||||
|
"many": "Rien de signalé près d'ici ces dernières {hours} h",
|
||||||
|
"other": "Rien de signalé près d'ici ces dernières {hours} h"
|
||||||
|
},
|
||||||
"Latest news": "Dernières actualités",
|
"Latest news": "Dernières actualités",
|
||||||
"No news matches these filters in the last {hours} h": "Aucune actualité ne correspond à ces filtres sur {hours} h",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "Aucune actualité de la dernière {hours} h ne correspond à ces filtres",
|
||||||
|
"many": "Aucune actualité des dernières {hours} h ne correspond à ces filtres",
|
||||||
|
"other": "Aucune actualité des dernières {hours} h ne correspond à ces filtres"
|
||||||
|
},
|
||||||
"Load older news": "Charger des actualités plus anciennes",
|
"Load older news": "Charger des actualités plus anciennes",
|
||||||
"How to read the map": "Comment lire la carte",
|
"How to read the map": "Comment lire la carte",
|
||||||
"Colour: severity · icon: type of event": "Couleur : gravité · icône : type d'événement",
|
"Colour: severity · icon: type of event": "Couleur : gravité · icône : type d'événement",
|
||||||
@@ -584,6 +600,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Précédent — contact visité précédemment dans la fenêtre de 250 km",
|
"Previous — prior visited contact in the 250 km window": "Précédent — contact visité précédemment dans la fenêtre de 250 km",
|
||||||
"PREVIOUS": "PRÉC.",
|
"PREVIOUS": "PRÉC.",
|
||||||
"FOCUS": "CENTRER",
|
"FOCUS": "CENTRER",
|
||||||
|
"site status|FOCUS": "VISÉ",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Suivant — contact non visité le plus proche dans la fenêtre de 250 km",
|
"Next — nearest unvisited contact in the 250 km window": "Suivant — contact non visité le plus proche dans la fenêtre de 250 km",
|
||||||
"NEXT": "SUIV.",
|
"NEXT": "SUIV.",
|
||||||
"Named areas ({n})": "Zones nommées ({n})",
|
"Named areas ({n})": "Zones nommées ({n})",
|
||||||
@@ -1832,7 +1849,11 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "DONNÉES DE ROUTE INDISPONIBLES",
|
"ROUTE DATA UNAVAILABLE": "DONNÉES DE ROUTE INDISPONIBLES",
|
||||||
"CONTACT NAVIGATION OFF": "NAVIGATION DES CONTACTS DÉSACTIVÉE",
|
"CONTACT NAVIGATION OFF": "NAVIGATION DES CONTACTS DÉSACTIVÉE",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "LA PASSERELLE RESTE SUR LE NAVIRE SÉLECTIONNÉ",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "LA PASSERELLE RESTE SUR LE NAVIRE SÉLECTIONNÉ",
|
||||||
"{range} NM": "{range} NM",
|
"{range} NM": {
|
||||||
|
"one": "{range} NM",
|
||||||
|
"many": "{range} NM",
|
||||||
|
"other": "{range} NM"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "DESTINATION AIS · DÉCLARÉE PAR L'ÉQUIPAGE",
|
"AIS DESTINATION · CREW REPORTED": "DESTINATION AIS · DÉCLARÉE PAR L'ÉQUIPAGE",
|
||||||
"DEST {bearing}": "DEST {bearing}",
|
"DEST {bearing}": "DEST {bearing}",
|
||||||
"Disable cockpit weather effects": "Désactiver les effets météo du cockpit",
|
"Disable cockpit weather effects": "Désactiver les effets météo du cockpit",
|
||||||
@@ -1880,6 +1901,7 @@
|
|||||||
"Reclassify as TR-3B": "Reclasser en TR-3B",
|
"Reclassify as TR-3B": "Reclasser en TR-3B",
|
||||||
"TRACK ACQUIRED": "PISTE ACQUISE",
|
"TRACK ACQUIRED": "PISTE ACQUISE",
|
||||||
"{label} · COURSE {heading}°": "{label} · ROUTE {heading}°",
|
"{label} · COURSE {heading}°": "{label} · ROUTE {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · ROUTE {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · CAP {heading}°",
|
"{label} · HEADING {heading}°": "{label} · CAP {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · SANS CAP",
|
"{label} · NO HEADING": "{label} · SANS CAP",
|
||||||
"{label} · NADIR VIEW": "{label} · VUE AU NADIR",
|
"{label} · NADIR VIEW": "{label} · VUE AU NADIR",
|
||||||
@@ -2152,6 +2174,7 @@
|
|||||||
"Observed history": "Historique des observations",
|
"Observed history": "Historique des observations",
|
||||||
"Later observation": "Observation suivante",
|
"Later observation": "Observation suivante",
|
||||||
"Latest": "Dernière",
|
"Latest": "Dernière",
|
||||||
|
"news list|Latest": "Dernières",
|
||||||
"Newest frame per product": "Image la plus récente par produit",
|
"Newest frame per product": "Image la plus récente par produit",
|
||||||
"Replay observed history": "Rejouer l'historique des observations",
|
"Replay observed history": "Rejouer l'historique des observations",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Sur Esri · EFFACER rétablit Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "Sur Esri · EFFACER rétablit Google 3D",
|
||||||
@@ -2880,7 +2903,11 @@
|
|||||||
"Contact cockpit summary": "Résumé cockpit du contact",
|
"Contact cockpit summary": "Résumé cockpit du contact",
|
||||||
"CONTACT": "CONTACT",
|
"CONTACT": "CONTACT",
|
||||||
"CONTACTS · 250 KM": "CONTACTS · 250 KM",
|
"CONTACTS · 250 KM": "CONTACTS · 250 KM",
|
||||||
"RINGS {spacing} NM": "CERCLES {spacing} NM",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "CERCLES {spacing} NM",
|
||||||
|
"many": "CERCLES {spacing} NM",
|
||||||
|
"other": "CERCLES {spacing} NM"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} CONTACT",
|
"one": "{count} CONTACT",
|
||||||
"many": "{count} DE CONTACTS",
|
"many": "{count} DE CONTACTS",
|
||||||
@@ -3207,6 +3234,7 @@
|
|||||||
"Left panels": "Panneaux de gauche",
|
"Left panels": "Panneaux de gauche",
|
||||||
"Right panels": "Panneaux de droite",
|
"Right panels": "Panneaux de droite",
|
||||||
"CONFIRMED": "CONFIRMÉE",
|
"CONFIRMED": "CONFIRMÉE",
|
||||||
|
"sightings filter|CONFIRMED": "CONFIRMÉES",
|
||||||
"PAD / RTLS": "PAS DE TIR / RTLS",
|
"PAD / RTLS": "PAS DE TIR / RTLS",
|
||||||
"fire confidence filter|All": "Toutes",
|
"fire confidence filter|All": "Toutes",
|
||||||
"fire confidence filter|Nominal+": "Nominale+",
|
"fire confidence filter|Nominal+": "Nominale+",
|
||||||
@@ -3473,7 +3501,11 @@
|
|||||||
"Photo of {name}": "Photo : {name}",
|
"Photo of {name}": "Photo : {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Observations dans la vue : {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} observation dans la vue",
|
||||||
|
"many": "{count} d'observations dans la vue",
|
||||||
|
"other": "{count} observations dans la vue"
|
||||||
|
},
|
||||||
"Close sighting": "Fermer l'observation",
|
"Close sighting": "Fermer l'observation",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Faune observée au cours des dernières 48 h (cétacés 7 jours). La couleur et le pictogramme du badge indiquent le groupe taxonomique. Les espèces sensibles sont limitées à des cellules de 0,2° ; les signalements de mammifères marins en mer des Salish provenant d'Acartia sont arrondis à 0,01°. Pas un recensement.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Faune observée au cours des dernières 48 h (cétacés 7 jours). La couleur et le pictogramme du badge indiquent le groupe taxonomique. Les espèces sensibles sont limitées à des cellules de 0,2° ; les signalements de mammifères marins en mer des Salish provenant d'Acartia sont arrondis à 0,01°. Pas un recensement.",
|
||||||
"Open {name} on iNaturalist": "Ouvrir {name} sur iNaturalist",
|
"Open {name} on iNaturalist": "Ouvrir {name} sur iNaturalist",
|
||||||
@@ -3484,20 +3516,31 @@
|
|||||||
"{days}d": "{days} j",
|
"{days}d": "{days} j",
|
||||||
"All markers": "Tous les marqueurs",
|
"All markers": "Tous les marqueurs",
|
||||||
"Show markers of every age": "Afficher les marqueurs de tout âge",
|
"Show markers of every age": "Afficher les marqueurs de tout âge",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Ancienneté maximale : {days} j ; marqueurs sans date conservés",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Ancienneté maximale : {days} j ; marqueurs sans date conservés",
|
||||||
|
"many": "Ancienneté maximale : {days} j ; marqueurs sans date conservés",
|
||||||
|
"other": "Ancienneté maximale : {days} j ; marqueurs sans date conservés"
|
||||||
|
},
|
||||||
"event age filter|All": "Tous",
|
"event age filter|All": "Tous",
|
||||||
"Event age": "Ancienneté",
|
"Event age": "Ancienneté",
|
||||||
"Event age: {days}d · undated kept": "Ancienneté : {days} j · marqueurs sans date conservés",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Ancienneté : {days} j · marqueurs sans date conservés",
|
||||||
|
"many": "Ancienneté : {days} j · marqueurs sans date conservés",
|
||||||
|
"other": "Ancienneté : {days} j · marqueurs sans date conservés"
|
||||||
|
},
|
||||||
"Close camera preview": "Fermer l'aperçu de la caméra",
|
"Close camera preview": "Fermer l'aperçu de la caméra",
|
||||||
"Refresh map still": "Actualiser l'image fixe sur la carte",
|
"Refresh map still": "Actualiser l'image fixe sur la carte",
|
||||||
"The map still uses a separate request and may show a different moment.": "L'image fixe de la carte utilise une requête distincte et peut montrer un autre moment.",
|
"The map still uses a separate request and may show a different moment.": "L'image fixe de la carte utilise une requête distincte et peut montrer un autre moment.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "La vidéo est lue depuis Québec 511, qui voit votre adresse IP.",
|
"Video plays from Québec 511, which sees your IP address.": "La vidéo est lue depuis Québec 511, qui voit votre adresse IP.",
|
||||||
"Press OPEN to load the photo": "Appuyez sur OUVRIR pour charger la photo",
|
"Press OPEN to load the photo": "Appuyez sur OUVRIR pour charger la photo",
|
||||||
"Captured {month}": "Prise en {month}",
|
"Captured {month}": "Prise en {month}",
|
||||||
"this spot": "cet endroit",
|
|
||||||
"the view centre": "le centre de la vue",
|
"the view centre": "le centre de la vue",
|
||||||
"{source} at {place}": "{source} : {place}",
|
"{source} at {place}": "{source} : {place}",
|
||||||
|
"{source} at this spot": "{source} à cet endroit",
|
||||||
|
"{source} at the view centre": "{source} au centre de la vue",
|
||||||
"Street level at {place}": "Niveau rue : {place}",
|
"Street level at {place}": "Niveau rue : {place}",
|
||||||
|
"Street level at this spot": "Niveau rue à cet endroit",
|
||||||
|
"Street level at the view centre": "Niveau rue au centre de la vue",
|
||||||
"Switching map": "Changement de carte",
|
"Switching map": "Changement de carte",
|
||||||
"Opening imagery": "Ouverture des images",
|
"Opening imagery": "Ouverture des images",
|
||||||
"Street View is ready: press {open} to load it": "Street View est prêt : appuyez sur {open} pour le charger",
|
"Street View is ready: press {open} to load it": "Street View est prêt : appuyez sur {open} pour le charger",
|
||||||
@@ -3589,6 +3632,7 @@
|
|||||||
"{source} imagery could not be opened": "Impossible d'ouvrir les images de {source}",
|
"{source} imagery could not be opened": "Impossible d'ouvrir les images de {source}",
|
||||||
"{place} has no position to look for imagery at": "{place} n'a pas de position où chercher des images",
|
"{place} has no position to look for imagery at": "{place} n'a pas de position où chercher des images",
|
||||||
"No {source} imagery near {place}": "Aucune image {source} près de {place}",
|
"No {source} imagery near {place}": "Aucune image {source} près de {place}",
|
||||||
|
"No {source} imagery near the view centre": "Aucune image {source} près du centre de la vue",
|
||||||
"{reason} (filters on: {filters})": "{reason} (filtres actifs : {filters})",
|
"{reason} (filters on: {filters})": "{reason} (filtres actifs : {filters})",
|
||||||
"tiles unavailable": "tuiles indisponibles",
|
"tiles unavailable": "tuiles indisponibles",
|
||||||
"zoom in to load this layer": "zoomez pour charger cette couche",
|
"zoom in to load this layer": "zoomez pour charger cette couche",
|
||||||
@@ -3691,13 +3735,10 @@
|
|||||||
"REMOVE ALL": "TOUT RETIRER",
|
"REMOVE ALL": "TOUT RETIRER",
|
||||||
"Confirm: remove every added entry from Data Layers": "Confirmer : retirer du panneau Couches toutes les entrées ajoutées",
|
"Confirm: remove every added entry from Data Layers": "Confirmer : retirer du panneau Couches toutes les entrées ajoutées",
|
||||||
"Remove every added entry from Data Layers": "Retirer du panneau Couches toutes les entrées ajoutées",
|
"Remove every added entry from Data Layers": "Retirer du panneau Couches toutes les entrées ajoutées",
|
||||||
"Showing {shown} of {total}": "Affichées : {shown} sur {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "PARCOURIR LA BIBLIOTHÈQUE DE DONNÉES",
|
"one": "{shown} sur {total} affichée",
|
||||||
"The globe stopped drawing": "Le globe ne s’affiche plus",
|
"many": "{shown} sur {total} affichées",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Le navigateur a retiré au globe ses graphismes 3D. Les calques et les commandes fonctionnent toujours, mais la carte ne pourra s’afficher qu’après le rechargement de la page.",
|
"other": "{shown} sur {total} affichées"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Le rendu s’est arrêté après une erreur. Les calques et les commandes fonctionnent toujours, mais la carte ne se met plus à jour.",
|
},
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "Réinitialiser la vue recharge l’app sans la vue restaurée depuis la barre d’adresse.",
|
"BROWSE DATA LIBRARY": "PARCOURIR LA BIBLIOTHÈQUE DE DONNÉES"
|
||||||
"Reload": "Recharger",
|
|
||||||
"Retry": "Réessayer",
|
|
||||||
"Reset view": "Réinitialiser la vue"
|
|
||||||
}
|
}
|
||||||
+60
-19
@@ -478,6 +478,7 @@
|
|||||||
"PREVIOUS": "הקודם",
|
"PREVIOUS": "הקודם",
|
||||||
"Previous — prior visited contact in the 250 km window": "הקודם — מגע שכבר ביקרת בו בחלון של 250 ק\"מ",
|
"Previous — prior visited contact in the 250 km window": "הקודם — מגע שכבר ביקרת בו בחלון של 250 ק\"מ",
|
||||||
"FOCUS": "התמקד",
|
"FOCUS": "התמקד",
|
||||||
|
"site status|FOCUS": "במוקד",
|
||||||
"NEXT": "הבא",
|
"NEXT": "הבא",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "הבא — המגע הקרוב ביותר שלא ביקרת בו בחלון של 250 ק\"מ",
|
"Next — nearest unvisited contact in the 250 km window": "הבא — המגע הקרוב ביותר שלא ביקרת בו בחלון של 250 ק\"מ",
|
||||||
"Named areas ({n})": "אזורים בעלי שם ({n})",
|
"Named areas ({n})": "אזורים בעלי שם ({n})",
|
||||||
@@ -832,6 +833,7 @@
|
|||||||
"ORBIT REPLAY · {mission}": "שחזור מסלול · {mission}",
|
"ORBIT REPLAY · {mission}": "שחזור מסלול · {mission}",
|
||||||
"PAUSED · {title}": "מושהה · {title}",
|
"PAUSED · {title}": "מושהה · {title}",
|
||||||
"CONFIRMED": "מאושר",
|
"CONFIRMED": "מאושר",
|
||||||
|
"sightings filter|CONFIRMED": "מאושרות",
|
||||||
"PAD / RTLS": "כן שיגור / RTLS",
|
"PAD / RTLS": "כן שיגור / RTLS",
|
||||||
"UNSPECIFIED": "לא צוין",
|
"UNSPECIFIED": "לא צוין",
|
||||||
"{value} KM DOWNRANGE": "{value} ק\"מ לאורך המסלול",
|
"{value} KM DOWNRANGE": "{value} ק\"מ לאורך המסלול",
|
||||||
@@ -1268,7 +1270,11 @@
|
|||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "חיות בר נצפו ב-48 השעות האחרונות (לווייתנים · 7 ימים). הצבע וגליף התג מציגים את קבוצת הטקסון. מינים רגישים מוצגים כתאי 0.2° בלבד; דיווחי יונקי הים של Salish Sea דרך Acartia מעוגלים ל-0.01°. אינו מפקד אוכלוסין.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "חיות בר נצפו ב-48 השעות האחרונות (לווייתנים · 7 ימים). הצבע וגליף התג מציגים את קבוצת הטקסון. מינים רגישים מוצגים כתאי 0.2° בלבד; דיווחי יונקי הים של Salish Sea דרך Acartia מעוגלים ל-0.01°. אינו מפקד אוכלוסין.",
|
||||||
"iNaturalist contributors · Acartia {count} (Salish Sea) · licences per record": "תורמי iNaturalist · Acartia {count} (Salish Sea) · רישיונות לכל רשומה",
|
"iNaturalist contributors · Acartia {count} (Salish Sea) · licences per record": "תורמי iNaturalist · Acartia {count} (Salish Sea) · רישיונות לכל רשומה",
|
||||||
"iNaturalist contributors · licences per record": "תורמי iNaturalist · רישיונות לכל רשומה",
|
"iNaturalist contributors · licences per record": "תורמי iNaturalist · רישיונות לכל רשומה",
|
||||||
"{count} sightings in view": "{count} תצפיות בתצוגה",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} תצפית בתצוגה",
|
||||||
|
"two": "{count} תצפיות בתצוגה",
|
||||||
|
"other": "{count} תצפיות בתצוגה"
|
||||||
|
},
|
||||||
"Zoom in for local sightings": "התקרב לתצפיות מקומיות",
|
"Zoom in for local sightings": "התקרב לתצפיות מקומיות",
|
||||||
"No recent sightings in view": "אין תצפיות אחרונות בתצוגה",
|
"No recent sightings in view": "אין תצפיות אחרונות בתצוגה",
|
||||||
"Acartia feed unavailable": "פיד Acartia אינו זמין",
|
"Acartia feed unavailable": "פיד Acartia אינו זמין",
|
||||||
@@ -1737,6 +1743,7 @@
|
|||||||
"SOURCE STATUS UNAVAILABLE": "מצב המקור אינו זמין",
|
"SOURCE STATUS UNAVAILABLE": "מצב המקור אינו זמין",
|
||||||
"TRACK ACQUIRED": "המעקב ננעל",
|
"TRACK ACQUIRED": "המעקב ננעל",
|
||||||
"{label} · COURSE {heading}°": "{label} · מהלך {heading}°",
|
"{label} · COURSE {heading}°": "{label} · מהלך {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · מהלך {heading}°",
|
||||||
"Context could not restore every layer; try again": "ההקשר לא שיחזר את כל השכבות; נסה שוב",
|
"Context could not restore every layer; try again": "ההקשר לא שיחזר את כל השכבות; נסה שוב",
|
||||||
"No selected data layers": "אין שכבות נתונים נבחרות",
|
"No selected data layers": "אין שכבות נתונים נבחרות",
|
||||||
"{count} data layers could not be cleared": {
|
"{count} data layers could not be cleared": {
|
||||||
@@ -1998,6 +2005,7 @@
|
|||||||
"Later observation": "תצפית מאוחרת",
|
"Later observation": "תצפית מאוחרת",
|
||||||
"Newest frame per product": "השקופית החדשה לכל מוצר",
|
"Newest frame per product": "השקופית החדשה לכל מוצר",
|
||||||
"Latest": "האחרונה",
|
"Latest": "האחרונה",
|
||||||
|
"news list|Latest": "האחרונות",
|
||||||
"Replay observed history": "הצגת ההיסטוריה הנצפית שוב",
|
"Replay observed history": "הצגת ההיסטוריה הנצפית שוב",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "צילומים ב-Esri · Google 3D חוזר כשמנקים",
|
"Imagery on Esri · Google 3D returns when cleared": "צילומים ב-Esri · Google 3D חוזר כשמנקים",
|
||||||
"Point the camera at the ground to use the view": "כוון את המצלמה אל הקרקע כדי להשתמש בתצוגה",
|
"Point the camera at the ground to use the view": "כוון את המצלמה אל הקרקע כדי להשתמש בתצוגה",
|
||||||
@@ -3238,9 +3246,17 @@
|
|||||||
"event age filter|All": "הכול",
|
"event age filter|All": "הכול",
|
||||||
"{days}d": "{days} ימ׳",
|
"{days}d": "{days} ימ׳",
|
||||||
"Show markers of every age": "הצגת סמנים בכל גיל",
|
"Show markers of every age": "הצגת סמנים בכל גיל",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "גיל אירוע מרבי: {days} ימ׳; סמנים ללא תאריך נשמרים",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "גיל אירוע מרבי: {days} יום; סמנים ללא תאריך נשמרים",
|
||||||
|
"two": "גיל אירוע מרבי: {days} ימים; סמנים ללא תאריך נשמרים",
|
||||||
|
"other": "גיל אירוע מרבי: {days} ימים; סמנים ללא תאריך נשמרים"
|
||||||
|
},
|
||||||
"All markers": "כל הסמנים",
|
"All markers": "כל הסמנים",
|
||||||
"Event age: {days}d · undated kept": "גיל האירוע: {days} ימ׳ · ללא תאריך נשמרים",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "גיל האירוע: {days} יום · ללא תאריך נשמרים",
|
||||||
|
"two": "גיל האירוע: {days} ימים · ללא תאריך נשמרים",
|
||||||
|
"other": "גיל האירוע: {days} ימים · ללא תאריך נשמרים"
|
||||||
|
},
|
||||||
"30-DAY MISSION INDEX UNAVAILABLE": "רשימת המשימות ל-30 יום אינה זמינה",
|
"30-DAY MISSION INDEX UNAVAILABLE": "רשימת המשימות ל-30 יום אינה זמינה",
|
||||||
"STATIONS": "תחנות",
|
"STATIONS": "תחנות",
|
||||||
"Crewed stations and the craft that visit them": "תחנות מאוישות והחלליות המבקרות בהן",
|
"Crewed stations and the craft that visit them": "תחנות מאוישות והחלליות המבקרות בהן",
|
||||||
@@ -3276,7 +3292,11 @@
|
|||||||
"LIVE AIS": "AIS חי",
|
"LIVE AIS": "AIS חי",
|
||||||
"CONTACT NAVIGATION OFF": "ניווט המגעים כבוי",
|
"CONTACT NAVIGATION OFF": "ניווט המגעים כבוי",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "הגשר נשאר עם האונייה הנבחרת",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "הגשר נשאר עם האונייה הנבחרת",
|
||||||
"{range} NM": "{range} NM",
|
"{range} NM": {
|
||||||
|
"one": "{range} NM",
|
||||||
|
"two": "{range} NM",
|
||||||
|
"other": "{range} NM"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "יעד AIS · דווח על ידי הצוות",
|
"AIS DESTINATION · CREW REPORTED": "יעד AIS · דווח על ידי הצוות",
|
||||||
"FWD": "קדימה",
|
"FWD": "קדימה",
|
||||||
"AFT": "אחורה",
|
"AFT": "אחורה",
|
||||||
@@ -3295,10 +3315,13 @@
|
|||||||
"Video plays from Québec 511, which sees your IP address.": "הווידאו זורם מ-Québec 511, שרואה את כתובת ה-IP שלך.",
|
"Video plays from Québec 511, which sees your IP address.": "הווידאו זורם מ-Québec 511, שרואה את כתובת ה-IP שלך.",
|
||||||
"Press OPEN to load the photo": "לחץ על \"פתח\" כדי לטעון את התמונה",
|
"Press OPEN to load the photo": "לחץ על \"פתח\" כדי לטעון את התמונה",
|
||||||
"Captured {month}": "צולם ב-{month}",
|
"Captured {month}": "צולם ב-{month}",
|
||||||
"this spot": "המקום הזה",
|
|
||||||
"the view centre": "מרכז התצוגה",
|
"the view centre": "מרכז התצוגה",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} במקום הזה",
|
||||||
|
"{source} at the view centre": "{source} במרכז התצוגה",
|
||||||
"Street level at {place}": "צילומי רחוב: {place}",
|
"Street level at {place}": "צילומי רחוב: {place}",
|
||||||
|
"Street level at this spot": "צילומי רחוב במקום הזה",
|
||||||
|
"Street level at the view centre": "צילומי רחוב במרכז התצוגה",
|
||||||
"Switching map": "מחליף מפה",
|
"Switching map": "מחליף מפה",
|
||||||
"Opening imagery": "פותח צילומים",
|
"Opening imagery": "פותח צילומים",
|
||||||
"Street View is ready: press {open} to load it": "Street View מוכן: לחץ על \"{open}\" כדי לטעון אותו",
|
"Street View is ready: press {open} to load it": "Street View מוכן: לחץ על \"{open}\" כדי לטעון אותו",
|
||||||
@@ -3358,7 +3381,11 @@
|
|||||||
"Zoom in (+)": "התקרבות (+)",
|
"Zoom in (+)": "התקרבות (+)",
|
||||||
"Zoom to the imaging sensor swath": "זום אל רצועת הסריקה של חיישן הצילום",
|
"Zoom to the imaging sensor swath": "זום אל רצועת הסריקה של חיישן הצילום",
|
||||||
"SENSOR": "חיישן",
|
"SENSOR": "חיישן",
|
||||||
"RINGS {spacing} NM": "טבעות {spacing} NM",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "טבעות {spacing} NM",
|
||||||
|
"two": "טבעות {spacing} NM",
|
||||||
|
"other": "טבעות {spacing} NM"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} מגע",
|
"one": "{count} מגע",
|
||||||
"two": "{count} מגעים",
|
"two": "{count} מגעים",
|
||||||
@@ -3504,20 +3531,36 @@
|
|||||||
"{severity} and above": "{severity} ומעלה",
|
"{severity} and above": "{severity} ומעלה",
|
||||||
"{severity} only": "{severity} בלבד",
|
"{severity} only": "{severity} בלבד",
|
||||||
"All categories": "כל הקטגוריות",
|
"All categories": "כל הקטגוריות",
|
||||||
"Last {hours} h": "{hours} שע׳ אחורה",
|
"Last {hours} h": {
|
||||||
|
"one": "השעה האחרונה ({hours})",
|
||||||
|
"two": "{hours} שע׳ אחרונות",
|
||||||
|
"other": "{hours} שע׳ אחרונות"
|
||||||
|
},
|
||||||
"News from {area}": "חדשות מ-{area}",
|
"News from {area}": "חדשות מ-{area}",
|
||||||
"Within {km} km of the view centre": "בטווח {km} ק\"מ ממרכז התצוגה",
|
"Within {km} km of the view centre": "בטווח {km} ק\"מ ממרכז התצוגה",
|
||||||
"Reading the news…": "קורא את החדשות…",
|
"Reading the news…": "קורא את החדשות…",
|
||||||
"News, last {hours} hours": "חדשות, {hours} שע׳ אחורה",
|
"News, last {hours} hours": {
|
||||||
|
"one": "חדשות, השעה האחרונה ({hours})",
|
||||||
|
"two": "חדשות, {hours} השעות האחרונות",
|
||||||
|
"other": "חדשות, {hours} השעות האחרונות"
|
||||||
|
},
|
||||||
"Time window": "חלון זמן",
|
"Time window": "חלון זמן",
|
||||||
"News sections": "מדורי חדשות",
|
"News sections": "מדורי חדשות",
|
||||||
"Hotspots": "מוקדים",
|
"Hotspots": "מוקדים",
|
||||||
"Severity": "חומרה",
|
"Severity": "חומרה",
|
||||||
"Category": "קטגוריה",
|
"Category": "קטגוריה",
|
||||||
"Zoom in to see local news": "התקרב כדי לראות חדשות מקומיות",
|
"Zoom in to see local news": "התקרב כדי לראות חדשות מקומיות",
|
||||||
"Nothing reported near here in the last {hours} h": "לא דווח דבר בסביבה בחלון של {hours} שע׳",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "לא דווח דבר בסביבה בשעה האחרונה ({hours})",
|
||||||
|
"two": "לא דווח דבר בסביבה ב־{hours} השעות האחרונות",
|
||||||
|
"other": "לא דווח דבר בסביבה ב־{hours} השעות האחרונות"
|
||||||
|
},
|
||||||
"Latest news": "חדשות אחרונות",
|
"Latest news": "חדשות אחרונות",
|
||||||
"No news matches these filters in the last {hours} h": "אין חדשות התואמות למסננים אלה בחלון של {hours} שע׳",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "אין חדשות התואמות למסננים אלה בשעה האחרונה ({hours})",
|
||||||
|
"two": "אין חדשות התואמות למסננים אלה ב־{hours} השעות האחרונות",
|
||||||
|
"other": "אין חדשות התואמות למסננים אלה ב־{hours} השעות האחרונות"
|
||||||
|
},
|
||||||
"Load older news": "טעינת חדשות ישנות יותר",
|
"Load older news": "טעינת חדשות ישנות יותר",
|
||||||
"How to read the map": "איך לקרוא את המפה",
|
"How to read the map": "איך לקרוא את המפה",
|
||||||
"Colour: severity · icon: type of event": "צבע: חומרה · סמל: סוג האירוע",
|
"Colour: severity · icon: type of event": "צבע: חומרה · סמל: סוג האירוע",
|
||||||
@@ -3589,6 +3632,7 @@
|
|||||||
"{source} imagery could not be opened": "לא ניתן היה לפתוח את הצילומים של {source}",
|
"{source} imagery could not be opened": "לא ניתן היה לפתוח את הצילומים של {source}",
|
||||||
"{place} has no position to look for imagery at": "ל-{place} אין מיקום לחיפוש צילומים",
|
"{place} has no position to look for imagery at": "ל-{place} אין מיקום לחיפוש צילומים",
|
||||||
"No {source} imagery near {place}": "אין צילומים של {source} ליד {place}",
|
"No {source} imagery near {place}": "אין צילומים של {source} ליד {place}",
|
||||||
|
"No {source} imagery near the view centre": "אין צילומים של {source} ליד מרכז התצוגה",
|
||||||
"{reason} (filters on: {filters})": "{reason} (מסננים פעילים: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (מסננים פעילים: {filters})",
|
||||||
"tiles unavailable": "האריחים אינם זמינים",
|
"tiles unavailable": "האריחים אינם זמינים",
|
||||||
"zoom in to load this layer": "התקרב כדי לטעון את השכבה הזאת",
|
"zoom in to load this layer": "התקרב כדי לטעון את השכבה הזאת",
|
||||||
@@ -3691,13 +3735,10 @@
|
|||||||
"REMOVE ALL": "הסרת הכול",
|
"REMOVE ALL": "הסרת הכול",
|
||||||
"Confirm: remove every added entry from Data Layers": "אישור: הסרת כל הפריטים שנוספו משכבות נתונים",
|
"Confirm: remove every added entry from Data Layers": "אישור: הסרת כל הפריטים שנוספו משכבות נתונים",
|
||||||
"Remove every added entry from Data Layers": "הסרת כל הפריטים שנוספו משכבות נתונים",
|
"Remove every added entry from Data Layers": "הסרת כל הפריטים שנוספו משכבות נתונים",
|
||||||
"Showing {shown} of {total}": "מוצגים {shown} מתוך {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "עיון בספריית הנתונים",
|
"one": "מוצג {shown} מתוך {total}",
|
||||||
"The globe stopped drawing": "הגלובוס הפסיק להצטייר",
|
"two": "מוצגים {shown} מתוך {total}",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "הדפדפן לקח בחזרה את הגרפיקה התלת-ממדית של הגלובוס. השכבות והפקדים עדיין פועלים, אבל המפה לא תוכל להצטייר עד שהעמוד ייטען מחדש.",
|
"other": "מוצגים {shown} מתוך {total}"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "הרינדור נעצר בעקבות שגיאה. השכבות והפקדים עדיין פועלים, אבל המפה אינה מתעדכנת.",
|
},
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "\"איפוס תצוגה\" טוען מחדש את האפליקציה בלי התצוגה ששוחזרה משורת הכתובת.",
|
"BROWSE DATA LIBRARY": "עיון בספריית הנתונים"
|
||||||
"Reload": "טען מחדש",
|
|
||||||
"Retry": "נסה שוב",
|
|
||||||
"Reset view": "איפוס תצוגה"
|
|
||||||
}
|
}
|
||||||
+61
-20
@@ -68,8 +68,16 @@
|
|||||||
"{severity} only": "Solo {severity}",
|
"{severity} only": "Solo {severity}",
|
||||||
"All categories": "Tutte le categorie",
|
"All categories": "Tutte le categorie",
|
||||||
"Reading the news…": "Lettura delle notizie…",
|
"Reading the news…": "Lettura delle notizie…",
|
||||||
"News, last {hours} hours": "Notizie, periodo in ore: {hours}",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "Periodo: {hours} h",
|
"one": "Notizie dell'ultima {hours} ora",
|
||||||
|
"many": "Notizie delle ultime {hours} di ore",
|
||||||
|
"other": "Notizie delle ultime {hours} ore"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "Ultima {hours} h",
|
||||||
|
"many": "Ultime {hours} h",
|
||||||
|
"other": "Ultime {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Periodo",
|
"Time window": "Periodo",
|
||||||
"Conflicts": "Conflitti",
|
"Conflicts": "Conflitti",
|
||||||
"Local": "Locale",
|
"Local": "Locale",
|
||||||
@@ -78,9 +86,17 @@
|
|||||||
"Hotspots": "Focolai",
|
"Hotspots": "Focolai",
|
||||||
"Severity": "Gravità",
|
"Severity": "Gravità",
|
||||||
"Category": "Categoria",
|
"Category": "Categoria",
|
||||||
"Nothing reported near here in the last {hours} h": "Nessuna segnalazione qui vicino · periodo: {hours} h",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "Nessuna segnalazione qui vicino nell'ultima {hours} h",
|
||||||
|
"many": "Nessuna segnalazione qui vicino nelle ultime {hours} h",
|
||||||
|
"other": "Nessuna segnalazione qui vicino nelle ultime {hours} h"
|
||||||
|
},
|
||||||
"Latest news": "Ultime notizie",
|
"Latest news": "Ultime notizie",
|
||||||
"No news matches these filters in the last {hours} h": "Nessuna notizia corrisponde a questi filtri · periodo: {hours} h",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "Nessuna notizia dell'ultima {hours} h corrisponde a questi filtri",
|
||||||
|
"many": "Nessuna notizia delle ultime {hours} h corrisponde a questi filtri",
|
||||||
|
"other": "Nessuna notizia delle ultime {hours} h corrisponde a questi filtri"
|
||||||
|
},
|
||||||
"Load older news": "Carica notizie precedenti",
|
"Load older news": "Carica notizie precedenti",
|
||||||
"How to read the map": "Come leggere la mappa",
|
"How to read the map": "Come leggere la mappa",
|
||||||
"Colour: severity · icon: type of event": "Colore: gravità · icona: tipo di evento",
|
"Colour: severity · icon: type of event": "Colore: gravità · icona: tipo di evento",
|
||||||
@@ -584,6 +600,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Precedente — contatto visitato in precedenza nella finestra di 250 km",
|
"Previous — prior visited contact in the 250 km window": "Precedente — contatto visitato in precedenza nella finestra di 250 km",
|
||||||
"PREVIOUS": "PREC",
|
"PREVIOUS": "PREC",
|
||||||
"FOCUS": "FOCUS",
|
"FOCUS": "FOCUS",
|
||||||
|
"site status|FOCUS": "FOCUS",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Successivo — contatto non visitato più vicino nella finestra di 250 km",
|
"Next — nearest unvisited contact in the 250 km window": "Successivo — contatto non visitato più vicino nella finestra di 250 km",
|
||||||
"NEXT": "SUCC",
|
"NEXT": "SUCC",
|
||||||
"Named areas ({n})": "Aree con nome ({n})",
|
"Named areas ({n})": "Aree con nome ({n})",
|
||||||
@@ -941,7 +958,7 @@
|
|||||||
"ORBITAL VELOCITY": "VELOCITÀ ORBITALE",
|
"ORBITAL VELOCITY": "VELOCITÀ ORBITALE",
|
||||||
"SUB-SATELLITE POINT": "PUNTO SUBSATELLITARE",
|
"SUB-SATELLITE POINT": "PUNTO SUBSATELLITARE",
|
||||||
"NORTH UP": "NORD IN ALTO",
|
"NORTH UP": "NORD IN ALTO",
|
||||||
"CURRENTLY OVER": "ATTUALMENTE SOPRA",
|
"CURRENTLY OVER": "SOTTO IL SATELLITE",
|
||||||
"{division}, {country}": "{division}, {country}",
|
"{division}, {country}": "{division}, {country}",
|
||||||
"{region} · near {place}": "{region} · vicino a {place}",
|
"{region} · near {place}": "{region} · vicino a {place}",
|
||||||
"NORAD {id}": "NORAD {id}",
|
"NORAD {id}": "NORAD {id}",
|
||||||
@@ -1832,7 +1849,11 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "DATI ROTTA NON DISPONIBILI",
|
"ROUTE DATA UNAVAILABLE": "DATI ROTTA NON DISPONIBILI",
|
||||||
"CONTACT NAVIGATION OFF": "NAVIGAZIONE CONTATTI DISATTIVATA",
|
"CONTACT NAVIGATION OFF": "NAVIGAZIONE CONTATTI DISATTIVATA",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "LA PLANCIA RESTA SULLA NAVE SELEZIONATA",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "LA PLANCIA RESTA SULLA NAVE SELEZIONATA",
|
||||||
"{range} NM": "{range} MN",
|
"{range} NM": {
|
||||||
|
"one": "{range} MN",
|
||||||
|
"many": "{range} MN",
|
||||||
|
"other": "{range} MN"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "DESTINAZIONE AIS · DICHIARATA DALL'EQUIPAGGIO",
|
"AIS DESTINATION · CREW REPORTED": "DESTINAZIONE AIS · DICHIARATA DALL'EQUIPAGGIO",
|
||||||
"DEST {bearing}": "DEST {bearing}",
|
"DEST {bearing}": "DEST {bearing}",
|
||||||
"Disable cockpit weather effects": "Disattiva gli effetti meteo del cockpit",
|
"Disable cockpit weather effects": "Disattiva gli effetti meteo del cockpit",
|
||||||
@@ -1880,6 +1901,7 @@
|
|||||||
"Reclassify as TR-3B": "Riclassifica come TR-3B",
|
"Reclassify as TR-3B": "Riclassifica come TR-3B",
|
||||||
"TRACK ACQUIRED": "TRACCIA ACQUISITA",
|
"TRACK ACQUIRED": "TRACCIA ACQUISITA",
|
||||||
"{label} · COURSE {heading}°": "{label} · ROTTA {heading}°",
|
"{label} · COURSE {heading}°": "{label} · ROTTA {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · ROTTA {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · PRUA {heading}°",
|
"{label} · HEADING {heading}°": "{label} · PRUA {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · PRUA N/D",
|
"{label} · NO HEADING": "{label} · PRUA N/D",
|
||||||
"{label} · NADIR VIEW": "{label} · VISTA AL NADIR",
|
"{label} · NADIR VIEW": "{label} · VISTA AL NADIR",
|
||||||
@@ -2152,6 +2174,7 @@
|
|||||||
"Observed history": "Storico osservato",
|
"Observed history": "Storico osservato",
|
||||||
"Later observation": "Osservazione successiva",
|
"Later observation": "Osservazione successiva",
|
||||||
"Latest": "Più recente",
|
"Latest": "Più recente",
|
||||||
|
"news list|Latest": "Più recenti",
|
||||||
"Newest frame per product": "Fotogramma più recente per prodotto",
|
"Newest frame per product": "Fotogramma più recente per prodotto",
|
||||||
"Replay observed history": "Rivedi lo storico osservato",
|
"Replay observed history": "Rivedi lo storico osservato",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Immagini su Esri · Google 3D torna con CANCELLA",
|
"Imagery on Esri · Google 3D returns when cleared": "Immagini su Esri · Google 3D torna con CANCELLA",
|
||||||
@@ -2880,7 +2903,11 @@
|
|||||||
"Contact cockpit summary": "Riepilogo contatto del cockpit",
|
"Contact cockpit summary": "Riepilogo contatto del cockpit",
|
||||||
"CONTACT": "CONTATTO",
|
"CONTACT": "CONTATTO",
|
||||||
"CONTACTS · 250 KM": "CONTATTI · 250 KM",
|
"CONTACTS · 250 KM": "CONTATTI · 250 KM",
|
||||||
"RINGS {spacing} NM": "CERCHI {spacing} MN",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "CERCHI {spacing} MN",
|
||||||
|
"many": "CERCHI {spacing} MN",
|
||||||
|
"other": "CERCHI {spacing} MN"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} CONTATTO",
|
"one": "{count} CONTATTO",
|
||||||
"many": "{count} DI CONTATTI",
|
"many": "{count} DI CONTATTI",
|
||||||
@@ -3207,6 +3234,7 @@
|
|||||||
"Left panels": "Pannelli a sinistra",
|
"Left panels": "Pannelli a sinistra",
|
||||||
"Right panels": "Pannelli a destra",
|
"Right panels": "Pannelli a destra",
|
||||||
"CONFIRMED": "CONFERMATA",
|
"CONFIRMED": "CONFERMATA",
|
||||||
|
"sightings filter|CONFIRMED": "CONFERMATE",
|
||||||
"PAD / RTLS": "RAMPA / RTLS",
|
"PAD / RTLS": "RAMPA / RTLS",
|
||||||
"fire confidence filter|All": "Tutti",
|
"fire confidence filter|All": "Tutti",
|
||||||
"fire confidence filter|Nominal+": "Nominale+",
|
"fire confidence filter|Nominal+": "Nominale+",
|
||||||
@@ -3473,7 +3501,11 @@
|
|||||||
"Photo of {name}": "Foto di {name}",
|
"Photo of {name}": "Foto di {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Avvistamenti nella vista: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} avvistamento nella vista",
|
||||||
|
"many": "{count} di avvistamenti nella vista",
|
||||||
|
"other": "{count} avvistamenti nella vista"
|
||||||
|
},
|
||||||
"Close sighting": "Chiudi avvistamento",
|
"Close sighting": "Chiudi avvistamento",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Fauna osservata nelle ultime 48 h (cetacei 7 giorni). Colore e simbolo del contrassegno indicano il gruppo tassonomico. Le specie sensibili sono mostrate solo in celle di 0,2°; le segnalazioni di mammiferi marini di Acartia nel mar dei Salish sono arrotondate a 0,01°. Non è un censimento.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Fauna osservata nelle ultime 48 h (cetacei 7 giorni). Colore e simbolo del contrassegno indicano il gruppo tassonomico. Le specie sensibili sono mostrate solo in celle di 0,2°; le segnalazioni di mammiferi marini di Acartia nel mar dei Salish sono arrotondate a 0,01°. Non è un censimento.",
|
||||||
"Open {name} on iNaturalist": "Apri {name} su iNaturalist",
|
"Open {name} on iNaturalist": "Apri {name} su iNaturalist",
|
||||||
@@ -3484,20 +3516,31 @@
|
|||||||
"{days}d": "{days} g",
|
"{days}d": "{days} g",
|
||||||
"All markers": "Tutti i marcatori",
|
"All markers": "Tutti i marcatori",
|
||||||
"Show markers of every age": "Mostra i marcatori di qualsiasi età",
|
"Show markers of every age": "Mostra i marcatori di qualsiasi età",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Età massima degli eventi: {days} g; marcatori senza data mantenuti",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Età massima degli eventi: {days} giorno; marcatori senza data mantenuti",
|
||||||
|
"many": "Età massima degli eventi: {days} di giorni; marcatori senza data mantenuti",
|
||||||
|
"other": "Età massima degli eventi: {days} giorni; marcatori senza data mantenuti"
|
||||||
|
},
|
||||||
"event age filter|All": "Tutti",
|
"event age filter|All": "Tutti",
|
||||||
"Event age": "Età degli eventi",
|
"Event age": "Età degli eventi",
|
||||||
"Event age: {days}d · undated kept": "Età degli eventi: {days} g · senza data mantenuti",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Età degli eventi: {days} giorno · senza data mantenuti",
|
||||||
|
"many": "Età degli eventi: {days} di giorni · senza data mantenuti",
|
||||||
|
"other": "Età degli eventi: {days} giorni · senza data mantenuti"
|
||||||
|
},
|
||||||
"Close camera preview": "Chiudi anteprima della telecamera",
|
"Close camera preview": "Chiudi anteprima della telecamera",
|
||||||
"Refresh map still": "Aggiorna immagine fissa sulla mappa",
|
"Refresh map still": "Aggiorna immagine fissa sulla mappa",
|
||||||
"The map still uses a separate request and may show a different moment.": "L'immagine sulla mappa usa una richiesta separata e può mostrare un momento diverso.",
|
"The map still uses a separate request and may show a different moment.": "L'immagine sulla mappa usa una richiesta separata e può mostrare un momento diverso.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "Il video viene riprodotto da Québec 511, che vede il tuo indirizzo IP.",
|
"Video plays from Québec 511, which sees your IP address.": "Il video viene riprodotto da Québec 511, che vede il tuo indirizzo IP.",
|
||||||
"Press OPEN to load the photo": "Premi APRI per caricare la foto",
|
"Press OPEN to load the photo": "Premi APRI per caricare la foto",
|
||||||
"Captured {month}": "Scattata: {month}",
|
"Captured {month}": "Scattata: {month}",
|
||||||
"this spot": "questo punto",
|
|
||||||
"the view centre": "il centro della vista",
|
"the view centre": "il centro della vista",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} in questo punto",
|
||||||
|
"{source} at the view centre": "{source} al centro della vista",
|
||||||
"Street level at {place}": "Vista stradale: {place}",
|
"Street level at {place}": "Vista stradale: {place}",
|
||||||
|
"Street level at this spot": "Vista stradale in questo punto",
|
||||||
|
"Street level at the view centre": "Vista stradale al centro della vista",
|
||||||
"Switching map": "Cambio mappa",
|
"Switching map": "Cambio mappa",
|
||||||
"Opening imagery": "Apertura delle immagini",
|
"Opening imagery": "Apertura delle immagini",
|
||||||
"Street View is ready: press {open} to load it": "Street View è pronto: premi {open} per caricarlo",
|
"Street View is ready: press {open} to load it": "Street View è pronto: premi {open} per caricarlo",
|
||||||
@@ -3589,6 +3632,7 @@
|
|||||||
"{source} imagery could not be opened": "Impossibile aprire le immagini di {source}",
|
"{source} imagery could not be opened": "Impossibile aprire le immagini di {source}",
|
||||||
"{place} has no position to look for imagery at": "{place} non ha una posizione in cui cercare immagini",
|
"{place} has no position to look for imagery at": "{place} non ha una posizione in cui cercare immagini",
|
||||||
"No {source} imagery near {place}": "Nessuna immagine di {source} vicino a {place}",
|
"No {source} imagery near {place}": "Nessuna immagine di {source} vicino a {place}",
|
||||||
|
"No {source} imagery near the view centre": "Nessuna immagine di {source} vicino al centro della vista",
|
||||||
"{reason} (filters on: {filters})": "{reason} (filtri attivi: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (filtri attivi: {filters})",
|
||||||
"tiles unavailable": "tile non disponibili",
|
"tiles unavailable": "tile non disponibili",
|
||||||
"zoom in to load this layer": "ingrandisci per caricare questo livello",
|
"zoom in to load this layer": "ingrandisci per caricare questo livello",
|
||||||
@@ -3691,13 +3735,10 @@
|
|||||||
"REMOVE ALL": "RIMUOVI TUTTO",
|
"REMOVE ALL": "RIMUOVI TUTTO",
|
||||||
"Confirm: remove every added entry from Data Layers": "Conferma: rimuovi da Livelli dati tutte le voci aggiunte",
|
"Confirm: remove every added entry from Data Layers": "Conferma: rimuovi da Livelli dati tutte le voci aggiunte",
|
||||||
"Remove every added entry from Data Layers": "Rimuovi da Livelli dati tutte le voci aggiunte",
|
"Remove every added entry from Data Layers": "Rimuovi da Livelli dati tutte le voci aggiunte",
|
||||||
"Showing {shown} of {total}": "Mostrati: {shown} su {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "ESPLORA LIBRERIA DATI",
|
"one": "{shown} di {total} mostrata",
|
||||||
"The globe stopped drawing": "Il globo ha smesso di disegnare",
|
"many": "{shown} di {total} mostrate",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Il browser ha ritirato la grafica 3D del globo. Livelli e controlli funzionano ancora, ma la mappa non può essere disegnata finché la pagina non viene ricaricata.",
|
"other": "{shown} di {total} mostrate"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Il rendering si è fermato dopo un errore. Livelli e controlli funzionano ancora, ma la mappa non si aggiorna.",
|
},
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "Ripristina vista ricarica l’app senza la vista ripristinata dalla barra degli indirizzi.",
|
"BROWSE DATA LIBRARY": "ESPLORA LIBRERIA DATI"
|
||||||
"Reload": "Ricarica",
|
|
||||||
"Retry": "Riprova",
|
|
||||||
"Reset view": "Ripristina vista"
|
|
||||||
}
|
}
|
||||||
+40
-19
@@ -62,8 +62,12 @@
|
|||||||
"{severity} only": "{severity}のみ",
|
"{severity} only": "{severity}のみ",
|
||||||
"All categories": "すべてのカテゴリ",
|
"All categories": "すべてのカテゴリ",
|
||||||
"Reading the news…": "ニュースを読み込み中…",
|
"Reading the news…": "ニュースを読み込み中…",
|
||||||
"News, last {hours} hours": "直近{hours}時間のニュース",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "直近{hours}時間",
|
"other": "直近{hours}時間のニュース"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"other": "直近{hours}時間"
|
||||||
|
},
|
||||||
"Time window": "期間",
|
"Time window": "期間",
|
||||||
"Conflicts": "紛争",
|
"Conflicts": "紛争",
|
||||||
"Local": "ローカル",
|
"Local": "ローカル",
|
||||||
@@ -72,9 +76,13 @@
|
|||||||
"Hotspots": "ホットスポット",
|
"Hotspots": "ホットスポット",
|
||||||
"Severity": "深刻度",
|
"Severity": "深刻度",
|
||||||
"Category": "カテゴリ",
|
"Category": "カテゴリ",
|
||||||
"Nothing reported near here in the last {hours} h": "直近{hours}時間、この付近の報道はありません",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"other": "直近{hours}時間、この付近の報道はありません"
|
||||||
|
},
|
||||||
"Latest news": "最新ニュース",
|
"Latest news": "最新ニュース",
|
||||||
"No news matches these filters in the last {hours} h": "直近{hours}時間で条件に合うニュースはありません",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"other": "直近{hours}時間で条件に合うニュースはありません"
|
||||||
|
},
|
||||||
"Load older news": "以前のニュースを読み込む",
|
"Load older news": "以前のニュースを読み込む",
|
||||||
"How to read the map": "地図の見方",
|
"How to read the map": "地図の見方",
|
||||||
"Colour: severity · icon: type of event": "色:深刻度 · アイコン:出来事の種類",
|
"Colour: severity · icon: type of event": "色:深刻度 · アイコン:出来事の種類",
|
||||||
@@ -564,6 +572,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "前へ:250 km圏内で直前に訪れたコンタクト",
|
"Previous — prior visited contact in the 250 km window": "前へ:250 km圏内で直前に訪れたコンタクト",
|
||||||
"PREVIOUS": "前へ",
|
"PREVIOUS": "前へ",
|
||||||
"FOCUS": "フォーカス",
|
"FOCUS": "フォーカス",
|
||||||
|
"site status|FOCUS": "フォーカス",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "次へ:250 km圏内で最も近い未訪問のコンタクト",
|
"Next — nearest unvisited contact in the 250 km window": "次へ:250 km圏内で最も近い未訪問のコンタクト",
|
||||||
"NEXT": "次へ",
|
"NEXT": "次へ",
|
||||||
"Named areas ({n})": "名前付きエリア({n})",
|
"Named areas ({n})": "名前付きエリア({n})",
|
||||||
@@ -1776,7 +1785,9 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "経路データなし",
|
"ROUTE DATA UNAVAILABLE": "経路データなし",
|
||||||
"CONTACT NAVIGATION OFF": "コンタクトナビゲーション オフ",
|
"CONTACT NAVIGATION OFF": "コンタクトナビゲーション オフ",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "ブリッジは選択中の船舶に固定",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "ブリッジは選択中の船舶に固定",
|
||||||
"{range} NM": "{range}海里",
|
"{range} NM": {
|
||||||
|
"other": "{range}海里"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "AIS目的地 · 乗組員入力",
|
"AIS DESTINATION · CREW REPORTED": "AIS目的地 · 乗組員入力",
|
||||||
"DEST {bearing}": "目的地 {bearing}",
|
"DEST {bearing}": "目的地 {bearing}",
|
||||||
"Disable cockpit weather effects": "コックピットの天候エフェクトを無効にする",
|
"Disable cockpit weather effects": "コックピットの天候エフェクトを無効にする",
|
||||||
@@ -1822,6 +1833,7 @@
|
|||||||
"Reclassify as TR-3B": "TR-3Bに再分類",
|
"Reclassify as TR-3B": "TR-3Bに再分類",
|
||||||
"TRACK ACQUIRED": "追跡開始",
|
"TRACK ACQUIRED": "追跡開始",
|
||||||
"{label} · COURSE {heading}°": "{label} · 対地針路 {heading}°",
|
"{label} · COURSE {heading}°": "{label} · 対地針路 {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · 対地針路 {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · 船首方位 {heading}°",
|
"{label} · HEADING {heading}°": "{label} · 船首方位 {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · 船首方位なし",
|
"{label} · NO HEADING": "{label} · 船首方位なし",
|
||||||
"{label} · NADIR VIEW": "{label} · 直下視",
|
"{label} · NADIR VIEW": "{label} · 直下視",
|
||||||
@@ -2078,6 +2090,7 @@
|
|||||||
"Observed history": "観測履歴",
|
"Observed history": "観測履歴",
|
||||||
"Later observation": "次の観測",
|
"Later observation": "次の観測",
|
||||||
"Latest": "最新",
|
"Latest": "最新",
|
||||||
|
"news list|Latest": "最新",
|
||||||
"Newest frame per product": "プロダクトごとの最新フレーム",
|
"Newest frame per product": "プロダクトごとの最新フレーム",
|
||||||
"Replay observed history": "観測履歴を再生",
|
"Replay observed history": "観測履歴を再生",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "画像はEsri上に表示 · 消去するとGoogle 3Dに戻ります",
|
"Imagery on Esri · Google 3D returns when cleared": "画像はEsri上に表示 · 消去するとGoogle 3Dに戻ります",
|
||||||
@@ -2730,7 +2743,9 @@
|
|||||||
"Contact cockpit summary": "コンタクトのコックピット概要",
|
"Contact cockpit summary": "コンタクトのコックピット概要",
|
||||||
"CONTACT": "コンタクト",
|
"CONTACT": "コンタクト",
|
||||||
"CONTACTS · 250 KM": "コンタクト · 250 km",
|
"CONTACTS · 250 KM": "コンタクト · 250 km",
|
||||||
"RINGS {spacing} NM": "距離環 {spacing}海里",
|
"RINGS {spacing} NM": {
|
||||||
|
"other": "距離環 {spacing}海里"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"other": "コンタクト {count}件"
|
"other": "コンタクト {count}件"
|
||||||
},
|
},
|
||||||
@@ -3057,6 +3072,7 @@
|
|||||||
"Left panels": "左パネル",
|
"Left panels": "左パネル",
|
||||||
"Right panels": "右パネル",
|
"Right panels": "右パネル",
|
||||||
"CONFIRMED": "確認済み",
|
"CONFIRMED": "確認済み",
|
||||||
|
"sightings filter|CONFIRMED": "確認済み",
|
||||||
"PAD / RTLS": "射場 / RTLS",
|
"PAD / RTLS": "射場 / RTLS",
|
||||||
"fire confidence filter|All": "すべて",
|
"fire confidence filter|All": "すべて",
|
||||||
"fire confidence filter|Nominal+": "中以上",
|
"fire confidence filter|Nominal+": "中以上",
|
||||||
@@ -3311,7 +3327,9 @@
|
|||||||
"Photo of {name}": "{name}の写真",
|
"Photo of {name}": "{name}の写真",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "表示範囲に{count}件の目撃情報",
|
"{count} sightings in view": {
|
||||||
|
"other": "表示範囲に{count}件の目撃情報"
|
||||||
|
},
|
||||||
"Close sighting": "目撃情報を閉じる",
|
"Close sighting": "目撃情報を閉じる",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "過去48時間以内に観察された野生生物(クジラ類は7日間)。色とバッジのアイコンは分類群を表します。保護が必要な種は0.2°のセルとしてのみ表示し、セイリッシュ海のAcartiaによる海生哺乳類の目撃情報は0.01°に丸めます。個体数調査ではありません。",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "過去48時間以内に観察された野生生物(クジラ類は7日間)。色とバッジのアイコンは分類群を表します。保護が必要な種は0.2°のセルとしてのみ表示し、セイリッシュ海のAcartiaによる海生哺乳類の目撃情報は0.01°に丸めます。個体数調査ではありません。",
|
||||||
"Open {name} on iNaturalist": "iNaturalistで{name}を開く",
|
"Open {name} on iNaturalist": "iNaturalistで{name}を開く",
|
||||||
@@ -3322,20 +3340,27 @@
|
|||||||
"{days}d": "{days}日",
|
"{days}d": "{days}日",
|
||||||
"All markers": "すべてのマーカー",
|
"All markers": "すべてのマーカー",
|
||||||
"Show markers of every age": "日付にかかわらずすべてのマーカーを表示",
|
"Show markers of every age": "日付にかかわらずすべてのマーカーを表示",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "イベントの最大経過日数:{days}日。日付のないマーカーは保持",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"other": "イベントの最大経過日数:{days}日。日付のないマーカーは保持"
|
||||||
|
},
|
||||||
"event age filter|All": "すべて",
|
"event age filter|All": "すべて",
|
||||||
"Event age": "経過日数",
|
"Event age": "経過日数",
|
||||||
"Event age: {days}d · undated kept": "経過日数:{days}日以内 · 日付なしは保持",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"other": "経過日数:{days}日以内 · 日付なしは保持"
|
||||||
|
},
|
||||||
"Close camera preview": "カメラのプレビューを閉じる",
|
"Close camera preview": "カメラのプレビューを閉じる",
|
||||||
"Refresh map still": "地図上の静止画を更新",
|
"Refresh map still": "地図上の静止画を更新",
|
||||||
"The map still uses a separate request and may show a different moment.": "地図上の静止画は別のリクエストで取得されるため、異なる時点を表示する場合があります。",
|
"The map still uses a separate request and may show a different moment.": "地図上の静止画は別のリクエストで取得されるため、異なる時点を表示する場合があります。",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "動画はQuébec 511から再生され、お使いのIPアドレスはQuébec 511側から見えます。",
|
"Video plays from Québec 511, which sees your IP address.": "動画はQuébec 511から再生され、お使いのIPアドレスはQuébec 511側から見えます。",
|
||||||
"Press OPEN to load the photo": "[開く]を押して写真を読み込む",
|
"Press OPEN to load the photo": "[開く]を押して写真を読み込む",
|
||||||
"Captured {month}": "{month}撮影",
|
"Captured {month}": "{month}撮影",
|
||||||
"this spot": "この地点",
|
|
||||||
"the view centre": "表示中心",
|
"the view centre": "表示中心",
|
||||||
"{source} at {place}": "{place}の{source}",
|
"{source} at {place}": "{place}の{source}",
|
||||||
|
"{source} at this spot": "この地点の{source}",
|
||||||
|
"{source} at the view centre": "表示中心の{source}",
|
||||||
"Street level at {place}": "{place}のストリートレベル",
|
"Street level at {place}": "{place}のストリートレベル",
|
||||||
|
"Street level at this spot": "この地点のストリートレベル",
|
||||||
|
"Street level at the view centre": "表示中心のストリートレベル",
|
||||||
"Switching map": "地図を切り替え中",
|
"Switching map": "地図を切り替え中",
|
||||||
"Opening imagery": "画像を開いています",
|
"Opening imagery": "画像を開いています",
|
||||||
"Street View is ready: press {open} to load it": "ストリートビューの準備ができました:[{open}]を押して読み込んでください",
|
"Street View is ready: press {open} to load it": "ストリートビューの準備ができました:[{open}]を押して読み込んでください",
|
||||||
@@ -3427,6 +3452,7 @@
|
|||||||
"{source} imagery could not be opened": "{source}の画像を開けませんでした",
|
"{source} imagery could not be opened": "{source}の画像を開けませんでした",
|
||||||
"{place} has no position to look for imagery at": "{place}には画像を探す位置がありません",
|
"{place} has no position to look for imagery at": "{place}には画像を探す位置がありません",
|
||||||
"No {source} imagery near {place}": "{place}の近くに{source}の画像はありません",
|
"No {source} imagery near {place}": "{place}の近くに{source}の画像はありません",
|
||||||
|
"No {source} imagery near the view centre": "表示中心の近くに{source}の画像はありません",
|
||||||
"{reason} (filters on: {filters})": "{reason}(有効なフィルター:{filters})",
|
"{reason} (filters on: {filters})": "{reason}(有効なフィルター:{filters})",
|
||||||
"tiles unavailable": "タイルを利用できません",
|
"tiles unavailable": "タイルを利用できません",
|
||||||
"zoom in to load this layer": "ズームインしてこのレイヤーを読み込む",
|
"zoom in to load this layer": "ズームインしてこのレイヤーを読み込む",
|
||||||
@@ -3517,13 +3543,8 @@
|
|||||||
"REMOVE ALL": "すべて削除",
|
"REMOVE ALL": "すべて削除",
|
||||||
"Confirm: remove every added entry from Data Layers": "確認:追加したすべての項目をデータレイヤーから削除",
|
"Confirm: remove every added entry from Data Layers": "確認:追加したすべての項目をデータレイヤーから削除",
|
||||||
"Remove every added entry from Data Layers": "追加したすべての項目をデータレイヤーから削除",
|
"Remove every added entry from Data Layers": "追加したすべての項目をデータレイヤーから削除",
|
||||||
"Showing {shown} of {total}": "{total}件中{shown}件を表示",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "データライブラリを見る",
|
"other": "{total}件中{shown}件を表示"
|
||||||
"The globe stopped drawing": "地球儀の描画が止まりました",
|
},
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "ブラウザーが地球儀の 3D グラフィックスを解放しました。レイヤーと操作は引き続き使えますが、ページを再読み込みするまで地図は描画されません。",
|
"BROWSE DATA LIBRARY": "データライブラリを見る"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "エラーのため描画が停止しました。レイヤーと操作は引き続き使えますが、地図は更新されません。",
|
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "「ビューをリセット」は、アドレスバーから復元したビューを使わずにアプリを再読み込みします。",
|
|
||||||
"Reload": "再読み込み",
|
|
||||||
"Retry": "再試行",
|
|
||||||
"Reset view": "ビューをリセット"
|
|
||||||
}
|
}
|
||||||
+62
-21
@@ -68,8 +68,16 @@
|
|||||||
"{severity} only": "Apenas {severity}",
|
"{severity} only": "Apenas {severity}",
|
||||||
"All categories": "Todas as categorias",
|
"All categories": "Todas as categorias",
|
||||||
"Reading the news…": "Lendo as notícias…",
|
"Reading the news…": "Lendo as notícias…",
|
||||||
"News, last {hours} hours": "Notícias de até {hours} h atrás",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "Até {hours} h atrás",
|
"one": "Notícias da última {hours} hora",
|
||||||
|
"many": "Notícias das últimas {hours} de horas",
|
||||||
|
"other": "Notícias das últimas {hours} horas"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "Última {hours} h",
|
||||||
|
"many": "Últimas {hours} h",
|
||||||
|
"other": "Últimas {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Período",
|
"Time window": "Período",
|
||||||
"Conflicts": "Conflitos",
|
"Conflicts": "Conflitos",
|
||||||
"Local": "Local",
|
"Local": "Local",
|
||||||
@@ -78,9 +86,17 @@
|
|||||||
"Hotspots": "Focos",
|
"Hotspots": "Focos",
|
||||||
"Severity": "Gravidade",
|
"Severity": "Gravidade",
|
||||||
"Category": "Categoria",
|
"Category": "Categoria",
|
||||||
"Nothing reported near here in the last {hours} h": "Nada noticiado aqui perto desde {hours} h atrás",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "Nada noticiado aqui perto na última {hours} h",
|
||||||
|
"many": "Nada noticiado aqui perto nas últimas {hours} h",
|
||||||
|
"other": "Nada noticiado aqui perto nas últimas {hours} h"
|
||||||
|
},
|
||||||
"Latest news": "Últimas notícias",
|
"Latest news": "Últimas notícias",
|
||||||
"No news matches these filters in the last {hours} h": "Nenhuma notícia de até {hours} h atrás corresponde a estes filtros",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "Nenhuma notícia da última {hours} h corresponde a estes filtros",
|
||||||
|
"many": "Nenhuma notícia das últimas {hours} h corresponde a estes filtros",
|
||||||
|
"other": "Nenhuma notícia das últimas {hours} h corresponde a estes filtros"
|
||||||
|
},
|
||||||
"Load older news": "Carregar notícias anteriores",
|
"Load older news": "Carregar notícias anteriores",
|
||||||
"How to read the map": "Como ler o mapa",
|
"How to read the map": "Como ler o mapa",
|
||||||
"Colour: severity · icon: type of event": "Cor: gravidade · ícone: tipo de evento",
|
"Colour: severity · icon: type of event": "Cor: gravidade · ícone: tipo de evento",
|
||||||
@@ -584,6 +600,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Anterior — contato visitado anteriormente na janela de 250 km",
|
"Previous — prior visited contact in the 250 km window": "Anterior — contato visitado anteriormente na janela de 250 km",
|
||||||
"PREVIOUS": "ANT.",
|
"PREVIOUS": "ANT.",
|
||||||
"FOCUS": "FOCAR",
|
"FOCUS": "FOCAR",
|
||||||
|
"site status|FOCUS": "EM FOCO",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Próximo — contato não visitado mais próximo na janela de 250 km",
|
"Next — nearest unvisited contact in the 250 km window": "Próximo — contato não visitado mais próximo na janela de 250 km",
|
||||||
"NEXT": "PRÓX.",
|
"NEXT": "PRÓX.",
|
||||||
"Named areas ({n})": "Áreas com nome ({n})",
|
"Named areas ({n})": "Áreas com nome ({n})",
|
||||||
@@ -939,7 +956,7 @@
|
|||||||
"ORBITAL VELOCITY": "VELOCIDADE ORBITAL",
|
"ORBITAL VELOCITY": "VELOCIDADE ORBITAL",
|
||||||
"SUB-SATELLITE POINT": "PONTO SUBSATÉLITE",
|
"SUB-SATELLITE POINT": "PONTO SUBSATÉLITE",
|
||||||
"NORTH UP": "NORTE PARA CIMA",
|
"NORTH UP": "NORTE PARA CIMA",
|
||||||
"CURRENTLY OVER": "SOBREVOANDO AGORA",
|
"CURRENTLY OVER": "SOB O SATÉLITE",
|
||||||
"{division}, {country}": "{division}, {country}",
|
"{division}, {country}": "{division}, {country}",
|
||||||
"{region} · near {place}": "{region} · perto de {place}",
|
"{region} · near {place}": "{region} · perto de {place}",
|
||||||
"NORAD {id}": "NORAD {id}",
|
"NORAD {id}": "NORAD {id}",
|
||||||
@@ -1832,7 +1849,11 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "DADOS DE ROTA INDISPONÍVEIS",
|
"ROUTE DATA UNAVAILABLE": "DADOS DE ROTA INDISPONÍVEIS",
|
||||||
"CONTACT NAVIGATION OFF": "NAVEGAÇÃO DE CONTATOS DESLIGADA",
|
"CONTACT NAVIGATION OFF": "NAVEGAÇÃO DE CONTATOS DESLIGADA",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "O PASSADIÇO PERMANECE NA EMBARCAÇÃO SELECIONADA",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "O PASSADIÇO PERMANECE NA EMBARCAÇÃO SELECIONADA",
|
||||||
"{range} NM": "{range} MN",
|
"{range} NM": {
|
||||||
|
"one": "{range} MN",
|
||||||
|
"many": "{range} MN",
|
||||||
|
"other": "{range} MN"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "DESTINO AIS · INFORMADO PELA TRIPULAÇÃO",
|
"AIS DESTINATION · CREW REPORTED": "DESTINO AIS · INFORMADO PELA TRIPULAÇÃO",
|
||||||
"DEST {bearing}": "DEST {bearing}",
|
"DEST {bearing}": "DEST {bearing}",
|
||||||
"Disable cockpit weather effects": "Desativar efeitos de clima na cabine",
|
"Disable cockpit weather effects": "Desativar efeitos de clima na cabine",
|
||||||
@@ -1879,7 +1900,8 @@
|
|||||||
"Restore real aircraft": "Restaurar a aeronave real",
|
"Restore real aircraft": "Restaurar a aeronave real",
|
||||||
"Reclassify as TR-3B": "Reclassificar como TR-3B",
|
"Reclassify as TR-3B": "Reclassificar como TR-3B",
|
||||||
"TRACK ACQUIRED": "RASTRO ADQUIRIDO",
|
"TRACK ACQUIRED": "RASTRO ADQUIRIDO",
|
||||||
"{label} · COURSE {heading}°": "{label} · RUMO {heading}°",
|
"{label} · COURSE {heading}°": "{label} · CURSO {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · RUMO {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · PROA {heading}°",
|
"{label} · HEADING {heading}°": "{label} · PROA {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · SEM PROA",
|
"{label} · NO HEADING": "{label} · SEM PROA",
|
||||||
"{label} · NADIR VIEW": "{label} · VISTA AO NADIR",
|
"{label} · NADIR VIEW": "{label} · VISTA AO NADIR",
|
||||||
@@ -2152,6 +2174,7 @@
|
|||||||
"Observed history": "Histórico observado",
|
"Observed history": "Histórico observado",
|
||||||
"Later observation": "Observação posterior",
|
"Later observation": "Observação posterior",
|
||||||
"Latest": "Mais recente",
|
"Latest": "Mais recente",
|
||||||
|
"news list|Latest": "Mais recentes",
|
||||||
"Newest frame per product": "Quadro mais recente por produto",
|
"Newest frame per product": "Quadro mais recente por produto",
|
||||||
"Replay observed history": "Reproduzir o histórico observado",
|
"Replay observed history": "Reproduzir o histórico observado",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Imagens sobre o Esri · LIMPAR restaura o Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "Imagens sobre o Esri · LIMPAR restaura o Google 3D",
|
||||||
@@ -2882,7 +2905,11 @@
|
|||||||
"Contact cockpit summary": "Resumo do contato na cabine",
|
"Contact cockpit summary": "Resumo do contato na cabine",
|
||||||
"CONTACT": "CONTATO",
|
"CONTACT": "CONTATO",
|
||||||
"CONTACTS · 250 KM": "CONTATOS · 250 KM",
|
"CONTACTS · 250 KM": "CONTATOS · 250 KM",
|
||||||
"RINGS {spacing} NM": "ANÉIS {spacing} MN",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "ANÉIS {spacing} MN",
|
||||||
|
"many": "ANÉIS {spacing} MN",
|
||||||
|
"other": "ANÉIS {spacing} MN"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} CONTATO",
|
"one": "{count} CONTATO",
|
||||||
"many": "{count} DE CONTATOS",
|
"many": "{count} DE CONTATOS",
|
||||||
@@ -3207,6 +3234,7 @@
|
|||||||
"Left panels": "Painéis à esquerda",
|
"Left panels": "Painéis à esquerda",
|
||||||
"Right panels": "Painéis à direita",
|
"Right panels": "Painéis à direita",
|
||||||
"CONFIRMED": "CONFIRMADA",
|
"CONFIRMED": "CONFIRMADA",
|
||||||
|
"sightings filter|CONFIRMED": "CONFIRMADOS",
|
||||||
"PAD / RTLS": "PLATAFORMA / RTLS",
|
"PAD / RTLS": "PLATAFORMA / RTLS",
|
||||||
"fire confidence filter|All": "Todas",
|
"fire confidence filter|All": "Todas",
|
||||||
"fire confidence filter|Nominal+": "Nominal+",
|
"fire confidence filter|Nominal+": "Nominal+",
|
||||||
@@ -3473,7 +3501,11 @@
|
|||||||
"Photo of {name}": "Foto de {name}",
|
"Photo of {name}": "Foto de {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Avistamentos na vista: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} avistamento na vista",
|
||||||
|
"many": "{count} de avistamentos na vista",
|
||||||
|
"other": "{count} avistamentos na vista"
|
||||||
|
},
|
||||||
"Close sighting": "Fechar avistamento",
|
"Close sighting": "Fechar avistamento",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Vida selvagem observada nas últimas 48 h (cetáceos 7 dias). A cor e o símbolo do emblema indicam o grupo taxonômico. Espécies sensíveis aparecem apenas em células de 0,2°; os relatos de mamíferos marinhos do mar de Salish via Acartia são arredondados para 0,01°. Não é um censo.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Vida selvagem observada nas últimas 48 h (cetáceos 7 dias). A cor e o símbolo do emblema indicam o grupo taxonômico. Espécies sensíveis aparecem apenas em células de 0,2°; os relatos de mamíferos marinhos do mar de Salish via Acartia são arredondados para 0,01°. Não é um censo.",
|
||||||
"Open {name} on iNaturalist": "Abrir {name} no iNaturalist",
|
"Open {name} on iNaturalist": "Abrir {name} no iNaturalist",
|
||||||
@@ -3484,20 +3516,31 @@
|
|||||||
"{days}d": "{days} d",
|
"{days}d": "{days} d",
|
||||||
"All markers": "Todos os marcadores",
|
"All markers": "Todos os marcadores",
|
||||||
"Show markers of every age": "Mostrar marcadores de qualquer idade",
|
"Show markers of every age": "Mostrar marcadores de qualquer idade",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Idade máxima do evento: {days} d; marcadores sem data são mantidos",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Idade máxima do evento: {days} dia; marcadores sem data são mantidos",
|
||||||
|
"many": "Idade máxima do evento: {days} de dias; marcadores sem data são mantidos",
|
||||||
|
"other": "Idade máxima do evento: {days} dias; marcadores sem data são mantidos"
|
||||||
|
},
|
||||||
"event age filter|All": "Todos",
|
"event age filter|All": "Todos",
|
||||||
"Event age": "Idade do evento",
|
"Event age": "Idade do evento",
|
||||||
"Event age: {days}d · undated kept": "Idade do evento: {days} d · sem data mantidos",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Idade do evento: {days} dia · sem data mantidos",
|
||||||
|
"many": "Idade do evento: {days} de dias · sem data mantidos",
|
||||||
|
"other": "Idade do evento: {days} dias · sem data mantidos"
|
||||||
|
},
|
||||||
"Close camera preview": "Fechar prévia da câmera",
|
"Close camera preview": "Fechar prévia da câmera",
|
||||||
"Refresh map still": "Atualizar imagem fixa no mapa",
|
"Refresh map still": "Atualizar imagem fixa no mapa",
|
||||||
"The map still uses a separate request and may show a different moment.": "A imagem no mapa usa uma solicitação separada e pode mostrar outro momento.",
|
"The map still uses a separate request and may show a different moment.": "A imagem no mapa usa uma solicitação separada e pode mostrar outro momento.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "O vídeo é reproduzido a partir do Québec 511, que vê o seu endereço IP.",
|
"Video plays from Québec 511, which sees your IP address.": "O vídeo é reproduzido a partir do Québec 511, que vê o seu endereço IP.",
|
||||||
"Press OPEN to load the photo": "Clique em ABRIR para carregar a foto",
|
"Press OPEN to load the photo": "Clique em ABRIR para carregar a foto",
|
||||||
"Captured {month}": "Capturada em {month}",
|
"Captured {month}": "Capturada em {month}",
|
||||||
"this spot": "este ponto",
|
|
||||||
"the view centre": "o centro da vista",
|
"the view centre": "o centro da vista",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} neste ponto",
|
||||||
|
"{source} at the view centre": "{source} no centro da vista",
|
||||||
"Street level at {place}": "Nível da rua: {place}",
|
"Street level at {place}": "Nível da rua: {place}",
|
||||||
|
"Street level at this spot": "Nível da rua neste ponto",
|
||||||
|
"Street level at the view centre": "Nível da rua no centro da vista",
|
||||||
"Switching map": "Mudando de mapa",
|
"Switching map": "Mudando de mapa",
|
||||||
"Opening imagery": "Abrindo imagens",
|
"Opening imagery": "Abrindo imagens",
|
||||||
"Street View is ready: press {open} to load it": "O Street View está pronto: clique em {open} para carregar",
|
"Street View is ready: press {open} to load it": "O Street View está pronto: clique em {open} para carregar",
|
||||||
@@ -3589,6 +3632,7 @@
|
|||||||
"{source} imagery could not be opened": "Não foi possível abrir as imagens de {source}",
|
"{source} imagery could not be opened": "Não foi possível abrir as imagens de {source}",
|
||||||
"{place} has no position to look for imagery at": "{place} não tem uma posição para buscar imagens",
|
"{place} has no position to look for imagery at": "{place} não tem uma posição para buscar imagens",
|
||||||
"No {source} imagery near {place}": "Nenhuma imagem de {source} perto de {place}",
|
"No {source} imagery near {place}": "Nenhuma imagem de {source} perto de {place}",
|
||||||
|
"No {source} imagery near the view centre": "Nenhuma imagem de {source} perto do centro da vista",
|
||||||
"{reason} (filters on: {filters})": "{reason} (filtros ativos: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (filtros ativos: {filters})",
|
||||||
"tiles unavailable": "blocos indisponíveis",
|
"tiles unavailable": "blocos indisponíveis",
|
||||||
"zoom in to load this layer": "aproxime o zoom para carregar esta camada",
|
"zoom in to load this layer": "aproxime o zoom para carregar esta camada",
|
||||||
@@ -3691,13 +3735,10 @@
|
|||||||
"REMOVE ALL": "REMOVER TUDO",
|
"REMOVE ALL": "REMOVER TUDO",
|
||||||
"Confirm: remove every added entry from Data Layers": "Confirmar: remover de \"Camadas de dados\" todas as entradas adicionadas",
|
"Confirm: remove every added entry from Data Layers": "Confirmar: remover de \"Camadas de dados\" todas as entradas adicionadas",
|
||||||
"Remove every added entry from Data Layers": "Remover de \"Camadas de dados\" todas as entradas adicionadas",
|
"Remove every added entry from Data Layers": "Remover de \"Camadas de dados\" todas as entradas adicionadas",
|
||||||
"Showing {shown} of {total}": "Mostrando {shown} de {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "EXPLORAR BIBLIOTECA DE DADOS",
|
"one": "Mostrando {shown} de {total}",
|
||||||
"The globe stopped drawing": "O globo parou de desenhar",
|
"many": "Mostrando {shown} de {total}",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "O navegador retirou os gráficos 3D do globo. As camadas e os controles continuam funcionando, mas o mapa só volta a ser desenhado depois de recarregar a página.",
|
"other": "Mostrando {shown} de {total}"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "A renderização parou após um erro. As camadas e os controles continuam funcionando, mas o mapa não está sendo atualizado.",
|
},
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "Redefinir vista recarrega o app sem a vista restaurada da barra de endereços.",
|
"BROWSE DATA LIBRARY": "EXPLORAR BIBLIOTECA DE DADOS"
|
||||||
"Reload": "Recarregar",
|
|
||||||
"Retry": "Tentar novamente",
|
|
||||||
"Reset view": "Redefinir vista"
|
|
||||||
}
|
}
|
||||||
+70
-19
@@ -71,8 +71,18 @@
|
|||||||
"{severity} only": "Только: {severity}",
|
"{severity} only": "Только: {severity}",
|
||||||
"All categories": "Все категории",
|
"All categories": "Все категории",
|
||||||
"Reading the news…": "Читаем новости…",
|
"Reading the news…": "Читаем новости…",
|
||||||
"News, last {hours} hours": "Новости за последние {hours} ч",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "За {hours} ч",
|
"one": "Новости за последний {hours} час",
|
||||||
|
"few": "Новости за последние {hours} часа",
|
||||||
|
"many": "Новости за последние {hours} часов",
|
||||||
|
"other": "Новости за последние {hours} часа"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "За {hours} ч",
|
||||||
|
"few": "За {hours} ч",
|
||||||
|
"many": "За {hours} ч",
|
||||||
|
"other": "За {hours} ч"
|
||||||
|
},
|
||||||
"Time window": "Период",
|
"Time window": "Период",
|
||||||
"Conflicts": "Конфликты",
|
"Conflicts": "Конфликты",
|
||||||
"Local": "Местные",
|
"Local": "Местные",
|
||||||
@@ -81,9 +91,19 @@
|
|||||||
"Hotspots": "Горячие точки",
|
"Hotspots": "Горячие точки",
|
||||||
"Severity": "Тяжесть",
|
"Severity": "Тяжесть",
|
||||||
"Category": "Категория",
|
"Category": "Категория",
|
||||||
"Nothing reported near here in the last {hours} h": "За последние {hours} ч поблизости ничего не сообщалось",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "За последний {hours} ч поблизости ничего не сообщалось",
|
||||||
|
"few": "За последние {hours} ч поблизости ничего не сообщалось",
|
||||||
|
"many": "За последние {hours} ч поблизости ничего не сообщалось",
|
||||||
|
"other": "За последние {hours} ч поблизости ничего не сообщалось"
|
||||||
|
},
|
||||||
"Latest news": "Последние новости",
|
"Latest news": "Последние новости",
|
||||||
"No news matches these filters in the last {hours} h": "За последние {hours} ч нет новостей по этим фильтрам",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "За последний {hours} ч нет новостей по этим фильтрам",
|
||||||
|
"few": "За последние {hours} ч нет новостей по этим фильтрам",
|
||||||
|
"many": "За последние {hours} ч нет новостей по этим фильтрам",
|
||||||
|
"other": "За последние {hours} ч нет новостей по этим фильтрам"
|
||||||
|
},
|
||||||
"Load older news": "Загрузить более ранние новости",
|
"Load older news": "Загрузить более ранние новости",
|
||||||
"How to read the map": "Как читать карту",
|
"How to read the map": "Как читать карту",
|
||||||
"Colour: severity · icon: type of event": "Цвет: тяжесть · значок: тип события",
|
"Colour: severity · icon: type of event": "Цвет: тяжесть · значок: тип события",
|
||||||
@@ -179,6 +199,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Назад — предыдущий посещённый контакт в радиусе 250 км",
|
"Previous — prior visited contact in the 250 km window": "Назад — предыдущий посещённый контакт в радиусе 250 км",
|
||||||
"PREVIOUS": "ПРЕД.",
|
"PREVIOUS": "ПРЕД.",
|
||||||
"FOCUS": "ФОКУС",
|
"FOCUS": "ФОКУС",
|
||||||
|
"site status|FOCUS": "ФОКУС",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Далее — ближайший непосещённый контакт в радиусе 250 км",
|
"Next — nearest unvisited contact in the 250 km window": "Далее — ближайший непосещённый контакт в радиусе 250 км",
|
||||||
"NEXT": "СЛЕД.",
|
"NEXT": "СЛЕД.",
|
||||||
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Контекст из открытых источников. Отсутствие трансляций, незагруженные области карты или неотмеченные объекты — не доказательство отсутствия.",
|
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Контекст из открытых источников. Отсутствие трансляций, незагруженные области карты или неотмеченные объекты — не доказательство отсутствия.",
|
||||||
@@ -346,6 +367,7 @@
|
|||||||
"Cancel replay": "Отменить повтор",
|
"Cancel replay": "Отменить повтор",
|
||||||
"EST. DOWNRANGE": "РАСЧЁТ ПО ТРАССЕ",
|
"EST. DOWNRANGE": "РАСЧЁТ ПО ТРАССЕ",
|
||||||
"CONFIRMED": "ПОДТВЕРЖДЕНО",
|
"CONFIRMED": "ПОДТВЕРЖДЕНО",
|
||||||
|
"sightings filter|CONFIRMED": "ПОДТВЕРЖДЕНО",
|
||||||
"PAD / RTLS": "ПЛОЩАДКА / RTLS",
|
"PAD / RTLS": "ПЛОЩАДКА / RTLS",
|
||||||
"PLANNED · ": "ПЛАН · ",
|
"PLANNED · ": "ПЛАН · ",
|
||||||
"SUPPLIED TRAJECTORY POINTS": "ПРЕДОСТАВЛЕННЫЕ ТОЧКИ ТРАЕКТОРИИ",
|
"SUPPLIED TRAJECTORY POINTS": "ПРЕДОСТАВЛЕННЫЕ ТОЧКИ ТРАЕКТОРИИ",
|
||||||
@@ -545,7 +567,12 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "ДАННЫЕ МАРШРУТА НЕДОСТУПНЫ",
|
"ROUTE DATA UNAVAILABLE": "ДАННЫЕ МАРШРУТА НЕДОСТУПНЫ",
|
||||||
"CONTACT NAVIGATION OFF": "НАВИГАЦИЯ ПО КОНТАКТАМ ВЫКЛЮЧЕНА",
|
"CONTACT NAVIGATION OFF": "НАВИГАЦИЯ ПО КОНТАКТАМ ВЫКЛЮЧЕНА",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "МОСТИК ОСТАЁТСЯ НА ВЫБРАННОМ СУДНЕ",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "МОСТИК ОСТАЁТСЯ НА ВЫБРАННОМ СУДНЕ",
|
||||||
"{range} NM": "{range} МОР. МИЛ.",
|
"{range} NM": {
|
||||||
|
"one": "{range} МОР. МИЛЯ",
|
||||||
|
"few": "{range} МОР. МИЛИ",
|
||||||
|
"many": "{range} МОР. МИЛЬ",
|
||||||
|
"other": "{range} МОР. МИЛИ"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "ПУНКТ НАЗНАЧЕНИЯ AIS · УКАЗАН ЭКИПАЖЕМ",
|
"AIS DESTINATION · CREW REPORTED": "ПУНКТ НАЗНАЧЕНИЯ AIS · УКАЗАН ЭКИПАЖЕМ",
|
||||||
"DEST {bearing}": "НАЗН. {bearing}",
|
"DEST {bearing}": "НАЗН. {bearing}",
|
||||||
"Disable cockpit weather effects": "Выключить погодные эффекты кабины",
|
"Disable cockpit weather effects": "Выключить погодные эффекты кабины",
|
||||||
@@ -591,6 +618,7 @@
|
|||||||
"Select flight {title}": "Выбрать рейс {title}",
|
"Select flight {title}": "Выбрать рейс {title}",
|
||||||
"TRACK ACQUIRED": "ЦЕЛЬ ЗАХВАЧЕНА",
|
"TRACK ACQUIRED": "ЦЕЛЬ ЗАХВАЧЕНА",
|
||||||
"{label} · COURSE {heading}°": "{label} · ПУТЕВОЙ УГОЛ {heading}°",
|
"{label} · COURSE {heading}°": "{label} · ПУТЕВОЙ УГОЛ {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · ПУТЕВОЙ УГОЛ {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · КУРС {heading}°",
|
"{label} · HEADING {heading}°": "{label} · КУРС {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · НЕТ КУРСА",
|
"{label} · NO HEADING": "{label} · НЕТ КУРСА",
|
||||||
"{label} · NADIR VIEW": "{label} · ВИД В НАДИР",
|
"{label} · NADIR VIEW": "{label} · ВИД В НАДИР",
|
||||||
@@ -842,7 +870,12 @@
|
|||||||
"EXIT BRIDGE": "ВЫЙТИ С МОСТИКА",
|
"EXIT BRIDGE": "ВЫЙТИ С МОСТИКА",
|
||||||
"EXIT ORBIT": "ВЫЙТИ С ОРБИТЫ",
|
"EXIT ORBIT": "ВЫЙТИ С ОРБИТЫ",
|
||||||
"CONTACTS · 250 KM": "КОНТАКТЫ · 250 КМ",
|
"CONTACTS · 250 KM": "КОНТАКТЫ · 250 КМ",
|
||||||
"RINGS {spacing} NM": "КОЛЬЦА {spacing} МОР. МИЛ.",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "КОЛЬЦА {spacing} МОР. МИЛЯ",
|
||||||
|
"few": "КОЛЬЦА {spacing} МОР. МИЛИ",
|
||||||
|
"many": "КОЛЬЦА {spacing} МОР. МИЛЬ",
|
||||||
|
"other": "КОЛЬЦА {spacing} МОР. МИЛИ"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} КОНТАКТ",
|
"one": "{count} КОНТАКТ",
|
||||||
"few": "{count} КОНТАКТА",
|
"few": "{count} КОНТАКТА",
|
||||||
@@ -1699,6 +1732,7 @@
|
|||||||
"Observed history": "История наблюдений",
|
"Observed history": "История наблюдений",
|
||||||
"Later observation": "Более позднее наблюдение",
|
"Later observation": "Более позднее наблюдение",
|
||||||
"Latest": "Последние",
|
"Latest": "Последние",
|
||||||
|
"news list|Latest": "Последние",
|
||||||
"Newest frame per product": "Самый свежий кадр каждого продукта",
|
"Newest frame per product": "Самый свежий кадр каждого продукта",
|
||||||
"Replay observed history": "Воспроизвести историю наблюдений",
|
"Replay observed history": "Воспроизвести историю наблюдений",
|
||||||
"S on another day replaces it · × unpins": "S в другой день заменяет его · × открепляет",
|
"S on another day replaces it · × unpins": "S в другой день заменяет его · × открепляет",
|
||||||
@@ -3554,7 +3588,12 @@
|
|||||||
"Photo of {name}": "Фото: {name}",
|
"Photo of {name}": "Фото: {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Наблюдений в кадре: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} наблюдение в кадре",
|
||||||
|
"few": "{count} наблюдения в кадре",
|
||||||
|
"many": "{count} наблюдений в кадре",
|
||||||
|
"other": "{count} наблюдения в кадре"
|
||||||
|
},
|
||||||
"Close sighting": "Закрыть наблюдение",
|
"Close sighting": "Закрыть наблюдение",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Наблюдения живой природы за 48 ч (китообразные — 7 дней). Цвет и символ значка обозначают таксономическую группу. Уязвимые виды показаны только ячейками 0,2°; сообщения Acartia о морских млекопитающих моря Селиш округлены до 0,01°. Это не учёт численности.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Наблюдения живой природы за 48 ч (китообразные — 7 дней). Цвет и символ значка обозначают таксономическую группу. Уязвимые виды показаны только ячейками 0,2°; сообщения Acartia о морских млекопитающих моря Селиш округлены до 0,01°. Это не учёт численности.",
|
||||||
"Open {name} on iNaturalist": "Открыть {name} в iNaturalist",
|
"Open {name} on iNaturalist": "Открыть {name} в iNaturalist",
|
||||||
@@ -3565,20 +3604,33 @@
|
|||||||
"{days}d": "{days} дн.",
|
"{days}d": "{days} дн.",
|
||||||
"All markers": "Все метки",
|
"All markers": "Все метки",
|
||||||
"Show markers of every age": "Показать метки любого возраста",
|
"Show markers of every age": "Показать метки любого возраста",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Максимальный возраст события: {days} дн.; метки без даты остаются",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Максимальный возраст события: {days} дн.; метки без даты остаются",
|
||||||
|
"few": "Максимальный возраст события: {days} дн.; метки без даты остаются",
|
||||||
|
"many": "Максимальный возраст события: {days} дн.; метки без даты остаются",
|
||||||
|
"other": "Максимальный возраст события: {days} дн.; метки без даты остаются"
|
||||||
|
},
|
||||||
"event age filter|All": "Все",
|
"event age filter|All": "Все",
|
||||||
"Event age": "Возраст события",
|
"Event age": "Возраст события",
|
||||||
"Event age: {days}d · undated kept": "Возраст события: {days} дн. · метки без даты остаются",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Возраст события: {days} дн. · метки без даты остаются",
|
||||||
|
"few": "Возраст события: {days} дн. · метки без даты остаются",
|
||||||
|
"many": "Возраст события: {days} дн. · метки без даты остаются",
|
||||||
|
"other": "Возраст события: {days} дн. · метки без даты остаются"
|
||||||
|
},
|
||||||
"Close camera preview": "Закрыть просмотр камеры",
|
"Close camera preview": "Закрыть просмотр камеры",
|
||||||
"Refresh map still": "Обновить кадр на карте",
|
"Refresh map still": "Обновить кадр на карте",
|
||||||
"The map still uses a separate request and may show a different moment.": "Кадр на карте запрашивается отдельно и может показывать другой момент.",
|
"The map still uses a separate request and may show a different moment.": "Кадр на карте запрашивается отдельно и может показывать другой момент.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "Видео воспроизводится с Québec 511, который видит ваш IP-адрес.",
|
"Video plays from Québec 511, which sees your IP address.": "Видео воспроизводится с Québec 511, который видит ваш IP-адрес.",
|
||||||
"Press OPEN to load the photo": "Нажмите ОТКРЫТЬ, чтобы загрузить снимок",
|
"Press OPEN to load the photo": "Нажмите ОТКРЫТЬ, чтобы загрузить снимок",
|
||||||
"Captured {month}": "Снято: {month}",
|
"Captured {month}": "Снято: {month}",
|
||||||
"this spot": "эта точка",
|
|
||||||
"the view centre": "центр вида",
|
"the view centre": "центр вида",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} в этой точке",
|
||||||
|
"{source} at the view centre": "{source} в центре вида",
|
||||||
"Street level at {place}": "Уличные снимки: {place}",
|
"Street level at {place}": "Уличные снимки: {place}",
|
||||||
|
"Street level at this spot": "Уличные снимки в этой точке",
|
||||||
|
"Street level at the view centre": "Уличные снимки в центре вида",
|
||||||
"Switching map": "Смена карты",
|
"Switching map": "Смена карты",
|
||||||
"Opening imagery": "Открытие снимков",
|
"Opening imagery": "Открытие снимков",
|
||||||
"Street View is ready: press {open} to load it": "Просмотр улиц готов: нажмите {open}, чтобы загрузить",
|
"Street View is ready: press {open} to load it": "Просмотр улиц готов: нажмите {open}, чтобы загрузить",
|
||||||
@@ -3670,6 +3722,7 @@
|
|||||||
"{source} imagery could not be opened": "Не удалось открыть снимки {source}",
|
"{source} imagery could not be opened": "Не удалось открыть снимки {source}",
|
||||||
"{place} has no position to look for imagery at": "У места «{place}» нет координат для поиска снимков",
|
"{place} has no position to look for imagery at": "У места «{place}» нет координат для поиска снимков",
|
||||||
"No {source} imagery near {place}": "Нет снимков {source} рядом с местом «{place}»",
|
"No {source} imagery near {place}": "Нет снимков {source} рядом с местом «{place}»",
|
||||||
|
"No {source} imagery near the view centre": "Нет снимков {source} рядом с центром вида",
|
||||||
"{reason} (filters on: {filters})": "{reason} (включены фильтры: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (включены фильтры: {filters})",
|
||||||
"tiles unavailable": "тайлы недоступны",
|
"tiles unavailable": "тайлы недоступны",
|
||||||
"Environment": "Окружающая среда",
|
"Environment": "Окружающая среда",
|
||||||
@@ -3758,7 +3811,12 @@
|
|||||||
"switch on a dataset to load features": "включите набор данных, чтобы загрузить объекты",
|
"switch on a dataset to load features": "включите набор данных, чтобы загрузить объекты",
|
||||||
"Data Library at a glance": "Библиотека данных вкратце",
|
"Data Library at a glance": "Библиотека данных вкратце",
|
||||||
"Map features": "Объекты на карте",
|
"Map features": "Объекты на карте",
|
||||||
"Showing {shown} of {total}": "Показано {shown} из {total}",
|
"Showing {shown} of {total}": {
|
||||||
|
"one": "Показано {shown} из {total}",
|
||||||
|
"few": "Показано {shown} из {total}",
|
||||||
|
"many": "Показано {shown} из {total}",
|
||||||
|
"other": "Показано {shown} из {total}"
|
||||||
|
},
|
||||||
"Datasets": "Наборы данных",
|
"Datasets": "Наборы данных",
|
||||||
"Themes": "Темы",
|
"Themes": "Темы",
|
||||||
"zoom in to load this layer": "приблизьте, чтобы загрузить этот слой",
|
"zoom in to load this layer": "приблизьте, чтобы загрузить этот слой",
|
||||||
@@ -3779,12 +3837,5 @@
|
|||||||
"500–1,000 m": "500–1000 м",
|
"500–1,000 m": "500–1000 м",
|
||||||
"1,000–2,000 m": "1000–2000 м",
|
"1,000–2,000 m": "1000–2000 м",
|
||||||
"2,000–3,000 m": "2000–3000 м",
|
"2,000–3,000 m": "2000–3000 м",
|
||||||
"3,000 m and above": "3000 м и выше",
|
"3,000 m and above": "3000 м и выше"
|
||||||
"The globe stopped drawing": "Глобус перестал отрисовываться",
|
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Браузер отключил 3D-графику глобуса. Слои и элементы управления работают, но карта не отрисуется, пока страница не будет перезагружена.",
|
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Отрисовка остановилась из-за ошибки. Слои и элементы управления работают, но карта не обновляется.",
|
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "«Сбросить вид» перезагружает приложение без вида, восстановленного из адресной строки.",
|
|
||||||
"Reload": "Перезагрузить",
|
|
||||||
"Retry": "Повторить",
|
|
||||||
"Reset view": "Сбросить вид"
|
|
||||||
}
|
}
|
||||||
+50
-19
@@ -67,18 +67,30 @@
|
|||||||
"{severity} and above": "{severity} och uppåt",
|
"{severity} and above": "{severity} och uppåt",
|
||||||
"{severity} only": "Endast ”{severity}”",
|
"{severity} only": "Endast ”{severity}”",
|
||||||
"All categories": "Alla kategorier",
|
"All categories": "Alla kategorier",
|
||||||
"Last {hours} h": "Senaste {hours} h",
|
"Last {hours} h": {
|
||||||
|
"one": "Senaste {hours} h",
|
||||||
|
"other": "Senaste {hours} h"
|
||||||
|
},
|
||||||
"Reading the news…": "Läser in nyheterna …",
|
"Reading the news…": "Läser in nyheterna …",
|
||||||
"News, last {hours} hours": "Nyheter, senaste {hours} h",
|
"News, last {hours} hours": {
|
||||||
|
"one": "Nyheter, senaste {hours} h",
|
||||||
|
"other": "Nyheter, senaste {hours} h"
|
||||||
|
},
|
||||||
"Time window": "Tidsfönster",
|
"Time window": "Tidsfönster",
|
||||||
"News sections": "Nyhetssektioner",
|
"News sections": "Nyhetssektioner",
|
||||||
"Hotspots": "Oroshärdar",
|
"Hotspots": "Oroshärdar",
|
||||||
"Severity": "Allvarlighetsgrad",
|
"Severity": "Allvarlighetsgrad",
|
||||||
"Category": "Kategori",
|
"Category": "Kategori",
|
||||||
"Zoom in to see local news": "Zooma in för att se lokala nyheter",
|
"Zoom in to see local news": "Zooma in för att se lokala nyheter",
|
||||||
"Nothing reported near here in the last {hours} h": "Inget rapporterat i närheten senaste {hours} h",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "Inget rapporterat i närheten senaste {hours} h",
|
||||||
|
"other": "Inget rapporterat i närheten senaste {hours} h"
|
||||||
|
},
|
||||||
"Latest news": "Senaste nyheterna",
|
"Latest news": "Senaste nyheterna",
|
||||||
"No news matches these filters in the last {hours} h": "Inga nyheter matchar filtren senaste {hours} h",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "Inga nyheter matchar filtren senaste {hours} h",
|
||||||
|
"other": "Inga nyheter matchar filtren senaste {hours} h"
|
||||||
|
},
|
||||||
"Load older news": "Ladda äldre nyheter",
|
"Load older news": "Ladda äldre nyheter",
|
||||||
"How to read the map": "Så läses kartan",
|
"How to read the map": "Så läses kartan",
|
||||||
"Colour: severity · icon: type of event": "Färg: allvarlighetsgrad · ikon: typ av händelse",
|
"Colour: severity · icon: type of event": "Färg: allvarlighetsgrad · ikon: typ av händelse",
|
||||||
@@ -198,6 +210,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Förra – senast besökta kontakt inom 250 km",
|
"Previous — prior visited contact in the 250 km window": "Förra – senast besökta kontakt inom 250 km",
|
||||||
"PREVIOUS": "FÖRRA",
|
"PREVIOUS": "FÖRRA",
|
||||||
"FOCUS": "FOKUS",
|
"FOCUS": "FOKUS",
|
||||||
|
"site status|FOCUS": "FOKUS",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Nästa – närmaste obesökta kontakt inom 250 km",
|
"Next — nearest unvisited contact in the 250 km window": "Nästa – närmaste obesökta kontakt inom 250 km",
|
||||||
"NEXT": "NÄSTA",
|
"NEXT": "NÄSTA",
|
||||||
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Kartlagd/observerad kontext från öppna källor. Uteblivna sändningar, kartområden som inte har laddats eller platser som inte är kartlagda bevisar inte att något saknas.",
|
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "Kartlagd/observerad kontext från öppna källor. Uteblivna sändningar, kartområden som inte har laddats eller platser som inte är kartlagda bevisar inte att något saknas.",
|
||||||
@@ -565,6 +578,7 @@
|
|||||||
"Reclassify as TR-3B": "Klassa om som TR-3B",
|
"Reclassify as TR-3B": "Klassa om som TR-3B",
|
||||||
"TRACK ACQUIRED": "SPÅR LÅST",
|
"TRACK ACQUIRED": "SPÅR LÅST",
|
||||||
"{label} · COURSE {heading}°": "{label} · FÄRDRIKTNING {heading}°",
|
"{label} · COURSE {heading}°": "{label} · FÄRDRIKTNING {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · FÄRDRIKTNING {heading}°",
|
||||||
"Context could not restore every layer; try again": "Läget ”Kontext” kunde inte återställa alla lager – försök igen",
|
"Context could not restore every layer; try again": "Läget ”Kontext” kunde inte återställa alla lager – försök igen",
|
||||||
"No selected data layers": "Inga valda datalager",
|
"No selected data layers": "Inga valda datalager",
|
||||||
"{count} data layers could not be cleared": {
|
"{count} data layers could not be cleared": {
|
||||||
@@ -2326,6 +2340,7 @@
|
|||||||
"Observed history": "Observerad historik",
|
"Observed history": "Observerad historik",
|
||||||
"Later observation": "Senare observation",
|
"Later observation": "Senare observation",
|
||||||
"Latest": "Senaste",
|
"Latest": "Senaste",
|
||||||
|
"news list|Latest": "Senaste",
|
||||||
"Newest frame per product": "Senaste bildruta per produkt",
|
"Newest frame per product": "Senaste bildruta per produkt",
|
||||||
"Replay observed history": "Spela upp observerad historik",
|
"Replay observed history": "Spela upp observerad historik",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Bilder på Esri · Google 3D återkommer när de rensas",
|
"Imagery on Esri · Google 3D returns when cleared": "Bilder på Esri · Google 3D återkommer när de rensas",
|
||||||
@@ -3049,6 +3064,7 @@
|
|||||||
"Left panels": "Vänstra paneler",
|
"Left panels": "Vänstra paneler",
|
||||||
"Right panels": "Högra paneler",
|
"Right panels": "Högra paneler",
|
||||||
"CONFIRMED": "BEKRÄFTAD",
|
"CONFIRMED": "BEKRÄFTAD",
|
||||||
|
"sightings filter|CONFIRMED": "BEKRÄFTADE",
|
||||||
"PAD / RTLS": "RAMP / RTLS",
|
"PAD / RTLS": "RAMP / RTLS",
|
||||||
"fire confidence filter|All": "Alla",
|
"fire confidence filter|All": "Alla",
|
||||||
"fire confidence filter|Nominal+": "Normal+",
|
"fire confidence filter|Nominal+": "Normal+",
|
||||||
@@ -3309,7 +3325,10 @@
|
|||||||
"Photo of {name}": "Foto av {name}",
|
"Photo of {name}": "Foto av {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Observationer i vyn: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} observation i vyn",
|
||||||
|
"other": "{count} observationer i vyn"
|
||||||
|
},
|
||||||
"Close sighting": "Stäng observationen",
|
"Close sighting": "Stäng observationen",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Djur, växter och svampar observerade de senaste 48 h (valar 7 d). Färg och symbol visar artgruppen. Känsliga arter visas bara som rutor på 0,2°; rapporter om marina däggdjur i Salish Sea via Acartia avrundas till 0,01°. Ingen inventering.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Djur, växter och svampar observerade de senaste 48 h (valar 7 d). Färg och symbol visar artgruppen. Känsliga arter visas bara som rutor på 0,2°; rapporter om marina däggdjur i Salish Sea via Acartia avrundas till 0,01°. Ingen inventering.",
|
||||||
"Open {name} on iNaturalist": "Öppna {name} på iNaturalist",
|
"Open {name} on iNaturalist": "Öppna {name} på iNaturalist",
|
||||||
@@ -3320,20 +3339,29 @@
|
|||||||
"{days}d": "{days} d",
|
"{days}d": "{days} d",
|
||||||
"All markers": "Alla markörer",
|
"All markers": "Alla markörer",
|
||||||
"Show markers of every age": "Visa markörer oavsett ålder",
|
"Show markers of every age": "Visa markörer oavsett ålder",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Högsta ålder på händelser: {days} d; markörer utan datum behålls",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Högsta ålder på händelser: {days} d; markörer utan datum behålls",
|
||||||
|
"other": "Högsta ålder på händelser: {days} d; markörer utan datum behålls"
|
||||||
|
},
|
||||||
"event age filter|All": "Alla",
|
"event age filter|All": "Alla",
|
||||||
"Event age": "Händelsernas ålder",
|
"Event age": "Händelsernas ålder",
|
||||||
"Event age: {days}d · undated kept": "Händelsernas ålder: {days} d · utan datum behålls",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Händelsernas ålder: {days} d · utan datum behålls",
|
||||||
|
"other": "Händelsernas ålder: {days} d · utan datum behålls"
|
||||||
|
},
|
||||||
"Close camera preview": "Stäng förhandsvisningen av kameran",
|
"Close camera preview": "Stäng förhandsvisningen av kameran",
|
||||||
"Refresh map still": "Uppdatera stillbilden på kartan",
|
"Refresh map still": "Uppdatera stillbilden på kartan",
|
||||||
"The map still uses a separate request and may show a different moment.": "Stillbilden på kartan hämtas separat och kan visa ett annat ögonblick.",
|
"The map still uses a separate request and may show a different moment.": "Stillbilden på kartan hämtas separat och kan visa ett annat ögonblick.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "Videon spelas upp från Québec 511, som ser din IP-adress.",
|
"Video plays from Québec 511, which sees your IP address.": "Videon spelas upp från Québec 511, som ser din IP-adress.",
|
||||||
"Press OPEN to load the photo": "Tryck på ÖPPNA för att ladda fotot",
|
"Press OPEN to load the photo": "Tryck på ÖPPNA för att ladda fotot",
|
||||||
"Captured {month}": "Tagen {month}",
|
"Captured {month}": "Tagen {month}",
|
||||||
"this spot": "den här platsen",
|
|
||||||
"the view centre": "vyns mitt",
|
"the view centre": "vyns mitt",
|
||||||
"{source} at {place}": "{source} vid {place}",
|
"{source} at {place}": "{source} vid {place}",
|
||||||
|
"{source} at this spot": "{source} på den här platsen",
|
||||||
|
"{source} at the view centre": "{source} i vyns mitt",
|
||||||
"Street level at {place}": "Gatunivå vid {place}",
|
"Street level at {place}": "Gatunivå vid {place}",
|
||||||
|
"Street level at this spot": "Gatunivå på den här platsen",
|
||||||
|
"Street level at the view centre": "Gatunivå i vyns mitt",
|
||||||
"Switching map": "Byter karta",
|
"Switching map": "Byter karta",
|
||||||
"Opening imagery": "Öppnar bilder",
|
"Opening imagery": "Öppnar bilder",
|
||||||
"Street View is ready: press {open} to load it": "Street View är redo: tryck på {open} för att ladda den",
|
"Street View is ready: press {open} to load it": "Street View är redo: tryck på {open} för att ladda den",
|
||||||
@@ -3367,7 +3395,10 @@
|
|||||||
"LIVE AIS": "LIVE-AIS",
|
"LIVE AIS": "LIVE-AIS",
|
||||||
"CONTACT NAVIGATION OFF": "KONTAKTNAVIGERING AV",
|
"CONTACT NAVIGATION OFF": "KONTAKTNAVIGERING AV",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "BRYGGAN STANNAR PÅ DET VALDA FARTYGET",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "BRYGGAN STANNAR PÅ DET VALDA FARTYGET",
|
||||||
"{range} NM": "{range} NM",
|
"{range} NM": {
|
||||||
|
"one": "{range} NM",
|
||||||
|
"other": "{range} NM"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "AIS-DESTINATION · ANGIVEN AV BESÄTTNINGEN",
|
"AIS DESTINATION · CREW REPORTED": "AIS-DESTINATION · ANGIVEN AV BESÄTTNINGEN",
|
||||||
"{label} · NADIR VIEW": "{label} · NADIRVY",
|
"{label} · NADIR VIEW": "{label} · NADIRVY",
|
||||||
"SITES STANDBY": "PLATSER I VÄNTLÄGE",
|
"SITES STANDBY": "PLATSER I VÄNTLÄGE",
|
||||||
@@ -3446,7 +3477,10 @@
|
|||||||
"Zoom in (+)": "Zooma in (+)",
|
"Zoom in (+)": "Zooma in (+)",
|
||||||
"Zoom to the imaging sensor swath": "Zooma till sensorns avbildningsstråk",
|
"Zoom to the imaging sensor swath": "Zooma till sensorns avbildningsstråk",
|
||||||
"SENSOR": "SENSOR",
|
"SENSOR": "SENSOR",
|
||||||
"RINGS {spacing} NM": "RINGAR {spacing} NM",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "RINGAR {spacing} NM",
|
||||||
|
"other": "RINGAR {spacing} NM"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} KONTAKT",
|
"one": "{count} KONTAKT",
|
||||||
"other": "{count} KONTAKTER"
|
"other": "{count} KONTAKTER"
|
||||||
@@ -3500,6 +3534,7 @@
|
|||||||
"{source} imagery could not be opened": "Bilder från {source} kunde inte öppnas",
|
"{source} imagery could not be opened": "Bilder från {source} kunde inte öppnas",
|
||||||
"{place} has no position to look for imagery at": "{place} har ingen position att söka efter bilder vid",
|
"{place} has no position to look for imagery at": "{place} har ingen position att söka efter bilder vid",
|
||||||
"No {source} imagery near {place}": "Inga bilder från {source} nära {place}",
|
"No {source} imagery near {place}": "Inga bilder från {source} nära {place}",
|
||||||
|
"No {source} imagery near the view centre": "Inga bilder från {source} nära vyns mitt",
|
||||||
"{reason} (filters on: {filters})": "{reason} (aktiva filter: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (aktiva filter: {filters})",
|
||||||
"1 h/s": "1 h/s",
|
"1 h/s": "1 h/s",
|
||||||
"1 d/s": "1 d/s",
|
"1 d/s": "1 d/s",
|
||||||
@@ -3604,13 +3639,9 @@
|
|||||||
"REMOVE ALL": "TA BORT ALLA",
|
"REMOVE ALL": "TA BORT ALLA",
|
||||||
"Confirm: remove every added entry from Data Layers": "Bekräfta: ta bort alla tillagda poster från Datalager",
|
"Confirm: remove every added entry from Data Layers": "Bekräfta: ta bort alla tillagda poster från Datalager",
|
||||||
"Remove every added entry from Data Layers": "Ta bort alla tillagda poster från Datalager",
|
"Remove every added entry from Data Layers": "Ta bort alla tillagda poster från Datalager",
|
||||||
"Showing {shown} of {total}": "Visar {shown} av {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "BLÄDDRA I DATABIBLIOTEKET",
|
"one": "Visar {shown} av {total}",
|
||||||
"The globe stopped drawing": "Globen ritas inte längre",
|
"other": "Visar {shown} av {total}"
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Webbläsaren tog tillbaka globens 3D-grafik. Lager och reglage fungerar fortfarande, men kartan kan inte ritas förrän sidan laddas om.",
|
},
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Renderingen stoppades efter ett fel. Lager och reglage fungerar fortfarande, men kartan uppdateras inte.",
|
"BROWSE DATA LIBRARY": "BLÄDDRA I DATABIBLIOTEKET"
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "”Återställ vy” laddar om appen utan vyn som återställdes från adressfältet.",
|
|
||||||
"Reload": "Ladda om",
|
|
||||||
"Retry": "Försök igen",
|
|
||||||
"Reset view": "Återställ vy"
|
|
||||||
}
|
}
|
||||||
+71
-20
@@ -71,8 +71,18 @@
|
|||||||
"{severity} only": "Лише {severity}",
|
"{severity} only": "Лише {severity}",
|
||||||
"All categories": "Усі категорії",
|
"All categories": "Усі категорії",
|
||||||
"Reading the news…": "Читаємо новини…",
|
"Reading the news…": "Читаємо новини…",
|
||||||
"News, last {hours} hours": "Новини за {hours} год",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "За {hours} год",
|
"one": "Новини за останню {hours} годину",
|
||||||
|
"few": "Новини за останні {hours} години",
|
||||||
|
"many": "Новини за останні {hours} годин",
|
||||||
|
"other": "Новини за останні {hours} години"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"one": "За {hours} год",
|
||||||
|
"few": "За {hours} год",
|
||||||
|
"many": "За {hours} год",
|
||||||
|
"other": "За {hours} год"
|
||||||
|
},
|
||||||
"Time window": "Період",
|
"Time window": "Період",
|
||||||
"Conflicts": "Конфлікти",
|
"Conflicts": "Конфлікти",
|
||||||
"Local": "Місцеві",
|
"Local": "Місцеві",
|
||||||
@@ -81,9 +91,19 @@
|
|||||||
"Hotspots": "Гарячі точки",
|
"Hotspots": "Гарячі точки",
|
||||||
"Severity": "Тяжкість",
|
"Severity": "Тяжкість",
|
||||||
"Category": "Категорія",
|
"Category": "Категорія",
|
||||||
"Nothing reported near here in the last {hours} h": "За {hours} год поблизу нічого не повідомлялося",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"one": "За останню {hours} год поблизу нічого не повідомлялося",
|
||||||
|
"few": "За останні {hours} год поблизу нічого не повідомлялося",
|
||||||
|
"many": "За останні {hours} год поблизу нічого не повідомлялося",
|
||||||
|
"other": "За останні {hours} год поблизу нічого не повідомлялося"
|
||||||
|
},
|
||||||
"Latest news": "Останні новини",
|
"Latest news": "Останні новини",
|
||||||
"No news matches these filters in the last {hours} h": "За {hours} год немає новин за цими фільтрами",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"one": "За останню {hours} год немає новин за цими фільтрами",
|
||||||
|
"few": "За останні {hours} год немає новин за цими фільтрами",
|
||||||
|
"many": "За останні {hours} год немає новин за цими фільтрами",
|
||||||
|
"other": "За останні {hours} год немає новин за цими фільтрами"
|
||||||
|
},
|
||||||
"Load older news": "Завантажити давніші новини",
|
"Load older news": "Завантажити давніші новини",
|
||||||
"How to read the map": "Як читати карту",
|
"How to read the map": "Як читати карту",
|
||||||
"Colour: severity · icon: type of event": "Колір: тяжкість · значок: тип події",
|
"Colour: severity · icon: type of event": "Колір: тяжкість · значок: тип події",
|
||||||
@@ -594,6 +614,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "Назад — попередній переглянутий контакт у радіусі 250 км",
|
"Previous — prior visited contact in the 250 km window": "Назад — попередній переглянутий контакт у радіусі 250 км",
|
||||||
"PREVIOUS": "НАЗАД",
|
"PREVIOUS": "НАЗАД",
|
||||||
"FOCUS": "ФОКУС",
|
"FOCUS": "ФОКУС",
|
||||||
|
"site status|FOCUS": "ФОКУС",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "Далі — найближчий ще не переглянутий контакт у радіусі 250 км",
|
"Next — nearest unvisited contact in the 250 km window": "Далі — найближчий ще не переглянутий контакт у радіусі 250 км",
|
||||||
"NEXT": "ДАЛІ",
|
"NEXT": "ДАЛІ",
|
||||||
"Named areas ({n})": "Іменовані зони ({n})",
|
"Named areas ({n})": "Іменовані зони ({n})",
|
||||||
@@ -956,7 +977,7 @@
|
|||||||
"ORBITAL VELOCITY": "ОРБІТАЛЬНА ШВИДКІСТЬ",
|
"ORBITAL VELOCITY": "ОРБІТАЛЬНА ШВИДКІСТЬ",
|
||||||
"SUB-SATELLITE POINT": "ПІДСУПУТНИКОВА ТОЧКА",
|
"SUB-SATELLITE POINT": "ПІДСУПУТНИКОВА ТОЧКА",
|
||||||
"NORTH UP": "ПІВНІЧ УГОРІ",
|
"NORTH UP": "ПІВНІЧ УГОРІ",
|
||||||
"CURRENTLY OVER": "ЗАРАЗ НАД",
|
"CURRENTLY OVER": "ПІД СУПУТНИКОМ",
|
||||||
"{division}, {country}": "{division}, {country}",
|
"{division}, {country}": "{division}, {country}",
|
||||||
"{region} · near {place}": "{region} · поблизу: {place}",
|
"{region} · near {place}": "{region} · поблизу: {place}",
|
||||||
"NORAD {id}": "NORAD {id}",
|
"NORAD {id}": "NORAD {id}",
|
||||||
@@ -1859,7 +1880,12 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "ДАНІ МАРШРУТУ НЕДОСТУПНІ",
|
"ROUTE DATA UNAVAILABLE": "ДАНІ МАРШРУТУ НЕДОСТУПНІ",
|
||||||
"CONTACT NAVIGATION OFF": "НАВІГАЦІЮ КОНТАКТАМИ ВИМКНЕНО",
|
"CONTACT NAVIGATION OFF": "НАВІГАЦІЮ КОНТАКТАМИ ВИМКНЕНО",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "МІСТОК ЗАЛИШАЄТЬСЯ НА ВИБРАНОМУ СУДНІ",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "МІСТОК ЗАЛИШАЄТЬСЯ НА ВИБРАНОМУ СУДНІ",
|
||||||
"{range} NM": "{range} МОР. МИЛ.",
|
"{range} NM": {
|
||||||
|
"one": "{range} МОР. МИЛЯ",
|
||||||
|
"few": "{range} МОР. МИЛІ",
|
||||||
|
"many": "{range} МОР. МИЛЬ",
|
||||||
|
"other": "{range} МОР. МИЛІ"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "ПРИЗНАЧЕННЯ AIS · ВКАЗАНО ЕКІПАЖЕМ",
|
"AIS DESTINATION · CREW REPORTED": "ПРИЗНАЧЕННЯ AIS · ВКАЗАНО ЕКІПАЖЕМ",
|
||||||
"DEST {bearing}": "ПРИЗН. {bearing}",
|
"DEST {bearing}": "ПРИЗН. {bearing}",
|
||||||
"Disable cockpit weather effects": "Вимкнути погодні ефекти кабіни",
|
"Disable cockpit weather effects": "Вимкнути погодні ефекти кабіни",
|
||||||
@@ -1908,6 +1934,7 @@
|
|||||||
"Reclassify as TR-3B": "Перекласифікувати як TR-3B",
|
"Reclassify as TR-3B": "Перекласифікувати як TR-3B",
|
||||||
"TRACK ACQUIRED": "ТРЕК ЗАХОПЛЕНО",
|
"TRACK ACQUIRED": "ТРЕК ЗАХОПЛЕНО",
|
||||||
"{label} · COURSE {heading}°": "{label} · ШЛЯХОВИЙ КУТ {heading}°",
|
"{label} · COURSE {heading}°": "{label} · ШЛЯХОВИЙ КУТ {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · ШЛЯХОВИЙ КУТ {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · КУРС {heading}°",
|
"{label} · HEADING {heading}°": "{label} · КУРС {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · НЕМАЄ КУРСУ",
|
"{label} · NO HEADING": "{label} · НЕМАЄ КУРСУ",
|
||||||
"{label} · NADIR VIEW": "{label} · ВИГЛЯД У НАДИР",
|
"{label} · NADIR VIEW": "{label} · ВИГЛЯД У НАДИР",
|
||||||
@@ -2188,6 +2215,7 @@
|
|||||||
"Observed history": "Історія спостережень",
|
"Observed history": "Історія спостережень",
|
||||||
"Later observation": "Наступне спостереження",
|
"Later observation": "Наступне спостереження",
|
||||||
"Latest": "Останні",
|
"Latest": "Останні",
|
||||||
|
"news list|Latest": "Останні",
|
||||||
"Newest frame per product": "Найсвіжіший кадр кожного продукту",
|
"Newest frame per product": "Найсвіжіший кадр кожного продукту",
|
||||||
"Replay observed history": "Відтворити історію спостережень",
|
"Replay observed history": "Відтворити історію спостережень",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "Підкладка: Esri · скидання поверне Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "Підкладка: Esri · скидання поверне Google 3D",
|
||||||
@@ -2954,7 +2982,12 @@
|
|||||||
"Contact cockpit summary": "Зведення контакту в кабіні",
|
"Contact cockpit summary": "Зведення контакту в кабіні",
|
||||||
"CONTACT": "КОНТАКТ",
|
"CONTACT": "КОНТАКТ",
|
||||||
"CONTACTS · 250 KM": "КОНТАКТИ · 250 КМ",
|
"CONTACTS · 250 KM": "КОНТАКТИ · 250 КМ",
|
||||||
"RINGS {spacing} NM": "КІЛЬЦЯ {spacing} МОР. МИЛ.",
|
"RINGS {spacing} NM": {
|
||||||
|
"one": "КІЛЬЦЯ {spacing} МОР. МИЛЯ",
|
||||||
|
"few": "КІЛЬЦЯ {spacing} МОР. МИЛІ",
|
||||||
|
"many": "КІЛЬЦЯ {spacing} МОР. МИЛЬ",
|
||||||
|
"other": "КІЛЬЦЯ {spacing} МОР. МИЛІ"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"one": "{count} КОНТАКТ",
|
"one": "{count} КОНТАКТ",
|
||||||
"few": "{count} КОНТАКТИ",
|
"few": "{count} КОНТАКТИ",
|
||||||
@@ -3295,6 +3328,7 @@
|
|||||||
"Left panels": "Ліві панелі",
|
"Left panels": "Ліві панелі",
|
||||||
"Right panels": "Праві панелі",
|
"Right panels": "Праві панелі",
|
||||||
"CONFIRMED": "ПІДТВЕРДЖЕНО",
|
"CONFIRMED": "ПІДТВЕРДЖЕНО",
|
||||||
|
"sightings filter|CONFIRMED": "ПІДТВЕРДЖЕНО",
|
||||||
"PAD / RTLS": "МІСЦЕ ЗАПУСКУ / RTLS",
|
"PAD / RTLS": "МІСЦЕ ЗАПУСКУ / RTLS",
|
||||||
"Name of {name}": "Назва: {name}",
|
"Name of {name}": "Назва: {name}",
|
||||||
"Change the colour of {name}": "Змінити колір: {name}",
|
"Change the colour of {name}": "Змінити колір: {name}",
|
||||||
@@ -3554,7 +3588,12 @@
|
|||||||
"Photo of {name}": "Фото {name}",
|
"Photo of {name}": "Фото {name}",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "Спостережень у кадрі: {count}",
|
"{count} sightings in view": {
|
||||||
|
"one": "{count} спостереження в кадрі",
|
||||||
|
"few": "{count} спостереження в кадрі",
|
||||||
|
"many": "{count} спостережень у кадрі",
|
||||||
|
"other": "{count} спостереження в кадрі"
|
||||||
|
},
|
||||||
"Close sighting": "Закрити спостереження",
|
"Close sighting": "Закрити спостереження",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Спостереження живої природи за 48 год (китоподібні — 7 днів). Колір і символ значка позначають таксономічну групу. Вразливі види показано лише комірками 0,2°; повідомлення Acartia про морських ссавців моря Селіш округлено до 0,01°. Не перепис.",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "Спостереження живої природи за 48 год (китоподібні — 7 днів). Колір і символ значка позначають таксономічну групу. Вразливі види показано лише комірками 0,2°; повідомлення Acartia про морських ссавців моря Селіш округлено до 0,01°. Не перепис.",
|
||||||
"Open {name} on iNaturalist": "Відкрити {name} в iNaturalist",
|
"Open {name} on iNaturalist": "Відкрити {name} в iNaturalist",
|
||||||
@@ -3565,20 +3604,33 @@
|
|||||||
"{days}d": "{days} дн",
|
"{days}d": "{days} дн",
|
||||||
"All markers": "Усі мітки",
|
"All markers": "Усі мітки",
|
||||||
"Show markers of every age": "Показати мітки будь-якого віку",
|
"Show markers of every age": "Показати мітки будь-якого віку",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "Максимальний вік події: {days} дн; мітки без дати залишаються",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"one": "Максимальний вік події: {days} дн; мітки без дати залишаються",
|
||||||
|
"few": "Максимальний вік події: {days} дн; мітки без дати залишаються",
|
||||||
|
"many": "Максимальний вік події: {days} дн; мітки без дати залишаються",
|
||||||
|
"other": "Максимальний вік події: {days} дн; мітки без дати залишаються"
|
||||||
|
},
|
||||||
"event age filter|All": "Усі",
|
"event age filter|All": "Усі",
|
||||||
"Event age": "Вік події",
|
"Event age": "Вік події",
|
||||||
"Event age: {days}d · undated kept": "Вік події: {days} дн · мітки без дати залишаються",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"one": "Вік події: {days} дн · мітки без дати залишаються",
|
||||||
|
"few": "Вік події: {days} дн · мітки без дати залишаються",
|
||||||
|
"many": "Вік події: {days} дн · мітки без дати залишаються",
|
||||||
|
"other": "Вік події: {days} дн · мітки без дати залишаються"
|
||||||
|
},
|
||||||
"Close camera preview": "Закрити перегляд камери",
|
"Close camera preview": "Закрити перегляд камери",
|
||||||
"Refresh map still": "Оновити кадр на карті",
|
"Refresh map still": "Оновити кадр на карті",
|
||||||
"The map still uses a separate request and may show a different moment.": "Кадр на карті запитується окремо та може показувати інший момент.",
|
"The map still uses a separate request and may show a different moment.": "Кадр на карті запитується окремо та може показувати інший момент.",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "Відео відтворюється з Québec 511, який бачить вашу IP-адресу.",
|
"Video plays from Québec 511, which sees your IP address.": "Відео відтворюється з Québec 511, який бачить вашу IP-адресу.",
|
||||||
"Press OPEN to load the photo": "Натисніть «ВІДКРИТИ», щоб завантажити фото",
|
"Press OPEN to load the photo": "Натисніть «ВІДКРИТИ», щоб завантажити фото",
|
||||||
"Captured {month}": "Знято: {month}",
|
"Captured {month}": "Знято: {month}",
|
||||||
"this spot": "це місце",
|
|
||||||
"the view centre": "центр карти",
|
"the view centre": "центр карти",
|
||||||
"{source} at {place}": "{source}: {place}",
|
"{source} at {place}": "{source}: {place}",
|
||||||
|
"{source} at this spot": "{source} у цьому місці",
|
||||||
|
"{source} at the view centre": "{source} у центрі карти",
|
||||||
"Street level at {place}": "Вуличні знімки: {place}",
|
"Street level at {place}": "Вуличні знімки: {place}",
|
||||||
|
"Street level at this spot": "Вуличні знімки в цьому місці",
|
||||||
|
"Street level at the view centre": "Вуличні знімки в центрі карти",
|
||||||
"Switching map": "Перемикання карти",
|
"Switching map": "Перемикання карти",
|
||||||
"Opening imagery": "Відкриття знімків",
|
"Opening imagery": "Відкриття знімків",
|
||||||
"Street View is ready: press {open} to load it": "Перегляд вулиць готовий: натисніть «{open}», щоб завантажити",
|
"Street View is ready: press {open} to load it": "Перегляд вулиць готовий: натисніть «{open}», щоб завантажити",
|
||||||
@@ -3670,6 +3722,7 @@
|
|||||||
"{source} imagery could not be opened": "Не вдалося відкрити знімки {source}",
|
"{source} imagery could not be opened": "Не вдалося відкрити знімки {source}",
|
||||||
"{place} has no position to look for imagery at": "Для місця «{place}» немає координат для пошуку знімків",
|
"{place} has no position to look for imagery at": "Для місця «{place}» немає координат для пошуку знімків",
|
||||||
"No {source} imagery near {place}": "Немає знімків {source} поблизу: {place}",
|
"No {source} imagery near {place}": "Немає знімків {source} поблизу: {place}",
|
||||||
|
"No {source} imagery near the view centre": "Немає знімків {source} поблизу центру карти",
|
||||||
"{reason} (filters on: {filters})": "{reason} (увімкнено фільтри: {filters})",
|
"{reason} (filters on: {filters})": "{reason} (увімкнено фільтри: {filters})",
|
||||||
"tiles unavailable": "тайли недоступні",
|
"tiles unavailable": "тайли недоступні",
|
||||||
"zoom in to load this layer": "наблизьте карту, щоб завантажити цей шар",
|
"zoom in to load this layer": "наблизьте карту, щоб завантажити цей шар",
|
||||||
@@ -3778,13 +3831,11 @@
|
|||||||
"REMOVE ALL": "ВИДАЛИТИ ВСЕ",
|
"REMOVE ALL": "ВИДАЛИТИ ВСЕ",
|
||||||
"Confirm: remove every added entry from Data Layers": "Підтвердити: видалити всі додані записи з «Шарів даних»",
|
"Confirm: remove every added entry from Data Layers": "Підтвердити: видалити всі додані записи з «Шарів даних»",
|
||||||
"Remove every added entry from Data Layers": "Видалити всі додані записи з «Шарів даних»",
|
"Remove every added entry from Data Layers": "Видалити всі додані записи з «Шарів даних»",
|
||||||
"Showing {shown} of {total}": "Показано {shown} з {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "ВІДКРИТИ БІБЛІОТЕКУ ДАНИХ",
|
"one": "Показано {shown} з {total}",
|
||||||
"The globe stopped drawing": "Глобус перестав відмальовуватися",
|
"few": "Показано {shown} з {total}",
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "Браузер вимкнув 3D-графіку глобуса. Шари та елементи керування працюють, але карта не відмалюється, доки сторінку не буде перезавантажено.",
|
"many": "Показано {shown} з {total}",
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "Відмальовування зупинилося через помилку. Шари та елементи керування працюють, але карта не оновлюється.",
|
"other": "Показано {shown} з {total}"
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "«Скинути вигляд» перезавантажує застосунок без вигляду, відновленого з адресного рядка.",
|
},
|
||||||
"Reload": "Перезавантажити",
|
"BROWSE DATA LIBRARY": "ВІДКРИТИ БІБЛІОТЕКУ ДАНИХ"
|
||||||
"Retry": "Повторити",
|
|
||||||
"Reset view": "Скинути вигляд"
|
|
||||||
}
|
}
|
||||||
+40
-19
@@ -62,8 +62,12 @@
|
|||||||
"{severity} only": "仅{severity}",
|
"{severity} only": "仅{severity}",
|
||||||
"All categories": "所有类别",
|
"All categories": "所有类别",
|
||||||
"Reading the news…": "正在读取新闻…",
|
"Reading the news…": "正在读取新闻…",
|
||||||
"News, last {hours} hours": "最近 {hours} 小时的新闻",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "最近 {hours} 小时",
|
"other": "最近 {hours} 小时的新闻"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小时"
|
||||||
|
},
|
||||||
"Time window": "时间范围",
|
"Time window": "时间范围",
|
||||||
"Conflicts": "冲突",
|
"Conflicts": "冲突",
|
||||||
"Local": "本地",
|
"Local": "本地",
|
||||||
@@ -72,9 +76,13 @@
|
|||||||
"Hotspots": "热点",
|
"Hotspots": "热点",
|
||||||
"Severity": "严重程度",
|
"Severity": "严重程度",
|
||||||
"Category": "类别",
|
"Category": "类别",
|
||||||
"Nothing reported near here in the last {hours} h": "最近 {hours} 小时附近无报道",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小时附近无报道"
|
||||||
|
},
|
||||||
"Latest news": "最新新闻",
|
"Latest news": "最新新闻",
|
||||||
"No news matches these filters in the last {hours} h": "最近 {hours} 小时没有符合这些筛选条件的新闻",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小时没有符合这些筛选条件的新闻"
|
||||||
|
},
|
||||||
"Load older news": "加载更早的新闻",
|
"Load older news": "加载更早的新闻",
|
||||||
"How to read the map": "如何解读地图",
|
"How to read the map": "如何解读地图",
|
||||||
"Colour: severity · icon: type of event": "颜色:严重程度 · 图标:事件类型",
|
"Colour: severity · icon: type of event": "颜色:严重程度 · 图标:事件类型",
|
||||||
@@ -563,6 +571,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "上一个:250 km 范围内先前访问过的目标",
|
"Previous — prior visited contact in the 250 km window": "上一个:250 km 范围内先前访问过的目标",
|
||||||
"PREVIOUS": "上一个",
|
"PREVIOUS": "上一个",
|
||||||
"FOCUS": "聚焦",
|
"FOCUS": "聚焦",
|
||||||
|
"site status|FOCUS": "聚焦",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "下一个:250 km 范围内最近的未访问目标",
|
"Next — nearest unvisited contact in the 250 km window": "下一个:250 km 范围内最近的未访问目标",
|
||||||
"NEXT": "下一个",
|
"NEXT": "下一个",
|
||||||
"Named areas ({n})": "已命名区域({n})",
|
"Named areas ({n})": "已命名区域({n})",
|
||||||
@@ -929,6 +938,7 @@
|
|||||||
"Cancel replay": "取消回放",
|
"Cancel replay": "取消回放",
|
||||||
"EST. DOWNRANGE": "预估落点距离",
|
"EST. DOWNRANGE": "预估落点距离",
|
||||||
"CONFIRMED": "已确认",
|
"CONFIRMED": "已确认",
|
||||||
|
"sightings filter|CONFIRMED": "已确认",
|
||||||
"PAD / RTLS": "发射台 / RTLS",
|
"PAD / RTLS": "发射台 / RTLS",
|
||||||
"PLANNED · ": "计划 · ",
|
"PLANNED · ": "计划 · ",
|
||||||
"SUPPLIED TRAJECTORY POINTS": "提供的轨迹点",
|
"SUPPLIED TRAJECTORY POINTS": "提供的轨迹点",
|
||||||
@@ -1750,7 +1760,9 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "航线数据不可用",
|
"ROUTE DATA UNAVAILABLE": "航线数据不可用",
|
||||||
"CONTACT NAVIGATION OFF": "目标导航已关闭",
|
"CONTACT NAVIGATION OFF": "目标导航已关闭",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "驾驶台保持在所选船舶上",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "驾驶台保持在所选船舶上",
|
||||||
"{range} NM": "{range} 海里",
|
"{range} NM": {
|
||||||
|
"other": "{range} 海里"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "AIS 目的地 · 船员申报",
|
"AIS DESTINATION · CREW REPORTED": "AIS 目的地 · 船员申报",
|
||||||
"DEST {bearing}": "目的地 {bearing}",
|
"DEST {bearing}": "目的地 {bearing}",
|
||||||
"Disable cockpit weather effects": "停用座舱天气效果",
|
"Disable cockpit weather effects": "停用座舱天气效果",
|
||||||
@@ -1796,6 +1808,7 @@
|
|||||||
"Reclassify as TR-3B": "重新归类为 TR-3B",
|
"Reclassify as TR-3B": "重新归类为 TR-3B",
|
||||||
"TRACK ACQUIRED": "已获取航迹",
|
"TRACK ACQUIRED": "已获取航迹",
|
||||||
"{label} · COURSE {heading}°": "{label} · 对地航向 {heading}°",
|
"{label} · COURSE {heading}°": "{label} · 对地航向 {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · 对地航向 {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · 船首向 {heading}°",
|
"{label} · HEADING {heading}°": "{label} · 船首向 {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · 无船首向",
|
"{label} · NO HEADING": "{label} · 无船首向",
|
||||||
"{label} · NADIR VIEW": "{label} · 天底视图",
|
"{label} · NADIR VIEW": "{label} · 天底视图",
|
||||||
@@ -2048,6 +2061,7 @@
|
|||||||
"Observed history": "观测历史",
|
"Observed history": "观测历史",
|
||||||
"Later observation": "较晚观测",
|
"Later observation": "较晚观测",
|
||||||
"Latest": "最新",
|
"Latest": "最新",
|
||||||
|
"news list|Latest": "最新",
|
||||||
"Newest frame per product": "每个产品的最新一帧",
|
"Newest frame per product": "每个产品的最新一帧",
|
||||||
"Replay observed history": "回放观测历史",
|
"Replay observed history": "回放观测历史",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "影像显示在 Esri 底图上 · 清除后恢复 Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "影像显示在 Esri 底图上 · 清除后恢复 Google 3D",
|
||||||
@@ -2629,7 +2643,9 @@
|
|||||||
"Contact cockpit summary": "目标座舱摘要",
|
"Contact cockpit summary": "目标座舱摘要",
|
||||||
"CONTACT": "目标",
|
"CONTACT": "目标",
|
||||||
"CONTACTS · 250 KM": "目标 · 250 km",
|
"CONTACTS · 250 KM": "目标 · 250 km",
|
||||||
"RINGS {spacing} NM": "距标圈 {spacing} 海里",
|
"RINGS {spacing} NM": {
|
||||||
|
"other": "距标圈 {spacing} 海里"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"other": "{count} 个目标"
|
"other": "{count} 个目标"
|
||||||
},
|
},
|
||||||
@@ -3311,7 +3327,9 @@
|
|||||||
"Photo of {name}": "{name} 的照片",
|
"Photo of {name}": "{name} 的照片",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "视野内有 {count} 次目击",
|
"{count} sightings in view": {
|
||||||
|
"other": "视野内有 {count} 次目击"
|
||||||
|
},
|
||||||
"Close sighting": "关闭目击记录",
|
"Close sighting": "关闭目击记录",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "近 48 小时内观察到的野生生物(鲸类为 7 天)。颜色与徽章图标表示分类群。敏感物种仅以 0.2° 网格显示;Acartia 提供的萨利希海海洋哺乳动物目击位置四舍五入到 0.01°。并非种群普查。",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "近 48 小时内观察到的野生生物(鲸类为 7 天)。颜色与徽章图标表示分类群。敏感物种仅以 0.2° 网格显示;Acartia 提供的萨利希海海洋哺乳动物目击位置四舍五入到 0.01°。并非种群普查。",
|
||||||
"Open {name} on iNaturalist": "在 iNaturalist 上打开 {name}",
|
"Open {name} on iNaturalist": "在 iNaturalist 上打开 {name}",
|
||||||
@@ -3322,20 +3340,27 @@
|
|||||||
"{days}d": "{days} 天",
|
"{days}d": "{days} 天",
|
||||||
"All markers": "所有标记",
|
"All markers": "所有标记",
|
||||||
"Show markers of every age": "显示所有时间的标记",
|
"Show markers of every age": "显示所有时间的标记",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "仅显示 {days} 天内的事件;保留无日期标记",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"other": "仅显示 {days} 天内的事件;保留无日期标记"
|
||||||
|
},
|
||||||
"event age filter|All": "全部",
|
"event age filter|All": "全部",
|
||||||
"Event age": "事件时间",
|
"Event age": "事件时间",
|
||||||
"Event age: {days}d · undated kept": "事件时间:{days} 天内 · 保留无日期标记",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"other": "事件时间:{days} 天内 · 保留无日期标记"
|
||||||
|
},
|
||||||
"Close camera preview": "关闭摄像头预览",
|
"Close camera preview": "关闭摄像头预览",
|
||||||
"Refresh map still": "刷新地图静止画面",
|
"Refresh map still": "刷新地图静止画面",
|
||||||
"The map still uses a separate request and may show a different moment.": "地图静止画面通过单独的请求获取,可能显示不同的时刻。",
|
"The map still uses a separate request and may show a different moment.": "地图静止画面通过单独的请求获取,可能显示不同的时刻。",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "视频从 Québec 511 播放,Québec 511 会看到你的 IP 地址。",
|
"Video plays from Québec 511, which sees your IP address.": "视频从 Québec 511 播放,Québec 511 会看到你的 IP 地址。",
|
||||||
"Press OPEN to load the photo": "点击“打开”加载照片",
|
"Press OPEN to load the photo": "点击“打开”加载照片",
|
||||||
"Captured {month}": "拍摄于 {month}",
|
"Captured {month}": "拍摄于 {month}",
|
||||||
"this spot": "此处",
|
|
||||||
"the view centre": "视野中心",
|
"the view centre": "视野中心",
|
||||||
"{source} at {place}": "{place} 的 {source}",
|
"{source} at {place}": "{place} 的 {source}",
|
||||||
|
"{source} at this spot": "此处的 {source}",
|
||||||
|
"{source} at the view centre": "视图中心的 {source}",
|
||||||
"Street level at {place}": "{place} 的街景",
|
"Street level at {place}": "{place} 的街景",
|
||||||
|
"Street level at this spot": "此处的街景",
|
||||||
|
"Street level at the view centre": "视图中心的街景",
|
||||||
"Switching map": "正在切换地图",
|
"Switching map": "正在切换地图",
|
||||||
"Opening imagery": "正在打开影像",
|
"Opening imagery": "正在打开影像",
|
||||||
"Street View is ready: press {open} to load it": "街景已就绪:点击“{open}”加载",
|
"Street View is ready: press {open} to load it": "街景已就绪:点击“{open}”加载",
|
||||||
@@ -3427,6 +3452,7 @@
|
|||||||
"{source} imagery could not be opened": "无法打开 {source} 影像",
|
"{source} imagery could not be opened": "无法打开 {source} 影像",
|
||||||
"{place} has no position to look for imagery at": "{place} 没有可查找影像的位置",
|
"{place} has no position to look for imagery at": "{place} 没有可查找影像的位置",
|
||||||
"No {source} imagery near {place}": "{place} 附近没有 {source} 影像",
|
"No {source} imagery near {place}": "{place} 附近没有 {source} 影像",
|
||||||
|
"No {source} imagery near the view centre": "视图中心附近没有 {source} 影像",
|
||||||
"{reason} (filters on: {filters})": "{reason}(已开启筛选:{filters})",
|
"{reason} (filters on: {filters})": "{reason}(已开启筛选:{filters})",
|
||||||
"tiles unavailable": "瓦片不可用",
|
"tiles unavailable": "瓦片不可用",
|
||||||
"zoom in to load this layer": "放大以加载此图层",
|
"zoom in to load this layer": "放大以加载此图层",
|
||||||
@@ -3517,13 +3543,8 @@
|
|||||||
"REMOVE ALL": "全部移除",
|
"REMOVE ALL": "全部移除",
|
||||||
"Confirm: remove every added entry from Data Layers": "确认:从数据图层中移除所有已添加条目",
|
"Confirm: remove every added entry from Data Layers": "确认:从数据图层中移除所有已添加条目",
|
||||||
"Remove every added entry from Data Layers": "从数据图层中移除所有已添加条目",
|
"Remove every added entry from Data Layers": "从数据图层中移除所有已添加条目",
|
||||||
"Showing {shown} of {total}": "显示 {shown} / {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "浏览数据资源库",
|
"other": "显示 {shown} / {total}"
|
||||||
"The globe stopped drawing": "地球已停止绘制",
|
},
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "浏览器收回了地球的 3D 图形。图层和控件仍可使用,但在重新加载页面之前地图无法绘制。",
|
"BROWSE DATA LIBRARY": "浏览数据资源库"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "渲染因错误而停止。图层和控件仍可使用,但地图不再更新。",
|
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "“重置视图”会重新加载应用,不使用从地址栏恢复的视图。",
|
|
||||||
"Reload": "重新加载",
|
|
||||||
"Retry": "重试",
|
|
||||||
"Reset view": "重置视图"
|
|
||||||
}
|
}
|
||||||
@@ -62,8 +62,12 @@
|
|||||||
"{severity} only": "僅{severity}",
|
"{severity} only": "僅{severity}",
|
||||||
"All categories": "所有類別",
|
"All categories": "所有類別",
|
||||||
"Reading the news…": "正在讀取新聞…",
|
"Reading the news…": "正在讀取新聞…",
|
||||||
"News, last {hours} hours": "最近 {hours} 小時的新聞",
|
"News, last {hours} hours": {
|
||||||
"Last {hours} h": "最近 {hours} 小時",
|
"other": "最近 {hours} 小時的新聞"
|
||||||
|
},
|
||||||
|
"Last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小時"
|
||||||
|
},
|
||||||
"Time window": "時間範圍",
|
"Time window": "時間範圍",
|
||||||
"Conflicts": "衝突",
|
"Conflicts": "衝突",
|
||||||
"Local": "本地",
|
"Local": "本地",
|
||||||
@@ -72,9 +76,13 @@
|
|||||||
"Hotspots": "熱點",
|
"Hotspots": "熱點",
|
||||||
"Severity": "嚴重程度",
|
"Severity": "嚴重程度",
|
||||||
"Category": "類別",
|
"Category": "類別",
|
||||||
"Nothing reported near here in the last {hours} h": "最近 {hours} 小時附近無報道",
|
"Nothing reported near here in the last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小時附近無報道"
|
||||||
|
},
|
||||||
"Latest news": "最新新聞",
|
"Latest news": "最新新聞",
|
||||||
"No news matches these filters in the last {hours} h": "最近 {hours} 小時沒有符合這些篩選條件的新聞",
|
"No news matches these filters in the last {hours} h": {
|
||||||
|
"other": "最近 {hours} 小時沒有符合這些篩選條件的新聞"
|
||||||
|
},
|
||||||
"Load older news": "加載更早的新聞",
|
"Load older news": "加載更早的新聞",
|
||||||
"How to read the map": "如何解讀地圖",
|
"How to read the map": "如何解讀地圖",
|
||||||
"Colour: severity · icon: type of event": "顏色:嚴重程度 · 圖標:事件類型",
|
"Colour: severity · icon: type of event": "顏色:嚴重程度 · 圖標:事件類型",
|
||||||
@@ -563,6 +571,7 @@
|
|||||||
"Previous — prior visited contact in the 250 km window": "上一個:250 km 範圍內先前訪問過的目標",
|
"Previous — prior visited contact in the 250 km window": "上一個:250 km 範圍內先前訪問過的目標",
|
||||||
"PREVIOUS": "上一個",
|
"PREVIOUS": "上一個",
|
||||||
"FOCUS": "聚焦",
|
"FOCUS": "聚焦",
|
||||||
|
"site status|FOCUS": "聚焦",
|
||||||
"Next — nearest unvisited contact in the 250 km window": "下一個:250 km 範圍內最近的未訪問目標",
|
"Next — nearest unvisited contact in the 250 km window": "下一個:250 km 範圍內最近的未訪問目標",
|
||||||
"NEXT": "下一個",
|
"NEXT": "下一個",
|
||||||
"Named areas ({n})": "已命名區域({n})",
|
"Named areas ({n})": "已命名區域({n})",
|
||||||
@@ -924,6 +933,7 @@
|
|||||||
"Cancel replay": "取消回放",
|
"Cancel replay": "取消回放",
|
||||||
"EST. DOWNRANGE": "預估落點距離",
|
"EST. DOWNRANGE": "預估落點距離",
|
||||||
"CONFIRMED": "已確認",
|
"CONFIRMED": "已確認",
|
||||||
|
"sightings filter|CONFIRMED": "已確認",
|
||||||
"PAD / RTLS": "發射臺 / RTLS",
|
"PAD / RTLS": "發射臺 / RTLS",
|
||||||
"PLANNED · ": "計劃 · ",
|
"PLANNED · ": "計劃 · ",
|
||||||
"SUPPLIED TRAJECTORY POINTS": "提供的軌跡點",
|
"SUPPLIED TRAJECTORY POINTS": "提供的軌跡點",
|
||||||
@@ -1742,7 +1752,9 @@
|
|||||||
"ROUTE DATA UNAVAILABLE": "航線數據不可用",
|
"ROUTE DATA UNAVAILABLE": "航線數據不可用",
|
||||||
"CONTACT NAVIGATION OFF": "目標導航已關閉",
|
"CONTACT NAVIGATION OFF": "目標導航已關閉",
|
||||||
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "駕駛臺保持在所選船舶上",
|
"THE BRIDGE STAYS WITH THE SELECTED VESSEL": "駕駛臺保持在所選船舶上",
|
||||||
"{range} NM": "{range} 海里",
|
"{range} NM": {
|
||||||
|
"other": "{range} 海里"
|
||||||
|
},
|
||||||
"AIS DESTINATION · CREW REPORTED": "AIS 目的地 · 船員申報",
|
"AIS DESTINATION · CREW REPORTED": "AIS 目的地 · 船員申報",
|
||||||
"DEST {bearing}": "目的地 {bearing}",
|
"DEST {bearing}": "目的地 {bearing}",
|
||||||
"Disable cockpit weather effects": "停用座艙天氣效果",
|
"Disable cockpit weather effects": "停用座艙天氣效果",
|
||||||
@@ -1788,6 +1800,7 @@
|
|||||||
"Reclassify as TR-3B": "重新歸類為 TR-3B",
|
"Reclassify as TR-3B": "重新歸類為 TR-3B",
|
||||||
"TRACK ACQUIRED": "已獲取航跡",
|
"TRACK ACQUIRED": "已獲取航跡",
|
||||||
"{label} · COURSE {heading}°": "{label} · 對地航向 {heading}°",
|
"{label} · COURSE {heading}°": "{label} · 對地航向 {heading}°",
|
||||||
|
"vessel|{label} · COURSE {heading}°": "{label} · 對地航向 {heading}°",
|
||||||
"{label} · HEADING {heading}°": "{label} · 船首向 {heading}°",
|
"{label} · HEADING {heading}°": "{label} · 船首向 {heading}°",
|
||||||
"{label} · NO HEADING": "{label} · 無船首向",
|
"{label} · NO HEADING": "{label} · 無船首向",
|
||||||
"{label} · NADIR VIEW": "{label} · 天底視圖",
|
"{label} · NADIR VIEW": "{label} · 天底視圖",
|
||||||
@@ -2038,6 +2051,7 @@
|
|||||||
"Observed history": "觀測歷史",
|
"Observed history": "觀測歷史",
|
||||||
"Later observation": "較晚觀測",
|
"Later observation": "較晚觀測",
|
||||||
"Latest": "最新",
|
"Latest": "最新",
|
||||||
|
"news list|Latest": "最新",
|
||||||
"Newest frame per product": "每個產品的最新一幀",
|
"Newest frame per product": "每個產品的最新一幀",
|
||||||
"Replay observed history": "回放觀測歷史",
|
"Replay observed history": "回放觀測歷史",
|
||||||
"Imagery on Esri · Google 3D returns when cleared": "影像顯示在 Esri 底圖上 · 清除後恢復 Google 3D",
|
"Imagery on Esri · Google 3D returns when cleared": "影像顯示在 Esri 底圖上 · 清除後恢復 Google 3D",
|
||||||
@@ -2619,7 +2633,9 @@
|
|||||||
"Contact cockpit summary": "目標座艙摘要",
|
"Contact cockpit summary": "目標座艙摘要",
|
||||||
"CONTACT": "目標",
|
"CONTACT": "目標",
|
||||||
"CONTACTS · 250 KM": "目標 · 250 km",
|
"CONTACTS · 250 KM": "目標 · 250 km",
|
||||||
"RINGS {spacing} NM": "距標圈 {spacing} 海里",
|
"RINGS {spacing} NM": {
|
||||||
|
"other": "距標圈 {spacing} 海里"
|
||||||
|
},
|
||||||
"{count} CONTACTS": {
|
"{count} CONTACTS": {
|
||||||
"other": "{count} 個目標"
|
"other": "{count} 個目標"
|
||||||
},
|
},
|
||||||
@@ -3311,7 +3327,9 @@
|
|||||||
"Photo of {name}": "{name} 的照片",
|
"Photo of {name}": "{name} 的照片",
|
||||||
"Acartia": "Acartia",
|
"Acartia": "Acartia",
|
||||||
"CC BY 4.0": "CC BY 4.0",
|
"CC BY 4.0": "CC BY 4.0",
|
||||||
"{count} sightings in view": "視野內有 {count} 次目擊",
|
"{count} sightings in view": {
|
||||||
|
"other": "視野內有 {count} 次目擊"
|
||||||
|
},
|
||||||
"Close sighting": "關閉目擊記錄",
|
"Close sighting": "關閉目擊記錄",
|
||||||
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "近 48 小時內觀察到的野生生物(鯨類為 7 天)。顏色與徽章圖標表示分類群。敏感物種僅以 0.2° 網格顯示;Acartia 提供的薩利希海海洋哺乳動物目擊位置四捨五入到 0.01°。並非種群普查。",
|
"Wildlife observed within 48 h (whales 7 d). Color and badge glyph show the taxon group. Sensitive species are shown only as 0.2° cells; Salish Sea marine mammal reports via Acartia are rounded to 0.01°. Not a census.": "近 48 小時內觀察到的野生生物(鯨類為 7 天)。顏色與徽章圖標表示分類群。敏感物種僅以 0.2° 網格顯示;Acartia 提供的薩利希海海洋哺乳動物目擊位置四捨五入到 0.01°。並非種群普查。",
|
||||||
"Open {name} on iNaturalist": "在 iNaturalist 上打開 {name}",
|
"Open {name} on iNaturalist": "在 iNaturalist 上打開 {name}",
|
||||||
@@ -3322,20 +3340,27 @@
|
|||||||
"{days}d": "{days} 天",
|
"{days}d": "{days} 天",
|
||||||
"All markers": "所有標記",
|
"All markers": "所有標記",
|
||||||
"Show markers of every age": "顯示所有時間的標記",
|
"Show markers of every age": "顯示所有時間的標記",
|
||||||
"Maximum event age: {days}d; undated markers are kept": "僅顯示 {days} 天內的事件;保留無日期標記",
|
"Maximum event age: {days}d; undated markers are kept": {
|
||||||
|
"other": "僅顯示 {days} 天內的事件;保留無日期標記"
|
||||||
|
},
|
||||||
"event age filter|All": "全部",
|
"event age filter|All": "全部",
|
||||||
"Event age": "事件時間",
|
"Event age": "事件時間",
|
||||||
"Event age: {days}d · undated kept": "事件時間:{days} 天內 · 保留無日期標記",
|
"Event age: {days}d · undated kept": {
|
||||||
|
"other": "事件時間:{days} 天內 · 保留無日期標記"
|
||||||
|
},
|
||||||
"Close camera preview": "關閉攝像頭預覽",
|
"Close camera preview": "關閉攝像頭預覽",
|
||||||
"Refresh map still": "刷新地圖靜止畫面",
|
"Refresh map still": "刷新地圖靜止畫面",
|
||||||
"The map still uses a separate request and may show a different moment.": "地圖靜止畫面通過單獨的請求獲取,可能顯示不同的時刻。",
|
"The map still uses a separate request and may show a different moment.": "地圖靜止畫面通過單獨的請求獲取,可能顯示不同的時刻。",
|
||||||
"Video plays from Québec 511, which sees your IP address.": "視頻從 Québec 511 播放,Québec 511 會看到你的 IP 地址。",
|
"Video plays from Québec 511, which sees your IP address.": "視頻從 Québec 511 播放,Québec 511 會看到你的 IP 地址。",
|
||||||
"Press OPEN to load the photo": "點擊“打開”加載照片",
|
"Press OPEN to load the photo": "點擊“打開”加載照片",
|
||||||
"Captured {month}": "拍攝於 {month}",
|
"Captured {month}": "拍攝於 {month}",
|
||||||
"this spot": "此處",
|
|
||||||
"the view centre": "視野中心",
|
"the view centre": "視野中心",
|
||||||
"{source} at {place}": "{place} 的 {source}",
|
"{source} at {place}": "{place} 的 {source}",
|
||||||
|
"{source} at this spot": "此處的 {source}",
|
||||||
|
"{source} at the view centre": "視圖中心的 {source}",
|
||||||
"Street level at {place}": "{place} 的街景",
|
"Street level at {place}": "{place} 的街景",
|
||||||
|
"Street level at this spot": "此處的街景",
|
||||||
|
"Street level at the view centre": "視圖中心的街景",
|
||||||
"Switching map": "正在切換地圖",
|
"Switching map": "正在切換地圖",
|
||||||
"Opening imagery": "正在打開影像",
|
"Opening imagery": "正在打開影像",
|
||||||
"Street View is ready: press {open} to load it": "街景已就緒:點擊“{open}”加載",
|
"Street View is ready: press {open} to load it": "街景已就緒:點擊“{open}”加載",
|
||||||
@@ -3427,6 +3452,7 @@
|
|||||||
"{source} imagery could not be opened": "無法打開 {source} 影像",
|
"{source} imagery could not be opened": "無法打開 {source} 影像",
|
||||||
"{place} has no position to look for imagery at": "{place} 沒有可查找影像的位置",
|
"{place} has no position to look for imagery at": "{place} 沒有可查找影像的位置",
|
||||||
"No {source} imagery near {place}": "{place} 附近沒有 {source} 影像",
|
"No {source} imagery near {place}": "{place} 附近沒有 {source} 影像",
|
||||||
|
"No {source} imagery near the view centre": "視圖中心附近沒有 {source} 影像",
|
||||||
"{reason} (filters on: {filters})": "{reason}(已開啟篩選:{filters})",
|
"{reason} (filters on: {filters})": "{reason}(已開啟篩選:{filters})",
|
||||||
"tiles unavailable": "瓦片不可用",
|
"tiles unavailable": "瓦片不可用",
|
||||||
"zoom in to load this layer": "放大以加載此圖層",
|
"zoom in to load this layer": "放大以加載此圖層",
|
||||||
@@ -3517,13 +3543,8 @@
|
|||||||
"REMOVE ALL": "全部移除",
|
"REMOVE ALL": "全部移除",
|
||||||
"Confirm: remove every added entry from Data Layers": "確認:從數據圖層中移除所有已添加條目",
|
"Confirm: remove every added entry from Data Layers": "確認:從數據圖層中移除所有已添加條目",
|
||||||
"Remove every added entry from Data Layers": "從數據圖層中移除所有已添加條目",
|
"Remove every added entry from Data Layers": "從數據圖層中移除所有已添加條目",
|
||||||
"Showing {shown} of {total}": "顯示 {shown} / {total}",
|
"Showing {shown} of {total}": {
|
||||||
"BROWSE DATA LIBRARY": "瀏覽數據資源庫",
|
"other": "顯示 {shown} / {total}"
|
||||||
"The globe stopped drawing": "地球已停止繪製",
|
},
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.": "瀏覽器收回了地球的 3D 圖形。圖層和控件仍可使用,但在重新加載頁面之前地圖無法繪製。",
|
"BROWSE DATA LIBRARY": "瀏覽數據資源庫"
|
||||||
"Rendering stopped after an error. Layers and controls still work, but the map is not updating.": "渲染因錯誤而停止。圖層和控件仍可使用,但地圖不再更新。",
|
|
||||||
"Reset view reloads the app without the view restored from the address bar.": "「重置視圖」會重新加載應用,不使用從地址欄恢復的視圖。",
|
|
||||||
"Reload": "重新加載",
|
|
||||||
"Retry": "重試",
|
|
||||||
"Reset view": "重置視圖"
|
|
||||||
}
|
}
|
||||||
@@ -685,6 +685,7 @@
|
|||||||
"FLIR",
|
"FLIR",
|
||||||
"FM MHZ",
|
"FM MHZ",
|
||||||
"FOCUS",
|
"FOCUS",
|
||||||
|
"site status|FOCUS",
|
||||||
"FOV",
|
"FOV",
|
||||||
"FOV {value}°",
|
"FOV {value}°",
|
||||||
"FT",
|
"FT",
|
||||||
|
|||||||
@@ -762,9 +762,12 @@ export function createGhostMapsLayer({
|
|||||||
title:
|
title:
|
||||||
days === null
|
days === null
|
||||||
? t('Show markers of every age')
|
? t('Show markers of every age')
|
||||||
: t('Maximum event age: {days}d; undated markers are kept', {
|
: tn(
|
||||||
days: formatNumber(days),
|
days,
|
||||||
}),
|
'Maximum event age: {days}d; undated markers are kept',
|
||||||
|
'Maximum event age: {days}d; undated markers are kept',
|
||||||
|
{ days: formatNumber(days) },
|
||||||
|
),
|
||||||
active: days === _ageDays,
|
active: days === _ageDays,
|
||||||
onClick: () => setAgeDays(days),
|
onClick: () => setAgeDays(days),
|
||||||
})),
|
})),
|
||||||
@@ -799,7 +802,7 @@ export function createGhostMapsLayer({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
legend: [],
|
legend: [],
|
||||||
info: `${_ageDays === null ? t('All markers') : t('Event age: {days}d · undated kept', { days: formatNumber(_ageDays) })} · ${info}`,
|
info: `${_ageDays === null ? t('All markers') : tn(_ageDays, 'Event age: {days}d · undated kept', 'Event age: {days}d · undated kept', { days: formatNumber(_ageDays) })} · ${info}`,
|
||||||
infoTitle: t(
|
infoTitle: t(
|
||||||
'S2 Underground publishes these KMZ products to GitHub. New uploads are detected within seconds and replace the previous edition automatically. Click a marker for its report.',
|
'S2 Underground publishes these KMZ products to GitHub. New uploads are detected within seconds and replace the previous edition automatically. Click a marker for its report.',
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
|
import { setLocale } from '../../i18n/index.js';
|
||||||
import { createGhostMapsLayer } from './index.js';
|
import { createGhostMapsLayer } from './index.js';
|
||||||
import { GHOSTMAPS_PRODUCTS, GHOSTMAPS_LATEST_DROP } from './catalog.js';
|
import { GHOSTMAPS_PRODUCTS, GHOSTMAPS_LATEST_DROP } from './catalog.js';
|
||||||
|
|
||||||
@@ -248,6 +249,35 @@ test('age controls default to seven days and widening restores filtered and expi
|
|||||||
assert.equal(ageChip(layer, null).active, true);
|
assert.equal(ageChip(layer, null).active, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the event age agrees with its number of days', async (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
const { layer, viewer } = fixture(t, { reports: [{ name: 'Undated' }] });
|
||||||
|
await layer.update(viewer);
|
||||||
|
const info = () =>
|
||||||
|
layer.getRowControls().info.split(' · ').slice(0, 2).join(' · ');
|
||||||
|
assert.equal(
|
||||||
|
ageChip(layer, 1).title,
|
||||||
|
'Maximum event age: 1d; undated markers are kept',
|
||||||
|
);
|
||||||
|
setLocale('de');
|
||||||
|
assert.equal(
|
||||||
|
ageChip(layer, 1).title,
|
||||||
|
'Maximales Ereignisalter: 1 Tag; undatierte Markierungen bleiben erhalten',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
ageChip(layer, 7).title,
|
||||||
|
'Maximales Ereignisalter: 7 Tage; undatierte Markierungen bleiben erhalten',
|
||||||
|
);
|
||||||
|
assert.equal(info(), 'Ereignisalter: 7 Tage · undatierte bleiben');
|
||||||
|
await ageChip(layer, 1).onClick();
|
||||||
|
assert.equal(info(), 'Ereignisalter: 1 Tag · undatierte bleiben');
|
||||||
|
setLocale('es');
|
||||||
|
assert.equal(
|
||||||
|
info(),
|
||||||
|
'Antigüedad: 1 día · se conservan los marcadores sin fecha',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('age preferences persist independently of product choices and reject invalid storage', async (t) => {
|
test('age preferences persist independently of product choices and reject invalid storage', async (t) => {
|
||||||
const values = new Map([['gev.ghostmaps.products', '["cip"]']]);
|
const values = new Map([['gev.ghostmaps.products', '["cip"]']]);
|
||||||
const storage = {
|
const storage = {
|
||||||
|
|||||||
@@ -69,3 +69,38 @@ test('the mission panel translates only the fallback status', (t) => {
|
|||||||
assert.equal(shownStatus(UNKNOWN_LAUNCH_STATUS), 'Desconocido');
|
assert.equal(shownStatus(UNKNOWN_LAUNCH_STATUS), 'Desconocido');
|
||||||
assert.equal(shownStatus('Launch Successful'), 'Launch Successful');
|
assert.equal(shownStatus('Launch Successful'), 'Launch Successful');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a confirmed landing position is worded apart from the CONFIRMED sightings chip', (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
const position = () =>
|
||||||
|
createPanel({
|
||||||
|
state: {
|
||||||
|
_selectedLaunchId: 'mission-1',
|
||||||
|
_launches: [
|
||||||
|
{
|
||||||
|
id: 'mission-1',
|
||||||
|
name: 'Example',
|
||||||
|
status: 'Launch Successful',
|
||||||
|
payloads: [],
|
||||||
|
recoveryStages: [
|
||||||
|
{
|
||||||
|
name: 'B1080',
|
||||||
|
endpoint: { lat: 28.41, lon: -80.6, accuracy: 'CONFIRMED' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_replayTracks: new Map(),
|
||||||
|
},
|
||||||
|
parts: {
|
||||||
|
overlays: { shortMissionLabel: (name) => name },
|
||||||
|
policyHelpers: { missionPathPresentation: () => ({}) },
|
||||||
|
replay: { presentReplay: () => ({}) },
|
||||||
|
},
|
||||||
|
}).presentMission().stages[0].position;
|
||||||
|
assert.match(position(), / · CONFIRMED$/);
|
||||||
|
// One landing, singular: sv BEKRÄFTAD, where the sightings chip says
|
||||||
|
// BEKRÄFTADE.
|
||||||
|
setLocale('sv');
|
||||||
|
assert.match(position(), / · BEKRÄFTAD$/);
|
||||||
|
});
|
||||||
@@ -542,7 +542,7 @@ test('retention never revives an incumbent card after the observation window exp
|
|||||||
test('all heights and the sampled fallback use the same sightings-in-view count label', () => {
|
test('all heights and the sampled fallback use the same sightings-in-view count label', () => {
|
||||||
const h = harness();
|
const h = harness();
|
||||||
h.state.renderedRows = [sighting];
|
h.state.renderedRows = [sighting];
|
||||||
assert.equal(h.layer.getStats().countLabel, '1 sightings in view');
|
assert.equal(h.layer.getStats().countLabel, '1 sighting in view');
|
||||||
h.state.sightingCount = 2000;
|
h.state.sightingCount = 2000;
|
||||||
assert.equal(h.layer.getStats().count, 2000);
|
assert.equal(h.layer.getStats().count, 2000);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@@ -560,6 +560,32 @@ test('all heights and the sampled fallback use the same sightings-in-view count
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the sightings count takes the plural form of its number', (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
const h = harness();
|
||||||
|
const label = (count) => {
|
||||||
|
h.state.sightingCount = count;
|
||||||
|
return h.layer.getStats().countLabel;
|
||||||
|
};
|
||||||
|
setLocale('ru');
|
||||||
|
assert.equal(label(1), '1 наблюдение в кадре');
|
||||||
|
assert.equal(label(3), '3 наблюдения в кадре');
|
||||||
|
assert.equal(label(5), '5 наблюдений в кадре');
|
||||||
|
setLocale('de');
|
||||||
|
assert.equal(label(1), '1 Beobachtung im Bild');
|
||||||
|
assert.equal(label(2000), '2.000 Beobachtungen im Bild');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the CONFIRMED chip names the sightings, apart from a confirmed launch landing', (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
const h = harness();
|
||||||
|
assert.equal(h.chip('quality-confirmed').label, 'CONFIRMED');
|
||||||
|
setLocale('sv');
|
||||||
|
assert.equal(h.chip('quality-confirmed').label, 'BEKRÄFTADE');
|
||||||
|
setLocale('es');
|
||||||
|
assert.equal(h.chip('quality-confirmed').label, 'CONFIRMADAS');
|
||||||
|
});
|
||||||
|
|
||||||
test('a stale fallback with a failed-refresh deadline remains DEGRADED, retryable and dated to the cached data', async () => {
|
test('a stale fallback with a failed-refresh deadline remains DEGRADED, retryable and dated to the cached data', async () => {
|
||||||
const h = harness({
|
const h = harness({
|
||||||
getSnapshotDetail: async () => ({
|
getSnapshotDetail: async () => ({
|
||||||
|
|||||||
+9
-5
@@ -1,4 +1,4 @@
|
|||||||
import { t, formatNumber, translateKnown } from '../../i18n/index.js';
|
import { t, tn, formatNumber, translateKnown } from '../../i18n/index.js';
|
||||||
import { msg } from '../../i18n/marker.js';
|
import { msg } from '../../i18n/marker.js';
|
||||||
import { translateFeedError } from '../../ui/statusText.js';
|
import { translateFeedError } from '../../ui/statusText.js';
|
||||||
import { wildlifeIconicGroup } from './model.js';
|
import { wildlifeIconicGroup } from './model.js';
|
||||||
@@ -137,7 +137,8 @@ export function createControls({ state, services, parts }) {
|
|||||||
const chips = [
|
const chips = [
|
||||||
chip({
|
chip({
|
||||||
id: WILDLIFE_CONFIRMED_CHIP_ID,
|
id: WILDLIFE_CONFIRMED_CHIP_ID,
|
||||||
label: t('CONFIRMED'),
|
// The sightings, plural; a launch landing is CONFIRMED alone.
|
||||||
|
label: t('CONFIRMED', { context: 'sightings filter' }),
|
||||||
title: t('Show only community-confirmed (research-grade) sightings'),
|
title: t('Show only community-confirmed (research-grade) sightings'),
|
||||||
params: {
|
params: {
|
||||||
confirmed: confirmedFailed ? state.confirmed : !state.confirmed,
|
confirmed: confirmedFailed ? state.confirmed : !state.confirmed,
|
||||||
@@ -214,9 +215,12 @@ export function createControls({ state, services, parts }) {
|
|||||||
stale: state.stale,
|
stale: state.stale,
|
||||||
loading,
|
loading,
|
||||||
refreshing,
|
refreshing,
|
||||||
countLabel: t('{count} sightings in view', {
|
countLabel: tn(
|
||||||
count: formatNumber(count),
|
count,
|
||||||
}),
|
'{count} sighting in view',
|
||||||
|
'{count} sightings in view',
|
||||||
|
{ count: formatNumber(count) },
|
||||||
|
),
|
||||||
loadingLabel: loading
|
loadingLabel: loading
|
||||||
? t('loading...')
|
? t('loading...')
|
||||||
: refreshing
|
: refreshing
|
||||||
|
|||||||
@@ -1987,7 +1987,7 @@ test('unavailable grids fail closed to visible sampled sprites without a global
|
|||||||
assert.equal(h.clicks.collection.values[0].id, 'wildlife:inat:1');
|
assert.equal(h.clicks.collection.values[0].id, 'wildlife:inat:1');
|
||||||
assert.ok(h.clicks.collection.values[0].width * 0.5 >= 8);
|
assert.ok(h.clicks.collection.values[0].width * 0.5 >= 8);
|
||||||
assert.equal(h.layer.getStats().count, 1);
|
assert.equal(h.layer.getStats().count, 1);
|
||||||
assert.match(h.layer.getStats().countLabel, /sightings in view/);
|
assert.equal(h.layer.getStats().countLabel, '1 sighting in view');
|
||||||
assert.equal(h.layer.getStats().partial, undefined);
|
assert.equal(h.layer.getStats().partial, undefined);
|
||||||
assert.equal(layerFeedState(h.layer.getStats()), 'degraded');
|
assert.equal(layerFeedState(h.layer.getStats()), 'degraded');
|
||||||
assert.match(h.layer.getStats().error, /Malformed iNaturalist grid response/);
|
assert.match(h.layer.getStats().error, /Malformed iNaturalist grid response/);
|
||||||
@@ -2024,7 +2024,7 @@ test('an oversized view coarsens to a complete grid within the six-tile cap', as
|
|||||||
await h.update();
|
await h.update();
|
||||||
assert.equal(h.clicks.collection.values.length, 1);
|
assert.equal(h.clicks.collection.values.length, 1);
|
||||||
assert.equal(h.clicks.collection.values[0].id, 'wildlife:inat:1');
|
assert.equal(h.clicks.collection.values[0].id, 'wildlife:inat:1');
|
||||||
assert.match(h.layer.getStats().countLabel, /sightings in view/);
|
assert.match(h.layer.getStats().countLabel, /sightings? in view/);
|
||||||
assert.equal(layerFeedState(h.layer.getStats()), 'nominal');
|
assert.equal(layerFeedState(h.layer.getStats()), 'nominal');
|
||||||
h.layer.destroy();
|
h.layer.destroy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { createApp, defineComponent, h, nextTick } from 'vue';
|
import { createApp, defineComponent, h, nextTick } from 'vue';
|
||||||
import { CockpitViewController } from './cockpitController.js';
|
import { CockpitViewController } from './cockpitController.js';
|
||||||
|
import { setLocale } from '../i18n/index.js';
|
||||||
import { useCockpitStore } from './stores/cockpit.js';
|
import { useCockpitStore } from './stores/cockpit.js';
|
||||||
import CockpitView from './views/CockpitView.vue';
|
import CockpitView from './views/CockpitView.vue';
|
||||||
import ContextView from './views/ContextView.vue';
|
import ContextView from './views/ContextView.vue';
|
||||||
@@ -355,6 +356,33 @@ test('the bridge radar shows nearby ships without its own and steps its range',
|
|||||||
assert.ok(!$('#cockpit-radar') || $('#cockpit-radar').hidden);
|
assert.ok(!$('#cockpit-radar') || $('#cockpit-radar').hidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the radar range and ring spacing agree with their distance (ru)', async (t) => {
|
||||||
|
const { $, cockpit } = fixture(t);
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('ru');
|
||||||
|
cockpit.enter();
|
||||||
|
await settle();
|
||||||
|
const shown = () => [
|
||||||
|
$('#cockpit-radar-range').textContent.trim(),
|
||||||
|
$('.cockpit-radar-rings').textContent.trim(),
|
||||||
|
];
|
||||||
|
// The ring spacing is a fraction at the short ranges: Intl.PluralRules
|
||||||
|
// gives 0.75 and 1.5 the form of their own.
|
||||||
|
const expected = [
|
||||||
|
['3 МОР. МИЛИ', 'КОЛЬЦА 0,75 МОР. МИЛИ'],
|
||||||
|
['6 МОР. МИЛЬ', 'КОЛЬЦА 1,5 МОР. МИЛИ'],
|
||||||
|
['12 МОР. МИЛЬ', 'КОЛЬЦА 3 МОР. МИЛИ'],
|
||||||
|
['24 МОР. МИЛИ', 'КОЛЬЦА 6 МОР. МИЛЬ'],
|
||||||
|
];
|
||||||
|
for (const [index, labels] of expected.entries()) {
|
||||||
|
if (index) {
|
||||||
|
$('#cockpit-radar-range').click();
|
||||||
|
await settle();
|
||||||
|
}
|
||||||
|
assert.deepEqual(shown(), labels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('contact navigation does not jump ship from the bridge', async (t) => {
|
test('contact navigation does not jump ship from the bridge', async (t) => {
|
||||||
const { cockpit, navigations } = fixture(t);
|
const { cockpit, navigations } = fixture(t);
|
||||||
cockpit.enter();
|
cockpit.enter();
|
||||||
@@ -1201,6 +1229,18 @@ test('a ship with no true heading is held, and labelled, on its course', async (
|
|||||||
signals.some(([, , , detail]) => /COURSE 095°/.test(detail)),
|
signals.some(([, , , detail]) => /COURSE 095°/.test(detail)),
|
||||||
JSON.stringify(signals),
|
JSON.stringify(signals),
|
||||||
);
|
);
|
||||||
|
// A ship's course has a word of its own: pt RUMO, an aircraft's CURSO.
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('pt');
|
||||||
|
signals.length = 0;
|
||||||
|
cockpit.exit();
|
||||||
|
await settle();
|
||||||
|
cockpit.enter();
|
||||||
|
await settle();
|
||||||
|
assert.ok(
|
||||||
|
signals.some(([, , , detail]) => /· RUMO 095°$/.test(detail)),
|
||||||
|
JSON.stringify(signals),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('both entries name the subject the same way', async () => {
|
test('both entries name the subject the same way', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** Instrument values, routes and vision controls for the Cockpit controller. */
|
/** Instrument values, routes and vision controls for the Cockpit controller. */
|
||||||
import { formatNumber, t } from '../i18n/index.js';
|
import { formatNumber, t, tn } from '../i18n/index.js';
|
||||||
import {
|
import {
|
||||||
COCKPIT_VISION_MODES,
|
COCKPIT_VISION_MODES,
|
||||||
normalizeCockpitVisionMode,
|
normalizeCockpitVisionMode,
|
||||||
@@ -379,7 +379,10 @@ export function updateRadar(info) {
|
|||||||
);
|
);
|
||||||
this.publishRadar({
|
this.publishRadar({
|
||||||
...picture,
|
...picture,
|
||||||
rangeLabel: t('{range} NM', { range: formatNumber(rangeNm) }),
|
// A plural, as the ring spacing is: ru «3 МОР. МИЛИ», «12 МОР. МИЛЬ».
|
||||||
|
rangeLabel: tn(rangeNm, '{range} NM', '{range} NM', {
|
||||||
|
range: formatNumber(rangeNm),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { createPinia } from 'pinia';
|
|||||||
import { createApp, defineComponent, h, nextTick } from 'vue';
|
import { createApp, defineComponent, h, nextTick } from 'vue';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { CockpitViewController } from './cockpitController.js';
|
import { CockpitViewController } from './cockpitController.js';
|
||||||
|
import { setLocale, t as translate } from '../i18n/index.js';
|
||||||
import { useCockpitStore } from './stores/cockpit.js';
|
import { useCockpitStore } from './stores/cockpit.js';
|
||||||
import { useHudStore } from './stores/hud.js';
|
import { useHudStore } from './stores/hud.js';
|
||||||
import CockpitView from './views/CockpitView.vue';
|
import CockpitView from './views/CockpitView.vue';
|
||||||
@@ -534,6 +535,13 @@ test('a site row turns the camera onto it, holds it, and reads its off-nadir ang
|
|||||||
assert.match(held.className, /^track actionable$/);
|
assert.match(held.className, /^track actionable$/);
|
||||||
assert.match(held.detail, /^FOCUS · BASE · OFF-NADIR/);
|
assert.match(held.detail, /^FOCUS · BASE · OFF-NADIR/);
|
||||||
assert.equal(siteRow(scene, 'alpha').getAttribute('aria-label'), 'Release the camera from Camp Alpha');
|
assert.equal(siteRow(scene, 'alpha').getAttribute('aria-label'), 'Release the camera from Camp Alpha');
|
||||||
|
// The held site's FOCUS is a status, worded apart from the FOCUS command.
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('es');
|
||||||
|
await refreshRows(scene);
|
||||||
|
const heldEs = rows(scene).find((row) => row.target?.id === SITES.alpha.id);
|
||||||
|
assert.match(heldEs.detail, /^ENFOCADO · /);
|
||||||
|
assert.equal(translate('FOCUS'), 'ENFOCAR', 'the command keeps its verb');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('another site row switches the focus; the held site row ends it', async (t) => {
|
test('another site row switches the focus; the held site row ends it', async (t) => {
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ function siteRows(info, { settled = true } = {}) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const words = [installationClassLabel(site.class), angle];
|
const words = [installationClassLabel(site.class), angle];
|
||||||
if (focused) words.unshift(t('FOCUS'));
|
// A status, not the FOCUS command: es «ENFOCADO», not «ENFOCAR».
|
||||||
|
if (focused) words.unshift(t('FOCUS', { context: 'site status' }));
|
||||||
return {
|
return {
|
||||||
key: `site:${site.id}`,
|
key: `site:${site.id}`,
|
||||||
// The held site reads as the subject does.
|
// The held site reads as the subject does.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { createApp, defineComponent, h, nextTick } from 'vue';
|
import { createApp, defineComponent, h, nextTick } from 'vue';
|
||||||
import { CockpitViewController } from './cockpitController.js';
|
import { CockpitViewController } from './cockpitController.js';
|
||||||
|
import { setLocale } from '../i18n/index.js';
|
||||||
import { useCockpitStore } from './stores/cockpit.js';
|
import { useCockpitStore } from './stores/cockpit.js';
|
||||||
import CockpitView from './views/CockpitView.vue';
|
import CockpitView from './views/CockpitView.vue';
|
||||||
import ContextView from './views/ContextView.vue';
|
import ContextView from './views/ContextView.vue';
|
||||||
@@ -255,3 +256,16 @@ test('entering asks for a panel layout before Vue shows the cards', async (t) =>
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
assert.deepEqual(laid, ['context', 'signals']);
|
assert.deepEqual(laid, ['context', 'signals']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an aircraft is acquired on its CURSO in Portuguese; a ship keeps RUMO', async (t) => {
|
||||||
|
const { cockpit } = fixture(t);
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('pt');
|
||||||
|
const signals = [];
|
||||||
|
cockpit.pushCockpitSignal = (...args) => signals.push(args);
|
||||||
|
cockpit.enter();
|
||||||
|
await nextTick();
|
||||||
|
const acquired = signals.find(([key]) => key === 'track');
|
||||||
|
assert.ok(acquired, JSON.stringify(signals));
|
||||||
|
assert.equal(acquired[3], 'UAL123 · CURSO 092°');
|
||||||
|
});
|
||||||
@@ -326,9 +326,11 @@ export function enter({ kind = null } = {}) {
|
|||||||
'track',
|
'track',
|
||||||
t('TRACK ACQUIRED'),
|
t('TRACK ACQUIRED'),
|
||||||
// A ship with no true heading is held on its course over ground; one
|
// A ship with no true heading is held on its course over ground; one
|
||||||
// with neither is named without a direction.
|
// with neither is named without a direction. A ship's course has a
|
||||||
|
// word of its own: pt «RUMO», where an aircraft's is «CURSO».
|
||||||
info.trackSource === 'course'
|
info.trackSource === 'course'
|
||||||
? t('{label} · COURSE {heading}°', {
|
? t('{label} · COURSE {heading}°', {
|
||||||
|
context: 'vessel',
|
||||||
label: info.name,
|
label: info.name,
|
||||||
heading: trackHeading,
|
heading: trackHeading,
|
||||||
})
|
})
|
||||||
|
|||||||
+34
-11
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { t } from '../../i18n/index.js';
|
import { formatNumber, t, tn } from '../../i18n/index.js';
|
||||||
import UiButton from '../kit/UiButton.vue';
|
import UiButton from '../kit/UiButton.vue';
|
||||||
import UiEmpty from '../kit/UiEmpty.vue';
|
import UiEmpty from '../kit/UiEmpty.vue';
|
||||||
import UiSelect from '../kit/UiSelect.js';
|
import UiSelect from '../kit/UiSelect.js';
|
||||||
@@ -63,9 +63,15 @@ const categoryOptions = computed(() => [
|
|||||||
const windowOptions = computed(() =>
|
const windowOptions = computed(() =>
|
||||||
(props.view?.windowChoices ?? []).map((hours) => ({
|
(props.view?.windowChoices ?? []).map((hours) => ({
|
||||||
value: String(hours),
|
value: String(hours),
|
||||||
label: t('Last {hours} h', { hours }),
|
label: lastHours(hours),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
// The window offers 1 h, so every hour count is a plural: es «Última 1 h».
|
||||||
|
function lastHours(hours) {
|
||||||
|
return tn(hours, 'Last {hours} h', 'Last {hours} h', {
|
||||||
|
hours: formatNumber(hours),
|
||||||
|
});
|
||||||
|
}
|
||||||
// The longest choice is the server's whole window, kept as no choice.
|
// The longest choice is the server's whole window, kept as no choice.
|
||||||
const chooseWindow = (hours) =>
|
const chooseWindow = (hours) =>
|
||||||
setFilter('hours', hours === props.view?.windowChoices?.[0] ? null : hours);
|
setFilter('hours', hours === props.view?.windowChoices?.[0] ? null : hours);
|
||||||
@@ -109,7 +115,16 @@ const clear = () => props.onParams({ clear: true });
|
|||||||
<section
|
<section
|
||||||
v-if="view"
|
v-if="view"
|
||||||
class="news-readout"
|
class="news-readout"
|
||||||
:aria-label="t('News, last {hours} hours', { hours: view.windowHours })"
|
:aria-label="
|
||||||
|
tn(
|
||||||
|
view.windowHours,
|
||||||
|
'News, last {hours} hour',
|
||||||
|
'News, last {hours} hours',
|
||||||
|
{
|
||||||
|
hours: formatNumber(view.windowHours),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<UiSelect
|
<UiSelect
|
||||||
v-if="windowOptions.length > 1"
|
v-if="windowOptions.length > 1"
|
||||||
@@ -120,7 +135,7 @@ const clear = () => props.onParams({ clear: true });
|
|||||||
@update:model-value="chooseWindow(Number($event))"
|
@update:model-value="chooseWindow(Number($event))"
|
||||||
/>
|
/>
|
||||||
<p v-else class="news-readout__window ui-mono">{{
|
<p v-else class="news-readout__window ui-mono">{{
|
||||||
t('Last {hours} h', { hours: view.windowHours })
|
lastHours(view.windowHours)
|
||||||
}}</p>
|
}}</p>
|
||||||
<div
|
<div
|
||||||
class="news-readout__sections"
|
class="news-readout__sections"
|
||||||
@@ -196,9 +211,12 @@ const clear = () => props.onParams({ clear: true });
|
|||||||
v-else-if="!view.inView.items.length && !view.inView.loading"
|
v-else-if="!view.inView.items.length && !view.inView.loading"
|
||||||
class="news-readout__empty"
|
class="news-readout__empty"
|
||||||
:title="
|
:title="
|
||||||
t('Nothing reported near here in the last {hours} h', {
|
tn(
|
||||||
hours: view.windowHours,
|
view.windowHours,
|
||||||
})
|
'Nothing reported near here in the last {hours} h',
|
||||||
|
'Nothing reported near here in the last {hours} h',
|
||||||
|
{ hours: formatNumber(view.windowHours) },
|
||||||
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<ol
|
<ol
|
||||||
@@ -223,7 +241,9 @@ const clear = () => props.onParams({ clear: true });
|
|||||||
class="news-readout__section"
|
class="news-readout__section"
|
||||||
:aria-label="t('Latest news')"
|
:aria-label="t('Latest news')"
|
||||||
>
|
>
|
||||||
<h3 class="news-readout__head ui-mono">{{ t('Latest') }}</h3>
|
<h3 class="news-readout__head ui-mono">{{
|
||||||
|
t('Latest', { context: 'news list' })
|
||||||
|
}}</h3>
|
||||||
<UiEmpty
|
<UiEmpty
|
||||||
v-if="view.key"
|
v-if="view.key"
|
||||||
class="news-readout__empty"
|
class="news-readout__empty"
|
||||||
@@ -247,9 +267,12 @@ const clear = () => props.onParams({ clear: true });
|
|||||||
v-else-if="!view.loading && !view.error"
|
v-else-if="!view.loading && !view.error"
|
||||||
class="news-readout__empty"
|
class="news-readout__empty"
|
||||||
:title="
|
:title="
|
||||||
t('No news matches these filters in the last {hours} h', {
|
tn(
|
||||||
hours: view.windowHours,
|
view.windowHours,
|
||||||
})
|
'No news matches these filters in the last {hours} h',
|
||||||
|
'No news matches these filters in the last {hours} h',
|
||||||
|
{ hours: formatNumber(view.windowHours) },
|
||||||
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div class="news-readout__footer">
|
<div class="news-readout__footer">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import '../testSupport/registerDom.mjs';
|
import '../testSupport/registerDom.mjs';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import { setLocale, t as translate } from '../i18n/index.js';
|
||||||
import { createNewsPanel } from './newsPanel.js';
|
import { createNewsPanel } from './newsPanel.js';
|
||||||
import {
|
import {
|
||||||
NEWS_CATEGORIES,
|
NEWS_CATEGORIES,
|
||||||
@@ -385,6 +386,55 @@ test('the window choice offers shorter windows; the longest is the server window
|
|||||||
assert.deepEqual(calls.at(-1)[1], { filters: { hours: null } });
|
assert.deepEqual(calls.at(-1)[1], { filters: { hours: null } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the time window agrees with its number of hours', (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('es');
|
||||||
|
const { news, panel } = fixture();
|
||||||
|
news.update(view({ windowHours: 24, windowChoices: [24, 12, 6, 3, 1] }));
|
||||||
|
const choice = panel.querySelector(
|
||||||
|
'select.news-readout__window-choice, .news-readout__window-choice select',
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
[...choice.options].map((option) => option.textContent),
|
||||||
|
['Últimas 24 h', 'Últimas 12 h', 'Últimas 6 h', 'Últimas 3 h', 'Última 1 h'],
|
||||||
|
);
|
||||||
|
news.update(view({ windowHours: 1, items: [], hasMore: false }));
|
||||||
|
assert.equal(
|
||||||
|
panel.querySelector('.news-readout__window').textContent,
|
||||||
|
'Última 1 h',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
panel.querySelector('.news-readout').getAttribute('aria-label'),
|
||||||
|
'Noticias de la última 1 hora',
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
panel.textContent,
|
||||||
|
/En la última 1 h no hay noticias que coincidan con estos filtros/,
|
||||||
|
);
|
||||||
|
setLocale('ru');
|
||||||
|
news.update(view({ windowHours: 1, items: [], hasMore: false }));
|
||||||
|
assert.equal(
|
||||||
|
panel.querySelector('.news-readout').getAttribute('aria-label'),
|
||||||
|
'Новости за последний 1 час',
|
||||||
|
);
|
||||||
|
news.update(view({ windowHours: 12, items: [], hasMore: false }));
|
||||||
|
assert.equal(
|
||||||
|
panel.querySelector('.news-readout').getAttribute('aria-label'),
|
||||||
|
'Новости за последние 12 часов',
|
||||||
|
);
|
||||||
|
news.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the Latest news heading is worded apart from the timeline button (fr)', (t) => {
|
||||||
|
t.after(() => setLocale('en'));
|
||||||
|
setLocale('fr');
|
||||||
|
const { news, panel } = fixture();
|
||||||
|
news.update(view());
|
||||||
|
assert.equal(panel.querySelector('.news-readout__head').textContent, 'Dernières');
|
||||||
|
assert.equal(translate('Latest'), 'Dernière', 'the timeline keeps its own');
|
||||||
|
news.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
test('the panel names the window the server serves', () => {
|
test('the panel names the window the server serves', () => {
|
||||||
const { news, panel } = fixture();
|
const { news, panel } = fixture();
|
||||||
news.update(view({ windowHours: 12 }));
|
news.update(view({ windowHours: 12 }));
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
import { useStatusStore } from './stores/status.js';
|
|
||||||
import { applicationPinia } from './vueRuntime.js';
|
|
||||||
import { describeError } from '../app/errors.js';
|
|
||||||
import { appRootUrl } from '../share/shareLinkPath.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a recoverable notice when the globe stops drawing, instead of a black
|
|
||||||
* globe behind working controls. Retry restarts the render loop, or reloads
|
|
||||||
* after a lost WebGL context; Reset opens the app's root address. With
|
|
||||||
* `allowReload: false`, Reset is dropped, and so is Retry after a context loss.
|
|
||||||
* Returns a function that removes the notice.
|
|
||||||
*/
|
|
||||||
export function installRenderFailureNotice(
|
|
||||||
viewer,
|
|
||||||
{
|
|
||||||
store = useStatusStore(applicationPinia()),
|
|
||||||
locationRef = globalThis.location,
|
|
||||||
allowReload = true,
|
|
||||||
logError = (error) =>
|
|
||||||
console.error('[Render] The globe stopped drawing:', error),
|
|
||||||
} = {},
|
|
||||||
) {
|
|
||||||
const { scene } = viewer;
|
|
||||||
const { canvas } = viewer;
|
|
||||||
let notice = null;
|
|
||||||
let contextLost = false;
|
|
||||||
// With rethrowRenderErrors, Scene throws the error it has just raised;
|
|
||||||
// the render() wrapper must not raise it again.
|
|
||||||
let raisedByScene = null;
|
|
||||||
let frameFailed = false;
|
|
||||||
let removeFrameListeners = null;
|
|
||||||
|
|
||||||
function retry() {
|
|
||||||
if (contextLost) {
|
|
||||||
locationRef.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
scene.requestRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
locationRef.assign(appRootUrl(locationRef.href));
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopWatchingFrames() {
|
|
||||||
removeFrameListeners?.();
|
|
||||||
removeFrameListeners = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
stopWatchingFrames();
|
|
||||||
if (store.renderFailure === notice) store.publishRenderFailure(null);
|
|
||||||
notice = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cesium raises postRender after a failed frame too, so only a frame that
|
|
||||||
// starts and ends without a render error clears the notice.
|
|
||||||
function watchFrames() {
|
|
||||||
if (removeFrameListeners || contextLost) return;
|
|
||||||
const removeStart = scene.preUpdate.addEventListener(() => {
|
|
||||||
frameFailed = false;
|
|
||||||
raisedByScene = null;
|
|
||||||
});
|
|
||||||
const removeEnd = scene.postRender.addEventListener(() => {
|
|
||||||
if (!frameFailed) clear();
|
|
||||||
});
|
|
||||||
removeFrameListeners = () => {
|
|
||||||
removeStart();
|
|
||||||
removeEnd();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function present(detail) {
|
|
||||||
const reason = contextLost ? 'context-lost' : 'render-error';
|
|
||||||
// A loop restarted into the same error keeps the notice as it was.
|
|
||||||
if (notice?.reason === reason && notice.detail === detail) return;
|
|
||||||
notice = {
|
|
||||||
reason,
|
|
||||||
detail,
|
|
||||||
retry: contextLost && !allowReload ? null : retry,
|
|
||||||
reset: allowReload ? reset : null,
|
|
||||||
};
|
|
||||||
store.publishRenderFailure(notice);
|
|
||||||
watchFrames();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onRenderError = (_scene, error) => {
|
|
||||||
raisedByScene = { error };
|
|
||||||
frameFailed = true;
|
|
||||||
logError(error);
|
|
||||||
// A lost context fails every frame after it; the notice keeps saying why.
|
|
||||||
if (contextLost) return;
|
|
||||||
present(error ? describeError(error) : '');
|
|
||||||
};
|
|
||||||
const onContextLost = () => {
|
|
||||||
contextLost = true;
|
|
||||||
// No frame can clear this one; only a reload draws again.
|
|
||||||
stopWatchingFrames();
|
|
||||||
present('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeRenderError = scene.renderError.addEventListener(onRenderError);
|
|
||||||
// An error thrown outside Scene's error handling (a frame listener, onTick,
|
|
||||||
// resize) stops the loop without a renderError. Raise one, so Cesium, an
|
|
||||||
// inline panel's timer and this notice all see it. First finish the event
|
|
||||||
// raises it cut short, so listeners added or removed meanwhile apply.
|
|
||||||
const unguard = ['resize', 'render'].map((name) =>
|
|
||||||
reportThrown(viewer.cesiumWidget, name, (error) => {
|
|
||||||
frameEvents(viewer).forEach(finishRaise);
|
|
||||||
if (!raisedByScene || raisedByScene.error !== error)
|
|
||||||
scene.renderError.raiseEvent(scene, error);
|
|
||||||
raisedByScene = null;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
canvas.addEventListener('webglcontextlost', onContextLost);
|
|
||||||
return () => {
|
|
||||||
removeRenderError();
|
|
||||||
unguard.forEach((restore) => restore());
|
|
||||||
canvas.removeEventListener('webglcontextlost', onContextLost);
|
|
||||||
clear();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events Cesium raises in a frame outside Scene's error handling, which a
|
|
||||||
* listener's error can leave mid-raise. Only top-level tilesets are reached.
|
|
||||||
*/
|
|
||||||
function frameEvents(viewer) {
|
|
||||||
const { scene } = viewer;
|
|
||||||
const events = [
|
|
||||||
scene.preUpdate,
|
|
||||||
scene.postUpdate,
|
|
||||||
scene.preRender,
|
|
||||||
scene.postRender,
|
|
||||||
scene.camera?.changed,
|
|
||||||
scene.camera?.moveStart,
|
|
||||||
scene.camera?.moveEnd,
|
|
||||||
viewer.clock?.onTick,
|
|
||||||
viewer.clock?.onStop,
|
|
||||||
scene.globe?.tileLoadProgressEvent,
|
|
||||||
];
|
|
||||||
const primitives = scene.primitives;
|
|
||||||
for (let i = 0; i < (primitives?.length ?? 0); i += 1) {
|
|
||||||
const tileset = primitives.get(i);
|
|
||||||
events.push(
|
|
||||||
tileset?.loadProgress,
|
|
||||||
tileset?.allTilesLoaded,
|
|
||||||
tileset?.initialTilesLoaded,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish a raise a throwing listener cut short. Until a raise ends, Cesium's
|
|
||||||
* `Event` only queues added and removed listeners; apply that queue.
|
|
||||||
*/
|
|
||||||
function finishRaise(event) {
|
|
||||||
if (!event?._invokingListeners) return;
|
|
||||||
event._invokingListeners = false;
|
|
||||||
for (const [listener, scopes] of event._toAdd) {
|
|
||||||
const added = event._listeners.get(listener) ?? new Set();
|
|
||||||
scopes.forEach((scope) => added.add(scope));
|
|
||||||
event._listeners.set(listener, added);
|
|
||||||
}
|
|
||||||
event._toAdd.clear();
|
|
||||||
for (const [listener, scopes] of event._toRemove) {
|
|
||||||
const left = event._listeners.get(listener);
|
|
||||||
scopes.forEach((scope) => left?.delete(scope));
|
|
||||||
if (left?.size === 0) event._listeners.delete(listener);
|
|
||||||
}
|
|
||||||
event._toRemove.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report an error the widget's `name` method throws, and rethrow it.
|
|
||||||
*
|
|
||||||
* A render error marks the loop stopped, but a call made while it ran leaves
|
|
||||||
* its next frame queued, and that frame stops it. Keep the loop marked
|
|
||||||
* running, so a Retry or a shown tab before that frame starts no second loop.
|
|
||||||
* After a throw, the loop's own catch still marks it stopped.
|
|
||||||
*/
|
|
||||||
function reportThrown(widget, name, report) {
|
|
||||||
const original = widget?.[name];
|
|
||||||
if (typeof original !== 'function') return () => {};
|
|
||||||
const own = Object.hasOwn(widget, name);
|
|
||||||
const guarded = function (...args) {
|
|
||||||
const looping = widget._renderLoopRunning;
|
|
||||||
try {
|
|
||||||
return original.apply(this, args);
|
|
||||||
} catch (error) {
|
|
||||||
report(error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
if (looping) widget._renderLoopRunning = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
widget[name] = guarded;
|
|
||||||
return () => {
|
|
||||||
if (widget[name] !== guarded) return;
|
|
||||||
if (own) widget[name] = original;
|
|
||||||
else delete widget[name];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
|
||||||
|
|
||||||
function fixture(t, options = {}) {
|
|
||||||
const canvasListeners = new Map();
|
|
||||||
const scene = {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
requestRender() {
|
|
||||||
this.requests = (this.requests ?? 0) + 1;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const viewer = {
|
|
||||||
scene,
|
|
||||||
useDefaultRenderLoop: true,
|
|
||||||
canvas: {
|
|
||||||
addEventListener: (name, listener) => canvasListeners.set(name, listener),
|
|
||||||
removeEventListener: (name, listener) => {
|
|
||||||
if (canvasListeners.get(name) === listener)
|
|
||||||
canvasListeners.delete(name);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
published: [],
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
this.published.push(next);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const visits = [];
|
|
||||||
const locationRef = {
|
|
||||||
href: 'https://gev.example/share/AQc?lang=ru#lat=48.8584&lon=2.2945',
|
|
||||||
reload: () => visits.push('reload'),
|
|
||||||
assign: (url) => visits.push(String(url)),
|
|
||||||
};
|
|
||||||
const logged = [];
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef,
|
|
||||||
logError: (error) => logged.push(error),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
t.after(uninstall);
|
|
||||||
// One Cesium frame: preUpdate, an optional render error, then postRender,
|
|
||||||
// which Cesium raises after a failed frame too.
|
|
||||||
const frame = (error) => {
|
|
||||||
scene.preUpdate.raiseEvent(scene);
|
|
||||||
if (error) {
|
|
||||||
viewer.useDefaultRenderLoop = false;
|
|
||||||
scene.renderError.raiseEvent(scene, error);
|
|
||||||
}
|
|
||||||
scene.postRender.raiseEvent(scene);
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
viewer,
|
|
||||||
scene,
|
|
||||||
store,
|
|
||||||
visits,
|
|
||||||
logged,
|
|
||||||
uninstall,
|
|
||||||
frame,
|
|
||||||
loseContext: () => canvasListeners.get('webglcontextlost')?.({}),
|
|
||||||
canvasListeners,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test('a render error shows a notice that survives the failed frame', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
assert.equal(f.store.renderFailure, null);
|
|
||||||
const error = new Error('Expected width to be greater than 0');
|
|
||||||
f.frame(error);
|
|
||||||
const notice = f.store.renderFailure;
|
|
||||||
assert.equal(notice.reason, 'render-error');
|
|
||||||
assert.equal(notice.detail, 'Expected width to be greater than 0');
|
|
||||||
assert.equal(typeof notice.retry, 'function');
|
|
||||||
assert.equal(typeof notice.reset, 'function');
|
|
||||||
assert.deepEqual(f.logged, [error]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Retry restarts the render loop and a clean frame clears the notice', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
f.store.renderFailure.retry();
|
|
||||||
assert.equal(f.viewer.useDefaultRenderLoop, true);
|
|
||||||
assert.equal(f.scene.requests, 1);
|
|
||||||
assert.ok(f.store.renderFailure, 'the notice waits for a frame');
|
|
||||||
f.frame();
|
|
||||||
assert.equal(f.store.renderFailure, null);
|
|
||||||
// Clearing stops the per-frame listeners.
|
|
||||||
assert.equal(f.scene.preUpdate.numberOfListeners, 0);
|
|
||||||
assert.equal(f.scene.postRender.numberOfListeners, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a loop restarted into the same error keeps one notice', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
const first = f.store.renderFailure;
|
|
||||||
f.store.renderFailure.retry();
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
assert.equal(f.store.renderFailure, first);
|
|
||||||
assert.equal(f.store.published.length, 1);
|
|
||||||
f.frame(new Error('other'));
|
|
||||||
assert.equal(f.store.renderFailure.detail, 'other');
|
|
||||||
assert.ok(!f.store.published.includes(null), 'no new appearance');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a new failure after recovery is a new appearance', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
f.store.renderFailure.retry();
|
|
||||||
f.frame();
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
assert.deepEqual(
|
|
||||||
f.store.published.map((notice) => notice?.reason ?? null),
|
|
||||||
['render-error', null, 'render-error'],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a render error without a value shows no detail', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.viewer.useDefaultRenderLoop = false;
|
|
||||||
f.scene.renderError.raiseEvent(f.scene, undefined);
|
|
||||||
assert.equal(f.store.renderFailure.reason, 'render-error');
|
|
||||||
assert.equal(f.store.renderFailure.detail, '');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Reset opens the app root without the restored view', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
f.store.renderFailure.reset();
|
|
||||||
assert.deepEqual(f.visits, ['https://gev.example/?lang=ru']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a lost context offers a reload and no frame clears it', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.loseContext();
|
|
||||||
const notice = f.store.renderFailure;
|
|
||||||
assert.equal(notice.reason, 'context-lost');
|
|
||||||
assert.equal(notice.detail, '');
|
|
||||||
// Cesium's next frame then fails on the dead drawing buffer.
|
|
||||||
f.frame(new Error('Expected width to be greater than 0'));
|
|
||||||
assert.equal(f.store.renderFailure, notice, 'the notice keeps saying why');
|
|
||||||
f.frame();
|
|
||||||
assert.ok(f.store.renderFailure, 'a lost context never draws again');
|
|
||||||
f.store.renderFailure.retry();
|
|
||||||
assert.deepEqual(f.visits, ['reload']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a page that must not reload offers neither reload nor reset', (t) => {
|
|
||||||
const f = fixture(t, { allowReload: false });
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
assert.equal(typeof f.store.renderFailure.retry, 'function');
|
|
||||||
assert.equal(f.store.renderFailure.reset, null);
|
|
||||||
f.loseContext();
|
|
||||||
assert.equal(f.store.renderFailure.retry, null);
|
|
||||||
assert.equal(f.store.renderFailure.reset, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uninstall removes the listeners and the notice', (t) => {
|
|
||||||
const f = fixture(t);
|
|
||||||
f.frame(new Error('boom'));
|
|
||||||
f.uninstall();
|
|
||||||
assert.equal(f.store.renderFailure, null);
|
|
||||||
assert.equal(f.scene.renderError.numberOfListeners, 0);
|
|
||||||
assert.equal(f.scene.postRender.numberOfListeners, 0);
|
|
||||||
assert.equal(f.canvasListeners.size, 0);
|
|
||||||
f.frame(new Error('later'));
|
|
||||||
assert.equal(f.store.renderFailure, null);
|
|
||||||
});
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
// Retry after a scene event listener throws. Cesium's Event.raiseEvent has no
|
|
||||||
// try/finally, so the event stays mid-raise and only queues listener changes.
|
|
||||||
// Drives Cesium's real Event and startRenderLoop.
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
|
||||||
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
// Scene.render as in Scene.js:4369: preUpdate always, preRender/postRender
|
|
||||||
// only when a frame is rendered (requestRenderMode idle skips them).
|
|
||||||
const scene = {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
preRender: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
requestRenderMode: true,
|
|
||||||
_renderRequested: false,
|
|
||||||
rendered: 0,
|
|
||||||
requestRender() {
|
|
||||||
this._renderRequested = true;
|
|
||||||
},
|
|
||||||
initializeFrame() {},
|
|
||||||
render(time) {
|
|
||||||
this.preUpdate.raiseEvent(this, time);
|
|
||||||
const shouldRender = !this.requestRenderMode || this._renderRequested;
|
|
||||||
if (!shouldRender) return;
|
|
||||||
this._renderRequested = false;
|
|
||||||
this.preRender.raiseEvent(this, time);
|
|
||||||
this.rendered += 1;
|
|
||||||
this.postRender.raiseEvent(this, time);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return { scene, ...renderLoopFixture(scene) };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const eventName of ['postRender', 'preUpdate']) {
|
|
||||||
test(`idle: a ${eventName} listener that threw once; a successful Retry clears the notice`, () => {
|
|
||||||
const { scene, viewer, store, runFrames, restore } = setup();
|
|
||||||
try {
|
|
||||||
let broken = true;
|
|
||||||
scene[eventName].addEventListener(() => {
|
|
||||||
if (broken) throw new TypeError('listener failed');
|
|
||||||
});
|
|
||||||
scene.requestRender();
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(1);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
assert.equal(viewer.useDefaultRenderLoop, false);
|
|
||||||
broken = false; // the cause goes away
|
|
||||||
store.renderFailure.retry();
|
|
||||||
const before = scene.rendered;
|
|
||||||
runFrames(10); // idle: no camera move, no render request
|
|
||||||
assert.ok(scene.rendered > before, 'the globe drew again');
|
|
||||||
assert.equal(store.renderFailure, null, 'the notice is cleared');
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('a throwing postRender listener removed before Retry is not called again', () => {
|
|
||||||
const { scene, viewer, store, runFrames, restore } = setup();
|
|
||||||
try {
|
|
||||||
let calls = 0;
|
|
||||||
const remove = scene.postRender.addEventListener(() => {
|
|
||||||
calls += 1;
|
|
||||||
throw new TypeError('layer listener failed');
|
|
||||||
});
|
|
||||||
scene.requestRender();
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(1);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
remove(); // the user turns the layer off
|
|
||||||
store.renderFailure.retry();
|
|
||||||
runFrames(5);
|
|
||||||
assert.equal(calls, 1, 'the removed listener ran again after Retry');
|
|
||||||
assert.equal(viewer.useDefaultRenderLoop, true, 'the loop keeps running');
|
|
||||||
assert.equal(store.renderFailure, null, 'the notice is cleared');
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const listenerCount = (event) =>
|
|
||||||
[...event._listeners.values()].reduce((sum, scopes) => sum + scopes.size, 0);
|
|
||||||
|
|
||||||
// While the loop is stopped, a layer turned off and on again, or a ground
|
|
||||||
// task in worldAnnotationRenderer that starts, times out and starts again.
|
|
||||||
const RESUBSCRIBE = {
|
|
||||||
'turned off and on': (event, listener) => {
|
|
||||||
event.removeEventListener(listener);
|
|
||||||
event.addEventListener(listener);
|
|
||||||
},
|
|
||||||
'added, removed and added again': (event, listener) => {
|
|
||||||
event.addEventListener(listener);
|
|
||||||
event.removeEventListener(listener);
|
|
||||||
event.addEventListener(listener);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [name, resubscribe] of Object.entries(RESUBSCRIBE)) {
|
|
||||||
test(`a listener ${name} while the loop is stopped runs after Retry`, () => {
|
|
||||||
const { scene, viewer, store, runFrames, restore } = setup();
|
|
||||||
try {
|
|
||||||
let broken = true;
|
|
||||||
scene.postRender.addEventListener(() => {
|
|
||||||
if (broken) throw new TypeError('layer listener failed');
|
|
||||||
});
|
|
||||||
let calls = 0;
|
|
||||||
const listener = () => {
|
|
||||||
calls += 1;
|
|
||||||
};
|
|
||||||
if (name === 'turned off and on')
|
|
||||||
scene.postRender.addEventListener(listener);
|
|
||||||
scene.requestRender();
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(1);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
resubscribe(scene.postRender, listener);
|
|
||||||
broken = false;
|
|
||||||
store.renderFailure.retry();
|
|
||||||
calls = 0;
|
|
||||||
scene.requestRender();
|
|
||||||
runFrames(3);
|
|
||||||
assert.ok(calls > 0, 'the re-added listener runs after Retry');
|
|
||||||
assert.equal(
|
|
||||||
scene.postRender.numberOfListeners,
|
|
||||||
listenerCount(scene.postRender),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every event a listener's error can cut short is finished when the error
|
|
||||||
// surfaces, so a listener added at once is subscribed.
|
|
||||||
const FRAME_EVENTS = {
|
|
||||||
'scene.preUpdate': (v) => v.scene.preUpdate,
|
|
||||||
'scene.postUpdate': (v) => v.scene.postUpdate,
|
|
||||||
'scene.preRender': (v) => v.scene.preRender,
|
|
||||||
'scene.postRender': (v) => v.scene.postRender,
|
|
||||||
'camera.changed': (v) => v.scene.camera.changed,
|
|
||||||
'camera.moveStart': (v) => v.scene.camera.moveStart,
|
|
||||||
'camera.moveEnd': (v) => v.scene.camera.moveEnd,
|
|
||||||
'clock.onTick': (v) => v.clock.onTick,
|
|
||||||
'clock.onStop': (v) => v.clock.onStop,
|
|
||||||
'globe.tileLoadProgressEvent': (v) => v.scene.globe.tileLoadProgressEvent,
|
|
||||||
'tileset.loadProgress': (v) => v.scene.primitives.get(0).loadProgress,
|
|
||||||
'tileset.allTilesLoaded': (v) => v.scene.primitives.get(0).allTilesLoaded,
|
|
||||||
'tileset.initialTilesLoaded': (v) =>
|
|
||||||
v.scene.primitives.get(0).initialTilesLoaded,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [name, pick] of Object.entries(FRAME_EVENTS)) {
|
|
||||||
test(`a listener that throws in ${name} leaves it finished`, () => {
|
|
||||||
const events = () => ({
|
|
||||||
changed: new Cesium.Event(),
|
|
||||||
moveStart: new Cesium.Event(),
|
|
||||||
moveEnd: new Cesium.Event(),
|
|
||||||
});
|
|
||||||
const tileset = {
|
|
||||||
loadProgress: new Cesium.Event(),
|
|
||||||
allTilesLoaded: new Cesium.Event(),
|
|
||||||
initialTilesLoaded: new Cesium.Event(),
|
|
||||||
};
|
|
||||||
const viewer = {
|
|
||||||
scene: {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
postUpdate: new Cesium.Event(),
|
|
||||||
preRender: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
camera: events(),
|
|
||||||
globe: { tileLoadProgressEvent: new Cesium.Event() },
|
|
||||||
primitives: { length: 1, get: () => tileset },
|
|
||||||
},
|
|
||||||
clock: { onTick: new Cesium.Event(), onStop: new Cesium.Event() },
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
cesiumWidget: {
|
|
||||||
_renderLoopRunning: false,
|
|
||||||
render: () => pick(viewer).raiseEvent(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const store = { publishRenderFailure() {} };
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError() {},
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const event = pick(viewer);
|
|
||||||
event.addEventListener(() => {
|
|
||||||
throw new TypeError('listener failed');
|
|
||||||
});
|
|
||||||
assert.throws(() => viewer.cesiumWidget.render(), TypeError);
|
|
||||||
assert.equal(event._invokingListeners, false);
|
|
||||||
const listener = () => {};
|
|
||||||
event.addEventListener(listener);
|
|
||||||
assert.ok(event._listeners.has(listener));
|
|
||||||
} finally {
|
|
||||||
uninstall();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// Errors thrown in the render loop outside Scene's tryAndCatchError. Drives
|
|
||||||
// Cesium's real startRenderLoop through the useDefaultRenderLoop setter.
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
|
||||||
|
|
||||||
function runLoop(render) {
|
|
||||||
const scene = {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
requestRender() {},
|
|
||||||
initializeFrame() {},
|
|
||||||
render,
|
|
||||||
};
|
|
||||||
const fixture = renderLoopFixture(scene);
|
|
||||||
fixture.viewer.useDefaultRenderLoop = true;
|
|
||||||
fixture.runFrames(3);
|
|
||||||
return { ...fixture, uninstall: fixture.restore };
|
|
||||||
}
|
|
||||||
|
|
||||||
test('an error thrown outside tryAndCatchError still shows the notice', () => {
|
|
||||||
// Scene.render raises preRender outside tryAndCatchError (Scene.js:4439),
|
|
||||||
// so a listener's exception propagates.
|
|
||||||
const { widget, store, logged, uninstall } = runLoop(function () {
|
|
||||||
this.preUpdate.raiseEvent(this);
|
|
||||||
throw new TypeError("Cannot read properties of undefined (reading 'x')");
|
|
||||||
});
|
|
||||||
assert.equal(widget.useDefaultRenderLoop, false);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
assert.equal(logged.length, 1);
|
|
||||||
uninstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('an error Scene raised and threw on with rethrowRenderErrors is reported once', () => {
|
|
||||||
const { store, logged, uninstall } = runLoop(function () {
|
|
||||||
this.preUpdate.raiseEvent(this);
|
|
||||||
const error = new Error('boom');
|
|
||||||
this.renderError.raiseEvent(this, error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
assert.equal(store.renderFailure?.detail, 'boom');
|
|
||||||
assert.equal(logged.length, 1);
|
|
||||||
uninstall();
|
|
||||||
});
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// Retry after an error Scene catches itself: the loop is marked stopped but
|
|
||||||
// its next frame is still queued, so an early Retry must not start a second
|
|
||||||
// loop. Drives Cesium's real startRenderLoop and useDefaultRenderLoop setter.
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
|
||||||
|
|
||||||
function setup({ targetFrameRate } = {}) {
|
|
||||||
const scene = {
|
|
||||||
renderError: new Cesium.Event(),
|
|
||||||
preUpdate: new Cesium.Event(),
|
|
||||||
preRender: new Cesium.Event(),
|
|
||||||
postRender: new Cesium.Event(),
|
|
||||||
broken: false,
|
|
||||||
drawn: 0,
|
|
||||||
requestRender() {},
|
|
||||||
initializeFrame() {},
|
|
||||||
// Scene.render (Scene.js:4369): an update error is caught by
|
|
||||||
// tryAndCatchError, which raises renderError and returns normally.
|
|
||||||
render(time) {
|
|
||||||
this.drawn += 1;
|
|
||||||
this.preUpdate.raiseEvent(this, time);
|
|
||||||
if (this.broken) this.renderError.raiseEvent(this, new Error('boom'));
|
|
||||||
this.preRender.raiseEvent(this, time);
|
|
||||||
this.postRender.raiseEvent(this, time);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return { scene, ...renderLoopFixture(scene, { targetFrameRate }) };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const targetFrameRate of [undefined, 60]) {
|
|
||||||
test(`targetFrameRate ${targetFrameRate}: Retry before the next frame after an error Scene caught keeps one render loop`, () => {
|
|
||||||
const { frames, scene, viewer, store, runFrames, restore } = setup({
|
|
||||||
targetFrameRate,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(2);
|
|
||||||
scene.broken = true;
|
|
||||||
runFrames(1);
|
|
||||||
assert.equal(store.renderFailure?.reason, 'render-error');
|
|
||||||
// The cause is gone; Retry runs (a queued Enter on the focused Retry)
|
|
||||||
// before the loop's next animation frame.
|
|
||||||
scene.broken = false;
|
|
||||||
store.renderFailure.retry();
|
|
||||||
assert.equal(frames.length, 1, 'one animation frame queued after Retry');
|
|
||||||
scene.drawn = 0;
|
|
||||||
runFrames(3);
|
|
||||||
assert.equal(store.renderFailure, null);
|
|
||||||
assert.equal(scene.drawn, 3, 'the globe draws once a frame');
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('control: Retry after the next frame ran keeps one render loop', () => {
|
|
||||||
const { frames, scene, viewer, store, runFrames, restore } = setup();
|
|
||||||
try {
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(2);
|
|
||||||
scene.broken = true;
|
|
||||||
runFrames(1);
|
|
||||||
scene.broken = false;
|
|
||||||
runFrames(1);
|
|
||||||
assert.equal(frames.length, 0, 'the stopped loop queued nothing');
|
|
||||||
store.renderFailure.retry();
|
|
||||||
scene.drawn = 0;
|
|
||||||
runFrames(3);
|
|
||||||
assert.equal(scene.drawn, 3);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a tab hidden and shown again before the next frame after an error Scene caught keeps one render loop', () => {
|
|
||||||
const { scene, viewer, runFrames, restore } = setup();
|
|
||||||
try {
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
runFrames(2);
|
|
||||||
scene.broken = true;
|
|
||||||
runFrames(1);
|
|
||||||
scene.broken = false;
|
|
||||||
// src/app/tools.js turns the loop off while the tab is hidden and back on
|
|
||||||
// when it is shown, before the queued frame runs.
|
|
||||||
viewer.useDefaultRenderLoop = false;
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
scene.drawn = 0;
|
|
||||||
runFrames(3);
|
|
||||||
assert.equal(scene.drawn, 3, 'the globe draws once a frame');
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
// The scene chrome renders the render failure notice and its actions.
|
|
||||||
import '../testSupport/registerDom.mjs';
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { createApp, nextTick } from 'vue';
|
|
||||||
import { createPinia } from 'pinia';
|
|
||||||
import { useStatusStore } from './stores/status.js';
|
|
||||||
import SceneChromeView from './views/SceneChromeView.vue';
|
|
||||||
|
|
||||||
function mount(t) {
|
|
||||||
document.body.replaceChildren();
|
|
||||||
const pinia = createPinia();
|
|
||||||
const app = createApp(SceneChromeView).use(pinia);
|
|
||||||
app.mount(document.body.appendChild(document.createElement('div')));
|
|
||||||
t.after(() => app.unmount());
|
|
||||||
return useStatusStore(pinia);
|
|
||||||
}
|
|
||||||
|
|
||||||
const notice = () => document.getElementById('render-failure');
|
|
||||||
const buttons = () =>
|
|
||||||
[...(notice()?.querySelectorAll('button') ?? [])].map((button) =>
|
|
||||||
button.textContent.trim(),
|
|
||||||
);
|
|
||||||
|
|
||||||
test('a render failure shows an alert dialog that moves focus to Retry', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
assert.equal(notice(), null, 'no notice while the globe draws');
|
|
||||||
const calls = [];
|
|
||||||
store.publishRenderFailure({
|
|
||||||
reason: 'render-error',
|
|
||||||
detail: 'Expected width to be greater than 0',
|
|
||||||
retry: () => calls.push('retry'),
|
|
||||||
reset: () => calls.push('reset'),
|
|
||||||
});
|
|
||||||
await nextTick();
|
|
||||||
await nextTick();
|
|
||||||
const element = notice();
|
|
||||||
assert.equal(element.getAttribute('role'), 'alertdialog');
|
|
||||||
assert.equal(
|
|
||||||
document.getElementById(element.getAttribute('aria-labelledby'))
|
|
||||||
.textContent,
|
|
||||||
'The globe stopped drawing',
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
document.getElementById(element.getAttribute('aria-describedby'))
|
|
||||||
.textContent,
|
|
||||||
/^Rendering stopped after an error\./,
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
element.querySelector('.render-failure-detail').textContent,
|
|
||||||
'Expected width to be greater than 0',
|
|
||||||
);
|
|
||||||
assert.deepEqual(buttons(), ['Retry', 'Reset view']);
|
|
||||||
assert.equal(document.activeElement?.textContent.trim(), 'Retry');
|
|
||||||
for (const button of element.querySelectorAll('button')) button.click();
|
|
||||||
assert.deepEqual(calls, ['retry', 'reset']);
|
|
||||||
store.publishRenderFailure(null);
|
|
||||||
await nextTick();
|
|
||||||
assert.equal(notice(), null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a lost context offers Reload, and a panel that cannot reload offers nothing', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
store.publishRenderFailure({
|
|
||||||
reason: 'context-lost',
|
|
||||||
detail: '',
|
|
||||||
retry: () => {},
|
|
||||||
reset: () => {},
|
|
||||||
});
|
|
||||||
await nextTick();
|
|
||||||
assert.match(
|
|
||||||
document.getElementById('render-failure-message').textContent,
|
|
||||||
/3D graphics/,
|
|
||||||
);
|
|
||||||
assert.equal(notice().querySelector('.render-failure-detail'), null);
|
|
||||||
assert.deepEqual(buttons(), ['Reload', 'Reset view']);
|
|
||||||
store.publishRenderFailure({
|
|
||||||
reason: 'context-lost',
|
|
||||||
detail: '',
|
|
||||||
retry: null,
|
|
||||||
reset: null,
|
|
||||||
});
|
|
||||||
await nextTick();
|
|
||||||
assert.deepEqual(buttons(), []);
|
|
||||||
assert.equal(notice().querySelector('.render-failure-hint'), null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const failure = () => ({
|
|
||||||
reason: 'render-error',
|
|
||||||
detail: '',
|
|
||||||
retry: () => {},
|
|
||||||
reset: null,
|
|
||||||
});
|
|
||||||
const settle = async () => {
|
|
||||||
await nextTick();
|
|
||||||
await nextTick();
|
|
||||||
};
|
|
||||||
|
|
||||||
test('focus goes back where it was when the globe draws again', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
const search = document.body.appendChild(document.createElement('input'));
|
|
||||||
search.focus();
|
|
||||||
store.publishRenderFailure(failure());
|
|
||||||
await settle();
|
|
||||||
assert.equal(document.activeElement?.textContent.trim(), 'Retry');
|
|
||||||
store.publishRenderFailure(null);
|
|
||||||
await settle();
|
|
||||||
assert.equal(document.activeElement, search);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The globe's canvas, as CesiumWidget builds it inside the container.
|
|
||||||
const addGlobeCanvas = () => {
|
|
||||||
const widget = document.createElement('div');
|
|
||||||
widget.className = 'cesium-widget';
|
|
||||||
document.getElementById('cesiumContainer').appendChild(widget);
|
|
||||||
return widget.appendChild(document.createElement('canvas'));
|
|
||||||
};
|
|
||||||
|
|
||||||
test('focus goes to the globe canvas when the element it came from is gone', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
const canvas = addGlobeCanvas();
|
|
||||||
const search = document.body.appendChild(document.createElement('input'));
|
|
||||||
search.focus();
|
|
||||||
store.publishRenderFailure(failure());
|
|
||||||
await settle();
|
|
||||||
search.remove();
|
|
||||||
store.publishRenderFailure(null);
|
|
||||||
await settle();
|
|
||||||
// The map's keys, Enter to finish a drawing among them, read the canvas
|
|
||||||
// as the map (src/annotations/drawTool.js).
|
|
||||||
assert.equal(document.activeElement, canvas);
|
|
||||||
assert.equal(canvas.getAttribute('tabindex'), '-1');
|
|
||||||
canvas.blur();
|
|
||||||
assert.equal(canvas.hasAttribute('tabindex'), false, 'only this once');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('focus goes to the globe canvas when the element it came from cannot take it', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
const canvas = addGlobeCanvas();
|
|
||||||
const button = document.body.appendChild(document.createElement('button'));
|
|
||||||
button.focus();
|
|
||||||
store.publishRenderFailure(failure());
|
|
||||||
await settle();
|
|
||||||
button.disabled = true;
|
|
||||||
store.publishRenderFailure(null);
|
|
||||||
await settle();
|
|
||||||
assert.equal(document.activeElement, canvas);
|
|
||||||
// A press on the globe leaves focus on the page again, as Cesium has it.
|
|
||||||
canvas.dispatchEvent(new Event('pointerdown'));
|
|
||||||
assert.equal(document.activeElement, document.body);
|
|
||||||
assert.equal(canvas.hasAttribute('tabindex'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('focus the user moved while the notice was up stays put', async (t) => {
|
|
||||||
const store = mount(t);
|
|
||||||
store.publishRenderFailure(failure());
|
|
||||||
await settle();
|
|
||||||
const chat = document.body.appendChild(document.createElement('textarea'));
|
|
||||||
chat.focus();
|
|
||||||
store.publishRenderFailure(null);
|
|
||||||
await settle();
|
|
||||||
assert.equal(document.activeElement, chat);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('the globe drawing from the start leaves focus alone', async (t) => {
|
|
||||||
mount(t);
|
|
||||||
await settle();
|
|
||||||
assert.equal(document.activeElement, document.body);
|
|
||||||
assert.equal(
|
|
||||||
document.getElementById('cesiumContainer').hasAttribute('tabindex'),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// A widget on Cesium's real CesiumWidget.prototype (startRenderLoop, the
|
|
||||||
// useDefaultRenderLoop setter, render) around a scene double, with animation
|
|
||||||
// frames queued by hand and the render failure notice installed.
|
|
||||||
import * as Cesium from 'cesium';
|
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
|
||||||
|
|
||||||
export function renderLoopFixture(
|
|
||||||
scene,
|
|
||||||
{ targetFrameRate, viewer: viewerExtras = {}, notice = {} } = {},
|
|
||||||
) {
|
|
||||||
const frames = [];
|
|
||||||
const previousRaf = globalThis.requestAnimationFrame;
|
|
||||||
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
|
||||||
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
|
||||||
Object.assign(widget, {
|
|
||||||
_scene: scene,
|
|
||||||
_clock: { tick() {} },
|
|
||||||
_canRender: true,
|
|
||||||
_useDefaultRenderLoop: false,
|
|
||||||
_renderLoopRunning: false,
|
|
||||||
// What src/app/scene.js passes to the viewer.
|
|
||||||
_showRenderLoopErrors: false,
|
|
||||||
_targetFrameRate: targetFrameRate,
|
|
||||||
isDestroyed: () => false,
|
|
||||||
resize() {},
|
|
||||||
});
|
|
||||||
// CesiumWidget's own renderError handler, which marks the loop stopped.
|
|
||||||
scene.renderError.addEventListener(() => {
|
|
||||||
widget._useDefaultRenderLoop = false;
|
|
||||||
widget._renderLoopRunning = false;
|
|
||||||
});
|
|
||||||
const viewer = Object.assign(
|
|
||||||
{
|
|
||||||
scene,
|
|
||||||
cesiumWidget: widget,
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
get useDefaultRenderLoop() {
|
|
||||||
return widget.useDefaultRenderLoop;
|
|
||||||
},
|
|
||||||
set useDefaultRenderLoop(value) {
|
|
||||||
widget.useDefaultRenderLoop = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
viewerExtras,
|
|
||||||
);
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const logged = [];
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError: (error) => logged.push(error),
|
|
||||||
...notice,
|
|
||||||
});
|
|
||||||
let time = 0;
|
|
||||||
const runFrames = (n) => {
|
|
||||||
for (let i = 0; i < n; i += 1) {
|
|
||||||
// Above a 60 fps target's interval, so every frame draws.
|
|
||||||
time += 17;
|
|
||||||
for (const fn of frames.splice(0)) fn(time);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const restore = () => {
|
|
||||||
uninstall();
|
|
||||||
globalThis.requestAnimationFrame = previousRaf;
|
|
||||||
};
|
|
||||||
return { frames, widget, viewer, store, logged, runFrames, restore };
|
|
||||||
}
|
|
||||||
+1
-17
@@ -7,9 +7,7 @@ import { markRaw, shallowRef } from 'vue';
|
|||||||
* road network chip, CctvControls the camera grid chip; the toast shows
|
* road network chip, CctvControls the camera grid chip; the toast shows
|
||||||
* what any owner's createToast() (src/ui/toast.js) publishes, and the title
|
* what any owner's createToast() (src/ui/toast.js) publishes, and the title
|
||||||
* bar the FPS readout createFrameRateMonitor() publishes. Each label keeps its
|
* bar the FPS readout createFrameRateMonitor() publishes. Each label keeps its
|
||||||
* last text while hidden, so the next change flaps from what was shown. The
|
* last text while hidden, so the next change flaps from what was shown.
|
||||||
* render failure notice is what installRenderFailureNotice()
|
|
||||||
* (src/ui/renderFailure.js) publishes while the globe cannot draw.
|
|
||||||
*/
|
*/
|
||||||
export const useStatusStore = defineStore('gev-status', () => {
|
export const useStatusStore = defineStore('gev-status', () => {
|
||||||
/**
|
/**
|
||||||
@@ -34,13 +32,6 @@ export const useStatusStore = defineStore('gev-status', () => {
|
|||||||
*/
|
*/
|
||||||
const fps = shallowRef(null);
|
const fps = shallowRef(null);
|
||||||
|
|
||||||
/**
|
|
||||||
* The globe's render failure, `{reason, detail, retry, reset}`; null while
|
|
||||||
* the globe draws. `reason` is 'render-error' or 'context-lost', and `retry`
|
|
||||||
* and `reset` are null where that action is not offered.
|
|
||||||
*/
|
|
||||||
const renderFailure = shallowRef(null);
|
|
||||||
|
|
||||||
/** @param {?object} next A fresh presentation of the loading status. */
|
/** @param {?object} next A fresh presentation of the loading status. */
|
||||||
function publishLoading(next) {
|
function publishLoading(next) {
|
||||||
loading.value = next && markRaw(next);
|
loading.value = next && markRaw(next);
|
||||||
@@ -66,11 +57,6 @@ export const useStatusStore = defineStore('gev-status', () => {
|
|||||||
fps.value = next && markRaw(next);
|
fps.value = next && markRaw(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {?object} next A fresh presentation of the render failure. */
|
|
||||||
function publishRenderFailure(next) {
|
|
||||||
renderFailure.value = next && markRaw(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fps,
|
fps,
|
||||||
publishFps,
|
publishFps,
|
||||||
@@ -82,7 +68,5 @@ export const useStatusStore = defineStore('gev-status', () => {
|
|||||||
publishTraffic,
|
publishTraffic,
|
||||||
cctv,
|
cctv,
|
||||||
publishCctv,
|
publishCctv,
|
||||||
renderFailure,
|
|
||||||
publishRenderFailure,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -405,76 +405,6 @@ html:not([lang='en']) #global-loading-detail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Render failure notice ───────────────────── */
|
|
||||||
/* Above the loading screen, so a globe that fails while it loads says so too,
|
|
||||||
and above floating panels such as the Québec camera viewer (2000). */
|
|
||||||
#render-failure {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 2001;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: min(420px, calc(100vw - 32px));
|
|
||||||
max-height: calc(100vh - 32px);
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px 22px;
|
|
||||||
background: var(--glass-bg);
|
|
||||||
border: 1px solid #ff7272;
|
|
||||||
border-radius: 12px;
|
|
||||||
backdrop-filter: blur(16px);
|
|
||||||
-webkit-backdrop-filter: blur(16px);
|
|
||||||
box-shadow: 0 0 24px rgba(255, 88, 88, 0.18);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure-title {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #ff7272;
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure p {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure .render-failure-detail {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure .render-failure-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure .render-failure-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-failure .gev-action-button {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 8px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
|
||||||
#render-failure .gev-action-button {
|
|
||||||
min-height: 44px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Toast Notification ──────────────────────── */
|
/* ── Toast Notification ──────────────────────── */
|
||||||
#toast {
|
#toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import ScreenAnnotations from './panels/ScreenAnnotations.vue';
|
|||||||
import AwarenessDirection from './panels/AwarenessDirection.vue';
|
import AwarenessDirection from './panels/AwarenessDirection.vue';
|
||||||
import UiButton from '../kit/UiButton.vue';
|
import UiButton from '../kit/UiButton.vue';
|
||||||
import GlobeActions from './panels/GlobeActions.vue';
|
import GlobeActions from './panels/GlobeActions.vue';
|
||||||
import RenderFailure from './panels/RenderFailure.vue';
|
|
||||||
import WorldTimeline from './panels/WorldTimeline.vue';
|
import WorldTimeline from './panels/WorldTimeline.vue';
|
||||||
|
|
||||||
// The ACTIVE STYLE corner shows the style VisualSettings publishes.
|
// The ACTIVE STYLE corner shows the style VisualSettings publishes.
|
||||||
@@ -62,7 +61,6 @@ const vToastText = {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="cesiumContainer"></div>
|
<div id="cesiumContainer"></div>
|
||||||
<RenderFailure />
|
|
||||||
<div id="world-overlay-root" aria-hidden="true">
|
<div id="world-overlay-root" aria-hidden="true">
|
||||||
<canvas id="world-overlay-canvas"></canvas>
|
<canvas id="world-overlay-canvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,9 +118,11 @@ const ringFractions = computed(() =>
|
|||||||
? radar.value.ringsNm.map((ring) => ring / radar.value.rangeNm)
|
? radar.value.ringsNm.map((ring) => ring / radar.value.rangeNm)
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
// The spacing is a fraction at the shorter ranges (0.75, 1.5 NM), which
|
||||||
|
// Intl.PluralRules gives a form of its own: ru «0,75 МОР. МИЛИ».
|
||||||
const ringLabel = computed(() =>
|
const ringLabel = computed(() =>
|
||||||
radar.value?.ringsNm.length
|
radar.value?.ringsNm.length
|
||||||
? t('RINGS {spacing} NM', {
|
? tn(radar.value.ringsNm[0], 'RINGS {spacing} NM', 'RINGS {spacing} NM', {
|
||||||
spacing: formatNumber(radar.value.ringsNm[0]),
|
spacing: formatNumber(radar.value.ringsNm[0]),
|
||||||
})
|
})
|
||||||
: '',
|
: '',
|
||||||
|
|||||||
@@ -163,6 +163,20 @@ test('filters narrow the list as the viewer types and picks', async (t_) => {
|
|||||||
assert.deepEqual(shownIds(), ['nasa-eonet-events']);
|
assert.deepEqual(shownIds(), ['nasa-eonet-events']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the shown count agrees with its number (he)', async (t_) => {
|
||||||
|
const { owner } = mount(t_);
|
||||||
|
setLocale('he');
|
||||||
|
owner.open();
|
||||||
|
await settle();
|
||||||
|
const status = () => $('.data-library-status').textContent.trim();
|
||||||
|
assert.match(status(), /^מוצגים\s+\S*3\S*\s+מתוך/);
|
||||||
|
const search = $('#data-library input[type="search"]');
|
||||||
|
search.value = 'eonet';
|
||||||
|
search.dispatchEvent(new Event('input'));
|
||||||
|
await settle();
|
||||||
|
assert.match(status(), /^מוצג\s+\S*1\S*\s+מתוך/);
|
||||||
|
});
|
||||||
|
|
||||||
test('Add becomes Remove on the same focused button; Zoom closes first', async (t_) => {
|
test('Add becomes Remove on the same focused button; Zoom closes first', async (t_) => {
|
||||||
const { owner, library } = mount(t_);
|
const { owner, library } = mount(t_);
|
||||||
owner.open();
|
owner.open();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
||||||
import { t, tn } from '../../../i18n/index.js';
|
import { formatNumber, t, tn } from '../../../i18n/index.js';
|
||||||
import UiButton from '../../kit/UiButton.vue';
|
import UiButton from '../../kit/UiButton.vue';
|
||||||
import UiSelect from '../../kit/UiSelect.js';
|
import UiSelect from '../../kit/UiSelect.js';
|
||||||
import {
|
import {
|
||||||
@@ -53,12 +53,18 @@ watch(
|
|||||||
|
|
||||||
const tiles = computed(() => libraryStatTiles(props.view.summary));
|
const tiles = computed(() => libraryStatTiles(props.view.summary));
|
||||||
const themes = computed(() => libraryThemeOptions());
|
const themes = computed(() => libraryThemeOptions());
|
||||||
|
// The words for the shown entries agree with their count: he «מוצג 1».
|
||||||
const status = computed(() =>
|
const status = computed(() =>
|
||||||
props.view.ready
|
props.view.ready
|
||||||
? t('Showing {shown} of {total}', {
|
? tn(
|
||||||
shown: props.view.shown.length,
|
props.view.shown.length,
|
||||||
total: props.view.total,
|
'Showing {shown} of {total}',
|
||||||
})
|
'Showing {shown} of {total}',
|
||||||
|
{
|
||||||
|
shown: formatNumber(props.view.shown.length),
|
||||||
|
total: formatNumber(props.view.total),
|
||||||
|
},
|
||||||
|
)
|
||||||
: t('Checking what this instance serves…'),
|
: t('Checking what this instance serves…'),
|
||||||
);
|
);
|
||||||
const resetText = computed(() => {
|
const resetText = computed(() => {
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { nextTick, ref, watch } from 'vue';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { msg, t } from '../../../i18n/index.js';
|
|
||||||
import { useStatusStore } from '../../stores/status.js';
|
|
||||||
import UiButton from '../../kit/UiButton.vue';
|
|
||||||
|
|
||||||
// Published by installRenderFailureNotice() (src/ui/renderFailure.js).
|
|
||||||
const { renderFailure: failure } = storeToRefs(useStatusStore());
|
|
||||||
const panel = ref(null);
|
|
||||||
const MESSAGES = {
|
|
||||||
'context-lost': msg(
|
|
||||||
"The browser took back the globe's 3D graphics. Layers and controls still work, but the map cannot draw until the page reloads.",
|
|
||||||
),
|
|
||||||
'render-error': msg(
|
|
||||||
'Rendering stopped after an error. Layers and controls still work, but the map is not updating.',
|
|
||||||
),
|
|
||||||
};
|
|
||||||
// Focus goes to the first action when the notice appears. When the globe
|
|
||||||
// draws again, it goes back, or to the map, unless the user moved it.
|
|
||||||
let focusBefore;
|
|
||||||
watch(
|
|
||||||
() => Boolean(failure.value),
|
|
||||||
async (shown) => {
|
|
||||||
if (shown) {
|
|
||||||
focusBefore = document.activeElement;
|
|
||||||
await nextTick();
|
|
||||||
panel.value?.querySelector('button')?.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (focusBefore === undefined) return;
|
|
||||||
const previous = focusBefore;
|
|
||||||
focusBefore = undefined;
|
|
||||||
await nextTick();
|
|
||||||
const active = document.activeElement;
|
|
||||||
if (active && active !== document.body) return;
|
|
||||||
if (previous?.isConnected && previous !== document.body) {
|
|
||||||
previous.focus({ preventScroll: true });
|
|
||||||
// A control disabled, hidden or made inert meanwhile does not take it.
|
|
||||||
if (document.activeElement === previous) return;
|
|
||||||
}
|
|
||||||
focusMap();
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// The map's keys (Enter to finish a drawing, Space to talk) read the canvas
|
|
||||||
// as the map. It stays focusable only until focus leaves or the globe is
|
|
||||||
// pressed; a press leaves focus on the page, as in Cesium.
|
|
||||||
function focusMap() {
|
|
||||||
const canvas = document.querySelector(
|
|
||||||
'#cesiumContainer .cesium-widget canvas',
|
|
||||||
);
|
|
||||||
if (!canvas) return;
|
|
||||||
if (!canvas.hasAttribute('tabindex')) {
|
|
||||||
canvas.tabIndex = -1;
|
|
||||||
const done = new AbortController();
|
|
||||||
const release = () => {
|
|
||||||
done.abort();
|
|
||||||
canvas.removeAttribute('tabindex');
|
|
||||||
};
|
|
||||||
canvas.addEventListener('blur', release, { signal: done.signal });
|
|
||||||
canvas.addEventListener(
|
|
||||||
'pointerdown',
|
|
||||||
() => {
|
|
||||||
release();
|
|
||||||
canvas.blur();
|
|
||||||
},
|
|
||||||
{ capture: true, signal: done.signal },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
canvas.focus({ preventScroll: true });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="failure" id="render-failure" ref="panel" role="alertdialog" aria-labelledby="render-failure-title" aria-describedby="render-failure-message">
|
|
||||||
<h2 id="render-failure-title">{{ t('The globe stopped drawing') }}</h2>
|
|
||||||
<p id="render-failure-message">{{ t(MESSAGES[failure.reason] ?? MESSAGES['render-error']) }}</p>
|
|
||||||
<p v-if="failure.detail" class="render-failure-detail" translate="no">{{ failure.detail }}</p>
|
|
||||||
<p v-if="failure.reset" class="render-failure-hint">{{ t('Reset view reloads the app without the view restored from the address bar.') }}</p>
|
|
||||||
<div v-if="failure.retry || failure.reset" class="render-failure-actions">
|
|
||||||
<UiButton v-if="failure.retry" class="gev-action-button render-failure-retry" :onClick="failure.retry">{{ failure.reason === 'context-lost' ? t('Reload') : t('Retry') }}</UiButton>
|
|
||||||
<UiButton v-if="failure.reset" class="gev-action-button render-failure-reset" :onClick="failure.reset">{{ t('Reset view') }}</UiButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -3917,6 +3917,8 @@ async function requestStreetLevel(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Then the place: a name that geocodes to nothing changes nothing else.
|
// Then the place: a name that geocodes to nothing changes nothing else.
|
||||||
|
// "the view centre" is English for the model; the card (speech.js) gives
|
||||||
|
// it messages of its own, since it is a phrase and not a place name.
|
||||||
label =
|
label =
|
||||||
query ||
|
query ||
|
||||||
(entity ? String(entity.label || entity.id) : '') ||
|
(entity ? String(entity.label || entity.id) : '') ||
|
||||||
|
|||||||
+32
-11
@@ -332,6 +332,16 @@ const ERROR_PATTERNS = [
|
|||||||
place: localizePlaceName(place),
|
place: localizePlaceName(place),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
// show_street_level names no place when it looks at the view centre. The
|
||||||
|
// phrase is not a name: it has a message of its own, so each language
|
||||||
|
// words it in the case its preposition asks for.
|
||||||
|
[
|
||||||
|
/^No (.+) imagery near the view centre$/,
|
||||||
|
([, source]) =>
|
||||||
|
t('No {source} imagery near the view centre', {
|
||||||
|
source: translateKnown(source),
|
||||||
|
}),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
/^No (.+) imagery near (.+)$/,
|
/^No (.+) imagery near (.+)$/,
|
||||||
([, source, place]) =>
|
([, source, place]) =>
|
||||||
@@ -1112,14 +1122,21 @@ export function planStepLabel(name, args = {}) {
|
|||||||
}
|
}
|
||||||
case 'show_street_level': {
|
case 'show_street_level': {
|
||||||
const source = STREET_LEVEL_SOURCE_NAMES[args.provider];
|
const source = STREET_LEVEL_SOURCE_NAMES[args.provider];
|
||||||
const place = args.query
|
// "This spot" and "the view centre" are phrases, not place names:
|
||||||
? shownPlace(args.query, 32)
|
// each is a message of its own, worded in the grammar of the language.
|
||||||
: args.latitude != null
|
if (args.query) {
|
||||||
? t('this spot')
|
const place = shownPlace(args.query, 32);
|
||||||
: t('the view centre');
|
return source
|
||||||
|
? t('{source} at {place}', { source, place })
|
||||||
|
: t('Street level at {place}', { place });
|
||||||
|
}
|
||||||
|
if (args.latitude != null)
|
||||||
|
return source
|
||||||
|
? t('{source} at this spot', { source })
|
||||||
|
: t('Street level at this spot');
|
||||||
return source
|
return source
|
||||||
? t('{source} at {place}', { source, place })
|
? t('{source} at the view centre', { source })
|
||||||
: t('Street level at {place}', { place });
|
: t('Street level at the view centre');
|
||||||
}
|
}
|
||||||
case 'set_layer_visibility': {
|
case 'set_layer_visibility': {
|
||||||
const on = args.enabled !== false;
|
const on = args.enabled !== false;
|
||||||
@@ -1195,6 +1212,9 @@ export function narrationLabel(name, args = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The place show_street_level reports when it names none (gevActions). */
|
||||||
|
const VIEW_CENTRE_LABEL = 'the view centre';
|
||||||
|
|
||||||
/** On-screen labels for progress steps. */
|
/** On-screen labels for progress steps. */
|
||||||
const STEP_LABELS = Object.freeze({
|
const STEP_LABELS = Object.freeze({
|
||||||
resolve: msg('Finding places'),
|
resolve: msg('Finding places'),
|
||||||
@@ -1219,10 +1239,11 @@ export function progressStepLabel(step, label) {
|
|||||||
: step
|
: step
|
||||||
? spokenLabel(step, 24)
|
? spokenLabel(step, 24)
|
||||||
: t('Working');
|
: t('Working');
|
||||||
// The join is a catalog string so each language spaces its colon.
|
// The join is a catalog string so each language spaces its colon. The
|
||||||
return label
|
// view centre is a phrase show_street_level reports in English, not a name.
|
||||||
? t('{name}: {state}', { name: base, state: spokenLabel(label, 32) })
|
const state =
|
||||||
: base;
|
label === VIEW_CENTRE_LABEL ? t('the view centre') : spokenLabel(label, 32);
|
||||||
|
return label ? t('{name}: {state}', { name: base, state }) : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from './speech.js';
|
} from './speech.js';
|
||||||
import { presentResult } from './resultDisplay.js';
|
import { presentResult } from './resultDisplay.js';
|
||||||
import { countWords, findHedges } from './speechLint.js';
|
import { countWords, findHedges } from './speechLint.js';
|
||||||
import { setLocale } from '../i18n/index.js';
|
import { setLocale, t } from '../i18n/index.js';
|
||||||
|
|
||||||
function envelope(name, result, args) {
|
function envelope(name, result, args) {
|
||||||
return attachVoiceResult(name, result, args);
|
return attachVoiceResult(name, result, args);
|
||||||
@@ -779,3 +779,51 @@ test('progress step labels join through the catalog colon', () => {
|
|||||||
setLocale('en');
|
setLocale('en');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the street-level card words "the view centre" and "this spot" as phrases, not names', () => {
|
||||||
|
setLocale('de');
|
||||||
|
try {
|
||||||
|
assert.equal(
|
||||||
|
planStepLabel('show_street_level', { provider: 'mapillary' }),
|
||||||
|
'Mapillary in der Bildmitte',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
planStepLabel('show_street_level', { latitude: 1, longitude: 2 }),
|
||||||
|
'Straßenebene an dieser Stelle',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
planStepLabel('show_street_level', { query: 'Alexanderplatz', provider: 'kartaview' }),
|
||||||
|
'KartaView: Alexanderplatz',
|
||||||
|
);
|
||||||
|
const empty = envelope('show_street_level', {
|
||||||
|
ok: false,
|
||||||
|
stage: 'imagery',
|
||||||
|
noImagery: true,
|
||||||
|
atViewCentre: true,
|
||||||
|
label: 'the view centre',
|
||||||
|
providerName: 'Mapillary',
|
||||||
|
error: 'No Mapillary imagery near the view centre',
|
||||||
|
});
|
||||||
|
assert.deepEqual(empty.display.lines, [
|
||||||
|
'Keine Bilder von Mapillary in der Nähe der Bildmitte',
|
||||||
|
]);
|
||||||
|
assert.equal(empty.say, 'No Mapillary imagery near the view centre.');
|
||||||
|
const named = envelope('show_street_level', {
|
||||||
|
ok: false,
|
||||||
|
stage: 'imagery',
|
||||||
|
noImagery: true,
|
||||||
|
label: 'Alexanderplatz',
|
||||||
|
providerName: 'Mapillary',
|
||||||
|
error: 'No Mapillary imagery near Alexanderplatz',
|
||||||
|
});
|
||||||
|
assert.deepEqual(named.display.lines, [
|
||||||
|
'Keine Bilder von Mapillary in der Nähe von Alexanderplatz',
|
||||||
|
]);
|
||||||
|
assert.equal(
|
||||||
|
progressStepLabel('imagery', 'the view centre'),
|
||||||
|
`${t('Opening imagery')}: die Bildmitte`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLocale('en');
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user