Files
gods-eye-view/src/ui/styleParameters.js
T
German KhokhlovandClaude Opus 5.5 7db0abc396 fix(i18n): Spanish review fixes; report translations left in English
A screenshot review of the Spanish UI found:

- Text left in English. Catalog entries copied from English (Bloom,
  Cyber, Pin, NEWS, SITE, ON/OFF, RANGE, PITCH, HGT…) and the radio
  music genres, which never went through t(). The genres are translated
  in es and ru now.
- Wording: gender agreement, calques of the English, hints that name
  buttons that do not exist, and one concept worded two ways.
- Layout: the DESACTIVADO pill squeezed layer names until words broke
  mid-word; the scene and camera selects showed one word; a collapsed
  rail title pushed its button out of the card; the image slot line and
  a comparison readout were cut or split.

The layout fixes are scoped to languages other than English, so English
stays pixel-identical. Visual-style slider values now use the locale's
decimal separator (0,85).

npm run i18n now reports a translation that is identical to the
English, unless src/i18n/same-as-english.json lists it for that language
(codes, product names, words spelled the same).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 21:53:57 +04:00

79 lines
2.7 KiB
JavaScript

import { formatNumber, t } from '../i18n/index.js';
/** A slider value in the UI language: 0.85 in English, 0,85 in Spanish. */
const formatParam = (value, digits) =>
formatNumber(value, {
minimumFractionDigits: digits,
maximumFractionDigits: digits,
useGrouping: false,
});
/**
* Own generated parameter rows and their input listeners.
* The caller supplies uniform metadata/value access and rendering/persistence
* callbacks. Replacing or destroying rows revokes all old input handlers.
* @param {object} options
* @param {HTMLElement} options.container Container owned by this component.
* @param {Document} [options.documentRef] Document used to construct rows.
* @returns {{render: Function, clear: Function, destroy: Function}}
*/
export function createStyleParameters({
container,
documentRef = container.ownerDocument,
}) {
let listeners = [];
let destroyed = false;
const clear = () => {
if (destroyed) return;
for (const [slider, listener] of listeners)
slider.removeEventListener('input', listener);
listeners = [];
container.replaceChildren();
};
return {
/** Render uniform metadata with readValue, writeValue and onChange callbacks. */
render({ uniforms, readValue, writeValue, onChange }) {
if (destroyed) return;
clear();
for (const [name, metadata] of Object.entries(uniforms)) {
const row = documentRef.createElement('div');
row.className = 'param-slider-row';
const label = documentRef.createElement('span');
label.className = 'param-label';
label.textContent = t(metadata.label);
const slider = documentRef.createElement('input');
slider.type = 'range';
slider.className = 'param-slider';
slider.setAttribute('aria-label', t(metadata.label));
slider.min = metadata.min;
slider.max = metadata.max;
slider.step = metadata.max <= 1 ? '0.01' : '0.1';
slider.value = readValue(name);
const digits = metadata.max <= 1 ? 2 : 1;
const valueDisplay = documentRef.createElement('span');
valueDisplay.className = 'param-value';
valueDisplay.textContent = formatParam(
parseFloat(slider.value),
digits,
);
const onInput = () => {
const value = parseFloat(slider.value);
writeValue(name, value);
valueDisplay.textContent = formatParam(value, digits);
onChange();
};
slider.addEventListener('input', onInput);
listeners.push([slider, onInput]);
row.append(label, slider, valueDisplay);
container.appendChild(row);
}
},
clear,
destroy() {
if (destroyed) return;
clear();
destroyed = true;
},
};
}