diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index 99ebe1d1f..c3970214a 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -386,7 +386,11 @@ "modules": [ "src/ui/visualInput.js", "src/ui/applicationShortcuts.js", - "src/ui/styleParameters.js" + "src/ui/styleParameters.js", + "src/i18n/index.js", + "src/i18n/locale.js", + "src/i18n/locales/es.json", + "src/i18n/locales/ru.json" ], "external": [] }, @@ -407,7 +411,11 @@ "src/styles/thermal.js", "src/styles/anime.js", "src/styles/noir.js", - "src/styles/snow.js" + "src/styles/snow.js", + "src/i18n/index.js", + "src/i18n/locale.js", + "src/i18n/locales/es.json", + "src/i18n/locales/ru.json" ], "external": ["cesium"] }, diff --git a/src/hudA11y.test.mjs b/src/hudA11y.test.mjs index 920933852..0c0fd6fe2 100644 --- a/src/hudA11y.test.mjs +++ b/src/hudA11y.test.mjs @@ -28,6 +28,6 @@ test('the first-run checkbox keeps its native visible label', () => { }); test('generated style sliders use the visible parameter label as their name', () => { - assert.match(parameters, /label\.textContent\s*=\s*metadata\.label;/); - assert.match(parameters, /slider\.setAttribute\(['"]aria-label['"],\s*metadata\.label\)/); + assert.match(parameters, /label\.textContent\s*=\s*t\(metadata\.label\);/); + assert.match(parameters, /slider\.setAttribute\(['"]aria-label['"],\s*t\(metadata\.label\)\)/); }); diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 5ccdd163b..3ee5163c1 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -859,5 +859,19 @@ "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov · Unmatched: simulated": "EN VIVO · Vías: OpenStreetMap · Flujo: TomTom · {percent}% cob. · Sin coincidencia: simulado", "LIVE · Roads: OpenStreetMap · Flow: TomTom · {percent}% cov": "EN VIVO · Vías: OpenStreetMap · Flujo: TomTom · {percent}% cob.", "SIMULATED · Roads: OpenStreetMap · Flow: TomTom (no matches)": "SIMULADO · Vías: OpenStreetMap · Flujo: TomTom (sin coincidencias)", - "Data source unavailable": "Fuente de datos no disponible" + "Data source unavailable": "Fuente de datos no disponible", + "Saturation": "Saturación", + "Edge Thickness": "Grosor de bordes", + "Contrast": "Contraste", + "Grain": "Grano", + "Vignette": "Viñeta", + "Pixelation": "Pixelado", + "Distortion": "Distorsión", + "Instability": "Inestabilidad", + "Wind": "Viento", + "Gain": "Ganancia", + "Scanlines": "Líneas de barrido", + "Sensitivity": "Sensibilidad", + "WHOT/BHOT": "WHOT/BHOT", + "Ironbow": "Ironbow" } diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index dd1164bb8..46d99e42a 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -776,7 +776,7 @@ "Feather": "Растушёвка", "Celestial": "Небо", "Clean UI": "Чистый интерфейс", - "Bloom": "Bloom", + "Bloom": "Свечение", "Sharpen": "Резкость", "Initializing photorealistic world...": "Инициализация фотореалистичного мира...", "NEAREST": "БЛИЖАЙШАЯ", @@ -866,5 +866,19 @@ "Voice model: {pendingId} — click to switch to {target}; applies next session": "Голосовая модель: {pendingId} — нажмите, чтобы переключиться на {target}; вступит в силу со следующей сессии", "Estimated session cost on {modelId} — {responses} response(s). Warns at {warn}, ends the session at {cap}.": "Оценочная стоимость сессии на {modelId} — ответов: {responses}. Предупреждение при {warn}, завершение сессии при {cap}.", "Radio did not start": "Не удалось запустить радио", - "Running command": "Выполнение команды" + "Running command": "Выполнение команды", + "Saturation": "Насыщенность", + "Edge Thickness": "Толщина контуров", + "Contrast": "Контраст", + "Grain": "Зерно", + "Vignette": "Виньетка", + "Pixelation": "Пикселизация", + "Distortion": "Искажение", + "Instability": "Нестабильность", + "Wind": "Ветер", + "Gain": "Усиление", + "Scanlines": "Строки развёртки", + "Sensitivity": "Чувствительность", + "WHOT/BHOT": "WHOT/BHOT", + "Ironbow": "Ironbow" } diff --git a/src/styles/anime.js b/src/styles/anime.js index bfe51a685..deb79c355 100644 --- a/src/styles/anime.js +++ b/src/styles/anime.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * Anime / Studio Ghibli Style — Cel-shading + Saturation boost * Transforms the world into an animated film look @@ -5,8 +7,8 @@ export const animeShader = { name: 'anime', uniforms: { - saturation: { default: 1.0, min: 0, max: 2, label: 'Saturation' }, - edgeThick: { default: 0.5, min: 0, max: 1, label: 'Edge Thickness' }, + saturation: { default: 1.0, min: 0, max: 2, label: msg('Saturation') }, + edgeThick: { default: 0.5, min: 0, max: 1, label: msg('Edge Thickness') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/styles/noir.js b/src/styles/noir.js index ff01d58fc..5ea458859 100644 --- a/src/styles/noir.js +++ b/src/styles/noir.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * Noir Style — Desaturation + High Contrast + Vignette * Classic film noir / detective movie look @@ -5,9 +7,9 @@ export const noirShader = { name: 'noir', uniforms: { - contrastAmt: { default: 1.2, min: 0, max: 2, label: 'Contrast' }, - grainAmt: { default: 0.5, min: 0, max: 1, label: 'Grain' }, - vignetteAmt: { default: 0.5, min: 0, max: 1, label: 'Vignette' }, + contrastAmt: { default: 1.2, min: 0, max: 2, label: msg('Contrast') }, + grainAmt: { default: 0.5, min: 0, max: 1, label: msg('Grain') }, + vignetteAmt: { default: 0.5, min: 0, max: 1, label: msg('Vignette') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/styles/retro.js b/src/styles/retro.js index 7ff76e1d4..396f95853 100644 --- a/src/styles/retro.js +++ b/src/styles/retro.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * CRT Terminal — Dynamic CRT Monitor Aesthetic * Pixelation + Bayer dithering + barrel distortion + scanlines + @@ -12,9 +14,9 @@ export const retroShader = { name: 'retro', uniforms: { - pixelation: { default: 5.0, min: 1, max: 10, label: 'Pixelation' }, - distortion: { default: 0, min: 0, max: 1, label: 'Distortion' }, - instability: { default: 0.4, min: 0, max: 1, label: 'Instability' }, + pixelation: { default: 5.0, min: 1, max: 10, label: msg('Pixelation') }, + distortion: { default: 0, min: 0, max: 1, label: msg('Distortion') }, + instability: { default: 0.4, min: 0, max: 1, label: msg('Instability') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/styles/snow.js b/src/styles/snow.js index e2ada6ad7..f4bfebe86 100644 --- a/src/styles/snow.js +++ b/src/styles/snow.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * Snow Style — White overlay + Desaturation + Falling particles * Transforms the world into a winter wonderland @@ -5,8 +7,8 @@ export const snowShader = { name: 'snow', uniforms: { - density: { default: 0.6, min: 0, max: 1, label: 'Density' }, - wind: { default: 0.5, min: 0, max: 1, label: 'Wind' }, + density: { default: 0.6, min: 0, max: 1, label: msg('Density') }, + wind: { default: 0.5, min: 0, max: 1, label: msg('Wind') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/styles/surveillance.js b/src/styles/surveillance.js index 8ab7eb914..e290dddcb 100644 --- a/src/styles/surveillance.js +++ b/src/styles/surveillance.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * Night Vision / NVG — PVS-14 Image Intensifier Emulation * P43 phosphor green, intensifier tube bloom, circular vignette, @@ -12,10 +14,10 @@ export const nightVisionShader = { name: 'surveillance', uniforms: { - gain: { default: 0.55, min: 0, max: 1, label: 'Gain' }, - bloom: { default: 0.3, min: 0, max: 1, label: 'Bloom' }, - scanlineStr: { default: 1.0, min: 0, max: 1, label: 'Scanlines' }, - pixelation: { default: 2.5, min: 1, max: 6, label: 'Pixelation' }, + gain: { default: 0.55, min: 0, max: 1, label: msg('Gain') }, + bloom: { default: 0.3, min: 0, max: 1, label: msg('Bloom') }, + scanlineStr: { default: 1.0, min: 0, max: 1, label: msg('Scanlines') }, + pixelation: { default: 2.5, min: 1, max: 6, label: msg('Pixelation') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/styles/thermal.js b/src/styles/thermal.js index fe0796810..90aff5312 100644 --- a/src/styles/thermal.js +++ b/src/styles/thermal.js @@ -1,3 +1,5 @@ +import { msg } from '../i18n/index.js'; + /** * FLIR — Forward Looking Infrared * Military-grade thermal imaging with White-Hot/Black-Hot modes, @@ -14,11 +16,11 @@ export const thermalShader = { name: 'thermal', uniforms: { - sensitivity: { default: 0.75, min: 0, max: 1, label: 'Sensitivity' }, - bloom: { default: 0.65, min: 0, max: 1, label: 'Bloom' }, - mode: { default: 0.0, min: 0, max: 1, label: 'WHOT/BHOT' }, - pixelation: { default: 1.5, min: 1, max: 6, label: 'Pixelation' }, - palette: { default: 0.0, min: 0, max: 1, label: 'Ironbow' }, + sensitivity: { default: 0.75, min: 0, max: 1, label: msg('Sensitivity') }, + bloom: { default: 0.65, min: 0, max: 1, label: msg('Bloom') }, + mode: { default: 0.0, min: 0, max: 1, label: msg('WHOT/BHOT') }, + pixelation: { default: 1.5, min: 1, max: 6, label: msg('Pixelation') }, + palette: { default: 0.0, min: 0, max: 1, label: msg('Ironbow') }, }, fragmentShader: /* glsl */ ` uniform sampler2D colorTexture; diff --git a/src/ui/styleParameters.js b/src/ui/styleParameters.js index 6afa3e36b..9b453a69a 100644 --- a/src/ui/styleParameters.js +++ b/src/ui/styleParameters.js @@ -1,3 +1,5 @@ +import { t } from '../i18n/index.js'; + /** * Own generated parameter rows and their input listeners. * The caller supplies uniform metadata/value access and rendering/persistence @@ -30,11 +32,11 @@ export function createStyleParameters({ row.className = 'param-slider-row'; const label = documentRef.createElement('span'); label.className = 'param-label'; - label.textContent = metadata.label; + label.textContent = t(metadata.label); const slider = documentRef.createElement('input'); slider.type = 'range'; slider.className = 'param-slider'; - slider.setAttribute('aria-label', metadata.label); + slider.setAttribute('aria-label', t(metadata.label)); slider.min = metadata.min; slider.max = metadata.max; slider.step = metadata.max <= 1 ? '0.01' : '0.1';