Compare commits

..
Author SHA1 Message Date
German Khokhlov 3953421aad Changelog fragment for #1700 2026-11-13 18:51:09 +04:00
German Khokhlov 209b135ff1 i18n: plural forms and contexts the catalogs could only work around
The last translation review left four kinds of problem it could only
reword around in the catalogs. They are fixed in the code now, and the
catalogs go back to natural wording.

- Counts shown with plain t() are tn() plurals: the wildlife count, the
  Data Library "Showing {shown} of {total}", the News time window (which
  offers 1 h) and the GhostMaps event age (which offers 1 day). The
  English keys stay; the catalogs get real forms, as in "Última 1 h" and
  "Últimas 24 h", or "1 Tag" and "7 Tage".
- The bridge radar range and ring spacing are tn() plurals too. The
  spacing is fractional at the short ranges, and Intl.PluralRules gives
  0.75 and 1.5 their own form: ru "0,75 МОР. МИЛИ", "12 МОР. МИЛЬ".
- Street level no longer passes "the view centre" or "this spot" as
  {place}: each case has a message of its own, so a preposition no
  longer meets a phrase in the wrong case. The progress line had the
  same leak and showed the English phrase.
- Latest, FOCUS, CONFIRMED and "{label} · COURSE {heading}°" are split
  with a context where one key served two places: the News heading, the
  orbital site status, the Wildlife chip and a ship's course.

"CURRENTLY OVER" ended in a preposition that a country name in the
nominative cannot follow in uk, de, it and pt. It reads "under the
satellite" there now, as ru already did.

I18N.md gains the two rules behind these fixes.
2026-11-13 18:50:34 +04:00
59 changed files with 1160 additions and 1688 deletions

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).
+7
View File
@@ -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).
-45
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
-7
View File
@@ -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",
-2
View File
@@ -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
View File
@@ -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);
-2
View File
@@ -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;
-120
View File
@@ -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();
}
});
-8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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": "重置视图"
} }
+40 -19
View File
@@ -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": "重置視圖"
} }
+1
View File
@@ -685,6 +685,7 @@
"FLIR", "FLIR",
"FM MHZ", "FM MHZ",
"FOCUS", "FOCUS",
"site status|FOCUS",
"FOV", "FOV",
"FOV {value}°", "FOV {value}°",
"FT", "FT",
+7 -4
View File
@@ -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.',
), ),
+30
View File
@@ -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
View File
@@ -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();
}); });
+40
View File
@@ -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 () => {
+5 -2
View File
@@ -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),
}),
}); });
} }
+8
View File
@@ -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) => {
+2 -1
View File
@@ -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.
+14
View File
@@ -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°');
});
+3 -1
View File
@@ -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
View File
@@ -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">
+50
View File
@@ -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 }));
-205
View File
@@ -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];
};
}
-174
View File
@@ -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);
});
-207
View File
@@ -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();
}
});
}
-46
View File
@@ -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();
});
-95
View File
@@ -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();
}
});
-173
View File
@@ -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,
);
});
-72
View File
@@ -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
View File
@@ -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,
}; };
}); });
-70
View File
@@ -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;
-2
View File
@@ -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>
+3 -1
View File
@@ -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();
+11 -5
View File
@@ -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(() => {
-87
View File
@@ -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>
+2
View File
@@ -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
View File
@@ -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;
} }
/** /**
+49 -1
View File
@@ -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');
}
});