mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
i18n(fr): apply review round 3
Apply the accepted findings from the third three-model French review:
tool-name agreement (« Outil actif : {name} »), elision before ADS-B,
« GORGE AVAL », « CLASSIQUES », « EN SURVOL », one name for the VIIRS
daily overview, U+202F thousands separator, and wording fixes for the
scene editor, directions, radio tuner and status strings.
The CCTV HUD summary now shows its CAL badge, source kind and view band
through existing catalog entries, so French reads « CAL ESTIM. INITIALE ·
SRC AMONT · CONTEXTE RUE »; English output and unknown codes are
unchanged. presentation.js matches the pt branch's round-3 version byte
for byte, so the two branches merge cleanly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e87001d0db
commit
7e55ee45fe
4 files changed
+139
-30
No files matched your search
@@ -9,7 +9,7 @@ Translation source: abiyeenzo (#907); the French part of meirm's #312 was used f
|
||||
- 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. Ellipsis is `…`, not `...`. Dashes keep their spaces: « flux indisponible — réessayez ».
|
||||
- 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é ».
|
||||
@@ -28,7 +28,7 @@ A `tn()` entry needs three forms. `Intl.PluralRules('fr')` picks:
|
||||
| `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é ».
|
||||
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
|
||||
|
||||
@@ -66,6 +66,7 @@ The `many` form is the `other` text with « de » (« d' » before a vowel) afte
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -75,6 +76,7 @@ The `many` form is the `other` text with « de » (« d' » before a vowel) afte
|
||||
| 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 |
|
||||
|
||||
+17
-17
@@ -140,7 +140,7 @@
|
||||
"◉ CINEMATIC": "◉ CINÉMATIQUE",
|
||||
"↺ FULL STORY": "↺ RÉCIT COMPLET",
|
||||
"↗ OPEN ORIGINAL": "↗ OUVRIR L'ORIGINAL",
|
||||
"⌖ LOWER GORGE": "⌖ GORGE INFÉRIEURE",
|
||||
"⌖ LOWER GORGE": "⌖ GORGE AVAL",
|
||||
"FIELD REPORTS · {count}": "RAPPORTS DE TERRAIN · {count}",
|
||||
"GEOLOCATIONS · {credit}": "GÉOLOCALISATIONS · {credit}",
|
||||
"↗ OPEN PUBLIC MAP": "↗ OUVRIR LA CARTE PUBLIQUE",
|
||||
@@ -184,7 +184,7 @@
|
||||
"USGS INITIAL REPORT": "RAPPORT INITIAL DE L'USGS",
|
||||
"26 AUG 2026 · 08:37 NPT": "26 AOÛT 2026 · 08:37 NPT",
|
||||
"28.2710° N · 85.5150° E": "28,2710° N · 85,5150° E",
|
||||
"INITIAL M4.4 · RECLASSIFIED M5.2 LANDSLIDE": "M4,4 INITIALE · RECLASSÉ EN GLISSEMENT DE TERRAIN M5,2",
|
||||
"INITIAL M4.4 · RECLASSIFIED M5.2 LANDSLIDE": "INITIALEMENT M4,4 · RECLASSÉ EN GLISSEMENT DE TERRAIN M5,2",
|
||||
"USGS EVENT us7000tbwb · OPEN SOURCE": "ÉVÉNEMENT USGS us7000tbwb · OUVRIR LA SOURCE",
|
||||
"Open the USGS initial report for the 26 August 2026 Nepal landslide": "Ouvrir le rapport initial de l'USGS sur le glissement de terrain du 26 août 2026 au Népal",
|
||||
"Show the Nepal context callout without a border or country fill": "Afficher l'encart de contexte du Népal sans frontière ni remplissage du pays",
|
||||
@@ -271,7 +271,7 @@
|
||||
"NAV": "NAV",
|
||||
"GNSS navigation — GPS, GLONASS, Galileo": "Navigation GNSS — GPS, GLONASS, Galileo",
|
||||
"GEO": "GEO",
|
||||
"Geostationary belt — comms and weather, fixed over the equator": "Ceinture géostationnaire — télécoms et météo, fixes au-dessus de l'équateur",
|
||||
"Geostationary belt — comms and weather, fixed over the equator": "Ceinture géostationnaire — télécoms et météo, en position fixe au-dessus de l'équateur",
|
||||
"VISUAL": "VISUEL",
|
||||
"Brightest naked-eye objects — CelesTrak visual group": "Objets les plus brillants à l'œil nu — groupe visuel CelesTrak",
|
||||
"COMMS": "TÉLÉCOM",
|
||||
@@ -668,7 +668,7 @@
|
||||
"Use the {direction} lane": "Prenez la voie {direction}",
|
||||
"Continue onto {road}": "Continuez sur {road}",
|
||||
"maneuver|Continue": "Continuez",
|
||||
"{mode} — reroute for {modeLower}": "{mode} — recalculer l'itinéraire : {modeLower}",
|
||||
"{mode} — reroute for {modeLower}": "{mode} — recalculer l'itinéraire {modeLower}",
|
||||
"CLICK MAP": "CLIQUER SUR LA CARTE",
|
||||
"SET A": "PLACER A",
|
||||
"Click a spot on the globe to place A (click again to cancel)": "Cliquez sur un point du globe pour placer A (cliquez à nouveau pour annuler)",
|
||||
@@ -678,14 +678,14 @@
|
||||
"Then click the globe to place the destination": "Puis cliquez sur le globe pour placer la destination",
|
||||
"Swap A and B": "Inverser A et B",
|
||||
"FLY ···": "SURVOL ···",
|
||||
"FLYING": "SURVOL EN COURS",
|
||||
"FLYING": "EN SURVOL",
|
||||
"FLY": "SURVOLER",
|
||||
"Fly the camera along the route": "Faire voler la caméra le long de l'itinéraire",
|
||||
"Place A and B first": "Placez d'abord A et B",
|
||||
"action|CLEAR": "EFFACER",
|
||||
"Remove the route and both markers": "Supprimer l'itinéraire et les deux marqueurs",
|
||||
"press Escape twice to leave Draw": "appuyez deux fois sur Esc pour quitter Dessin",
|
||||
"{name} is active — turn {name} off, then set {endpoint}": "{name} est actif — désactivez {name}, puis placez {endpoint}",
|
||||
"{name} is active — turn {name} off, then set {endpoint}": "Outil actif : {name} — désactivez {name}, puis placez {endpoint}",
|
||||
"{name} is active — {leave}, then set {endpoint}": "{name} est actif — {leave}, puis placez {endpoint}",
|
||||
"Only the first {count} turns of this route are shown": "Seules les {count} premières manœuvres de cet itinéraire sont affichées",
|
||||
"Turn-by-turn directions": "Instructions pas à pas",
|
||||
@@ -994,7 +994,7 @@
|
||||
"Techno": "Techno",
|
||||
"Trance": "Trance",
|
||||
"World": "Musiques du monde",
|
||||
"overview": "vue d'ensemble",
|
||||
"overview": "vue quotidienne",
|
||||
"cloud unknown": "nébulosité inconnue",
|
||||
"{percent}% scene cloud": "{percent} % de nuages sur la scène",
|
||||
"{min}–{max}% scene cloud": "{min}–{max} % de nuages sur la scène",
|
||||
@@ -1198,7 +1198,7 @@
|
||||
"Observation unavailable": "Observation indisponible",
|
||||
"Waiting for observation": "En attente d'observation",
|
||||
"{detail} · synced": "{detail} · synchro",
|
||||
"{detail} · nearest": "{detail} · la plus proche",
|
||||
"{detail} · nearest": "{detail} · plus proche",
|
||||
"RADAR REFLECTIVITY · dBZ": "RÉFLECTIVITÉ RADAR · dBZ",
|
||||
"LIGHTNING DENSITY · 15 min accumulation": "DENSITÉ DE FOUDRE · cumul sur 15 min",
|
||||
"GLOBAL INFRARED · hourly": "INFRAROUGE MONDIAL · horaire",
|
||||
@@ -1471,9 +1471,9 @@
|
||||
"Anchors and data packs": "Ancres et paquets de données",
|
||||
"Shot camera, timing, packs and actions": "Caméra, minutage, paquets et actions du plan",
|
||||
"New anchor ID": "ID de la nouvelle ancre",
|
||||
"Capture camera as anchor": "Capturer la caméra comme ancre",
|
||||
"Capture camera as anchor": "Créer une ancre à la position de la caméra",
|
||||
"Anchor added to draft": "Ancre ajoutée au brouillon",
|
||||
"Set move start to current camera": "Définir le début du mouvement sur la caméra actuelle",
|
||||
"Set move start to current camera": "Utiliser la vue actuelle comme départ du mouvement",
|
||||
"Move added to draft; destination remains the shot camera": "Mouvement ajouté au brouillon ; la destination reste la caméra du plan",
|
||||
"Use ordinary flight": "Utiliser un vol normal",
|
||||
"Invalid shot JSON": "JSON de plan non valide",
|
||||
@@ -1542,7 +1542,7 @@
|
||||
"Switching language reloads the page. This will be lost: {items}. Switch anyway?": "Changer de langue recharge la page. Ceci sera perdu : {items}. Changer quand même ?",
|
||||
"VISUAL PRESETS": "PRÉRÉGLAGES VISUELS",
|
||||
"LOCATION": "LIEU",
|
||||
"CONTACTS — nearest planes · vessels · sites": "CONTACTS — avions · navires · sites les plus proches",
|
||||
"CONTACTS — nearest planes · vessels · sites": "CONTACTS — avions, navires et sites les plus proches",
|
||||
"SPACE MISSIONS — launches & orbital assets": "MISSIONS SPATIALES — lancements et objets orbitaux",
|
||||
"LIVE SIGNALS": "SIGNAUX EN DIRECT",
|
||||
"Contacts could not complete the requested transition; try again": "Les contacts n'ont pas pu effectuer la transition demandée ; réessayez",
|
||||
@@ -1844,7 +1844,7 @@
|
||||
"Other layers": "Autres couches",
|
||||
"{state} · {source}": "{state} · {source}",
|
||||
"UNCERTAIN · {source} · lifecycle state requires reconciliation": "INCERTAIN · {source} · état à rétablir",
|
||||
"{state} · {source} · {detail} · retry {seconds}s": "{state} · {source} · {detail} · nouvel essai {seconds} s",
|
||||
"{state} · {source} · {detail} · retry {seconds}s": "{state} · {source} · {detail} · nouvel essai dans {seconds} s",
|
||||
"{state} · {source} · {detail}": "{state} · {source} · {detail}",
|
||||
"never": "jamais",
|
||||
"{source} · {loadingLabel}": "{source} · {loadingLabel}",
|
||||
@@ -1898,7 +1898,7 @@
|
||||
"LOCATING…": "LOCALISATION…",
|
||||
"LOCATED": "LOCALISÉ",
|
||||
"LOCATE": "LOCALISER",
|
||||
" · read while Local ADS-B is on": " · lus tant que ADS-B local est activé",
|
||||
" · read while Local ADS-B is on": " · lus tant qu'ADS-B local est activé",
|
||||
"Decoder feeds: {feeds}": "Flux du décodeur : {feeds}",
|
||||
"Language: {language}": "Langue : {language}",
|
||||
"Change the interface language": "Changer la langue de l'interface",
|
||||
@@ -2055,7 +2055,7 @@
|
||||
"S on another day replaces it · × unpins": "S sur un autre jour le remplace · × le désépingle",
|
||||
"← → preview · S shows the focused day": "← → aperçu · S affiche le jour ciblé",
|
||||
"ZOOM IN": "ZOOMER",
|
||||
"Fly in until the view fits the 1,000 km limit": "Rapprochez-vous jusqu'à ce que la vue tienne dans la limite de 1 000 km",
|
||||
"Fly in until the view fits the 1,000 km limit": "Rapprochez-vous jusqu'à ce que la vue tienne dans la limite de 1 000 km",
|
||||
"Imagery days, newest first · arrows move, S or A and B pin": "Jours d'imagerie, du plus récent au plus ancien · les flèches déplacent, S ou A et B épinglent",
|
||||
"On the map": "Sur la carte",
|
||||
"Mode": "Mode",
|
||||
@@ -2660,7 +2660,7 @@
|
||||
"Next ISS pass": "Prochain passage de l'ISS",
|
||||
"Next satellite pass": "Prochain passage du satellite",
|
||||
"Mark the map": "Marquer la carte",
|
||||
"Mark {targets} +{more}": "Marquer {targets} +{more}",
|
||||
"Mark {targets} +{more}": "Marquer : {targets} +{more}",
|
||||
"Mark {targets}": "Marquer : {targets}",
|
||||
"Fly to {place}": "Rejoindre {place}",
|
||||
"Fly to Location": "Rejoindre le lieu",
|
||||
@@ -2822,7 +2822,7 @@
|
||||
"All": "Tous",
|
||||
"Enable Radio, then choose a globe marker or use next.": "Activez la Radio, puis choisissez un marqueur sur le globe ou utilisez SUIV.",
|
||||
"DRAG TO TUNE": "GLISSER POUR RÉGLER",
|
||||
"Tune available internet radio stations": "Écouter les stations de radio Internet disponibles",
|
||||
"Tune available internet radio stations": "Régler sur une station de radio Internet disponible",
|
||||
"ALL · DRAG THE NEEDLE": "TOUTES · DÉPLACER L'AIGUILLE",
|
||||
"SNAPS TO AVAILABLE STATIONS": "SE CALE SUR LES STATIONS DISPONIBLES",
|
||||
"Radio playback": "Lecture radio",
|
||||
@@ -2964,7 +2964,7 @@
|
||||
"IMAGERY": "IMAGES",
|
||||
"Panorama filter": "Filtre de panoramas",
|
||||
"ALL": "TOUTES",
|
||||
"FLAT": "PLATES",
|
||||
"FLAT": "CLASSIQUES",
|
||||
"SINCE": "DEPUIS",
|
||||
"SCENES": "SCÈNES",
|
||||
"Scene recipe": "Recette de scène",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// The CCTV HUD summary shows the CAL badge, source kind and view band in the
|
||||
// UI language; English keeps its codes, and unknown source codes stay codes.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { setLocale } from '../../i18n/index.js';
|
||||
import { createPresentation } from './presentation.js';
|
||||
|
||||
function summary(sourceKind) {
|
||||
const camera = {
|
||||
id: 'c1',
|
||||
city: 'Austin',
|
||||
name: 'Cam',
|
||||
rangeM: 100,
|
||||
fovDeg: 60,
|
||||
feedType: 'image',
|
||||
};
|
||||
const record = { camera };
|
||||
const presentation = createPresentation({
|
||||
state: {
|
||||
_records: [record],
|
||||
_healthById: new Map(sourceKind ? [['c1', { sourceKind }]] : []),
|
||||
_showProjection: false,
|
||||
_coverageMode: 'sector',
|
||||
},
|
||||
services: {},
|
||||
parts: {
|
||||
selection: { getActiveRecord: () => record },
|
||||
model: {
|
||||
sectorAreaKm2: () => 1,
|
||||
currentViewContext: () => 'street:1:2',
|
||||
},
|
||||
geometry: { coverageNeighborCount: () => 0 },
|
||||
calibration: { deriveCalBadge: () => 'raw-prior' },
|
||||
},
|
||||
source: {},
|
||||
});
|
||||
return presentation.buildSummaryText();
|
||||
}
|
||||
|
||||
test('en: CAL, SRC and CONTEXT keep their English codes', () => {
|
||||
setLocale('en');
|
||||
assert.match(summary('upstream'), /CAL RAW PRIOR · SRC UPSTREAM/);
|
||||
assert.match(summary(null), /SRC IMAGE · STREET CONTEXT/);
|
||||
assert.match(summary('upstream-image'), /SRC UPSTREAM-IMAGE/);
|
||||
});
|
||||
|
||||
test('fr: CAL, SRC and CONTEXT values are translated', () => {
|
||||
setLocale('fr');
|
||||
try {
|
||||
assert.match(
|
||||
summary('upstream'),
|
||||
/CAL ESTIM\. INITIALE · SRC AMONT · CONTEXTE RUE/,
|
||||
);
|
||||
assert.match(summary('upstream-image'), /SRC UPSTREAM-IMAGE/);
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { msg, t } from '../../i18n/index.js';
|
||||
import { ACTIVE_FRAME_REFRESH_MS, IDLE_FRAME_REFRESH_MS } from './policy.js';
|
||||
import { headingHudToken, isHeadingEstimated } from './headingConfidence.js';
|
||||
import {
|
||||
@@ -8,6 +8,59 @@ import {
|
||||
isDatasheetFov,
|
||||
} from './modelSpecs.js';
|
||||
|
||||
// The summary line names the CAL badge, the source kind and the view band in
|
||||
// the page language; the codes underneath stay English.
|
||||
const CAL_BADGE_LABELS = Object.freeze({
|
||||
calibrated: msg('CALIBRATED'),
|
||||
curated: msg('CURATED'),
|
||||
'raw-prior': msg('RAW PRIOR'),
|
||||
});
|
||||
|
||||
// The same source-kind words as the CCTV panel (src/ui/cctvFrames.js).
|
||||
const SOURCE_KIND_LABELS = Object.freeze({
|
||||
snapshot: msg('SNAPSHOT'),
|
||||
live: msg('LIVE'),
|
||||
fallback: msg('FALLBACK'),
|
||||
upstream: msg('UPSTREAM'),
|
||||
synthetic: msg('SYNTHETIC'),
|
||||
configured: msg('CONFIGURED'),
|
||||
seed: msg('SEED'),
|
||||
image: msg('IMAGE'),
|
||||
unknown: msg('UNKNOWN'),
|
||||
});
|
||||
|
||||
/** CAL badge id ('raw-prior') as the summary shows it; unknown ids stay codes. */
|
||||
export function summaryCalLabel(badge) {
|
||||
const key = String(badge || '');
|
||||
return CAL_BADGE_LABELS[key]
|
||||
? t(CAL_BADGE_LABELS[key])
|
||||
: key.replace('-', ' ').toUpperCase();
|
||||
}
|
||||
|
||||
/** Source kind ('image', 'snapshot') for the summary; HLS, MJPEG… stay codes. */
|
||||
export function summarySourceLabel(kind) {
|
||||
const key = String(kind || 'image').toLowerCase();
|
||||
return SOURCE_KIND_LABELS[key]
|
||||
? t(SOURCE_KIND_LABELS[key])
|
||||
: key.toUpperCase();
|
||||
}
|
||||
|
||||
/** View band id ('street', 'city'…) as the HUD names it. */
|
||||
export function summaryViewBandLabel(band) {
|
||||
switch (band) {
|
||||
case 'street':
|
||||
return t('STREET', { context: 'view band' });
|
||||
case 'city':
|
||||
return t('CITY', { context: 'view band' });
|
||||
case 'regional':
|
||||
return t('REGIONAL', { context: 'view band' });
|
||||
case 'global':
|
||||
return t('GLOBAL', { context: 'view band' });
|
||||
default:
|
||||
return String(band || '').toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
export function createPresentation({
|
||||
state: layerState,
|
||||
services,
|
||||
@@ -72,17 +125,13 @@ export function createPresentation({
|
||||
: t('ISOLATED VIEW'),
|
||||
layerState._showProjection ? t('PROJ MONITOR') : t('PROJ OFF'),
|
||||
layerState._coverageMode === 'viewshed' ? t('VIEWSHED') : null,
|
||||
t('CAL {value}', {
|
||||
value: calBadge.replace('-', ' ').toUpperCase(),
|
||||
t('CAL {value}', { value: summaryCalLabel(calBadge) }),
|
||||
t('SRC {value}', {
|
||||
value: summarySourceLabel(
|
||||
health?.sourceKind || active.camera.feedType || 'image',
|
||||
),
|
||||
}),
|
||||
health?.sourceKind
|
||||
? t('SRC {value}', {
|
||||
value: String(health.sourceKind).toUpperCase(),
|
||||
})
|
||||
: t('SRC {value}', {
|
||||
value: String(active.camera.feedType || 'image').toUpperCase(),
|
||||
}),
|
||||
t('{value} CONTEXT', { value: viewBand.toUpperCase() }),
|
||||
t('{value} CONTEXT', { value: summaryViewBandLabel(viewBand) }),
|
||||
// Identified hardware: append the model's datasheet specs + attribution.
|
||||
active.camera.spec
|
||||
? t('{spec} · SPECS: CCTV-DATABASE.COM', {
|
||||
|
||||
Reference in new issue
Block a user