mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 13:22:18 +00:00
Merge main and switch Cesium and Navara in place through Vue
Preserve the camera, map source and layer selection across engine sessions. Restore the previous engine if startup fails and retain local history navigation.
This commit is contained in:
commit
b8f4d097c4
1691 files changed
+225876
-41594
No files matched your search
+22
-4
@@ -158,7 +158,7 @@ routing and regional caches. Request parameters cannot override these endpoints.
|
||||
Voice controls accept an action runner and a controller factory through
|
||||
`createVoiceCommands` (`./voice/commands`). The default Realtime controller owns
|
||||
microphone tracks, playback, push-to-talk, tool cancellation and radio handoff.
|
||||
Its backend supplies `requestToken({ tier, signal })` and
|
||||
Its backend supplies `requestToken({ tier, authMode, signal })` and
|
||||
`negotiate({ offerSdp, credential, signal })`. The Realtime-compatible adapter
|
||||
(`./voice/realtime-backend`) accepts separate token and connection transports
|
||||
and endpoints. Only the short-lived client secret reaches the SDP endpoint.
|
||||
@@ -200,6 +200,24 @@ its pending results. Source disposal does not silently reinstate a default.
|
||||
`ui/composition` supplies the default engines to the shell. Applications can
|
||||
supply a HUD implementation or request policy through control services. The
|
||||
shared chrome owns the welcome/loading transition; standalone composition adds
|
||||
Provider Settings. `build/html` expands an allowlist of component markers from
|
||||
`src/ui/templates`; unknown names cannot read arbitrary filesystem paths. The
|
||||
standalone document expands to the same markup as before this extraction.
|
||||
Provider Settings. A page places each Vue view in `src/ui/views` with a host,
|
||||
`<div class="gev-view" data-gev-view="NAME"></div>`, and writes its own loading
|
||||
screen (`#loading-screen` with a `.loader-status` line) as static markup so it
|
||||
paints before any script runs. `createApplication()` renders the views before
|
||||
anything reads the page; `omitViews` names views the application leaves out,
|
||||
such as `provider-settings`, and their hosts are removed. Leaving out
|
||||
`layer-panels` also leaves out the scene sharing dialogs it draws, and leaving
|
||||
out `scene-chrome` the before/after imagery dividers (the imagery still splits,
|
||||
with no line or handle to move it), the status chips, the toast, the ORBIT
|
||||
indicator, the FPS readout, the Scene actions panel, the data-pack attribution
|
||||
cards, the wind inspection marker, the Space Missions replay vehicle, the
|
||||
full-globe celestial ring, the screen-space map annotations, the visible
|
||||
map target list and the Global Context compass;
|
||||
leaving out `command-dock` the voice control and its pointer reticle;
|
||||
leaving out `context` the Bhote Koshi reconstruction panel; and leaving out
|
||||
`intel-hud` the Intel HUD overlay (`#intel-hud`), whose readouts the HUD
|
||||
implementation publishes to the `gev-hud` store. An unknown
|
||||
view name is an error. A host that does not initialize Core's locale passes
|
||||
`localePicker: false` to `createApplicationControls` instead of editing the
|
||||
markup. Node tests that import application modules register the component
|
||||
loader with `node --import=<core package>/build/register-vue`.
|
||||
+58
-25
@@ -76,11 +76,10 @@ Navara stays out of the Cesium-mode bundle. Common voice controls cannot depend
|
||||
Realtime protocol implementation. Negative fixtures cover indirect helpers,
|
||||
self-package imports, symlinks and unreachable files.
|
||||
|
||||
Source factories have dedicated exports for ALPR, bikeshare, CCTV, earthquakes,
|
||||
FIRMS, installations, launches, radio, satellites and traffic. They preserve the
|
||||
same factory implementations without loading layer rendering. ALPR/earthquake
|
||||
record normalization and CCTV source endpoint policy have plain owners separate
|
||||
from geometry/cards. Source exports do not start acquisition at import time.
|
||||
Source-only factories use dedicated `layers/<family>/source` exports where
|
||||
available, preserving the same factory implementation without loading rendering.
|
||||
Record normalization and endpoint policies have plain owners separate from
|
||||
geometry/cards. Source exports do not start acquisition at import time.
|
||||
|
||||
Other layer `ingestion.js` files may still coordinate Cesium resources; the
|
||||
portable contract applies to the explicitly reviewed graphs above. Their layer
|
||||
@@ -91,17 +90,13 @@ moves it. Do not label all ingestion modules platform-independent by filename.
|
||||
|
||||
| Retained entry | Owner and current reason |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `src/ui.js` | UI facade; direct callers retain standalone StyleManager defaults. Normal assembly uses `src/ui/composition.js` |
|
||||
| `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately |
|
||||
| `src/data/<layer>.js` | Legacy default instances; new assembly calls layer factories with supplied sources |
|
||||
| `src/data/localGeojson.js` | Infrastructure compatibility factories; catalog imports services from `src/app/localGeojsonServices.js` directly |
|
||||
| `src/app/sourceSlot.js` | Re-export for direct callers/tests; portable slot ownership is `src/sources/sourceSlot.js` |
|
||||
| `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances |
|
||||
| `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports |
|
||||
|
||||
The two exact standalone-import exceptions are `src/ui.js` to
|
||||
`src/standalone/catalog.js` and `server/providers/local.js` to
|
||||
`server/standalone/key-setup.js`. They do not authorize new compatibility
|
||||
The one exact standalone-import exception is `server/providers/local.js` to
|
||||
`server/standalone/key-setup.js`. It does not authorize new compatibility
|
||||
back-edges. Voice actions use plain `src/data/feedState.js`, not the manager
|
||||
facade. Settings filesystem hardening belongs in
|
||||
`server/standalone/key-setup-hardening.mjs`; its settings policy is unchanged.
|
||||
@@ -249,7 +244,7 @@ DOM elements and callbacks; importing it creates no browser state. `destroy()`
|
||||
removes owned listeners and cancels pending work without changing saved state
|
||||
or moving focus. Call it before removing or replacing the controls.
|
||||
|
||||
`src/ui.js` retains panel layout, persistence, share restoration and application
|
||||
`src/ui/applicationShell.js` retains panel layout, persistence, share restoration and application
|
||||
reactions to state changes. Map Source selection and Location draft cleanup are
|
||||
provided through callbacks. The component imports no globe, data, application
|
||||
or server modules. Package checks and scoped formatting cover this entry.
|
||||
@@ -292,7 +287,7 @@ compatible through re-exports.
|
||||
|
||||
### Adding a right-rail readout panel
|
||||
|
||||
Add the shell in `src/ui/templates/context.html` inside `#right-context-rail`:
|
||||
Add the shell in `src/ui/views/ContextView.vue` inside `#right-context-rail`:
|
||||
a `.panel-collapsible` with a stable `id` and matching `data-panel-id`, the
|
||||
existing `.panel-header`, collapse button and a dedicated body. Register it
|
||||
with the existing panel chrome/layout owners; do not allocate rail space in
|
||||
@@ -313,13 +308,13 @@ compactStatus, blocks }` in place. Their full-width native button headers expose
|
||||
adapter owns accordion state. Generic cards do not select data or enable layers.
|
||||
|
||||
`blocks` is an ordered array of loose, optional `{ id, type, ... }` descriptors,
|
||||
rendered by `railCardBlocks.js`. There are no per-section disclosures:
|
||||
rendered by `rail/RailCardBlocks.vue`. There are no per-section disclosures:
|
||||
|
||||
- `summary`: `text`, a single summary line.
|
||||
- `list`: `list: { ariaLabel, items }`, reconciled by `syncRowList` in `rowList.js`.
|
||||
- `list`: `list: { ariaLabel, items }`, keyed by `rail/RailRowList.vue`.
|
||||
- `lines`: keyed `lines: [{ id, text, muted? }]` for details and status.
|
||||
- `settings`: `settings: [{ id, label, chips }]`; each labelled row has a 56 px
|
||||
label column and a wrapping `syncChipGroup` from `chipGroup.js`.
|
||||
label column and wrapping keyed chips from `rail/RailChips.vue`.
|
||||
- `legend`: `legend: { colors, labels, units?, zeroIndex?, categorical? }` for a
|
||||
ramp/scale (including a physical freezing anchor) or separate keyed swatches.
|
||||
- `actions`: keyed `actions: [{ id, label, params?, href?, onClick?, disabled?,
|
||||
@@ -368,8 +363,8 @@ drag or final change. Feature adapters own data mapping and service subscription
|
||||
generic modules own DOM/listeners/timers and release them in `destroy()`.
|
||||
|
||||
Keep persistence with panel chrome: collapse uses
|
||||
`godsEyeView.v6.panelCollapsed.<panelId>`, position uses
|
||||
`godsEyeView.v8.panelPos.<panelId>`, and share state uses the registered panel id.
|
||||
`godsEyeView.v6.panelCollapsed.<panelId>`, positions are
|
||||
not stored (`godsEyeView.v8.panelPos.<panelId>` stays reserved), and share state uses the registered panel id.
|
||||
Do not rename existing
|
||||
ids or reset persistence versions when adding a body. The WEATHER adapter is
|
||||
`src/ui/weatherPanel.js`; its observed-history selection remains transient.
|
||||
@@ -386,6 +381,13 @@ renderer, persistence or services. The facade retains panel visibility, share
|
||||
restore claims and render scheduling. Both controls are destroyed before the
|
||||
facade's asynchronous teardown can yield.
|
||||
|
||||
## Ordinary action buttons
|
||||
|
||||
The scene panels view and the CCTV panel draw their action buttons with the shared
|
||||
`gev-action-button` classes in `src/ui/styles/action-button.css`. Feature
|
||||
controllers own action dispatch, state, persistence and cleanup, and the views
|
||||
preserve IDs and layout hooks.
|
||||
|
||||
## Display controls
|
||||
|
||||
`ui/display` owns Display button, selector and slider subscriptions. It receives
|
||||
@@ -408,9 +410,9 @@ remain in their callers.
|
||||
`ui/maps` owns source-chip presentation, selection feedback and its subscription
|
||||
lifetime. It receives the existing controller and explicit state/action callbacks;
|
||||
it imports no renderer or application. Source construction and availability policy
|
||||
remain with the map controller. Rebuilding controls removes their previous chip
|
||||
listeners, and destruction suppresses late completions without owning or destroying
|
||||
the supplied controller.
|
||||
remain with the map controller. The dock renders the chips; rebuilding controls
|
||||
drops their previous subscription, and destruction suppresses late completions
|
||||
without owning or destroying the supplied controller.
|
||||
|
||||
## Layer panel
|
||||
|
||||
@@ -473,8 +475,8 @@ release listeners; pending action feedback is limited to its current owner.
|
||||
## UI assembly and styles
|
||||
|
||||
`ui/shell` assembles controls from supplied existing layer, navigation, terrain,
|
||||
rendering, HUD and share operations. `src/standalone/ui.js` provides the running
|
||||
application's instances; `src/ui.js` remains the compatibility entry. The shell
|
||||
rendering, HUD and share operations. `src/standalone/controls.js` provides the running
|
||||
application's instances. The shell
|
||||
imports no standalone bootstrap or concrete live layer implementation.
|
||||
Panel layout, position/drag, notices, recording and deferred UI work have separate
|
||||
owners with synchronous cleanup. Existing scene, share and HUD engines retain
|
||||
@@ -605,6 +607,10 @@ state subscriptions retain their existing event contract.
|
||||
tracking handoff. It reads Cockpit admission and uses supplied tracking operations.
|
||||
- `ui/share-restoration` owns the initial restore transaction, layer coordinator,
|
||||
status notices and gesture/timer cleanup.
|
||||
- `share/view-codec` is the share-link codec (`decodeShareView`,
|
||||
`encodeShareView`). It is pure (no DOM, no Cesium), so a host's server can
|
||||
read a `/share/<code>` link, for example to describe it in link previews.
|
||||
`share/link-path` reads and builds the `/share/<code>` address itself.
|
||||
- `ui/visual-settings` owns style preferences, detection overrides, display inputs
|
||||
and IR cleanup. Its engine services and panel operations are explicit.
|
||||
- `ui/panel-chrome` owns disclosure, docking, collapse preferences and temporary
|
||||
@@ -641,9 +647,9 @@ explicit operations. Both exports exclude Cesium, DOM and application assembly.
|
||||
The snapshot renderer applies record changes; rendering owns a weak map of
|
||||
geometry and billboard resources used by cards, picking and trails.
|
||||
|
||||
`gods-eye-view/sources/reference` constructs fresh earthquake and bundled cable
|
||||
`gods-eye-view/sources/reference` constructs fresh earthquake, GNSS integrity and bundled cable
|
||||
source instances independently of standalone setup. Individual sources remain
|
||||
available through `layers/earthquakes/source` and `layers/submarine-cables/source`.
|
||||
available through their `layers/<family>/source` exports, including `layers/gnss/source`.
|
||||
The latter retains the bundled dataset’s attribution and licensing requirements.
|
||||
|
||||
## Geocoding and feature-query providers
|
||||
@@ -741,3 +747,30 @@ sources supply `{records, droppedCount, status, saturated}`; viewport filtering,
|
||||
exact-bound retry and rendering stay in the layer. Legacy cache saturation is
|
||||
decoded in the source. Source and geometry modules remain independent of Cesium,
|
||||
DOM and platform middleware.
|
||||
|
||||
### Native form fields
|
||||
|
||||
Native numeric inputs are rendered by the views that own them; the CCTV pose
|
||||
chip renders its inline input in `src/ui/cctv/CctvCalValue.vue`. Static
|
||||
selects and number inputs use the same `gev-field` surface, with explicit
|
||||
compact and inline densities. Feature owners retain
|
||||
option identity/order, bounds, parsing, draft values, input/change timing and
|
||||
teardown. CCTV effective-pose conversion and tuner/device operations remain
|
||||
feature responsibilities.
|
||||
|
||||
The shared stylesheet owns field surfaces and disabled opacity; feature and theme
|
||||
palettes use `--gev-field-background`, `--gev-field-border-color`,
|
||||
`--gev-field-color` and `--gev-field-disabled-opacity`. Radio retains its own
|
||||
compact density and arrow space. CCTV owns calibration editor width in
|
||||
`cctv.css`; the shared inline density does not impose a feature-specific width.
|
||||
Cyber supplies a shared-field palette explicitly and keeps its legacy native
|
||||
field rules separate, so Radio's palette remains an intentional exception.
|
||||
|
||||
### Read-only status badges
|
||||
|
||||
Views draw read-only badges with the `gev-status-badge` classes, without
|
||||
listeners, status inference or live-region policy. Appearance variants share
|
||||
CSS rather than mapping machine states to universal severity. CCTV owns
|
||||
frame/source and calibration rules and their data attributes, while Weather
|
||||
owns coverage labels. The rail card keeps node identity, header focus,
|
||||
disclosure and teardown, and does not announce every card update.
|
||||
+1539
-197
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
# Director authoring and file sharing
|
||||
|
||||
Open Scenes and select a scene and shot. Existing capture, update, playback and
|
||||
Open Authoring and select a scene and shot. Existing capture, update, playback and
|
||||
whole-project export controls remain available. Authored scene content, source
|
||||
links, assets and contributor credit are preserved.
|
||||
|
||||
@@ -42,7 +42,7 @@ field definitions. This is a basic draft editor, not a node graph or path editor
|
||||
SHARE SCENE exports only the selected scene. Download scene JSON preserves its
|
||||
camera, timing, layers, anchors, manifests, interactions and attribution, but
|
||||
contains no asset bytes. The recipient needs the referenced source registrations
|
||||
and files. Existing EXPORT PRESETS continues to export the entire project.
|
||||
and files. EXPORT (formerly EXPORT PRESETS) continues to export the entire project.
|
||||
|
||||
For a bundle, select the declared pack files or a folder, then Download asset
|
||||
bundle. Folder paths match a manifest path either directly or after removing the
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# UI text and translation
|
||||
|
||||
Every piece of text a user can see goes through `t()`, `tn()` or `msg()` in code, or a `data-i18n*` attribute in markup. The English text itself is the key. English needs no catalog; other languages live in `src/i18n/locales/<code>.json`. Translators work from [TRANSLATORS.md](./TRANSLATORS.md).
|
||||
|
||||
## Which one to use
|
||||
|
||||
| | Translates | Use for |
|
||||
| ------------------------------- | --------------------------------- | --------------------------------------------- |
|
||||
| `t(text, values)` | now | any text shown right where it is written |
|
||||
| `tn(count, one, other, values)` | now, picking the form for `count` | text that changes with a number |
|
||||
| `msg(text)` | never: returns `text` unchanged | text stored in data and passed to `t()` later |
|
||||
|
||||
```js
|
||||
import { t, tn, msg } from '../i18n/index.js';
|
||||
|
||||
status.textContent = t('Save keys');
|
||||
status.textContent = t('Saved to {store}', { store: storeLabel() });
|
||||
|
||||
chip.textContent = tn(missing, '{count} KEY WAITING', '{count} KEYS WAITING');
|
||||
// en: 1 KEY / 3 KEYS ru: 1 КЛЮЧ / 3 КЛЮЧА / 5 КЛЮЧЕЙ
|
||||
|
||||
const FEED_STATE_LABELS = { loading: msg('LOADING'), stale: msg('STALE') };
|
||||
label.textContent = t(FEED_STATE_LABELS[state]);
|
||||
```
|
||||
|
||||
A module the server, tools or other portable code also imports must not load the catalogs: it imports `msg` from `src/i18n/marker.js`, which only marks the text, and the UI translates it with `t()` where it shows it. (Older modules keep a local `const msg = (text) => text`, which works the same.)
|
||||
|
||||
Vue views call the same `t()` and `tn()`. The page's vue-i18n instance (`src/ui/i18n.js`), installed in every view, holds the active locale, and `t()`, `tn()` and the formatters read it from there, so a view that translates while rendering renders again when the locale changes. Views don't use vue-i18n's own `t`: English is the key, `context` picks an entry and plural forms follow `Intl.PluralRules`, which only Core's functions do.
|
||||
|
||||
`msg()` exists for two reasons. `npm run i18n` finds messages only as string literals inside `t(…)`, `tn(…)` and `msg(…)`, so `t(FEED_STATE_LABELS[state])` alone tells it nothing. And a `t()` call at module load runs before the page has picked its language, so it would freeze the English.
|
||||
|
||||
## Markup
|
||||
|
||||
Vue views translate in the template, so their text renders in the active locale:
|
||||
|
||||
```vue
|
||||
<span>{{ t('Normal') }}</span>
|
||||
<button :title="t('Pin visual presets')">…</button>
|
||||
<option value="all">{{ t('All', { context: 'radio tag' }) }}</option>
|
||||
```
|
||||
|
||||
Static page markup that no view renders (the loading screen in `index.html`, a host's own markup) opts in with attributes instead, and `npm run i18n` rejects them in a Vue template:
|
||||
|
||||
```html
|
||||
<span data-i18n>Normal</span>
|
||||
<button title="Pin visual presets" data-i18n-title>…</button>
|
||||
<input placeholder="Search any location..." data-i18n-placeholder />
|
||||
```
|
||||
|
||||
`data-i18n` replaces the element's text, so put it on the element that directly holds the text, never on a parent with child elements. `data-i18n-title`, `data-i18n-aria-label` and `data-i18n-placeholder` translate that attribute. `data-i18n-context="radio tag"` gives the element's text a context, the catalog entry `radio tag|All`, as `t('All', { context: 'radio tag' })` does in code. Markup is translated at boot and again after every language switch.
|
||||
|
||||
## Switching language
|
||||
|
||||
The language picker switches in place, without a reload. A view that calls `t()` while it renders follows by itself. Code that translates text and hands it on (to a store, a published view object, a layer's state, a DOM node) must word it again when the language changes:
|
||||
|
||||
- **Present again.** `onLocaleChange(listener)` from `src/i18n/index.js` calls the listener after each change and returns the function that removes it; call that at teardown. The listener presents the view again from what it keeps, as `layerPanel.js` and `cockpitController.js` do.
|
||||
- **Keep the English, translate when presenting.** Store `msg()` text or a function that words it, not its translation: the voice control takes `update(part, { text: () => t('MIC') })`, the scene director's status and the SDR receiver's messages keep such functions, and `imagerySplit.js` takes its titles that way.
|
||||
- **Translated constants in a view** go in `computed`, not in a plain `const` set up once.
|
||||
|
||||
A toast already on screen keeps its words.
|
||||
|
||||
## Rules
|
||||
|
||||
- **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 `tn()` for counts**, not `count === 1 ? … : …`. Russian has three forms.
|
||||
- **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.
|
||||
- **Numbers and dates** go through `formatNumber`, `formatDate`, `formatRelativeTime` and `formatUtcDayMonth` from `src/i18n/index.js`, which follow the active language.
|
||||
- **Identical English means identical translation,** unless you give it a context: `t('CLEAR', { context: 'action' })` (a button) is its own catalog entry, `action|CLEAR`, while `t('CLEAR')` (the sky) stays separate. English output is the same.
|
||||
- **Text that should stay English** (a brand, a code, a unit symbol) is simply not wrapped.
|
||||
|
||||
## After you add or change text
|
||||
|
||||
```sh
|
||||
npm run i18n # what each language lacks or no longer uses, and unmarked UI text
|
||||
npm run i18n -- --missing ru # the missing ru entries as JSON
|
||||
npm run i18n -- --prune # drop entries whose English no longer exists
|
||||
```
|
||||
|
||||
Changing the English changes the key: the old translation goes unused and the new text shows as missing. Add the translation to every `src/i18n/locales/<code>.json` (or ask a translator), prune, and run `npm test`; it fails until every language is complete.
|
||||
|
||||
To see a language, open the app with `?lang=ru`, or use the language picker (the globe button in the bottom-right corner).
|
||||
|
||||
An empty translation is reported, and so is one left as the English, plural forms included. When English is right on purpose (a code such as CRT, a product name), list it under the language in `src/i18n/same-as-english.json`.
|
||||
|
||||
## Text the check reports as unmarked
|
||||
|
||||
`npm run i18n` and `npm test` fail on UI text that is not marked:
|
||||
|
||||
- **Markup:** element text without `data-i18n`, and `title`, `aria-label` or `placeholder` without the matching `data-i18n-*`. In a Vue template, static text or a static `title`, `aria-label` or `placeholder` (rather than `t()`), and any `data-i18n*` attribute.
|
||||
- **Code:** string literals that reach the page without `t()`, `tn()` or `msg()`: assigned to `textContent`, `title` and the like; passed to `setAttribute('title' | 'aria-label' | …)`, `createTextNode`, `showToast` and other notice helpers; assigned to a layer's `error`, `lastError`, `loadingLabel`, `statusMessage` or `status` (a plain `error = …` too); or kept in `label:`, `title:`, `status:` and similar properties. Not reported: declarations (`let status = …`), default values, ternary branches and lower-case codes (`'auth-failed'`). The full list is `JS_SINKS` in `scripts/i18n.mjs`.
|
||||
|
||||
Fix it by marking the text. When the text must stay as it is, say so where it lives:
|
||||
|
||||
| Case | How |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| Codes, units, product names in markup (`0.9 dB`, `ADS-B · 1090`, `OpenSky`) | `translate="no"` on the element; it covers everything inside, as in HTML |
|
||||
| Page markup whose text code sets through `t()` (a status that starts as `OFF`) | `data-i18n-runtime` on the element (not in a Vue template) |
|
||||
| A literal in code that is not UI copy (a product name, sample data) | `// i18n-ignore: <reason>` on the line or the line above |
|
||||
|
||||
Icons (`aria-hidden="true"`, Material Symbols), comments, CSS values and lowercase identifiers are skipped automatically, and so is `src/tools/`, whose text is written for AI clients rather than the page.
|
||||
|
||||
## Where things live
|
||||
|
||||
| File | What it holds |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `src/i18n/index.js` | `t`, `tn`, `msg`, the Intl formatters, markup translation, switching language |
|
||||
| `src/ui/i18n.js` | the page's vue-i18n instance: the active locale and the catalogs |
|
||||
| `src/i18n/marker.js` | `msg` alone, for modules the server or tools import (no catalogs) |
|
||||
| `src/i18n/locale.js` | shipped languages, their names, choosing the language at boot |
|
||||
| `src/i18n/locales/<code>.json` | one catalog per language, keyed by English text |
|
||||
| `src/i18n/same-as-english.json` | per language, the entries that stay as in English on purpose |
|
||||
| `scripts/i18n.mjs` | `npm run i18n`: extracts messages, checks the catalogs, finds unmarked UI text |
|
||||
| `src/i18n/catalog.test.mjs` | runs that check under `npm test` |
|
||||
@@ -53,9 +53,8 @@ Cesium installation shared with its viewer; it must not bundle separate copies.
|
||||
`gods-eye-view/infrastructure/geojson` exports the lower-level
|
||||
`createLocalGeoJsonLayer(options, services)` and existing infrastructure overlay
|
||||
helpers. `gods-eye-view/infrastructure/lod` exports the existing LOD policy helpers.
|
||||
The standalone app's `src/data/localGeojson.js` keeps its original single-argument
|
||||
factory and supplies its existing functions. Other consumers should use the package
|
||||
exports, which do not import standalone application globals.
|
||||
Consumers use the package exports, which do not import standalone application
|
||||
globals.
|
||||
|
||||
Dataset files and source/license notices remain under `src/data/local_data/`.
|
||||
Their input counts are 4,351 datacenter features and 704 dam features. Cesium may
|
||||
|
||||
@@ -57,9 +57,8 @@ Related keys (current versions):
|
||||
- Panel collapsed state: `godsEyeView.v6.panelCollapsed.<panel-id>` — `'0'` open,
|
||||
`'1'` closed, absent means the panel's own default. A view opened from a share
|
||||
link ignores the stored value entirely.
|
||||
- Panel positions: `godsEyeView.v8.panelPos.<panel-id>` — written only for a
|
||||
floating portable panel (CCTV, Street Level). Deleting it, or double-clicking
|
||||
the panel header, docks the panel again.
|
||||
- Panel positions: `godsEyeView.v8.panelPos.<panel-id>` — the current layout
|
||||
writes none.
|
||||
- CCTV calibration: `godsEyeView.cctv.calibration.v2`
|
||||
|
||||
---
|
||||
|
||||
@@ -49,7 +49,8 @@ failure elsewhere. These questions guide review, not a mandatory questionnaire.
|
||||
regressions? Prefer fixes whose tests fail on the base. Test-only PRs should
|
||||
close a real coverage gap with deterministic fixtures and useful assertions.
|
||||
Inspect removed/skipped tests, weakened assertions, mocks, snapshots, and runner
|
||||
changes. Update `CURRENT-STATE.md` and `CHANGELOG.md` for runtime changes, and
|
||||
changes. Update `CURRENT-STATE.md` and add a `changelog.d/<pr-number>-<slug>.md`
|
||||
fragment (not an edit to `CHANGELOG.md`) for runtime changes, and
|
||||
`DATA_SOURCES.md` for source changes.
|
||||
|
||||
## 3. Choose execution proportionate to risk
|
||||
@@ -124,6 +125,15 @@ Condition the merge on the reviewed head where supported; if revisions change,
|
||||
return to validation. Verify the merged tree matches the validated candidate and
|
||||
record the merge commit/PR URL; stop on mismatch.
|
||||
|
||||
Pending release notes live in `changelog.d/`, one file per PR, so integration never
|
||||
resolves the top of `CHANGELOG.md`. At release, `npm run changelog:check` previews
|
||||
the batch and its order; `npm run changelog` splices it into `CHANGELOG.md` and
|
||||
empties the directory. The assembler refuses a malformed filename or two fragments
|
||||
claiming one PR number before writing, and copies text through unchanged so the
|
||||
contributor's wording and credit ship. The write is staged in
|
||||
`changelog.d/.assembling/`, so if it fails part-way, rerunning `npm run changelog`
|
||||
finishes or rolls back the batch. It never duplicates or drops an entry.
|
||||
|
||||
Return this concise record privately unless posting is authorized. Keep sensitive
|
||||
security evidence in the private report:
|
||||
|
||||
|
||||
+8
-1
@@ -97,7 +97,8 @@ operational sample contained 3,909 observations and two selected labels, but its
|
||||
live aircraft and traffic rows were empty, so it remains a limited stress case.
|
||||
|
||||
Snow, Noir, dense detection, and text-heavy combined layers are the clearest
|
||||
controlled comparison points for later optimization work.
|
||||
controlled comparison points for later optimization work. Snow has since been
|
||||
retired as a visual preset; its row records the measurement as taken.
|
||||
|
||||
## Keyed live sources
|
||||
|
||||
@@ -128,6 +129,12 @@ Use the same controls before attributing a difference to the application:
|
||||
6. Record live object counts before attributing a difference to the client.
|
||||
7. Treat a live-source outage as missing coverage, not as evidence of low client
|
||||
rendering cost.
|
||||
8. Record whether the app is running in Pinokio's embedded view or in an
|
||||
external browser. One Pinokio 8.2.0 user reported severe stutter in the
|
||||
embedded view that went away when the same local URL was opened in a normal
|
||||
browser ([#365](https://github.com/bilawalsidhu/gods-eye-view/issues/365)).
|
||||
Do not compare embedded-view and external-browser measurements directly;
|
||||
compare the reported GPU renderer and matched scene settings first.
|
||||
|
||||
## What is not established yet
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Scene document format
|
||||
|
||||
Scenes exports a UTF-8 JSON project. Version 5 adds [data packs](DIRECTOR-DATA-PACKS.md)
|
||||
Authoring exports a UTF-8 JSON project. Version 5 adds [data packs](DIRECTOR-DATA-PACKS.md)
|
||||
and is the write format. Versions 1, 2, 3, 4 and unversioned legacy files migrate on
|
||||
import without converting their existing shots into authored moves. Unknown versions or unsupported fields are rejected with
|
||||
a field path before the current project, selection or saved bytes change.
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
# Share-link format
|
||||
|
||||
A share link restores a view: where the camera is, what it follows, the visual
|
||||
style, data layers and panel layout. There are two forms.
|
||||
|
||||
- **Long form** — `#v=2&lat=30.2672&lon=-97.7431&…`. Parsed by
|
||||
`src/sharelink.js`. The address bar follows the live view in this form, and
|
||||
links already in the wild keep working; the long form is never removed.
|
||||
- **Short form** — `<app>/share/<code>`, where `<app>` is wherever the app is
|
||||
served (an origin's root, or a base path) and `<code>` is encoded and
|
||||
decoded by `src/share/viewCodec.js`, which is pure (no DOM, no Cesium) and
|
||||
needs no server. Hosts import it as `@gods-eye-view/core/share/view-codec`
|
||||
(a server can read a code to describe the view in a link preview). The share button makes this form. The same code opens on
|
||||
any copy of the app. `src/share/shareLinkPath.js` reads and builds the
|
||||
address, and `src/share/legacyShareState.js` converts between the long
|
||||
form's parsed state and the code's state.
|
||||
|
||||
The same view in both forms (a real session's link):
|
||||
|
||||
| Form | Characters |
|
||||
| ----- | ----------------- |
|
||||
| Long | 302 (the hash) |
|
||||
| Short | 39 (the `<code>`) |
|
||||
|
||||
For example, a camera-only place view is a 24-character code (31 with its
|
||||
created-at time).
|
||||
Following an aircraft is 42–50: 42 when the link is made from the app's own
|
||||
follow camera with the fix taken as the link is made, 44 with a fix four
|
||||
seconds older, and 50 after orbiting to another heading and pitch. A view in
|
||||
any other style that keeps the style's own look adds only the style (two
|
||||
bytes): the 42-character followed aircraft in Noir is 44.
|
||||
|
||||
## Opening a short link
|
||||
|
||||
At start-up the app reads `<code>` from an address whose path ends in
|
||||
`share/<code>` (a host can pass the code instead, as `initialShareCode` to
|
||||
`createApplicationControls`). A code that decodes runs the same restore as a
|
||||
long link; once the app next writes the address, `/share/<code>` is replaced
|
||||
by the app's own address and the long form, without a new history entry, so
|
||||
a reload follows the live view. A code that is rejected restores nothing: the
|
||||
app starts as usual and says the link can't be opened.
|
||||
|
||||
A thing view turns on its entity's layer and follows the entity by id, using
|
||||
the same restore as a long link's followed flight or satellite (and its
|
||||
expiry windows); a vessel is selected once the vessel feed has it. Opening is
|
||||
best effort: until the entity is found, and if it is not found in time, the
|
||||
camera is where the link was made, looking at the entity's last-known fix,
|
||||
and the app says that the entity isn't reporting and when the fix was taken.
|
||||
The share button makes a thing view while the camera follows an aircraft,
|
||||
military aircraft or satellite. A selected vessel is not followed (the camera
|
||||
stays where the user puts it), so a link made then is a place view; vessel
|
||||
thing views open, but the app does not make them yet.
|
||||
|
||||
## Payload
|
||||
|
||||
The code is unpadded base64url (`A–Z a–z 0–9 - _`). The decoded bytes are:
|
||||
|
||||
1. one **version byte**, currently `1`;
|
||||
2. a sequence of **fields**, each a varint tag `field_number << 3 | wire_type`
|
||||
followed by its value;
|
||||
3. one **check byte**: the CRC-8 of everything before it.
|
||||
|
||||
| Wire type | Value |
|
||||
| --------- | ----------------------------------------------------------- |
|
||||
| 0 varint | unsigned LEB128, at most 8 bytes, at most 2^53 − 1 |
|
||||
| 1 fixed64 | 8 bytes (not used by v1; skipped) |
|
||||
| 2 LEN | varint length, then that many bytes (text or a sub-message) |
|
||||
| 5 fixed32 | 4 bytes (not used by v1; skipped) |
|
||||
|
||||
Signed values are zigzag varints (0, −1, 1, −2 → 0, 1, 2, 3). Text is printable
|
||||
ASCII. Fields are written in ascending order; the decoder accepts any order.
|
||||
|
||||
A field whose value equals its default is **omitted**. The decoder fills every
|
||||
omitted field from the same defaults table the encoder reads (for a
|
||||
style-dependent field, the decoded style's defaults), so an empty
|
||||
payload (code `AQc`: the version byte and its check byte) is the all-default
|
||||
view.
|
||||
|
||||
### Check byte
|
||||
|
||||
The check byte is CRC-8 with polynomial x⁸ + x² + x + 1 (`0x07`), initial
|
||||
value 0, no reflection and no final XOR (CRC-8/SMBUS; the check value of the
|
||||
ASCII digits `123456789` is `0xF4`). A decoder that computes a different value
|
||||
rejects the code (`checksum`) before reading any field, so a mistyped code
|
||||
never opens a different view. A degree-8 CRC catches every error burst of up
|
||||
to 8 bits; one base64url character is 6 bits, so **every single-character
|
||||
typo is rejected** (a test tries every substitution at every position). Two
|
||||
swapped neighbours, a dropped or an extra character are caught with a
|
||||
probability of about 255 in 256 (many of them already fail as base64url).
|
||||
|
||||
## Field registry (top level)
|
||||
|
||||
| # | State key | Type | Default | Encoding |
|
||||
| --- | ---------------------------- | ----- | ----------- | ----------------------------------------------------------------- |
|
||||
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
|
||||
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 4 | `alt` | sint | 800 | whole metres |
|
||||
| 5 | `heading` | angle | 0 | whole degrees, see angles |
|
||||
| 6 | `pitch` | sint | −35 | whole degrees |
|
||||
| 7 | `roll` | angle | 0 | whole degrees |
|
||||
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 9 | booleans | flags | 0 | bit set when the value differs from default |
|
||||
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
|
||||
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
|
||||
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
|
||||
| 13 | thing | LEN | none | sub-message, see thing views |
|
||||
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
|
||||
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
|
||||
| 16 | `hudVariant` | enum | style | tactical, operator (retired), minimal, cyber |
|
||||
| 17 | `bloomIntensity` | uint | style | |
|
||||
| 18 | `bloomVersion` | uint | 2 | |
|
||||
| 19 | `sharpenIntensity` | uint | style | |
|
||||
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
|
||||
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
|
||||
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
|
||||
| 23 | `detectionFadePct` | uint | style | 0–40 |
|
||||
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
|
||||
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
|
||||
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
|
||||
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
|
||||
|
||||
Field 1 absent means `thing` when field 13 is present and `place` otherwise,
|
||||
so v1 never writes it; a later view kind writes its code.
|
||||
|
||||
Enum codes are the list position (0, 1, 2…). A name not in the list is written
|
||||
as LEN text in the same field, so the app can add a style or map before this
|
||||
table learns its code. A new style's settings are encoded against its own
|
||||
defaults, while a decoder that does not know the style uses Normal's; adding a
|
||||
style also extends the pinned style-defaults table.
|
||||
|
||||
`hudVariant` code 1 (`operator`) is retired: the app no longer offers that
|
||||
layout and restores it as `tactical`. The code stays reserved.
|
||||
|
||||
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
|
||||
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
|
||||
|
||||
### Style defaults
|
||||
|
||||
A default marked **style** is what the view's style applies when it is
|
||||
selected in a fresh session (`SHARE_VIEW_STYLE_DEFAULTS`): the app's
|
||||
first-load baseline (`GLOBAL_POST_DEFAULTS` in `src/ui/visualPresets.js`),
|
||||
with the style's preset on top (`STYLE_PRESET_DEFAULTS`), and the style's
|
||||
shader parameters at the preset's values, else the shader's own. The encoder
|
||||
compares against the defaults of the view's style, and the decoder fills
|
||||
omitted settings from the defaults of the style it decoded (an unknown style
|
||||
uses Normal's). So a view that keeps its style's look costs its camera and
|
||||
style alone. (A recipient-side preference the app remembers, such as the
|
||||
detection allocation, is not a style default: a sender who changed it pays
|
||||
for it.) In v1:
|
||||
|
||||
| Setting | Every style |
|
||||
| ------------------------- | ------------- |
|
||||
| bloom, intensity | off, 0 |
|
||||
| sharpen, intensity | on, 49 |
|
||||
| HUD visible, variant | on, tactical |
|
||||
| detection mode, density | DENSE, 75 |
|
||||
| allocation, fade, outside | ELASTIC, 7, 1 |
|
||||
| celestial ring | off |
|
||||
|
||||
| Style | Shader parameters (field 27 defaults) |
|
||||
| ------------ | --------------------------------------------------------------- |
|
||||
| normal | none |
|
||||
| retro | pixelation 1, distortion 0, instability 0.42 |
|
||||
| surveillance | gain 0.18, bloom 0.22, scanlineStr 0.96, pixelation 1 |
|
||||
| thermal | sensitivity 0.85, bloom 0.2, mode 0.33, pixelation 1, palette 0 |
|
||||
| anime | saturation 1, edgeThick 0.5 |
|
||||
| noir | contrastAmt 1.2, grainAmt 0.5, vignetteAmt 0.5 |
|
||||
| snow | retired: decodes as normal; its parameters are dropped |
|
||||
|
||||
These are frozen like every other default: the codec reads them from the
|
||||
app's tables today, and a test pins them, so if a table moves the old values
|
||||
are frozen in the codec rather than changing what shared links mean.
|
||||
|
||||
### Precision
|
||||
|
||||
| Value | Stored as | Matches the long form |
|
||||
| --------------------------- | ------------------------- | --------------------- |
|
||||
| Latitude, longitude | 10⁻⁴ degree (about 11 m) | `toFixed(4)` |
|
||||
| Altitude, range | whole metres | `Math.round` |
|
||||
| Heading, pitch, roll | whole degrees | `Math.round` |
|
||||
| Intensities and percentages | integers | `Math.round` |
|
||||
| Style parameters | hundredths after clamping | `sp` (× 100) |
|
||||
| Created-at and observed-at | whole seconds | `at` |
|
||||
|
||||
Angles: heading and roll are normalized into [0, 360) — the long form can
|
||||
write `roll=360` for a camera at 359.6°, which the compact form stores as 0 —
|
||||
and written as the nearer signed turn, so 350° costs one byte (−10).
|
||||
When a state is encoded, out-of-range input is clamped (latitude, pitch) or
|
||||
wrapped (longitude). A payload that stores a latitude or longitude beyond
|
||||
±90/±180 cannot come from the encoder and is rejected.
|
||||
|
||||
### Layers (fields 10 and 11)
|
||||
|
||||
A layer's number is derived from its permanent layer-state token
|
||||
(`src/data/layerStateTokenReservations.json`): a one-character token is its
|
||||
index in `0–9a–z`, a two-character token is 36 + first × 36 + second. The
|
||||
token ledger never reassigns a token, so layer numbers never change and new
|
||||
layers need no change here.
|
||||
|
||||
Field 10 is a bitset: bit _n_ of byte ⌊n/8⌋ (least significant first) is set
|
||||
when layer _n_ is on. Present-but-empty means "every layer off"; an absent
|
||||
field leaves the recipient's layers alone (the long form's `l=` versus no `l`).
|
||||
A thing view's entity layer is always on, so its bit is not written: in a
|
||||
thing view, present-but-empty means "only the entity's layer", and an absent
|
||||
field opens with only the entity's layer on (the app writes the field
|
||||
whenever it has a layer set).
|
||||
|
||||
Field 11 repeats `(layer number, option key, value)` for each option that
|
||||
differs from its frozen absent meaning (`SHARE_VIEW_LAYER_OPTION_ABSENT`). The
|
||||
option key is `option token index × 2 + kind`: kind 0 means the value is a
|
||||
zigzag varint (an option whose long-form spelling is a canonical integer),
|
||||
kind 1 means a varint length and ASCII text (the long-form spelling). Values
|
||||
are decoded by the layer-state codec's own option decoders.
|
||||
|
||||
A followed aircraft, military aircraft or satellite is a thing view in the
|
||||
compact form, not a tracking option: the encoder moves it out of the layer
|
||||
options, and the converter moves it back for the long form.
|
||||
|
||||
### Panels (field 12)
|
||||
|
||||
Each entry is one varint: `panel number × 16 + collapsed × 4 + pinned`, where
|
||||
collapsed and pinned are 0 (no value), 1 (false) or 2 (true). Only panels that
|
||||
differ from the frozen default layout are written, plus a collapsed-0 entry
|
||||
for each panel the state has no opinion about (an older long-form link that
|
||||
predates the panel); a panel with no entry takes its default. Values of 3 are
|
||||
reserved: a decoder ignores that entry, as for collapsed 0.
|
||||
|
||||
| # | Panel | Long-form token | Default |
|
||||
| --- | ---------------------- | --------------- | ------------------- |
|
||||
| 0 | `control-panel` | `c` | collapsed, unpinned |
|
||||
| 1 | `location-bar` | `l` | collapsed, unpinned |
|
||||
| 2 | `data-panel` | `d` | collapsed |
|
||||
| 3 | `cctv-panel` | `v` | collapsed |
|
||||
| 4 | `recent-imagery-panel` | `i` | expanded |
|
||||
| 5 | `street-level-panel` | `t` | collapsed |
|
||||
| 6 | `radio-panel` | `r` | collapsed |
|
||||
| 7 | `scene-panel` | `s` | collapsed |
|
||||
| 8 | `global-context-panel` | `g` | collapsed |
|
||||
| 9 | `pp-toggles` | `p` | collapsed |
|
||||
| 10 | `param-slider-panel` | `m` | expanded |
|
||||
|
||||
### Style parameters (field 27)
|
||||
|
||||
Pairs of varints `(token index, value × 100)` for the parameters of the
|
||||
view's style that differ from its defaults, using the long form's `sp` tokens
|
||||
(`SHARE_VIEW_STYLE_PARAMS`). The decoded state always lists every parameter
|
||||
of the style: the stored ones over the style's defaults. A long link without
|
||||
`sp` converts to the style's defaults.
|
||||
|
||||
## View kinds
|
||||
|
||||
A **place** view (field 13 absent) opens at its camera, fields 2–7.
|
||||
|
||||
A **thing** view (field 13 present) follows an entity. Field 13 is a
|
||||
sub-message:
|
||||
|
||||
| # | Field | Encoding | Default |
|
||||
| --- | ----------- | -------------------------------------------------------- | ------- |
|
||||
| 1 | aircraft | entity id: varint (24-bit ICAO address) or LEN text | |
|
||||
| 2 | military | entity id: varint (24-bit ICAO address) or LEN text | |
|
||||
| 3 | vessel | entity id: varint (MMSI) or LEN text | |
|
||||
| 4 | satellite | entity id: varint (NORAD catalog number) or LEN text | |
|
||||
| 5 | range | uint metres from the entity to the camera | 2000 |
|
||||
| 6 | heading | angle, the camera's heading while following | 0 |
|
||||
| 7 | pitch | sint whole degrees, the camera's pitch while following | −35 |
|
||||
| 8 | fix lat | coord, the entity's last-known latitude | none |
|
||||
| 9 | fix lon | coord, its last-known longitude | none |
|
||||
| 10 | fix alt | sint metres | 0 |
|
||||
| 11 | fix age | sint seconds the fix predates the created-at time | 0 |
|
||||
| 12 | observed-at | epoch, when the fix was taken; only without a created-at | none |
|
||||
|
||||
The entity kind is the field that carries the id (its code in
|
||||
`SHARE_VIEW_ENTITY_KINDS`), so it costs nothing; exactly one of fields 1–4 is
|
||||
present (two is malformed). Entity ids are the ones the layers look up: an
|
||||
aircraft's ICAO 24-bit address (six hex digits, stored as a number; other
|
||||
forms such as TIS-B `~abc123` as text), a vessel's MMSI and a satellite's
|
||||
NORAD catalog number (decimal numbers; an MMSI with a leading zero stays
|
||||
text). Ids are lowercase and match `[0-9a-z~_-]{1,16}`.
|
||||
|
||||
Fields 5–7 are the follow offset: the camera looks along (heading, pitch) at
|
||||
the entity from `range` metres away, heading clockwise from north, in the
|
||||
entity's local east-north-up frame. The defaults match the app's own follow
|
||||
camera, which looks north at about −35°, so only its range is written.
|
||||
|
||||
Fields 8–12 are the last-known fix, the entity's position when the link was
|
||||
made. Fields 8 and 9 are both written whenever there is a fix; their presence
|
||||
marks it. Its time is the created-at time (field 8 at the top level) minus
|
||||
field 11, or field 12 when the view has no created-at time.
|
||||
|
||||
**The camera of a thing view with a fix is derived, not stored**: fields 2–7
|
||||
are not written, and the camera is where following the fix from the offset
|
||||
puts it (`followCameraFor` in `src/share/follow.js`): the camera sits
|
||||
`−r·cos p·sin h`, `−r·cos p·cos h` and `−r·sin p` metres east, north and up
|
||||
of the fix (heading h, pitch p, range r), on a sphere of radius 6 371 000 m,
|
||||
and looks back at the fix (its heading and pitch are those of that direction
|
||||
at the camera, roll 0; at follow distances they equal h and p). A
|
||||
decoder ignores fields 2–7 when the fix is present. A thing view without a
|
||||
fix (one converted from a long link, which records neither offset nor fix)
|
||||
writes its camera in fields 2–7 like a place view.
|
||||
|
||||
A decoder that meets a view kind or an entity kind it does not know (an id in
|
||||
a thing field it skips) opens the view as a place at its camera — the derived
|
||||
one when there is a fix — and reports `fallback`.
|
||||
|
||||
## Decoding rules
|
||||
|
||||
`decodeShareView(code)` takes the bare code and never throws. It returns
|
||||
`{ ok: true, state, unknownFields?, fallback? }` or `{ ok: false, reason }`.
|
||||
|
||||
Rejected (the whole link): input over 2048 characters, bad base64url
|
||||
(padding, foreign characters, impossible length, stray bits), fewer than two
|
||||
bytes, a check byte that does not match (`checksum`), an unknown version byte,
|
||||
a truncated value, a varint over 8 bytes or above 2⁵³ − 1, an
|
||||
overlong varint, a LEN longer than what remains (at any level, including
|
||||
inside the thing sub-message), a reserved wire type, field number 0, a known
|
||||
field with the wrong wire type or repeated, two entity ids in one thing, a
|
||||
duplicate panel/option/style entry, non-ASCII text, and a latitude or
|
||||
longitude (camera or fix) beyond ±90/±180.
|
||||
|
||||
Tolerated and reported in `unknownFields`: unknown fields at any level
|
||||
(skipped by wire type), unknown enum codes and names (the default is used),
|
||||
unknown flag bits, layers, panel values and style parameters, and layer
|
||||
options the layer-state codec rejects or that are filed under a layer other
|
||||
than their owner. Detection mode and allocation names outside their lists
|
||||
fall back to the default rather than reaching the long form's lenient
|
||||
migration. The map stack and HUD variant are open: a name outside their lists
|
||||
that matches `[A-Za-z0-9_-]{1,32}` is kept as written (the app decides what
|
||||
it means) and is not reported; only a name outside that grammar is. A
|
||||
created-at time in the future is dropped, as the long form's `at` is.
|
||||
|
||||
## Changing the format
|
||||
|
||||
These rules keep every link that was ever shared working, in every version of
|
||||
the app that can open it.
|
||||
|
||||
1. **Field numbers are permanent.** Never renumber, never reuse. A field that
|
||||
is no longer written stays in `SHARE_VIEW_FIELDS` marked `retired: true`.
|
||||
2. **A field's type and wire type are permanent.** To change one, add a new
|
||||
field.
|
||||
3. **Defaults are frozen meanings.** A default is what an omitted field meant
|
||||
when links were written. If the app's default moves, leave the table alone:
|
||||
the encoder will start writing the new value explicitly. Style defaults
|
||||
are read from the app's style tables; when those move, freeze the old
|
||||
values in the codec (see Style defaults). The same applies
|
||||
to `SHARE_VIEW_LAYER_OPTION_ABSENT` and the panel defaults.
|
||||
4. **Codes are append-only.** Enum lists, entity kinds, flag bits, panel
|
||||
numbers and style parameter tokens only grow. A style the app no longer
|
||||
offers keeps its code: `snow` (6) is retired, so a link that names it
|
||||
opens as Normal and the parameters written for it are dropped.
|
||||
5. **Add, don't change.** A new setting takes the next free field number
|
||||
(28 onwards; fields 1–15 have one-byte tags and are all taken). A new
|
||||
boolean takes the next flag bit instead, which costs nothing when it is at
|
||||
its default.
|
||||
6. **New view kinds and entity kinds** get the next code. A new entity kind's
|
||||
id takes the next free thing field (13 onwards), so older apps skip it and
|
||||
fall back to the camera. A thing that needs more than an id (for example a
|
||||
CCTV camera, a radio station or a transit vehicle) adds further thing
|
||||
fields the same way.
|
||||
7. **A new layer option** needs an entry in `SHARE_VIEW_LAYER_OPTION_ABSENT`
|
||||
(a test fails until it has one).
|
||||
8. **Bump the version byte only** for a change older decoders must refuse.
|
||||
Decoders reject any version they do not know. The trailing CRC-8 check
|
||||
byte belongs to every version.
|
||||
9. **Derivations are frozen too.** `followCameraFor` and the follow-offset
|
||||
defaults are part of the format: changing them moves the camera of every
|
||||
thing link already shared.
|
||||
|
||||
Add a test for every change: a round trip, and a payload written with the new
|
||||
field that the previous decoder still reads.
|
||||
+5
-4
@@ -92,7 +92,8 @@ pitch), data layers, visual style, map imagery, and an aircraft, military
|
||||
aircraft or satellite to follow. `createView` builds and bounds one,
|
||||
`viewToParams` and `viewFromParams` write and read it in the share-link
|
||||
format the app restores, and `viewUrl` gives the address that opens it. The
|
||||
style names are the ones share links use. Ships cannot be followed from a
|
||||
style names are the ones share links use; a retired style (`snow`) is read
|
||||
as `normal`. Ships cannot be followed from a
|
||||
link yet. An aircraft's `follow` may ask for its cockpit view
|
||||
(`cockpit: true`); links cannot carry that and open the app following it.
|
||||
|
||||
@@ -263,17 +264,17 @@ and the development server's internal routes. See SECURITY.md.
|
||||
| `get_earthquakes` | `earthquakes` | USGS M2.5+ events in the last 24 hours, strongest first |
|
||||
| `get_active_fires` | `fires` | NASA FIRMS detections in an area, highest radiative power first |
|
||||
| `get_recent_launches` | `launches` | Launch Library 2 launches in the last 30 days, newest first |
|
||||
| `aircraft_in_area` | `aircraft` | Aircraft in an area, nearest first; `military: true` reads the `military` feed |
|
||||
| `aircraft_in_area` | `aircraft` | Aircraft in an area, nearest first; `military: true` reads the `military` feed; `group_by` and `stats` count and summarize every match |
|
||||
| `find_aircraft` | `aircraft` | Aircraft anywhere by callsign, ICAO address or registration |
|
||||
| `get_aircraft_track` | `aircraft` | Recent positions of one aircraft, thinned to 200 points |
|
||||
| `get_aircraft_info` | `aircraft` | Aircraft type and registration, and flight route, from adsbdb |
|
||||
| `vessels_in_area` | `vessels` | Ships reported by AIS in an area, nearest first, optionally by type |
|
||||
| `vessels_in_area` | `vessels` | Ships reported by AIS in an area, nearest first, optionally by type; `group_by` and `stats` count and summarize every match |
|
||||
| `find_vessel` | `vessels` | Ships anywhere by MMSI, IMO number or name |
|
||||
| `get_vessel_track` | `vessels` | Recent positions of one ship, thinned to 200 points |
|
||||
| `next_satellite_pass` | `satellites` | Next pass over a place or point (default the ISS), with naked-eye visibility |
|
||||
| `satellites_overhead` | `satellites` | Satellites in a CelesTrak group above a place or point now, highest first |
|
||||
| `find_cctv_cameras` | `cctv` | Public cameras in an area, nearest first |
|
||||
| `get_cctv_snapshot` | `cctv` | The current image from one camera, returned as image content |
|
||||
| `get_cctv_snapshot` | `cctv` | An image from one camera, returned as image content; Street View fallbacks are labelled historical |
|
||||
| `find_alpr_cameras` | `alpr` | OpenStreetMap-mapped license plate readers in a US/Canadian area up to 3° |
|
||||
| `find_radio_stations` | `radio` | Radio Browser stations by area and/or search terms, with stream URLs |
|
||||
| `search_places` | `placeSearch` | Points of interest matching a query within an area (Google Places) |
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Translating God's Eye View
|
||||
|
||||
Shipped languages: **English** (source and fallback), **Arabic** (`ar`), **German** (`de`), **Spanish** (`es`), **French** (`fr`), **Italian** (`it`), **Japanese** (`ja`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
|
||||
|
||||
## How it works
|
||||
|
||||
The English text is the key: code calls `t('Save keys')`, and markup marks elements with `data-i18n`. How developers mark text is in [I18N.md](./I18N.md).
|
||||
|
||||
Every other language is one flat JSON file, `src/i18n/locales/<code>.json`, keyed by the English text:
|
||||
|
||||
```json
|
||||
{
|
||||
"Save keys": "Сохранить ключи",
|
||||
"{count} cameras": {
|
||||
"one": "{count} камера",
|
||||
"few": "{count} камеры",
|
||||
"many": "{count} камер",
|
||||
"other": "{count} камеры"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A `tn()` entry is keyed by the English plural form and holds one string per `Intl.PluralRules` category of that language. A missing entry falls back to English.
|
||||
|
||||
The page picks its language from `?lang=`, then the choice stored by the language picker (the globe button in the bottom-right corner), then the browser languages, then English. `?lang=` is not stored, and copied share links leave it out. Switching applies at once, without a reload.
|
||||
|
||||
## The check
|
||||
|
||||
```sh
|
||||
npm run i18n # what is missing, unused or broken, per language
|
||||
npm run i18n -- --missing ru # missing ru entries as JSON, ready to translate
|
||||
npm run i18n -- --prune # drop entries the code no longer uses
|
||||
```
|
||||
|
||||
`npm test` runs the same check. It fails on a missing, empty or unused entry, on placeholders that differ from the English, on a missing plural form, and on text or plural forms still in English. The `--missing` template fails until every entry is translated. If an entry is right in English (a code such as CRT, a product name), add it under your language code in `src/i18n/same-as-english.json`.
|
||||
|
||||
Changing English text changes its key: the old translation becomes unused and the new text shows as missing. Translate it and prune.
|
||||
|
||||
## Fixing a translation
|
||||
|
||||
Edit `src/i18n/locales/<code>.json` and run `npm run i18n`. To see your change, open the app with `?lang=<code>`.
|
||||
|
||||
## Adding a language
|
||||
|
||||
1. Create `src/i18n/locales/<code>.json`, fill it from `npm run i18n -- --missing <code>` (start from `{}`), and translate every entry.
|
||||
2. Import it in `src/i18n/index.js` and add it to `CATALOGS`.
|
||||
3. Add the code to `LOCALES` and its own-language name to `LOCALE_NAMES` in `src/i18n/locale.js`. Right-to-left languages also go in `RTL_LOCALES`; the chrome mirrors through logical CSS properties (`inset-inline-*`, `margin-inline-*`), and `src/ui/styles/rtl.css` holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in `INTL_TAGS` in `locale.js`.
|
||||
4. Add `src/i18n/locales/<code>.json` next to `ru.json` in `scripts/package-boundaries.json`.
|
||||
5. Write `docs/translations/<code>.md`: glossary, plural forms, units and style (copy the structure of `ru.md`).
|
||||
6. Run `npm run i18n` and `npm test`, then check the panels in the app at narrow widths.
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep every `{placeholder}` name exactly as in the English: do not rename, drop or add one. Its position inside the sentence follows the grammar of your language, e.g. `"{accepted} of {raw} records accepted"` → `"принято записей: {accepted} из {raw}"`.
|
||||
- UPPERCASE English stays UPPERCASE. Short labels, chips and HUD readouts should stay short; prefer an abbreviation over a wrapped line.
|
||||
- Keep the separators `·`, `—`, `→`, `↗`, `…`.
|
||||
- Leave product, provider and data names in their original form: Google Maps, Cesium ion, OpenAI, ChatGPT, NASA FIRMS, USGS, OpenSky, ADS-B, AIS, CCTV, HUD, TomTom, OpenStreetMap, Mapillary, and keyboard keys (Space, Esc, H, 1–9).
|
||||
- Units follow the language's convention; see the language notes.
|
||||
|
||||
## Never translated
|
||||
|
||||
These are contracts, not copy: layer ids and registry keys, status enums and other machine values (translate only the label shown for them), share-link and hash parameters, `?lang=`, storage schemas, API fields, voice tool names and schemas, provider and dataset names, data from feeds (callsigns, vessel, satellite, station and place names, headlines), attribution, console diagnostics, and user-authored content.
|
||||
|
||||
## Language notes
|
||||
|
||||
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Arabic](./translations/ar.md), [German](./translations/de.md), [Spanish](./translations/es.md), [French](./translations/fr.md), [Italian](./translations/it.md), [Japanese](./translations/ja.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
|
||||
@@ -11,12 +11,18 @@ scene playback and voice. It delegates; new behavior belongs with its owner.
|
||||
| `cockpitCoordinator.js` | Cockpit entry/rollback, readouts and the single Display portal |
|
||||
| `visualSettings.js` | Effects, detection overrides, visual restoration and render-loop lifetime |
|
||||
| `panelChrome.js` | Disclosure, docking, layout and Cockpit panel restoration |
|
||||
| `panelNavigation.js` | Desktop panel rails: each side's offered panels and the open one |
|
||||
| `aircraftDisplay.js` | Shared commercial/military 3D display preferences and controls |
|
||||
| `layerBindings.js` | Data-manager attachment, selected-tracker adoption and camera-entry listeners |
|
||||
| `displayBindings.js` | Keyboard/display subscriptions and frame-rate monitor lifetime |
|
||||
| `shareRestoration.js` | Initial share/layer restoration and cancellation |
|
||||
| `shellFeedback.js` | Loading feedback, notices and toast lifetime |
|
||||
|
||||
Owners that present UI do so through Vue: they publish plain view objects to a
|
||||
Pinia store in `src/ui/stores/`, views in `src/ui/views/` render them, and user
|
||||
actions return through the handlers the owner connects to that store. See
|
||||
[Building UI](../CONTRIBUTING.md#building-ui).
|
||||
|
||||
Owners receive named services, elements, operations and readers. They do not
|
||||
receive the application shell itself. Readers resolve a replaceable collaborator
|
||||
at use time; operations cross an ownership boundary without copying its state.
|
||||
|
||||
@@ -8,6 +8,7 @@ The session adapter, action runner and backend interfaces remain unchanged.
|
||||
| Owner | State and responsibility |
|
||||
| --- | --- |
|
||||
| `realtimeConnection.js` | Start generation, request cancellation, peer/data channel, acquired microphone stream and playback element |
|
||||
| `realtimeBackend.js` | Token and SDP requests, their separate transports, the abort/timeout scope around both, and client-secret expiry |
|
||||
| `realtimeTurns.js` | Active and superseded responses, pending confirmations, deduplicated calls and action cancellation |
|
||||
| `realtimeRadio.js` | Speaker ducking, prepared playback, stronger-action reservations and playback observers |
|
||||
| `realtimeInput.js` | Physical Space gesture, microphone mute, input/output meters and their frame/listener lifetime |
|
||||
@@ -21,6 +22,15 @@ The session adapter, action runner and backend interfaces remain unchanged.
|
||||
| `resultDisplay.js` | What the voice card shows for a result and which items it numbers (read by the card and `referents.js`) |
|
||||
| `gevActions.js` runner | Analyst follow-up memory (session: `resetConversation()` on stop, `dispose()` on removal) |
|
||||
|
||||
The backend is replaceable per deployment: `realtimeController.js` accepts it as a
|
||||
`backend` option and falls back to `createRealtimeBackend()`. Its two method
|
||||
signatures live with [application construction](APPLICATION.md); what belongs here
|
||||
is the ownership. The connection is the backend's only consumer in the application.
|
||||
The connection reads credential metadata for diagnostics and cost accounting,
|
||||
then passes the complete credential unchanged to `negotiate`. Client-secret expiry is refused inside the backend, not by the connection.
|
||||
`realtimeOwners.test.mjs` drives whole sessions through a substitute that
|
||||
implements only those two methods.
|
||||
|
||||
Pure input policy, preferences and protocol-response policy have separate modules.
|
||||
Owners receive named readers, operations or focused collaborators. Cross-owner
|
||||
cancellation uses operations; response handling does not own Radio's reservations
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
# Vue migration
|
||||
|
||||
The application UI moves from static templates plus imperative DOM wiring to
|
||||
Vue 3 components with Pinia stores. The migration keeps the experience the
|
||||
same: element ids, classes, the global stylesheet, translations, keyboard
|
||||
shortcuts, voice actions, share links and the phone layout all behave as
|
||||
before. The one intended change is the panel rail described below.
|
||||
|
||||
## Goals
|
||||
|
||||
- Views are Vue single-file components; state that several views read lives in
|
||||
Pinia stores rather than in DOM classes.
|
||||
- The owners listed in [UI-OWNERSHIP.md](UI-OWNERSHIP.md) keep their
|
||||
responsibilities. A component replaces DOM construction; it does not add a
|
||||
second owner of the same state.
|
||||
- Public methods used by layers, scene playback, voice and host applications
|
||||
(`setPanelCollapsed`, `setContextMode`, `setStyle`, share and location
|
||||
subscriptions) keep their signatures.
|
||||
- Ids and classes used by browser QA and host applications stay stable until a
|
||||
host can use an explicit extension point instead.
|
||||
|
||||
## The panel rail
|
||||
|
||||
Each desktop sidebar gets a floating icon rail that lists every panel available
|
||||
on that side. Selecting an icon opens that panel; selecting it again closes it.
|
||||
One panel is open per side at a time, so the rail replaces the accordion
|
||||
stacking. Panels that are not available in the current mode (for example
|
||||
Weather before a weather layer is active) are shown disabled or omitted as the
|
||||
panel's own availability rule dictates. The rail hides with the rest of the
|
||||
chrome in Cockpit, scene playback, recording and the clean view.
|
||||
|
||||
Phones keep the existing bottom tab bar and sheets; the rail is desktop only.
|
||||
|
||||
Voice and keyboard openings go through the same panel store, so they follow the
|
||||
one-panel rule too. Share links continue to record which panels are open.
|
||||
|
||||
## Component kit
|
||||
|
||||
`src/ui/kit/` holds the generic components: `UiRail`, `UiTooltip`, `UiIcon`,
|
||||
`UiButton`, `UiPanel`, `UiSegmented`, `UiSlider`, `UiChip`, `UiSelect`,
|
||||
`UiBadge` and `UiEmpty` so far, with more joining as the controls they serve
|
||||
are converted. One component
|
||||
serves every use of a control, with props for the differences (`UiPanel`'s
|
||||
`sideways`, `glow`, `scroll`), rather than a copy per panel. The components
|
||||
are styled by the existing stylesheet and custom properties; a kit class such as
|
||||
`ui-button--icon` carries no Core style yet, and a converted control keeps
|
||||
its own classes beside it. They contain no application state.
|
||||
`UiPanel` takes a `title`, slots and `v-model:collapsed`, but its
|
||||
`collapsed` drives only the button and `ui-panel--collapsed`: the root's
|
||||
`collapsed` class, which hides the body, is drawn at once by `v-panel-state`
|
||||
from the panel chrome store, and there is no `closable`, since Core's panels collapse rather than close.
|
||||
`UiSegmented` adds `keyboard` (`none`, `arrows`, or `radio` with
|
||||
Home and End), since each of Core's groups keeps its own keyboard policy, and
|
||||
`itemClass`/`activeClass` for Core's segment classes. A key moves focus at
|
||||
once and asks for a change only when it lands on another option. Every
|
||||
segmented group in Core is a `UiSegmented`; the old `segmentedControl.js`
|
||||
adapter is gone.
|
||||
`UiButton` adds `pressedClass`, Core's class on a pressed toggle (most use
|
||||
`active`); its pressed classes toggle through `classList`, so a class other
|
||||
code adds (a voice highlight) survives a press. Every pressed toggle in the
|
||||
views is a `UiButton` except Bhote Koshi's cinematic button and the
|
||||
tilt-map button, whose owners still write their state into the DOM. Core's
|
||||
pressable chips and pills (rail card chips, map sources, cities and places)
|
||||
are `UiButton`s too; `UiChip` is a non-interactive label chip, used by
|
||||
the voice card.
|
||||
`UiSelect` renders the bare native select (Core's labels sit outside it or
|
||||
come as an `aria-label`) with `v-model`, `options` and `disabled`;
|
||||
options that carry their own attributes come through the slot, a caller's
|
||||
`@change` still reaches the select, and `hold` works as in `UiSlider`.
|
||||
Without `hold`, a select takes its model back after its own change renders,
|
||||
so a pick its owner refuses goes back; an owner that answers later (the SDR
|
||||
gain) takes `hold`. Every select in the views is a `UiSelect`.
|
||||
`UiBadge` takes a `tone` and carries Core's `gev-status-badge`; its
|
||||
density variants and colours stay caller classes, and it renders the CCTV
|
||||
source and calibration badges and the rail card badges. `UiEmpty` takes
|
||||
a `title` and `hint`, but Core's empty states are one line, so the title
|
||||
is the text itself rather than a bold heading; the Scene shot list, mission
|
||||
roster and imagery strip use it. The locale picker's and the mission tables'
|
||||
empty rows stay a paragraph and table cells.
|
||||
`UiSlider` keeps the native range input rather than drawing a
|
||||
`role="slider"` track, so Core's keyboard, form behaviour and styling stay;
|
||||
it takes `v-model`, `min`, `max`, `step` and `disabled`, emits a
|
||||
number, and adds `hold` (never rewrite a focused handle, and take the model
|
||||
back on blur) and `unstyled` (the browser's own range, as Bhote Koshi's
|
||||
are, without Core's slider surface). A caller's own input, change and blur
|
||||
handlers still reach the input, and a null model leaves it as it is. It is
|
||||
a functional render-function component, so the portable `./ui/input` rows
|
||||
use it without a component compiler. Every range in the views is a
|
||||
`UiSlider` except the radio tuner's: its owner moves the handle back to the
|
||||
selected station on a drag's first input without changing the slot, which
|
||||
a functional component would not render again.
|
||||
|
||||
Controls share one skin, so every panel reads the same. Every scrolling
|
||||
surface carries the kit's `.ui-scroll`, which draws the thin
|
||||
themed scrollbar from `--scroll-thumb` and `--scroll-track`; the Cyber theme
|
||||
sets those two tokens, and Cockpit's HUD windows keep their accent tint. A
|
||||
surface without it would draw the platform's light scrollbar, because the
|
||||
page declares no dark `color-scheme`. Selects and number fields are
|
||||
`.gev-field`, which draws its own chevron in place of the platform's arrow
|
||||
and keeps native spinners dark; ranges are `.gev-quantitative-slider`, with
|
||||
a feature supplying only its accent.
|
||||
|
||||
## Phases
|
||||
|
||||
1. **Foundation.** Vue, Pinia and the Vite Vue plugin join the browser build
|
||||
(`build/vite.js`, so host applications receive them too). One Vue runtime is
|
||||
created by the shell (`src/ui/vueRuntime.js`) with one Pinia instance that
|
||||
every view shares; disposing the shell unmounts them. Stores are added
|
||||
when a view needs one, mirroring state an owner already publishes, so
|
||||
there is never a second owner of the same state.
|
||||
2. **Rail.** `PanelChrome` enforces one panel per side; the panel store
|
||||
mirrors each side's panels and the rail renders from it. The rail panels
|
||||
are `RailPanel`s: the kit's `UiPanel` drawing Core's panel markup, bound to the `gev-panel-chrome`
|
||||
store, with their titles rendered by `t()` rather than `data-i18n`. Each panel's
|
||||
collapse button, the dock tray toggles and the
|
||||
dock pins render the open and pinned state PanelChrome presents to the
|
||||
`gev-panel-chrome` store, and their clicks call it there. Each panel's
|
||||
root draws its own state from there too (`v-panel-state`: `collapsed`,
|
||||
`dock-pinned`, `dock-pinned-top`, `layout-auto-collapsed`,
|
||||
`accordion-collapsed`): PanelChrome, the rails and the accordion present
|
||||
it rather than writing classes. The directive applies it in a synchronous
|
||||
watcher, not on Vue's next render, because the rails measure a panel in
|
||||
the same task that opens or closes it. PanelChrome and its preference
|
||||
restore, and the phone shell, read a panel's state back from the store
|
||||
(`_shownPanel`; the markup's classes before its first presentation), so
|
||||
once a panel is presented a class written outside the store no longer
|
||||
counts. The rails, the accordion and the hover disclosure
|
||||
(`panelDisclosure.js`), which act within the same task as a layout, read
|
||||
the classes the directive has just drawn. Done.
|
||||
3. **Panels.** Each template and its wiring become a component, keeping ids,
|
||||
classes and CSS; text a template renders with `t()` drops its `data-i18n`
|
||||
marker. The page places each view with a
|
||||
`.gev-view` host (`display: contents`), and `mountApplicationViews()`
|
||||
(`src/ui/views/index.js`) renders the view into it before the page is
|
||||
translated and wired. `createApplication()` calls it too, removing the
|
||||
hosts of any views its `omitViews` names. Views belong to the page and are
|
||||
unmounted only when a later call omits them. The loading screen is static markup in the page so it
|
||||
paints before any script runs. Markup
|
||||
tests read `renderApplicationHtml()` from `src/testSupport`, which renders
|
||||
the views on the server. Templates compile with Vue's `preserve`
|
||||
whitespace, which keeps the whitespace between elements as a single
|
||||
space, so a view renders as its template did; no view markup sits under a
|
||||
`white-space: pre` rule. Application views (`src/ui/views/*View.vue`) are
|
||||
kept out of Prettier; other components are formatted with strict
|
||||
whitespace sensitivity, so their source spacing is what renders. The
|
||||
markup has moved, unchanged:
|
||||
every template except the loading screen is a view.
|
||||
|
||||
Next each view takes over its wiring: markup an owner builds with
|
||||
`createElement` becomes component markup that renders from a Pinia store
|
||||
the owner publishes to, and user actions come back to the owner through
|
||||
the store, so the owner keeps every decision. Component text uses
|
||||
`t()`, which follows the locale as the view renders; the views carry no
|
||||
`data-i18n*` markers, which `npm run i18n` rejects in a template. Shared builders become components first and
|
||||
keep their imperative entry points until their owners are components:
|
||||
the rail cards, card blocks and timeline (`src/ui/rail/`) render through
|
||||
Vue's synchronous `render()` behind `createRailCards()`,
|
||||
`createRailCardBlocks()` and `createRailTimeline()`. Order: Scenes (done: `SceneOptions` and
|
||||
`SceneShotList` over the `gev-scenes` store; the panel's buttons, status
|
||||
line, progress bar and the runtime chip render the panel SceneControls
|
||||
presents there, and the buttons and file chooser call its actions), Weather (done: the owner
|
||||
renders `WeatherReadout` from the view it computes), Recent imagery
|
||||
(done: `RecentImageryReadout` and its day cards, the same way),
|
||||
Street level (done: `StreetLevelPanel` renders the `gev-street-level`
|
||||
store that `StreetLevelControls` publishes to; the controls keep their
|
||||
listeners, EXPAND, focus and resizing), CCTV (done: `CctvPanel` renders
|
||||
the whole panel from the `gev-cctv` store, including the camera picker,
|
||||
the inline pose editor, the frame preview, source badge and typed
|
||||
summary; `CctvControls` keeps its listeners, the frame requests and the
|
||||
live video surface), Layers (done: `LayerPanel` presents each row and
|
||||
renders `LayerRows` synchronously; keyed chips and list items keep focus
|
||||
through the refresh their own click triggers, and clicks re-read the live
|
||||
descriptor before acting), Display (in progress: `DisplayPanel` renders
|
||||
the HUD, Sonar, Scope, Celestial, detection, bloom, sharpen and the
|
||||
active style's parameter sliders from the `gev-display` store, which
|
||||
VisualSettings publishes to and answers the sliders through, and the 3D
|
||||
aircraft group, the Draw controls and the imported file list, which
|
||||
AircraftDisplay, the draw tool and the file import publish beside it;
|
||||
the controls call DisplayBindings, the draw tool and the file import
|
||||
through the store's `connect`/`act`, the newest owner of an action
|
||||
answering it; `GeoImport` hands keyboard focus on before a focused row
|
||||
goes, with `v-focus-safe-children`; the
|
||||
style parameter rows render through Vue behind `createStyleParameters()`,
|
||||
as a render function so the portable `./ui/input` entry needs no
|
||||
component compiler; the other groups follow one owner at a time),
|
||||
Context and Radio (in progress: `RadioPanel` renders the directory row,
|
||||
station card, transport, volume and playback line from the `gev-radio`
|
||||
store that RadioControls publishes to and whose controls it answers
|
||||
through the store's `connect`/`act` (the shared `createOwners()`; a
|
||||
refused station tag gets the kept tag back from `act`), and `SdrCard` the
|
||||
local RTL-SDR card that LocalSdrControls publishes beside it and answers
|
||||
the same way; fields a person edits take their value through
|
||||
`v-field-value`, which never writes a focused field;
|
||||
`RadioTuner` renders the directory tuner, where RadioControls keeps the
|
||||
drag and its slot, takes the slider's pointer, key and value events
|
||||
through the store and publishes the band, readouts, slider and needle,
|
||||
and the component lays out the tick tape against the dial's measured
|
||||
width and lays out the snapped needle before it drops the drag class;
|
||||
`ContextView` renders the mode switch and the nearby sites
|
||||
search from the `gev-context` store, which ContextControls reaches only
|
||||
through its `publish` option, so `./ui/context` needs no Vue;
|
||||
`AwarenessPanel` renders the Contacts panel that the awareness layer
|
||||
publishes through `subscribePanel()`, the first layer to hand its rail
|
||||
content to a view, and `MissionRoster` and `MissionDetail` the Space
|
||||
Missions roster and selected mission that the launches layer publishes
|
||||
the same way), the command dock (in progress: the style buttons, the
|
||||
dock's style mini status and the ACTIVE STYLE corner render the style
|
||||
VisualSettings publishes to the `gev-display` store; the buttons keep
|
||||
their owner's listeners; the map source chips and status badge render
|
||||
what `createMapSourceControls()` publishes beside it, so `./ui/maps`
|
||||
needs no Vue; the location tray's city and landmark pills, mini status
|
||||
and search error render what `LocationControls` publishes to the
|
||||
`gev-location` store, and the search field keeps its listeners; beside
|
||||
the voice control, the dock teleports each voice pointer reticle to the
|
||||
body where its `PointerReticle` presents it in the `gev-pointer-reticle`
|
||||
store), the phone shell (`PhoneTabBar` renders the tabs and open sheet the shell
|
||||
publishes to the `gev-display` store, teleporting the tab bar and sheet
|
||||
backdrop to the end of the page from the first publish until the shell
|
||||
is destroyed; the shell takes their clicks from the document), the
|
||||
imagery dividers (`ImagerySplitView`, in the scene chrome, teleports each
|
||||
before/after divider that `createImagerySplit` publishes to the
|
||||
`gev-imagery-split` store to the body; the split keeps the
|
||||
drag, keyboard, CSS position and scene split), the status chips
|
||||
(the top-center loading status and the road network and camera grid
|
||||
chips render what ShellFeedback and CctvControls publish to the
|
||||
`gev-status` store; `SplitFlapText` plays the departure-board flip that
|
||||
`splitFlap.js` plans, writing only the label's one text node; the toast
|
||||
renders what each owner's `createToast()` publishes there, on that
|
||||
owner's own timer, and the title bar's FPS readout what
|
||||
`createFrameRateMonitor()` publishes), the globe actions
|
||||
(`GlobeActions` renders the top-center bar; its clear, share, tilt,
|
||||
north-up and reset buttons, and the cockpit's reset, call the shell
|
||||
through the `gev-globe-actions` store, which also carries a clear or a
|
||||
reset under way and the camera's tilt and heading
|
||||
that `bindCameraOrientationControls` publishes each drawn frame it
|
||||
changes, which only `GlobeCompass` renders; an owner publishes only
|
||||
while it answers the button, so a shell replaced by a newer one leaves
|
||||
its state alone), the recording safe frame (the scene chrome renders
|
||||
the frame `RecordingControls` publishes to the `gev-recording` store),
|
||||
the Scene actions panel (`SceneInteractions`
|
||||
teleports what the scene interactions owner publishes to the
|
||||
`gev-scene-interactions` store into the viewer's container; the owner
|
||||
keeps the pick handler and focuses the picked action), the data-pack
|
||||
attribution cards (`DataPacks` teleports what the data-pack presentations
|
||||
publish to the `gev-data-packs` store into the viewer's container and
|
||||
stops a removed card's player), the wind inspection marker
|
||||
(`WindInspectionMarker` teleports the marker into the wind layer's
|
||||
container where `createWindInspectionMarker()`, now in
|
||||
`src/layers/wind/inspectionMarker.js` so the portable `./tools` entry
|
||||
needs no Vue, places it after each rendered frame; an unchanged frame
|
||||
publishes nothing), the Space Missions replay vehicle
|
||||
(`MissionReplayOverlay` teleports into the globe container what the
|
||||
launches layer presents through `subscribeReplayOverlay()`, which the
|
||||
application shell publishes to the `gev-mission-replay` store, so the
|
||||
portable `./layers/launches` entry needs no Vue), the Bhote Koshi
|
||||
reconstruction panel (`BhoteKoshiPanel`, last in the context rail,
|
||||
renders what the event layer publishes to the `gev-bhote-koshi-panel`
|
||||
store: each control's text, hidden, disabled, value and attributes as
|
||||
the layer writes them, unchanged frames sending nothing; the rail takes
|
||||
`bhote-event-active` while it shows), the full-globe celestial ring
|
||||
(`CelestialRing` teleports into the globe container what `CelestialRing`
|
||||
in `src/celestialRing.js` publishes to the `gev-celestial-ring` store and
|
||||
hands its two canvases back for the owner to paint; the application shell
|
||||
passes the store in, so the keyhole helpers other packages import need no
|
||||
Vue), the screen-space map annotations (`ScreenAnnotations` teleports to
|
||||
the body the board the screen annotation renderer publishes to the
|
||||
`gev-screen-annotations` store after each rendered frame: each mark's SVG
|
||||
parts as attribute records, the fading marks until they leave; the view
|
||||
hands back the callout text and mark groups the renderer measures, and
|
||||
the tracked-entity fade is measured once the frame is in the document;
|
||||
the styles it injected now live in `src/ui/styles/annotations.css`), the
|
||||
visible map target list (the scene chrome renders the buttons and status
|
||||
the world overlay presents through `subscribeWorldOverlayActions()`, which
|
||||
the application shell publishes to the `gev-world-overlay-actions` store
|
||||
and answers with `activateWorldOverlayAction()`; the module needs no Vue
|
||||
for the portable transit entry, and its card and detection canvases stay
|
||||
painting surfaces it draws on, like the Cesium canvas), the Global
|
||||
Context compass and contact bearings (`AwarenessDirection` teleports into
|
||||
the globe container what the awareness layer presents through
|
||||
`subscribeDirectionOverlay()`, which the application shell publishes to
|
||||
the `gev-awareness-direction` store, so the portable `./layers/awareness`
|
||||
entry needs no Vue), the Intel HUD (the `intel-hud` view renders
|
||||
`#intel-hud`, its corners, edges and bottom bar from the `gev-hud`
|
||||
store, which IntelHUD publishes to through its `publish` option: the
|
||||
readouts, the typing summary, the REC blink, visibility, layout variant
|
||||
and style colors; the vessel layer presents the selected vessel's
|
||||
readout through `src/hudReadouts.js` instead of writing into the HUD),
|
||||
the first-run mission launcher (the welcome view renders the card's
|
||||
visibility, busy state, status line, copy and scroll fade from the
|
||||
`gev-first-run` store that `initFirstRunExperience` publishes to, and its
|
||||
choices, DISMISS and "Don't show this again" call the launcher's
|
||||
controls; a refused save hands the tick back to the view to revert. The
|
||||
launcher still reads the rendered card for its hit test, keyboard trap,
|
||||
focus and overflow measurement), then
|
||||
Cockpit (in progress: `CockpitBriefing` renders the briefing card's
|
||||
pages, cycle toggle, regional news and local conditions, and
|
||||
`CockpitContext` the Contact card's scope, navigation, counts, nearest
|
||||
contact and weather toggle, `CockpitView` the instruments, destination
|
||||
arrow and vision selector, and `CockpitRoute` the flight plan card, from
|
||||
the `gev-cockpit` store that the Cockpit controller publishes to,
|
||||
starting from `cockpitBriefPages.js`, `cockpitContextState.js` and
|
||||
`cockpitInstrumentState.js`, which both read; the odometer readouts
|
||||
(`CockpitRollingValue`) keep their throttle and roll in
|
||||
`cockpitRollingValue.js`. `CockpitSignalList` renders the live signal
|
||||
rows; `v-focus-safe-children` turns Vue's move of the focused row into
|
||||
moves of the rows around it, and hands focus to the shown briefing tab
|
||||
before the focused row is removed, so a keyed list reorders several times
|
||||
a second without dropping keyboard focus. The instruments publish on
|
||||
every HUD update (about 10 Hz); in a dev build their Vue patch costs
|
||||
about 0.5 ms, close to the DOM writes it replaced. The controller also
|
||||
publishes which surfaces are shown: the entry button and TR-3B chip in
|
||||
the Context panel, the HUD with its exit and reset buttons and briefing
|
||||
card, and the Contact card. Focus moves to the exit button, and back to
|
||||
the entry, only after Vue renders them. The cards' collapse is part of
|
||||
that presence too: each card's `data-collapsed` and its toggle's words
|
||||
and icon render from it. The controls keep the controller's listeners,
|
||||
and the cards' layout stays with the controller, which reads their
|
||||
visibility when it lays them out. Its layout pass publishes the utility
|
||||
strip's measured fit (the HUD's anchor and height limits, whether only the
|
||||
expanded control fits and which control is hidden from assistive
|
||||
technology), which `CockpitView` and `CockpitUtilityControls` render; the
|
||||
briefing and Contact cards' positions stay imperative, because the same
|
||||
pass measures them straight after writing them.
|
||||
`CockpitUtilityControls` renders the Display
|
||||
and Radio launchers, which popover is open and the compact Radio
|
||||
controls from the `gev-radio` store that RadioControls publishes to and
|
||||
answers through. The Cockpit display portal publishes the Display
|
||||
popover's slots to the `gev-display` store while Cockpit is on, and
|
||||
`DisplayPanel` teleports its HUD, Detection, Parameters and 3D groups into
|
||||
them and back; the portal keeps each surface's scroll position and puts
|
||||
back focus the move dropped).
|
||||
Layers that write into the right rail directly gain a slot instead.
|
||||
|
||||
4. **Cleanup.** The build-time template expansion is retired: `index.html`
|
||||
lists the view hosts and the loading screen itself. Every owner that
|
||||
showed UI state through hand-written DOM writes now publishes to a store
|
||||
that a view renders, apart from the imperative parts this document
|
||||
records surface by surface; [Building UI](../CONTRIBUTING.md#building-ui)
|
||||
now requires the pattern for new UI. The page-level markup tests read
|
||||
`renderApplicationHtml()`, the real views rendered on the server, and new
|
||||
tests mount components; three tests still read a `.vue` file's source
|
||||
(`cyberTheme`, `panelStorageDocs`, `geoFileImport`). The views translate with `t()`
|
||||
while rendering, and vue-i18n holds the locale they read; the attribute
|
||||
translation pass is left for static page markup (the loading screen and a
|
||||
host's own markup). An explicit API for hosts to register panels and rail items is
|
||||
deferred until a host adds one: no host does today (the hosted
|
||||
application's account interface is its own markup), and the first such
|
||||
panel, the hosted timeline, will shape it.
|
||||
|
||||
## Verification
|
||||
|
||||
- Unit tests: stores and components run under `npm test`. Single-file
|
||||
components are compiled by a test loader; component tests render with
|
||||
`@vue/test-utils` and `happy-dom`. `npm test` registers the loader; to run
|
||||
one file that reaches a component (including anything importing the
|
||||
shell), use `node --import=./build/registerVue.js --test <file>`.
|
||||
- Never pass happy-dom nodes to `assert.equal` or `deepEqual`: a failure
|
||||
prints both operands, and a node reaches the whole page and window, which
|
||||
runs the process out of memory. Compare nodes with `assertSameNode()`
|
||||
(`src/testSupport/domAssert.mjs`).
|
||||
- Panels lifted out of a view keep its verbatim markup in
|
||||
`src/ui/views/panels/`, which Prettier leaves alone like the views.
|
||||
- Visual parity: before/after screenshots of every panel at desktop and phone
|
||||
widths, in the default and Cyber themes, with the rail as the only expected
|
||||
difference.
|
||||
- Browser QA: the selector-based `scripts/qa-*.mjs` suites keep passing.
|
||||
- `npm run format:check`, `npm run check:boundaries`, `npm run i18n` and
|
||||
`npm run build`.
|
||||
+15
-9
@@ -3,10 +3,11 @@
|
||||
God's Eye View uses explicit auth modes for `/api/flights`:
|
||||
|
||||
- `OPENSKY_AUTH_MODE=oauth` (default, recommended)
|
||||
- `OPENSKY_AUTH_MODE=auto` (OAuth first, then Basic fallback)
|
||||
- `OPENSKY_AUTH_MODE=basic`
|
||||
- `OPENSKY_AUTH_MODE=auto` (OAuth when configured, otherwise anonymous)
|
||||
- `OPENSKY_AUTH_MODE=anon`
|
||||
|
||||
`OPENSKY_AUTH_MODE=basic` is retired (see below).
|
||||
|
||||
Reference: OpenSky REST API docs recommend OAuth2 Client Credentials flow.
|
||||
|
||||
## Quick Start (OAuth Client JSON)
|
||||
@@ -41,13 +42,18 @@ Launchers resolve OAuth creds in this order:
|
||||
2. `OPENSKY_CREDENTIALS_FILE`
|
||||
3. Keychain (`opensky-network` / `client_id`, `client_secret`)
|
||||
|
||||
## Basic Auth (Legacy)
|
||||
The server itself reads 1 and 2, so `npm run dev`, `npm run preview` and
|
||||
Pinokio also work with only `OPENSKY_CREDENTIALS_FILE` set in `.env`, on any
|
||||
platform. It reads the file on each token refresh.
|
||||
|
||||
Basic auth is still supported for now, but OpenSky is moving away from it.
|
||||
## Basic Auth (Retired)
|
||||
|
||||
```bash
|
||||
OPENSKY_AUTH_MODE=basic ./scripts/dev-fresh.sh
|
||||
```
|
||||
OpenSky stopped accepting username/password (HTTP Basic) sign-in on
|
||||
2026-03-18; its API now takes only OAuth2 client credentials. With
|
||||
`OPENSKY_AUTH_MODE=basic` the server no longer sends `OPENSKY_USERNAME` /
|
||||
`OPENSKY_PASSWORD`: it requests anonymously (reason `basic_retired`) and
|
||||
logs once what to set instead. Create an API client on your OpenSky
|
||||
account page and use its `credentials.json` as above.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -61,6 +67,6 @@ Useful reasons:
|
||||
|
||||
- `oauth_invalid_or_missing`: OAuth client not found/usable
|
||||
- `oauth_invalid_credentials`: OAuth client rejected by OpenSky
|
||||
- `basic_invalid_credentials`: username/password rejected
|
||||
- `missing_basic_creds`: basic mode missing user/pass
|
||||
- `missing_oauth_creds`: auto mode found no OAuth client
|
||||
- `basic_retired`: basic mode requested; answered anonymously
|
||||
- `rate_limited`: OpenSky throttling
|
||||
+24
-15
@@ -12,7 +12,7 @@ A rail panel uses an outer owner with its existing stable `id`, matching
|
||||
`data-panel-id`, and `panel-collapsible` class. Its first non-glow child is the
|
||||
surface. Keep the header and any outward popup outside the scrolling body:
|
||||
|
||||
```html
|
||||
```vue
|
||||
<section
|
||||
id="example-panel"
|
||||
data-panel-id="example-panel"
|
||||
@@ -21,14 +21,11 @@ surface. Keep the header and any outward popup outside the scrolling body:
|
||||
<div data-panel-surface>
|
||||
<header data-panel-header>
|
||||
<h2 class="panel-title panel-surface-title">Example</h2>
|
||||
<button
|
||||
class="panel-collapse-btn panel-surface-control"
|
||||
data-collapse-target="example-panel"
|
||||
aria-expanded="true"
|
||||
aria-label="Collapse Example"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<PanelCollapseButton
|
||||
class="panel-surface-control"
|
||||
target="example-panel"
|
||||
:name="t('Example')"
|
||||
/>
|
||||
</header>
|
||||
<div data-panel-body data-rail-scroller>
|
||||
<label>Filter <input class="panel-surface-control" type="text" /></label>
|
||||
@@ -92,15 +89,27 @@ Do not copy the rail example's `.panel-collapse-btn` class into the dock:
|
||||
existing dock rules intentionally hide that class. The generic surface skin
|
||||
does not replace the dock's disclosure policy.
|
||||
|
||||
The existing `PanelChrome` binds `.panel-collapse-btn[data-collapse-target]`
|
||||
at initialization and routes state through `setPanelCollapsed`. For additions
|
||||
A panel with Core's standard header (glow, title, badges, divider, actions
|
||||
and collapse button) is a `RailPanel` (`src/ui/rail/`): the kit's `UiPanel`
|
||||
bound to the `gev-panel-chrome` store, with its state classes drawn by
|
||||
`v-panel-state` and its button's click asking `PanelChrome` to toggle it.
|
||||
A panel with its own header uses `PanelCollapseButton`, which renders the
|
||||
collapse button (`.panel-collapse-btn`) from the open state `PanelChrome`
|
||||
presents to that store; until then, both show their `startCollapsed` state,
|
||||
which should match the markup's `collapsed` class.
|
||||
Either way, the click reaches `PanelChrome`'s `setPanelCollapsed`. `PanelChrome` collects the buttons'
|
||||
targets at initialization and binds each panel's Escape. For additions
|
||||
present at startup, keep those hooks and the existing chrome lifecycle. A
|
||||
dynamically mounted panel must bind and dispose its own disclosure using
|
||||
`bindPanelDisclosure` / `collapsePanelOnEscape` from `panelDisclosure.js`, with
|
||||
the application owning state changes. Do not rerun the whole application chrome
|
||||
dynamically mounted panel must own its disclosure: its own state, button and
|
||||
Escape, bound and disposed with `bindPanelDisclosure` /
|
||||
`collapsePanelOnEscape` from `panelDisclosure.js` (which bind Escape only). Do not rerun the whole application chrome
|
||||
initializer just to add one panel. `layoutLeftPanelRail` and
|
||||
`layoutRightPanelRail` accept arbitrary direct `data-panel-id` children; their
|
||||
host owns layout scheduling, observations and collapse callbacks. Dynamic
|
||||
host owns layout scheduling, observations and collapse callbacks. A rail
|
||||
never writes a panel's classes: it calls `onCollapse(panel, {collapsed,
|
||||
auto})`, and the host draws that state (the application presents it to the
|
||||
panel chrome store, which each root's `v-panel-state` draws at once, so the
|
||||
rail can measure the panel in the same task). Dynamic
|
||||
content that changes size may require an owner-scheduled layout pass or an
|
||||
owned `ResizeObserver`; adding CSS attributes does not register new observers.
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Arabic (`ar`)
|
||||
|
||||
Catalog: `src/i18n/locales/ar.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation sources: abiyeenzo (#915).
|
||||
|
||||
## Style
|
||||
|
||||
- Modern Standard Arabic, neutral across regions. Address the user with plain imperatives: «انقر»، «فعّل»، «أعد المحاولة».
|
||||
- Arabic has no letter case: an UPPERCASE English HUD label becomes the same short Arabic label. Keep labels as short as the English: «مباشر»، «متقادم»، «متوقف».
|
||||
- Status words in progress use «جارٍ …» («جارٍ التحميل»، «جارٍ الضبط»، «… جارٍ تشغيل اللقطة»), so a status chip never reads as the command next to it («ضبط» is the TUNE button); finished actions use the passive («تم الحفظ»، «أُلغي التحميل»). Data that refreshes itself «يُحدَّث» (يتحدّث means "speaks"). A sentence about a `{placeholder}` whose gender and number vary (tool, layer and cohort names, call signs, vessel names) stays impersonal: «{layers}: تم التفعيل للتو»، ««{name}» قيد الاستخدام»، «{id}: لا بيانات واردة الآن»، «لم يعد {name} ضمن البيانات المحمّلة».
|
||||
- A label followed by its value takes a colon, so it does not read as a sentence: «الصوت: نشط»، «السياق: {value}»، «الاحتواء: {percent}%». A named mode or control quoted in a sentence takes «»: «يظهر في وضع «كثيف» فقط».
|
||||
- Arabic punctuation in Arabic text: ، ؛ ؟ and «» for quotes. Keep `·` as the HUD separator, `—` with spaces, and `…`.
|
||||
- Arrows that show a sequence point the way Arabic reads: «الإشارات ← الأخبار ← المحلي», «{origin} ← {destination}».
|
||||
- Product names, provider names and codes stay in Latin letters: Google, Cesium ion, CelesTrak, NOAA, FIRMS, AIS, ADS-B, GNSS, CCTV, HUD, CRT, NVG, FLIR, WHOT/BHOT, Ironbow, Street View, Mapillary. Aviation and camera abbreviations stay Latin too: HDG, FOV, BRG, CRS, GS, TRK, V/S, SQUAWK. Local place names from the Bhote Koshi scene (Rasuwagadhi, Bidur, Timure…) stay Latin, and a site known by two names takes one Arabic noun in front: «جسر Mailung / Upper Trishuli»; well-known cities and countries are written in Arabic (أوستن، طوكيو، نيبال).
|
||||
- Months are the international names used across the Arab world: يناير، فبراير … أغسطس.
|
||||
|
||||
## Digits and direction
|
||||
|
||||
- **Western digits (0–9)**, as in the catalog. The HUD reads like an instrument panel, and mixing ٠–٩ with Latin codes and coordinates misreads. `intlLocale('ar')` in `src/i18n/locale.js` passes `ar-u-nu-latn` to every Intl formatter, so browsers that default to Arabic-Indic digits for an Arabic region still show 0–9.
|
||||
- **Right to left.** `ar` is in `RTL_LOCALES`, so the page gets `<html dir="rtl">`. The title, style indicator, both panel stacks and their icon rails swap sides; the dock and the corner row reverse. The intel HUD (classification block, MGRS, coordinates, GSD) stays a left-to-right instrument overlay; its translated lines read right to left inside it. FM seek and the radio dial stay left to right, as on any receiver. The rails' screen-reader names follow the mirrored layout: «Left panels» is «اللوحات اليمنى». Layout rules: `src/ui/styles/rtl.css`.
|
||||
- Latin runs inside Arabic sentences (product names, codes, `{placeholders}` filled with English data) are isolated by the bidi algorithm; write them as plain text, without manual direction marks.
|
||||
- Position counters: a translated counter says «من» («القناة {index} من {total}», «الإطار {index} من {total}»); a spaced slash between two isolated numbers swaps them in right-to-left text. Counters the code builds from bare numbers (`01 / 25`, `1 / 3`) are shown left to right by `rtl.css`.
|
||||
- Code stays left to right: the JSON fields of the scene editor are LTR in every language.
|
||||
- Comparison signs `<` and `>` are mirrored glyphs in right-to-left text («< 2%» shows as «> 2%»). Write them as words: «أقل من 2%»، «أكثر من 10%»، «أقل من ساعة». A legend's open upper bound («300+», «-40») is isolated by `formatScaleLabel()`, so its sign stays where it is written; `formatScaleRange()` isolates the unit after the range too («°C», not «C°»).
|
||||
|
||||
## Units
|
||||
|
||||
Distances and speeds take Arabic abbreviations after the number, with a space: كم، م، كم/س، كم/ث، م/ث، ميل/س، عقدة (knots)، قدم (feet)، مم. Time: ث (seconds)، د (minutes)، س (hours)، يوم; compact age labels («3d», «5D AGO») use ي: «{count} ي»، «قبل {count} ي», since a plain string cannot make يوم agree with the count. Area: آكر (acres, the value is not converted; فدان is a different unit). Money: the word دولار after the amount, not a bare `$`: «{amount} مليون دولار». Scientific and radio units stay Latin: MHz، dB، dBFS، dBZ، hPa، MW، MiB. `°` and `%` stay. Hemisphere letters in coordinates: ش (N)، ج (S)، ق (E)، غ (W), as in `21.33ش`.
|
||||
|
||||
## Plurals
|
||||
|
||||
`Intl.PluralRules('ar')` uses all six CLDR categories, and a `tn()` entry needs every one:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | --------------------------- | ------------- |
|
||||
| `zero` | 0 | 0 مصدر |
|
||||
| `one` | 1 | مصدر واحد (1) |
|
||||
| `two` | 2 | مصدران (2) |
|
||||
| `few` | 3–10, 103–110… | 3 مصادر |
|
||||
| `many` | 11–99, 111–199… | 11 مصدرًا |
|
||||
| `other` | 100–102, 200–202, fractions | 100 مصدر |
|
||||
|
||||
Every form must keep `{count}`. For one and two, Arabic names the quantity with the noun itself (مصدر واحد، مصدران), so the number follows in parentheses. `few` takes the plural noun; `many` the accusative singular (مصدرًا، مفتاحًا); `zero` and `other` the genitive singular. When a word next to the number cannot agree in every form, or a short chip has no room, put the number after a colon instead: «القريبة: {count}»، «الملفات المحددة: {count}». Where neither fits, «من أصل {total} من السجلات» agrees with any number. A standalone unit label («الدقائق») is never glued after a number: the voice-timeout options («3 دقائق»، «15 دقيقة») come from `Intl.NumberFormat` unit formatting, not from the catalog.
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | العربية |
|
||||
| ------------------------------------------ | ---------------------------------------------------------- |
|
||||
| layer / data layer | طبقة / طبقة بيانات |
|
||||
| feed / source | مصدر بيانات (short: مصدر) / مصدر |
|
||||
| LIVE (status) / live data | مباشر / البيانات المباشرة (not حي) |
|
||||
| Live ships, live detections (descriptions) | بيانات مباشرة عن السفن، رصد مباشر (not the adverb مباشرةً) |
|
||||
| track, tracking | تتبّع (always with the shadda) |
|
||||
| SIMULATED | محاكى (status) / محاكاة |
|
||||
| stale / degraded / unavailable | متقادم / متدهور / غير متاح |
|
||||
| ON / OFF (state) | مفعّل / متوقف |
|
||||
| retry | إعادة المحاولة |
|
||||
| key (API key) / POWER UP | مفتاح (مفتاح API) / تشغيل |
|
||||
| Provider Settings | إعدادات المزوّدين |
|
||||
| globe / map / map source / basemap | الكرة الأرضية / الخريطة / مصدر الخريطة / الخريطة الأساسية |
|
||||
| globe map (map source drawn on the globe) | خريطة على الكرة الأرضية (not خريطة كروية) |
|
||||
| tiles | بلاطات |
|
||||
| aircraft / flight / military flights | طائرة / رحلة / الرحلات العسكرية |
|
||||
| Live Flights / Live Vessels (layers) | تتبّع الرحلات / السفن المباشر (not الرحلات المباشرة) |
|
||||
| call sign | رمز النداء |
|
||||
| vessel, ship | سفينة |
|
||||
| satellite | قمر صناعي |
|
||||
| launch (rocket) / space mission | إطلاق / مهمة فضائية |
|
||||
| contact (tracked object) | هدف |
|
||||
| installation, site / mapped | منشأة، موقع / مرسوم |
|
||||
| camera / CCTV camera | كاميرا / كاميرا CCTV |
|
||||
| earthquake / active fire / fire perimeter | زلزال / حريق نشط / محيط حريق |
|
||||
| containment (wildfire) | احتواء (not السيطرة) |
|
||||
| cyclone advisory | نشرة إعصار |
|
||||
| scene / shot / scene recipe | مشهد / لقطة / قالب المشهد |
|
||||
| briefing / panel / dock | إحاطة / لوحة / الشريط السفلي |
|
||||
| preset / visual preset | إعداد مسبق / نمط بصري |
|
||||
| Scope (circular viewport mask) / keyhole | المنظار / الفتحة |
|
||||
| Clean UI / EXIT CLEAN VIEW | واجهة نظيفة / الخروج من الواجهة النظيفة (not العرض) |
|
||||
| cockpit | القمرة (قمرة القيادة) |
|
||||
| heading / altitude / ground speed | الاتجاه (HDG) / الارتفاع / السرعة الأرضية |
|
||||
| Street Level / Street View | مستوى الشارع / Street View |
|
||||
| Recent Imagery | الصور الحديثة |
|
||||
| Global Context / Context | السياق العالمي / السياق |
|
||||
| radio station / tuner / gain | محطة / موالف / الكسب |
|
||||
| broadcaster (radio stream) | محطة البث (not المحطة المُذيعة) |
|
||||
| VOLUME (slider) / VOICE | مستوى الصوت / الصوت |
|
||||
| decoder (SDR) | مفكّك |
|
||||
| transit / bike share | النقل العام / الدراجات المشتركة |
|
||||
| Previous / Next | السابق / التالي |
|
||||
| compass points (N, NNE, …) | شمال، شمال شمال شرق، … |
|
||||
| forecast / valid (time) / issued | توقع / عند، وقت التوقع (not صالح حتى) / صدر |
|
||||
| observation / observed | رصد / مرصود |
|
||||
| heard (aircraft received by the SDR) | الملتقطة |
|
||||
| upstream (camera feed) | المصدر الأصلي (in every CCTV message, not bare المصدر) |
|
||||
| field (weather) / model | حقل / نموذج |
|
||||
| parameters / ellipsoid | المعلمات / المجسّم الإهليلجي |
|
||||
| scene cloud | غيوم المشهد |
|
||||
| box (Recent Imagery area) / pin | منطقة / تثبيت |
|
||||
| width (box size) / view (current camera) | اتساع / العرض الحالي (عرض alone reads as display) |
|
||||
| N/A | غير متاح (no abbreviation) |
|
||||
| METRO (view band) / REGIONAL | حضري (not حاضرة) / إقليمي |
|
||||
| THIS (voice pointer) | هذا |
|
||||
| outline / mark (map annotation) | مخطط / علامة، تعليم |
|
||||
| share (a view or link) | مشاركة |
|
||||
| AI agent | وكيل ذكاء اصطناعي |
|
||||
| spacecraft | مركبة فضائية (مركبة alone is any vehicle) |
|
||||
| stage (rocket) / downrange | مرحلة / على امتداد المسار |
|
||||
| landing position: CONFIRMED / PAD (RTLS) | مؤكَّد / منصة الإطلاق |
|
||||
| recovery (rocket stage) | استعادة (not استرجاع) |
|
||||
| Arrive at your destination (route step) | الوصول إلى وجهتك (a step, not وصلت) |
|
||||
| forecast lead hours | مدة التوقع بالساعات (not ساعات المهلة) |
|
||||
| cel-shaded | بتظليل كرتوني |
|
||||
| (debris-)dammed lake | بحيرة خلف سدّ (ركامي) |
|
||||
| frame (bring into view) | إظهار في الإطار |
|
||||
| story beat | مرحلة القصة |
|
||||
@@ -0,0 +1,108 @@
|
||||
# German (`de`)
|
||||
|
||||
Catalog: `src/i18n/locales/de.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: abiyeenzo (#908).
|
||||
|
||||
## Style
|
||||
|
||||
- Standard German that reads naturally in Germany, Austria and Switzerland; ß where it belongs (Straße, schließen), no Swiss ss.
|
||||
- No direct address: neither «Sie» nor «du». Actions and hints use the neutral infinitive: «Erneut versuchen», «Kamera anklicken zum Aktivieren», «Zuerst eine Aufnahme wählen», «Mikrofonzugriff erlauben und erneut versuchen». "Your" becomes the article or «eigene»: «Eigener RTL-SDR-Empfänger», «Die IP-Adresse ist für diesen Sender sichtbar».
|
||||
- Nouns are capitalized, as German requires; otherwise sentence case, not Title Case: "Live Flights" → «Live-Flüge», "Provider Settings" → «Anbieter-Einstellungen». UPPERCASE English stays UPPERCASE: «LIVE-DATEN AUS», «KEINE ENTWARNUNG». In UPPERCASE, ß is written SS: «SCHLIESSEN», «STRASSENEBENE».
|
||||
- Compounds are written as one word or with a hyphen next to a name, code or English term: «Datenebene», «Wetterkacheln», «TomTom-Schlüssel», «AIS-Schiffe», «Street-View-Fallback». Do not split them with spaces.
|
||||
- Short labels, chips and HUD readouts stay short. German runs about a third longer than English, so prefer a short word or a standard abbreviation with a period over a wrapped line: REICHW., GESCHW., NIEDERSCHL., SONST., NACHR., VERST., VGL., KAL., KOMM. (a bare «KOMM» reads as the imperative)
|
||||
- The avionics codes HDG, BRG, CRS, DEST, SPD, ALT, GS, TRK, V/S, FOV and SQUAWK stay in English, as in the cockpit. Words used in a sentence are translated: heading → «Kurs», bearing → «Peilung», range → «Reichweite».
|
||||
- A dash between clauses is a spaced en dash « – »; a colon is fine where it explains: «Feed ausgefallen – neue Versuche in größeren Abständen», «SIMULIERT: Verkehrsdienst nicht erreichbar». Quotes are „…“.
|
||||
- Status and waiting messages use the neutral infinitive too: «Warten auf Aktualisierung», not «Warte auf …». Reasons inserted after a dash stay lower case: «Szene nicht gespeichert – das Projekt ist größer als 5 MiB».
|
||||
- A layer named in a sentence takes its panel name in quotes: «Die Ebene „Live-Flüge“ ist nicht aktiviert», «die Ebene „Live-Flüge“ lädt Daten für den jeweiligen Bildausschnitt».
|
||||
- Rolling time windows say their length, so they cannot read as the previous calendar period: LAST YEAR → «LETZTE 12 MONATE», LAST MONTH → «LETZTE 30 TAGE».
|
||||
- Keyboard keys are named as on German keyboards where they differ: Leertaste, Rücktaste, Umschalt; Esc, Enter, Tab and letter keys stay.
|
||||
- Decimal comma in fixed text: «M4,4», «28,2710° N». Thousands separator is a period: «1.000 km». Formatted numbers come from `Intl` and are already German.
|
||||
- Units: km, m, km/h, m/s, kn (knots, also in the cockpit), ft (feet), hPa, MW, h, min, s, T (days, in short readouts); in UPPERCASE text KM, KM/H, KN, FT, MIN, H, T («VOR {count} H»). A space between number and unit, in compact readouts too: «{value} m», «{count} min», «50 %», «{speed} KN», «{count} / 30 T». `°` sticks to the number.
|
||||
- "ago" goes before the time: «vor {count} min», «vor {value}». When a placeholder already holds a localized "ago" text (`{age}` in `STALE · cached {age}`, `{ago}`), do not add «vor» again.
|
||||
- Compass points use O for Ost: N, NNO, NO, ONO, O, OSO, SO, SSO, S, SSW, SW, WSW, W, WNW, NW, NNW; hemisphere letters N, S, O, W.
|
||||
- Routing: direction words are lowercase adverbs (links, leicht rechts, scharf links, geradeaus), so a sentence never starts with one: «Auf {road} {direction} abbiegen», «Hier {direction} abbiegen», «Weiter {direction}». «Hier», not «Dann»: the step card already says «dann {leg}» and «Nächster Schritt: …». At a fork the verb is «weiter», which also works with «geradeaus»: «An der Gabelung {direction} weiter». Roundabout exits are ordinals with a period: «die {exit} Ausfahrt» → «die 2. Ausfahrt». A lane is named by where it leads, not by its position: «Die geradeaus führende Spur benutzen», «die leicht nach links führende», «die scharf nach rechts führende» (not «halblinke» or «äußerst linke»).
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs two forms. `Intl.PluralRules('de')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | --------------------------- | --------- |
|
||||
| `one` | 1 | 1 Kamera |
|
||||
| `other` | everything else: 0, 2, 1,5… | 3 Kameras |
|
||||
|
||||
Keep the verb in agreement: «{count} Station meldet» / «{count} Stationen melden». Abbreviated units and codes do not change: «{count} NACHR.», «{count} EMPFANGEN».
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Deutsch |
|
||||
| ---------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| layer / data layer | Ebene / Datenebene |
|
||||
| feed / source / data source / input (cockpit) | Feed / Quelle / Datenquelle / Quelle |
|
||||
| LIVE (status) / live data | LIVE / Live-Daten |
|
||||
| SIMULATED | SIMULIERT |
|
||||
| stale / degraded / unavailable | veraltet / eingeschränkt / nicht verfügbar |
|
||||
| retry / retrying / retrying slowly | neuer Versuch / erneut versuchen / neue Versuche in größeren Abständen |
|
||||
| fallback | Fallback |
|
||||
| key (API key) / POWER UP / POWERED UP | Schlüssel / STARTKLAR / HOCHGEFAHREN |
|
||||
| Provider Settings | Anbieter-Einstellungen |
|
||||
| ON / OFF (state chips) | EIN / AUS |
|
||||
| enable / disable | aktivieren / deaktivieren |
|
||||
| globe / map / map source / map stack / basemap | Globus / Karte / Kartenquelle / Kartenstapel / Basiskarte |
|
||||
| aircraft / flight / military flights | Luftfahrzeug (HUD and short labels: Flugzeug) / Flug / Militärflüge |
|
||||
| vessel, ship | Schiff |
|
||||
| satellite / shell (Starlink) / DENSE | Satellit / Schale / DICHT |
|
||||
| launch (rocket) / launch site / space mission | Start / Startplatz / Raummission |
|
||||
| ascent / replay / payload / stage / downrange | Aufstieg / Replay, Wiedergabe / Nutzlast / Stufe / Downrange |
|
||||
| contact / tracking | Kontakt / Verfolgung |
|
||||
| installation / site | Anlage / Standort |
|
||||
| mapped / observed | kartiert / beobachtet (Bhote chip OBSERVED IMAGERY: REALE BILDDATEN) |
|
||||
| camera / CCTV camera / camera badge | Kamera / CCTV-Kamera / Kamera-Markierung |
|
||||
| earthquake / active fire / fire perimeter | Erdbeben / aktiver Brand / Brandgrenze |
|
||||
| cyclone / advisory (NHC/CPHC) / cone | Wirbelsturm / Lagebericht (not «Warnung») / Kegel |
|
||||
| issued (advisory, forecast) | ausgegeben |
|
||||
| radio station | Sender |
|
||||
| broadcast FM | UKW |
|
||||
| road / flow (traffic) / flow coverage | Straße / Verkehrsfluss / Live-Abdeckung |
|
||||
| transit / bike share | ÖPNV / Bikesharing |
|
||||
| scene / shot / scene recipe | Szene / Aufnahme / Szenenrezept |
|
||||
| capture shot / camera move | Aufnahme speichern / Kameraflug |
|
||||
| anchor / data pack / bundle | Anker / Datenpaket / Bundle |
|
||||
| story beat / evidence beat | Story-Schritt / Beweisschritt |
|
||||
| briefing / panel / dock | Briefing / Bereich / Dock |
|
||||
| cockpit | Cockpit |
|
||||
| preset / visual preset | Preset / visuelles Preset |
|
||||
| Scope (circular viewport mask) / keyhole | Scope / Schlüsselloch |
|
||||
| feather / fade | Weichzeichnung / Ausblenden |
|
||||
| overlay / detection overlay | Overlay / Erkennungs-Overlay |
|
||||
| Clean UI | Reine Ansicht |
|
||||
| Night vision / Thermal / Snow (cockpit styles) | Nachtsicht / Wärmebild / Schnee |
|
||||
| heading / altitude / ground speed | Kurs / Höhe / Geschwindigkeit über Grund (short: GESCHW. ÜBER GRUND) |
|
||||
| HDG / BRG / CRS / DEST / SPD / FOV | unchanged |
|
||||
| RANGE / PITCH / HGT (CCTV calibration chips) | REICHW. / NEIGUNG / HÖHE |
|
||||
| PREV / NEXT | ZURÜCK / WEITER |
|
||||
| RESUME (radio transport button) | FORTSETZEN (not «WEITER», which is NEXT in the same row) |
|
||||
| nearest (spatial) | nächstgelegen (always; «nächste» is NEXT) |
|
||||
| expand / collapse (sections) | ausklappen / einklappen |
|
||||
| L / R (relative bearing) | L / R |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | SIG / NEWS / LOKAL |
|
||||
| NOT AN ALL-CLEAR | KEINE ENTWARNUNG |
|
||||
| Street Level / Street View (Google) | Straßenebene / Street View |
|
||||
| Recent Imagery / box / pin (a day) | Aktuelle Bilddaten / Bereich / fixieren |
|
||||
| frame (bring into view) | ins Bild nehmen |
|
||||
| mark (map annotation) / outline | Markierung / Umriss |
|
||||
| voice (control) / voice session | Sprachsteuerung / Sprachsitzung |
|
||||
| YOU / THIS / HERE (voice card labels) | EINGABE / DIES / HIER |
|
||||
| click / click to … | anklicken / … klicken |
|
||||
| tap (touch) | Antippen (not «Tipp», which is a hint) |
|
||||
| press / tap a key | Taste drücken / kurz drücken (never «tippen» for keys) |
|
||||
| tilted view | Schrägansicht |
|
||||
| card (host card) / voice card | Eintrag (in hints), Infokarte / Sprachkarte |
|
||||
| label (on the map) / clear (switch off layers) | Beschriftung / ausschalten (not «löschen») |
|
||||
| station tag (radio filter) | Kategorie |
|
||||
| DIRECTORY BAND / {category} BAND (radio dial) | SENDERSKALA / SENDERSKALA · {category} (not «-BAND»: «ROCK-BAND») |
|
||||
| MIC (dock button) | MIKRO |
|
||||
| frame (weather, video) / image | Frame / Bild |
|
||||
| collapse / landslide (Bhote Koshi) | Erdrutsch (not «Abbruch», which reads as "cancel") |
|
||||
| website (radio station) | Website (SENDER-WEBSITE; «Standort» is a physical site) |
|
||||
@@ -0,0 +1,75 @@
|
||||
# Spanish (`es`)
|
||||
|
||||
Catalog: `src/i18n/locales/es.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
## Style
|
||||
|
||||
- Neutral Spanish that reads naturally in Spain and Latin America; avoid regionalisms.
|
||||
- Address the user with informal "tú" imperatives: «Añade», «Activa», «Elige», «Pega», «Inténtalo de nuevo». Tooltips that name an action may use the infinitive: «Expandir el panel», «Restablecer la cámara».
|
||||
- Sentence case, not Title Case: "Live Flights" → «Vuelos en vivo». UPPERCASE English stays UPPERCASE: «EN VIVO», «SIN DATOS».
|
||||
- Quotes inside Spanish text are straight double quotes: «¿Eliminar el plano "{shot}"?». Questions and exclamations open with ¿ and ¡.
|
||||
- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: ANT., SIG., VEL., DIST., SINCR., cám., cob.
|
||||
- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD, RANGE and ALT stay in English everywhere, as in cockpit avionics. Words used in a sentence are translated: heading → «rumbo», range → «alcance».
|
||||
- Keyboard keys stay in English: Space, Esc, Enter, Tab, Shift, H, 1–9 («Mantén pulsado Space para hablar»).
|
||||
- Units: km, m, km/h, m/s, kn (knots), ft (feet), h, min, s; in UPPERCASE text KM, KM/S, KN, FT, MIN, H. "m" is always metres, so minutes are «min» («hace 5 min»). MW stays MW. `°` and `%` stay.
|
||||
- "ago" goes before the time: «hace {count} min». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «hace» again.
|
||||
- Spelling: «costo» (not «coste»).
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs three forms. `Intl.PluralRules('es')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | -------------------------------------- | -------------------- |
|
||||
| `one` | 1 | 1 cámara |
|
||||
| `many` | 1 000 000, 2 000 000… (exact millions) | 1 000 000 de cámaras |
|
||||
| `other` | everything else: 0, 2–999 999, 1.5… | 3 cámaras |
|
||||
|
||||
The `many` form is the `other` text with «de» after the number: «{count} de cámaras cargadas», «{count} DE CLAVES EN ESPERA».
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Español |
|
||||
| --------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| layer / data layer | capa / capa de datos |
|
||||
| feed / source / data source | fuente / fuente / fuente de datos |
|
||||
| LIVE (status) / live data | EN VIVO / datos en vivo |
|
||||
| SIMULATED | SIMULADO |
|
||||
| stale / degraded / unavailable | desactualizado / degradado / no disponible |
|
||||
| retry / retrying | reintento / reintentando |
|
||||
| key (API key) / POWER UP / POWERED UP | clave / ENCENDER / ENCENDIDO |
|
||||
| Provider Settings | Ajustes de proveedores |
|
||||
| ON / OFF (state chips) | ACTIVO / INACTIVO (short enough for the layer pill) |
|
||||
| enable / disable | activar / desactivar |
|
||||
| globe / map / map source / map stack | globo / mapa / fuente del mapa / conjunto de mapas |
|
||||
| aircraft, plane / flight / military flights | aeronave / vuelo / vuelos militares |
|
||||
| vessel, ship | buque |
|
||||
| satellite / shell (Starlink) / DENSE | satélite / capa / DENSO |
|
||||
| launch (rocket) / launch site / space mission | lanzamiento / lugar de lanzamiento / misión espacial |
|
||||
| ascent / replay / payload / stage | ascenso / repetición / carga útil / etapa |
|
||||
| contact / tracking | contacto / rastreo |
|
||||
| installation / site | instalación / sitio |
|
||||
| mapped / observed | mapeado / observado |
|
||||
| camera / CCTV camera | cámara / cámara CCTV |
|
||||
| earthquake / active fire | terremoto / incendio activo |
|
||||
| radio station | emisora |
|
||||
| road / flow (traffic) | vía / flujo |
|
||||
| scene / shot / scene recipe | escena / plano / receta de escena |
|
||||
| briefing / panel / dock | información / panel / dock |
|
||||
| cockpit | cabina |
|
||||
| preset / visual preset | preajuste / preajuste visual |
|
||||
| Scope (circular viewport mask) / keyhole | mirilla / ojo de cerradura |
|
||||
| feather / fade | difuminado / desvanecimiento |
|
||||
| detection overlay | superposición de detección |
|
||||
| pin (Draw tool) | marcador |
|
||||
| Bloom / Glow (visual effect) | resplandor |
|
||||
| Cyber (HUD layout, effect) | Ciber |
|
||||
| heading, course / altitude / ground speed | rumbo / altitud / velocidad respecto al suelo (short: VEL. SUELO) |
|
||||
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | HDG / BRG / CRS / FOV / DEST / SPD / RANGE (unchanged) |
|
||||
| PREV / NEXT | ANT. / SIG. |
|
||||
| Previous / Next (button text) | Anterior / Siguiente |
|
||||
| L / R (relative bearing) | I / D |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | SIG / NOTICIAS / LOCAL |
|
||||
| PITCH / HGT (CCTV calibration chips) | INCL. / ALT. |
|
||||
| NOT AN ALL-CLEAR | NO IMPLICA TODO DESPEJADO |
|
||||
| click / click to … | haz clic / clic para … |
|
||||
@@ -0,0 +1,105 @@
|
||||
# French (`fr`)
|
||||
|
||||
Catalog: `src/i18n/locales/fr.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: abiyeenzo (#907); the French part of meirm's #312 was used for comparison.
|
||||
|
||||
## Style
|
||||
|
||||
- Standard French that reads naturally in France, Belgium, Switzerland and Canada; avoid regionalisms.
|
||||
- Address the user with "vous" imperatives: « Cliquez », « Activez », « Réessayez ». Tooltips that name an action use the infinitive: « Réduire le panneau », « Réinitialiser la caméra ». UPPERCASE HUD instructions also use the infinitive: « CLIQUER SUR UNE CAMÉRA POUR L'ACTIVER ».
|
||||
- Sentence case, not Title Case: "Live Flights" → « Vols en direct ». UPPERCASE English stays UPPERCASE, accents included: « ÉTAT », « PRÉC. ».
|
||||
- Typography: a narrow no-break space (U+202F) goes before `:` `;` `?` `!` and `%`, and inside « » quotes: `Supprimer le plan « {shot} » ?`. It keeps the sign on the same line as the word before it. Do not type a plain space there. The same narrow no-break space separates thousands in a number written into the text: « 1 000 km ». Ellipsis is `…`, not `...`. Dashes keep their spaces: « flux indisponible — réessayez ».
|
||||
- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: PRÉC., SUIV., RÉINIT., ENREG., INDISP., DIST.
|
||||
- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD and ALT stay in English everywhere, as in cockpit avionics. Words are translated: RANGE → « PORTÉE », heading in a sentence → « cap », ground-track course → « route » (« ROUTE 270° »).
|
||||
- ON and OFF stay as they are on HUD toggles and chips (« CCTV ON », « PROJECTION OFF »); in a sentence use « activé » / « désactivé ».
|
||||
- Keyboard keys stay in English: Space, Esc, Enter, Tab, Shift, Backspace, H, 1–9 (« Maintenez Space pour parler », « ESC pour fermer »).
|
||||
- Units: km, m, km/h, m/s, nd (knots), ft (feet), h, min, s, with a space before the unit: « 250 km », « il y a 5 min »; in UPPERCASE text KM, KM/S, ND, FT, MIN. MW, dBZ, hPa and `°` stay. Decimal comma in fixed numbers: « 28,2710° N », « M4,4 ». Money: « 12 k$ », « 3 M$ », « 2 Md$ ».
|
||||
- "ago" goes before the time: « il y a {count} min ». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add « il y a » again.
|
||||
- Round-about exit ordinals: 1re, 2e, 3e… (« prenez la 1re sortie »).
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs three forms. `Intl.PluralRules('fr')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | -------------------------------------- | -------------------- |
|
||||
| `one` | 0 and 1, fractions below 2 (1,5) | 1 caméra, 0 caméra |
|
||||
| `many` | 1 000 000, 2 000 000… (exact millions) | 1 000 000 de caméras |
|
||||
| `other` | everything else: 2–999 999, 2,5… | 3 caméras |
|
||||
|
||||
The `many` form is the `other` text with « de » (« d' » before a vowel) after the number: « {count} de couches effacées », « {count} D'ENTRÉES INCONNUES ». When the words after the number cannot agree (an UPPERCASE readout, a list), put the number after a colon in every form: « CAPTÉS : {count} ». The same goes for a layer name whose gender and number vary: « Couche activée : {label} », not « {label} activé », or a tool name: « Outil actif : {name} », not « {name} est actif ».
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Français |
|
||||
| ------------------------------------------------------------ | ----------------------------------------------------------------------- |
|
||||
| layer / data layer / DATA LAYERS (panel title) | couche / couche de données / COUCHES |
|
||||
| feed / source / data source | flux / source / source de données |
|
||||
| LIVE (status) / live data | DIRECT / données en direct |
|
||||
| SIMULATED | SIMULÉ |
|
||||
| stale / degraded / unavailable | obsolète / dégradé / indisponible |
|
||||
| retry / retrying (status: RETRYING X) | nouvel essai / réessayez (NOUVEL ESSAI : X) |
|
||||
| rate-limited | limite de requêtes atteinte |
|
||||
| key (API key) / POWER UP / POWERED UP | clé (clé API) / ALLUMER / ALLUMÉ |
|
||||
| Provider Settings | paramètres des fournisseurs |
|
||||
| enable / disable | activer / désactiver |
|
||||
| globe / map / map source / basemap / map stack | globe / carte / source de carte / fond de carte / fond de carte |
|
||||
| tile (map) | tuile |
|
||||
| aircraft / flight / military flights | aéronef (avion in short copy) / vol / vols militaires |
|
||||
| vessel, ship / tanker (AIS type) | navire / navire-citerne |
|
||||
| satellite / shell (Starlink) / DENSE | satellite / couche de la constellation / DENSE |
|
||||
| launch / launch site / space mission | lancement / site de lancement / mission spatiale |
|
||||
| ascent / replay / payload / stage / downrange | ascension / rejeu / charge utile / étage / distance au sol (DIST. SOL) |
|
||||
| contact / tracking / track (HUD) | contact / suivi / piste |
|
||||
| brackets (HUD corner brackets around contacts) | cadres de ciblage |
|
||||
| installation / site | installation / site |
|
||||
| mapped / observed | cartographié / observé |
|
||||
| camera / CCTV camera / ALPR camera | caméra / caméra CCTV / caméra LAPI |
|
||||
| earthquake | séisme |
|
||||
| active fire (FIRMS detection) / wildfire | feu actif / incendie |
|
||||
| contained / containment (wildfire) | circonscrit / part circonscrite (not « maîtrisé », which is controlled) |
|
||||
| advisory (NHC/CPHC cyclone) | bulletin |
|
||||
| forecast / lead hours / valid / issued | prévision / échéances / valide / émise |
|
||||
| AQHI (Canada) | CQAS (cote air santé) |
|
||||
| radio station / tune | station / régler |
|
||||
| bikeshare / transit | vélos partagés / transports en commun |
|
||||
| route (directions) / turn (maneuver) | itinéraire / manœuvre |
|
||||
| scene / shot / scene recipe | scène / plan / recette de scène |
|
||||
| bundle / data pack / anchor | archive / paquet de données / ancre |
|
||||
| import / export (noun: file, scene, project) | importation / exportation (not « import » / « export ») |
|
||||
| capture camera as anchor / set move start (scene editor) | ancre à la position de la caméra / vue actuelle comme départ |
|
||||
| story beat | étape du récit |
|
||||
| briefing / panel / dock | briefing / panneau / dock |
|
||||
| cockpit | cockpit |
|
||||
| preset / visual preset | préréglage / préréglage visuel |
|
||||
| Scope (circular viewport mask) / keyhole | lunette / ouverture |
|
||||
| detection overlay | surcouche de détection |
|
||||
| Clean UI | vue épurée |
|
||||
| Street Level (layer) / Street View (Google) | Niveau rue / Street View |
|
||||
| Recent Imagery / box | imagerie récente / zone |
|
||||
| Daily overview / overview (Recent Imagery VIIRS product) | vue quotidienne (one name for the product, not « vue d'ensemble ») |
|
||||
| pin (Draw tool) / pin (Recent Imagery) | épingle / épingler |
|
||||
| Cyber (HUD layout, effect) | Cyber |
|
||||
| heading / course (ground track) / altitude / ground speed | cap / route / altitude / vitesse sol |
|
||||
| HDG / BRG / CRS / FOV / DEST / SPD / ALT | unchanged |
|
||||
| RANGE / PITCH / HGT (CCTV calibration chips) | PORTÉE / INCL. / HAUT. |
|
||||
| PREV / PREVIOUS / NEXT (chips) | PRÉC. / PRÉC. / SUIV. |
|
||||
| Previous / Next (button text) | Précédent / Suivant |
|
||||
| L / R (relative bearing) | G / D |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | SIG / ACTUS / LOCAL |
|
||||
| compass points (N, NNE, …, W, NW) | N, NNE, …, O, NO |
|
||||
| NOT AN ALL-CLEAR | PAS UN FEU VERT |
|
||||
| AI agent | agent IA |
|
||||
| share (a view or link) | partager |
|
||||
| click / click to … | cliquez / cliquer pour … |
|
||||
| map heading (compass, north-up control) | orientation (de la carte) |
|
||||
| subject (cockpit / shared contact) | cible |
|
||||
| cloud cover / CLOUD (briefing readout) | nébulosité / NÉBULOSITÉ |
|
||||
| LEVEL (cockpit horizon guide) | HORIZON |
|
||||
| CALIBRATED / CURATED / RAW PRIOR (CCTV pose) | CALIBRÉE / VÉRIFIÉE / ESTIM. INITIALE |
|
||||
| DATASHEET / SPEC FOV (CCTV lens range) | FICHE TECHNIQUE / FOV CONSTRUCTEUR |
|
||||
| PORT TENDER (AIS type) | SERVITUDE PORT. (navire de servitude portuaire) |
|
||||
| ChatGPT sign-in (action) / opening sign-in / sign-in expired | connexion / page de connexion / la session a expiré |
|
||||
| authored shot (scene) | plan scénarisé |
|
||||
| report / record (USGS event card) / readout, reading | rapport / relevé (a measured value only) |
|
||||
@@ -0,0 +1,108 @@
|
||||
# Italian (`it`)
|
||||
|
||||
Catalog: `src/i18n/locales/it.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: abiyeenzo (#912).
|
||||
|
||||
## Style
|
||||
|
||||
- Standard Italian as in Italian software UIs. Address the user with informal "tu" imperatives: «Aggiungi», «Attiva», «Scegli», «Incolla», «Riprova». Tooltips that name an action use the same imperative: «Espandi il pannello», «Ripristina la camera».
|
||||
- Sentence case, not Title Case: "Live Flights" → «Voli live», "Space Missions" → «Missioni spaziali». UPPERCASE English stays UPPERCASE: «LIVE», «NON DISPONIBILE».
|
||||
- Quotes inside Italian text are straight double quotes: «Eliminare lo scatto "{shot}"?». Use the accented capitals (È, À) in UPPERCASE text: «VELOCITÀ», «CITTÀ».
|
||||
- Errors read as a statement, not a past participle on its own: "Search failed" → «Ricerca non riuscita»; "Could not …" → «Impossibile …». A dash that joins a cause and a hint becomes a colon: «Chiave API rifiutata: controlla AISSTREAM_API_KEY». The same goes for a label and its hint in a tooltip: «Direzione (bussola °): clic per digitare».
|
||||
- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: REGOLAZ., RILEV., INCL., ALT., VEL., STIM., PROIEZ., ESP.; PREC / SUCC for PREV / NEXT and PREVIOUS / NEXT.
|
||||
- The HUD codes HDG, BRG, CRS, FOV and DEST stay in English, as in cockpit avionics. RANGE is «PORTATA». Words used in a sentence are translated: heading → «prua» (aircraft, ships) or «direzione» (camera, buses, trams, trains), course → «rotta».
|
||||
- Keyboard keys stay in English: Space, Enter, Esc, Tab, Shift, Backspace, H, 1–9 («Tieni premuto Space per parlare», «Premi Enter per riprovare»).
|
||||
- Decimal comma in numbers written in the text, magnitudes included: «28,2710° N», «M5,2». Numbers formatted by the code follow `Intl` and need nothing.
|
||||
- Amounts: thousands, millions and billions are written out or abbreviated in Italian, not with K/M/B: «{amount} mila $», «{amount} mln $», «{amount} mld $».
|
||||
- Units: km, m, km/h, m/s, kn (knots), ft (feet), h, min, s; in UPPERCASE text KM, KM/S, KT, FT, MIN, H. "m" is always metres, so minutes are «min» («5 min fa»). MW stays MW. `°` and `%` stay.
|
||||
- "ago" goes after the time: «{count} min fa». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «fa» again.
|
||||
- A feature name used as a sentence subject gets a head noun so the verb agrees: «La modalità Missioni spaziali isola…», «Il pannello Impostazioni provider è disattivato…», «Il livello {layer} è attivo…», «Lo strumento {name} è attivo…». A short card title with a placeholder name puts the state after a colon: «{label}: attivato». The same goes for a placeholder list of names: «Attivazione recente: {layers}». A count in a plain `t()` entry (no plural forms) that can be 1 goes after a label, so nothing has to agree with it: «Record accettati: {accepted} su {raw}».
|
||||
- English loanwords that Italian UIs use as they are stay in English: feed, live, cockpit, briefing, preset, overlay, layout, snapshot, tile, provider, timeout, payload, bloom, scope.
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs three forms. `Intl.PluralRules('it')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | -------------------------------------- | ----------------------- |
|
||||
| `one` | 1 | 1 telecamera |
|
||||
| `many` | 1 000 000, 2 000 000… (exact millions) | 1 000 000 di telecamere |
|
||||
| `other` | everything else: 0, 2–999 999, 1,5… | 3 telecamere |
|
||||
|
||||
The `many` form is the `other` text with «di» after the number when a noun follows: «{count} di telecamere caricate», «{count} DI CHIAVI IN ATTESA». When no noun follows the number («{count} vicine», «{count} RICEVUTI»), `many` repeats `other`. Nouns that do not change in the plural keep one form for all three (`{count} file`, `{count} data center`), with «di» in `many`.
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Italiano |
|
||||
| --------------------------------------------- | ------------------------------------------------------------ |
|
||||
| layer / data layer | livello / livello dati |
|
||||
| feed / source / data source | feed / fonte / fonte dati |
|
||||
| LIVE (status) / live data / live (feed state) | LIVE / dati live / live |
|
||||
| SIMULATED | SIMULATO |
|
||||
| stale / degraded / unavailable | obsoleto / degradato / non disponibile |
|
||||
| retry / retrying | riprova / nuovo tentativo |
|
||||
| rate limited | limite di richieste (raggiunto) |
|
||||
| key (API key) / POWER UP / POWERED UP | chiave / ACCENDI / ACCESO |
|
||||
| Provider Settings | Impostazioni provider |
|
||||
| ON / OFF (state chips) | ON / OFF |
|
||||
| enable / disable | attivare / disattivare |
|
||||
| globe / map / map source / map stack | globo / mappa / fonte mappa / stack mappa |
|
||||
| basemap / box (Recent Imagery area) | mappa di base / riquadro |
|
||||
| aircraft, plane / flight / military flights | aereo / volo / voli militari |
|
||||
| vessel, ship | nave |
|
||||
| satellite / shell (Starlink) / DENSE | satellite / shell / DENSO |
|
||||
| SPARSE / BALANCED / DENSE (detection modes) | RADO / BILANCIATO / DENSO (agree with «overlay») |
|
||||
| broadband (Starlink shell) | a banda larga |
|
||||
| VISUAL (CelesTrak group) | VISIBILI |
|
||||
| launch (rocket) / launch site / space mission | lancio / sito di lancio / missione spaziale |
|
||||
| ascent / replay / payload / stage | salita / replay / payload / stadio |
|
||||
| contact / tracking | contatto / tracciamento |
|
||||
| installation / site | installazione / sito |
|
||||
| mapped / observed | mappato / osservato |
|
||||
| camera (view) / camera, CCTV camera | camera / telecamera, telecamera CCTV |
|
||||
| earthquake / active fire / fire perimeter | terremoto / incendio attivo / perimetro dell'incendio |
|
||||
| lightning strike / cloud (cover, %) | fulmine / nuvole («non singoli fulmini», «20% di nuvole») |
|
||||
| incident (wildfire, Type N) / scene incident | evento (Evento di tipo N, Incendio senza nome) / evento |
|
||||
| trigger record (USGS card) / OPEN SOURCE link | scheda / APRI LA FONTE |
|
||||
| advisory (NHC/CPHC cyclone) / cone | bollettino / cono di incertezza |
|
||||
| forecast lead hours (cyclone points) | scadenze di previsione in ore |
|
||||
| AIR / SEA (radio category) / ground station | ARIA / MARE / stazione di terra |
|
||||
| radio station / broadcaster | stazione radio / emittente |
|
||||
| CH (radio channel readout) | CH (unchanged) |
|
||||
| road / flow (traffic) | strada / flusso |
|
||||
| transit / bike share | trasporto pubblico / bike sharing |
|
||||
| last report (vehicle position) | ultima segnalazione |
|
||||
| scene / shot / scene recipe | scena / scatto / ricetta della scena |
|
||||
| anchor / data pack / bundle (scene) | ancora / pacchetto dati / pacchetto |
|
||||
| story beat | tappa del racconto |
|
||||
| briefing / panel / dock | briefing / pannello / dock |
|
||||
| cockpit | cockpit |
|
||||
| preset / visual preset | preset / preset visivo |
|
||||
| Tactical / Minimal / Cyber | Tattico / Minimale / Cyber |
|
||||
| Scope (circular viewport mask) / keyhole | scope / foro |
|
||||
| feather / fade | sfumatura / dissolvenza |
|
||||
| detection overlay | overlay di rilevamento |
|
||||
| pin (Draw tool) / mark | segnaposto / segno |
|
||||
| pin (Recent Imagery) | fissare |
|
||||
| Draw / Directions (tools) | Disegno / Indicazioni |
|
||||
| Street Level (layer) / Street View (Google) | Vista stradale / Street View |
|
||||
| Recent Imagery | Immagini recenti |
|
||||
| Global Context / Context (panel) | Contesto globale / Contesto |
|
||||
| heading, course / altitude / ground speed | prua, rotta / altitudine / velocità al suolo (VEL. AL SUOLO) |
|
||||
| altitude in short HUD captions / Alt unknown | QUOTA (QUOTA · FT) / Quota sconosciuta |
|
||||
| fallback (FALLBACK, Street View fallback) | riserva / di riserva (SUPERFICIE DI RISERVA) |
|
||||
| forecast track / advisory center (cyclones) | traccia prevista / centro del ciclone da bollettino |
|
||||
| witness source / authored (scene) | fonte testimoniale / predefinito, definito dall'autore |
|
||||
| explore manually | esplora manualmente |
|
||||
| HDG / BRG / CRS / FOV / DEST | HDG / BRG / CRS / FOV / DEST (unchanged) |
|
||||
| RANGE / PITCH / HGT (CCTV calibration) | PORTATA / INCL. / ALT. |
|
||||
| PREV / NEXT | PREC / SUCC |
|
||||
| Previous / Next (button text) | Precedente / Successivo |
|
||||
| Focus {target} (action, aria label) | Inquadra {target} (the FOCUS button stays FOCUS) |
|
||||
| L / R (relative bearing) | S / D |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | SIG / NOTIZIE / LOCALE |
|
||||
| compass points (N, NNE, …, W, NW) | N, NNE, …, O, NO (west is «O», ovest) |
|
||||
| NOT AN ALL-CLEAR | NON È UN VIA LIBERA |
|
||||
| weather field / model / valid / issued | campo / modello / valida / emessa |
|
||||
| click / click to … | fai clic / clic per … |
|
||||
@@ -0,0 +1,112 @@
|
||||
# Japanese (`ja`)
|
||||
|
||||
Catalog: `src/i18n/locales/ja.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: abiyeenzo (#913).
|
||||
|
||||
## Style
|
||||
|
||||
- Sentences use です/ます: 「キーを保存しました」, 「もう一度お試しください」. Labels, buttons and HUD chips are short nouns or verb stems without a final 。: 「再試行」, 「保存」, 「ライブ」, 「オフ」.
|
||||
- Japanese has no case: an UPPERCASE English label becomes the same short Japanese label. Codes, units and names stay in their own case (HDG, ADS-B, km).
|
||||
- Full-width punctuation in Japanese text: 、。:?!()「」. Quote names and titles with 「」: 「{shot}」を削除しますか? Keep `·` as the HUD separator, `…` for an ellipsis and `—`, `→`, `↗` as in English. A dash inside a sentence becomes 。 or :.
|
||||
- Button names cited in running text go in []: [次へ]を使ってください.
|
||||
- No space between Japanese and Latin letters, digits or a `{placeholder}`: 「AIS船舶」, 「Spaceを長押しで話す」, 「{count}件」, 「{name}を表示」.
|
||||
- Two exceptions keep the space: a Japanese label before its value in a readout (「雲量 {pct}%」, 「ステーション {id}」, 「高度 {altitude}」), and the state words in toggles (「CCTV オン」, 「ラジオ オフ」).
|
||||
- Waiting states are 「X待ち」, not 「Xを待機中」: 「観測データ待ち」, 「進路/予報円は第{number}報待ち」, 「RTL-SDRデバイスの接続待ち…」. 待機中 is for something on standby with no object: 「フィード待機中」, 「音声待機中」.
|
||||
- Key names keep their own case in running text and hints: Tab, Enter, Space, Esc (「Tabでプレビュー · Enter / Spaceで選択」). Text that CSS upper-cases shows them in capitals, as in English.
|
||||
- Loanwords are katakana with the long-vowel mark, as in Microsoft and Apple style: ユーザー, ブラウザー, ビューアー, プロバイダー, レイヤー, フィルター, モニター, パラメーター. Pick one katakana form per term and keep it (see the glossary).
|
||||
- Product, provider and dataset names stay in Latin script: Google Maps, Cesium ion, OpenSky, CelesTrak, NASA FIRMS, Sentinel-2, KartaView. Google's own product names use Google's Japanese, joined like any Latin word: Googleストリートビュー. Nepal place names in the Bhote Koshi scenes stay in Latin script, as in zh-CN; country and well-known city names are translated (ネパール, 東京, オースティン).
|
||||
|
||||
## Numbers, units and dates
|
||||
|
||||
- Units: km, m, km/h, m/s, kt, ft, mph, hPa, MW, dB, °, % stay as written, with the English spacing (「250 km」, 「{frp} MW」, 「95 kt」). Code-built cockpit distances follow this too (`cockpitUnitCase` skips the HUD upper-casing for ja, as for zh). The cockpit's own speed caption is the HUD code KT, in 対地速度 · KT and under the readout.
|
||||
- Number ranges keep the English en dash, not ~: 「2–6日ごと」, 「50–99%」, 「南緯25°–北緯80°」.
|
||||
- Time units are words written straight after the number: 秒, 分, 時間, 日 (「5分前」, 「{count}時間」, 「1時間5分」). `formatQuantity` and `formatRelativeTime` drop the space for Japanese unit words, so code-built values match the catalog.
|
||||
- Counters follow the thing counted: 機 (aircraft), 隻 (ships), 基 (satellites), 台 (cameras, vehicles), 局 (stations), 件 (records, results, events), か所 (sites), 個 (generic). Prefer a counter to a bare number.
|
||||
- Dates come from `Intl` and read year first: 2026年8月26日. Coordinates in prose are 北緯 / 東経; HUD hemisphere letters stay N, S, E, W.
|
||||
|
||||
## Plurals
|
||||
|
||||
`Intl.PluralRules('ja')` has one category, `other`: a `tn()` entry has only that form. Put the counter next to the number: 「航空機{n}機」, 「船舶{n}隻」, 「メッセージ {count}件」.
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | 日本語 |
|
||||
| ----------------------------------------- | --------------------------------------- |
|
||||
| layer / data layer | レイヤー / データレイヤー |
|
||||
| feed | フィード |
|
||||
| source / data source / attribution | ソース / データソース / 出典 |
|
||||
| LIVE (status) / live data | ライブ / ライブデータ |
|
||||
| SIMULATED | シミュレーション |
|
||||
| stale / degraded / unavailable | 更新遅延 / 性能低下 / 利用不可 |
|
||||
| cached (status) | キャッシュの(キャッシュの予報) |
|
||||
| PARTIAL (feed state) | 部分的 |
|
||||
| upstream (remote data source) | 配信元 |
|
||||
| retry | 再試行 |
|
||||
| key (API key) / POWER UP | キー(APIキー)/ パワーアップ |
|
||||
| Provider Settings | プロバイダー設定 |
|
||||
| globe / map / map source / basemap | 地球儀 / 地図 / マップソース / ベースマップ |
|
||||
| mapped (OSM-mapped sites) | マップ済み |
|
||||
| aircraft / flight / military flights | 航空機 / フライト / 軍用機 |
|
||||
| FLIGHT {count} (a rocket stage's nth flight) | {count}回目の飛行 |
|
||||
| vessel, ship | 船舶 |
|
||||
| satellite | 衛星 |
|
||||
| launch (rocket) / space mission | 打ち上げ / 宇宙ミッション |
|
||||
| stage (rocket) / downrange | 段 / ダウンレンジ |
|
||||
| contact (tracked object) | コンタクト |
|
||||
| installation, site | 施設 |
|
||||
| camera / CCTV camera | カメラ / CCTVカメラ |
|
||||
| earthquake / active fire / fire perimeter | 地震 / 火災検知 / 延焼範囲 |
|
||||
| contained / containment (fire) | 封じ込め / 封じ込め率 |
|
||||
| scene / shot | シーン / ショット |
|
||||
| briefing / panel / dock | ブリーフィング / パネル / ドック |
|
||||
| preset / visual preset | プリセット / ビジュアルプリセット |
|
||||
| Tactical / Minimal / Cyber | タクティカル / ミニマル / サイバー |
|
||||
| Scope (circular viewport mask) / keyhole | スコープ / キーホール |
|
||||
| Clean UI | クリーンUI |
|
||||
| cockpit | コックピット |
|
||||
| heading / altitude / ground speed | 機首方位 / 高度 / 対地速度 |
|
||||
| track (TRK, direction over the ground) | 対地針路 |
|
||||
| HDG / BRG / CRS / FOV / SPD / POS | そのまま(HUDコード) |
|
||||
| squawk | スコーク |
|
||||
| radio failure / emergency (7600 / 7700) | 通信機故障 / 緊急事態 |
|
||||
| map heading / North up | 方位 / 北を上に |
|
||||
| Street Level / Street View (Google) | ストリートレベル / ストリートビュー |
|
||||
| Recent Imagery | 最近の衛星画像 |
|
||||
| Global Context / Context (panel) | グローバルコンテキスト / コンテキスト |
|
||||
| radio (station) / tune / seek | ラジオ(局)/ 選局・同調 / シーク |
|
||||
| Public Safety / SAFETY (radio category) | 警察・消防 |
|
||||
| transit / bikeshare | 公共交通 / シェアサイクル |
|
||||
| occupancy: empty / standing room only / crushed | 乗客なし / 立ち乗りのみ / 非常に混雑 |
|
||||
| traffic flow (TomTom) | 交通流 |
|
||||
| Previous / Next | 前へ / 次へ |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | シグナル / ニュース / ローカル |
|
||||
| compass points (N, NNE, NE, ENE, …) | 北, 北北東, 北東, 東北東, … |
|
||||
| weather field / FIELD (selector) | 要素 |
|
||||
| weather model / forecast lead hours | モデル / 予報時間 |
|
||||
| valid (forecast time) / issued | 対象時刻 / 発表 |
|
||||
| radar reflectivity / lightning density | レーダー反射強度 / 雷密度 |
|
||||
| cyclone, storm (NHC/CPHC) / advisory | サイクロン / 情報(第N報); alone: サイクロン情報 |
|
||||
| potential tropical cyclone (NHC PC) | サイクロン化のおそれがある擾乱 |
|
||||
| forecast uncertainty cone | 予報円 |
|
||||
| tropical storm / tropical depression | 熱帯暴風雨 / 熱帯低気圧 |
|
||||
| scene cloud / mosaic / daily overview | シーン雲量 / モザイク / 日次概観 |
|
||||
| box (Recent Imagery area) | 範囲 |
|
||||
| WINDOW (search radius around a subject) | 範囲 |
|
||||
| coverage / coverage % / full, partial | カバー範囲 / カバー率(%のみ)/ 全体, 一部 |
|
||||
| pin (Recent Imagery) | 固定 |
|
||||
| clear day (Recent Imagery) | 雲の少ない日 |
|
||||
| outline / mark (map annotation) | 輪郭 / マーク |
|
||||
| share (a view or link) | 共有 |
|
||||
| import / export (project, presets) | インポート / エクスポート |
|
||||
| EXPORT (image, PNG) | 書き出し |
|
||||
| voice (the voice assistant) | 音声アシスタント / 音声機能 |
|
||||
| activate (a control, screen-reader text) | 押す(「押すと…」) |
|
||||
| AI agent | AIエージェント |
|
||||
| frame (bring into view) | 視野に収める |
|
||||
| story beat / evidence beat | ストーリー区切り / 証拠の区切り |
|
||||
| camera pose / camera move | カメラの姿勢 / 移動 |
|
||||
| roundabout / ramp / exit | ロータリー / ランプ / 出口 |
|
||||
| reduced motion | 動きを減らす |
|
||||
| NOT AN ALL-CLEAR | 安全の保証ではありません |
|
||||
| … Watch (scene titles: Earth, Orbital) | 監視(地球監視, 軌道監視) |
|
||||
@@ -0,0 +1,107 @@
|
||||
# Brazilian Portuguese (`pt`)
|
||||
|
||||
Catalog: `src/i18n/locales/pt.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: abiyeenzo (#909).
|
||||
|
||||
## Style
|
||||
|
||||
- Brazilian Portuguese (pt-BR): câmera, tela, compartilhar, arquivo, baixar, ônibus, metrô. Every `pt` tag, `pt-PT` included, gets this catalog, so avoid slang and regionalisms.
|
||||
- Address the user with «você» imperatives: «Clique no mapa», «Ative o CCTV», «Tente novamente». Tooltips that name an action use the infinitive: «Expandir painel», «Redefinir a câmera».
|
||||
- Sentence case, not Title Case: "Live Flights" → «Voos ao vivo». UPPERCASE English stays UPPERCASE: «AO VIVO», «SEM DADOS».
|
||||
- Quotes inside Portuguese text are straight double quotes: «Excluir o plano "{shot}"?». A sentence that names a panel quotes it: «o painel "Configurações de provedores"» as the subject, so the verb agrees, and «adicione em "Configurações de provedores"» after a preposition.
|
||||
- A dash between clauses is a spaced em dash « — », even where the English runs it in.
|
||||
- A date or time after a participle takes a preposition: «emitido em {time}», «válida para {valid}», «Posição em {time}», «onde estava em {time}». Use «em», not «às», wherever {time} can include a date.
|
||||
- A count in a string with no plural form (no `tn()`) goes after a colon, so it reads right for 1: «sincronizando feeds de cidades: {count}...», «CÂMERAS EM ESPERA: {count}».
|
||||
- A placeholder that holds a layer or tool name of unknown gender and number gets a noun to agree with: «{label}: camada ativada», «A ferramenta "{owner}" está usando o ponteiro — feche-a primeiro».
|
||||
- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: ANT., PRÓX., DESLIG., EXCL., INCL., ALT., VEL., câm.
|
||||
- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD, RANGE, ALT, GS, TRK and V/S stay in English, as in cockpit avionics («ALT {altitude} FT · GS {speed} KT · TRK {track}», «V/S {rate} FT/MIN»), and so does the HUD recording mark REC. Words used in a sentence are translated: heading → «rumo», range → «alcance».
|
||||
- Keyboard keys stay in English: Space, Esc, Enter, Tab, Shift, Backspace, H, 1–9 («Segure Space para falar»).
|
||||
- Common English tech words used in Brazil stay: feed, stream, snapshot, briefing, dock, layout, bloom, status, app, link, token.
|
||||
- Units: km, m, km/h, m/s, kt (knots), ft (feet), h, min, s, always spaced from the number («Terremotos (24 h)», «Missões espaciais (30 d)»; in the layer names the space is a no-break space, U+00A0, so the unit never wraps away from the number); in UPPERCASE text KM, KM/S, KT, FT, MIN, H. "m" is always metres, so minutes are «min» («há 5 min»). MW stays MW. `°` and `%` stay. Decimal comma in written numbers («M4,4»).
|
||||
- "ago" goes before the time: «há {count} min», «mais recente há {value}». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «há» again.
|
||||
- Money: «US$ {amount} mil / mi / bi» for thousands, millions and billions.
|
||||
- Compass points use Leste/Oeste: N, NNE, NE, ENE, L, ESE, SE, SSE, S, SSO, SO, OSO, O, ONO, NO, NNO; hemisphere letters N, S, L, O.
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs three forms. `Intl.PluralRules('pt')` follows Brazilian usage:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | -------------------------------------- | ------------------ |
|
||||
| `one` | 0, 1 and anything below 2: 0, 1, 1.5 | 0 câmera, 1 câmera |
|
||||
| `many` | 1 000 000, 2 000 000… (exact millions) | 1000000 de câmeras |
|
||||
| `other` | everything else: 2–999 999, 2.5… | 3 câmeras |
|
||||
|
||||
The `many` form is the `other` text with «de» after the number: «{count} de câmeras carregadas», «{count} DE CHAVES PENDENTES». A `one` form is also read for 0, so keep it natural with zero («0 arquivo selecionado» is standard Brazilian usage).
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Português |
|
||||
| --------------------------------------------- | ------------------------------------------------------------ |
|
||||
| layer / data layer | camada / camada de dados |
|
||||
| feed / source / data source | feed / fonte / fonte de dados |
|
||||
| LIVE (status) / live data | AO VIVO / dados ao vivo |
|
||||
| SIMULATED | SIMULADO |
|
||||
| stale / degraded / unavailable | desatualizado / degradado / indisponível |
|
||||
| retry / retrying | nova tentativa / tentando de novo |
|
||||
| retrying (a fetch) / retrying slowly | buscando de novo / tentando de novo em intervalos maiores |
|
||||
| rate-limited | «X: limite de requisições atingido» |
|
||||
| dismiss (window, card, error) | fechar |
|
||||
| key (API key) / POWER UP / POWERED UP | chave / ATIVAR / ATIVADO |
|
||||
| Provider Settings | Configurações de provedores |
|
||||
| ON / OFF (state chips) | ATIVO / INATIVO |
|
||||
| enable / disable | ativar / desativar |
|
||||
| globe / map / map source / map stack | globo / mapa / fonte do mapa / pilha de mapas |
|
||||
| basemap / tile | mapa base / bloco |
|
||||
| aircraft, plane / flight / military flights | aeronave, avião / voo / voos militares |
|
||||
| vessel, ship / TANKER (AIS class) | embarcação / NAVIO-TANQUE |
|
||||
| satellite / shell (Starlink) / DENSE | satélite / camada orbital / DENSO |
|
||||
| SPARSE / BALANCED / DENSE (detection modes) | ESPARSO / EQUILIBRADO / DENSO (o modo) |
|
||||
| launch (rocket) / launch site / space mission | lançamento / local de lançamento / missão espacial |
|
||||
| KM DOWNRANGE / EST. DOWNRANGE | KM DO LOCAL DE LANÇAMENTO / EST. PELA DIST. DO LANÇAMENTO |
|
||||
| ascent / replay / payload / stage | subida / reprise / carga útil / estágio |
|
||||
| contact / tracking / track | contato / rastreamento / rastro |
|
||||
| installation / site | instalação / local |
|
||||
| PLACES CANDIDATE (installation class) | CANDIDATO DO GOOGLE PLACES |
|
||||
| mapped / observed | mapeado / observado |
|
||||
| camera / CCTV camera | câmera / câmera de CCTV |
|
||||
| CALIBRATED / CURATED (CCTV badge) | CALIBRADA / COM CURADORIA |
|
||||
| AUTO HOP ON / OFF (CCTV) | TROCA AUTO. LIGADA / DESLIG. (as the other CCTV toggles) |
|
||||
| earthquake / active fire | terremoto / incêndio ativo |
|
||||
| advisory (NHC/CPHC cyclone) | boletim |
|
||||
| E/C Pacific (NHC/CPHC coverage) | Pacífico Leste/Central (no «L/C») |
|
||||
| weather (layer, card) / weather satellite | clima / meteorologia, meteorológico |
|
||||
| FOG / cloud unknown | NEVOEIRO / nebulosidade desconhecida |
|
||||
| AQHI bands Low … Very High | Risco baixo … Risco muito alto |
|
||||
| radio station / broadcaster | estação de rádio / emissora |
|
||||
| Oldies / World (radio tags) | Flashback / Músicas do mundo |
|
||||
| radio genre tags agree with «música» | Alternativa, Clássica, Eletrônica, Latina |
|
||||
| road / flow (traffic) | via / fluxo |
|
||||
| Street Level (layer) | Nível da rua |
|
||||
| scene / shot / scene recipe | cena / plano / receita de cena |
|
||||
| briefing / panel / dock | briefing / painel / dock |
|
||||
| cockpit | cabine |
|
||||
| preset / visual preset / EXPORT PRESETS | predefinição / predefinição visual / EXPORTAR PREDEF. |
|
||||
| VISUAL PRESETS (dock caption) | PREDEF. VISUAIS |
|
||||
| Scope (circular viewport mask) / keyhole | mira / visor |
|
||||
| feather / fade | suavização / esmaecimento |
|
||||
| detection overlay | sobreposição de detecção |
|
||||
| pin (Draw tool) / box (Recent Imagery) | marcador / área |
|
||||
| Cyber (HUD layout, effect) | Ciber |
|
||||
| heading / course / altitude / ground speed | rumo / curso / altitude / velocidade solo (VELOC. SOLO) |
|
||||
| map heading / bearing (compass, north up) | orientação (do mapa) |
|
||||
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | HDG / BRG / CRS / FOV / DEST / SPD / RANGE (unchanged) |
|
||||
| V/S (vertical speed code) | V/S (unchanged) |
|
||||
| PREV / NEXT | ANT. / PRÓX. |
|
||||
| Previous / Next (button text) | Anterior / Próximo (Próxima) |
|
||||
| L / R (relative bearing) | E / D |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | SIG / NOTÍCIAS / LOCAL |
|
||||
| REGIONAL NEWS (fits the 16-character kicker) | NOTÍCIAS DA ÁREA |
|
||||
| PITCH / HGT (CCTV calibration chips) | INCL. / ALT. |
|
||||
| NOT AN ALL-CLEAR | NÃO GARANTE SEGURANÇA |
|
||||
| Last report (transit vehicle position) | Último registro |
|
||||
| intersection (CCTV) | cruzamento |
|
||||
| {layers} just turned on (any list of layers) | «{layers}: ativação recente» (no agreement needed) |
|
||||
| {label} on / off (one layer) | «{label}: camada ativada / desativada» |
|
||||
| turn-by-turn: turn left / keep left / lane | vire à esquerda / mantenha-se à esquerda / faixa da esquerda |
|
||||
@@ -0,0 +1,65 @@
|
||||
# Russian (`ru`)
|
||||
|
||||
Catalog: `src/i18n/locales/ru.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
## Style
|
||||
|
||||
- Address the user with polite imperatives: «Нажмите», «Включите», «Попробуйте снова».
|
||||
- Sentence case, not Title Case: "Live Flights" → «Рейсы в реальном времени».
|
||||
- Quotes inside Russian text are «ёлочки». Write ё where it belongs: её, ещё, всё.
|
||||
- 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.
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs four forms. `Intl.PluralRules('ru')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | ------------------------ | ---------- |
|
||||
| `one` | 1, 21, 31, 101… | 1 камера |
|
||||
| `few` | 2–4, 22–24, 32–34… | 3 камеры |
|
||||
| `many` | 0, 5–20, 25–30, 111–114… | 5 камер |
|
||||
| `other` | fractions: 1.5, 2.25… | 1,5 камеры |
|
||||
|
||||
When a word next to a number cannot agree in every form, move the number after a colon instead: «доступно {bikes}», «ответов: {responses}». The same trick keeps place names and tool names out of a preposition's case: «откуда: {origin}», «Активно: {name}».
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Русский |
|
||||
| ------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| layer / data layer | слой / слой данных |
|
||||
| feed | поток данных (short: поток) |
|
||||
| source / data source | источник / источник данных |
|
||||
| LIVE (status) / live data | ОНЛАЙН / в реальном времени |
|
||||
| SIMULATED | СИМУЛЯЦИЯ |
|
||||
| stale / degraded / unavailable | устарел / с перебоями / недоступен |
|
||||
| retry | повтор |
|
||||
| key (API key) / POWER UP | ключ / ЗАПУСК |
|
||||
| Provider Settings | Настройки провайдеров |
|
||||
| globe / map / map source | глобус / карта / источник карты |
|
||||
| aircraft / flight / military flights | самолёт (short HUD: борт) / рейс / военные рейсы |
|
||||
| vessel, ship / vessels | судно / суда |
|
||||
| satellite | спутник |
|
||||
| launch (rocket) / space mission | пуск / космическая миссия |
|
||||
| contact / tracking | контакт / отслеживание |
|
||||
| installation, site | объект |
|
||||
| camera / CCTV camera | камера / камера CCTV |
|
||||
| earthquake / active fire | землетрясение / активный пожар |
|
||||
| scene / shot / scene recipe | сцена / кадр / рецепт сцены |
|
||||
| briefing / panel / dock | брифинг / панель / док |
|
||||
| preset / visual preset | пресет / визуальный пресет |
|
||||
| Scope (circular viewport mask) | прицел |
|
||||
| heading / altitude / ground speed | курс / высота / путевая скорость |
|
||||
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | КУРС / ПЕЛ. / ПУТЬ / ОБЗОР / НАЗН. / СКОР / ДАЛЬН. |
|
||||
| advisory (NHC/CPHC cyclone) | сводка (не «бюллетень»); Advisory center — центр циклона по сводке |
|
||||
| Street View (Google) | Просмотр улиц (как в русских Google Картах) |
|
||||
| CONUS (NOAA region code) | CONUS, без перевода |
|
||||
| WHOT/BHOT, Ironbow (thermal modes, palette) | без перевода |
|
||||
| PREV / NEXT | ПРЕД. / СЛЕД. |
|
||||
| Previous / Next (button text) | Назад / Далее |
|
||||
| anchor / data pack / bundle (scene) | якорь / пакет данных / архив |
|
||||
| basemap / box (Recent Imagery area) | подложка / область |
|
||||
| camera move | перелёт |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | СИГН. / НОВОСТИ / МЕСТН. |
|
||||
| compass points (N, NNE, …) | С, ССВ, … |
|
||||
| L / R (relative bearing) | Л / П |
|
||||
@@ -0,0 +1,106 @@
|
||||
# Ukrainian (`uk`)
|
||||
|
||||
Catalog: `src/i18n/locales/uk.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation source: meirm (#312).
|
||||
|
||||
## Style
|
||||
|
||||
- Address the user with polite imperatives: «Натисніть», «Увімкніть», «Спробуйте ще раз».
|
||||
- Sentence case, not Title Case: "Live Flights" → «Рейси онлайн».
|
||||
- Ukrainian norms, not Russian calques: «у радіусі», «за посиланням», «надто довгий», «потім», «вигляд» (a camera view: «вигляд згори»), «стеження» (tracking); prepositions alternate у/в and і/й by euphony.
|
||||
- The apostrophe is always ʼ (U+02BC): обʼєкт, звʼязок, памʼять. Not ' or ’.
|
||||
- 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: ПОПЕР., ДАЛЬН., КАЛІБР.
|
||||
- Units: км, м, км/год, м/с, вуз (knots), фт (feet), год, хв, с, МГц, дБ, гПа; in UPPERCASE text КМ, М, КМ/С, ВУЗ, ФТ. MW is МВт in any case. `°` and `%` stay. Decimal comma in coordinates written as text: 28,2710° пн. ш.
|
||||
- Coordinates: пн. ш. / пд. ш. / сх. д. / зх. д.; the HUD hemisphere letters are Пн, Пд, Сх, Зх.
|
||||
|
||||
## Plurals
|
||||
|
||||
A `tn()` entry needs four forms. `Intl.PluralRules('uk')` picks:
|
||||
|
||||
| Form | Counts | Example |
|
||||
| ------- | ------------------------ | ---------- |
|
||||
| `one` | 1, 21, 31, 101… | 1 камера |
|
||||
| `few` | 2–4, 22–24, 32–34… | 3 камери |
|
||||
| `many` | 0, 5–20, 25–30, 111–114… | 5 камер |
|
||||
| `other` | fractions: 1.5, 2.25… | 1,5 камери |
|
||||
|
||||
`other` takes the genitive singular: 1,5 кадру, 1,5 літака, 1,5 ключа. When a word next to a number cannot agree in every form, move the number after a colon instead: «поруч: {count}», «відповідей: {responses}». The same trick keeps place names and tool names out of a preposition's case: «звідки: {origin}», «Активно: {name}».
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | Українська |
|
||||
| ----------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| layer / data layer | шар / шар даних |
|
||||
| feed | потік даних (short: потік) |
|
||||
| source / data source | джерело / джерело даних |
|
||||
| LIVE (status) / live data | ОНЛАЙН / у реальному часі (status chip: ДАНІ ОНЛАЙН) |
|
||||
| SIMULATED / SYNTHETIC | ІМІТАЦІЯ |
|
||||
| stale / degraded / unavailable | застарів / з перебоями (chip: ПЕРЕБОЇ) / недоступний |
|
||||
| retry | повтор |
|
||||
| key (API key) / POWER UP | ключ / ЗАПУСК |
|
||||
| Provider Settings / provider | Налаштування постачальників / постачальник |
|
||||
| globe / map / map source / basemap | глобус / карта / джерело карти / підкладка |
|
||||
| aircraft / flight / military flights | літак (short HUD: борт) / рейс / військові рейси |
|
||||
| vessel, ship / vessels | судно / судна (gen. pl. суден) |
|
||||
| satellite | супутник |
|
||||
| launch (rocket) / space mission | запуск / космічна місія |
|
||||
| ascent (rocket) / booster FLIGHT {n} | виведення / ПОЛІТ {n} (not рейс) |
|
||||
| contact / tracking | контакт / стеження |
|
||||
| installation, site / mapped | обʼєкт / нанесений (обʼєкт, контакт) |
|
||||
| camera / CCTV camera | камера / камера CCTV |
|
||||
| earthquake / active fire / fire perimeter | землетрус / активна пожежа / контур пожежі |
|
||||
| advisory (NHC/CPHC cyclone) | зведення |
|
||||
| scene / shot / scene recipe | сцена / кадр / рецепт сцени |
|
||||
| briefing / panel / dock | брифінг / панель / док |
|
||||
| preset / visual preset | пресет / візуальний пресет |
|
||||
| Scope, keyhole (circular viewport mask) | приціл |
|
||||
| overlay (detection overlay) / world overlay | накладка / накладка на глобусі |
|
||||
| heading / altitude / ground speed | курс / висота / шляхова швидкість |
|
||||
| map bearing, map heading | азимут карти |
|
||||
| HDG / BRG / CRS, TRK / FOV / DEST / SPD / RANGE | КУРС / ПЕЛ. / ШК (шляховий кут) / ОГЛЯД / ПРИЗН. / ШВИД / ДАЛЬН. |
|
||||
| Street View (Google) | Перегляд вулиць (as in Ukrainian Google Maps) |
|
||||
| Street Level (layer) | Вуличні знімки |
|
||||
| imagery / recent imagery | знімки / свіжі знімки |
|
||||
| radar reflectivity / lightning density | радіолокаційна відбивність / щільність блискавок |
|
||||
| infrared | інфрачервоний (short: ІЧ) |
|
||||
| receiver / gain / decoder | приймач / підсилення / декодер |
|
||||
| broadcaster | мовник |
|
||||
| CONUS, WHOT/BHOT, Ironbow, CRT, NVG, FLIR | без перекладу |
|
||||
| PREV / NEXT (HUD) / Previous / Next | ПОПЕР. / ДАЛІ / Попередній / Наступний |
|
||||
| anchor / data pack / bundle (scene) | якір / пакет даних / архів |
|
||||
| box (Recent Imagery area) | область |
|
||||
| camera move | переліт |
|
||||
| SIG / NEWS / LOCAL (briefing tabs) | СИГН. / НОВИНИ / МІСЦ. |
|
||||
| compass points (N, NNE, NE, …) | Пн, ПнПнСх, ПнСх, … |
|
||||
| L / R (relative bearing) | Л / П |
|
||||
| click / tap | клацнути / натиснути |
|
||||
| session (voice, OAuth) | сеанс |
|
||||
| sequence (street-level photos) | послідовність |
|
||||
| search (place search) | пошук місця |
|
||||
| pitch (CCTV camera pose) | нахил |
|
||||
| traffic (road traffic layer) | дорожній рух |
|
||||
| phosphor (CRT) | люмінофор |
|
||||
| view (map extent) / view centre / in view | видима область (карти) / центр карти / у кадрі |
|
||||
| select (verb) / selected | вибрати / вибраний (not обрати) |
|
||||
| ping (cockpit proximity contacts) | контакт |
|
||||
| merge (turn-by-turn) / on ramp | влитися в потік / вʼїзд (off ramp: зʼїзд) |
|
||||
| watch (Earth/Orbital Watch) | спостереження |
|
||||
| timeout | тайм-аут |
|
||||
| icon | значок (not іконка) |
|
||||
| run (scene playback) / RUN LOG | відтворення / ЖУРНАЛ ВІДТВОРЕННЯ (запуск is a rocket launch) |
|
||||
| Preview (voice panel replies) | відповіді |
|
||||
| Marine (radio tag) / pleasure craft | море (not флот) / прогулянкове судно |
|
||||
| emergency (ADS-B) / Emergency (radio tag) | аварійний стан / надзвичайні ситуації |
|
||||
| confidence (FIRMS fire detection) | достовірність (short: достов.) |
|
||||
| thermal (FLIR-style imaging) | тепловий (not термальний) |
|
||||
| map stack | набір карт |
|
||||
| stage (rocket) re-entry / recovery | вхід ступеня в атмосферу / повернення |
|
||||
| ON / OFF (toggle chips) | УВІМК. / ВИМК. |
|
||||
| Clean UI / EXIT CLEAN VIEW | чистий інтерфейс / ПОВЕРНУТИ ІНТЕРФЕЙС |
|
||||
| full globe view | вигляд всього глобуса |
|
||||
| inputs (cockpit context chip) | потоки (not вхідні дані) |
|
||||
| downrange (rocket stage) | дальність |
|
||||
| resolution (spatial) | роздільна здатність (not роздільність) |
|
||||
| shared follow expired | посилання на стеження застаріло |
|
||||
@@ -0,0 +1,77 @@
|
||||
# Simplified Chinese (`zh-CN`)
|
||||
|
||||
Catalog: `src/i18n/locales/zh-CN.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
Translation sources: abiyeenzo (#914) and EDIT-Aeterna (#979).
|
||||
|
||||
## Style
|
||||
|
||||
- Mainland usage and Simplified characters. Address the user neutrally, without 您/你 where a plain imperative works: 「点击地图」, 「重试」.
|
||||
- Chinese has no case: an UPPERCASE English HUD label becomes the same short Chinese label. Keep labels as short as the English: 「实时」, 「过期」, 「关」.
|
||||
- Full-width punctuation in Chinese text: ,。:;?!()“”. A dash is 「——」 without spaces; keep `·` as the HUD separator and `…` for ellipsis.
|
||||
- One space between Chinese and Latin letters or digits: 「AIS 船舶」, 「24 小时」, 「{count} 个」. No space next to full-width punctuation.
|
||||
- Units: km, m, km/h, m/s, kn, ft, MW, °, % stay as written; time words are 小时, 分钟, 秒 after a number (「5 分钟前」).
|
||||
- Product names, provider names and codes stay as they are: Google, Cesium, CelesTrak, NOAA, FIRMS, AIS, ADS-B, GNSS, CCTV, HUD, CRT, NVG, FLIR, WHOT/BHOT, Ironbow, CONUS.
|
||||
|
||||
## Plurals
|
||||
|
||||
`Intl.PluralRules('zh-CN')` has one category, `other`: a `tn()` entry has only that form. Use a measure word next to the number where Chinese needs one: 「{count} 个密钥」, 「{count} 架飞机」, 「{count} 艘船」.
|
||||
|
||||
## Glossary
|
||||
|
||||
| English | 中文 |
|
||||
| ------------------------------------- | --------------------------- |
|
||||
| layer / data layer | 图层 / 数据图层 |
|
||||
| feed | 数据源 |
|
||||
| source / data source | 来源 / 数据源 |
|
||||
| LIVE (status) / live data | 实时 / 实时数据 |
|
||||
| SIMULATED | 模拟 |
|
||||
| stale / degraded / unavailable | 过期 / 降级 / 不可用 |
|
||||
| retry | 重试 |
|
||||
| key (API key) / POWER UP | 密钥(API 密钥)/ 增强 |
|
||||
| Provider Settings | 服务商设置 |
|
||||
| globe / map / map source / basemap | 地球 / 地图 / 地图源 / 底图 |
|
||||
| aircraft / flight / military flights | 飞机 / 航班 / 军用航班 |
|
||||
| vessel, ship | 船舶 |
|
||||
| satellite | 卫星 |
|
||||
| launch (rocket) / space mission | 发射 / 太空任务 |
|
||||
| contact (tracked object) | 目标 |
|
||||
| installation, site | 设施 |
|
||||
| camera / CCTV camera | 摄像头 / CCTV 摄像头 |
|
||||
| earthquake / active fire | 地震 / 活跃火点 |
|
||||
| scene / shot | 场景 / 镜头 |
|
||||
| briefing / panel / dock | 简报 / 面板 / 停靠栏 |
|
||||
| preset / visual preset | 预设 / 视觉预设 |
|
||||
| Display / HUD layout | 显示 / HUD 布局 |
|
||||
| Tactical / Minimal / Cyber | 战术 / 精简 / 赛博 |
|
||||
| Scope (circular viewport mask) / keyhole | 视域 |
|
||||
| Clean UI | 纯净界面 |
|
||||
| cockpit | 座舱 |
|
||||
| heading / altitude / ground speed | 航向 / 高度 / 地速 |
|
||||
| map heading / North up | 方位 / 正北朝上 |
|
||||
| Street Level / Street View (Google) | 街景 / 街景(Google) |
|
||||
| Recent Imagery | 近期影像 |
|
||||
| Global Context / Context (panel) | 全局态势 / 态势 |
|
||||
| radio (station) | 电台 |
|
||||
| transit / bikeshare | 公共交通 / 共享单车 |
|
||||
| Previous / Next | 上一个 / 下一个 |
|
||||
| compass points (N, NNE, …) | 北, 北东北, … |
|
||||
| weather field / FIELD (selector) | 气象场 / 要素 |
|
||||
| weather model / forecast lead hours | 模式 / 预报时效 |
|
||||
| valid (forecast time) | 有效时间 |
|
||||
| scene cloud / mosaic | 整景云量 / 镶嵌图 |
|
||||
| pin (Recent Imagery) | 固定 |
|
||||
| THIS (voice pointer) | 所指 |
|
||||
| outline / trace an outline | 轮廓 / 绘制轮廓 |
|
||||
| mark (map annotation) | 标注 |
|
||||
| camera badge | 摄像头标记 |
|
||||
| provider / embedded provider controls | 服务商 / 平台控件 |
|
||||
| share (a view or link) | 分享 |
|
||||
| AI agent | AI 智能体 |
|
||||
| stage (rocket) / downrange | 子级 / 航程 |
|
||||
| capture shot / camera move | 保存镜头 / 运镜 |
|
||||
| story beat / evidence beat | 事件节点 / 证据节点 |
|
||||
| camera pose | 位姿 |
|
||||
| forecast uncertainty cone | 预报不确定范围 |
|
||||
| seed (catalog data) | 预置 |
|
||||
| frame (bring into view) | 将…纳入视野 |
|
||||
@@ -0,0 +1,17 @@
|
||||
# Traditional Chinese (`zh-Hant`)
|
||||
|
||||
Catalog: `src/i18n/locales/zh-Hant.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
|
||||
|
||||
`zh-Hant` is the Simplified Chinese catalog (`zh-CN`, credited to abiyeenzo, #914, and EDIT-Aeterna, #979) written in Traditional characters with OpenCC character conversion. Wording and terminology are the same as in `zh-CN`: change a term in [zh-CN.md](./zh-CN.md) and the `zh-CN` catalog first, then convert. Only the script differs.
|
||||
|
||||
After `s2tw` conversion, fix the characters OpenCC picks wrongly: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道, 發佈 → 發布, 公佈 → 公布.
|
||||
|
||||
## Style
|
||||
|
||||
- Follow [zh-CN.md](./zh-CN.md) for wording, punctuation, spacing, units and the glossary, written in Traditional characters.
|
||||
- Relative-time placeholders may already include 前. In `LIVE · updated {age}`, the caller supplies the complete relative age; do not append another 前.
|
||||
- The picker displays 中文(繁體) / Chinese (Traditional); explicit Hans tags select Simplified Chinese.
|
||||
|
||||
## Plurals
|
||||
|
||||
`Intl.PluralRules('zh-Hant')` uses only `other`. Keep the measure words of the `zh-CN` entry: 「{count} 架飛機」、「{count} 艘船」.
|
||||
Reference in new issue
Block a user