mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(i18n): keep English pixel-identical and stored data English
From the English-parity and breakage review against main:
- English wording back to main: "Configured Source", the "SHARED SUBJECT"
fallback, CCTV calibration "RANGE 120m".
- Voice header and CCTV calibration chip/select tweaks for long labels now
apply to non-English only (html:not([lang='en'])); English matches main.
The scene select font change is scoped the same way.
- New scenes and shots store their default title in English ("Scene 3",
"Shot 2") and translate it when shown, so a title captured in one language
does not leak into projects, storage or shared JSON.
- Choosing a language with storage blocked keeps it in ?lang instead of
silently doing nothing.
- One picked import file shows its name again.
- qa-l9-matrix reads the voice state from data-status, not the label.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a28e29e05e
commit
9d01f9bfcd
15 files changed
+118
-54
No files matched your search
@@ -1,5 +1,4 @@
|
||||
import { formatNumber, t, msg } from '../i18n/index.js';
|
||||
import { formatQuantity } from './unitLabels.js';
|
||||
|
||||
/** Shortest-wrap signed degrees, for heading offsets typed as absolute values. */
|
||||
const signedNormalizeDeg = (deg) => ((((deg + 180) % 360) + 360) % 360) - 180;
|
||||
@@ -73,8 +72,14 @@ function calValueText(field, value) {
|
||||
minimumFractionDigits: field.decimals,
|
||||
maximumFractionDigits: field.decimals,
|
||||
};
|
||||
if (field.unit === 'm')
|
||||
return `${t(field.label)} ${formatQuantity(Number(value), 'm', options)}`;
|
||||
if (field.unit === 'm') {
|
||||
// English keeps its compact "RANGE 120m"; other languages space the unit.
|
||||
const metres = formatNumber(Number(value), {
|
||||
useGrouping: false,
|
||||
...options,
|
||||
});
|
||||
return `${t(field.label)} ${t('{value}m', { value: metres })}`;
|
||||
}
|
||||
const number = formatNumber(Number(value), {
|
||||
useGrouping: false,
|
||||
...options,
|
||||
|
||||
@@ -39,7 +39,7 @@ const SOURCE_MESSAGES = Object.freeze({
|
||||
|
||||
const SOURCE_LABELS = Object.freeze({
|
||||
'Configured source': msg('Configured source'),
|
||||
'Configured Source': msg('Configured source'),
|
||||
'Configured Source': msg('Configured Source'),
|
||||
'Synthetic fallback': msg('Synthetic fallback'),
|
||||
});
|
||||
|
||||
|
||||
@@ -126,9 +126,13 @@ export function createSceneDialog(title, onClose) {
|
||||
const chosen = document.createElement('span');
|
||||
const showChosen = () => {
|
||||
const count = node.files?.length || 0;
|
||||
chosen.textContent = count
|
||||
? tn(count, '{count} file selected', '{count} files selected')
|
||||
: t('No file chosen');
|
||||
// One file shows its name, like the browser's own picker did.
|
||||
chosen.textContent =
|
||||
count === 1
|
||||
? node.files[0].name
|
||||
: count
|
||||
? tn(count, '{count} file selected', '{count} files selected')
|
||||
: t('No file chosen');
|
||||
};
|
||||
showChosen();
|
||||
row.append(chooser, chosen);
|
||||
|
||||
@@ -193,7 +193,7 @@ export class ShareRestoration {
|
||||
state: 'acquiring',
|
||||
detail: t('SHARED {subject}', {
|
||||
subject: String(
|
||||
result.label ? t(result.label) : t('entity'),
|
||||
result.label ? t(result.label) : t('subject'),
|
||||
).toUpperCase(),
|
||||
}),
|
||||
persistent: true,
|
||||
|
||||
@@ -139,15 +139,14 @@ export class ShellFeedback {
|
||||
// the flap keeps textContent equal to the settled label throughout, so
|
||||
// this stays a no-op on the repeat ticks exactly as it did before.
|
||||
if (presentation.label)
|
||||
if (presentation.label)
|
||||
setSplitFlapText(
|
||||
this._trafficSyncLabel,
|
||||
localizeStatusLabel(presentation.label),
|
||||
{
|
||||
// Completion is a timing signal, not another animated loading stage.
|
||||
immediate: !presentation.busy,
|
||||
},
|
||||
);
|
||||
setSplitFlapText(
|
||||
this._trafficSyncLabel,
|
||||
localizeStatusLabel(presentation.label),
|
||||
{
|
||||
// Completion is a timing signal, not another animated loading stage.
|
||||
immediate: !presentation.busy,
|
||||
},
|
||||
);
|
||||
// Written on every change INCLUDING the empty settled value — the reducer
|
||||
// clears the progress number once the sync lands, and a truthiness guard
|
||||
// here would strand the last "..." beside the settled label.
|
||||
|
||||
+26
-11
@@ -106,26 +106,22 @@ html[lang='es'] .cctv-controls {
|
||||
}
|
||||
|
||||
/* Click-to-edit effective-pose readout (viewshed/gizmo design §3d): compact
|
||||
value chips replace the seven offset sliders. Tight enough that the seven
|
||||
chips take two rows in a long-label language too, which keeps SAVE / RESET
|
||||
in view; chips grow to fill each row, and a value never wraps inside one. */
|
||||
value chips replace the seven offset sliders. */
|
||||
.cctv-cal-readout {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.cctv-cal-value {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.3px;
|
||||
letter-spacing: 0.7px;
|
||||
color: #91edff;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 7px;
|
||||
padding: 4px;
|
||||
padding: 4px 7px;
|
||||
cursor: text;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
@@ -175,9 +171,8 @@ html[lang='es'] .cctv-controls {
|
||||
border-radius: 7px;
|
||||
color: var(--text-primary);
|
||||
padding: 6px 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -389,3 +384,23 @@ html[lang='es'] .cctv-controls {
|
||||
color: #a8f7ff;
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
|
||||
/* Longer translated labels and units: tighter calibration chips keep the
|
||||
seven values to two rows so SAVE / RESET stay in view, and the camera
|
||||
select uses the compact mono face. English keeps its original look. */
|
||||
html:not([lang='en']) .cctv-cal-readout {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
html:not([lang='en']) .cctv-cal-value {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.3px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #cctv-camera-select {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
@@ -158,9 +158,8 @@
|
||||
border-radius: 7px;
|
||||
color: var(--text-primary);
|
||||
padding: 6px 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -466,3 +465,9 @@
|
||||
.director-sharing-dialog::backdrop {
|
||||
background: rgba(0, 8, 14, 0.65);
|
||||
}
|
||||
|
||||
html:not([lang='en']) #scene-select {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
@@ -89,17 +89,18 @@
|
||||
cost past the card edge. The kicker already collapses to zero width here;
|
||||
taking it out of the row returns its gap to the state label. The row also
|
||||
reaches left over the gutter beside the mic ring: at heading height the
|
||||
ring has already curved away. */
|
||||
#command-dock .gev-voice-heading {
|
||||
ring has already curved away. English fits as it is, so this applies to
|
||||
the other languages only. */
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading {
|
||||
gap: 0.2rem;
|
||||
margin-left: -0.6rem;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-heading > .gev-voice-kicker {
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading > .gev-voice-kicker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-heading > #gev-voice-status {
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading > #gev-voice-status {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -108,24 +109,25 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-cost {
|
||||
html:not([lang='en']) #command-dock .gev-voice-cost {
|
||||
gap: 0.16rem;
|
||||
padding-left: 0.1rem;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-tier-btn {
|
||||
html:not([lang='en']) #command-dock .gev-voice-tier-btn {
|
||||
min-width: 0;
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-cost-value {
|
||||
html:not([lang='en']) #command-dock .gev-voice-cost-value {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Live hints ("Ask or command", "Requesting microphone") run longer in other
|
||||
languages; give them a second line from the spare space under the readout
|
||||
rather than cutting them mid-word. */
|
||||
#command-dock
|
||||
html:not([lang='en'])
|
||||
#command-dock
|
||||
#gev-voice-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening'],
|
||||
|
||||
Reference in new issue
Block a user