From cbaa8bdddafcb908d1e209e368541e0d774d96fc Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 01:32:31 -0700 Subject: [PATCH 1/7] feat(i18n): wrap fork feature DrawSettings strings in T() Wrap user-facing strings in the fork-diverged feature DrawSettings (and GetFeatureSummary) for LightLimitFix, ScreenSpaceGI, VolumetricLighting, DynamicCubemaps, Upscaling, RenderDoc, and VR/VR stereo so they are translatable. English defaults are the exact current inline literals, so there is no behavior or visual change for English users (T() falls back to the default when no translation exists). Part of #123. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Features/DynamicCubemaps.cpp | 4 +-- src/Features/LightLimitFix.cpp | 25 ++++++++++-------- src/Features/LightLimitFix.h | 15 +++++------ src/Features/RenderDoc.cpp | 8 +++--- src/Features/ScreenSpaceGI.h | 24 ++++++++++-------- src/Features/Upscaling.cpp | 35 +++++++++++++------------- src/Features/VR.h | 16 ++++++------ src/Features/VRStereoOptimizations.cpp | 33 ++++++++++++------------ src/Features/VolumetricLighting.cpp | 2 +- 9 files changed, 86 insertions(+), 76 deletions(-) diff --git a/src/Features/DynamicCubemaps.cpp b/src/Features/DynamicCubemaps.cpp index f18f961b2d..6f36712db6 100644 --- a/src/Features/DynamicCubemaps.cpp +++ b/src/Features/DynamicCubemaps.cpp @@ -33,7 +33,7 @@ void DynamicCubemaps::DrawSettings() if (ImGui::TreeNodeEx(T(TKEY("screen_space_reflections"), "Screen Space Reflections"), ImGuiTreeNodeFlags_DefaultOpen)) { recompileFlag |= ImGui::Checkbox(T(TKEY("enable_ssr"), "Enable Screen Space Reflections"), reinterpret_cast(&settings.EnabledSSR)); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Enable Screen Space Reflections on Water"); + ImGui::Text("%s", T(TKEY("enable_ssr_tooltip"), "Enable Screen Space Reflections on Water")); } if (globals::game::isVR) Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::EnabledSSR); @@ -119,7 +119,7 @@ void DynamicCubemaps::DrawSettings() ImGui::TreePop(); } if (globals::game::isVR) { - if (ImGui::TreeNodeEx("Advanced VR Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNodeEx(T(TKEY("advanced_vr_settings"), "Advanced VR Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { Util::RenderImGuiSettingsTree(iniVRCubeMapSettings, "VR"); Util::RenderImGuiSettingsTree(hiddenVRCubeMapSettings, "hiddenVR"); ImGui::TreePop(); diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 78013aa874..5c4bc46687 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -3,6 +3,7 @@ #include "Features/LightLimitFix/SettingsSanitize.h" #include "Features/LightLimitFix/ShadowCasterMath.h" #include "Globals.h" +#include "I18n/I18n.h" #include "InverseSquareLighting.h" #include "LinearLighting.h" #include "Utils/UI.h" @@ -116,7 +117,7 @@ void LightLimitFix::DrawSettings() ShadowCasterManager::DrawSettings(settings.ShadowSettings); - if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNodeEx(T("feature.light_limit_fix.statistics", "Statistics"), ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text(std::format("Clustered Light Count : {}", lightCount).c_str()); ImGui::Text(std::format("Particle Lights Count : {}", currentParticleLights.size()).c_str()); ImGui::TreePop(); @@ -318,12 +319,12 @@ void LightLimitFix::DrawSettings() } /////////////////////////////// - ImGui::SeparatorText("Debug"); + ImGui::SeparatorText(T("feature.light_limit_fix.debug", "Debug")); - if (ImGui::TreeNode("Light Limit Visualization")) { - ImGui::Checkbox("Enable Lights Visualisation", &EnableLightsVisualisation); + if (ImGui::TreeNode(T("feature.light_limit_fix.light_limit_vis", "Light Limit Visualization"))) { + ImGui::Checkbox(T("feature.light_limit_fix.enable_lights_vis", "Enable Lights Visualisation"), &EnableLightsVisualisation); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Enables visualization of the light limit\n"); + ImGui::Text("%s", T("feature.light_limit_fix.enable_lights_vis_tooltip", "Enables visualization of the light limit\n")); } { @@ -344,15 +345,17 @@ void LightLimitFix::DrawSettings() // persisted value that might still exist from older builds. int visMode = std::clamp(static_cast(LightsVisualisationMode), 0, IM_ARRAYSIZE(comboOptions) - 1); - ImGui::Combo("Lights Visualisation Mode", &visMode, comboOptions, IM_ARRAYSIZE(comboOptions)); + ImGui::Combo(T("feature.light_limit_fix.lights_vis_mode", "Lights Visualisation Mode"), &visMode, comboOptions, IM_ARRAYSIZE(comboOptions)); LightsVisualisationMode = static_cast(visMode); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text( - "Light Limit: Red when the strict light limit is reached (>=7 portal-strict lights).\n" - "\n" - "Strict Lights Count: Heatmap of portal-strict lights per pixel (blue=0, red=15).\n" - "\n" - "Clustered Lights Count: Heatmap of dynamic lights in each screen tile (blue=0, red=128)."); + "%s", + T("feature.light_limit_fix.lights_vis_mode_tooltip", + "Light Limit: Red when the strict light limit is reached (>=7 portal-strict lights).\n" + "\n" + "Strict Lights Count: Heatmap of portal-strict lights per pixel (blue=0, red=15).\n" + "\n" + "Clustered Lights Count: Heatmap of dynamic lights in each screen tile (blue=0, red=128).")); ShadowCasterManager::DrawVisualisationTooltipShadowModes(); } } diff --git a/src/Features/LightLimitFix.h b/src/Features/LightLimitFix.h index de52e2f058..d57064ea18 100644 --- a/src/Features/LightLimitFix.h +++ b/src/Features/LightLimitFix.h @@ -27,13 +27,14 @@ struct LightLimitFix : OverlayFeature virtual std::pair> GetFeatureSummary() override { return { - "Light Limit Fix removes the vanilla game's 4-light limit, allowing unlimited dynamic lights in scenes. " - "It also extends shadow support to all point and spot lights.", - { "Removes 4-light limit", - "Unlimited dynamic lights", - "Shadow support for point and spot lights", - "Improved lighting quality", - "Particle lights from configurable INI" } + T("feature.light_limit_fix.description", + "Light Limit Fix removes the vanilla game's 4-light limit, allowing unlimited dynamic lights in scenes. " + "It also extends shadow support to all point and spot lights."), + { T("feature.light_limit_fix.key_feature_1", "Removes 4-light limit"), + T("feature.light_limit_fix.key_feature_2", "Unlimited dynamic lights"), + T("feature.light_limit_fix.key_feature_3", "Shadow support for point and spot lights"), + T("feature.light_limit_fix.key_feature_4", "Improved lighting quality"), + T("feature.light_limit_fix.key_feature_5", "Particle lights from configurable INI") } }; } diff --git a/src/Features/RenderDoc.cpp b/src/Features/RenderDoc.cpp index 19af1ac3d1..6b7527ec70 100644 --- a/src/Features/RenderDoc.cpp +++ b/src/Features/RenderDoc.cpp @@ -142,7 +142,7 @@ void RenderDoc::DrawSettings() // Include enable toggle and annotation forcing logic here bool prevRenderDocCapture = settings.enableCapture; - if (ImGui::Checkbox("Enable RenderDoc Capture", &settings.enableCapture)) { + if (ImGui::Checkbox(T(TKEY("enable_capture"), "Enable RenderDoc Capture"), &settings.enableCapture)) { if (settings.enableCapture && !prevRenderDocCapture) { globals::state->useFrameAnnotations = globals::state->frameAnnotations; globals::state->frameAnnotations = true; @@ -156,8 +156,8 @@ void RenderDoc::DrawSettings() // pending banner below it -- the previous ordering drew the banner between // the checkbox and the tooltip, so a pending banner would steal the hover. Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::enableCapture, [] { - ImGui::TextUnformatted("Enable RenderDoc frame capture for providing debug captures to the Open Shaders team (or upstream Community Shaders for upstream-relevant issues)."); - ImGui::TextUnformatted("Enabling capture will force-enable frame annotations for easier debugging and will restore the previous setting when disabled."); + ImGui::TextUnformatted(T(TKEY("enable_capture_tooltip"), "Enable RenderDoc frame capture for providing debug captures to the Open Shaders team (or upstream Community Shaders for upstream-relevant issues).")); + ImGui::TextUnformatted(T(TKEY("enable_capture_tooltip2"), "Enabling capture will force-enable frame annotations for easier debugging and will restore the previous setting when disabled.")); }); // The rest of the UI renders only when capture is active @@ -171,7 +171,7 @@ void RenderDoc::DrawSettings() } if (!renderDocCaptureEnabled && renderDocActive) { - ImGui::TextColored(themeSettings.StatusPalette.Warning, "Performance will be severely impacted until the game is restarted."); + ImGui::TextColored(themeSettings.StatusPalette.Warning, "%s", T(TKEY("restart_to_disable"), "Performance will be severely impacted until the game is restarted.")); return; } diff --git a/src/Features/ScreenSpaceGI.h b/src/Features/ScreenSpaceGI.h index 133eb43fa7..73f4aedd2d 100644 --- a/src/Features/ScreenSpaceGI.h +++ b/src/Features/ScreenSpaceGI.h @@ -19,23 +19,25 @@ struct ScreenSpaceGI : Feature virtual std::pair> GetFeatureSummary() override { std::string desc = - "Screen Space Global Illumination adds realistic indirect lighting and " - "ambient occlusion to the game. This technique simulates how light " - "bounces off surfaces to illuminate other objects naturally."; + T("feature.screen_space_gi.description", + "Screen Space Global Illumination adds realistic indirect lighting and " + "ambient occlusion to the game. This technique simulates how light " + "bounces off surfaces to illuminate other objects naturally."); if (globals::game::isVR) { desc += - "\n\nWarning: In VR, this feature may have visual artifacts and " - "can have a significant performance impact due to the nature of " - "screen space effects."; + T("feature.screen_space_gi.vr_warning", + "\n\nWarning: In VR, this feature may have visual artifacts and " + "can have a significant performance impact due to the nature of " + "screen space effects."); } return std::make_pair( desc, std::vector{ - "Realistic indirect lighting", - "Enhanced ambient occlusion", - "Improved visual depth and atmosphere", - "Temporal denoising for smooth results", - "Configurable quality and performance settings" }); + T("feature.screen_space_gi.key_feature_1", "Realistic indirect lighting"), + T("feature.screen_space_gi.key_feature_2", "Enhanced ambient occlusion"), + T("feature.screen_space_gi.key_feature_3", "Improved visual depth and atmosphere"), + T("feature.screen_space_gi.key_feature_4", "Temporal denoising for smooth results"), + T("feature.screen_space_gi.key_feature_5", "Configurable quality and performance settings") }); } virtual void RestoreDefaultSettings() override; diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 73582c075b..61e93f8b45 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -225,7 +225,7 @@ void Upscaling::DrawSettings() modeLabels.push_back(upscaleModes[i].c_str()); if (openCompositeBlocksUpscaling) ImGui::BeginDisabled(); - ImGui::Combo("Method", (int*)currentUpscaleMode, modeLabels.data(), (int)modeLabels.size()); + ImGui::Combo(T(TKEY("method"), "Method"), (int*)currentUpscaleMode, modeLabels.data(), (int)modeLabels.size()); if (openCompositeBlocksUpscaling) ImGui::EndDisabled(); if (auto _tt = Util::HoverTooltipWrapper()) { @@ -325,7 +325,7 @@ void Upscaling::DrawSettings() const float displayScale = 1.0f / GetQualityModeRatio(settings.qualityMode); std::string labelWithScale = std::format("{} ( {:.2f}x )", baseLabel, displayScale); - ImGui::SliderInt("Upscale Preset", (int*)&settings.qualityMode, 0, 4, labelWithScale.c_str()); + ImGui::SliderInt(T(TKEY("upscale_preset"), "Upscale Preset"), (int*)&settings.qualityMode, 0, 4, labelWithScale.c_str()); // Pending-diff vs the boot snapshot the runtime upscaler is // actually using. Without this the slider change looks like a @@ -354,7 +354,7 @@ void Upscaling::DrawSettings() }; ImGui::Combo(T(TKEY("dlss_model_preset"), "DLSS Model Preset"), (int*)&settings.presetDLSS, presets, 5); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Choose which DLSS AI model preset to use."); + ImGui::Text("%s", T(TKEY("dlss_model_preset_tooltip"), "Choose which DLSS AI model preset to use.")); ImGui::Text("Each model offers different visual quality, performance, and motion stability."); ImGui::Text("Set to 'Default' for automatic selection based on your Upscale Preset and hardware."); } @@ -411,12 +411,12 @@ void Upscaling::DrawSettings() const bool frameGenerationDx12PathActive = IsFrameGenerationDx12PathActive(); if (!globals::game::isVR) { - if (ImGui::TreeNodeEx("Frame Generation", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Frame Generation interpolates real frames with generated ones for a smoother experience"); - ImGui::Text("Uses AMD FSR Frame Generation technology"); + if (ImGui::TreeNodeEx(T(TKEY("frame_generation"), "Frame Generation"), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("%s", T(TKEY("frame_generation_desc"), "Frame Generation interpolates real frames with generated ones for a smoother experience")); + ImGui::Text("%s", T(TKEY("frame_generation_tech"), "Uses AMD FSR Frame Generation technology")); if (HasFrameGenModule()) - ImGui::Text("AMD FSR Frame Generation is available."); - ImGui::Text("Requires a D3D11 to D3D12 proxy which can create compatibility issues"); + ImGui::Text("%s", T(TKEY("frame_generation_available"), "AMD FSR Frame Generation is available.")); + ImGui::Text("%s", T(TKEY("frame_generation_proxy_note"), "Requires a D3D11 to D3D12 proxy which can create compatibility issues")); if (!isWindowed) { Util::Text::Warning("Warning: Requires windowed mode"); @@ -431,7 +431,7 @@ void Upscaling::DrawSettings() } bool fgEnabled = settings.frameGenerationMode != 0; - if (ImGui::Checkbox("Frame Generation", &fgEnabled)) + if (ImGui::Checkbox(T(TKEY("frame_generation"), "Frame Generation"), &fgEnabled)) settings.frameGenerationMode = fgEnabled ? 1 : 0; Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationMode, "Interpolate real frames with generated ones for a smoother experience. Uses AMD FSR Frame\n" @@ -442,7 +442,7 @@ void Upscaling::DrawSettings() ImGui::BeginDisabled(); bool flEnabled = settings.frameLimitMode != 0; - if (ImGui::Checkbox("Frame Limit (Variable Refresh Rate)", &flEnabled)) + if (ImGui::Checkbox(T(TKEY("frame_limit_vrr"), "Frame Limit (Variable Refresh Rate)"), &flEnabled)) settings.frameLimitMode = flEnabled ? 1 : 0; if (!frameGenerationDx12PathActive) @@ -450,17 +450,17 @@ void Upscaling::DrawSettings() ImGui::TextWrapped("Allows frame generation to function on low refresh rate monitors. Detected: %.2f Hz", refreshRate); bool fgForce = settings.frameGenerationForceEnable != 0; - if (ImGui::Checkbox("Force Enable Frame Generation", &fgForce)) + if (ImGui::Checkbox(T(TKEY("force_enable_frame_generation"), "Force Enable Frame Generation"), &fgForce)) settings.frameGenerationForceEnable = fgForce ? 1 : 0; Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationForceEnable, "Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\n" "displays. Useful for laptops and older monitors at the cost of less headroom for the\n" "generated frames."); - ImGui::Checkbox("Frame Generation in Menus", &settings.frameGenerationAllowInMenus); + ImGui::Checkbox(T(TKEY("frame_generation_in_menus"), "Frame Generation in Menus"), &settings.frameGenerationAllowInMenus); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::TextUnformatted("Keeps frame generation active while game menus are open."); - ImGui::TextUnformatted("May feel smoother, but increases menu input latency."); + ImGui::TextUnformatted(T(TKEY("frame_generation_in_menus_tooltip_1"), "Keeps frame generation active while game menus are open.")); + ImGui::TextUnformatted(T(TKEY("frame_generation_in_menus_tooltip_2"), "May feel smoother, but increases menu input latency.")); } ImGui::TreePop(); @@ -563,7 +563,7 @@ void Upscaling::DrawSettings() ImGui::EndDisabled(); } - if (ImGui::TreeNodeEx("Backend Diagnostics")) { + if (ImGui::TreeNodeEx(T(TKEY("backend_diagnostics"), "Backend Diagnostics"))) { // Streamline log level selection const char* logLevels[] = { "Off", "Default", "Verbose" }; // streamlineLogLevel is sanitized in LoadSettings (runs on every load, @@ -573,8 +573,9 @@ void Upscaling::DrawSettings() settings.streamlineLogLevel = static_cast(logLevelIdx); } Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::streamlineLogLevel, - "Verbosity of the NVIDIA Streamline backend logs. Useful for debugging issues with DLSS / " - "DLSS-G."); + T(TKEY("streamline_logging_tooltip"), + "Verbosity of the NVIDIA Streamline backend logs. Useful for debugging issues with DLSS / " + "DLSS-G.")); // VR Debug visualization -- per-eye buffers and native inputs if (globals::game::isVR) { diff --git a/src/Features/VR.h b/src/Features/VR.h index fbaf952545..4986446434 100644 --- a/src/Features/VR.h +++ b/src/Features/VR.h @@ -1,4 +1,5 @@ #pragma once +#include "I18n/I18n.h" #include "Menu.h" #include "OverlayFeature.h" #include "Utils/Input.h" @@ -96,17 +97,18 @@ struct VR : OverlayFeature //============================================================================= virtual inline std::string GetName() override { return "VR"; } + virtual std::string GetDisplayName() override { return T("feature.vr.name", "VR"); } virtual inline std::string GetShortName() override { return "VR"; } virtual std::pair> GetFeatureSummary() override { return { - "Provides VR-specific optimizations and enhancements for Open Shaders, improving performance and visual quality in virtual reality environments.", - { "Depth buffer culling optimization for VR performance", - "In-scene overlay menu with HMD/Controller/Fixed World attach modes", - "VR controller input with customizable button mappings", - "Grip-to-drag overlay positioning with depth control", - "Configurable occlusion culling parameters", - "Enhanced VR compatibility with SteamVR and OpenComposite" } + T("feature.vr.description", "Provides VR-specific optimizations and enhancements for Open Shaders, improving performance and visual quality in virtual reality environments."), + { T("feature.vr.key_feature_1", "Depth buffer culling optimization for VR performance"), + T("feature.vr.key_feature_2", "In-scene overlay menu with HMD/Controller/Fixed World attach modes"), + T("feature.vr.key_feature_3", "VR controller input with customizable button mappings"), + T("feature.vr.key_feature_4", "Grip-to-drag overlay positioning with depth control"), + T("feature.vr.key_feature_5", "Configurable occlusion culling parameters"), + T("feature.vr.key_feature_6", "Enhanced VR compatibility with SteamVR and OpenComposite") } }; } diff --git a/src/Features/VRStereoOptimizations.cpp b/src/Features/VRStereoOptimizations.cpp index 6df2f4be52..686cf4b4f0 100644 --- a/src/Features/VRStereoOptimizations.cpp +++ b/src/Features/VRStereoOptimizations.cpp @@ -2,6 +2,7 @@ #include "ExtendedMaterials.h" #include "Globals.h" +#include "I18n/I18n.h" #include "Menu.h" #include "State.h" #include "Utils/D3D.h" @@ -253,34 +254,34 @@ void VRStereoOptimizations::ClearPomOffsetTexture() void VRStereoOptimizations::DrawSettings() { - const char* modeNames[] = { "Off", "Enable" }; + const char* modeNames[] = { T("feature.vr_stereo.off", "Off"), T("feature.vr_stereo.enable", "Enable") }; int currentMode = static_cast(settings.stereoMode); - if (ImGui::Combo("Enable Stereo Reprojection", ¤tMode, modeNames, IM_ARRAYSIZE(modeNames))) + if (ImGui::Combo(T("feature.vr_stereo.enable_stereo_reprojection", "Enable Stereo Reprojection"), ¤tMode, modeNames, IM_ARRAYSIZE(modeNames))) settings.stereoMode = static_cast(currentMode); - Util::AddTooltip("Reprojects Eye 0 (left) pixels into Eye 1 (right) using depth and motion data,\nskipping redundant full shading where the views overlap.\nReduces GPU cost in VR by shading each pixel fewer times per frame."); + Util::AddTooltip(T("feature.vr_stereo.enable_stereo_reprojection_tooltip", "Reprojects Eye 0 (left) pixels into Eye 1 (right) using depth and motion data,\nskipping redundant full shading where the views overlap.\nReduces GPU cost in VR by shading each pixel fewer times per frame.")); if (globals::game::isVR) Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::stereoMode); if (settings.stereoMode == StereoMode::Off) return; - ImGui::SliderFloat("Disocclusion Depth Threshold", &settings.disocclusionDepthThreshold, 0.001f, 0.1f, "%.4f"); + ImGui::SliderFloat(T("feature.vr_stereo.disocclusion_depth_threshold", "Disocclusion Depth Threshold"), &settings.disocclusionDepthThreshold, 0.001f, 0.1f, "%.4f"); - ImGui::SliderFloat("Forward Occlusion Scale", &settings.forwardOcclusionScale, 0.0f, 1.0f, "%.2f"); - Util::AddTooltip("Prevents Eye 0 silhouette edges from bleeding onto Eye 1 backgrounds.\nFires when Eye 0 depth is within this fraction of Eye 1 depth (e.g. 0.5 = Eye 0 less than 2x Eye 1 depth).\nLower = more aggressive. 0 = disabled."); + ImGui::SliderFloat(T("feature.vr_stereo.forward_occlusion_scale", "Forward Occlusion Scale"), &settings.forwardOcclusionScale, 0.0f, 1.0f, "%.2f"); + Util::AddTooltip(T("feature.vr_stereo.forward_occlusion_scale_tooltip", "Prevents Eye 0 silhouette edges from bleeding onto Eye 1 backgrounds.\nFires when Eye 0 depth is within this fraction of Eye 1 depth (e.g. 0.5 = Eye 0 less than 2x Eye 1 depth).\nLower = more aggressive. 0 = disabled.")); if (globals::state->IsDeveloperMode()) { - if (ImGui::TreeNode("Debug")) { - ImGui::SliderFloat("Full Blend Distance", &settings.fullBlendDistance, 0.0f, 10000.0f, "%.0f"); - Util::AddTooltip("Geometry closer than this distance (game units) is fully shaded in both eyes and bilaterally blended for 2x supersampling. 0 = disabled."); - - ImGui::SliderFloat("POM Depth Scale", &settings.pomDepthScale, 0.0f, 500.0f, "%.1f"); - Util::AddTooltip("Scale factor for POM depth correction in stereo reprojection.\n1.0 = physical scale. Increase for more visible POM stereo depth."); - ImGui::Checkbox("Skip Pixel Reprojection", &settings.debugSkipMerge); - ImGui::Checkbox("Full Blend Depth View", &settings.debugFullBlendDepth); - ImGui::Checkbox("Debug POM Depth", &settings.debugPOMDepth); + if (ImGui::TreeNode(T("feature.vr_stereo.debug", "Debug"))) { + ImGui::SliderFloat(T("feature.vr_stereo.full_blend_distance", "Full Blend Distance"), &settings.fullBlendDistance, 0.0f, 10000.0f, "%.0f"); + Util::AddTooltip(T("feature.vr_stereo.full_blend_distance_tooltip", "Geometry closer than this distance (game units) is fully shaded in both eyes and bilaterally blended for 2x supersampling. 0 = disabled.")); + + ImGui::SliderFloat(T("feature.vr_stereo.pom_depth_scale", "POM Depth Scale"), &settings.pomDepthScale, 0.0f, 500.0f, "%.1f"); + Util::AddTooltip(T("feature.vr_stereo.pom_depth_scale_tooltip", "Scale factor for POM depth correction in stereo reprojection.\n1.0 = physical scale. Increase for more visible POM stereo depth.")); + ImGui::Checkbox(T("feature.vr_stereo.skip_pixel_reprojection", "Skip Pixel Reprojection"), &settings.debugSkipMerge); + ImGui::Checkbox(T("feature.vr_stereo.full_blend_depth_view", "Full Blend Depth View"), &settings.debugFullBlendDepth); + ImGui::Checkbox(T("feature.vr_stereo.debug_pom_depth", "Debug POM Depth"), &settings.debugPOMDepth); if (settings.debugFullBlendDepth) - ImGui::TextColored(ImVec4(0, 1, 1, 1), " Cyan = full blend zone (closer = stronger tint)"); + ImGui::TextColored(ImVec4(0, 1, 1, 1), "%s", T("feature.vr_stereo.full_blend_zone_hint", " Cyan = full blend zone (closer = stronger tint)")); ImGui::Text("Stencil swaps this frame: %u", stencilSwapCount); ImGui::TreePop(); } diff --git a/src/Features/VolumetricLighting.cpp b/src/Features/VolumetricLighting.cpp index 4f3c4b37b6..fc85d63b65 100644 --- a/src/Features/VolumetricLighting.cpp +++ b/src/Features/VolumetricLighting.cpp @@ -27,7 +27,7 @@ void VolumetricLighting::DrawSettings() { // VR pre-allocates VL render targets at boot, so a runtime toggle can't // resize them -- gate only in VR. Non-VR resizes live. - if (ImGui::Checkbox("Enable Volumetric Lighting in Exteriors", &settings.ExteriorEnabled)) + if (ImGui::Checkbox(T(TKEY("enable_exteriors"), "Enable Volumetric Lighting in Exteriors"), &settings.ExteriorEnabled)) SetupVL(); if (globals::game::isVR) Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::ExteriorEnabled, From 86cb51db01ea5499defa7cb9bfd3b52656a42b62 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 01:32:55 -0700 Subject: [PATCH 2/7] feat(i18n): wrap fork menu strings in T() Wrap user-facing strings in the fork-customized menu code: the advanced settings tab (shader-compiler stats, parallelism metrics, logging, runtime debug), the home/FAQ/setup page, and the feature-issues panel. Format-string sites use std::vformat(T(...), make_format_args(...)) and the home welcome line uses I18n::Format with named placeholders, matching existing codebase i18n conventions. English output is unchanged. Part of #123. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/FeatureIssues.cpp | 40 +++---- src/Menu/AdvancedSettingsRenderer.cpp | 162 +++++++++++++------------- src/Menu/HomePageRenderer.cpp | 82 ++++++------- 3 files changed, 145 insertions(+), 139 deletions(-) diff --git a/src/FeatureIssues.cpp b/src/FeatureIssues.cpp index 71a08b5407..9bb349bfc1 100644 --- a/src/FeatureIssues.cpp +++ b/src/FeatureIssues.cpp @@ -616,7 +616,7 @@ namespace FeatureIssues ImGui::SameLine(); ImGui::Text("%s", T("menu.issues.core_feature_installed", "Core feature already installed")); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::TextWrapped("This feature is already included as part of the core Open Shaders installation. Uninstall this feature with your mod manager."); + ImGui::TextWrapped("%s", T("menu.issues.core_feature_installed_tooltip", "This feature is already included as part of the core Open Shaders installation. Uninstall this feature with your mod manager.")); } } else if (issue.IsVersionMismatch()) { ImGui::SameLine(); @@ -732,10 +732,10 @@ namespace FeatureIssues ImGui::TextWrapped("%s", T("menu.issues.unknown_delete_warning", "This is an UNKNOWN feature. If it modified core shader files (outside of its own folder), deleting these files alone will NOT fix shader compilation issues.")); ImGui::Spacing(); - ImGui::TextColored(theme.StatusPalette.Warning, "If compilation issues persist after deletion:"); - ImGui::BulletText("Completely uninstall the feature via your mod manager"); - ImGui::BulletText("Check for modified files in Data/Shaders/ (not in feature subfolders)"); - ImGui::BulletText("Consider reinstalling Open Shaders if issues persist"); + ImGui::TextColored(theme.StatusPalette.Warning, "%s", T("menu.issues.compilation_persist_warning", "If compilation issues persist after deletion:")); + ImGui::BulletText("%s", T("menu.issues.uninstall_via_mod_manager", "Completely uninstall the feature via your mod manager")); + ImGui::BulletText("%s", T("menu.issues.check_modified_files", "Check for modified files in Data/Shaders/ (not in feature subfolders)")); + ImGui::BulletText("%s", T("menu.issues.reinstall_cs", "Consider reinstalling Open Shaders if issues persist")); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); @@ -1532,15 +1532,15 @@ namespace FeatureIssues auto* menu = Menu::GetSingleton(); const auto& themeSettings = menu->GetTheme(); - auto sectionWrapper = Util::SectionWrapper("Feature Issue Testing", - "These tools create test INI files to trigger all known feature issue types for testing purposes.", + auto sectionWrapper = Util::SectionWrapper(T("menu.issues.test.feature_issue_testing", "Feature Issue Testing"), + T("menu.issues.test.feature_issue_testing_desc", "These tools create test INI files to trigger all known feature issue types for testing purposes."), themeSettings.Palette.Text); if (sectionWrapper) { const bool hasActiveTests = HasActiveTestInis(); if (hasActiveTests) { // Warning section using theme colors ImGui::PushStyleColor(ImGuiCol_Text, themeSettings.StatusPalette.RestartNeeded); - ImGui::TextWrapped("Test INI files are currently active. Restart CS to see feature issues."); + ImGui::TextWrapped("%s", T("menu.issues.test.active_inis_warning", "Test INI files are currently active. Restart CS to see feature issues.")); ImGui::PopStyleColor(); // Show detailed test state information ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, themeSettings.StatusPalette.RestartNeeded); @@ -1557,19 +1557,19 @@ namespace FeatureIssues themeSettings.StatusPalette.RestartNeeded, themeSettings.StatusPalette.CurrentHotkey); - if (ImGui::Button("Create Test INIs", { -1, 0 })) { + if (ImGui::Button(T("menu.issues.test.create_test_inis", "Create Test INIs"), { -1, 0 })) { auto testInis = CreateTestInis(); logger::info("Created {} test INI files for feature issue testing", testInis.size()); } } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Creates test INI files that trigger all known feature issue cases:\n" - "- Obsolete features (ComplexParallaxMaterials, TerrainBlending, etc.)\n" - "- Unknown features (fake non-existent features)\n" - "- Version mismatch (modifies existing feature version)\n" - "Restart CS after creating to see the issues in action."); + ImGui::Text("%s", T("menu.issues.test.create_test_inis_tooltip", + "Creates test INI files that trigger all known feature issue cases:\n" + "- Obsolete features (ComplexParallaxMaterials, TerrainBlending, etc.)\n" + "- Unknown features (fake non-existent features)\n" + "- Version mismatch (modifies existing feature version)\n" + "Restart CS after creating to see the issues in action.")); } // Restore button @@ -1580,7 +1580,7 @@ namespace FeatureIssues themeSettings.StatusPalette.Error, themeSettings.StatusPalette.CurrentHotkey); - if (ImGui::Button("Restore", { -1, 0 })) { + if (ImGui::Button(T("menu.issues.test.restore", "Restore"), { -1, 0 })) { auto& testInis = GetCurrentTestInis(); if (RestoreOriginalState(testInis)) { logger::info("Successfully restored original state"); @@ -1591,10 +1591,10 @@ namespace FeatureIssues } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Removes all test INI files and restores any modified INI files to their original state.\n" - "This undoes all changes made by 'Create Test INIs'.\n" - "Restart CS after restoring to see normal operation."); + ImGui::Text("%s", T("menu.issues.test.restore_tooltip", + "Removes all test INI files and restores any modified INI files to their original state.\n" + "This undoes all changes made by 'Create Test INIs'.\n" + "Restart CS after restoring to see normal operation.")); } } } diff --git a/src/Menu/AdvancedSettingsRenderer.cpp b/src/Menu/AdvancedSettingsRenderer.cpp index d5ea7de491..5d09edafcc 100644 --- a/src/Menu/AdvancedSettingsRenderer.cpp +++ b/src/Menu/AdvancedSettingsRenderer.cpp @@ -35,7 +35,7 @@ void AdvancedSettingsRenderer::RenderAdvancedSettings( ImGui::EndTabItem(); } - if (MenuFonts::BeginTabItemWithFont("Disable at Boot", Menu::FontRole::Subheading)) { + if (MenuFonts::BeginTabItemWithFont(T("menu.advanced.tab_disable_at_boot", "Disable at Boot"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##DisableAtBootContent", ImVec2(0, 0), false)) { RenderDisableAtBootSection(drawDisableAtBootSettings); } @@ -51,7 +51,7 @@ void AdvancedSettingsRenderer::RenderAdvancedSettings( ImGui::EndTabItem(); } - if (MenuFonts::BeginTabItemWithFont("Testing", Menu::FontRole::Subheading)) { + if (MenuFonts::BeginTabItemWithFont(T("menu.advanced.tab_testing", "Testing"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##TestingContent", ImVec2(0, 0), false)) { RenderTestingSection(); } @@ -119,39 +119,39 @@ void AdvancedSettingsRenderer::RenderShaderCompileFlags() // Half-precision (partial precision) shader compile flag bool partialPrecision = globals::state->enablePartialPrecision.load(std::memory_order_relaxed); - if (ImGui::Checkbox("Half Precision (Partial Precision)", &partialPrecision)) { + if (ImGui::Checkbox(T("menu.advanced.half_precision", "Half Precision (Partial Precision)"), &partialPrecision)) { globals::state->enablePartialPrecision.store(partialPrecision, std::memory_order_relaxed); // Force a recompile so the flag actually takes effect on subsequent shader builds. shaderCache->Clear(); } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Adds D3DCOMPILE_PARTIAL_PRECISION to the shader compiler flags.\n" - "Lets fxc downgrade unmarked float ops to FP16 where it can prove safety, " - "on top of the existing min16float type hints.\n" - "On FP16-capable GPUs (Pascal+ / GCN+ / Skylake+) this can halve register " - "pressure and double ALU throughput, but it can also introduce minor visual " - "differences in shaders that haven't been audited for precision sensitivity.\n" - "Toggling this clears the shader cache and triggers a full recompile."); + ImGui::Text("%s", T("menu.advanced.half_precision_tooltip", + "Adds D3DCOMPILE_PARTIAL_PRECISION to the shader compiler flags.\n" + "Lets fxc downgrade unmarked float ops to FP16 where it can prove safety, " + "on top of the existing min16float type hints.\n" + "On FP16-capable GPUs (Pascal+ / GCN+ / Skylake+) this can halve register " + "pressure and double ALU throughput, but it can also introduce minor visual " + "differences in shaders that haven't been audited for precision sensitivity.\n" + "Toggling this clears the shader cache and triggers a full recompile.")); } // Avoid flow control compiler flag (transient — not saved to config because the // right setting depends on the current scene, not the user). bool avoidFlowControl = globals::state->enableAvoidFlowControl.load(std::memory_order_relaxed); - if (ImGui::Checkbox("Avoid Flow Control", &avoidFlowControl)) { + if (ImGui::Checkbox(T("menu.advanced.avoid_flow_control", "Avoid Flow Control"), &avoidFlowControl)) { globals::state->enableAvoidFlowControl.store(avoidFlowControl, std::memory_order_relaxed); // Force a recompile so the flag actually takes effect on subsequent shader builds. shaderCache->Clear(); } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Adds D3DCOMPILE_AVOID_FLOW_CONTROL to the shader compiler flags.\n" - "Forces fxc to flatten branches into predicated ops rather than emitting " - "dynamic flow control. Often a win for short branch bodies and uniformly-" - "taken branches; usually a loss for long divergent branches that vanilla " - "flow control would skip entirely.\n" - "Resets every launch. Toggling this clears the shader cache and triggers a " - "full recompile."); + ImGui::Text("%s", T("menu.advanced.avoid_flow_control_tooltip", + "Adds D3DCOMPILE_AVOID_FLOW_CONTROL to the shader compiler flags.\n" + "Forces fxc to flatten branches into predicated ops rather than emitting " + "dynamic flow control. Often a win for short branch bodies and uniformly-" + "taken branches; usually a loss for long divergent branches that vanilla " + "flow control would skip entirely.\n" + "Resets every launch. Toggling this clears the shader cache and triggers a " + "full recompile.")); } } @@ -176,14 +176,14 @@ void AdvancedSettingsRenderer::RenderShaderThreading() shaderCache->compilationThreadCount = std::clamp(shaderCache->compilationThreadCount, 1, maxThreads); shaderCache->backgroundCompilationThreadCount = std::clamp(shaderCache->backgroundCompilationThreadCount, 1, maxThreads); - ImGui::SliderInt("Compiler Threads", &shaderCache->compilationThreadCount, 1, maxThreads); + ImGui::SliderInt(T("menu.advanced.compiler_threads", "Compiler Threads"), &shaderCache->compilationThreadCount, 1, maxThreads); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", T("menu.advanced.compiler_threads_tooltip", "Number of threads used to compile shaders at startup. " "Defaults to all logical cores minus one for OS headroom (E-cores included). " "Higher values finish compilation faster but may make the system less responsive.")); } - ImGui::SliderInt("Background Compiler Threads", &shaderCache->backgroundCompilationThreadCount, 1, maxThreads); + ImGui::SliderInt(T("menu.advanced.background_compiler_threads", "Background Compiler Threads"), &shaderCache->backgroundCompilationThreadCount, 1, maxThreads); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", T("menu.advanced.background_compiler_threads_tooltip", "Number of threads used to compile shaders during gameplay. " @@ -200,13 +200,13 @@ void AdvancedSettingsRenderer::RenderShaderCacheControls() // File Watcher option bool useFileWatcher = shaderCache->UseFileWatcher(); - if (ImGui::Checkbox("Enable File Watcher", &useFileWatcher)) { + if (ImGui::Checkbox(T("menu.advanced.enable_file_watcher", "Enable File Watcher"), &useFileWatcher)) { shaderCache->SetFileWatcher(useFileWatcher); } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Automatically recompile shaders on file change. " - "Intended for development."); + ImGui::Text("%s", T("menu.advanced.enable_file_watcher_tooltip", + "Automatically recompile shaders on file change. " + "Intended for development.")); } // Dump Shaders option @@ -231,7 +231,7 @@ void AdvancedSettingsRenderer::RenderShaderReplacementTable() { auto state = globals::state; - Util::DrawSectionHeader("Replace Original Shaders"); + Util::DrawSectionHeader(T("menu.advanced.replace_original_shaders", "Replace Original Shaders")); if (ImGui::BeginTable("##ReplaceToggles", 3, ImGuiTableFlags_SizingStretchSame)) { globals::state->ForEachShaderTypeWithIndex([&](auto type, int classIndex) { @@ -277,11 +277,11 @@ void AdvancedSettingsRenderer::RenderShaderCompileStatistics() { auto shaderCache = globals::shaderCache; - if (!ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + if (!ImGui::TreeNodeEx(T("menu.advanced.statistics", "Statistics"), ImGuiTreeNodeFlags_DefaultOpen)) { return; } - ImGui::Text("Shader Compiler : %s", shaderCache->GetShaderStatsString().c_str()); + ImGui::Text(T("menu.advanced.shader_compiler_stats", "Shader Compiler : %s"), shaderCache->GetShaderStatsString().c_str()); // Derived parallelism metrics are computed lazily on demand and only shown // once compilation has completed to avoid per-frame analysis while compiling. @@ -290,66 +290,70 @@ void AdvancedSettingsRenderer::RenderShaderCompileStatistics() if (parallelism.has_value()) { const auto& p = parallelism.value(); ImGui::Spacing(); - ImGui::TextDisabled("Parallelism (derived from %zu compiled tasks)", p.sampleCount); + ImGui::TextDisabled(T("menu.advanced.parallelism_header", "Parallelism (derived from %zu compiled tasks)"), p.sampleCount); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Computed lazily from the last completed build."); - ImGui::Text("Only evaluated when this Statistics section is open."); + ImGui::Text("%s", T("menu.advanced.parallelism_tooltip_1", "Computed lazily from the last completed build.")); + ImGui::Text("%s", T("menu.advanced.parallelism_tooltip_2", "Only evaluated when this Statistics section is open.")); } - ImGui::Text("Work (W, sum of task wall times): %s", Util::FormatDuration(p.workMs).c_str()); + ImGui::Text(T("menu.advanced.work_metric", "Work (W, sum of task wall times): %s"), Util::FormatDuration(p.workMs).c_str()); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Total compile work: sum of all per-shader wall-clock compile times."); - ImGui::Text("This is not CPU time; it is accumulated task elapsed time."); - ImGui::Text("Equivalent serial time on one worker if overhead stayed the same."); + ImGui::Text("%s", T("menu.advanced.work_tooltip_1", "Total compile work: sum of all per-shader wall-clock compile times.")); + ImGui::Text("%s", T("menu.advanced.work_tooltip_2", "This is not CPU time; it is accumulated task elapsed time.")); + ImGui::Text("%s", T("menu.advanced.work_tooltip_3", "Equivalent serial time on one worker if overhead stayed the same.")); } - ImGui::Text("Span (S, longest): %s", Util::FormatDuration(p.spanMs).c_str()); + ImGui::Text(T("menu.advanced.span_metric", "Span (S, longest): %s"), Util::FormatDuration(p.spanMs).c_str()); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Critical-path lower bound, approximated by the single slowest shader."); - ImGui::Text("Even infinite cores cannot finish faster than this."); + ImGui::Text("%s", T("menu.advanced.span_tooltip_1", "Critical-path lower bound, approximated by the single slowest shader.")); + ImGui::Text("%s", T("menu.advanced.span_tooltip_2", "Even infinite cores cannot finish faster than this.")); } - ImGui::Text("Makespan (T_p): %s", Util::FormatDuration(p.makespanMs).c_str()); + ImGui::Text(T("menu.advanced.makespan_metric", "Makespan (T_p): %s"), Util::FormatDuration(p.makespanMs).c_str()); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Observed wall-clock duration for the full shader build."); + ImGui::Text("%s", T("menu.advanced.makespan_tooltip", "Observed wall-clock duration for the full shader build.")); } - ImGui::Text("Queue wait (avg/max): %s / %s", + ImGui::Text(T("menu.advanced.queue_wait_metric", "Queue wait (avg/max): %s / %s"), Util::FormatDuration(p.avgQueueWaitMs).c_str(), Util::FormatDuration(p.maxQueueWaitMs).c_str()); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Time spent waiting in the ready queue before a worker started compilation."); - ImGui::Text("Useful for identifying scheduler-induced delay separate from compile cost."); + ImGui::Text("%s", T("menu.advanced.queue_wait_tooltip_1", "Time spent waiting in the ready queue before a worker started compilation.")); + ImGui::Text("%s", T("menu.advanced.queue_wait_tooltip_2", "Useful for identifying scheduler-induced delay separate from compile cost.")); } - ImGui::Text("Average parallelism (W/S): %.2fx", p.avgParallelism); + ImGui::Text(T("menu.advanced.avg_parallelism_metric", "Average parallelism (W/S): %.2fx"), p.avgParallelism); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Average useful concurrency in this workload."); - ImGui::Text("Roughly the worker count where adding more cores gives diminishing returns."); + ImGui::Text("%s", T("menu.advanced.avg_parallelism_tooltip_1", "Average useful concurrency in this workload.")); + ImGui::Text("%s", T("menu.advanced.avg_parallelism_tooltip_2", "Roughly the worker count where adding more cores gives diminishing returns.")); } - ImGui::Text("Infinite-core efficiency (S/T_p): %.1f%%", 100.0 * p.infiniteCoreEfficiency); + ImGui::Text(T("menu.advanced.infinite_core_efficiency_metric", "Infinite-core efficiency (S/T_p): %.1f%%"), 100.0 * p.infiniteCoreEfficiency); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("How close runtime is to the infinite-core lower bound."); - ImGui::Text("100%% means T_p == S."); + ImGui::Text("%s", T("menu.advanced.infinite_core_efficiency_tooltip_1", "How close runtime is to the infinite-core lower bound.")); + ImGui::Text(T("menu.advanced.infinite_core_efficiency_tooltip_2", "100%% means T_p == S.")); } - ImGui::Text("Infinite-core gap: %.1f%%", p.infiniteCoreGapPercent); + ImGui::Text(T("menu.advanced.infinite_core_gap_metric", "Infinite-core gap: %.1f%%"), p.infiniteCoreGapPercent); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Distance from ideal infinite-core time."); - ImGui::Text("Defined as 100 * (1 - S / T_p). Lower is better."); + ImGui::Text("%s", T("menu.advanced.infinite_core_gap_tooltip_1", "Distance from ideal infinite-core time.")); + ImGui::Text("%s", T("menu.advanced.infinite_core_gap_tooltip_2", "Defined as 100 * (1 - S / T_p). Lower is better.")); } ImGui::Spacing(); - ImGui::TextDisabled("Infinite-core efficiency"); + ImGui::TextDisabled("%s", T("menu.advanced.infinite_core_efficiency", "Infinite-core efficiency")); float efficiency = static_cast(std::clamp(p.infiniteCoreEfficiency, 0.0, 1.0)); - ImGui::ProgressBar(efficiency, ImVec2(-1.0f, 0.0f), std::format("{:.1f}% efficient / {:.1f}% gap", 100.0 * p.infiniteCoreEfficiency, p.infiniteCoreGapPercent).c_str()); + double effPct = 100.0 * p.infiniteCoreEfficiency; + double gapPct = p.infiniteCoreGapPercent; + ImGui::ProgressBar(efficiency, ImVec2(-1.0f, 0.0f), std::vformat(T("menu.advanced.efficiency_progress", "{:.1f}% efficient / {:.1f}% gap"), std::make_format_args(effPct, gapPct)).c_str()); ImGui::Spacing(); - ImGui::TextDisabled("Relative durations (normalized)"); + ImGui::TextDisabled("%s", T("menu.advanced.relative_durations", "Relative durations (normalized)")); double maxMs = std::max({ p.workMs, p.spanMs, p.makespanMs, 1.0 }); auto drawRelativeBar = [maxMs](const char* label, double value) { float ratio = static_cast(std::clamp(value / maxMs, 0.0, 1.0)); ImGui::TextUnformatted(label); ImGui::SameLine(); - ImGui::ProgressBar(ratio, ImVec2(-1.0f, 0.0f), std::format("{} ({:.1f}%)", Util::FormatDuration(value), 100.0 * ratio).c_str()); + std::string durStr = Util::FormatDuration(value); + double pctVal = 100.0 * ratio; + ImGui::ProgressBar(ratio, ImVec2(-1.0f, 0.0f), std::vformat(T("menu.advanced.relative_bar_format", "{} ({:.1f}%)"), std::make_format_args(durStr, pctVal)).c_str()); }; - drawRelativeBar("Span (S)", p.spanMs); - drawRelativeBar("Makespan (T_p)", p.makespanMs); - drawRelativeBar("Work (W)", p.workMs); + drawRelativeBar(T("menu.advanced.span_label", "Span (S)"), p.spanMs); + drawRelativeBar(T("menu.advanced.makespan_label", "Makespan (T_p)"), p.makespanMs); + drawRelativeBar(T("menu.advanced.work_label", "Work (W)"), p.workMs); } } @@ -357,10 +361,10 @@ void AdvancedSettingsRenderer::RenderShaderCompileStatistics() auto topSlow = shaderCache->GetTopSlowTasks(3); if (!topSlow.empty()) { ImGui::Spacing(); - ImGui::TextDisabled("Top %zu Slowest Shaders (last build)", topSlow.size()); + ImGui::TextDisabled(T("menu.advanced.top_slowest_shaders", "Top %zu Slowest Shaders (last build)"), topSlow.size()); for (size_t i = 0; i < topSlow.size(); ++i) { const auto& rec = topSlow[i]; - ImGui::Text("#%zu %s (weight %d)", i + 1, + ImGui::Text(T("menu.advanced.shader_slow_entry", "#%zu %s (weight %d)"), i + 1, Util::FormatDuration(rec.elapsedMs).c_str(), rec.priority); ImGui::SameLine(); ImGui::TextDisabled("%s", rec.key.c_str()); @@ -371,7 +375,7 @@ void AdvancedSettingsRenderer::RenderShaderCompileStatistics() } // Allow copying the full key with a right-click if (ImGui::BeginPopupContextItem(std::format("##slowcopy{}", i).c_str())) { - if (ImGui::MenuItem("Copy key")) { + if (ImGui::MenuItem(T("menu.advanced.copy_key", "Copy key"))) { ImGui::SetClipboardText(rec.key.c_str()); } ImGui::EndPopup(); @@ -408,33 +412,33 @@ void AdvancedSettingsRenderer::RenderDiagnosticsSection() void AdvancedSettingsRenderer::RenderLoggingControls() { - Util::DrawSectionHeader("Logging"); + Util::DrawSectionHeader(T("menu.advanced.tab_logging", "Logging")); // Log Level selection. Resync from state every frame so external changes // (config reload, console command, another caller of SetLogLevel) don't // leave the combo displaying a stale selection. spdlog::level::level_enum logLevel = globals::state->GetLogLevel(); const char* items[] = { - "trace", - "debug", - "info", - "warn", - "err", - "critical", - "off" + T("menu.advanced.log_level_trace", "trace"), + T("menu.advanced.log_level_debug", "debug"), + T("menu.advanced.log_level_info", "info"), + T("menu.advanced.log_level_warn", "warn"), + T("menu.advanced.log_level_err", "err"), + T("menu.advanced.log_level_critical", "critical"), + T("menu.advanced.log_level_off", "off") }; int item_current = static_cast(logLevel); - if (ImGui::Combo("Log Level", &item_current, items, IM_ARRAYSIZE(items))) { + if (ImGui::Combo(T("menu.advanced.log_level", "Log Level"), &item_current, items, IM_ARRAYSIZE(items))) { globals::state->SetLogLevel(static_cast(item_current)); } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Log level. Trace is most verbose. Default is info."); + ImGui::Text("%s", T("menu.advanced.log_level_tooltip", "Log level. Trace is most verbose. Default is info.")); } ImGui::Columns(2, nullptr, false); // Dump Ini Settings button - if (ImGui::Button("Dump Ini Settings", { -1, 0 })) { + if (ImGui::Button(T("menu.advanced.dump_ini_settings", "Dump Ini Settings"), { -1, 0 })) { Util::DumpSettingsOptions(); } @@ -442,7 +446,7 @@ void AdvancedSettingsRenderer::RenderLoggingControls() // Open Logs button std::filesystem::path logPath = Util::PathHelpers::GetLogPath(); - if (!logPath.empty() && ImGui::Button("Open Logs", { -1, 0 })) { + if (!logPath.empty() && ImGui::Button(T("menu.advanced.open_logs", "Open Logs"), { -1, 0 })) { ShellExecuteA(NULL, "open", logPath.string().c_str(), NULL, NULL, SW_SHOWNORMAL); } @@ -454,13 +458,13 @@ void AdvancedSettingsRenderer::RenderRuntimeDebugControls() Util::DrawSectionHeader("Runtime Debug"); // Frame annotations toggle - ImGui::Checkbox("Frame Annotations", &globals::state->frameAnnotations); + ImGui::Checkbox(T("menu.advanced.frame_annotations", "Frame Annotations"), &globals::state->frameAnnotations); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Enable detailed frame annotations for debugging render passes and draw calls."); + ImGui::Text("%s", T("menu.advanced.frame_annotations_tooltip", "Enable detailed frame annotations for debugging render passes and draw calls.")); } // Debug addresses section - if (ImGui::TreeNodeEx("Addresses")) { + if (ImGui::TreeNodeEx(T("menu.advanced.addresses", "Addresses"))) { auto Renderer = globals::game::renderer; auto BSShaderAccumulator = *globals::game::currentAccumulator.get(); auto RendererShadowState = globals::game::shadowState; @@ -597,7 +601,7 @@ void AdvancedSettingsRenderer::RenderShaderBlockingPanel() // "Shader Blocking", so a nested CollapsingHeader was redundant noise. { ImGui::Spacing(); - Util::DrawSectionHeader("Active Shaders (Used Recently)"); + Util::DrawSectionHeader(T("menu.advanced.active_shaders_used_recently", "Active Shaders (Used Recently)")); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", T("menu.advanced.active_shaders_tooltip", "List of shaders that have been used in recent frames. " diff --git a/src/Menu/HomePageRenderer.cpp b/src/Menu/HomePageRenderer.cpp index 09a41cf4d1..35fc1a85ea 100644 --- a/src/Menu/HomePageRenderer.cpp +++ b/src/Menu/HomePageRenderer.cpp @@ -67,7 +67,9 @@ void HomePageRenderer::RenderWelcomeSection() ImVec2 windowSize = ImGui::GetWindowSize(); auto versionStr = Util::GetFormattedVersion(Plugin::VERSION); auto expectedTag = std::format("v{}", versionStr); - std::string titleWithVersion = Plugin::BUILD_DESCRIBE == expectedTag ? std::format("Welcome to Open Shaders {}", versionStr) : std::format("Welcome to Open Shaders {} [{}]", versionStr, Plugin::BUILD_DESCRIBE); + std::string titleWithVersion = Plugin::BUILD_DESCRIBE == expectedTag ? + I18n::GetSingleton()->Format("menu.home.welcome", { { "version", std::string(versionStr) } }, "Welcome to Open Shaders {version}") : + I18n::GetSingleton()->Format("menu.home.welcome_dev", { { "version", std::string(versionStr) }, { "build", std::string(Plugin::BUILD_DESCRIBE) } }, "Welcome to Open Shaders {version} [{build}]"); ImVec2 titleSize = ImGui::CalcTextSize(titleWithVersion.c_str()); ImGui::SetCursorPosX((windowSize.x - titleSize.x) * 0.5f); ImGui::Text("%s", titleWithVersion.c_str()); @@ -83,10 +85,10 @@ void HomePageRenderer::RenderWelcomeSection() ImGui::Spacing(); // Intro text - centered - const char* introText = + const char* introText = T("menu.home.intro", "Open Shaders is a fork of Community Shaders providing advanced graphics enhancements for Skyrim.\n" "This comprehensive collection of features brings modern rendering techniques\n" - "to enhance your visual experience."; + "to enhance your visual experience."); ImVec2 introSize = ImGui::CalcTextSize(introText); ImGui::SetCursorPosX((windowSize.x - introSize.x) * 0.5f); ImGui::TextWrapped("%s", introText); @@ -108,17 +110,17 @@ void HomePageRenderer::RenderQuickLinksSection() // Nexus button → the Open Shaders fork page (mod 180419). ImGui::Columns(3, nullptr, false); - if (ImGui::Button("Nexus Mods", ImVec2(-1, 0))) { + if (ImGui::Button(T("menu.home.nexus_mods", "Nexus Mods"), ImVec2(-1, 0))) { ShellExecuteA(NULL, "open", "https://www.nexusmods.com/skyrimspecialedition/mods/180419", NULL, NULL, SW_SHOWNORMAL); } ImGui::NextColumn(); - if (ImGui::Button("GitHub", ImVec2(-1, 0))) { + if (ImGui::Button(T("menu.home.github", "GitHub"), ImVec2(-1, 0))) { ShellExecuteA(NULL, "open", "https://github.com/alandtse/open-shaders", NULL, NULL, SW_SHOWNORMAL); } ImGui::NextColumn(); - if (ImGui::Button("Developer Wiki", ImVec2(-1, 0))) { + if (ImGui::Button(T("menu.home.dev_wiki", "Developer Wiki"), ImVec2(-1, 0))) { ShellExecuteA(NULL, "open", "https://github.com/alandtse/open-shaders/wiki", NULL, NULL, SW_SHOWNORMAL); } @@ -136,14 +138,14 @@ void HomePageRenderer::RenderFAQSection() ImGui::Separator(); // FAQ items with collapsible headers - if (ImGui::CollapsingHeader("What is Open Shaders?")) { - ImGui::TextWrapped( - "Open Shaders is a fork of Community Shaders that ships features the upstream project " - "has not yet released. Both projects are comprehensive graphics enhancement frameworks " - "for Skyrim that provide advanced lighting, materials, and visual effects. They're " - "designed to be modular, letting you enable only the features you want while " - "maintaining good performance. This fork preserves the upstream runtime layout so user " - "settings and themes are compatible."); + if (ImGui::CollapsingHeader(T("menu.faq.q1", "What is Open Shaders?"))) { + ImGui::TextWrapped("%s", T("menu.faq.a1", + "Open Shaders is a fork of Community Shaders that ships features the upstream project " + "has not yet released. Both projects are comprehensive graphics enhancement frameworks " + "for Skyrim that provide advanced lighting, materials, and visual effects. They're " + "designed to be modular, letting you enable only the features you want while " + "maintaining good performance. This fork preserves the upstream runtime layout so user " + "settings and themes are compatible.")); } if (ImGui::CollapsingHeader(T("menu.faq.q2", "How do I configure features?"))) { @@ -174,36 +176,36 @@ void HomePageRenderer::RenderFAQSection() "tab also includes upscaling options that can improve performance.")); } - if (ImGui::CollapsingHeader("Is Open Shaders compatible with ENB?")) { - ImGui::TextWrapped( - "No, Open Shaders (like upstream Community Shaders) is not compatible with ENB. The " - "plugin will automatically disable itself if ENB is detected."); + if (ImGui::CollapsingHeader(T("menu.faq.q6", "Is Open Shaders compatible with ENB?"))) { + ImGui::TextWrapped("%s", T("menu.faq.a6", + "No, Open Shaders (like upstream Community Shaders) is not compatible with ENB. The " + "plugin will automatically disable itself if ENB is detected.")); } - if (ImGui::CollapsingHeader("The menu hotkey isn't working!")) { - ImGui::TextWrapped( - "By default, Open Shaders uses the END key to open this menu. If your keyboard " - "doesn't have an END key or it's not working, you can change it in the General > Keybindings tab. " - "You can also edit the hotkey in the JSON configuration files."); + if (ImGui::CollapsingHeader(T("menu.faq.q7", "The menu hotkey isn't working!"))) { + ImGui::TextWrapped("%s", T("menu.faq.a7", + "By default, Open Shaders uses the END key to open this menu. If your keyboard " + "doesn't have an END key or it's not working, you can change it in the General > Keybindings tab. " + "You can also edit the hotkey in the JSON configuration files.")); } - if (ImGui::CollapsingHeader("I would like to help develop Open Shaders.")) { - ImGui::TextWrapped( - "Open Shaders is open source. Check out the upstream GitHub wiki for contribution " - "guidelines on the shared architecture; open issues and PRs against this repository for " - "fork-specific work, or against upstream Community Shaders for changes that benefit both " - "projects. Whether you're interested in shader programming, C++ development, or " - "documentation, there's always something to contribute."); + if (ImGui::CollapsingHeader(T("menu.faq.q8", "I would like to help develop Open Shaders."))) { + ImGui::TextWrapped("%s", T("menu.faq.a8", + "Open Shaders is open source. Check out the upstream GitHub wiki for contribution " + "guidelines on the shared architecture; open issues and PRs against this repository for " + "fork-specific work, or against upstream Community Shaders for changes that benefit both " + "projects. Whether you're interested in shader programming, C++ development, or " + "documentation, there's always something to contribute.")); } - if (ImGui::CollapsingHeader("Is Open Shaders open source?")) { - ImGui::TextWrapped( - "Yes! Open Shaders is completely open source and available on GitHub, as is upstream " - "Community Shaders. You can view the source code, report issues, suggest features, and " - "contribute to either project. Both are licensed under GPL, ensuring they remain free and " - "open for everyone. Branding materials and assets (icons, Nexus branding, typography, etc.) " - "are not covered by the GPL Licence. Any included assets may not be used without explicit " - "permission."); + if (ImGui::CollapsingHeader(T("menu.faq.q9", "Is Open Shaders open source?"))) { + ImGui::TextWrapped("%s", T("menu.faq.a9", + "Yes! Open Shaders is completely open source and available on GitHub, as is upstream " + "Community Shaders. You can view the source code, report issues, suggest features, and " + "contribute to either project. Both are licensed under GPL, ensuring they remain free and " + "open for everyone. Branding materials and assets (icons, Nexus branding, typography, etc.) " + "are not covered by the GPL Licence. Any included assets may not be used without explicit " + "permission.")); } } @@ -398,8 +400,8 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() }; // Version text - two lines, both centered (reduced spacing between lines) - const char* versionLine1 = "This appears to be a new install, update, or"; - const char* versionLine2 = "reinstallation of Open Shaders."; + const char* versionLine1 = T("menu.setup.new_install_line1", "This appears to be a new install, update, or"); + const char* versionLine2 = T("menu.setup.new_install_line2", "reinstallation of Open Shaders."); centerText(versionLine1); ImGui::Text("%s", versionLine1); From 79eb617ab47089e373c02b9f683e3c080b544aa8 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 01:33:20 -0700 Subject: [PATCH 3/7] chore(i18n): regen en.json for wrapped fork strings Run tools/extract-i18n.py --write so en.json contains the keys re-added by wrapping the fork feature and menu DrawSettings strings in T(). Restores the keys removed in a58e2df3 once their source strings are wrapped. Part of #123. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../CommunityShaders/Translations/en.json | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index e0888b1004..75a5ebd67f 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -546,12 +546,14 @@ "feature.cs_editor.wind_vs_player_tooltip_1": "- ~0° = Tailwind (wind behind player)", "feature.cs_editor.wind_vs_player_tooltip_2": "- ~±90° = Crosswind (left/right)", "feature.cs_editor.wind_vs_player_tooltip_3": "- ~±180° = Headwind (wind coming toward player)", + "feature.dynamic_cubemaps.advanced_vr_settings": "Advanced VR Settings", "feature.dynamic_cubemaps.color": "Color", "feature.dynamic_cubemaps.creator_info": "You must enable creator mode by adding the shader define CREATOR", "feature.dynamic_cubemaps.description": "Provides real-time environment mapping and reflections by generating dynamic cube maps that capture the surrounding environment, enabling realistic reflections on surfaces.", "feature.dynamic_cubemaps.dynamic_cubemap_creator": "Dynamic Cubemap Creator", "feature.dynamic_cubemaps.enable_creator": "Enable Creator", "feature.dynamic_cubemaps.enable_ssr": "Enable Screen Space Reflections", + "feature.dynamic_cubemaps.enable_ssr_tooltip": "Enable Screen Space Reflections on Water", "feature.dynamic_cubemaps.export": "Export", "feature.dynamic_cubemaps.key_feature_1": "Real-time environment capture for realistic reflections", "feature.dynamic_cubemaps.key_feature_2": "Dynamic cube map generation based on camera position", @@ -847,7 +849,20 @@ "feature.light_editor.sort_by": "Sort By", "feature.light_editor.spotlight_not_applicable": "Spotlight: ISL light type flags not applicable", "feature.light_editor.total_lights": "Total Lights: %u", + "feature.light_limit_fix.debug": "Debug", + "feature.light_limit_fix.description": "Light Limit Fix removes the vanilla game's 4-light limit, allowing unlimited dynamic lights in scenes. It also extends shadow support to all point and spot lights.", + "feature.light_limit_fix.enable_lights_vis": "Enable Lights Visualisation", + "feature.light_limit_fix.enable_lights_vis_tooltip": "Enables visualization of the light limit\n", + "feature.light_limit_fix.key_feature_1": "Removes 4-light limit", + "feature.light_limit_fix.key_feature_2": "Unlimited dynamic lights", + "feature.light_limit_fix.key_feature_3": "Shadow support for point and spot lights", + "feature.light_limit_fix.key_feature_4": "Improved lighting quality", + "feature.light_limit_fix.key_feature_5": "Particle lights from configurable INI", + "feature.light_limit_fix.light_limit_vis": "Light Limit Visualization", + "feature.light_limit_fix.lights_vis_mode": "Lights Visualisation Mode", + "feature.light_limit_fix.lights_vis_mode_tooltip": "Light Limit: Red when the strict light limit is reached (>=7 portal-strict lights).\n\nStrict Lights Count: Heatmap of portal-strict lights per pixel (blue=0, red=15).\n\nClustered Lights Count: Heatmap of dynamic lights in each screen tile (blue=0, red=128).", "feature.light_limit_fix.name": "Light Limit Fix", + "feature.light_limit_fix.statistics": "Statistics", "feature.linear_lighting.ambient_gamma": "Ambient Gamma", "feature.linear_lighting.ambient_multiplier": "Ambient Multiplier", "feature.linear_lighting.blood_effects_multiplier": "Blood Effects Multiplier", @@ -967,12 +982,16 @@ "feature.renderdoc.disk_usage": "Disk Usage", "feature.renderdoc.disk_usage_tooltip": "Monitor capture storage usage", "feature.renderdoc.double_click_hint": "Double-click a filename to open the capture file", + "feature.renderdoc.enable_capture": "Enable RenderDoc Capture", + "feature.renderdoc.enable_capture_tooltip": "Enable RenderDoc frame capture for providing debug captures to the Open Shaders team (or upstream Community Shaders for upstream-relevant issues).", + "feature.renderdoc.enable_capture_tooltip2": "Enabling capture will force-enable frame annotations for easier debugging and will restore the previous setting when disabled.", "feature.renderdoc.hover_hint": "Hover over filenames for file details", "feature.renderdoc.no_files": "No capture files found.", "feature.renderdoc.not_enough_space": "Not enough free disk space to create a capture.", "feature.renderdoc.ok": "OK", "feature.renderdoc.open_capture_dir": "Open Capture Directory", "feature.renderdoc.refresh_list": "Refresh List", + "feature.renderdoc.restart_to_disable": "Performance will be severely impacted until the game is restarted.", "feature.renderdoc.space_required": "At least {} MB of free space is required.", "feature.renderdoc.yes_delete": "Yes, Delete All", "feature.screen_space_gi.ao_only": "AO only", @@ -986,6 +1005,7 @@ "feature.screen_space_gi.denoising": "Denoising", "feature.screen_space_gi.depth_fade_range": "Depth Fade Range", "feature.screen_space_gi.depth_fade_range_tooltip": "Distance range where depth-based effects fade out.", + "feature.screen_space_gi.description": "Screen Space Global Illumination adds realistic indirect lighting and ambient occlusion to the game. This technique simulates how light bounces off surfaces to illuminate other objects naturally.", "feature.screen_space_gi.enabled": "Enabled", "feature.screen_space_gi.enabled_tooltip": "Enable Screen Space Global Illumination. When disabled, all other settings are ignored.", "feature.screen_space_gi.extreme": "Extreme", @@ -1003,6 +1023,11 @@ "feature.screen_space_gi.il_saturation": "IL Saturation", "feature.screen_space_gi.il_source_brightness": "IL Source Brightness", "feature.screen_space_gi.indirect_lighting": "Indirect Lighting (IL)", + "feature.screen_space_gi.key_feature_1": "Realistic indirect lighting", + "feature.screen_space_gi.key_feature_2": "Enhanced ambient occlusion", + "feature.screen_space_gi.key_feature_3": "Improved visual depth and atmosphere", + "feature.screen_space_gi.key_feature_4": "Temporal denoising for smooth results", + "feature.screen_space_gi.key_feature_5": "Configurable quality and performance settings", "feature.screen_space_gi.low": "Low", "feature.screen_space_gi.low_tooltip": "Quarter res and blurry.", "feature.screen_space_gi.max_frame_accumulation": "Max Frame Accumulation", @@ -1034,6 +1059,7 @@ "feature.screen_space_gi.view_resize": "View Resize", "feature.screen_space_gi.visual": "Visual", "feature.screen_space_gi.visual_il": "Visual - IL", + "feature.screen_space_gi.vr_warning": "\n\nWarning: In VR, this feature may have visual artifacts and can have a significant performance impact due to the nature of screen space effects.", "feature.screen_space_shadows.bilinear_threshold": "Bilinear Threshold", "feature.screen_space_shadows.bilinear_threshold_tooltip": "Depth threshold for edge detection during bilinear interpolation. Higher values smooth more aggressively across edges.", "feature.screen_space_shadows.description": "Screen Space Shadows enhances shadow quality by adding detailed contact shadows and improving shadow accuracy.\nThis technique adds fine-detail shadows that traditional shadow mapping might miss.", @@ -1294,6 +1320,7 @@ "feature.unified_water.regenerate_flowmap": "Regenerate Flowmap", "feature.unified_water.use_optimised_meshes": "Use Optimised Meshes", "feature.unified_water.use_optimised_meshes_tooltip": "Uses meshes with significantly lower tri-count for improved performance with no visual quality loss.\nWill only affect newly created water - requires a change of location or game restart to take effect.", + "feature.upscaling.backend_diagnostics": "Backend Diagnostics", "feature.upscaling.description": "Advanced upscaling and frame generation technologies for improved performance", "feature.upscaling.dlss_model_preset": "DLSS Model Preset", "feature.upscaling.dlss_model_preset_default": "Default", @@ -1301,9 +1328,20 @@ "feature.upscaling.dlss_model_preset_k": "Preset K", "feature.upscaling.dlss_model_preset_l": "Preset L", "feature.upscaling.dlss_model_preset_m": "Preset M", + "feature.upscaling.dlss_model_preset_tooltip": "Choose which DLSS AI model preset to use.", + "feature.upscaling.force_enable_frame_generation": "Force Enable Frame Generation", "feature.upscaling.fps_limit": "FPS Limit", "feature.upscaling.fps_limit_tooltip_1": "Set your frame cap target.", "feature.upscaling.fps_limit_tooltip_2": "Start about 2-3 FPS below refresh rate (e.g. 117 for 120 Hz).", + "feature.upscaling.frame_generation": "Frame Generation", + "feature.upscaling.frame_generation_available": "AMD FSR Frame Generation is available.", + "feature.upscaling.frame_generation_desc": "Frame Generation interpolates real frames with generated ones for a smoother experience", + "feature.upscaling.frame_generation_in_menus": "Frame Generation in Menus", + "feature.upscaling.frame_generation_in_menus_tooltip_1": "Keeps frame generation active while game menus are open.", + "feature.upscaling.frame_generation_in_menus_tooltip_2": "May feel smoother, but increases menu input latency.", + "feature.upscaling.frame_generation_proxy_note": "Requires a D3D11 to D3D12 proxy which can create compatibility issues", + "feature.upscaling.frame_generation_tech": "Uses AMD FSR Frame Generation technology", + "feature.upscaling.frame_limit_vrr": "Frame Limit (Variable Refresh Rate)", "feature.upscaling.key_feature_1": "DLSS (Deep Learning Super Sampling) support", "feature.upscaling.key_feature_2": "FSR (FidelityFX Super Resolution) support", "feature.upscaling.key_feature_3": "TAA (Temporal Anti-Aliasing) support", @@ -1315,6 +1353,7 @@ "feature.upscaling.low_latency_mode_tooltip_1": "Cuts input delay by syncing CPU work closer to the GPU.", "feature.upscaling.low_latency_mode_tooltip_2": "Can reduce max FPS a little, but usually feels more responsive.", "feature.upscaling.marker_optimization_unavailable": "Marker optimization unavailable (PCL not loaded).", + "feature.upscaling.method": "Method", "feature.upscaling.method_none": "None", "feature.upscaling.method_taa": "TAA", "feature.upscaling.name": "Upscaling", @@ -1330,6 +1369,8 @@ "feature.upscaling.reflex_not_available": "Reflex is not available. Ensure sl.reflex.dll is present and restart.", "feature.upscaling.sharpness": "Sharpness", "feature.upscaling.streamline_logging": "Streamline Logging", + "feature.upscaling.streamline_logging_tooltip": "Verbosity of the NVIDIA Streamline backend logs. Useful for debugging issues with DLSS / DLSS-G.", + "feature.upscaling.upscale_preset": "Upscale Preset", "feature.upscaling.upscaling_intermediates": "Upscaling Intermediates", "feature.upscaling.use_fps_limit": "Use FPS Limit", "feature.upscaling.use_fps_limit_tooltip_1": "Uses Reflex's internal FPS cap for steadier frametimes.", @@ -1340,6 +1381,7 @@ "feature.upscaling.view_resize": "View Resize", "feature.upscaling.vr_intermediates_not_created": "VR intermediates not yet created (enter game world)", "feature.volumetric_lighting.description": "Volumetric Lighting creates realistic light scattering effects through fog, dust, and atmospheric particles.\nThis adds dramatic god rays and atmospheric depth to both interior and exterior environments.", + "feature.volumetric_lighting.enable_exteriors": "Enable Volumetric Lighting in Exteriors", "feature.volumetric_lighting.enable_interiors": "Enable Volumetric Lighting in Interiors", "feature.volumetric_lighting.exterior_depth": "Exterior Depth", "feature.volumetric_lighting.exterior_height": "Exterior Height", @@ -1365,6 +1407,30 @@ "feature.volumetric_shadows.key_feature_3": "Multi-cascade support", "feature.volumetric_shadows.key_feature_4": "Optimized for effects rendering", "feature.volumetric_shadows.name": "Volumetric Shadows", + "feature.vr.description": "Provides VR-specific optimizations and enhancements for Open Shaders, improving performance and visual quality in virtual reality environments.", + "feature.vr.key_feature_1": "Depth buffer culling optimization for VR performance", + "feature.vr.key_feature_2": "In-scene overlay menu with HMD/Controller/Fixed World attach modes", + "feature.vr.key_feature_3": "VR controller input with customizable button mappings", + "feature.vr.key_feature_4": "Grip-to-drag overlay positioning with depth control", + "feature.vr.key_feature_5": "Configurable occlusion culling parameters", + "feature.vr.key_feature_6": "Enhanced VR compatibility with SteamVR and OpenComposite", + "feature.vr.name": "VR", + "feature.vr_stereo.debug": "Debug", + "feature.vr_stereo.debug_pom_depth": "Debug POM Depth", + "feature.vr_stereo.disocclusion_depth_threshold": "Disocclusion Depth Threshold", + "feature.vr_stereo.enable": "Enable", + "feature.vr_stereo.enable_stereo_reprojection": "Enable Stereo Reprojection", + "feature.vr_stereo.enable_stereo_reprojection_tooltip": "Reprojects Eye 0 (left) pixels into Eye 1 (right) using depth and motion data,\nskipping redundant full shading where the views overlap.\nReduces GPU cost in VR by shading each pixel fewer times per frame.", + "feature.vr_stereo.forward_occlusion_scale": "Forward Occlusion Scale", + "feature.vr_stereo.forward_occlusion_scale_tooltip": "Prevents Eye 0 silhouette edges from bleeding onto Eye 1 backgrounds.\nFires when Eye 0 depth is within this fraction of Eye 1 depth (e.g. 0.5 = Eye 0 less than 2x Eye 1 depth).\nLower = more aggressive. 0 = disabled.", + "feature.vr_stereo.full_blend_depth_view": "Full Blend Depth View", + "feature.vr_stereo.full_blend_distance": "Full Blend Distance", + "feature.vr_stereo.full_blend_distance_tooltip": "Geometry closer than this distance (game units) is fully shaded in both eyes and bilaterally blended for 2x supersampling. 0 = disabled.", + "feature.vr_stereo.full_blend_zone_hint": " Cyan = full blend zone (closer = stronger tint)", + "feature.vr_stereo.off": "Off", + "feature.vr_stereo.pom_depth_scale": "POM Depth Scale", + "feature.vr_stereo.pom_depth_scale_tooltip": "Scale factor for POM depth correction in stereo reprojection.\n1.0 = physical scale. Increase for more visible POM stereo depth.", + "feature.vr_stereo.skip_pixel_reprojection": "Skip Pixel Reprojection", "feature.water_effects.description": "Water Effects enhances water rendering with realistic caustics and underwater lighting effects.\nThis feature adds dynamic light patterns and improved water visual quality.", "feature.water_effects.key_feature_1": "Realistic water caustics", "feature.water_effects.key_feature_2": "Enhanced underwater lighting", @@ -1509,6 +1575,14 @@ "feature.wetness_effects.wetness_effects": "Wetness Effects", "feature.wetness_effects.wetness_in_exterior": "Wetness In/Exterior", "menu.advanced.active_shaders_tooltip": "List of shaders that have been used in recent frames. Enable Shader Blocking above to use hotkeys to cycle through and block shaders for debugging. Shaders not used for ~1 second are removed from this list.", + "menu.advanced.active_shaders_used_recently": "Active Shaders (Used Recently)", + "menu.advanced.addresses": "Addresses", + "menu.advanced.avg_parallelism_metric": "Average parallelism (W/S): %.2fx", + "menu.advanced.avg_parallelism_tooltip_1": "Average useful concurrency in this workload.", + "menu.advanced.avg_parallelism_tooltip_2": "Roughly the worker count where adding more cores gives diminishing returns.", + "menu.advanced.avoid_flow_control": "Avoid Flow Control", + "menu.advanced.avoid_flow_control_tooltip": "Adds D3DCOMPILE_AVOID_FLOW_CONTROL to the shader compiler flags.\nForces fxc to flatten branches into predicated ops rather than emitting dynamic flow control. Often a win for short branch bodies and uniformly-taken branches; usually a loss for long divergent branches that vanilla flow control would skip entirely.\nResets every launch. Toggling this clears the shader cache and triggers a full recompile.", + "menu.advanced.background_compiler_threads": "Background Compiler Threads", "menu.advanced.background_compiler_threads_tooltip": "Number of threads used to compile shaders during gameplay. Defaults to half of performance cores to avoid impacting the render thread. Higher values finish compilation faster but may cause stuttering.", "menu.advanced.block_next": "Block Next:", "menu.advanced.block_previous": "Block Previous:", @@ -1529,41 +1603,106 @@ "menu.advanced.column_key_tooltip": "Shader key", "menu.advanced.column_type": "Type", "menu.advanced.column_type_tooltip": "Shader type", + "menu.advanced.compiler_threads": "Compiler Threads", "menu.advanced.compiler_threads_tooltip": "Number of threads used to compile shaders at startup. Defaults to all logical cores minus one for OS headroom (E-cores included). Higher values finish compilation faster but may make the system less responsive.", "menu.advanced.compute": "Compute", "menu.advanced.compute_tooltip": "Replace Compute Shaders. When false, will disable the custom Compute Shaders for the types above. For developers to test whether CS shaders match vanilla behavior. ", "menu.advanced.copy_info": "Copy Info", "menu.advanced.copy_info_tooltip": "Copy complete shader information including cache path to clipboard", + "menu.advanced.copy_key": "Copy key", + "menu.advanced.dump_ini_settings": "Dump Ini Settings", "menu.advanced.dump_shaders": "Dump Shaders", "menu.advanced.dump_shaders_tooltip": "Dump shaders at startup. This should be used only when reversing shaders. Normal users don't need this.", + "menu.advanced.efficiency_progress": "{:.1f}% efficient / {:.1f}% gap", + "menu.advanced.enable_file_watcher": "Enable File Watcher", + "menu.advanced.enable_file_watcher_tooltip": "Automatically recompile shaders on file change. Intended for development.", "menu.advanced.enable_shader_blocking": "Enable Shader Blocking", "menu.advanced.enable_shader_blocking_tooltip": "Enables hotkeys to cycle through and block individual shaders for debugging purposes.", + "menu.advanced.frame_annotations": "Frame Annotations", + "menu.advanced.frame_annotations_tooltip": "Enable detailed frame annotations for debugging render passes and draw calls.", + "menu.advanced.half_precision": "Half Precision (Partial Precision)", + "menu.advanced.half_precision_tooltip": "Adds D3DCOMPILE_PARTIAL_PRECISION to the shader compiler flags.\nLets fxc downgrade unmarked float ops to FP16 where it can prove safety, on top of the existing min16float type hints.\nOn FP16-capable GPUs (Pascal+ / GCN+ / Skylake+) this can halve register pressure and double ALU throughput, but it can also introduce minor visual differences in shaders that haven't been audited for precision sensitivity.\nToggling this clears the shader cache and triggers a full recompile.", + "menu.advanced.infinite_core_efficiency": "Infinite-core efficiency", + "menu.advanced.infinite_core_efficiency_metric": "Infinite-core efficiency (S/T_p): %.1f%%", + "menu.advanced.infinite_core_efficiency_tooltip_1": "How close runtime is to the infinite-core lower bound.", + "menu.advanced.infinite_core_efficiency_tooltip_2": "100%% means T_p == S.", + "menu.advanced.infinite_core_gap_metric": "Infinite-core gap: %.1f%%", + "menu.advanced.infinite_core_gap_tooltip_1": "Distance from ideal infinite-core time.", + "menu.advanced.infinite_core_gap_tooltip_2": "Defined as 100 * (1 - S / T_p). Lower is better.", + "menu.advanced.log_level": "Log Level", + "menu.advanced.log_level_critical": "critical", + "menu.advanced.log_level_debug": "debug", + "menu.advanced.log_level_err": "err", + "menu.advanced.log_level_info": "info", + "menu.advanced.log_level_off": "off", + "menu.advanced.log_level_tooltip": "Log level. Trace is most verbose. Default is info.", + "menu.advanced.log_level_trace": "trace", + "menu.advanced.log_level_warn": "warn", + "menu.advanced.makespan_label": "Makespan (T_p)", + "menu.advanced.makespan_metric": "Makespan (T_p): %s", + "menu.advanced.makespan_tooltip": "Observed wall-clock duration for the full shader build.", + "menu.advanced.open_logs": "Open Logs", + "menu.advanced.parallelism_header": "Parallelism (derived from %zu compiled tasks)", + "menu.advanced.parallelism_tooltip_1": "Computed lazily from the last completed build.", + "menu.advanced.parallelism_tooltip_2": "Only evaluated when this Statistics section is open.", "menu.advanced.pixel": "Pixel", "menu.advanced.pixel_tooltip": "Replace Pixel Shaders. When false, will disable the custom Pixel Shaders for the types above. For developers to test whether CS shaders match vanilla behavior. ", "menu.advanced.press_key_shader_block_next": "Press any key for Shader Block Next...", "menu.advanced.press_key_shader_block_prev": "Press any key for Shader Block Previous...", + "menu.advanced.queue_wait_metric": "Queue wait (avg/max): %s / %s", + "menu.advanced.queue_wait_tooltip_1": "Time spent waiting in the ready queue before a worker started compilation.", + "menu.advanced.queue_wait_tooltip_2": "Useful for identifying scheduler-induced delay separate from compile cost.", + "menu.advanced.relative_bar_format": "{} ({:.1f}%)", + "menu.advanced.relative_durations": "Relative durations (normalized)", + "menu.advanced.replace_original_shaders": "Replace Original Shaders", "menu.advanced.shader_blocking_active": "Shader Blocking Active", "menu.advanced.shader_class_label": "Class: %s", + "menu.advanced.shader_compiler_stats": "Shader Compiler : %s", "menu.advanced.shader_defines": "Shader Defines", "menu.advanced.shader_defines_tooltip": "Defines for Shader Compiler. Semicolon \";\" separated. Clear with space. Rebuild shaders after making change. Compute Shaders require a restart to recompile.", "menu.advanced.shader_descriptor": "Descriptor: 0x%X", "menu.advanced.shader_row_tooltip": "Type: {}\nClass: {}\nDescriptor: 0x{:X}\nKey: {}\n\n{}", + "menu.advanced.shader_slow_entry": "#%zu %s (weight %d)", "menu.advanced.shader_type_label": "Type: %s", + "menu.advanced.span_label": "Span (S)", + "menu.advanced.span_metric": "Span (S, longest): %s", + "menu.advanced.span_tooltip_1": "Critical-path lower bound, approximated by the single slowest shader.", + "menu.advanced.span_tooltip_2": "Even infinite cores cannot finish faster than this.", + "menu.advanced.statistics": "Statistics", "menu.advanced.stop_blocking": "Stop Blocking##Section", + "menu.advanced.tab_disable_at_boot": "Disable at Boot", + "menu.advanced.tab_logging": "Logging", + "menu.advanced.tab_testing": "Testing", "menu.advanced.test_conditions": "Test Conditions", + "menu.advanced.top_slowest_shaders": "Top %zu Slowest Shaders (last build)", "menu.advanced.vertex": "Vertex", "menu.advanced.vertex_tooltip": "Replace Vertex Shaders. When false, will disable the custom Vertex Shaders for the types above. For developers to test whether CS shaders match vanilla behavior. ", + "menu.advanced.work_label": "Work (W)", + "menu.advanced.work_metric": "Work (W, sum of task wall times): %s", + "menu.advanced.work_tooltip_1": "Total compile work: sum of all per-shader wall-clock compile times.", + "menu.advanced.work_tooltip_2": "This is not CPU time; it is accumulated task elapsed time.", + "menu.advanced.work_tooltip_3": "Equivalent serial time on one worker if overhead stayed the same.", "menu.clear_shader_cache": "Clear Shader Cache", "menu.clear_shader_cache_tooltip": "Clears the shader cache and disk cache (if enabled). The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. The Disk Cache is a collection of compiled shaders on disk. Clearing will mean that shaders are recompiled only when the game re-encounters them.", "menu.disable_at_boot_desc": "Select features to disable at boot. This is the same as deleting a feature.ini file. Restart will be required to reenable.", + "menu.faq.a1": "Open Shaders is a fork of Community Shaders that ships features the upstream project has not yet released. Both projects are comprehensive graphics enhancement frameworks for Skyrim that provide advanced lighting, materials, and visual effects. They're designed to be modular, letting you enable only the features you want while maintaining good performance. This fork preserves the upstream runtime layout so user settings and themes are compatible.", "menu.faq.a2": "Each feature can be found in the left sidebar menu. Click on any feature to access its settings. Most features include presets and detailed tooltips to help you understand what each setting does.", "menu.faq.a3": "Features may fail to load due to hardware incompatibility, missing dependencies, or conflicts with other mods. Check the 'Feature Issues' tab for detailed information about any problematic features.", "menu.faq.a4": "Failed shaders are usually caused by mixed file versions. Ensure all features are up to date and avoid mixing files from test builds or outdated versions. Please review the 'Feature Issues' tab and/or Wiki for more information. Update your features and remove any obsolete features.", "menu.faq.a5": "Start by enabling the Performance Overlay to monitor your FPS. Consider disabling expensive features like Screen Space GI or reducing quality settings. The 'Display' tab also includes upscaling options that can improve performance.", + "menu.faq.a6": "No, Open Shaders (like upstream Community Shaders) is not compatible with ENB. The plugin will automatically disable itself if ENB is detected.", + "menu.faq.a7": "By default, Open Shaders uses the END key to open this menu. If your keyboard doesn't have an END key or it's not working, you can change it in the General > Keybindings tab. You can also edit the hotkey in the JSON configuration files.", + "menu.faq.a8": "Open Shaders is open source. Check out the upstream GitHub wiki for contribution guidelines on the shared architecture; open issues and PRs against this repository for fork-specific work, or against upstream Community Shaders for changes that benefit both projects. Whether you're interested in shader programming, C++ development, or documentation, there's always something to contribute.", + "menu.faq.a9": "Yes! Open Shaders is completely open source and available on GitHub, as is upstream Community Shaders. You can view the source code, report issues, suggest features, and contribute to either project. Both are licensed under GPL, ensuring they remain free and open for everyone. Branding materials and assets (icons, Nexus branding, typography, etc.) are not covered by the GPL Licence. Any included assets may not be used without explicit permission.", + "menu.faq.q1": "What is Open Shaders?", "menu.faq.q2": "How do I configure features?", "menu.faq.q3": "Why are some features not loading?", "menu.faq.q4": "I have \"Failed Shaders\" when compiling?", "menu.faq.q5": "How do I improve performance?", + "menu.faq.q6": "Is Open Shaders compatible with ENB?", + "menu.faq.q7": "The menu hotkey isn't working!", + "menu.faq.q8": "I would like to help develop Open Shaders.", + "menu.faq.q9": "Is Open Shaders open source?", "menu.faq.title": "Frequently Asked Questions", "menu.features": "Features", "menu.features.advanced": "Advanced", @@ -1614,16 +1753,25 @@ "menu.home.constraint_header_forced_to": "Forced To", "menu.home.constraint_header_setting": "Setting", "menu.home.constraints_desc": "Some settings are constrained by other features. Hover over rows for details.", + "menu.home.dev_wiki": "Developer Wiki", + "menu.home.github": "GitHub", + "menu.home.intro": "Open Shaders is a fork of Community Shaders providing advanced graphics enhancements for Skyrim.\nThis comprehensive collection of features brings modern rendering techniques\nto enhance your visual experience.", + "menu.home.nexus_mods": "Nexus Mods", "menu.home.quick_links": "Quick Links", + "menu.home.welcome": "Welcome to Open Shaders {version}", + "menu.home.welcome_dev": "Welcome to Open Shaders {version} [{build}]", "menu.issues.all_ini_loading": "All feature INI files are loading successfully.", "menu.issues.cancel": "Cancel", "menu.issues.cannot_be_undone": "This action cannot be undone!", + "menu.issues.check_modified_files": "Check for modified files in Data/Shaders/ (not in feature subfolders)", "menu.issues.cleanup_actions": "Cleanup Actions:", "menu.issues.clear_issue_list": "Clear Issue List", "menu.issues.clear_issue_list_tooltip": "Clears this issue list (useful after cleanup).", "menu.issues.compilation_breaking_desc": "The following features modified core shader files and must be completely uninstalled via your mod manager. Deleting just the INI file will not fix compilation errors if core shaders were modified.", "menu.issues.compilation_breaking_header": "Compilation Breaking Features", + "menu.issues.compilation_persist_warning": "If compilation issues persist after deletion:", "menu.issues.core_feature_installed": "Core feature already installed", + "menu.issues.core_feature_installed_tooltip": "This feature is already included as part of the core Open Shaders installation. Uninstall this feature with your mod manager.", "menu.issues.current_version": "Current Version: %s", "menu.issues.delete": "Delete", "menu.issues.delete_confirm": "Are you sure? This will delete all files for feature '%s'?", @@ -1656,16 +1804,25 @@ "menu.issues.override_failures_desc": "The following override files failed to load or apply. Check the file format and content.", "menu.issues.override_failures_header": "Override Failures", "menu.issues.potential_compilation_failure": "POTENTIAL COMPILATION FAILURE", + "menu.issues.reinstall_cs": "Consider reinstalling Open Shaders if issues persist", "menu.issues.replaced_by_prefix": "(replaced by ", "menu.issues.replaced_by_suffix": ")", "menu.issues.replacement_label": "Replacement: %s", "menu.issues.shader_directory_label": "Shader directory: %s", "menu.issues.shader_folder": "Shader Folder: %s", "menu.issues.test.active_inis_count": "Active test INI files ({count}):\n", + "menu.issues.test.active_inis_warning": "Test INI files are currently active. Restart CS to see feature issues.", + "menu.issues.test.create_test_inis": "Create Test INIs", + "menu.issues.test.create_test_inis_tooltip": "Creates test INI files that trigger all known feature issue cases:\n- Obsolete features (ComplexParallaxMaterials, TerrainBlending, etc.)\n- Unknown features (fake non-existent features)\n- Version mismatch (modifies existing feature version)\nRestart CS after creating to see the issues in action.", + "menu.issues.test.feature_issue_testing": "Feature Issue Testing", + "menu.issues.test.feature_issue_testing_desc": "These tools create test INI files to trigger all known feature issue types for testing purposes.", "menu.issues.test.modified_notice": "\nSome test files modified - restore recommended to clean up", "menu.issues.test.no_active_inis": "No test INI files are currently active.", + "menu.issues.test.restore": "Restore", + "menu.issues.test.restore_tooltip": "Removes all test INI files and restores any modified INI files to their original state.\nThis undoes all changes made by 'Create Test INIs'.\nRestart CS after restoring to see normal operation.", "menu.issues.this_will_delete": "This will delete:", "menu.issues.time_label": "Time: %s", + "menu.issues.uninstall_via_mod_manager": "Completely uninstall the feature via your mod manager", "menu.issues.unknown_compilation_warning": "This unknown feature may have modified core shader files and could be causing compilation failures. Unknown features should be removed if failures continue.", "menu.issues.unknown_delete_warning": "This is an UNKNOWN feature. If it modified core shader files (outside of its own folder), deleting these files alone will NOT fix shader compilation issues.", "menu.issues.unknown_features_desc": "The following features are not recognized and we tried to disable automatically. They may be from development branches or newer CS versions. Since we cannot determine what files they may have modified, they should be removed as a precaution to prevent potential shader compilation failures.", @@ -1935,6 +2092,8 @@ "menu.setup.choose_hotkey": "Please choose a hotkey to access the menu:", "menu.setup.cs_editor_unbound": "CS Editor hotkey unbound - chosen key uses Shift", "menu.setup.cs_editor_will_be": "CS Editor hotkey will be: {key}", + "menu.setup.new_install_line1": "This appears to be a new install, update, or", + "menu.setup.new_install_line2": "reinstallation of Open Shaders.", "menu.setup.press_any_key": "Press any key to set as toggle key...", "menu.setup.press_to_close": "Press Escape or Enter to continue", "menu.toggle_error_message": "Toggle Error Message", From f86c46ef1eccab69de55a75af41d0394eaf3f2c9 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 02:59:12 -0700 Subject: [PATCH 4/7] i18n: wrap remaining flagged DrawSettings strings Second pass over strings review flagged as still hardcoded: Advanced tab labels (Diagnostics/Shaders) and section headers (Compile Flags, Threading, Cache & File Watcher, Runtime Debug); LLF Statistics stat lines + lights visualisation-mode combo options; Upscaling DLSS-preset tooltip tail lines, frame-limit refresh-rate status line, Force-Enable-FrameGen restart tooltip body, and Streamline log-level options; VR stereo stencil-swap debug readout; VolumetricLighting exteriors restart tooltip body. Regen en.json. English output unchanged (defaults are the current literals). std::format format-string sites use std::vformat(T(...), make_format_args(...)). Part of #123. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../CommunityShaders/Translations/en.json | 27 ++++++++++++++++++- src/Features/LightLimitFix.cpp | 27 ++++++++++--------- src/Features/Upscaling.cpp | 22 +++++++++------ src/Features/VRStereoOptimizations.cpp | 2 +- src/Features/VolumetricLighting.cpp | 2 +- src/Menu/AdvancedSettingsRenderer.cpp | 12 ++++----- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index 75a5ebd67f..ba4facffbf 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -860,8 +860,20 @@ "feature.light_limit_fix.key_feature_5": "Particle lights from configurable INI", "feature.light_limit_fix.light_limit_vis": "Light Limit Visualization", "feature.light_limit_fix.lights_vis_mode": "Lights Visualisation Mode", + "feature.light_limit_fix.lights_vis_mode_opt_clustered_lights_count": "Clustered Lights Count", + "feature.light_limit_fix.lights_vis_mode_opt_light_limit": "Light Limit", + "feature.light_limit_fix.lights_vis_mode_opt_light_type_visualization": "Light Type Visualization", + "feature.light_limit_fix.lights_vis_mode_opt_point_light_shadow_factor": "Point Light Shadow Factor", + "feature.light_limit_fix.lights_vis_mode_opt_shadow_caster_density": "Shadow Caster Density", + "feature.light_limit_fix.lights_vis_mode_opt_shadow_light_count": "Shadow Light Count", + "feature.light_limit_fix.lights_vis_mode_opt_shadow_mask": "Shadow Mask", + "feature.light_limit_fix.lights_vis_mode_opt_shadow_slot_index_color": "Shadow Slot Index Color", + "feature.light_limit_fix.lights_vis_mode_opt_strict_lights_count": "Strict Lights Count", + "feature.light_limit_fix.lights_vis_mode_opt_unshadowed_point_lights": "Unshadowed Point Lights", "feature.light_limit_fix.lights_vis_mode_tooltip": "Light Limit: Red when the strict light limit is reached (>=7 portal-strict lights).\n\nStrict Lights Count: Heatmap of portal-strict lights per pixel (blue=0, red=15).\n\nClustered Lights Count: Heatmap of dynamic lights in each screen tile (blue=0, red=128).", "feature.light_limit_fix.name": "Light Limit Fix", + "feature.light_limit_fix.stat_clustered_light_count": "Clustered Light Count : {}", + "feature.light_limit_fix.stat_particle_lights_count": "Particle Lights Count : {}", "feature.light_limit_fix.statistics": "Statistics", "feature.linear_lighting.ambient_gamma": "Ambient Gamma", "feature.linear_lighting.ambient_multiplier": "Ambient Multiplier", @@ -1328,8 +1340,9 @@ "feature.upscaling.dlss_model_preset_k": "Preset K", "feature.upscaling.dlss_model_preset_l": "Preset L", "feature.upscaling.dlss_model_preset_m": "Preset M", - "feature.upscaling.dlss_model_preset_tooltip": "Choose which DLSS AI model preset to use.", + "feature.upscaling.dlss_model_preset_tooltip": "Choose which DLSS AI model preset to use.\nEach model offers different visual quality, performance, and motion stability.\nSet to 'Default' for automatic selection based on your Upscale Preset and hardware.", "feature.upscaling.force_enable_frame_generation": "Force Enable Frame Generation", + "feature.upscaling.force_enable_frame_generation_tooltip": "Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\ndisplays. Useful for laptops and older monitors at the cost of less headroom for the\ngenerated frames.", "feature.upscaling.fps_limit": "FPS Limit", "feature.upscaling.fps_limit_tooltip_1": "Set your frame cap target.", "feature.upscaling.fps_limit_tooltip_2": "Start about 2-3 FPS below refresh rate (e.g. 117 for 120 Hz).", @@ -1341,6 +1354,7 @@ "feature.upscaling.frame_generation_in_menus_tooltip_2": "May feel smoother, but increases menu input latency.", "feature.upscaling.frame_generation_proxy_note": "Requires a D3D11 to D3D12 proxy which can create compatibility issues", "feature.upscaling.frame_generation_tech": "Uses AMD FSR Frame Generation technology", + "feature.upscaling.frame_limit_refresh_rate": "Allows frame generation to function on low refresh rate monitors. Detected: %.2f Hz", "feature.upscaling.frame_limit_vrr": "Frame Limit (Variable Refresh Rate)", "feature.upscaling.key_feature_1": "DLSS (Deep Learning Super Sampling) support", "feature.upscaling.key_feature_2": "FSR (FidelityFX Super Resolution) support", @@ -1368,6 +1382,9 @@ "feature.upscaling.reflex_blocked_by_fg": "Reflex is unavailable while the DX12 frame-generation swapchain is active.", "feature.upscaling.reflex_not_available": "Reflex is not available. Ensure sl.reflex.dll is present and restart.", "feature.upscaling.sharpness": "Sharpness", + "feature.upscaling.streamline_log_level_default": "Default", + "feature.upscaling.streamline_log_level_off": "Off", + "feature.upscaling.streamline_log_level_verbose": "Verbose", "feature.upscaling.streamline_logging": "Streamline Logging", "feature.upscaling.streamline_logging_tooltip": "Verbosity of the NVIDIA Streamline backend logs. Useful for debugging issues with DLSS / DLSS-G.", "feature.upscaling.upscale_preset": "Upscale Preset", @@ -1382,6 +1399,7 @@ "feature.upscaling.vr_intermediates_not_created": "VR intermediates not yet created (enter game world)", "feature.volumetric_lighting.description": "Volumetric Lighting creates realistic light scattering effects through fog, dust, and atmospheric particles.\nThis adds dramatic god rays and atmospheric depth to both interior and exterior environments.", "feature.volumetric_lighting.enable_exteriors": "Enable Volumetric Lighting in Exteriors", + "feature.volumetric_lighting.enable_exteriors_tooltip": "Volumetric god-rays / fog scattering in exterior cells.", "feature.volumetric_lighting.enable_interiors": "Enable Volumetric Lighting in Interiors", "feature.volumetric_lighting.exterior_depth": "Exterior Depth", "feature.volumetric_lighting.exterior_height": "Exterior Height", @@ -1431,6 +1449,7 @@ "feature.vr_stereo.pom_depth_scale": "POM Depth Scale", "feature.vr_stereo.pom_depth_scale_tooltip": "Scale factor for POM depth correction in stereo reprojection.\n1.0 = physical scale. Increase for more visible POM stereo depth.", "feature.vr_stereo.skip_pixel_reprojection": "Skip Pixel Reprojection", + "feature.vr_stereo.stencil_swaps_this_frame": "Stencil swaps this frame: %u", "feature.water_effects.description": "Water Effects enhances water rendering with realistic caustics and underwater lighting effects.\nThis feature adds dynamic light patterns and improved water visual quality.", "feature.water_effects.key_feature_1": "Realistic water caustics", "feature.water_effects.key_feature_2": "Enhanced underwater lighting", @@ -1587,6 +1606,7 @@ "menu.advanced.block_next": "Block Next:", "menu.advanced.block_previous": "Block Previous:", "menu.advanced.blocked_shader": "Blocked: %s", + "menu.advanced.cache_watcher_header": "Cache & File Watcher", "menu.advanced.change_shader_block_next": "Change##ShaderBlockNext", "menu.advanced.change_shader_block_prev": "Change##ShaderBlockPrev", "menu.advanced.clear_shader_cache": "Clear Shader Cache", @@ -1603,6 +1623,7 @@ "menu.advanced.column_key_tooltip": "Shader key", "menu.advanced.column_type": "Type", "menu.advanced.column_type_tooltip": "Shader type", + "menu.advanced.compile_flags_header": "Compile Flags", "menu.advanced.compiler_threads": "Compiler Threads", "menu.advanced.compiler_threads_tooltip": "Number of threads used to compile shaders at startup. Defaults to all logical cores minus one for OS headroom (E-cores included). Higher values finish compilation faster but may make the system less responsive.", "menu.advanced.compute": "Compute", @@ -1655,6 +1676,7 @@ "menu.advanced.relative_bar_format": "{} ({:.1f}%)", "menu.advanced.relative_durations": "Relative durations (normalized)", "menu.advanced.replace_original_shaders": "Replace Original Shaders", + "menu.advanced.runtime_debug_header": "Runtime Debug", "menu.advanced.shader_blocking_active": "Shader Blocking Active", "menu.advanced.shader_class_label": "Class: %s", "menu.advanced.shader_compiler_stats": "Shader Compiler : %s", @@ -1670,10 +1692,13 @@ "menu.advanced.span_tooltip_2": "Even infinite cores cannot finish faster than this.", "menu.advanced.statistics": "Statistics", "menu.advanced.stop_blocking": "Stop Blocking##Section", + "menu.advanced.tab_diagnostics": "Diagnostics", "menu.advanced.tab_disable_at_boot": "Disable at Boot", "menu.advanced.tab_logging": "Logging", + "menu.advanced.tab_shaders": "Shaders", "menu.advanced.tab_testing": "Testing", "menu.advanced.test_conditions": "Test Conditions", + "menu.advanced.threading_header": "Threading", "menu.advanced.top_slowest_shaders": "Top %zu Slowest Shaders (last build)", "menu.advanced.vertex": "Vertex", "menu.advanced.vertex_tooltip": "Replace Vertex Shaders. When false, will disable the custom Vertex Shaders for the types above. For developers to test whether CS shaders match vanilla behavior. ", diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 5c4bc46687..5254dc93c9 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -118,8 +118,9 @@ void LightLimitFix::DrawSettings() ShadowCasterManager::DrawSettings(settings.ShadowSettings); if (ImGui::TreeNodeEx(T("feature.light_limit_fix.statistics", "Statistics"), ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text(std::format("Clustered Light Count : {}", lightCount).c_str()); - ImGui::Text(std::format("Particle Lights Count : {}", currentParticleLights.size()).c_str()); + ImGui::Text(std::vformat(T("feature.light_limit_fix.stat_clustered_light_count", "Clustered Light Count : {}"), std::make_format_args(lightCount)).c_str()); + auto particleLightCount = currentParticleLights.size(); + ImGui::Text(std::vformat(T("feature.light_limit_fix.stat_particle_lights_count", "Particle Lights Count : {}"), std::make_format_args(particleLightCount)).c_str()); ImGui::TreePop(); } @@ -328,17 +329,17 @@ void LightLimitFix::DrawSettings() } { - static const char* comboOptions[] = { - "Light Limit", - "Strict Lights Count", - "Clustered Lights Count", - "Shadow Mask", - "Shadow Light Count", - "Point Light Shadow Factor", - "Unshadowed Point Lights", - "Shadow Caster Density", - "Shadow Slot Index Color", - "Light Type Visualization", + const char* comboOptions[] = { + T("feature.light_limit_fix.lights_vis_mode_opt_light_limit", "Light Limit"), + T("feature.light_limit_fix.lights_vis_mode_opt_strict_lights_count", "Strict Lights Count"), + T("feature.light_limit_fix.lights_vis_mode_opt_clustered_lights_count", "Clustered Lights Count"), + T("feature.light_limit_fix.lights_vis_mode_opt_shadow_mask", "Shadow Mask"), + T("feature.light_limit_fix.lights_vis_mode_opt_shadow_light_count", "Shadow Light Count"), + T("feature.light_limit_fix.lights_vis_mode_opt_point_light_shadow_factor", "Point Light Shadow Factor"), + T("feature.light_limit_fix.lights_vis_mode_opt_unshadowed_point_lights", "Unshadowed Point Lights"), + T("feature.light_limit_fix.lights_vis_mode_opt_shadow_caster_density", "Shadow Caster Density"), + T("feature.light_limit_fix.lights_vis_mode_opt_shadow_slot_index_color", "Shadow Slot Index Color"), + T("feature.light_limit_fix.lights_vis_mode_opt_light_type_visualization", "Light Type Visualization"), }; // Round-trip through int instead of `(int*)&uint` to avoid strict-aliasing UB // (ImGui has no ComboScalar). Clamp on the way in defends against any stale diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 61e93f8b45..0e0a2eccd7 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -354,9 +354,10 @@ void Upscaling::DrawSettings() }; ImGui::Combo(T(TKEY("dlss_model_preset"), "DLSS Model Preset"), (int*)&settings.presetDLSS, presets, 5); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("%s", T(TKEY("dlss_model_preset_tooltip"), "Choose which DLSS AI model preset to use.")); - ImGui::Text("Each model offers different visual quality, performance, and motion stability."); - ImGui::Text("Set to 'Default' for automatic selection based on your Upscale Preset and hardware."); + ImGui::Text("%s", T(TKEY("dlss_model_preset_tooltip"), + "Choose which DLSS AI model preset to use.\n" + "Each model offers different visual quality, performance, and motion stability.\n" + "Set to 'Default' for automatic selection based on your Upscale Preset and hardware.")); } } @@ -448,14 +449,15 @@ void Upscaling::DrawSettings() if (!frameGenerationDx12PathActive) ImGui::EndDisabled(); - ImGui::TextWrapped("Allows frame generation to function on low refresh rate monitors. Detected: %.2f Hz", refreshRate); + ImGui::TextWrapped(T(TKEY("frame_limit_refresh_rate"), "Allows frame generation to function on low refresh rate monitors. Detected: %.2f Hz"), refreshRate); bool fgForce = settings.frameGenerationForceEnable != 0; if (ImGui::Checkbox(T(TKEY("force_enable_frame_generation"), "Force Enable Frame Generation"), &fgForce)) settings.frameGenerationForceEnable = fgForce ? 1 : 0; Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationForceEnable, - "Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\n" - "displays. Useful for laptops and older monitors at the cost of less headroom for the\n" - "generated frames."); + T(TKEY("force_enable_frame_generation_tooltip"), + "Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\n" + "displays. Useful for laptops and older monitors at the cost of less headroom for the\n" + "generated frames.")); ImGui::Checkbox(T(TKEY("frame_generation_in_menus"), "Frame Generation in Menus"), &settings.frameGenerationAllowInMenus); if (auto _tt = Util::HoverTooltipWrapper()) { @@ -565,7 +567,11 @@ void Upscaling::DrawSettings() if (ImGui::TreeNodeEx(T(TKEY("backend_diagnostics"), "Backend Diagnostics"))) { // Streamline log level selection - const char* logLevels[] = { "Off", "Default", "Verbose" }; + const char* logLevels[] = { + T(TKEY("streamline_log_level_off"), "Off"), + T(TKEY("streamline_log_level_default"), "Default"), + T(TKEY("streamline_log_level_verbose"), "Verbose") + }; // streamlineLogLevel is sanitized in LoadSettings (runs on every load, // not gated on this node being expanded), so the stored value is in range. int logLevelIdx = static_cast(settings.streamlineLogLevel); diff --git a/src/Features/VRStereoOptimizations.cpp b/src/Features/VRStereoOptimizations.cpp index 686cf4b4f0..d84af6d5a0 100644 --- a/src/Features/VRStereoOptimizations.cpp +++ b/src/Features/VRStereoOptimizations.cpp @@ -282,7 +282,7 @@ void VRStereoOptimizations::DrawSettings() ImGui::Checkbox(T("feature.vr_stereo.debug_pom_depth", "Debug POM Depth"), &settings.debugPOMDepth); if (settings.debugFullBlendDepth) ImGui::TextColored(ImVec4(0, 1, 1, 1), "%s", T("feature.vr_stereo.full_blend_zone_hint", " Cyan = full blend zone (closer = stronger tint)")); - ImGui::Text("Stencil swaps this frame: %u", stencilSwapCount); + ImGui::Text(T("feature.vr_stereo.stencil_swaps_this_frame", "Stencil swaps this frame: %u"), stencilSwapCount); ImGui::TreePop(); } } diff --git a/src/Features/VolumetricLighting.cpp b/src/Features/VolumetricLighting.cpp index fc85d63b65..0b3e50414e 100644 --- a/src/Features/VolumetricLighting.cpp +++ b/src/Features/VolumetricLighting.cpp @@ -31,7 +31,7 @@ void VolumetricLighting::DrawSettings() SetupVL(); if (globals::game::isVR) Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::ExteriorEnabled, - "Volumetric god-rays / fog scattering in exterior cells."); + T(TKEY("enable_exteriors_tooltip"), "Volumetric god-rays / fog scattering in exterior cells.")); if (settings.ExteriorEnabled) DrawVolumetricLightingSettings(settings.ExteriorQuality, settings.ExteriorCustomSize, false, !inInterior); diff --git a/src/Menu/AdvancedSettingsRenderer.cpp b/src/Menu/AdvancedSettingsRenderer.cpp index 5d09edafcc..7b73849475 100644 --- a/src/Menu/AdvancedSettingsRenderer.cpp +++ b/src/Menu/AdvancedSettingsRenderer.cpp @@ -27,7 +27,7 @@ void AdvancedSettingsRenderer::RenderAdvancedSettings( // Disable at Boot = user-facing failsafe toggles // Testing = A/B harness + dev-mode test scaffolding if (ImGui::BeginTabBar("##AdvancedSettingsTabs", ImGuiTabBarFlags_None)) { - if (MenuFonts::BeginTabItemWithFont("Diagnostics", Menu::FontRole::Subheading)) { + if (MenuFonts::BeginTabItemWithFont(T("menu.advanced.tab_diagnostics", "Diagnostics"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##DiagnosticsContent", ImVec2(0, 0), false)) { RenderDiagnosticsSection(); } @@ -43,7 +43,7 @@ void AdvancedSettingsRenderer::RenderAdvancedSettings( ImGui::EndTabItem(); } - if (MenuFonts::BeginTabItemWithFont("Shaders", Menu::FontRole::Subheading)) { + if (MenuFonts::BeginTabItemWithFont(T("menu.advanced.tab_shaders", "Shaders"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##ShadersContent", ImVec2(0, 0), false)) { RenderShadersSection(); } @@ -100,7 +100,7 @@ void AdvancedSettingsRenderer::RenderShaderCompileFlags() { auto shaderCache = globals::shaderCache; - Util::DrawSectionHeader("Compile Flags"); + Util::DrawSectionHeader(T("menu.advanced.compile_flags_header", "Compile Flags")); // Shader Defines input auto& shaderDefines = globals::state->shaderDefinesString; @@ -159,7 +159,7 @@ void AdvancedSettingsRenderer::RenderShaderThreading() { auto shaderCache = globals::shaderCache; - Util::DrawSectionHeader("Threading"); + Util::DrawSectionHeader(T("menu.advanced.threading_header", "Threading")); // hardware_concurrency() is permitted to return 0 if the implementation can't // detect it. Fall back to the actual compile-pool thread count we ended up @@ -196,7 +196,7 @@ void AdvancedSettingsRenderer::RenderShaderCacheControls() { auto shaderCache = globals::shaderCache; - Util::DrawSectionHeader("Cache & File Watcher"); + Util::DrawSectionHeader(T("menu.advanced.cache_watcher_header", "Cache & File Watcher")); // File Watcher option bool useFileWatcher = shaderCache->UseFileWatcher(); @@ -455,7 +455,7 @@ void AdvancedSettingsRenderer::RenderLoggingControls() void AdvancedSettingsRenderer::RenderRuntimeDebugControls() { - Util::DrawSectionHeader("Runtime Debug"); + Util::DrawSectionHeader(T("menu.advanced.runtime_debug_header", "Runtime Debug")); // Frame annotations toggle ImGui::Checkbox(T("menu.advanced.frame_annotations", "Frame Annotations"), &globals::state->frameAnnotations); From bb6f55042e9d3f83639b6d00f1f2ce3fc9463aeb Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 12:10:23 -0700 Subject: [PATCH 5/7] i18n: comprehensive wrap batch 1 (reformat) --- src/Features/LightLimitFix.cpp | 181 ++--- .../LightLimitFix/ShadowCasterManager.cpp | 730 +++++++++--------- src/Features/LightLimitFix/ShadowRenderer.cpp | 5 +- src/Features/PerformanceOverlay.cpp | 268 +++---- .../ABTesting/ABTesting.cpp | 36 +- src/Features/Upscaling.cpp | 67 +- src/Features/Upscaling/FoveatedRender.cpp | 149 ++-- src/Features/VR/SettingsUI.cpp | 532 +++++++------ src/Features/VolumetricLighting.cpp | 2 +- src/Features/WetnessEffects.cpp | 68 +- 10 files changed, 1064 insertions(+), 974 deletions(-) diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 5254dc93c9..7970a4e54c 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -130,15 +130,16 @@ void LightLimitFix::DrawSettings() // table below. Same layout as the overlay so testers see the same // thing in both views with the stats above the (potentially long) // table -- no scrolling required to find the headline numbers. - ImGui::SeparatorText("Shadow Limit Fix -- Active Casters"); + ImGui::SeparatorText(T("feature.light_limit_fix.shadow_limit_fix_active_casters", "Shadow Limit Fix -- Active Casters")); - ImGui::Checkbox("Show Shadow Overlay", &settings.ShowShadowOverlay); + ImGui::Checkbox(T("feature.light_limit_fix.show_shadow_overlay", "Show Shadow Overlay"), &settings.ShowShadowOverlay); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Pop out an always-visible overlay window with the shadow caster table.\n" - "Without this, the overlay only appears when a light is suppressed\n" - "or a visualisation mode is active. Enable to access the table's\n" - "debug controls (cycle button, solo, Shift+hover pulse) any time."); + ImGui::Text("%s", + T("feature.light_limit_fix.show_shadow_overlay_tooltip", + "Pop out an always-visible overlay window with the shadow caster table.\n" + "Without this, the overlay only appears when a light is suppressed\n" + "or a visualisation mode is active. Enable to access the table's\n" + "debug controls (cycle button, solo, Shift+hover pulse) any time.")); } ShadowCasterManager::DrawShadowSummary(lightCount, MAX_LIGHTS, shadowUnshadowedLightCount); @@ -147,176 +148,184 @@ void LightLimitFix::DrawSettings() ShadowCasterManager::DrawShadowLightTable(true, false); /////////////////////////////// - ImGui::SeparatorText("Contact Shadows"); + ImGui::SeparatorText(T("feature.light_limit_fix.contact_shadows_header", "Contact Shadows")); - ImGui::Checkbox("Enable Contact Shadows", &settings.EnableContactShadows); + ImGui::Checkbox(T("feature.light_limit_fix.enable_contact_shadows", "Enable Contact Shadows"), &settings.EnableContactShadows); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("All point lights (strict and clustered, except simple lights) cast short screen-space shadows. Performance impact."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_contact_shadows_tooltip", "All point lights (strict and clustered, except simple lights) cast short screen-space shadows. Performance impact.")); } - if (settings.EnableContactShadows && ImGui::TreeNode("Contact Shadow Tuning")) { + if (settings.EnableContactShadows && ImGui::TreeNode(T("feature.light_limit_fix.contact_shadow_tuning", "Contact Shadow Tuning"))) { // SliderScalar with ImGuiDataType_U32 instead of `SliderInt + (int*)cast`: // the cast violates strict aliasing (UB) and would also misinterpret any // transient negative value inside ImGui before clamp. SliderScalar // reads/writes the uint storage directly with explicit min/max bounds. constexpr uint32_t kMinSteps = 1, kMaxSteps = 16; - ImGui::SliderScalar("Max Steps", ImGuiDataType_U32, &settings.ContactShadowMaxSteps, + ImGui::SliderScalar(T("feature.light_limit_fix.contact_shadow_max_steps", "Max Steps"), ImGuiDataType_U32, &settings.ContactShadowMaxSteps, &kMinSteps, &kMaxSteps, "%u", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Raymarch steps at zero depth. Higher = longer / more accurate contact shadows, linearly more cost.\nVR users should consider 2 to halve per-eye cost."); + ImGui::Text("%s", T("feature.light_limit_fix.contact_shadow_max_steps_tooltip", "Raymarch steps at zero depth. Higher = longer / more accurate contact shadows, linearly more cost.\nVR users should consider 2 to halve per-eye cost.")); } // AlwaysClamp on every float slider too: without it, Ctrl+Click text entry can // land arbitrary out-of-range values in settings before GetCommonBufferData's // boundary clamp catches them at the GPU side. - ImGui::SliderFloat("Max Distance", &settings.ContactShadowMaxDistance, 64.0f, 4096.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T("feature.light_limit_fix.contact_shadow_max_distance", "Max Distance"), &settings.ContactShadowMaxDistance, 64.0f, 4096.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("View-space depth at which contact shadows fade to zero steps. Avoids paying for shadows on distant surfaces where they don't read."); + ImGui::Text("%s", T("feature.light_limit_fix.contact_shadow_max_distance_tooltip", "View-space depth at which contact shadows fade to zero steps. Avoids paying for shadows on distant surfaces where they don't read.")); } - ImGui::SliderFloat("Stride", &settings.ContactShadowStride, 0.5f, 8.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T("feature.light_limit_fix.contact_shadow_stride", "Stride"), &settings.ContactShadowStride, 0.5f, 8.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Per-step march length in view-space units at near depth (auto-scales linearly past ~100 units so far surfaces don't undersample). Larger = longer screen-space reach with coarser detail."); + ImGui::Text("%s", T("feature.light_limit_fix.contact_shadow_stride_tooltip", "Per-step march length in view-space units at near depth (auto-scales linearly past ~100 units so far surfaces don't undersample). Larger = longer screen-space reach with coarser detail.")); } - ImGui::SliderFloat("Thickness", &settings.ContactShadowThickness, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T("feature.light_limit_fix.contact_shadow_thickness", "Thickness"), &settings.ContactShadowThickness, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Depth-delta multiplier for shadow onset. Larger = darker contact at occluder edges."); + ImGui::Text("%s", T("feature.light_limit_fix.contact_shadow_thickness_tooltip", "Depth-delta multiplier for shadow onset. Larger = darker contact at occluder edges.")); } - ImGui::SliderFloat("Depth Fade", &settings.ContactShadowDepthFade, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T("feature.light_limit_fix.contact_shadow_depth_fade", "Depth Fade"), &settings.ContactShadowDepthFade, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Depth-delta multiplier for shadow falloff. Larger = shadows truncate sooner behind thick occluders."); + ImGui::Text("%s", T("feature.light_limit_fix.contact_shadow_depth_fade_tooltip", "Depth-delta multiplier for shadow falloff. Larger = shadows truncate sooner behind thick occluders.")); } - ImGui::SliderFloat("Min Light Intensity", &settings.ContactShadowMinIntensity, 0.0f, 1.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T("feature.light_limit_fix.contact_shadow_min_intensity", "Min Light Intensity"), &settings.ContactShadowMinIntensity, 0.0f, 1.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Skip contact shadows for CLUSTERED lights whose normalized distance falloff " - "`1 - (lightDist/radius)^2` at the pixel is below this threshold. " - "Strict lights are always raymarched regardless of this threshold. " - "Higher = larger perf win, may drop subtle shadows from weak lights at their reach edge."); + ImGui::Text("%s", + T("feature.light_limit_fix.contact_shadow_min_intensity_tooltip", + "Skip contact shadows for CLUSTERED lights whose normalized distance falloff " + "`1 - (lightDist/radius)^2` at the pixel is below this threshold. " + "Strict lights are always raymarched regardless of this threshold. " + "Higher = larger perf win, may drop subtle shadows from weak lights at their reach edge.")); } ImGui::TreePop(); } ImGui::BeginDisabled(!settings.EnableContactShadows); - ImGui::Checkbox("Enable Particle Contact Shadows", &settings.EnableParticleContactShadows); + ImGui::Checkbox(T("feature.light_limit_fix.enable_particle_contact_shadows", "Enable Particle Contact Shadows"), &settings.EnableParticleContactShadows); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Also cast contact shadows from particle lights. Larger performance impact in fire/magic-heavy scenes."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_particle_contact_shadows_tooltip", "Also cast contact shadows from particle lights. Larger performance impact in fire/magic-heavy scenes.")); } ImGui::EndDisabled(); - ImGui::SeparatorText("Particle Lights"); - - ImGui::TextWrapped( - "Turns configured particle effects (candles, braziers, torches, magic) into dynamic lights. " - "Requires a particle-light config pack shipping Data\\ParticleLights\\*.ini (e.g. Embers HD, " - "Lanterns of Skyrim); with no pack installed this section has no effect."); - ImGui::TextWrapped( - "Particle lights are additive emitters and do NOT cast shadow-map shadows, so they never appear " - "in the shadow caster table above. Turn on \"Enable Particle Contact Shadows\" in the Contact " - "Shadows section for short screen-space contact shadows."); + ImGui::SeparatorText(T("feature.light_limit_fix.particle_lights_header", "Particle Lights")); + + ImGui::TextWrapped("%s", + T("feature.light_limit_fix.particle_lights_intro", + "Turns configured particle effects (candles, braziers, torches, magic) into dynamic lights. " + "Requires a particle-light config pack shipping Data\\ParticleLights\\*.ini (e.g. Embers HD, " + "Lanterns of Skyrim); with no pack installed this section has no effect.")); + ImGui::TextWrapped("%s", + T("feature.light_limit_fix.particle_lights_additive_note", + "Particle lights are additive emitters and do NOT cast shadow-map shadows, so they never appear " + "in the shadow caster table above. Turn on \"Enable Particle Contact Shadows\" in the Contact " + "Shadows section for short screen-space contact shadows.")); ImGui::Spacing(); - ImGui::Checkbox("Enable Particle Lights", &settings.EnableParticleLights); + ImGui::Checkbox(T("feature.light_limit_fix.enable_particle_lights", "Enable Particle Lights"), &settings.EnableParticleLights); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Master toggle for the particle-light feature."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_particle_lights_tooltip", "Master toggle for the particle-light feature.")); } - if (ImGui::TreeNode("Performance##particles")) { - ImGui::Checkbox("Enable Culling", &settings.EnableParticleLightsCulling); + if (ImGui::TreeNode(T("feature.light_limit_fix.particle_performance", "Performance##particles"))) { + ImGui::Checkbox(T("feature.light_limit_fix.enable_particle_culling", "Enable Culling"), &settings.EnableParticleLightsCulling); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Significantly improves performance by not rendering empty textures. Only disable if you are encountering issues."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_particle_culling_tooltip", "Significantly improves performance by not rendering empty textures. Only disable if you are encountering issues.")); } - ImGui::Checkbox("Enable Detection", &settings.EnableParticleLightsDetection); + ImGui::Checkbox(T("feature.light_limit_fix.enable_particle_detection", "Enable Detection"), &settings.EnableParticleLightsDetection); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Adds particle lights to the player light level so that NPCs detect them for stealth and gameplay."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_particle_detection_tooltip", "Adds particle lights to the player light level so that NPCs detect them for stealth and gameplay.")); } - ImGui::Checkbox("Enable Optimization", &settings.EnableParticleLightsOptimization); + ImGui::Checkbox(T("feature.light_limit_fix.enable_particle_optimization", "Enable Optimization"), &settings.EnableParticleLightsOptimization); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Merges vertices which are close enough to each other to improve performance."); + ImGui::Text("%s", T("feature.light_limit_fix.enable_particle_optimization_tooltip", "Merges vertices which are close enough to each other to improve performance.")); } - ImGui::SliderFloat("Cluster Threshold", &settings.ParticleClusterThreshold, kParticleClusterThresholdMin, kParticleClusterThresholdMax, "%.1f"); + ImGui::SliderFloat(T("feature.light_limit_fix.particle_cluster_threshold", "Cluster Threshold"), &settings.ParticleClusterThreshold, kParticleClusterThresholdMin, kParticleClusterThresholdMax, "%.1f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Distance+radius similarity threshold for merging particles into one light.\n" - "Higher = more merging, better performance, blurrier lights.\n" - "Lower = less merging, more precise, more expensive."); + ImGui::Text("%s", + T("feature.light_limit_fix.particle_cluster_threshold_tooltip", + "Distance+radius similarity threshold for merging particles into one light.\n" + "Higher = more merging, better performance, blurrier lights.\n" + "Lower = less merging, more precise, more expensive.")); } - ImGui::SliderInt("Max Particles per Emitter", &settings.MaxParticlesPerEmitter, kMaxParticlesPerEmitterMin, kMaxParticlesPerEmitterMax); + ImGui::SliderInt(T("feature.light_limit_fix.max_particles_per_emitter", "Max Particles per Emitter"), &settings.MaxParticlesPerEmitter, kMaxParticlesPerEmitterMin, kMaxParticlesPerEmitterMax); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Maximum number of particles sampled per emitter per frame.\n" - "Higher = closer to the real particle system but more CPU work.\n" - "Lower = faster, especially for very dense effects."); + ImGui::Text("%s", + T("feature.light_limit_fix.max_particles_per_emitter_tooltip", + "Maximum number of particles sampled per emitter per frame.\n" + "Higher = closer to the real particle system but more CPU work.\n" + "Lower = faster, especially for very dense effects.")); } - ImGui::SliderFloat("Max Particle Distance", &settings.MaxParticleDistance, 1000.0f, kMaxParticleDistanceMax, "%.0f"); + ImGui::SliderFloat(T("feature.light_limit_fix.max_particle_distance", "Max Particle Distance"), &settings.MaxParticleDistance, 1000.0f, kMaxParticleDistanceMax, "%.0f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Particle lights beyond this distance from the camera are skipped entirely.\n" - "Lower = better performance, but distant effects won't contribute light.\n" - "Higher = more distant particle lighting, but more cost."); + ImGui::Text("%s", + T("feature.light_limit_fix.max_particle_distance_tooltip", + "Particle lights beyond this distance from the camera are skipped entirely.\n" + "Lower = better performance, but distant effects won't contribute light.\n" + "Higher = more distant particle lighting, but more cost.")); } ImGui::TreePop(); } - if (ImGui::TreeNode("Appearance##particles")) { - ImGui::SliderFloat("Saturation", &settings.ParticleLightsSaturation, kParticleLightsSaturationMin, kParticleLightsSaturationMax, "%.2f"); + if (ImGui::TreeNode(T("feature.light_limit_fix.particle_appearance", "Appearance##particles"))) { + ImGui::SliderFloat(T("feature.light_limit_fix.particle_saturation", "Saturation"), &settings.ParticleLightsSaturation, kParticleLightsSaturationMin, kParticleLightsSaturationMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Color saturation of particle/billboard lights. 1.0 = source color; higher = more vivid."); + ImGui::Text("%s", T("feature.light_limit_fix.particle_saturation_tooltip", "Color saturation of particle/billboard lights. 1.0 = source color; higher = more vivid.")); } - ImGui::SliderFloat("Particle Brightness", &settings.ParticleBrightness, kParticleBrightnessMin, kParticleBrightnessMax, "%.2f"); + ImGui::SliderFloat(T("feature.light_limit_fix.particle_brightness", "Particle Brightness"), &settings.ParticleBrightness, kParticleBrightnessMin, kParticleBrightnessMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Intensity multiplier for particle-system emitters (fire, sparks, magic)."); + ImGui::Text("%s", T("feature.light_limit_fix.particle_brightness_tooltip", "Intensity multiplier for particle-system emitters (fire, sparks, magic).")); } - ImGui::SliderFloat("Particle Radius", &settings.ParticleRadius, kParticleRadiusMin, kParticleRadiusMax, "%.2f"); + ImGui::SliderFloat(T("feature.light_limit_fix.particle_radius", "Particle Radius"), &settings.ParticleRadius, kParticleRadiusMin, kParticleRadiusMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Radius multiplier for particle-system emitters. Larger = light reaches further."); + ImGui::Text("%s", T("feature.light_limit_fix.particle_radius_tooltip", "Radius multiplier for particle-system emitters. Larger = light reaches further.")); } - ImGui::SliderFloat("Billboard Brightness", &settings.BillboardBrightness, kBillboardBrightnessMin, kBillboardBrightnessMax, "%.2f"); + ImGui::SliderFloat(T("feature.light_limit_fix.billboard_brightness", "Billboard Brightness"), &settings.BillboardBrightness, kBillboardBrightnessMin, kBillboardBrightnessMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Intensity multiplier for billboard (single-quad) emitters such as candle flames."); + ImGui::Text("%s", T("feature.light_limit_fix.billboard_brightness_tooltip", "Intensity multiplier for billboard (single-quad) emitters such as candle flames.")); } - ImGui::SliderFloat("Billboard Radius", &settings.BillboardRadius, kBillboardRadiusMin, kBillboardRadiusMax, "%.2f"); + ImGui::SliderFloat(T("feature.light_limit_fix.billboard_radius", "Billboard Radius"), &settings.BillboardRadius, kBillboardRadiusMin, kBillboardRadiusMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Radius multiplier for billboard emitters. Larger = light reaches further."); + ImGui::Text("%s", T("feature.light_limit_fix.billboard_radius_tooltip", "Radius multiplier for billboard emitters. Larger = light reaches further.")); } ImGui::TreePop(); } - ImGui::SeparatorText("Placed Lights (JSON)"); + ImGui::SeparatorText(T("feature.light_limit_fix.placed_lights_json_header", "Placed Lights (JSON)")); - ImGui::TextWrapped( - "Scales the intensity of runtime lights attached from Light records by Light Placer-style mods. " - "Separate from particle lights; requires Inverse Square Lighting for the runtime metadata."); + ImGui::TextWrapped("%s", + T("feature.light_limit_fix.placed_lights_json_intro", + "Scales the intensity of runtime lights attached from Light records by Light Placer-style mods. " + "Separate from particle lights; requires Inverse Square Lighting for the runtime metadata.")); ImGui::Spacing(); { const bool jsonPlacedLightsSupported = globals::features::inverseSquareLighting.loaded; ImGui::BeginDisabled(!jsonPlacedLightsSupported); - ImGui::SliderFloat("Intensity Scale", &settings.JsonPlacedLightIntensity, kJsonPlacedLightIntensityMin, kJsonPlacedLightIntensityMax, "%.2f"); + ImGui::SliderFloat(T("feature.light_limit_fix.json_intensity_scale", "Intensity Scale"), &settings.JsonPlacedLightIntensity, kJsonPlacedLightIntensityMin, kJsonPlacedLightIntensityMax, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Scales intensity for attached runtime lights generated from Light records.\n" - "Primarily targets Light Placer-style JSON lights.\n" - "Requires Inverse Square Lighting runtime metadata."); + ImGui::Text("%s", + T("feature.light_limit_fix.json_intensity_scale_tooltip", + "Scales intensity for attached runtime lights generated from Light records.\n" + "Primarily targets Light Placer-style JSON lights.\n" + "Requires Inverse Square Lighting runtime metadata.")); } - ImGui::Checkbox("Interiors Only", &settings.JsonPlacedLightsInteriorsOnly); - ImGui::Checkbox("Portal Strict Only", &settings.JsonPlacedLightsPortalStrictOnly); + ImGui::Checkbox(T("feature.light_limit_fix.json_interiors_only", "Interiors Only"), &settings.JsonPlacedLightsInteriorsOnly); + ImGui::Checkbox(T("feature.light_limit_fix.json_portal_strict_only", "Portal Strict Only"), &settings.JsonPlacedLightsPortalStrictOnly); ImGui::EndDisabled(); if (!jsonPlacedLightsSupported) - ImGui::TextDisabled("Requires Inverse Square Lighting to identify JSON-placed runtime lights."); + ImGui::TextDisabled("%s", T("feature.light_limit_fix.json_requires_isl", "Requires Inverse Square Lighting to identify JSON-placed runtime lights.")); } /////////////////////////////// diff --git a/src/Features/LightLimitFix/ShadowCasterManager.cpp b/src/Features/LightLimitFix/ShadowCasterManager.cpp index a6b5c84fca..ec548867cf 100644 --- a/src/Features/LightLimitFix/ShadowCasterManager.cpp +++ b/src/Features/LightLimitFix/ShadowCasterManager.cpp @@ -14,9 +14,12 @@ #include "../../Utils/UI.h" #include "../Upscaling.h" #include "../VR.h" +#include "I18n/I18n.h" #include +#define I18N_KEY_PREFIX "feature.light_limit_fix." + namespace ShadowCasterManager { // ========================================================================= @@ -4171,15 +4174,15 @@ namespace ShadowCasterManager } if (rows.empty()) { - ImGui::TextDisabled("No shadow slots this frame."); + ImGui::TextDisabled(T(TKEY("no_shadow_slots_this_frame"), "No shadow slots this frame.")); return; } // -- Header: active count + suppression badge ---------------------- - ImGui::Text("Shadow slots: %u active", s_shadowSlotUsage); + ImGui::Text(T(TKEY("shadow_slots_active"), "Shadow slots: %u active"), s_shadowSlotUsage); if (!s_suppressedLights.empty()) { ImGui::SameLine(); - ImGui::TextColored(ImVec4(1, 0.6f, 0.2f, 1), " %zu suppressed", s_suppressedLights.size()); + ImGui::TextColored(ImVec4(1, 0.6f, 0.2f, 1), T(TKEY("suppressed_count"), " %zu suppressed"), s_suppressedLights.size()); } // -- Group toggle buttons ------------------------------------------ @@ -4228,18 +4231,19 @@ namespace ShadowCasterManager return [type](const SlotRow& r) { return r.info.type == type; }; }; groupButton( - "All", [](const SlotRow&) { return true; }, nullptr); + T(TKEY("group_btn_all"), "All"), [](const SlotRow&) { return true; }, nullptr); ImGui::SameLine(); - groupButton("Spot", typePred(0), "Toggle all spot/frustum shadow lights"); + groupButton(T(TKEY("group_btn_spot"), "Spot"), typePred(0), T(TKEY("group_tip_spot"), "Toggle all spot/frustum shadow lights")); ImGui::SameLine(); - groupButton("Hemi", typePred(1), "Toggle all hemisphere shadow lights"); + groupButton(T(TKEY("group_btn_hemi"), "Hemi"), typePred(1), T(TKEY("group_tip_hemi"), "Toggle all hemisphere shadow lights")); ImGui::SameLine(); - groupButton("Omni", typePred(2), "Toggle all omni (paraboloid) shadow lights"); + groupButton(T(TKEY("group_btn_omni"), "Omni"), typePred(2), T(TKEY("group_tip_omni"), "Toggle all omni (paraboloid) shadow lights")); ImGui::SameLine(); groupButton( - "Conv", [](const SlotRow& r) { return r.converted; }, - "Toggle all lights currently demoted from shadow to normal\n" - "(ConvertExcessToNormal). Hides their cluster-light contribution."); + T(TKEY("group_btn_conv"), "Conv"), [](const SlotRow& r) { return r.converted; }, + T(TKEY("group_tip_conv"), + "Toggle all lights currently demoted from shadow to normal\n" + "(ConvertExcessToNormal). Hides their cluster-light contribution.")); // "Clear All": resets every debug override (suppress / pin shadow / // pin convert / solo) so the table returns to scheduler-auto. Only @@ -4249,34 +4253,35 @@ namespace ShadowCasterManager ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.55f, 0.25f, 0.25f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.75f, 0.35f, 0.35f, 1)); - if (ImGui::SmallButton("Clear All")) + if (ImGui::SmallButton(T(TKEY("clear_all_btn"), "Clear All"))) ClearAllOverrides(); ImGui::PopStyleColor(2); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Reset every debug override:\n" - " - clear suppression\n" - " - clear shadow / convert pins\n" - " - clear solo\n" - "Returns the table to scheduler-auto behaviour."); + ImGui::SetTooltip("%s", T(TKEY("clear_all_tooltip"), + "Reset every debug override:\n" + " - clear suppression\n" + " - clear shadow / convert pins\n" + " - clear solo\n" + "Returns the table to scheduler-auto behaviour.")); } // Help marker: explains the per-row debug controls so users aren't // surprised by states / pulses they didn't know they could trigger. ImGui::SameLine(); Util::HelpMarker( - "Per-row controls:\n" - " * Cycle button (col 1): click to rotate this light through\n" - " Auto -> Shadow pin (S) -> Convert pin (C) -> Suppress (X) -> Auto.\n" - " * Solo button (col 2): isolate this light against a black scene.\n" - " Click again to clear; only one light may be soloed at a time.\n" - " * Hold Shift while hovering a row to highlight that light in the\n" - " world with a pulsing magenta tint. Release Shift or move the\n" - " cursor away to stop. Useful when you can't tell which entry\n" - " corresponds to which physical light. Does not affect rendering\n" - " when Shift is not held.\n\n" - "Group buttons toggle suppression for every matching row at once.\n" - "Clear All appears when any override is active and resets everything."); + T(TKEY("per_row_controls_help"), + "Per-row controls:\n" + " * Cycle button (col 1): click to rotate this light through\n" + " Auto -> Shadow pin (S) -> Convert pin (C) -> Suppress (X) -> Auto.\n" + " * Solo button (col 2): isolate this light against a black scene.\n" + " Click again to clear; only one light may be soloed at a time.\n" + " * Hold Shift while hovering a row to highlight that light in the\n" + " world with a pulsing magenta tint. Release Shift or move the\n" + " cursor away to stop. Useful when you can't tell which entry\n" + " corresponds to which physical light. Does not affect rendering\n" + " when Shift is not held.\n\n" + "Group buttons toggle suppression for every matching row at once.\n" + "Clear All appears when any override is active and resets everything.")); } // -- Filter input -------------------------------------------------- @@ -4288,7 +4293,7 @@ namespace ShadowCasterManager if (ImGui::InputText("##slotfilter", buf, sizeof(buf))) s_filterText = buf; ImGui::SameLine(); - ImGui::TextDisabled(sceneOnly ? "filter (yes/conv/type/range/addr)" : "filter (yes/conv/no/type/range/addr)"); + ImGui::TextDisabled(sceneOnly ? T(TKEY("filter_hint_scene_only"), "filter (yes/conv/type/range/addr)") : T(TKEY("filter_hint"), "filter (yes/conv/no/type/range/addr)")); } // Apply filter. @@ -4338,16 +4343,16 @@ namespace ShadowCasterManager std::vector headers; if (showButtons) { - headers.push_back("Mode"); // cycle: Auto / Pin-S / Pin-C / Suppress - headers.push_back("Solo"); + headers.push_back(T(TKEY("col_mode"), "Mode")); // cycle: Auto / Pin-S / Pin-C / Suppress + headers.push_back(T(TKEY("col_solo"), "Solo")); } - headers.push_back("Status"); - headers.push_back("Address"); + headers.push_back(T(TKEY("col_status"), "Status")); + headers.push_back(T(TKEY("col_address"), "Address")); if (showColor) - headers.push_back("Color"); - headers.push_back("Type"); - headers.push_back("Range"); - headers.push_back("Imp"); + headers.push_back(T(TKEY("col_color"), "Color")); + headers.push_back(T(TKEY("col_type"), "Type")); + headers.push_back(T(TKEY("col_range"), "Range")); + headers.push_back(T(TKEY("col_imp"), "Imp")); using SortFn = std::function; std::vector sorts(headers.size(), nullptr); @@ -4433,9 +4438,9 @@ namespace ShadowCasterManager // Hidden in readOnly mode (overlay with menu closed). // Focus rows skip Mode/Solo entirely -- engine owns the slot. if (row.isFocus && col == modeColIdx) { - ImGui::TextDisabled("eng"); + ImGui::TextDisabled(T(TKEY("mode_eng"), "eng")); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Engine-controlled focus shadow; not pinnable/suppressible."); + ImGui::SetTooltip("%s", T(TKEY("mode_eng_tooltip"), "Engine-controlled focus shadow; not pinnable/suppressible.")); return; } if (row.isFocus && col == soloColIdx) { @@ -4447,22 +4452,22 @@ namespace ShadowCasterManager const char* label = "·"; ImVec4 col4 = ImVec4(0.15f, 0.6f, 0.15f, 1); // green = auto/active ImVec4 colH = ImVec4(0.2f, 0.75f, 0.2f, 1); - const char* tip = "Auto (scheduler decides)\nClick: pin as shadow caster"; + const char* tip = T(TKEY("mode_tip_auto"), "Auto (scheduler decides)\nClick: pin as shadow caster"); if (pinShadow) { label = "S"; col4 = ImVec4(0.20f, 0.40f, 0.85f, 1); // blue colH = ImVec4(0.30f, 0.55f, 1.0f, 1); - tip = "Pinned: forced shadow caster\nClick: pin as converted (non-shadow)"; + tip = T(TKEY("mode_tip_pin_shadow"), "Pinned: forced shadow caster\nClick: pin as converted (non-shadow)"); } else if (pinConvert) { label = "C"; col4 = ImVec4(0.85f, 0.55f, 0.15f, 1); // amber colH = ImVec4(1.0f, 0.7f, 0.25f, 1); - tip = "Pinned: forced converted (non-shadow)\nClick: suppress entirely"; + tip = T(TKEY("mode_tip_pin_convert"), "Pinned: forced converted (non-shadow)\nClick: suppress entirely"); } else if (suppressed) { label = "X"; col4 = ImVec4(0.45f, 0.25f, 0.25f, 1); // dim red colH = ImVec4(0.6f, 0.35f, 0.35f, 1); - tip = "Suppressed (hidden)\nClick: return to auto"; + tip = T(TKEY("mode_tip_suppressed"), "Suppressed (hidden)\nClick: return to auto"); } ImGui::PushStyleColor(ImGuiCol_Button, col4); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colH); @@ -4505,8 +4510,8 @@ namespace ShadowCasterManager if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", isSolo ? - "Solo: this light is shown alone\nClick: clear solo" : - "Solo this light\n(suppresses every other light\nuntil cleared)"); + T(TKEY("solo_tip_active"), "Solo: this light is shown alone\nClick: clear solo") : + T(TKEY("solo_tip"), "Solo this light\n(suppresses every other light\nuntil cleared)")); ImGui::PopID(); return; } @@ -4518,27 +4523,27 @@ namespace ShadowCasterManager // Merged "In Scene" + "Slot" column. Four mutually-exclusive // states; suppressed wins because the user explicitly hid it. if (suppressed) { - ImGui::TextColored(ImVec4(0.85f, 0.35f, 0.35f, 1), "Suppr"); + ImGui::TextColored(ImVec4(0.85f, 0.35f, 0.35f, 1), T(TKEY("status_suppr"), "Suppr")); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Suppressed by debug override.\nClick the Mode button to clear."); + ImGui::SetTooltip("%s", T(TKEY("status_suppr_tooltip"), "Suppressed by debug override.\nClick the Mode button to clear.")); } else if (row.inScene) { - ImGui::Text("Slot %u", row.idx); + ImGui::Text(T(TKEY("status_slot"), "Slot %u"), row.idx); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Casting shadows this frame in slot %u.", row.idx); + ImGui::SetTooltip(T(TKEY("status_slot_tooltip"), "Casting shadows this frame in slot %u."), row.idx); } else if (row.converted) { - ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.25f, 1), "Conv"); + ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.25f, 1), T(TKEY("status_conv"), "Conv")); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Demoted to a normal (non-shadow) light this frame.\n" - "Cluster lighting still illuminates it; no shadow-map cost."); + ImGui::SetTooltip("%s", T(TKEY("status_conv_tooltip"), + "Demoted to a normal (non-shadow) light this frame.\n" + "Cluster lighting still illuminates it; no shadow-map cost.")); } else { - ImGui::TextDisabled("Out"); + ImGui::TextDisabled(T(TKEY("status_out"), "Out")); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Out of range / not active in the current frame."); + ImGui::SetTooltip("%s", T(TKEY("status_out_tooltip"), "Out of range / not active in the current frame.")); } } else if (col == addrColIdx) { if (row.isFocus) { - ImGui::TextDisabled("focus[%u]", row.idx - static_cast(kFocusShadowBaseSlotIndex)); + ImGui::TextDisabled(T(TKEY("addr_focus"), "focus[%u]"), row.idx - static_cast(kFocusShadowBaseSlotIndex)); } else { char addrFull[20]; snprintf(addrFull, sizeof(addrFull), "0x%016llX", static_cast(row.info.lightKey)); @@ -4547,7 +4552,7 @@ namespace ShadowCasterManager ImGui::SetClipboardText(addrFull); noteHover(); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Click to copy: %s", addrFull); + ImGui::SetTooltip(T(TKEY("addr_click_to_copy"), "Click to copy: %s"), addrFull); } } else if (showColor && col == addrColIdx + 1) { ImVec4 c = ShadowSlotHueColor(row.idx); @@ -4560,14 +4565,15 @@ namespace ShadowCasterManager ImGui::SetTooltip("#%02X%02X%02X", ri, gi, bi); } else if (col == typeColIdx) { if (row.isFocus) { - ImGui::TextColored(ImVec4(0.55f, 0.75f, 1.0f, 1.0f), "Focus"); + ImGui::TextColored(ImVec4(0.55f, 0.75f, 1.0f, 1.0f), T(TKEY("type_focus"), "Focus")); if (ImGui::IsItemHovered()) ImGui::SetTooltip( - "Engine-owned focus shadow slot.\n" - "FocusShadowActors[%u] = high-res shadow for a tracked\n" - "actor (player + dialog/combat NPCs). SCM reserves\n" - "this slot so the engine's focus render isn't trampled\n" - "by point/spot lights.", + T(TKEY("type_focus_tooltip"), + "Engine-owned focus shadow slot.\n" + "FocusShadowActors[%u] = high-res shadow for a tracked\n" + "actor (player + dialog/combat NPCs). SCM reserves\n" + "this slot so the engine's focus render isn't trampled\n" + "by point/spot lights."), row.idx - static_cast(kFocusShadowBaseSlotIndex)); } else { ImGui::TextUnformatted(kShadowTypeNames[std::min(row.info.type, 2u)]); @@ -4590,17 +4596,17 @@ namespace ShadowCasterManager ImVec4 colour = ImVec4(1.0f - t * 0.7f, 1.0f, 1.0f - t * 0.7f, 1.0f); // white → green ImGui::TextColored(colour, "%.2f", imp); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Contribution importance score:\n" - " luminance(diffuse * fade)\n" - " * max(att_camera, att_player)\n" - " where att = (1 - (dist/radius)^2)^2\n\n" - "Higher = light strongly illuminates the viewer area.\n" - "Drives interval multiplier (configurable in Advanced settings).\n" - "Default: 0 => x2.0, 0.5 => x0.32, 1 => x0.05\n\n" - "Rows tinted yellow are high-importance (>0.1)\n" - "-- they deliver meaningful illumination near the camera\n" - "or player and receive accelerated shadow redraw scheduling."); + ImGui::SetTooltip("%s", T(TKEY("importance_tooltip"), + "Contribution importance score:\n" + " luminance(diffuse * fade)\n" + " * max(att_camera, att_player)\n" + " where att = (1 - (dist/radius)^2)^2\n\n" + "Higher = light strongly illuminates the viewer area.\n" + "Drives interval multiplier (configurable in Advanced settings).\n" + "Default: 0 => x2.0, 0.5 => x0.32, 1 => x0.05\n\n" + "Rows tinted yellow are high-importance (>0.1)\n" + "-- they deliver meaningful illumination near the camera\n" + "or player and receive accelerated shadow redraw scheduling.")); } // Hi column dropped -- highImp now tints the row background // (see TableSetBgColor at the top of this lambda) so the visual @@ -4625,23 +4631,23 @@ namespace ShadowCasterManager const uint32_t requested = slotUsage + shadowUnshadowedLightCount; if (clusterCount >= clusterMax) - ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "Cluster lights : %u / %u (overflow)", clusterCount, clusterMax); + ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), T(TKEY("cluster_lights_overflow"), "Cluster lights : %u / %u (overflow)"), clusterCount, clusterMax); else - ImGui::Text("Cluster lights : %u / %u", clusterCount, clusterMax); + ImGui::Text(T(TKEY("cluster_lights"), "Cluster lights : %u / %u"), clusterCount, clusterMax); // "lights" rather than "slots" matches the Shadow Light Count // setting name -- users think in lights, the engine thinks in // texture slots, so we use the user's word. if (shadowUnshadowedLightCount > 0) ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), - "Shadow lights : %u / %u (%u wanted, %u dropped, %zu converted)", + T(TKEY("shadow_lights_dropped"), "Shadow lights : %u / %u (%u wanted, %u dropped, %zu converted)"), slotUsage, slots, requested, shadowUnshadowedLightCount, s_normalConvert.size()); else - ImGui::Text("Shadow lights : %u / %u (%u wanted, 0 dropped, %zu converted)", + ImGui::Text(T(TKEY("shadow_lights"), "Shadow lights : %u / %u (%u wanted, 0 dropped, %zu converted)"), slotUsage, slots, requested, s_normalConvert.size()); if (s_highImportanceLightCount > 0 && ImGui::IsItemHovered()) - ImGui::SetTooltip("%u high-importance (near camera/player).", + ImGui::SetTooltip(T(TKEY("high_importance_tooltip"), "%u high-importance (near camera/player)."), s_highImportanceLightCount); } @@ -4650,16 +4656,16 @@ namespace ShadowCasterManager // Avg redraws/frame: rolling average of how many shadow casters per frame // the scheduler decided to (re)render. Bounded by MaxRedrawPerFrame. float avgRedraws = static_cast(s_redrawSum) / static_cast(kRedrawHistorySize); - ImGui::Text("Avg redraws/frame : %.1f (cap: %d)", avgRedraws, s_settings.MaxRedrawPerFrame); + ImGui::Text(T(TKEY("avg_redraws_per_frame"), "Avg redraws/frame : %.1f (cap: %d)"), avgRedraws, s_settings.MaxRedrawPerFrame); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Rolling average over the last %d frames.", kRedrawHistorySize); + ImGui::SetTooltip(T(TKEY("avg_redraws_tooltip"), "Rolling average over the last %d frames."), kRedrawHistorySize); // Avg per-light cost: budget tracker's measured GPU cost per shadow caster. // Used by the formula budget mode to decide how many casters fit in the // per-frame time budget. int32_t avgCost = s_budget.GetAverageCostUs(); if (avgCost > 0) - ImGui::Text("Avg light cost : %.2f ms", avgCost / 1000.0f); + ImGui::Text(T(TKEY("avg_light_cost"), "Avg light cost : %.2f ms"), avgCost / 1000.0f); // ---- Budget verdict --------------------------------------------- // Cross-checks measured shadow cost against the user-chosen budget @@ -4679,7 +4685,7 @@ namespace ShadowCasterManager const bool headroom = avgCost > 0 && budgetMs > 0.0f && usedMs < budgetMs * 0.5f && !capLimited; if (avgCost <= 0 || budgetMs <= 0.0f) { - ImGui::TextDisabled("Budget usage : (warming up)"); + ImGui::TextDisabled(T(TKEY("budget_usage_warming_up"), "Budget usage : (warming up)")); return; } @@ -4691,28 +4697,28 @@ namespace ShadowCasterManager const char* tip; if (overBudget) { col = ImVec4(0.95f, 0.35f, 0.35f, 1); - verdict = "OVER BUDGET"; - tip = "Shadow time exceeds Redraw Budget. Lower Max Redraws or raise Redraw Budget."; + verdict = T(TKEY("verdict_over_budget"), "OVER BUDGET"); + tip = T(TKEY("verdict_over_budget_tip"), "Shadow time exceeds Redraw Budget. Lower Max Redraws or raise Redraw Budget."); } else if (capLimited && slotLimited) { col = ImVec4(0.95f, 0.65f, 0.25f, 1); - verdict = "AT LIMITS"; - tip = "Both Max Redraws and Shadow Light Count are full. Enable Convert to Normal or raise Shadow Light Count."; + verdict = T(TKEY("verdict_at_limits"), "AT LIMITS"); + tip = T(TKEY("verdict_at_limits_tip"), "Both Max Redraws and Shadow Light Count are full. Enable Convert to Normal or raise Shadow Light Count."); } else if (slotLimited) { col = ImVec4(0.95f, 0.65f, 0.25f, 1); - verdict = "LIGHT LIMITED"; - tip = "Shadow Light Count is full. Enable Convert to Normal or raise Shadow Light Count."; + verdict = T(TKEY("verdict_light_limited"), "LIGHT LIMITED"); + tip = T(TKEY("verdict_light_limited_tip"), "Shadow Light Count is full. Enable Convert to Normal or raise Shadow Light Count."); } else if (capLimited) { col = ImVec4(0.95f, 0.85f, 0.25f, 1); - verdict = "REDRAW LIMITED"; - tip = "Hitting Max Redraws Per Frame. Raise it to spend the unused Redraw Budget."; + verdict = T(TKEY("verdict_redraw_limited"), "REDRAW LIMITED"); + tip = T(TKEY("verdict_redraw_limited_tip"), "Hitting Max Redraws Per Frame. Raise it to spend the unused Redraw Budget."); } else if (headroom) { col = ImVec4(0.55f, 0.85f, 0.55f, 1); - verdict = "HEADROOM"; - tip = "Under half the Redraw Budget is being used. Raise Max Redraws or accept the slack."; + verdict = T(TKEY("verdict_headroom"), "HEADROOM"); + tip = T(TKEY("verdict_headroom_tip"), "Under half the Redraw Budget is being used. Raise Max Redraws or accept the slack."); } else { col = ImVec4(0.55f, 0.85f, 0.55f, 1); - verdict = "OK"; - tip = "Within Redraw Budget; no limits hit."; + verdict = T(TKEY("verdict_ok"), "OK"); + tip = T(TKEY("verdict_ok_tip"), "Within Redraw Budget; no limits hit."); } // Budget gauge: progress bar tinted by the verdict colour so the // state is readable at a glance, with the numeric reading and @@ -4722,7 +4728,7 @@ namespace ShadowCasterManager char overlay[80]; snprintf(overlay, sizeof(overlay), "%.2f / %.2f ms - %s", usedMs, budgetMs, verdict); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, col); - ImGui::Text("Budget usage :"); + ImGui::Text("%s", T(TKEY("budget_usage_label"), "Budget usage :")); ImGui::SameLine(); ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay); ImGui::PopStyleColor(); @@ -4747,28 +4753,29 @@ namespace ShadowCasterManager static_cast(vinfo.currentUsageBytes) / static_cast(vinfo.budgetBytes)); char overlayText[96]; snprintf(overlayText, sizeof(overlayText), - "%.0f / %.0f MB - shadows %.0f MB (%u slices)", + T(TKEY("shadow_vram_overlay"), "%.0f / %.0f MB - shadows %.0f MB (%u slices)"), usageMB, budgetMBf, arrayMB, vinfo.shadowSlices); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, vramVerdict.colour); - ImGui::Text("Shadow VRAM :"); + ImGui::Text("%s", T(TKEY("shadow_vram_label"), "Shadow VRAM :")); ImGui::SameLine(); ImGui::ProgressBar(fillFraction, ImVec2(-1.0f, 0.0f), overlayText); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip( - "Bar fill = process VRAM usage / DXGI budget (same data the\n" - "performance overlay reports). Overlay text shows the shadow\n" - "array's contribution to that usage.\n" - "\n" - "Slices : %u (sun lives in its own kSHADOWMAPS_ESRAM texture)\n" - "Per slice : %.2f MB (%u x %u @ %u B/pixel)\n" - "Shadow array : %.1f MB\n" - "Free in budget : %.1f MB\n" - "\n" - "Green when free VRAM and shadow share are comfortable.\n" - "Yellow when free < 512 MB or shadow array > 25%% of budget.\n" - "Red when free < 128 MB or shadow array > 50%% of budget --\n" - "lower Shadow Light Count or iShadowMapResolution.", + T(TKEY("shadow_vram_tooltip"), + "Bar fill = process VRAM usage / DXGI budget (same data the\n" + "performance overlay reports). Overlay text shows the shadow\n" + "array's contribution to that usage.\n" + "\n" + "Slices : %u (sun lives in its own kSHADOWMAPS_ESRAM texture)\n" + "Per slice : %.2f MB (%u x %u @ %u B/pixel)\n" + "Shadow array : %.1f MB\n" + "Free in budget : %.1f MB\n" + "\n" + "Green when free VRAM and shadow share are comfortable.\n" + "Yellow when free < 512 MB or shadow array > 25%% of budget.\n" + "Red when free < 128 MB or shadow array > 50%% of budget --\n" + "lower Shadow Light Count or iShadowMapResolution."), vinfo.shadowSlices, perSliceMB, vinfo.shadowWidth, vinfo.shadowHeight, vinfo.shadowWidth && vinfo.shadowHeight ? vinfo.bytesPerSlice / (vinfo.shadowWidth * vinfo.shadowHeight) : 0u, @@ -4784,19 +4791,19 @@ namespace ShadowCasterManager // function now carries only mode-specific information that wouldn't be // meaningful elsewhere -- channel meanings, heatmap legends, etc. if (mode == 3) { - ImGui::Text("R channel = directional soft shadow"); - ImGui::Text("G channel = directional detailed shadow"); - ImGui::TextDisabled("(B = unused)"); + ImGui::Text("%s", T(TKEY("mode3_r_channel"), "R channel = directional soft shadow")); + ImGui::Text("%s", T(TKEY("mode3_g_channel"), "G channel = directional detailed shadow")); + ImGui::TextDisabled("%s", T(TKEY("mode3_b_unused"), "(B = unused)")); } else if (mode == 4) { - ImGui::TextDisabled("Pixel heatmap: 0=blue 8+=red"); + ImGui::TextDisabled("%s", T(TKEY("mode4_heatmap"), "Pixel heatmap: 0=blue 8+=red")); } else if (mode == 5) { - ImGui::TextDisabled("White = fully lit, black = fully in shadow"); + ImGui::TextDisabled("%s", T(TKEY("mode5_lit_shadow"), "White = fully lit, black = fully in shadow")); } else if (mode == 6) { - ImGui::TextDisabled("Pixel heatmap: 0=blue 8+=red (lights without shadow maps)"); + ImGui::TextDisabled("%s", T(TKEY("mode6_heatmap"), "Pixel heatmap: 0=blue 8+=red (lights without shadow maps)")); } else if (mode == 7) { - ImGui::TextDisabled("Cool Turbo[0.0-0.3] = 1-4 shadows"); - ImGui::TextDisabled("Warm Turbo[0.3-0.8] = 5-%u shadows", GetInstalledSlotCount()); - ImGui::TextDisabled("Red = overflow"); + ImGui::TextDisabled("%s", T(TKEY("mode7_cool"), "Cool Turbo[0.0-0.3] = 1-4 shadows")); + ImGui::TextDisabled(T(TKEY("mode7_warm"), "Warm Turbo[0.3-0.8] = 5-%u shadows"), GetInstalledSlotCount()); + ImGui::TextDisabled("%s", T(TKEY("mode7_red"), "Red = overflow")); } else if (mode == 9) { uint32_t spotC = 0, hemiC = 0, omniC = 0; for (const auto& info : GetSlotInfos()) { @@ -4809,48 +4816,48 @@ namespace ShadowCasterManager else omniC++; } - ImGui::Text("R Spot (frustum) : %u", spotC); - ImGui::Text("G Hemisphere : %u", hemiC); - ImGui::Text("B Omni (paraboloid): %u", omniC); + ImGui::Text(T(TKEY("mode9_spot"), "R Spot (frustum) : %u"), spotC); + ImGui::Text(T(TKEY("mode9_hemi"), "G Hemisphere : %u"), hemiC); + ImGui::Text(T(TKEY("mode9_omni"), "B Omni (paraboloid): %u"), omniC); } } void DrawVisualisationTooltipShadowModes() { - ImGui::Text( - "\n" - "Shadow Mask: R=directional soft shadow, G=directional detailed shadow.\n" - "\n" - "Shadow Light Count: Heatmap of shadow-casting point/spot lights per pixel (blue=0, red=8+).\n" - "Use to gauge shadow density; high counts indicate expensive shadow sampling.\n" - "\n" - "Point Light Shadow Factor: Brightness shows the darkest shadow value from any point/spot\n" - "light. White=fully lit, black=fully shadowed. Shows where PCF/PCSS filtering is active.\n" - "\n" - "Unshadowed Point Lights: Heatmap of point/spot lights without shadow maps (blue=0, red=8+).\n" - "High values where lights are bright indicate where the shadow slot limit is costing quality.\n" - "\n" - "Shadow Caster Density: Custom Turbo ranges show how heavily shadow slots are used.\n" - " Cool (Turbo 0.0-0.3): 1-4 shadow lights per pixel.\n" - " Warm (Turbo 0.3-0.8): 5 to ShadowMapSlots lights (dynamic range).\n" - " Bright red: overflow - a light wanted a shadow slot but none was available.\n" - "\n" - "Shadow Slot Index Color: Assigns each shadow-map slot a unique high-contrast hue\n" - "(golden-ratio sequence) so you can identify which slot is casting the primary shadow.\n" - "First valid shadow light index per pixel is shown. Bright red = slot overflow.\n" - "\n" - "Light Type Visualization: RGB channels encode shadow light types per pixel.\n" - " R = spot/frustum lights (ShadowParam.x == 0).\n" - " G = hemisphere/paraboloid lights (ShadowParam.x == 1).\n" - " B = omnidirectional/full-paraboloid lights (ShadowParam.x == 2).\n" - " Dark grey = unshadowed lights only (no shadow maps assigned).\n" - " Bright red = overflow (slot capacity exceeded).\n" - "Intensity scales with count (up to 4); channels blend for mixed-type pixels."); + ImGui::Text("%s", T(TKEY("visualisation_tooltip_shadow_modes"), + "\n" + "Shadow Mask: R=directional soft shadow, G=directional detailed shadow.\n" + "\n" + "Shadow Light Count: Heatmap of shadow-casting point/spot lights per pixel (blue=0, red=8+).\n" + "Use to gauge shadow density; high counts indicate expensive shadow sampling.\n" + "\n" + "Point Light Shadow Factor: Brightness shows the darkest shadow value from any point/spot\n" + "light. White=fully lit, black=fully shadowed. Shows where PCF/PCSS filtering is active.\n" + "\n" + "Unshadowed Point Lights: Heatmap of point/spot lights without shadow maps (blue=0, red=8+).\n" + "High values where lights are bright indicate where the shadow slot limit is costing quality.\n" + "\n" + "Shadow Caster Density: Custom Turbo ranges show how heavily shadow slots are used.\n" + " Cool (Turbo 0.0-0.3): 1-4 shadow lights per pixel.\n" + " Warm (Turbo 0.3-0.8): 5 to ShadowMapSlots lights (dynamic range).\n" + " Bright red: overflow - a light wanted a shadow slot but none was available.\n" + "\n" + "Shadow Slot Index Color: Assigns each shadow-map slot a unique high-contrast hue\n" + "(golden-ratio sequence) so you can identify which slot is casting the primary shadow.\n" + "First valid shadow light index per pixel is shown. Bright red = slot overflow.\n" + "\n" + "Light Type Visualization: RGB channels encode shadow light types per pixel.\n" + " R = spot/frustum lights (ShadowParam.x == 0).\n" + " G = hemisphere/paraboloid lights (ShadowParam.x == 1).\n" + " B = omnidirectional/full-paraboloid lights (ShadowParam.x == 2).\n" + " Dark grey = unshadowed lights only (no shadow maps assigned).\n" + " Bright red = overflow (slot capacity exceeded).\n" + "Intensity scales with count (up to 4); channels blend for mixed-type pixels.")); } void DrawSettings(Settings& settings) { - ImGui::SeparatorText("Shadow Limit Fix"); + ImGui::SeparatorText(T(TKEY("shadow_limit_fix_header"), "Shadow Limit Fix")); // ---- External conflict banner -------------------------------------- if (s_externalConflict) { @@ -4860,18 +4867,18 @@ namespace ShadowCasterManager } // ---- Enable toggle (requires restart) ------------------------------ - ImGui::Checkbox("Enable Shadow Limit Fix", &settings.Enabled); + ImGui::Checkbox(T(TKEY("enable_shadow_limit_fix"), "Enable Shadow Limit Fix"), &settings.Enabled); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Extends Skyrim's hard limit of 4 simultaneous shadow-casting lights.\n" - "Intelligently selects which lights cast shadows each frame based on\n" - "distance, intensity, and a configurable priority formula.\n\n" - "Based on Intellightent by meh321.\n" - "https://www.nexusmods.com/skyrimspecialedition/mods/172423\n\n" - "Restart required to take effect in either direction. The boot-time\n" - "patches (extended atlas slices, depth buffer creation loop, color-mask\n" - "pass replacement) cannot be safely reversed at runtime -- vanilla\n" - "shadow scheduling crashes when run on top of them. Toggle and restart."); + ImGui::SetTooltip("%s", T(TKEY("enable_shadow_limit_fix_tooltip"), + "Extends Skyrim's hard limit of 4 simultaneous shadow-casting lights.\n" + "Intelligently selects which lights cast shadows each frame based on\n" + "distance, intensity, and a configurable priority formula.\n\n" + "Based on Intellightent by meh321.\n" + "https://www.nexusmods.com/skyrimspecialedition/mods/172423\n\n" + "Restart required to take effect in either direction. The boot-time\n" + "patches (extended atlas slices, depth buffer creation loop, color-mask\n" + "pass replacement) cannot be safely reversed at runtime -- vanilla\n" + "shadow scheduling crashes when run on top of them. Toggle and restart.")); // Either direction requires restart -- the boot-time patches modify // the engine's shadow texture array, depth buffer creation, and // color-mask pass. Vanilla scheduling cannot run on top of those @@ -4886,7 +4893,7 @@ namespace ShadowCasterManager if (s_bootEnabledCaptured && settings.Enabled != s_bootEnabled) { const auto& theme = Menu::GetSingleton()->GetTheme(); ImGui::TextColored(theme.StatusPalette.RestartNeeded, - "Restart required -- this session is %s.", s_bootEnabled ? "enabled" : "disabled"); + T(TKEY("restart_session_state"), "Restart required -- this session is %s."), s_bootEnabled ? T(TKEY("session_enabled"), "enabled") : T(TKEY("session_disabled"), "disabled")); } if (!settings.Enabled) @@ -4898,7 +4905,7 @@ namespace ShadowCasterManager // successfully -- some internal limit (likely an 8-bit shadow index // somewhere we haven't patched) silently disables shadow rendering. // 127 is the highest value that actually works. - ImGui::SliderInt("Shadow Light Count", &settings.ShadowLightCount, 0, 127); + ImGui::SliderInt(T(TKEY("shadow_light_count"), "Shadow Light Count"), &settings.ShadowLightCount, 0, 127); // Compute projected VRAM for the slider's current value so the user // can see the cost of a higher count *before* committing the restart. // kSHADOWMAPS holds exactly ShadowLightCount slices -- the sun lives @@ -4917,20 +4924,22 @@ namespace ShadowCasterManager projectedFreeBytes = (static_cast(sliderVram.budgetBytes) > projectedUsage) ? static_cast(sliderVram.budgetBytes - projectedUsage) : 0; } if (ImGui::IsItemHovered()) { - constexpr const char* kSliderBase = - "Maximum simultaneous shadow-casting point/spot lights (directional sun not counted).\n" - " 0 = scheduler runs but selects no point lights (sun/directional unaffected).\n" - " 4 = vanilla point light count with intelligent selection.\n" - " >4 = extended mode; depth buffer expanded when >8. Max 127\n" - " (VRAM is the practical limit -- watch the projected-VRAM bar).\n" - "Requires a game restart to take effect."; + const char* kSliderBase = + T(TKEY("shadow_light_count_tooltip"), + "Maximum simultaneous shadow-casting point/spot lights (directional sun not counted).\n" + " 0 = scheduler runs but selects no point lights (sun/directional unaffected).\n" + " 4 = vanilla point light count with intelligent selection.\n" + " >4 = extended mode; depth buffer expanded when >8. Max 127\n" + " (VRAM is the practical limit -- watch the projected-VRAM bar).\n" + "Requires a game restart to take effect."); if (projectionValid) { ImGui::SetTooltip( - "%s\n" - "\n" - "Projected kSHADOWMAPS array at %d slots: %.1f MB\n" - "Per-slice cost: %.2f MB (%u x %u, %u B/pixel)\n" - "Projected free VRAM after restart: %.1f MB", + T(TKEY("shadow_light_count_projection_tooltip"), + "%s\n" + "\n" + "Projected kSHADOWMAPS array at %d slots: %.1f MB\n" + "Per-slice cost: %.2f MB (%u x %u, %u B/pixel)\n" + "Projected free VRAM after restart: %.1f MB"), kSliderBase, settings.ShadowLightCount, static_cast(projectedBytes) / (1024.f * 1024.f), @@ -4955,7 +4964,7 @@ namespace ShadowCasterManager const float currentShadowMB = static_cast(sliderVram.shadowArrayBytes) / (1024.f * 1024.f); const float projectedShadowMB = static_cast(projectedBytes) / (1024.f * 1024.f); - ImGui::Text("Projected shadow VRAM :"); + ImGui::Text("%s", T(TKEY("projected_shadow_vram_label"), "Projected shadow VRAM :")); ImGui::SameLine(); const ImVec2 cursor = ImGui::GetCursorScreenPos(); const float fullWidth = ImGui::GetContentRegionAvail().x; @@ -4989,7 +4998,7 @@ namespace ShadowCasterManager char overlay[128]; snprintf(overlay, sizeof(overlay), - "shadows %.0f -> %.0f MB (%d slots, %.0f MB free after restart)", + T(TKEY("projected_shadow_vram_overlay"), "shadows %.0f -> %.0f MB (%d slots, %.0f MB free after restart)"), currentShadowMB, projectedShadowMB, settings.ShadowLightCount, static_cast(projectedFreeBytes) / (1024.f * 1024.f)); @@ -5000,22 +5009,23 @@ namespace ShadowCasterManager ImGui::Dummy(ImVec2(fullWidth, barHeight)); // reserve layout space if (ImGui::IsItemHovered()) { ImGui::SetTooltip( - "Stacked VRAM bar against DXGI budget.\n" - " Grey block : process VRAM not counted as shadow array\n" - " Blue block : current kSHADOWMAPS allocation this session\n" - " Outlined block: what the slider's value would allocate\n" - " after restart (colour reflects verdict)\n" - "\n" - "Solid colour past the blue: shadow array would GROW by that\n" - "amount. Dark stripe inside the blue: shadow array would\n" - "SHRINK by that amount.\n" - "\n" - "Slots requested : %d (sun lives in kSHADOWMAPS_ESRAM)\n" - "Per-slice cost : %.2f MB (%u x %u @ %u B/pixel)\n" - "Current array : %.1f MB\n" - "Projected array : %.1f MB\n" - "Free after restart : %.1f MB / %.0f MB budget\n" - "%s", + T(TKEY("projected_shadow_vram_tooltip"), + "Stacked VRAM bar against DXGI budget.\n" + " Grey block : process VRAM not counted as shadow array\n" + " Blue block : current kSHADOWMAPS allocation this session\n" + " Outlined block: what the slider's value would allocate\n" + " after restart (colour reflects verdict)\n" + "\n" + "Solid colour past the blue: shadow array would GROW by that\n" + "amount. Dark stripe inside the blue: shadow array would\n" + "SHRINK by that amount.\n" + "\n" + "Slots requested : %d (sun lives in kSHADOWMAPS_ESRAM)\n" + "Per-slice cost : %.2f MB (%u x %u @ %u B/pixel)\n" + "Current array : %.1f MB\n" + "Projected array : %.1f MB\n" + "Free after restart : %.1f MB / %.0f MB budget\n" + "%s"), settings.ShadowLightCount, static_cast(sliderVram.bytesPerSlice) / (1024.f * 1024.f), sliderVram.shadowWidth, sliderVram.shadowHeight, @@ -5027,14 +5037,16 @@ namespace ShadowCasterManager static_cast(projectedFreeBytes) / (1024.f * 1024.f), budgetMBf, verdict.over ? - "\nRED: this projection won't fit in the current VRAM budget.\n" - "The driver will page or refuse the allocation, leaving the\n" - "shadow array smaller than requested -- shadows will silently\n" - "break. Lower the slot count or reduce iShadowMapResolution." : + T(TKEY("projected_vram_verdict_red"), + "\nRED: this projection won't fit in the current VRAM budget.\n" + "The driver will page or refuse the allocation, leaving the\n" + "shadow array smaller than requested -- shadows will silently\n" + "break. Lower the slot count or reduce iShadowMapResolution.") : verdict.tight ? - "\nYELLOW: tight headroom. A driver or OS spike could push\n" - "shadow allocation into paging. Safe for testing, risky for\n" - "long sessions or heavily-modded scenes." : + T(TKEY("projected_vram_verdict_yellow"), + "\nYELLOW: tight headroom. A driver or OS spike could push\n" + "shadow allocation into paging. Safe for testing, risky for\n" + "long sessions or heavily-modded scenes.") : ""); } } @@ -5049,33 +5061,34 @@ namespace ShadowCasterManager uint32_t requested = s_requestedSlotCount; if (installed > 0 && requested > 0 && installed < requested) { ImGui::TextColored(ImVec4(0.95f, 0.35f, 0.35f, 1), - "VRAM exhausted: requested %u slots, GPU allocated %u.", + T(TKEY("vram_exhausted_banner"), "VRAM exhausted: requested %u slots, GPU allocated %u."), requested, installed); if (ImGui::IsItemHovered()) ImGui::SetTooltip( - "The engine tried to create kSHADOWMAPS with %u slices but\n" - "the GPU / driver returned a smaller array (likely out of\n" - "VRAM at the configured iShadowMapResolution). The scheduler\n" - "has clamped itself to the actual count so the existing %u\n" - "slices work correctly, but to reach the requested %u you'll\n" - "need to free VRAM (lower resolution, other features, etc).", + T(TKEY("vram_exhausted_tooltip"), + "The engine tried to create kSHADOWMAPS with %u slices but\n" + "the GPU / driver returned a smaller array (likely out of\n" + "VRAM at the configured iShadowMapResolution). The scheduler\n" + "has clamped itself to the actual count so the existing %u\n" + "slices work correctly, but to reach the requested %u you'll\n" + "need to free VRAM (lower resolution, other features, etc)."), requested, installed, requested); } else if (installed == 0 && s_settings.Enabled && !s_externalConflict) { ImGui::TextColored(ImVec4(0.95f, 0.85f, 0.25f, 1), - "Shadow array not yet verified -- load a save to confirm allocation."); + "%s", T(TKEY("shadow_array_unverified_banner"), "Shadow array not yet verified -- load a save to confirm allocation.")); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "kSHADOWMAPS isn't readable yet (main menu / loading screen).\n" - "Once you reach gameplay the scheduler verifies the actual\n" - "slice count against your requested value. If they disagree\n" - "this banner turns red."); + ImGui::SetTooltip("%s", T(TKEY("shadow_array_unverified_tooltip"), + "kSHADOWMAPS isn't readable yet (main menu / loading screen).\n" + "Once you reach gameplay the scheduler verifies the actual\n" + "slice count against your requested value. If they disagree\n" + "this banner turns red.")); } } if (settings.ShadowLightCount != s_installedShadowLightCount) { const auto& theme = Menu::GetSingleton()->GetTheme(); ImGui::TextColored(theme.StatusPalette.RestartNeeded, - "Restart required -- current session uses %d lights.", s_installedShadowLightCount); + T(TKEY("restart_session_lights"), "Restart required -- current session uses %d lights."), s_installedShadowLightCount); } // ---- Shadow Map Resolution (requires restart) --------------------- @@ -5087,13 +5100,14 @@ namespace ShadowCasterManager if (auto* setting = prefColl->GetSetting("iShadowMapResolution:Display")) { static constexpr struct { + const char* key; const char* label; std::int32_t value; } kResTiers[] = { - { "Low (1024)", 1024 }, - { "Medium (2048)", 2048 }, - { "High (4096)", 4096 }, - { "Ultra (8192)", 8192 }, + { TKEY("res_tier_low"), "Low (1024)", 1024 }, + { TKEY("res_tier_medium"), "Medium (2048)", 2048 }, + { TKEY("res_tier_high"), "High (4096)", 4096 }, + { TKEY("res_tier_ultra"), "Ultra (8192)", 8192 }, }; constexpr int kTierCount = static_cast(sizeof(kResTiers) / sizeof(kResTiers[0])); @@ -5111,16 +5125,16 @@ namespace ShadowCasterManager char previewBuf[32]; const char* preview; if (tierIdx >= 0) { - preview = kResTiers[tierIdx].label; + preview = T(kResTiers[tierIdx].key, kResTiers[tierIdx].label); } else { - snprintf(previewBuf, sizeof(previewBuf), "Custom (%d)", currentRes); + snprintf(previewBuf, sizeof(previewBuf), T(TKEY("res_tier_custom"), "Custom (%d)"), currentRes); preview = previewBuf; } - if (ImGui::BeginCombo("Shadow Map Resolution", preview)) { + if (ImGui::BeginCombo(T(TKEY("shadow_map_resolution"), "Shadow Map Resolution"), preview)) { for (int i = 0; i < kTierCount; ++i) { const bool selected = (i == tierIdx); - if (ImGui::Selectable(kResTiers[i].label, selected) && + if (ImGui::Selectable(T(kResTiers[i].key, kResTiers[i].label), selected) && kResTiers[i].value != currentRes) { setting->SetInteger(kResTiers[i].value); s_shadowResolutionDirty = true; @@ -5131,18 +5145,18 @@ namespace ShadowCasterManager ImGui::EndCombo(); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "Drives iShadowMapResolution:Display in SkyrimPrefs.ini.\n" - "Affects both omni/spot shadow slices and the sun cascade\n" - "texture; per-slice VRAM scales as resolution^2 * 4 bytes\n" - "(4 / 16 / 64 / 256 MB at 1024 / 2048 / 4096 / 8192).\n" - "Requires a game restart to take effect."); + ImGui::SetTooltip("%s", T(TKEY("shadow_map_resolution_tooltip"), + "Drives iShadowMapResolution:Display in SkyrimPrefs.ini.\n" + "Affects both omni/spot shadow slices and the sun cascade\n" + "texture; per-slice VRAM scales as resolution^2 * 4 bytes\n" + "(4 / 16 / 64 / 256 MB at 1024 / 2048 / 4096 / 8192).\n" + "Requires a game restart to take effect.")); } if (s_initialShadowMapResolution > 0 && currentRes != s_initialShadowMapResolution) { const auto& theme = Menu::GetSingleton()->GetTheme(); ImGui::TextColored(theme.StatusPalette.RestartNeeded, - "Restart required -- current session uses %d px shadow maps.", + T(TKEY("restart_session_resolution"), "Restart required -- current session uses %d px shadow maps."), s_initialShadowMapResolution); } } @@ -5161,50 +5175,50 @@ namespace ShadowCasterManager // opaque DRS controller that confused users when the budget moved without // a visible cause. The default Formula expresses the same behaviour // transparently and stays editable. - static const char* budgetModeNames[] = { "Manual", "Formula" }; + const char* budgetModeNames[] = { T(TKEY("budget_mode_manual"), "Manual"), T(TKEY("budget_mode_formula"), "Formula") }; int budgetModeIdx = (settings.BudgetMode == BudgetModeEnum::Manual) ? 0 : 1; - if (ImGui::Combo("Budget Mode", &budgetModeIdx, budgetModeNames, 2)) + if (ImGui::Combo(T(TKEY("budget_mode"), "Budget Mode"), &budgetModeIdx, budgetModeNames, 2)) settings.BudgetMode = (budgetModeIdx == 0) ? BudgetModeEnum::Manual : BudgetModeEnum::Formula; if (ImGui::IsItemHovered()) { if (budgetModeIdx == 0) - ImGui::SetTooltip( - "Manual (default): fixed per-frame GPU time budget for shadow re-renders.\n" - "Predictable; doesn't oscillate. Adjust the slider to trade FPS for shadow quality."); + ImGui::SetTooltip("%s", T(TKEY("budget_mode_manual_tooltip"), + "Manual (default): fixed per-frame GPU time budget for shadow re-renders.\n" + "Predictable; doesn't oscillate. Adjust the slider to trade FPS for shadow quality.")); else - ImGui::SetTooltip( - "Formula: user-editable exprtk expression for per-frame budget.\n" - "Default expression matches Intellightent's original behaviour\n" - "(1 ms outdoors, 2 ms indoors). Edit the expression in the\n" - "Advanced section below.\n" - "\n" - "Caveat: adaptive expressions referencing `frametime` tend to\n" - "ping-pong because rendering shadows raises frametime, removing\n" - "the headroom that allowed the budget. Stick to static or\n" - "slowly-varying inputs (`isinterior`, `frametarget`)."); + ImGui::SetTooltip("%s", T(TKEY("budget_mode_formula_tooltip"), + "Formula: user-editable exprtk expression for per-frame budget.\n" + "Default expression matches Intellightent's original behaviour\n" + "(1 ms outdoors, 2 ms indoors). Edit the expression in the\n" + "Advanced section below.\n" + "\n" + "Caveat: adaptive expressions referencing `frametime` tend to\n" + "ping-pong because rendering shadows raises frametime, removing\n" + "the headroom that allowed the budget. Stick to static or\n" + "slowly-varying inputs (`isinterior`, `frametarget`).")); } // Per-mode controls. if (budgetModeIdx == 0) { - ImGui::SliderFloat("Redraw Budget (ms)", &settings.RedrawBudgetMs, 0.1f, 32.0f, "%.2f ms"); + ImGui::SliderFloat(T(TKEY("redraw_budget_ms"), "Redraw Budget (ms)"), &settings.RedrawBudgetMs, 0.1f, 32.0f, "%.2f ms"); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Per-frame GPU time budget for shadow re-renders (milliseconds).\n" - "Lights whose estimated render cost exceeds the remaining budget are deferred.\n" - "The first eligible light always renders regardless of budget (starvation prevention).\n" - "\n" - "Reference points:\n" - " 1-2 ms: Intellightent's original (1 outdoors, 2 indoors)\n" - " 5 ms : default — comfortable for typical scenes (~5-8 lights at ~1 ms each)\n" - " 16 ms: full 60 fps frame; shadows can saturate the frame here\n" - " 32 ms: extreme — only useful for very high light counts on fast GPUs\n" - "\n" - "Higher = more shadow lights redraw per frame, fewer stale shadow maps,\n" - "at the cost of frametime. The Budget verdict in the Active Casters\n" - "section shows whether the current setting has headroom to spare."); + ImGui::SetTooltip("%s", T(TKEY("redraw_budget_ms_tooltip"), + "Per-frame GPU time budget for shadow re-renders (milliseconds).\n" + "Lights whose estimated render cost exceeds the remaining budget are deferred.\n" + "The first eligible light always renders regardless of budget (starvation prevention).\n" + "\n" + "Reference points:\n" + " 1-2 ms: Intellightent's original (1 outdoors, 2 indoors)\n" + " 5 ms : default — comfortable for typical scenes (~5-8 lights at ~1 ms each)\n" + " 16 ms: full 60 fps frame; shadows can saturate the frame here\n" + " 32 ms: extreme — only useful for very high light counts on fast GPUs\n" + "\n" + "Higher = more shadow lights redraw per frame, fewer stale shadow maps,\n" + "at the cost of frametime. The Budget verdict in the Active Casters\n" + "section shows whether the current setting has headroom to spare.")); } else { - ImGui::Text("Budget from formula: %.2f ms", s_autoBudgetMs); + ImGui::Text(T(TKEY("budget_from_formula"), "Budget from formula: %.2f ms"), s_autoBudgetMs); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Edit the Redraw Budget formula in the Advanced section below."); + ImGui::SetTooltip("%s", T(TKEY("budget_from_formula_tooltip"), "Edit the Redraw Budget formula in the Advanced section below.")); } // Budget consumption visualisation lives in the Active Casters block @@ -5229,24 +5243,25 @@ namespace ShadowCasterManager const float rawHeadroom = targetMs - s_ftEMA; const float headroomMs = rawHeadroom - kFrameHeadroomSafetyMs; - const char* state = "steady"; + const char* state = T(TKEY("frame_state_steady"), "steady"); if (rawHeadroom > kFrameHeadroomSafetyMs + kFrameHeadroomDeadZoneMs) - state = "growing"; + state = T(TKEY("frame_state_growing"), "growing"); else if (rawHeadroom < -kFrameHeadroomDeadZoneMs) - state = "throttling"; + state = T(TKEY("frame_state_throttling"), "throttling"); - ImGui::Text("Frame: %.1f FPS (%.1f ms) | frametarget: %.0f FPS (%.1f ms) | headroom: %+.1f ms | %s", + ImGui::Text(T(TKEY("frame_diagnostic"), "Frame: %.1f FPS (%.1f ms) | frametarget: %.0f FPS (%.1f ms) | headroom: %+.1f ms | %s"), currentFPS, currentFrameMs, targetFPS, targetMs, headroomMs, state); if (ImGui::IsItemHovered()) ImGui::SetTooltip( - "Live values of the exprtk variables exposed to the Redraw\n" - "Budget formula. `frametarget` is the rolling 90th-percentile\n" - "frame time, used as a self-measured ceiling -- not a vsync\n" - "target. State indicator:\n" - " steady -- within +/-%.1f ms of target\n" - " growing -- frametime well below target; headroom available\n" - " throttling -- frametime over target; expressions returning\n" - " nonzero values here will keep frametime high", + T(TKEY("frame_diagnostic_tooltip"), + "Live values of the exprtk variables exposed to the Redraw\n" + "Budget formula. `frametarget` is the rolling 90th-percentile\n" + "frame time, used as a self-measured ceiling -- not a vsync\n" + "target. State indicator:\n" + " steady -- within +/-%.1f ms of target\n" + " growing -- frametime well below target; headroom available\n" + " throttling -- frametime over target; expressions returning\n" + " nonzero values here will keep frametime high"), kFrameHeadroomDeadZoneMs); } { @@ -5261,46 +5276,47 @@ namespace ShadowCasterManager // redraw cap should be allowed to follow. int maxRedraws = s_totalShadowLightsThisFrame > 0 ? s_totalShadowLightsThisFrame : settings.ShadowLightCount; maxRedraws = std::max(maxRedraws, Settings::kMinMaxRedrawPerFrame); - ImGui::SliderInt("Max Redraws Per Frame", &settings.MaxRedrawPerFrame, + ImGui::SliderInt(T(TKEY("max_redraws_per_frame"), "Max Redraws Per Frame"), &settings.MaxRedrawPerFrame, Settings::kMinMaxRedrawPerFrame, maxRedraws); if (ImGui::IsItemHovered()) ImGui::SetTooltip( - "Hard cap on how many shadow lights may re-render their shadow maps in one frame.\n" - "Acts as a safety valve regardless of budget -- the budget controls time spent,\n" - "this controls count. The sun directional light always counts as one redraw.\n" - "Minimum is %d (lower values cause shadow flicker as redraw rotation outpaces TAA).\n" - "Upper bound tracks the number of active shadow lights this frame (%d).", + T(TKEY("max_redraws_per_frame_tooltip"), + "Hard cap on how many shadow lights may re-render their shadow maps in one frame.\n" + "Acts as a safety valve regardless of budget -- the budget controls time spent,\n" + "this controls count. The sun directional light always counts as one redraw.\n" + "Minimum is %d (lower values cause shadow flicker as redraw rotation outpaces TAA).\n" + "Upper bound tracks the number of active shadow lights this frame (%d)."), Settings::kMinMaxRedrawPerFrame, maxRedraws); } // ---- Light conversion (requires restart for hooks) ----------------- - if (ImGui::TreeNode("Light Conversion##LightConv")) { - ImGui::Checkbox("Convert Excess Lights to Normal", &settings.ConvertExcessToNormal); + if (ImGui::TreeNode(T(TKEY("light_conversion"), "Light Conversion##LightConv"))) { + ImGui::Checkbox(T(TKEY("convert_excess_to_normal"), "Convert Excess Lights to Normal"), &settings.ConvertExcessToNormal); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Shadow lights that exceed the active shadow caster limit are demoted to\n" - "normal (unshadowed) lights so they still contribute diffuse and specular\n" - "lighting at no shadow-map cost. Lights that fail culling are dropped entirely.\n" - "Requires a game restart to change."); + ImGui::SetTooltip("%s", T(TKEY("convert_excess_to_normal_tooltip"), + "Shadow lights that exceed the active shadow caster limit are demoted to\n" + "normal (unshadowed) lights so they still contribute diffuse and specular\n" + "lighting at no shadow-map cost. Lights that fail culling are dropped entirely.\n" + "Requires a game restart to change.")); // No texture-array cost -- converted lights flow through the cluster // pipeline as ordinary non-shadow lights. Match the ShadowLightCount // max so users can pair a large shadow pool with a matching converted // pool without the slider lying about the upper bound. - ImGui::SliderInt("Converted Shadow Slots", &settings.ConvertedShadowSlots, 0, 127); + ImGui::SliderInt(T(TKEY("converted_shadow_slots"), "Converted Shadow Slots"), &settings.ConvertedShadowSlots, 0, 127); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Extra pool slots for lights converted to normal (unshadowed) mode.\n" - "Increase if Convert Excess Lights drops lights you expect to see."); + ImGui::SetTooltip("%s", T(TKEY("converted_shadow_slots_tooltip"), + "Extra pool slots for lights converted to normal (unshadowed) mode.\n" + "Increase if Convert Excess Lights drops lights you expect to see.")); - ImGui::Checkbox("Promote Normal Lights to Shadow Casters", &settings.PromoteNormalToShadow); + ImGui::Checkbox(T(TKEY("promote_normal_to_shadow"), "Promote Normal Lights to Shadow Casters"), &settings.PromoteNormalToShadow); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Experimental: elevate high-scoring unshadowed lights to shadow casters\n" - "when shadow slots are available.\n" - "Requires a game restart to change."); + ImGui::SetTooltip("%s", T(TKEY("promote_normal_to_shadow_tooltip"), + "Experimental: elevate high-scoring unshadowed lights to shadow casters\n" + "when shadow slots are available.\n" + "Requires a game restart to change.")); - ImGui::SeparatorText("Portal-Strict Enforcement"); + ImGui::SeparatorText(T(TKEY("portal_strict_enforcement"), "Portal-Strict Enforcement")); // Three-way toggle plus master row. SCM forces the engine's // portal-strict flag on shadow casters at creation time, gated // per shadow type (FOV-derived). Defaults enforce on omni and @@ -5323,7 +5339,7 @@ namespace ShadowCasterManager // control without misrepresenting state. ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.6f); } - if (ImGui::Checkbox("Force Enable Portal Strict (All)", &master)) { + if (ImGui::Checkbox(T(TKEY("force_portal_strict_all"), "Force Enable Portal Strict (All)"), &master)) { settings.ForceEnablePortalStrictOmni = master; settings.ForceEnablePortalStrictHemi = master; settings.ForceEnablePortalStrictSpot = master; @@ -5331,89 +5347,89 @@ namespace ShadowCasterManager if (indeterminate) ImGui::PopStyleVar(); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Master toggle for the three per-type rows below.\n" - "Checked when all three are enforced, unchecked when none are,\n" - "and rendered translucent when mixed.\n" - "Requires a game restart to change."); + ImGui::SetTooltip("%s", T(TKEY("force_portal_strict_all_tooltip"), + "Master toggle for the three per-type rows below.\n" + "Checked when all three are enforced, unchecked when none are,\n" + "and rendered translucent when mixed.\n" + "Requires a game restart to change.")); } ImGui::Indent(); - ImGui::Checkbox("Force Portal Strict on Omni Lights", &settings.ForceEnablePortalStrictOmni); + ImGui::Checkbox(T(TKEY("force_portal_strict_omni"), "Force Portal Strict on Omni Lights"), &settings.ForceEnablePortalStrictOmni); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Force-enable portal-strict on dual-paraboloid (omnidirectional)\n" - "shadow casters. Recommended on -- tightens portal-graph visibility\n" - "culling for full-sphere shadow lights without side effects.\n" - "Requires a game restart to change."); - ImGui::Checkbox("Force Portal Strict on Hemisphere Lights", &settings.ForceEnablePortalStrictHemi); + ImGui::SetTooltip("%s", T(TKEY("force_portal_strict_omni_tooltip"), + "Force-enable portal-strict on dual-paraboloid (omnidirectional)\n" + "shadow casters. Recommended on -- tightens portal-graph visibility\n" + "culling for full-sphere shadow lights without side effects.\n" + "Requires a game restart to change.")); + ImGui::Checkbox(T(TKEY("force_portal_strict_hemi"), "Force Portal Strict on Hemisphere Lights"), &settings.ForceEnablePortalStrictHemi); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Force-enable portal-strict on single-paraboloid (hemisphere)\n" - "shadow casters. Recommended on -- behaves like the omni case\n" - "under portal culling.\n" - "Requires a game restart to change."); - ImGui::Checkbox("Force Portal Strict on Spot Lights", &settings.ForceEnablePortalStrictSpot); + ImGui::SetTooltip("%s", T(TKEY("force_portal_strict_hemi_tooltip"), + "Force-enable portal-strict on single-paraboloid (hemisphere)\n" + "shadow casters. Recommended on -- behaves like the omni case\n" + "under portal culling.\n" + "Requires a game restart to change.")); + ImGui::Checkbox(T(TKEY("force_portal_strict_spot"), "Force Portal Strict on Spot Lights"), &settings.ForceEnablePortalStrictSpot); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Force-enable portal-strict on perspective (frustum/spot) shadow\n" - "casters. Off by default: the cone test rejects spots whose\n" - "origin sits behind a portal even when their beam sweeps into a\n" - "visible room, which drops culled-but-visible spots entirely.\n" - "Enable only for debugging.\n" - "Requires a game restart to change."); + ImGui::SetTooltip("%s", T(TKEY("force_portal_strict_spot_tooltip"), + "Force-enable portal-strict on perspective (frustum/spot) shadow\n" + "casters. Off by default: the cone test rejects spots whose\n" + "origin sits behind a portal even when their beam sweeps into a\n" + "visible room, which drops culled-but-visible spots entirely.\n" + "Enable only for debugging.\n" + "Requires a game restart to change.")); ImGui::Unindent(); ImGui::TreePop(); } // ---- Advanced (dynamic) ------------------------------------------- - if (ImGui::TreeNode("Advanced##ShadowScheduling")) { - ImGui::Checkbox("Allow Immediate Draw for New Lights", &settings.AllowDrawNewLight); + if (ImGui::TreeNode(T(TKEY("advanced"), "Advanced##ShadowScheduling"))) { + ImGui::Checkbox(T(TKEY("allow_immediate_draw_new_lights"), "Allow Immediate Draw for New Lights"), &settings.AllowDrawNewLight); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Allow a light just added to the active pool to render its shadow map this frame.\n" - "Prevents a one-frame shadow-map gap when new lights enter view."); + ImGui::SetTooltip("%s", T(TKEY("allow_immediate_draw_new_lights_tooltip"), + "Allow a light just added to the active pool to render its shadow map this frame.\n" + "Prevents a one-frame shadow-map gap when new lights enter view.")); // ---- Importance scheduling curve ------------------------------ - ImGui::SeparatorText("Importance Scheduling"); - ImGui::SliderFloat("Max Interval Scale", &settings.ImportanceMaxScale, 0.5f, 5.0f, "%.2f"); + ImGui::SeparatorText(T(TKEY("importance_scheduling"), "Importance Scheduling")); + ImGui::SliderFloat(T(TKEY("max_interval_scale"), "Max Interval Scale"), &settings.ImportanceMaxScale, 0.5f, 5.0f, "%.2f"); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Interval multiplier applied to unimportant lights (importance = 0).\n" - "Higher values defer dim or distant lights more aggressively.\n" - "Default: 2.0"); + ImGui::SetTooltip("%s", T(TKEY("max_interval_scale_tooltip"), + "Interval multiplier applied to unimportant lights (importance = 0).\n" + "Higher values defer dim or distant lights more aggressively.\n" + "Default: 2.0")); settings.ImportanceMaxScale = std::max(settings.ImportanceMaxScale, settings.ImportanceMinScale); - ImGui::SliderFloat("Min Interval Scale", &settings.ImportanceMinScale, 0.01f, 1.0f, "%.3f"); + ImGui::SliderFloat(T(TKEY("min_interval_scale"), "Min Interval Scale"), &settings.ImportanceMinScale, 0.01f, 1.0f, "%.3f"); if (ImGui::IsItemHovered()) - ImGui::SetTooltip( - "Interval multiplier applied to high-importance lights (importance >= 1).\n" - "Lower values make bright/close lights update shadows more frequently.\n" - "The ratio Max/Min defines the scheduling dynamic range.\n" - "Default: 0.05 (40x range at default Max=2.0)"); + ImGui::SetTooltip("%s", T(TKEY("min_interval_scale_tooltip"), + "Interval multiplier applied to high-importance lights (importance >= 1).\n" + "Lower values make bright/close lights update shadows more frequently.\n" + "The ratio Max/Min defines the scheduling dynamic range.\n" + "Default: 0.05 (40x range at default Max=2.0)")); settings.ImportanceMinScale = std::min(settings.ImportanceMinScale, settings.ImportanceMaxScale); { float ratio = settings.ImportanceMaxScale / std::max(settings.ImportanceMinScale, 0.001f); - ImGui::Text("Dynamic range: %.0fx (unimportant lights wait %.0fx longer)", ratio, ratio); + ImGui::Text(T(TKEY("dynamic_range"), "Dynamic range: %.0fx (unimportant lights wait %.0fx longer)"), ratio, ratio); } - if (ImGui::Button("Reset Importance Defaults")) { + if (ImGui::Button(T(TKEY("reset_importance_defaults"), "Reset Importance Defaults"))) { settings.ImportanceMinScale = 0.05f; settings.ImportanceMaxScale = 2.0f; } // ---- Formula editor ------------------------------------------ - if (ImGui::TreeNode("Formula Editor##Formulas")) { + if (ImGui::TreeNode(T(TKEY("formula_editor"), "Formula Editor##Formulas"))) { // Build variable reference from the DRY table. - if (ImGui::TreeNode("Available Variables##FormulaVars")) { + if (ImGui::TreeNode(T(TKEY("available_variables"), "Available Variables##FormulaVars"))) { if (ImGui::BeginTable("##FormulaVarTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, ImVec2(0, std::min(static_cast(IM_ARRAYSIZE(kFormulaVars)) * 20.0f + 28.0f, 320.0f)))) { - ImGui::TableSetupColumn("Variable"); - ImGui::TableSetupColumn("Description"); + ImGui::TableSetupColumn(T(TKEY("col_variable"), "Variable")); + ImGui::TableSetupColumn(T(TKEY("col_description"), "Description")); ImGui::TableHeadersRow(); for (const auto& v : kFormulaVars) { ImGui::TableNextRow(); @@ -5458,7 +5474,7 @@ namespace ShadowCasterManager helper->Parse(settingStr); } } else { - snprintf(errBuf, errBufSize, "Parse error: %s", err.c_str()); + snprintf(errBuf, errBufSize, T(TKEY("parse_error"), "Parse error: %s"), err.c_str()); snprintf(buf, bufSize, "%s", settingStr.c_str()); } } @@ -5466,19 +5482,19 @@ namespace ShadowCasterManager ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", errBuf); }; - applyFormula("Score", scoreBuf, sizeof(scoreBuf), + applyFormula(T(TKEY("formula_score"), "Score"), scoreBuf, sizeof(scoreBuf), settings.ScoreFormula, scoreErr, sizeof(scoreErr), s_formulaScore); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Light priority scoring formula. Higher score = more likely to get a shadow slot."); + ImGui::SetTooltip("%s", T(TKEY("formula_score_tooltip"), "Light priority scoring formula. Higher score = more likely to get a shadow slot.")); - applyFormula("Redraw Interval", redrawIntervalBuf, sizeof(redrawIntervalBuf), + applyFormula(T(TKEY("formula_redraw_interval"), "Redraw Interval"), redrawIntervalBuf, sizeof(redrawIntervalBuf), settings.RedrawIntervalFormula, redrawIntervalErr, sizeof(redrawIntervalErr), s_formulaRedrawInterval); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Per-light redraw interval formula. Higher = less frequent shadow map updates."); - applyFormula("Redraw Budget", redrawBudgetBuf, sizeof(redrawBudgetBuf), + ImGui::SetTooltip("%s", T(TKEY("formula_redraw_interval_tooltip"), "Per-light redraw interval formula. Higher = less frequent shadow map updates.")); + applyFormula(T(TKEY("formula_redraw_budget"), "Redraw Budget"), redrawBudgetBuf, sizeof(redrawBudgetBuf), settings.RedrawBudgetFormula, redrawBudgetErr, sizeof(redrawBudgetErr), s_formulaRedrawBudget); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Per-frame redraw budget formula (ms). Empty = use the Redraw Budget (ms) slider value."); + ImGui::SetTooltip("%s", T(TKEY("formula_redraw_budget_tooltip"), "Per-frame redraw budget formula (ms). Empty = use the Redraw Budget (ms) slider value.")); ImGui::TreePop(); } @@ -5498,3 +5514,5 @@ namespace ShadowCasterManager ImGui::EndDisabled(); } } + +#undef I18N_KEY_PREFIX diff --git a/src/Features/LightLimitFix/ShadowRenderer.cpp b/src/Features/LightLimitFix/ShadowRenderer.cpp index 9b44116d6b..15db976bf5 100644 --- a/src/Features/LightLimitFix/ShadowRenderer.cpp +++ b/src/Features/LightLimitFix/ShadowRenderer.cpp @@ -3,6 +3,7 @@ #include "../LightLimitFix.h" #include "Deferred.h" +#include "I18n/I18n.h" #include "Menu/ThemeManager.h" #include "State.h" #include "Util.h" @@ -300,9 +301,9 @@ void LightLimitFix::DrawOverlay() }; uint32_t m = LightsVisualisationMode; const char* vizName = (m < IM_ARRAYSIZE(kVizNames)) ? kVizNames[m] : "Unknown"; - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "LLF DEBUG - %s", vizName); + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), T("feature.light_limit_fix.overlay_debug_label", "LLF DEBUG - %s"), vizName); } else - ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "LLF - Shadow Suppression"); + ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "%s", T("feature.light_limit_fix.overlay_shadow_suppression", "LLF - Shadow Suppression")); ImGui::Separator(); uint32_t mode = vizOn ? LightsVisualisationMode : UINT32_MAX; diff --git a/src/Features/PerformanceOverlay.cpp b/src/Features/PerformanceOverlay.cpp index 8cabbbc397..3e14db07b6 100644 --- a/src/Features/PerformanceOverlay.cpp +++ b/src/Features/PerformanceOverlay.cpp @@ -121,16 +121,16 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( PositionSet) static const std::unordered_map kShaderTypeTooltips = { - { RE::BSShader::Type::Grass, "Draw calls using the Grass shader. Typically many, but each is usually cheap." }, - { RE::BSShader::Type::Sky, "Draw calls for the sky dome, clouds, and related effects." }, - { RE::BSShader::Type::Water, "Draw calls for water surfaces and effects." }, - { RE::BSShader::Type::Lighting, "Draw calls for dynamic and static lighting passes." }, - { RE::BSShader::Type::Effect, "Draw calls for special effects, particles, and post-processing." }, - { RE::BSShader::Type::Utility, "Draw calls for utility passes, such as shadow masks or G-buffer fills." }, - { RE::BSShader::Type::DistantTree, "Draw calls for distant tree rendering (LOD vegetation)." }, - { RE::BSShader::Type::Particle, "Draw calls for particle systems (smoke, sparks, etc.)." }, - { RE::BSShader::Type::BloodSplatter, "Draw calls for blood splatter effects." }, - { RE::BSShader::Type::ImageSpace, "Draw calls for image space post-processing effects." } + { RE::BSShader::Type::Grass, T(TKEY("tip_grass"), "Draw calls using the Grass shader. Typically many, but each is usually cheap.") }, + { RE::BSShader::Type::Sky, T(TKEY("tip_sky"), "Draw calls for the sky dome, clouds, and related effects.") }, + { RE::BSShader::Type::Water, T(TKEY("tip_water"), "Draw calls for water surfaces and effects.") }, + { RE::BSShader::Type::Lighting, T(TKEY("tip_lighting"), "Draw calls for dynamic and static lighting passes.") }, + { RE::BSShader::Type::Effect, T(TKEY("tip_effect"), "Draw calls for special effects, particles, and post-processing.") }, + { RE::BSShader::Type::Utility, T(TKEY("tip_utility"), "Draw calls for utility passes, such as shadow masks or G-buffer fills.") }, + { RE::BSShader::Type::DistantTree, T(TKEY("tip_distant_tree"), "Draw calls for distant tree rendering (LOD vegetation).") }, + { RE::BSShader::Type::Particle, T(TKEY("tip_particle"), "Draw calls for particle systems (smoke, sparks, etc.).") }, + { RE::BSShader::Type::BloodSplatter, T(TKEY("tip_blood_splatter"), "Draw calls for blood splatter effects.") }, + { RE::BSShader::Type::ImageSpace, T(TKEY("tip_image_space"), "Draw calls for image space post-processing effects.") } }; // ============================================================================ // VIRTUAL OVERRIDES (Feature.h interface) @@ -443,8 +443,8 @@ void PerformanceOverlay::DrawFPS() // Prepare overlay text char overlay_text[128]; snprintf(overlay_text, IM_ARRAYSIZE(overlay_text), - "%s%.2f ms (%.1f FPS)", - this->state.isFrameGenerationActive ? "Pre-FG: " : "", + T(TKEY("graph_overlay_fmt"), "%s%.2f ms (%.1f FPS)"), + this->state.isFrameGenerationActive ? T(TKEY("pre_fg_prefix"), "Pre-FG: ") : "", this->state.smoothFrameTimeMs, this->state.smoothFps); // Set graph colors @@ -465,13 +465,13 @@ void PerformanceOverlay::DrawFPS() // Draw frametime target reference lines if (ImGui::BeginTable("FrametimeTargets", 3, ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextColumn(); - ImGui::Text("30 FPS: 33.3 ms"); + ImGui::Text("%s", T(TKEY("ref_30fps"), "30 FPS: 33.3 ms")); ImGui::TableNextColumn(); - ImGui::Text("60 FPS: 16.7 ms"); + ImGui::Text("%s", T(TKEY("ref_60fps"), "60 FPS: 16.7 ms")); ImGui::TableNextColumn(); - ImGui::Text("120 FPS: 8.3 ms"); + ImGui::Text("%s", T(TKEY("ref_120fps"), "120 FPS: 8.3 ms")); ImGui::EndTable(); } @@ -486,7 +486,7 @@ void PerformanceOverlay::DrawFPS() // Show note that FSR uses calculated data ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", T(TKEY("post_fg_calculated"), "Post-FG: Calculated timing (2x Pre-FG)")); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("AMD FSR Frame Generation uses calculated timing data (2x Pre-FG).\nNVIDIA DLSS Frame Generation provides measured timing data."); + ImGui::Text("%s", T(TKEY("fsr_dlss_timing_tooltip"), "AMD FSR Frame Generation uses calculated timing data (2x Pre-FG).\nNVIDIA DLSS Frame Generation provides measured timing data.")); } } @@ -541,7 +541,7 @@ void PerformanceOverlay::DrawPostFGFrameTimeGraph() // Prepare overlay text char overlay_text[128]; snprintf(overlay_text, IM_ARRAYSIZE(overlay_text), - "Post-FG: %.2f ms (%.1f FPS)", + T(TKEY("post_fg_graph_overlay_fmt"), "Post-FG: %.2f ms (%.1f FPS)"), state.postFGSmoothFrameTimeMs, state.postFGSmoothFps); // Set graph colors - blue for post-FG @@ -562,13 +562,13 @@ void PerformanceOverlay::DrawPostFGFrameTimeGraph() // Draw frametime target reference lines if (ImGui::BeginTable("PostFGFrametimeTargets", 3, ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextColumn(); - ImGui::Text("30 FPS: 33.3 ms"); + ImGui::Text("%s", T(TKEY("ref_30fps"), "30 FPS: 33.3 ms")); ImGui::TableNextColumn(); - ImGui::Text("60 FPS: 16.7 ms"); + ImGui::Text("%s", T(TKEY("ref_60fps"), "60 FPS: 16.7 ms")); ImGui::TableNextColumn(); - ImGui::Text("120 FPS: 8.3 ms"); + ImGui::Text("%s", T(TKEY("ref_120fps"), "120 FPS: 8.3 ms")); ImGui::EndTable(); } @@ -676,20 +676,20 @@ void PerformanceOverlay::DrawABTestStatisticalValidity(const Menu::ThemeSettings } ImGui::PushStyleColor(ImGuiCol_Text, validityColor); - ImGui::Text("Test Duration: %.1f seconds | Valid Frames: %d/%d (%.1f%%) | Excluded: %d", + ImGui::Text(T(TKEY("test_duration_line"), "Test Duration: %.1f seconds | Valid Frames: %d/%d (%.1f%%) | Excluded: %d"), totalDuration, validFrames, totalWithExcluded, validPercent, excludedFrames); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { char validStr[128], marginalStr[128]; - snprintf(validStr, sizeof(validStr), "Statistically valid (>%d samples, >%.0fs duration, >%.0f%% valid)", kMinimumSamplesForValidity, static_cast(kMinimumTestDuration), kMinimumValidFramesPercent); - snprintf(marginalStr, sizeof(marginalStr), "Marginal validity (>%d samples, >%.0fs duration)", kMinimumSamplesForMarginal, static_cast(kMinimumDurationForMarginal)); + snprintf(validStr, sizeof(validStr), T(TKEY("validity_valid_fmt"), "Statistically valid (>%d samples, >%.0fs duration, >%.0f%% valid)"), kMinimumSamplesForValidity, static_cast(kMinimumTestDuration), kMinimumValidFramesPercent); + snprintf(marginalStr, sizeof(marginalStr), T(TKEY("validity_marginal_fmt"), "Marginal validity (>%d samples, >%.0fs duration)"), kMinimumSamplesForMarginal, static_cast(kMinimumDurationForMarginal)); Util::ColoredTextLines validityLegend = { - { "Valid frames are those not excluded as outliers.\nA low percentage may indicate instability or test interruptions.\nExcluded frames are those with frame times > 3x median or > 100ms.\nThis removes shader compilation spikes, JSON loading overhead, and other anomalies\nthat would skew the performance comparison.", theme.Palette.Text }, + { T(TKEY("validity_legend_body"), "Valid frames are those not excluded as outliers.\nA low percentage may indicate instability or test interruptions.\nExcluded frames are those with frame times > 3x median or > 100ms.\nThis removes shader compilation spikes, JSON loading overhead, and other anomalies\nthat would skew the performance comparison."), theme.Palette.Text }, { "", theme.Palette.Text }, { validStr, theme.StatusPalette.SuccessColor }, { marginalStr, theme.StatusPalette.Warning }, - { "Insufficient data for reliable results", theme.StatusPalette.Error } + { T(TKEY("validity_insufficient"), "Insufficient data for reliable results"), theme.StatusPalette.Error } }; Util::DrawColoredMultiLineTooltip(validityLegend); } @@ -727,17 +727,17 @@ void PerformanceOverlay::ConvertABTestResultsToRows(const std::vectorsecond; } else { - row.tooltip = "Draw calls for this shader type."; + row.tooltip = T(TKEY("tip_generic_shader"), "Draw calls for this shader type."); } } else { auto maybeSpecialType = magic_enum::enum_cast(row.shaderType); if (maybeSpecialType.has_value()) { switch (*maybeSpecialType) { case SpecialShaderType::Total: - row.tooltip = "Total frame time."; + row.tooltip = T(TKEY("tip_total"), "Total frame time."); break; case SpecialShaderType::Other: - row.tooltip = "Frame time not attributed to any measured shader type. This includes UI, post-processing, engine work, and any GPU activity not directly measured by the overlay."; + row.tooltip = T(TKEY("tip_other_abtest"), "Frame time not attributed to any measured shader type. This includes UI, post-processing, engine work, and any GPU activity not directly measured by the overlay."); break; } } @@ -765,76 +765,76 @@ ABTestLegends PerformanceOverlay::BuildABTestLegends(const Menu::ThemeSettings& ABTestLegends legends; legends.shaderType = { - "Shader Type", - { { "Shader Type: The type of shader being measured.", theme.Palette.Text }, - { "Click to toggle shader on/off for performance testing.", theme.Palette.Text } } + T(TKEY("col_shader_type"), "Shader Type"), + { { T(TKEY("ableg_shader_type_desc"), "Shader Type: The type of shader being measured."), theme.Palette.Text }, + { T(TKEY("ableg_shader_type_toggle"), "Click to toggle shader on/off for performance testing."), theme.Palette.Text } } }; legends.aAvg = { - "A Avg (ms)", - { { "A Avg (ms): Average frame time for Variant A (USER config).", theme.Palette.Text }, + T(TKEY("col_a_avg"), "A Avg (ms)"), + { { T(TKEY("ableg_a_avg_desc"), "A Avg (ms): Average frame time for Variant A (USER config)."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to Variant B):", theme.Palette.Text }, - { " Better (lower than B)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than B)", theme.StatusPalette.Error }, - { " Same as B", theme.Palette.Text } } + { T(TKEY("ableg_color_vs_b"), "Color Legend (compared to Variant B):"), theme.Palette.Text }, + { T(TKEY("ableg_better_than_b"), " Better (lower than B)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_worse_than_b"), " Worse (higher than B)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_same_as_b"), " Same as B"), theme.Palette.Text } } }; legends.bAvg = { - "B Avg (ms)", - { { "B Avg (ms): Average frame time for Variant B (TEST config).", theme.Palette.Text }, + T(TKEY("col_b_avg"), "B Avg (ms)"), + { { T(TKEY("ableg_b_avg_desc"), "B Avg (ms): Average frame time for Variant B (TEST config)."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to Variant A):", theme.Palette.Text }, - { " Better (lower than A)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than A)", theme.StatusPalette.Error }, - { " Same as A", theme.Palette.Text } } + { T(TKEY("ableg_color_vs_a"), "Color Legend (compared to Variant A):"), theme.Palette.Text }, + { T(TKEY("ableg_better_than_a"), " Better (lower than A)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_worse_than_a"), " Worse (higher than A)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_same_as_a"), " Same as A"), theme.Palette.Text } } }; legends.delta = { - "Delta (ms)", - { { "Delta (ms): Difference between Variant B and Variant A (B - A).", theme.Palette.Text }, - { "Negative values indicate Variant B is better (lower frame time).", theme.Palette.Text }, - { "Positive values indicate Variant A is better (lower frame time).", theme.Palette.Text }, - { "Percentage shows relative performance difference.", theme.Palette.Text }, + T(TKEY("col_delta"), "Delta (ms)"), + { { T(TKEY("ableg_delta_desc"), "Delta (ms): Difference between Variant B and Variant A (B - A)."), theme.Palette.Text }, + { T(TKEY("ableg_delta_neg"), "Negative values indicate Variant B is better (lower frame time)."), theme.Palette.Text }, + { T(TKEY("ableg_delta_pos"), "Positive values indicate Variant A is better (lower frame time)."), theme.Palette.Text }, + { T(TKEY("ableg_delta_percent"), "Percentage shows relative performance difference."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend:", theme.Palette.Text }, - { " Negative (B better)", theme.StatusPalette.SuccessColor }, - { " Positive (A better)", theme.StatusPalette.Error }, - { " Zero (same)", theme.Palette.Text } } + { T(TKEY("ableg_color_legend"), "Color Legend:"), theme.Palette.Text }, + { T(TKEY("ableg_negative_b_better"), " Negative (B better)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_positive_a_better"), " Positive (A better)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_zero_same"), " Zero (same)"), theme.Palette.Text } } }; legends.aMedian = { - "A Median (ms)", - { { "A Median: Median frame time for Variant A (USER config).", theme.Palette.Text }, - { "Median is less sensitive to outliers than average.", theme.Palette.Text }, + T(TKEY("col_a_median"), "A Median (ms)"), + { { T(TKEY("ableg_a_median_desc"), "A Median: Median frame time for Variant A (USER config)."), theme.Palette.Text }, + { T(TKEY("ableg_median_outliers"), "Median is less sensitive to outliers than average."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to Variant B median):", theme.Palette.Text }, - { " Better (lower than B)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than B)", theme.StatusPalette.Error }, - { " Same as B", theme.Palette.Text } } + { T(TKEY("ableg_color_vs_b_median"), "Color Legend (compared to Variant B median):"), theme.Palette.Text }, + { T(TKEY("ableg_better_than_b"), " Better (lower than B)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_worse_than_b"), " Worse (higher than B)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_same_as_b"), " Same as B"), theme.Palette.Text } } }; legends.bMedian = { - "B Median (ms)", - { { "B Median: Median frame time for Variant B (TEST config).", theme.Palette.Text }, - { "Median is less sensitive to outliers than average.", theme.Palette.Text }, + T(TKEY("col_b_median"), "B Median (ms)"), + { { T(TKEY("ableg_b_median_desc"), "B Median: Median frame time for Variant B (TEST config)."), theme.Palette.Text }, + { T(TKEY("ableg_median_outliers"), "Median is less sensitive to outliers than average."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to Variant A median):", theme.Palette.Text }, - { " Better (lower than A)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than A)", theme.StatusPalette.Error }, - { " Same as A", theme.Palette.Text } } + { T(TKEY("ableg_color_vs_a_median"), "Color Legend (compared to Variant A median):"), theme.Palette.Text }, + { T(TKEY("ableg_better_than_a"), " Better (lower than A)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_worse_than_a"), " Worse (higher than A)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_same_as_a"), " Same as A"), theme.Palette.Text } } }; legends.medianDelta = { - "Median Delta (ms)", - { { "Median Delta: Difference between Variant B and Variant A medians (B - A).", theme.Palette.Text }, - { "Negative values indicate Variant B is better (lower median).", theme.Palette.Text }, - { "Positive values indicate Variant A is better (lower median).", theme.Palette.Text }, + T(TKEY("col_median_delta"), "Median Delta (ms)"), + { { T(TKEY("ableg_median_delta_desc"), "Median Delta: Difference between Variant B and Variant A medians (B - A)."), theme.Palette.Text }, + { T(TKEY("ableg_median_delta_neg"), "Negative values indicate Variant B is better (lower median)."), theme.Palette.Text }, + { T(TKEY("ableg_median_delta_pos"), "Positive values indicate Variant A is better (lower median)."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend:", theme.Palette.Text }, - { " Negative (B better)", theme.StatusPalette.SuccessColor }, - { " Positive (A better)", theme.StatusPalette.Error }, - { " Zero (same)", theme.Palette.Text } } + { T(TKEY("ableg_color_legend"), "Color Legend:"), theme.Palette.Text }, + { T(TKEY("ableg_negative_b_better"), " Negative (B better)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("ableg_positive_a_better"), " Positive (A better)"), theme.StatusPalette.Error }, + { T(TKEY("ableg_zero_same"), " Zero (same)"), theme.Palette.Text } } }; return legends; @@ -863,7 +863,7 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con // Add FPS for Total row if (row.label == "Total:") { float fps = row.frameTime > 0.0f ? 1000.0f / row.frameTime : 0.0f; - ImGui::Text("FPS: %.2f", fps); + ImGui::Text(T(TKEY("fps_value"), "FPS: %.2f"), fps); } } } @@ -894,7 +894,7 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.label == "Total:") { - ImGui::Text("A (USER) FPS: %.2f", Util::CalcFPS(value)); + ImGui::Text(T(TKEY("a_user_fps"), "A (USER) FPS: %.2f"), Util::CalcFPS(value)); } else { Util::DrawColoredMultiLineTooltip(legends.aAvg.tooltip); } @@ -929,7 +929,7 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.label == "Total:") { - ImGui::Text("B (TEST) FPS: %.2f", Util::CalcFPS(value)); + ImGui::Text(T(TKEY("b_test_fps"), "B (TEST) FPS: %.2f"), Util::CalcFPS(value)); } else { Util::DrawColoredMultiLineTooltip(legends.bAvg.tooltip); } @@ -970,15 +970,15 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (row.testFrameTime.has_value()) { // Show detailed values for rows with test data if (row.label == "Total:") { - ImGui::TextUnformatted("Delta (B - A):"); + ImGui::TextUnformatted(T(TKEY("delta_b_minus_a"), "Delta (B - A):")); ImGui::Separator(); - ImGui::Text("A (USER) FPS: %.2f", Util::CalcFPS(row.frameTime)); - ImGui::Text("B (TEST) FPS: %.2f", Util::CalcFPS(*row.testFrameTime)); + ImGui::Text(T(TKEY("a_user_fps"), "A (USER) FPS: %.2f"), Util::CalcFPS(row.frameTime)); + ImGui::Text(T(TKEY("b_test_fps"), "B (TEST) FPS: %.2f"), Util::CalcFPS(*row.testFrameTime)); } else { - ImGui::TextUnformatted("Delta (B - A):"); + ImGui::TextUnformatted(T(TKEY("delta_b_minus_a"), "Delta (B - A):")); ImGui::Separator(); - ImGui::Text("A (USER): %.3f ms", row.frameTime); - ImGui::Text("B (TEST): %.3f ms", *row.testFrameTime); + ImGui::Text(T(TKEY("a_user_ms"), "A (USER): %.3f ms"), row.frameTime); + ImGui::Text(T(TKEY("b_test_ms"), "B (TEST): %.3f ms"), *row.testFrameTime); } ImGui::Separator(); } @@ -1017,8 +1017,9 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.label == "Total:") { + float fpsVal = Util::CalcFPS(value); Util::ColoredTextLines fpsTooltip{ - { std::format("A (USER) Median FPS: {:.2f}", Util::CalcFPS(value)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } + { std::vformat(T(TKEY("a_user_median_fps"), "A (USER) Median FPS: {:.2f}"), std::make_format_args(fpsVal)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } }; Util::DrawColoredMultiLineTooltip(fpsTooltip); } else { @@ -1055,8 +1056,9 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.label == "Total:") { + float fpsVal = Util::CalcFPS(value); Util::ColoredTextLines fpsTooltip{ - { std::format("B (TEST) Median FPS: {:.2f}", Util::CalcFPS(value)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } + { std::vformat(T(TKEY("b_test_median_fps"), "B (TEST) Median FPS: {:.2f}"), std::make_format_args(fpsVal)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } }; Util::DrawColoredMultiLineTooltip(fpsTooltip); } else { @@ -1098,13 +1100,15 @@ std::vector PerformanceOverlay::BuildABTestResultsTableColumns(con if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.label == "Total:" && row.testCostPerCall.has_value()) { + float aMedianFps = Util::CalcFPS(row.costPerCall); + float bMedianFps = Util::CalcFPS(*row.testCostPerCall); Util::ColoredTextLines fpsTooltip{ - { "Median Delta (B - A):", ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { T(TKEY("median_delta_b_minus_a"), "Median Delta (B - A):"), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, { "", ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, - { std::format("A (USER) Median FPS: {:.2f}", Util::CalcFPS(row.costPerCall)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, - { std::format("B (TEST) Median FPS: {:.2f}", Util::CalcFPS(*row.testCostPerCall)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { std::vformat(T(TKEY("a_user_median_fps"), "A (USER) Median FPS: {:.2f}"), std::make_format_args(aMedianFps)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { std::vformat(T(TKEY("b_test_median_fps"), "B (TEST) Median FPS: {:.2f}"), std::make_format_args(bMedianFps)), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, { "", ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, - { "Median is less sensitive to outliers than average.", ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } + { T(TKEY("ableg_median_outliers"), "Median is less sensitive to outliers than average."), ImVec4(1.0f, 1.0f, 1.0f, 1.0f) } }; Util::DrawColoredMultiLineTooltip(fpsTooltip); } else { @@ -1189,11 +1193,11 @@ void PerformanceOverlay::DrawABTestSection(const std::vector& allRo // --- A/B Results Controls --- static bool showSettingsDiff = false; ImGui::BeginGroup(); - if (ImGui::Button(showSettingsDiff ? "Hide Settings Diff" : "Show Settings Diff")) { + if (ImGui::Button(showSettingsDiff ? T(TKEY("hide_settings_diff"), "Hide Settings Diff") : T(TKEY("show_settings_diff"), "Show Settings Diff"))) { showSettingsDiff = !showSettingsDiff; } ImGui::SameLine(); - if (ImGui::Button("Clear A/B Test Results")) { + if (ImGui::Button(T(TKEY("clear_abtest_results"), "Clear A/B Test Results"))) { aggregator.Clear(); this->settingsDiff.clear(); this->settingsDiffLoaded = false; @@ -1221,13 +1225,13 @@ void PerformanceOverlay::DrawABTestSection(const std::vector& allRo } this->settingsDiffLoaded = true; } - ImGui::TextUnformatted("Differences between USER (A) and TEST (B) configs:"); + ImGui::TextUnformatted(T(TKEY("diff_header"), "Differences between USER (A) and TEST (B) configs:")); if (this->settingsDiff.empty()) { - ImGui::TextUnformatted("No setting changes detected between USER (A) and TEST (B) configs."); + ImGui::TextUnformatted(T(TKEY("diff_none"), "No setting changes detected between USER (A) and TEST (B) configs.")); } else if (ImGui::BeginTable("ABSettingsDiffTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Sortable)) { - ImGui::TableSetupColumn("Setting Path", ImGuiTableColumnFlags_DefaultSort); - ImGui::TableSetupColumn("A Value"); - ImGui::TableSetupColumn("B Value"); + ImGui::TableSetupColumn(T(TKEY("col_setting_path"), "Setting Path"), ImGuiTableColumnFlags_DefaultSort); + ImGui::TableSetupColumn(T(TKEY("col_a_value"), "A Value")); + ImGui::TableSetupColumn(T(TKEY("col_b_value"), "B Value")); ImGui::TableHeadersRow(); // Determine which variant performed better based on Total row @@ -1364,54 +1368,54 @@ DrawCallLegends PerformanceOverlay::BuildDrawCallLegends(const Menu::ThemeSettin DrawCallLegends legends; legends.shaderType = { - "Shader Type", - { { "Shader Type: The type of shader being measured.", theme.Palette.Text }, - { "Click to toggle shader on/off for performance testing.", theme.Palette.Text } } + T(TKEY("col_shader_type"), "Shader Type"), + { { T(TKEY("dcleg_shader_type_desc"), "Shader Type: The type of shader being measured."), theme.Palette.Text }, + { T(TKEY("dcleg_shader_type_toggle"), "Click to toggle shader on/off for performance testing."), theme.Palette.Text } } }; legends.drawCalls = { - "Draw Calls", - { { "Draw Calls: Number of draw calls for this shader type in the current frame.", theme.Palette.Text } } + T(TKEY("col_draw_calls"), "Draw Calls"), + { { T(TKEY("dcleg_draw_calls_desc"), "Draw Calls: Number of draw calls for this shader type in the current frame."), theme.Palette.Text } } }; legends.frameTime = { - "Frame Time (%)", + T(TKEY("col_frame_time"), "Frame Time (%)"), { { GetTestDataTooltip(), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Performance Color Legend (ms):", theme.Palette.Text }, - { " <= 2 ms", theme.StatusPalette.SuccessColor }, - { " > 2 ms and <= 5 ms", theme.StatusPalette.Warning }, - { " > 5 ms", theme.StatusPalette.Error } } + { T(TKEY("dcleg_perf_color_ms"), "Performance Color Legend (ms):"), theme.Palette.Text }, + { T(TKEY("dcleg_ft_good"), " <= 2 ms"), theme.StatusPalette.SuccessColor }, + { T(TKEY("dcleg_ft_warn"), " > 2 ms and <= 5 ms"), theme.StatusPalette.Warning }, + { T(TKEY("dcleg_ft_bad"), " > 5 ms"), theme.StatusPalette.Error } } }; legends.costPerCall = { - "Cost/Call", - { { "Cost/Call: Average time per draw call for this shader type.", theme.Palette.Text }, + T(TKEY("col_cost_per_call"), "Cost/Call"), + { { T(TKEY("dcleg_cost_per_call_desc"), "Cost/Call: Average time per draw call for this shader type."), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (ms/call):", theme.Palette.Text }, - { " <= 0.05 ms/call", theme.StatusPalette.SuccessColor }, - { " > 0.05 ms and <= 0.2 ms/call", theme.StatusPalette.Warning }, - { " > 0.2 ms/call", theme.StatusPalette.Error } } + { T(TKEY("dcleg_color_ms_call"), "Color Legend (ms/call):"), theme.Palette.Text }, + { T(TKEY("dcleg_cpc_good"), " <= 0.05 ms/call"), theme.StatusPalette.SuccessColor }, + { T(TKEY("dcleg_cpc_warn"), " > 0.05 ms and <= 0.2 ms/call"), theme.StatusPalette.Warning }, + { T(TKEY("dcleg_cpc_bad"), " > 0.2 ms/call"), theme.StatusPalette.Error } } }; legends.testFrameTime = { - "Test Frame Time (%)", + T(TKEY("col_test_frame_time"), "Test Frame Time (%)"), { { GetTestDataTooltip(), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to live data):", theme.Palette.Text }, - { " Better (lower than live)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than live)", theme.StatusPalette.Error }, - { " Same as live", theme.Palette.Text } } + { T(TKEY("dcleg_color_vs_live"), "Color Legend (compared to live data):"), theme.Palette.Text }, + { T(TKEY("dcleg_better_than_live"), " Better (lower than live)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("dcleg_worse_than_live"), " Worse (higher than live)"), theme.StatusPalette.Error }, + { T(TKEY("dcleg_same_as_live"), " Same as live"), theme.Palette.Text } } }; legends.testCostPerCall = { - "Test Cost/Call", + T(TKEY("col_test_cost_per_call"), "Test Cost/Call"), { { GetTestDataTooltip(), theme.Palette.Text }, { "", theme.Palette.Text }, - { "Color Legend (compared to live data):", theme.Palette.Text }, - { " Better (lower than live)", theme.StatusPalette.SuccessColor }, - { " Worse (higher than live)", theme.StatusPalette.Error }, - { " Same as live", theme.Palette.Text } } + { T(TKEY("dcleg_color_vs_live"), "Color Legend (compared to live data):"), theme.Palette.Text }, + { T(TKEY("dcleg_better_than_live"), " Better (lower than live)"), theme.StatusPalette.SuccessColor }, + { T(TKEY("dcleg_worse_than_live"), " Worse (higher than live)"), theme.StatusPalette.Error }, + { T(TKEY("dcleg_same_as_live"), " Same as live"), theme.Palette.Text } } }; return legends; @@ -1461,9 +1465,9 @@ std::vector PerformanceOverlay::BuildDrawCallTableColumns(const Me if (ImGui::IsItemHovered()) { if (auto _tt = Util::HoverTooltipWrapper()) { if (row.drawCalls == kDrawCallsNotApplicable) { - ImGui::TextUnformatted("Draw Calls: Not applicable for unmeasured GPU time."); + ImGui::TextUnformatted(T(TKEY("draw_calls_na"), "Draw Calls: Not applicable for unmeasured GPU time.")); } else { - ImGui::TextUnformatted("Draw Calls: Number of draw calls for this shader type in the current frame."); + ImGui::TextUnformatted(T(TKEY("dcleg_draw_calls_desc"), "Draw Calls: Number of draw calls for this shader type in the current frame.")); } } } @@ -1559,7 +1563,7 @@ std::pair, std::vector> PerformanceOverlay testCostPerCall = it->second.costPerCall; } std::string label = std::string(magic_enum::enum_name(type)) + ":"; - std::string tooltip = "Draw calls for this shader type."; + std::string tooltip = T(TKEY("tip_generic_shader"), "Draw calls for this shader type."); auto tipIt = kShaderTypeTooltips.find(type); if (tipIt != kShaderTypeTooltips.end()) { tooltip = tipIt->second; @@ -1591,13 +1595,13 @@ std::pair, std::vector> PerformanceOverlay DrawCallRow csPassesRow = { "CS Passes:", magic_enum::enum_integer(SpecialShaderType::CSPasses), kDrawCallsNotApplicable, csPassesTime, csPercent, 0.0f, - std::string("GPU time spent in Community Shaders compute passes (profiled)."), + std::string(T(TKEY("tip_cs_passes"), "GPU time spent in Community Shaders compute passes (profiled).")), true, std::nullopt, std::nullopt }; DrawCallRow otherRow = { "Other:", magic_enum::enum_integer(SpecialShaderType::Other), kDrawCallsNotApplicable, remainingOtherTime, remainingOtherPercent, 0.0f, - std::string("Frame time not attributed to any measured shader type or CS compute pass. This includes UI, post-processing, engine work, and any GPU activity not directly measured."), + std::string(T(TKEY("tip_other"), "Frame time not attributed to any measured shader type or CS compute pass. This includes UI, post-processing, engine work, and any GPU activity not directly measured.")), true, otherTestFrameTime, otherTestCostPerCall }; float totalFrameTime = smoothedFrameTime; @@ -1606,7 +1610,7 @@ std::pair, std::vector> PerformanceOverlay DrawCallRow totalRow = { "Total:", magic_enum::enum_integer(SpecialShaderType::Total), static_cast(globals::state->GetTotalSmoothedDrawCalls()), totalFrameTime, totalPercent, totalCostPerCall, - std::string("Total frame time."), + std::string(T(TKEY("tip_total"), "Total frame time.")), true, totalTestFrameTime, totalTestCostPerCall }; std::vector summaryRows; @@ -1641,7 +1645,7 @@ std::function PerformanceOverlay::CreateTabl if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::TextUnformatted(row.tooltip.c_str()); float _fps = row.frameTime > 0.0f ? 1000.0f / row.frameTime : 0.0f; - ImGui::Text("FPS: %.2f", _fps); + ImGui::Text(T(TKEY("fps_value"), "FPS: %.2f"), _fps); } } } else if (row.label == "Other:") { @@ -1842,11 +1846,11 @@ std::string PerformanceOverlay::GetTestDataTooltip() const { switch (testDataSource) { case TestDataSource::ABTest_VariantB: - return std::string("Test data from Test (Variant B).\nLast updated: ") + Util::TimeAgoStringQPC(testDataLastUpdated, state.overlayTimingFrequency) + " ago."; + return std::string(T(TKEY("testdata_from_variant_b"), "Test data from Test (Variant B).\nLast updated: ")) + Util::TimeAgoStringQPC(testDataLastUpdated, state.overlayTimingFrequency) + T(TKEY("testdata_ago_suffix"), " ago."); case TestDataSource::ManualShaderToggle: - return std::string("Test data from manual shader toggle.\nLast updated: ") + Util::TimeAgoStringQPC(testDataLastUpdated, state.overlayTimingFrequency) + " ago."; + return std::string(T(TKEY("testdata_from_manual_toggle"), "Test data from manual shader toggle.\nLast updated: ")) + Util::TimeAgoStringQPC(testDataLastUpdated, state.overlayTimingFrequency) + T(TKEY("testdata_ago_suffix"), " ago."); default: - return "No test data available."; + return T(TKEY("testdata_none"), "No test data available."); } } diff --git a/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp b/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp index fa3cde2c35..14dc3f6fb2 100644 --- a/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp +++ b/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp @@ -1,10 +1,14 @@ #include "ABTesting.h" #include "Features/PerformanceOverlay.h" +#include "I18n/I18n.h" #include "Menu.h" #include "Menu/ThemeManager.h" #include "State.h" #include "Utils/FileSystem.h" #include "Utils/UI.h" + +#define I18N_KEY_PREFIX "feature.perf_overlay." + #include #include #include @@ -138,7 +142,7 @@ void ABTestingManager::DrawSettingsUI() { auto& performanceOverlay = globals::features::performanceOverlay; - if (ImGui::SliderInt("A/B Test Interval", reinterpret_cast(&testInterval), 0, 10)) { + if (ImGui::SliderInt(T(TKEY("abtest_interval"), "A/B Test Interval"), reinterpret_cast(&testInterval), 0, 10)) { bool overlayWasEnabled = performanceOverlay.settings.ShowInOverlay; if (testInterval == 0) { Disable(); @@ -151,13 +155,14 @@ void ABTestingManager::DrawSettingsUI() } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "A/B Testing compares two configurations by automatically swapping between them.\n" - "Workflow: Configure your test settings, then enable A/B testing.\n" - "- Variant B (TEST) = Your current settings when you enable testing\n" - "- Variant A (USER) = Your previously saved user configuration\n" - "Testing starts with Variant B, then swaps every N seconds.\n" - "Set to 0 to disable and restore TEST settings."); + ImGui::Text("%s", + T(TKEY("abtest_tooltip"), + "A/B Testing compares two configurations by automatically swapping between them.\n" + "Workflow: Configure your test settings, then enable A/B testing.\n" + "- Variant B (TEST) = Your current settings when you enable testing\n" + "- Variant A (USER) = Your previously saved user configuration\n" + "Testing starts with Variant B, then swaps every N seconds.\n" + "Set to 0 to disable and restore TEST settings.")); } } @@ -248,9 +253,10 @@ void ABTestingManager::DrawOverlayUI() remaining = std::max(0.0f, remaining); // Show current variant and time - ImGui::Text(fmt::format("{} : {:.1f}s left", - usingTestConfig ? "Variant B (TEST)" : "Variant A (USER)", remaining) - .c_str()); + const char* variantLabel = usingTestConfig ? T(TKEY("variant_b_test"), "Variant B (TEST)") : T(TKEY("variant_a_user"), "Variant A (USER)"); + ImGui::Text("%s", fmt::format(fmt::runtime(T(TKEY("variant_time_left_fmt"), "{} : {:.1f}s left")), + variantLabel, remaining) + .c_str()); // Show what changed (for both variants) if (hasTestSnapshot) { @@ -261,16 +267,18 @@ void ABTestingManager::DrawOverlayUI() constexpr size_t MAX_CHANGES_DISPLAYED = 10; // Show max 10 individual changes, otherwise show count if (differences.size() <= MAX_CHANGES_DISPLAYED) { - ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "Changes from USER:"); + ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s", T(TKEY("changes_from_user"), "Changes from USER:")); for (const auto& diff : differences) { ImGui::BulletText("%s", diff.c_str()); } } else { ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), - "%zu settings changed", differences.size()); + T(TKEY("settings_changed_count"), "%zu settings changed"), differences.size()); } } } ImGui::End(); -} \ No newline at end of file +} + +#undef I18N_KEY_PREFIX \ No newline at end of file diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 0e0a2eccd7..6844ca9301 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -230,9 +230,9 @@ void Upscaling::DrawSettings() ImGui::EndDisabled(); if (auto _tt = Util::HoverTooltipWrapper()) { if (openCompositeBlocksUpscaling) - ImGui::Text("Locked to None while OpenComposite has %s=true.", openCompositeBlocker.settingName.c_str()); + ImGui::Text(T(TKEY("method_locked_opencomposite"), "Locked to None while OpenComposite has %s=true."), openCompositeBlocker.settingName.c_str()); else - ImGui::TextUnformatted("Selects the upscaling backend."); + ImGui::TextUnformatted(T(TKEY("method_tooltip"), "Selects the upscaling backend.")); } *currentUpscaleMode = std::min(availableModes, *currentUpscaleMode); @@ -258,9 +258,9 @@ void Upscaling::DrawSettings() // diff uses the RestartNeeded color so users learn the cue means "you // changed something that won't apply yet." if (perfMode.IsHookActive()) { - ImGui::TextWrapped( + ImGui::TextWrapped(T(TKEY("perfmode_active_note"), "Render-at-upscaled-resolution is active: Method and Upscale Preset changes only take effect after a game restart. " - "Sharpness / model preset / Reflex remain live."); + "Sharpness / model preset / Reflex remain live.")); // Method pending-diff. Only fires when the user is editing the DLSS- // path mode slot (upscaleMethod, not upscaleMethodNoDLSS), since @@ -279,9 +279,9 @@ void Upscaling::DrawSettings() if (!globals::game::isVR && upscaleMethod == UpscaleMethod::kDLSS) { auto screenSize = globals::state->screenSize; if (screenSize.x > streamline.MAX_RESOLUTION || screenSize.y > streamline.MAX_RESOLUTION) { - Util::Text::Warning("Warning: Requested resolution %.0f x %.0f exceeds maximum supported resolution %d x %d for DLSS.", + Util::Text::Warning(T(TKEY("dlss_resolution_warning"), "Warning: Requested resolution %.0f x %.0f exceeds maximum supported resolution %d x %d for DLSS."), screenSize.x, screenSize.y, streamline.MAX_RESOLUTION, streamline.MAX_RESOLUTION); - Util::Text::Warning("DLSS will not function. Lower your resolution or select a different upscaling method."); + Util::Text::Warning(T(TKEY("dlss_will_not_function"), "DLSS will not function. Lower your resolution or select a different upscaling method.")); } } @@ -378,32 +378,32 @@ void Upscaling::DrawSettings() upscaleMethod == UpscaleMethod::kFSR; if (!methodSupportsPerf) ImGui::BeginDisabled(); - ImGui::Checkbox("Render engine at upscaled resolution", &settings.renderAtUpscaleRes); + ImGui::Checkbox(T(TKEY("render_at_upscale_res"), "Render engine at upscaled resolution"), &settings.renderAtUpscaleRes); if (!methodSupportsPerf) ImGui::EndDisabled(); // Hover tooltip always renders (so users learn what the option does even when greyed out). // The pending-restart banner fires only when DLSS or FSR is the active upscaler — the // feature can't take effect otherwise, so a "pending restart" hint there would mislead. if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "On by default. The engine pipeline allocates render targets at the upscaled-render\n" - "resolution instead of the HMD display resolution; the upscaler (DLSS or FSR) writes\n" - "its output to a private DisplayRes texture. Substantial VRAM and bandwidth savings,\n" - "especially at high HMD resolutions.\n" - "\n" - "Locked to the Upscale Preset selected at launch: changing the preset (or this\n" - "toggle) takes effect after a game restart. At Native AA (1.0x) there is no\n" - "render-res reduction, so the lock stays off and preset changes apply live.\n" - "\n" - "Requires DLSS or FSR. Sharpness / model preset / Reflex remain live."); + ImGui::Text("%s", T(TKEY("render_at_upscale_res_tooltip"), + "On by default. The engine pipeline allocates render targets at the upscaled-render\n" + "resolution instead of the HMD display resolution; the upscaler (DLSS or FSR) writes\n" + "its output to a private DisplayRes texture. Substantial VRAM and bandwidth savings,\n" + "especially at high HMD resolutions.\n" + "\n" + "Locked to the Upscale Preset selected at launch: changing the preset (or this\n" + "toggle) takes effect after a game restart. At Native AA (1.0x) there is no\n" + "render-res reduction, so the lock stays off and preset changes apply live.\n" + "\n" + "Requires DLSS or FSR. Sharpness / model preset / Reflex remain live.")); } if (!methodSupportsPerf && settings.renderAtUpscaleRes) - Util::Text::Disabled("Render-at-upscaled-resolution requires DLSS or FSR — switch upscaler Method to activate."); + Util::Text::Disabled(T(TKEY("render_at_upscale_res_requires"), "Render-at-upscaled-resolution requires DLSS or FSR — switch upscaler Method to activate.")); // At Native AA (1x) there's nothing to bank, so the size hook stays dormant even while // checked — surface the no-op rather than implying the toggle does something. if (methodSupportsPerf && settings.renderAtUpscaleRes && GetQualityModeRatio(settings.qualityMode) <= 1.0f) - Util::Text::Disabled("No effect at Native AA (1x) — renders at full resolution; raise the Upscale Preset to engage."); + Util::Text::Disabled(T(TKEY("render_at_upscale_res_native_noop"), "No effect at Native AA (1x) — renders at full resolution; raise the Upscale Preset to engage.")); if (methodSupportsPerf) Util::UI::DrawSettingDiff(bootSnapshot, settings, &Settings::renderAtUpscaleRes); } @@ -420,24 +420,25 @@ void Upscaling::DrawSettings() ImGui::Text("%s", T(TKEY("frame_generation_proxy_note"), "Requires a D3D11 to D3D12 proxy which can create compatibility issues")); if (!isWindowed) { - Util::Text::Warning("Warning: Requires windowed mode"); + Util::Text::Warning(T(TKEY("fg_warn_windowed"), "Warning: Requires windowed mode")); } if (lowRefreshRate && !settings.frameGenerationForceEnable) { - Util::Text::Warning("Warning: Requires a high refresh rate monitor or Force Enable Frame Generation"); + Util::Text::Warning(T(TKEY("fg_warn_refresh_rate"), "Warning: Requires a high refresh rate monitor or Force Enable Frame Generation")); } if (fidelityFXMissing) { - Util::Text::Warning("Warning: FidelityFX DLLs are not loaded"); + Util::Text::Warning(T(TKEY("fg_warn_fidelityfx_missing"), "Warning: FidelityFX DLLs are not loaded")); } bool fgEnabled = settings.frameGenerationMode != 0; if (ImGui::Checkbox(T(TKEY("frame_generation"), "Frame Generation"), &fgEnabled)) settings.frameGenerationMode = fgEnabled ? 1 : 0; Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::frameGenerationMode, - "Interpolate real frames with generated ones for a smoother experience. Uses AMD FSR Frame\n" - "Generation. Requires a D3D11-to-D3D12 proxy swapchain which can introduce compatibility\n" - "issues; in particular, frame generation works only in windowed mode."); + T(TKEY("frame_generation_tooltip"), + "Interpolate real frames with generated ones for a smoother experience. Uses AMD FSR Frame\n" + "Generation. Requires a D3D11-to-D3D12 proxy swapchain which can introduce compatibility\n" + "issues; in particular, frame generation works only in windowed mode.")); if (!frameGenerationDx12PathActive) ImGui::BeginDisabled(); @@ -557,7 +558,7 @@ void Upscaling::DrawSettings() const bool enabled = foveatedRender.settings.enabled != 0; if (!enabled) ImGui::BeginDisabled(); - if (ImGui::TreeNodeEx("Foveated DLSS — Tuning")) { + if (ImGui::TreeNodeEx(T(TKEY("foveated_tuning"), "Foveated DLSS — Tuning"))) { foveatedRender.DrawSettings(); ImGui::TreePop(); } @@ -587,9 +588,9 @@ void Upscaling::DrawSettings() if (globals::game::isVR) { ImGui::Separator(); static float debugRescale = 0.15f; - ImGui::SliderFloat("View Resize", &debugRescale, 0.05f, 1.f); + ImGui::SliderFloat(T(TKEY("view_resize"), "View Resize"), &debugRescale, 0.05f, 1.f); - if (ImGui::TreeNode("Upscaling Intermediates")) { + if (ImGui::TreeNode(T(TKEY("upscaling_intermediates"), "Upscaling Intermediates"))) { if (vrIntermediateMotionVectors[0]) { bool isDLSS = GetUpscaleMethod() == UpscaleMethod::kDLSS; if (vrIntermediateColorIn[0] && vrIntermediateColorOut[0]) { @@ -608,12 +609,12 @@ void Upscaling::DrawSettings() BUFFER_VIEWER_NODE_TITLE(vrIntermediateTransparencyMask[1], "Right Eye Transparency", debugRescale) } } else { - ImGui::TextDisabled("VR intermediates not yet created (enter game world)"); + ImGui::TextDisabled("%s", T(TKEY("vr_intermediates_not_created"), "VR intermediates not yet created (enter game world)")); } ImGui::TreePop(); } - if (ImGui::TreeNode("Native Inputs")) { + if (ImGui::TreeNode(T(TKEY("native_inputs"), "Native Inputs"))) { auto renderer = globals::game::renderer; auto& main = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMAIN]; auto& mvec = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMOTION_VECTOR]; @@ -708,8 +709,8 @@ void Upscaling::DrawSettings() } ImGui::Separator(); - Util::DrawDllVersionTable("AMD FidelityFX DLLs (click to open folder)", FidelityFX::PluginDir, FidelityFX::dllVersions, "ffx_dll_versions"); - Util::DrawDllVersionTable("NVIDIA Streamline DLLs (click to open folder)", Streamline::PluginDir, Streamline::dllVersions, "sl_dll_versions"); + Util::DrawDllVersionTable(T(TKEY("ffx_dll_table_title"), "AMD FidelityFX DLLs (click to open folder)"), FidelityFX::PluginDir, FidelityFX::dllVersions, "ffx_dll_versions"); + Util::DrawDllVersionTable(T(TKEY("sl_dll_table_title"), "NVIDIA Streamline DLLs (click to open folder)"), Streamline::PluginDir, Streamline::dllVersions, "sl_dll_versions"); ImGui::TreePop(); } } diff --git a/src/Features/Upscaling/FoveatedRender.cpp b/src/Features/Upscaling/FoveatedRender.cpp index e8f0265e62..11b019a15d 100644 --- a/src/Features/Upscaling/FoveatedRender.cpp +++ b/src/Features/Upscaling/FoveatedRender.cpp @@ -1,6 +1,7 @@ #include "FoveatedRender.h" #include "../../Globals.h" +#include "../../I18n/I18n.h" #include "../../Utils/Subrect.h" #include "../../Utils/UI.h" #include "../FoveatedCommon.h" @@ -9,6 +10,8 @@ #include +#define I18N_KEY_PREFIX "feature.upscaling." + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( FoveatedRender::Settings, enabled, @@ -206,10 +209,10 @@ void FoveatedRender::DrawEnable() { ClampSettings(); - ImGui::TextWrapped( + ImGui::TextWrapped(T(TKEY("foveated_overview"), "Foveated subrect DLSS: only the user-selected region gets full DLSS upscaling, " "the periphery is cheaply stretched. Significant DLSS cost reduction at the cost " - "of peripheral sharpness. VR + DLSS only."); + "of peripheral sharpness. VR + DLSS only.")); const bool runtimeSupported = IsRuntimeSupported(); if (!runtimeSupported) { @@ -219,58 +222,65 @@ void FoveatedRender::DrawEnable() if (!runtimeSupported) ImGui::BeginDisabled(); bool enabledBool = settings.enabled != 0; - if (ImGui::Checkbox("Enable Foveated DLSS", &enabledBool)) + if (ImGui::Checkbox(T(TKEY("foveated_enable"), "Enable Foveated DLSS"), &enabledBool)) settings.enabled = enabledBool ? 1u : 0u; if (!runtimeSupported) ImGui::EndDisabled(); if ((settings.enabled != 0) != enabledAtBoot) { - Util::Text::RestartNeeded("Pending restart: FoveatedRender will %s on next launch.", + Util::Text::RestartNeeded(T(TKEY("foveated_pending_restart"), "Pending restart: FoveatedRender will %s on next launch."), settings.enabled ? "enable" : "disable"); } if (enabledAtBoot) { if (globals::features::upscaling.GetUpscaleMethod() == Upscaling::UpscaleMethod::kDLSS) - Util::Text::WrappedInfo("Active: foveated subrect DLSS is enabled (skipped in menus / on preflight failure)."); + Util::Text::WrappedInfo(T(TKEY("foveated_active"), "Active: foveated subrect DLSS is enabled (skipped in menus / on preflight failure).")); else - Util::Text::Warning("Standing by: only active while the Upscaling Method is DLSS. Inactive right now."); + Util::Text::Warning(T(TKEY("foveated_standing_by"), "Standing by: only active while the Upscaling Method is DLSS. Inactive right now.")); } if (!globals::game::isVR) { - Util::Text::Warning("VR only. Non-VR / FSR support pending future contributors."); + Util::Text::Warning(T(TKEY("foveated_vr_only"), "VR only. Non-VR / FSR support pending future contributors.")); } if (globals::game::isVR && !globals::features::upscaling.streamline.featureDLSS) { - Util::Text::Warning("DLSS runtime not available. Enable is blocked."); + Util::Text::Warning(T(TKEY("foveated_dlss_unavailable"), "DLSS runtime not available. Enable is blocked.")); } } void FoveatedRender::DrawSettings() { - static const char* stretchModes[] = { "Bilinear", "Point", "Gaussian Blur" }; + const char* stretchModes[] = { + T(TKEY("foveated_stretch_bilinear"), "Bilinear"), + T(TKEY("foveated_stretch_point"), "Point"), + T(TKEY("foveated_stretch_gaussian"), "Gaussian Blur") + }; ClampSettings(); - Util::Text::WrappedInfo("Quality, Sharpness, and DLSS Preset are on the main Upscaling panel — changes there apply to foveated rendering too."); + Util::Text::WrappedInfo(T(TKEY("foveated_shared_panel_note"), "Quality, Sharpness, and DLSS Preset are on the main Upscaling panel — changes there apply to foveated rendering too.")); // ── VR-only knobs ── if (globals::game::isVR) { ImGui::Separator(); - ImGui::Text("VR DLSS Mode"); + ImGui::Text("%s", T(TKEY("foveated_dlss_mode_header"), "VR DLSS Mode")); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Default — highest quality. Each eye gets its own isolated copy of color/depth/motion\n" - "vectors so DLSS can't sample across the stereo midline. 5 copies per eye per frame.\n" - "All DLSS presets supported. Best for screenshots or when Faster shows edge artifacts.\n" - "\n" - "Faster — lower overhead. DLSS reads directly from the frame buffer using a viewport\n" - "offset instead of isolating each eye. 1 snapshot + 2 mask clears per frame.\n" - "DLSS may sample 1-2 pixels from the neighboring eye near the stereo center — usually\n" - "invisible in motion. Presets J and K are incompatible and auto-clamp to L."); + ImGui::Text("%s", T(TKEY("foveated_dlss_mode_tooltip"), + "Default — highest quality. Each eye gets its own isolated copy of color/depth/motion\n" + "vectors so DLSS can't sample across the stereo midline. 5 copies per eye per frame.\n" + "All DLSS presets supported. Best for screenshots or when Faster shows edge artifacts.\n" + "\n" + "Faster — lower overhead. DLSS reads directly from the frame buffer using a viewport\n" + "offset instead of isolating each eye. 1 snapshot + 2 mask clears per frame.\n" + "DLSS may sample 1-2 pixels from the neighboring eye near the stereo center — usually\n" + "invisible in motion. Presets J and K are incompatible and auto-clamp to L.")); } - static const char* dlssModes[] = { "Default", "Faster" }; + const char* dlssModes[] = { + T(TKEY("foveated_dlss_mode_default"), "Default"), + T(TKEY("foveated_dlss_mode_faster"), "Faster") + }; uint prevMode = settings.dlssMode; - ImGui::SliderInt("DLSS Mode", reinterpret_cast(&settings.dlssMode), 0, 1, dlssModes[std::min(settings.dlssMode, 1u)]); + ImGui::SliderInt(T(TKEY("foveated_dlss_mode_label"), "DLSS Mode"), reinterpret_cast(&settings.dlssMode), 0, 1, dlssModes[std::min(settings.dlssMode, 1u)]); if (settings.dlssMode != prevMode) { const uint prevPreset = globals::features::upscaling.settings.presetDLSS; ClampPresetToMode(); @@ -281,93 +291,100 @@ void FoveatedRender::DrawSettings() } switch (GetDlssMode()) { case DlssMode::kDefault: - ImGui::TextWrapped("Per-eye isolation: 5 copies per frame, 2 DLSS evaluates. All presets."); + ImGui::TextWrapped(T(TKEY("foveated_dlss_mode_default_desc"), "Per-eye isolation: 5 copies per frame, 2 DLSS evaluates. All presets.")); break; case DlssMode::kFaster: - ImGui::TextWrapped("Viewport offset: 1 snapshot, 2 mask clears, 2 DLSS evaluates. Presets J/K unavailable."); + ImGui::TextWrapped(T(TKEY("foveated_dlss_mode_faster_desc"), "Viewport offset: 1 snapshot, 2 mask clears, 2 DLSS evaluates. Presets J/K unavailable.")); break; default: break; } ImGui::Separator(); - ImGui::Text("Periphery Rendering"); + ImGui::Text("%s", T(TKEY("foveated_periphery_header"), "Periphery Rendering")); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "The area outside your selected subrect is filled cheaply rather than running DLSS.\n" - "These settings control how that cheap fill looks and whether it flickers.\n" - "\n" - "Stretch method: how pixels outside the subrect are reconstructed from the lower-res\n" - "render buffer. Does not affect the DLSS subrect region at all.\n" - "\n" - "Periphery AA: reduces temporal flicker in the stretched area using motion-compensated\n" - "history blending. Independent of the DLSS subrect.\n" - "\n" - "Edge Blend: controls how the DLSS subrect edge meets the stretched periphery.\n" - "Hard Copy leaves a sharp seam; Feather/Dither soften it. Only affects the boundary."); + ImGui::Text("%s", T(TKEY("foveated_periphery_tooltip"), + "The area outside your selected subrect is filled cheaply rather than running DLSS.\n" + "These settings control how that cheap fill looks and whether it flickers.\n" + "\n" + "Stretch method: how pixels outside the subrect are reconstructed from the lower-res\n" + "render buffer. Does not affect the DLSS subrect region at all.\n" + "\n" + "Periphery AA: reduces temporal flicker in the stretched area using motion-compensated\n" + "history blending. Independent of the DLSS subrect.\n" + "\n" + "Edge Blend: controls how the DLSS subrect edge meets the stretched periphery.\n" + "Hard Copy leaves a sharp seam; Feather/Dither soften it. Only affects the boundary.")); } - ImGui::SliderInt("Stretch", reinterpret_cast(&settings.stretchMode), 0, 2, stretchModes[settings.stretchMode]); + ImGui::SliderInt(T(TKEY("foveated_stretch_label"), "Stretch"), reinterpret_cast(&settings.stretchMode), 0, 2, stretchModes[settings.stretchMode]); switch (GetStretchMode()) { case StretchMode::kBilinear: - ImGui::TextWrapped("Bilinear: smooth upscale of the render buffer. Looks soft but clean."); + ImGui::TextWrapped(T(TKEY("foveated_stretch_bilinear_desc"), "Bilinear: smooth upscale of the render buffer. Looks soft but clean.")); break; case StretchMode::kPoint: - ImGui::TextWrapped("Point: cheapest, visibly pixelated. Good for benchmarking foveated savings."); + ImGui::TextWrapped(T(TKEY("foveated_stretch_point_desc"), "Point: cheapest, visibly pixelated. Good for benchmarking foveated savings.")); break; case StretchMode::kGaussianBlur: - ImGui::TextWrapped("Gaussian: blurs the periphery further into soft focus. Good default for foveated use."); - ImGui::SliderFloat("Blur Radius", &settings.peripheryBlurRadius, 0.5f, 4.0f, "%.1f px"); + ImGui::TextWrapped(T(TKEY("foveated_stretch_gaussian_desc"), "Gaussian: blurs the periphery further into soft focus. Good default for foveated use.")); + ImGui::SliderFloat(T(TKEY("foveated_blur_radius"), "Blur Radius"), &settings.peripheryBlurRadius, 0.5f, 4.0f, "%.1f px"); break; } { - static const char* peripheryAAModes[] = { "None", "Temporal Smooth" }; - ImGui::SliderInt("Periphery AA", reinterpret_cast(&settings.peripheryAAMode), 0, 1, peripheryAAModes[settings.peripheryAAMode]); + const char* peripheryAAModes[] = { + T(TKEY("foveated_periphery_aa_none"), "None"), + T(TKEY("foveated_periphery_aa_temporal"), "Temporal Smooth") + }; + ImGui::SliderInt(T(TKEY("foveated_periphery_aa_label"), "Periphery AA"), reinterpret_cast(&settings.peripheryAAMode), 0, 1, peripheryAAModes[settings.peripheryAAMode]); } if (GetPeripheryAAMode() == PeripheryAAMode::kTemporalSmooth) { - ImGui::TextWrapped("Blends the stretched periphery with motion-reprojected history to reduce flicker."); - ImGui::SliderFloat("Smoothing", &settings.peripheryTemporalAlpha, 0.05f, 0.5f, "%.2f"); + ImGui::TextWrapped(T(TKEY("foveated_periphery_aa_temporal_desc"), "Blends the stretched periphery with motion-reprojected history to reduce flicker.")); + ImGui::SliderFloat(T(TKEY("foveated_smoothing"), "Smoothing"), &settings.peripheryTemporalAlpha, 0.05f, 0.5f, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Lower = more temporal history (smoother but may ghost). Higher = more responsive."); + ImGui::Text("%s", T(TKEY("foveated_smoothing_tooltip"), "Lower = more temporal history (smoother but may ghost). Higher = more responsive.")); } } { - static const char* blendModes[] = { "Hard Copy", "Feather", "Dither" }; - ImGui::SliderInt("Edge Blend", reinterpret_cast(&settings.subrectBlendMode), 0, 2, blendModes[std::min(settings.subrectBlendMode, 2u)]); + const char* blendModes[] = { + T(TKEY("foveated_blend_hard_copy"), "Hard Copy"), + T(TKEY("foveated_blend_feather"), "Feather"), + T(TKEY("foveated_blend_dither"), "Dither") + }; + ImGui::SliderInt(T(TKEY("foveated_edge_blend_label"), "Edge Blend"), reinterpret_cast(&settings.subrectBlendMode), 0, 2, blendModes[std::min(settings.subrectBlendMode, 2u)]); } switch (GetSubrectBlendMode()) { case SubrectBlendMode::kHardCopy: - ImGui::TextWrapped("Sharp seam at the subrect boundary. Lowest cost."); + ImGui::TextWrapped(T(TKEY("foveated_blend_hard_copy_desc"), "Sharp seam at the subrect boundary. Lowest cost.")); break; case SubrectBlendMode::kFeather: - ImGui::TextWrapped("Smoothstep fade over N pixels at the boundary. Hides the seam."); - ImGui::SliderFloat("Feather Width", &settings.subrectFeatherWidth, 2.0f, 128.0f, "%.0f px"); + ImGui::TextWrapped(T(TKEY("foveated_blend_feather_desc"), "Smoothstep fade over N pixels at the boundary. Hides the seam.")); + ImGui::SliderFloat(T(TKEY("foveated_feather_width"), "Feather Width"), &settings.subrectFeatherWidth, 2.0f, 128.0f, "%.0f px"); break; case SubrectBlendMode::kDither: - ImGui::TextWrapped("Noise-dithered fade — more natural-looking than feather at large subrects."); - ImGui::SliderFloat("Band Width", &settings.subrectFeatherWidth, 2.0f, 128.0f, "%.0f px"); - ImGui::SliderFloat("Noise Amount", &settings.subrectDitherStrength, 0.0f, 2.0f, "%.2f"); + ImGui::TextWrapped(T(TKEY("foveated_blend_dither_desc"), "Noise-dithered fade — more natural-looking than feather at large subrects.")); + ImGui::SliderFloat(T(TKEY("foveated_band_width"), "Band Width"), &settings.subrectFeatherWidth, 2.0f, 128.0f, "%.0f px"); + ImGui::SliderFloat(T(TKEY("foveated_noise_amount"), "Noise Amount"), &settings.subrectDitherStrength, 0.0f, 2.0f, "%.2f"); break; } ImGui::Separator(); - ImGui::Text("Subrect Region"); - ImGui::TextWrapped( + ImGui::Text("%s", T(TKEY("foveated_subrect_region_header"), "Subrect Region")); + ImGui::TextWrapped(T(TKEY("foveated_subrect_region_desc"), "Drag in the preview below to select the region that gets full DLSS upscaling. " - "The rest is cheaply stretched — saves significant DLSS cost."); - Util::Text::WrappedInfo("Screenshot has its own subrect; align them only if you want pixel-matched captures."); + "The rest is cheaply stretched — saves significant DLSS cost.")); + Util::Text::WrappedInfo(T(TKEY("foveated_screenshot_subrect_note"), "Screenshot has its own subrect; align them only if you want pixel-matched captures.")); bool debugBool = settings.debugVisualize != 0; - if (ImGui::Checkbox("Visualize regions", &debugBool)) + if (ImGui::Checkbox(T(TKEY("foveated_visualize_regions"), "Visualize regions"), &debugBool)) settings.debugVisualize = debugBool ? 1u : 0u; if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Diagnostic: tint the cheap-stretched periphery red so the DLSS-reconstructed\n" - "subrect (un-tinted) pops visually in-game. Lets you confirm at a glance where\n" - "DLSS is actually running vs where the cheap stretch is filling. No perf impact;\n" - "runtime toggle, no restart needed."); + ImGui::Text("%s", T(TKEY("foveated_visualize_regions_tooltip"), + "Diagnostic: tint the cheap-stretched periphery red so the DLSS-reconstructed\n" + "subrect (un-tinted) pops visually in-game. Lets you confirm at a glance where\n" + "DLSS is actually running vs where the cheap stretch is filling. No perf impact;\n" + "runtime toggle, no restart needed.")); } // Preview off kVR_FRAMEBUFFER (the final composed SBS image the headset @@ -387,3 +404,5 @@ void FoveatedRender::DrawSettings() } } } + +#undef I18N_KEY_PREFIX diff --git a/src/Features/VR/SettingsUI.cpp b/src/Features/VR/SettingsUI.cpp index 58f87649e0..a7adc261dc 100644 --- a/src/Features/VR/SettingsUI.cpp +++ b/src/Features/VR/SettingsUI.cpp @@ -4,6 +4,7 @@ #include "Features/ScreenSpaceShadows.h" #include "Features/Upscaling.h" #include "Features/VR.h" +#include "I18n/I18n.h" #include "Menu.h" #include "Menu/Fonts.h" #include "RE/B/BSOpenVR.h" @@ -15,6 +16,8 @@ #include +#define I18N_KEY_PREFIX "feature.vr." + using AttachMode = VR::Settings::OverlayAttachMode; namespace @@ -110,28 +113,28 @@ void VR::DrawOverlay() ImGui::Begin("HowToUseOverlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f * scale); - ImGui::TextWrapped("How to Use VR Open Shaders Menu:"); + ImGui::TextWrapped("%s", T(TKEY("overlay_how_to_title"), "How to Use VR Open Shaders Menu:")); ImGui::Separator(); - ImGui::TextWrapped("You must open the Main Menu or Tween Menu before VR controls work."); + ImGui::TextWrapped("%s", T(TKEY("overlay_open_menu_first"), "You must open the Main Menu or Tween Menu before VR controls work.")); ImGui::Spacing(); ImGui::PopTextWrapPos(); - ImGui::Text("Open Menu: "); + ImGui::Text("%s", T(TKEY("overlay_open_menu_label"), "Open Menu: ")); ImGui::SameLine(); Util::DrawButtonCombo(settings.VRMenuOpenKeys, true); - ImGui::Text("Close Menu: "); + ImGui::Text("%s", T(TKEY("overlay_close_menu_label"), "Close Menu: ")); ImGui::SameLine(); Util::DrawButtonCombo(settings.VRMenuCloseKeys, true); ImGui::Spacing(); ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f * scale); - ImGui::TextWrapped("Grip + Thumbstick: Adjust overlay depth (closer/farther)"); + ImGui::TextWrapped("%s", T(TKEY("overlay_grip_thumbstick_depth"), "Grip + Thumbstick: Adjust overlay depth (closer/farther)")); ImGui::Spacing(); - ImGui::TextWrapped("Tip: Disable this VR overlay by setting Attach Mode to 'None' in VR settings."); + ImGui::TextWrapped("%s", T(TKEY("overlay_disable_tip"), "Tip: Disable this VR overlay by setting Attach Mode to 'None' in VR settings.")); ImGui::Spacing(); - ImGui::TextWrapped("(This welcome message will auto-hide in %d seconds)", secondsLeft); - ImGui::TextWrapped("(Disable in: VR settings > Controller Input Instructions)"); + ImGui::TextWrapped(T(TKEY("overlay_auto_hide_countdown"), "(This welcome message will auto-hide in %d seconds)"), secondsLeft); + ImGui::TextWrapped("%s", T(TKEY("overlay_disable_location"), "(Disable in: VR settings > Controller Input Instructions)")); ImGui::PopTextWrapPos(); ImGui::End(); @@ -149,77 +152,77 @@ namespace auto& settings = vr.settings; if (!vr.IsOpenVRCompatible()) return; - if (ImGui::CollapsingHeader("Controller Input Instructions", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderInt("Auto-hide Welcome overlay timeout", &settings.kAutoHideSeconds, 0, VR::Config::kMaxAutoHideSeconds, + if (ImGui::CollapsingHeader(T(TKEY("controller_input_header"), "Controller Input Instructions"), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderInt(T(TKEY("auto_hide_timeout"), "Auto-hide Welcome overlay timeout"), &settings.kAutoHideSeconds, 0, VR::Config::kMaxAutoHideSeconds, settings.kAutoHideSeconds <= 0 ? "Hidden" : "%d seconds"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Set to 0 to hide the overlay, or a positive value to show it for that many seconds"); + ImGui::Text("%s", T(TKEY("auto_hide_timeout_tooltip"), "Set to 0 to hide the overlay, or a positive value to show it for that many seconds")); } - ImGui::TextWrapped("Menu (while in the main menu or tween menu):"); + ImGui::TextWrapped("%s", T(TKEY("instructions_menu_section"), "Menu (while in the main menu or tween menu):")); if (ImGui::BeginTable("MenuInstructionsTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Open the Open Shaders Menu:"); + ImGui::Text("%s", T(TKEY("instructions_open_menu"), "Open the Open Shaders Menu:")); ImGui::TableSetColumnIndex(1); Util::DrawButtonCombo(settings.VRMenuOpenKeys, true); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Close the Open Shaders Menu:"); + ImGui::Text("%s", T(TKEY("instructions_close_menu"), "Close the Open Shaders Menu:")); ImGui::TableSetColumnIndex(1); Util::DrawButtonCombo(settings.VRMenuCloseKeys, true); ImGui::EndTable(); } - ImGui::TextWrapped("Overlay (while in the main menu or tween menu):"); + ImGui::TextWrapped("%s", T(TKEY("instructions_overlay_section"), "Overlay (while in the main menu or tween menu):")); if (ImGui::BeginTable("OverlayInstructionsTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Open Overlay:"); + ImGui::Text("%s", T(TKEY("instructions_open_overlay"), "Open Overlay:")); ImGui::TableSetColumnIndex(1); Util::DrawButtonCombo(settings.VROverlayOpenKeys, true); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Close Overlay:"); + ImGui::Text("%s", T(TKEY("instructions_close_overlay"), "Close Overlay:")); ImGui::TableSetColumnIndex(1); Util::DrawButtonCombo(settings.VROverlayCloseKeys, true); ImGui::EndTable(); } - ImGui::TextWrapped("Menu Controller Input:"); + ImGui::TextWrapped("%s", T(TKEY("instructions_controller_input_section"), "Menu Controller Input:")); if (ImGui::BeginTable("ControllerInputTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerBothColor(), "Trigger (Both Controllers)"); + ImGui::TextColored(Util::GetControllerBothColor(), "%s", T(TKEY("input_trigger_both"), "Trigger (Both Controllers)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Left mouse button"); + ImGui::Text("%s", T(TKEY("input_left_mouse_button"), "Left mouse button")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerBothColor(), "Grip (Both Controllers)"); + ImGui::TextColored(Util::GetControllerBothColor(), "%s", T(TKEY("input_grip_both"), "Grip (Both Controllers)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Right mouse button"); + ImGui::Text("%s", T(TKEY("input_right_mouse_button"), "Right mouse button")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerBothColor(), "Touchpad Click (Both Controllers)"); + ImGui::TextColored(Util::GetControllerBothColor(), "%s", T(TKEY("input_touchpad_both"), "Touchpad Click (Both Controllers)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Middle mouse button"); + ImGui::Text("%s", T(TKEY("input_middle_mouse_button"), "Middle mouse button")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerBothColor(), "Stick Click (Both Controllers)"); + ImGui::TextColored(Util::GetControllerBothColor(), "%s", T(TKEY("input_stick_click_both"), "Stick Click (Both Controllers)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Middle mouse button"); + ImGui::Text("%s", T(TKEY("input_middle_mouse_button_2"), "Middle mouse button")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerBothColor(), "A/X (Both Controllers)"); + ImGui::TextColored(Util::GetControllerBothColor(), "%s", T(TKEY("input_ax_both"), "A/X (Both Controllers)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Enter"); + ImGui::Text("%s", T(TKEY("input_enter"), "Enter")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerPrimaryColor(), "B/Y (Primary Controller)"); + ImGui::TextColored(Util::GetControllerPrimaryColor(), "%s", T(TKEY("input_by_primary"), "B/Y (Primary Controller)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Tab"); + ImGui::Text("%s", T(TKEY("input_tab"), "Tab")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerSecondaryColor(), "B/Y (Secondary Controller)"); + ImGui::TextColored(Util::GetControllerSecondaryColor(), "%s", T(TKEY("input_by_secondary"), "B/Y (Secondary Controller)")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Shift+Tab"); + ImGui::Text("%s", T(TKEY("input_shift_tab"), "Shift+Tab")); ImGui::EndTable(); } bool useAttachedControllerForCursor = (settings.attachMode == AttachMode::ControllerOnly || settings.attachMode == AttachMode::Both); @@ -228,37 +231,37 @@ namespace if (settings.VRMenuAttachController == ControllerDevice::Primary) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerPrimaryColor(), "Primary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerPrimaryColor(), "%s", T(TKEY("thumbstick_primary"), "Primary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Mouse movement (attached controller)"); + ImGui::Text("%s", T(TKEY("thumbstick_mouse_movement_attached"), "Mouse movement (attached controller)")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerSecondaryColor(), "Secondary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerSecondaryColor(), "%s", T(TKEY("thumbstick_secondary"), "Secondary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Scroll"); + ImGui::Text("%s", T(TKEY("thumbstick_scroll"), "Scroll")); } else { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerPrimaryColor(), "Primary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerPrimaryColor(), "%s", T(TKEY("thumbstick_primary"), "Primary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Scroll"); + ImGui::Text("%s", T(TKEY("thumbstick_scroll"), "Scroll")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerSecondaryColor(), "Secondary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerSecondaryColor(), "%s", T(TKEY("thumbstick_secondary"), "Secondary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Mouse movement (attached controller)"); + ImGui::Text("%s", T(TKEY("thumbstick_mouse_movement_attached"), "Mouse movement (attached controller)")); } } else { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerPrimaryColor(), "Primary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerPrimaryColor(), "%s", T(TKEY("thumbstick_primary"), "Primary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Mouse movement (HMD mode)"); + ImGui::Text("%s", T(TKEY("thumbstick_mouse_movement_hmd"), "Mouse movement (HMD mode)")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextColored(Util::GetControllerSecondaryColor(), "Secondary Controller Thumbstick"); + ImGui::TextColored(Util::GetControllerSecondaryColor(), "%s", T(TKEY("thumbstick_secondary"), "Secondary Controller Thumbstick")); ImGui::TableSetColumnIndex(1); - ImGui::Text("Scroll"); + ImGui::Text("%s", T(TKEY("thumbstick_scroll"), "Scroll")); } ImGui::EndTable(); } @@ -270,54 +273,58 @@ namespace auto& vr = globals::features::vr; VR::Settings& settings = vr.settings; - if (ImGui::CollapsingHeader("Stereo Reprojection", ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader(T(TKEY("stereo_reprojection_header"), "Stereo Reprojection"), ImGuiTreeNodeFlags_DefaultOpen)) vr.stereoOpt.DrawSettings(); bool hasEffects = VR::AnyScreenSpaceEffectLoaded(); bool isDev = globals::state && globals::state->IsDeveloperMode(); - if (ImGui::CollapsingHeader("Stereo Blend", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(T(TKEY("stereo_blend_header"), "Stereo Blend"), ImGuiTreeNodeFlags_DefaultOpen)) { if (!hasEffects && !isDev) { - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "Requires an active screen-space effect (SSGI, SS Shadows, SSR)."); + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "%s", T(TKEY("stereo_blend_requires_effect"), "Requires an active screen-space effect (SSGI, SS Shadows, SSR).")); } else { if (!hasEffects) - ImGui::TextColored(ImVec4(0.6f, 0.6f, 1.0f, 1.0f), "Developer mode: no screen-space effects active."); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 1.0f, 1.0f), "%s", T(TKEY("stereo_blend_dev_mode"), "Developer mode: no screen-space effects active.")); - ImGui::Checkbox("Enable Stereo Blend", &settings.EnableStereoBlend); + ImGui::Checkbox(T(TKEY("stereo_blend_enable"), "Enable Stereo Blend"), &settings.EnableStereoBlend); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Post-composite depth-aware bilateral blend between eyes.\n" - "Reduces stereo inconsistencies from screen-space effects (SSGI, SSR, etc.).\n" - "Each pixel is reprojected to the other eye; blending is applied only where\n" - "depth agrees (same surface). Full-screen pass in VR."); + ImGui::Text("%s", + T(TKEY("stereo_blend_enable_tooltip"), + "Post-composite depth-aware bilateral blend between eyes.\n" + "Reduces stereo inconsistencies from screen-space effects (SSGI, SSR, etc.).\n" + "Each pixel is reprojected to the other eye; blending is applied only where\n" + "depth agrees (same surface). Full-screen pass in VR.")); } ImGui::BeginDisabled(!settings.EnableStereoBlend); - ImGui::SliderFloat("Depth Sigma", &settings.StereoBlendDepthSigma, 0.001f, 0.1f, "%.4f"); + ImGui::SliderFloat(T(TKEY("stereo_blend_depth_sigma"), "Depth Sigma"), &settings.StereoBlendDepthSigma, 0.001f, 0.1f, "%.4f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Depth sensitivity for the bilateral weight.\n" - "Lower values are stricter -- only blend when depths match very closely.\n" - "Higher values allow blending across slight depth differences.\n" - "Default: 0.01"); + ImGui::Text("%s", + T(TKEY("stereo_blend_depth_sigma_tooltip"), + "Depth sensitivity for the bilateral weight.\n" + "Lower values are stricter -- only blend when depths match very closely.\n" + "Higher values allow blending across slight depth differences.\n" + "Default: 0.01")); } - ImGui::SliderFloat("Max Blend Factor", &settings.StereoBlendMaxFactor, 0.0f, 0.5f, "%.2f"); + ImGui::SliderFloat(T(TKEY("stereo_blend_max_factor"), "Max Blend Factor"), &settings.StereoBlendMaxFactor, 0.0f, 0.5f, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Maximum blend strength between the two eyes.\n" - "Higher values reduce screen-space effect flicker but destroy stereo depth.\n" - "Keep below ~0.15 to preserve 3D parallax.\n" - "Default: 0.1"); + ImGui::Text("%s", + T(TKEY("stereo_blend_max_factor_tooltip"), + "Maximum blend strength between the two eyes.\n" + "Higher values reduce screen-space effect flicker but destroy stereo depth.\n" + "Keep below ~0.15 to preserve 3D parallax.\n" + "Default: 0.1")); } - ImGui::SliderFloat("Color Difference Threshold", &settings.StereoBlendColorThreshold, 0.0f, 0.2f, "%.3f"); + ImGui::SliderFloat(T(TKEY("stereo_blend_color_threshold"), "Color Difference Threshold"), &settings.StereoBlendColorThreshold, 0.0f, 0.2f, "%.3f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Minimum luminance difference between eyes to trigger blending.\n" - "Set to 0 to blend everywhere. Higher = more selective.\n" - "Default: 0.02"); + ImGui::Text("%s", + T(TKEY("stereo_blend_color_threshold_tooltip"), + "Minimum luminance difference between eyes to trigger blending.\n" + "Set to 0 to blend everywhere. Higher = more selective.\n" + "Default: 0.02")); } ImGui::EndDisabled(); @@ -332,8 +339,15 @@ namespace static bool s_weEnabledStereoBlend = false; static bool s_weEnabledReproj = false; - const char* debugModes[] = { "Off", "Back-Check", "Blend Weight", "Edge Detection", "Overwrite", "Overwrite Eye1" }; - if (ImGui::Combo("Debug View", &settings.StereoBlendDebugMode, debugModes, IM_ARRAYSIZE(debugModes))) { + const char* debugModes[] = { + T(TKEY("stereo_debug_off"), "Off"), + T(TKEY("stereo_debug_back_check"), "Back-Check"), + T(TKEY("stereo_debug_blend_weight"), "Blend Weight"), + T(TKEY("stereo_debug_edge_detection"), "Edge Detection"), + T(TKEY("stereo_debug_overwrite"), "Overwrite"), + T(TKEY("stereo_debug_overwrite_eye1"), "Overwrite Eye1") + }; + if (ImGui::Combo(T(TKEY("stereo_debug_view"), "Debug View"), &settings.StereoBlendDebugMode, debugModes, IM_ARRAYSIZE(debugModes))) { int newMode = settings.StereoBlendDebugMode; bool needsBlend = (newMode >= 1 && newMode <= 3); bool needsReproj = (newMode == 4 || newMode == 5); @@ -358,44 +372,47 @@ namespace } } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Selecting a debug mode auto-enables the required feature; setting back to Off restores it.\n\n" - "Off: Normal rendering\n" - "Back-Check: Round-trip reprojection validation (auto-enables Stereo Blend)\n" - "Blend Weight: Heatmap of bilateral blend intensity (auto-enables Stereo Blend)\n" - "Edge Detection: Highlights depth discontinuities (auto-enables Stereo Blend)\n" - "Overwrite: Mode texture classification (auto-enables Reprojection -- restart required)\n" - " Green=edge Pink=edge neighbour Blue=disoccluded Orange=full blend\n" - "Overwrite Eye1: POM depth heatmap for Eye 1 (auto-enables Reprojection -- restart required)"); + ImGui::Text("%s", + T(TKEY("stereo_debug_view_tooltip"), + "Selecting a debug mode auto-enables the required feature; setting back to Off restores it.\n\n" + "Off: Normal rendering\n" + "Back-Check: Round-trip reprojection validation (auto-enables Stereo Blend)\n" + "Blend Weight: Heatmap of bilateral blend intensity (auto-enables Stereo Blend)\n" + "Edge Detection: Highlights depth discontinuities (auto-enables Stereo Blend)\n" + "Overwrite: Mode texture classification (auto-enables Reprojection -- restart required)\n" + " Green=edge Pink=edge neighbour Blue=disoccluded Orange=full blend\n" + "Overwrite Eye1: POM depth heatmap for Eye 1 (auto-enables Reprojection -- restart required)")); } } - if (ImGui::CollapsingHeader("Foveated Effects")) { + if (ImGui::CollapsingHeader(T(TKEY("foveated_effects_header"), "Foveated Effects"))) { auto& upscaling = globals::features::upscaling; auto& dynamicCubemaps = globals::features::dynamicCubemaps; const bool foveatedDLSSActive = upscaling.foveatedRender.IsActive(); const bool ssrEnabled = dynamicCubemaps.loaded && dynamicCubemaps.settings.EnabledSSR; if (!foveatedDLSSActive) - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "Requires Foveated DLSS to be active (Upscaling settings)."); + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "%s", T(TKEY("foveated_requires_dlss"), "Requires Foveated DLSS to be active (Upscaling settings).")); if (!ssrEnabled) - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "Requires Screen Space Reflections (Dynamic Cubemaps)."); + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "%s", T(TKEY("foveated_requires_ssr"), "Requires Screen Space Reflections (Dynamic Cubemaps).")); ImGui::BeginDisabled(!foveatedDLSSActive || !ssrEnabled); - ImGui::Checkbox("Foveate SSR Raymarching", &settings.EnableSSRFoveation); + ImGui::Checkbox(T(TKEY("foveated_ssr_raymarching"), "Foveate SSR Raymarching"), &settings.EnableSSRFoveation); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Reduces screen-space reflection raymarching toward the periphery, using the\n" - "active Foveated DLSS region. Central reflections stay full quality; peripheral\n" - "pixels fall back to the cubemap / water reflection. VR only."); + ImGui::Text("%s", + T(TKEY("foveated_ssr_raymarching_tooltip"), + "Reduces screen-space reflection raymarching toward the periphery, using the\n" + "active Foveated DLSS region. Central reflections stay full quality; peripheral\n" + "pixels fall back to the cubemap / water reflection. VR only.")); } ImGui::BeginDisabled(!settings.EnableSSRFoveation); ImGui::Checkbox("Hard Cutoff Outside Center##SSRFoveation", &settings.EnableSSRFoveationHardCutoff); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Hard-skip SSR outside the center region instead of a feathered falloff.\n" - "Cheaper, but the transition edge may be visible. Default off (feathered)."); + ImGui::Text("%s", + T(TKEY("foveated_hard_cutoff_tooltip"), + "Hard-skip SSR outside the center region instead of a feathered falloff.\n" + "Cheaper, but the transition edge may be visible. Default off (feathered).")); } ImGui::EndDisabled(); ImGui::EndDisabled(); @@ -406,28 +423,28 @@ namespace { auto& vr = globals::features::vr; VR::Settings& settings = vr.settings; - if (ImGui::CollapsingHeader("General Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - bool exteriorChanged = ImGui::Checkbox("Enable Depth Buffer Culling in Exteriors", &settings.EnableDepthBufferCullingExterior); + if (ImGui::CollapsingHeader(T(TKEY("general_settings_header"), "General Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { + bool exteriorChanged = ImGui::Checkbox(T(TKEY("depth_culling_exteriors"), "Enable Depth Buffer Culling in Exteriors"), &settings.EnableDepthBufferCullingExterior); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Improves performance in exteriors, recommended ON."); + ImGui::Text("%s", T(TKEY("depth_culling_exteriors_tooltip"), "Improves performance in exteriors, recommended ON.")); } - bool interiorChanged = ImGui::Checkbox("Enable Depth Buffer Culling in Interiors", &settings.EnableDepthBufferCullingInterior); + bool interiorChanged = ImGui::Checkbox(T(TKEY("depth_culling_interiors"), "Enable Depth Buffer Culling in Interiors"), &settings.EnableDepthBufferCullingInterior); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Improves performance in interiors, recommended ON."); + ImGui::Text("%s", T(TKEY("depth_culling_interiors_tooltip"), "Improves performance in interiors, recommended ON.")); } if (exteriorChanged || interiorChanged) { vr.UpdateDepthBufferCulling(); } - if (ImGui::SliderFloat("Min Occludee Box Extent", &settings.MinOccludeeBoxExtent, 0.0f, 1000.0f, "%.1f")) { + if (ImGui::SliderFloat(T(TKEY("min_occludee_box_extent"), "Min Occludee Box Extent"), &settings.MinOccludeeBoxExtent, 0.0f, 1000.0f, "%.1f")) { if (vr.gMinOccludeeBoxExtent) { *vr.gMinOccludeeBoxExtent = settings.MinOccludeeBoxExtent; } } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Minimum bounding box dimensions for object occlusion culling. Lower values improve performance but may result in visual artifacts."); + ImGui::Text("%s", T(TKEY("min_occludee_box_extent_tooltip"), "Minimum bounding box dimensions for object occlusion culling. Lower values improve performance but may result in visual artifacts.")); } } } @@ -438,12 +455,15 @@ namespace auto& settings = vr.settings; if (!vr.IsOpenVRCompatible()) return; - if (ImGui::CollapsingHeader("Menu Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(T(TKEY("menu_settings_header"), "Menu Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { float maxScale = VR::Config::kMaxMenuScale; - ImGui::SliderFloat("Menu Scale", &settings.VRMenuScale, VR::Config::kMinMenuScale, maxScale, "%.2f"); - const char* positioningMethods[] = { "HMD Relative", "Fixed World Position" }; + ImGui::SliderFloat(T(TKEY("menu_scale"), "Menu Scale"), &settings.VRMenuScale, VR::Config::kMinMenuScale, maxScale, "%.2f"); + const char* positioningMethods[] = { + T(TKEY("menu_pos_hmd_relative"), "HMD Relative"), + T(TKEY("menu_pos_fixed_world"), "Fixed World Position") + }; int prevMethod = settings.VRMenuPositioningMethod; - if (ImGui::Combo("Menu Positioning Method", &settings.VRMenuPositioningMethod, positioningMethods, IM_ARRAYSIZE(positioningMethods))) { + if (ImGui::Combo(T(TKEY("menu_positioning_method"), "Menu Positioning Method"), &settings.VRMenuPositioningMethod, positioningMethods, IM_ARRAYSIZE(positioningMethods))) { if (prevMethod != 1 && settings.VRMenuPositioningMethod == 1) { vr.SetFixedOverlayToCurrentHMD(); auto player = RE::PlayerCharacter::GetSingleton(); @@ -451,44 +471,52 @@ namespace vr.savedPlayerWorldPos = player->GetPosition(); } } - const char* attachModes[] = { "HMD Only", "Controller Only", "Both", "None (Disabled)" }; + const char* attachModes[] = { + T(TKEY("attach_mode_hmd_only"), "HMD Only"), + T(TKEY("attach_mode_controller_only"), "Controller Only"), + T(TKEY("attach_mode_both"), "Both"), + T(TKEY("attach_mode_none"), "None (Disabled)") + }; int attachModeInt = static_cast(settings.attachMode); - if (ImGui::Combo("Attach Mode", &attachModeInt, attachModes, IM_ARRAYSIZE(attachModes))) { + if (ImGui::Combo(T(TKEY("attach_mode"), "Attach Mode"), &attachModeInt, attachModes, IM_ARRAYSIZE(attachModes))) { settings.attachMode = static_cast(attachModeInt); } if (settings.attachMode == AttachMode::ControllerOnly || settings.attachMode == AttachMode::Both) { - const char* attachControllers[] = { "Primary Controller", "Secondary Controller" }; + const char* attachControllers[] = { + T(TKEY("attach_controller_primary"), "Primary Controller"), + T(TKEY("attach_controller_secondary"), "Secondary Controller") + }; int attachControllerInt = static_cast(settings.VRMenuAttachController); - if (ImGui::Combo("Attach to Controller", &attachControllerInt, attachControllers, IM_ARRAYSIZE(attachControllers))) { + if (ImGui::Combo(T(TKEY("attach_to_controller"), "Attach to Controller"), &attachControllerInt, attachControllers, IM_ARRAYSIZE(attachControllers))) { settings.VRMenuAttachController = static_cast(attachControllerInt); } ImGui::Separator(); - ImGui::Text("Controller Offset Settings"); - ImGui::SliderFloat("Controller Offset X", &settings.VRMenuControllerOffsetX, -2.0f, 2.0f, "%.2f"); - ImGui::SliderFloat("Controller Offset Y", &settings.VRMenuControllerOffsetY, -2.0f, 2.0f, "%.2f"); - ImGui::SliderFloat("Controller Offset Z", &settings.VRMenuControllerOffsetZ, -2.0f, 2.0f, "%.2f"); + ImGui::Text("%s", T(TKEY("controller_offset_settings"), "Controller Offset Settings")); + ImGui::SliderFloat(T(TKEY("controller_offset_x"), "Controller Offset X"), &settings.VRMenuControllerOffsetX, -2.0f, 2.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("controller_offset_y"), "Controller Offset Y"), &settings.VRMenuControllerOffsetY, -2.0f, 2.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("controller_offset_z"), "Controller Offset Z"), &settings.VRMenuControllerOffsetZ, -2.0f, 2.0f, "%.2f"); } if (settings.attachMode == AttachMode::HMDOnly || settings.attachMode == AttachMode::Both) { ImGui::Separator(); - ImGui::Text("HMD Offset Settings"); - ImGui::SliderFloat("HMD Offset X", &settings.VRMenuOffsetX, -2.0f, 2.0f, "%.2f"); - ImGui::SliderFloat("HMD Offset Y", &settings.VRMenuOffsetY, -2.0f, 2.0f, "%.2f"); - ImGui::SliderFloat("HMD Offset Z", &settings.VRMenuOffsetZ, -4.0f, 1.0f, "%.2f"); + ImGui::Text("%s", T(TKEY("hmd_offset_settings"), "HMD Offset Settings")); + ImGui::SliderFloat(T(TKEY("hmd_offset_x"), "HMD Offset X"), &settings.VRMenuOffsetX, -2.0f, 2.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("hmd_offset_y"), "HMD Offset Y"), &settings.VRMenuOffsetY, -2.0f, 2.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("hmd_offset_z"), "HMD Offset Z"), &settings.VRMenuOffsetZ, -4.0f, 1.0f, "%.2f"); } if (settings.VRMenuPositioningMethod == 1) { ImGui::Separator(); - ImGui::Text("Fixed World Position Settings"); - ImGui::SliderFloat("Auto Reset Distance (game units)", &settings.VRMenuAutoResetDistance, 100.0f, 5000.0f, "%.0f"); + ImGui::Text("%s", T(TKEY("fixed_world_pos_settings"), "Fixed World Position Settings")); + ImGui::SliderFloat(T(TKEY("auto_reset_distance"), "Auto Reset Distance (game units)"), &settings.VRMenuAutoResetDistance, 100.0f, 5000.0f, "%.0f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("If you move farther than this distance from the menu, it will automatically reset to your HMD position. %s", Util::Units::FormatDistance(settings.VRMenuAutoResetDistance).c_str()); + ImGui::Text(T(TKEY("auto_reset_distance_tooltip"), "If you move farther than this distance from the menu, it will automatically reset to your HMD position. %s"), Util::Units::FormatDistance(settings.VRMenuAutoResetDistance).c_str()); } - if (ImGui::Button("Reset Menu to HMD Position")) { + if (ImGui::Button(T(TKEY("reset_menu_to_hmd"), "Reset Menu to HMD Position"))) { vr.SetFixedOverlayToCurrentHMD(); } } @@ -501,22 +529,22 @@ namespace if (!vr.IsOpenVRCompatible()) return; VR::Settings& settings = vr.settings; - if (ImGui::CollapsingHeader("Input Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::Checkbox("Enable Wand Pointing", &settings.EnableWandPointing)) { + if (ImGui::CollapsingHeader(T(TKEY("input_settings_header"), "Input Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::Checkbox(T(TKEY("enable_wand_pointing"), "Enable Wand Pointing"), &settings.EnableWandPointing)) { vr.wandState.isIntersecting = false; } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Use controller ray-casting to point at UI elements"); + ImGui::Text("%s", T(TKEY("enable_wand_pointing_tooltip"), "Use controller ray-casting to point at UI elements")); } ImGui::Separator(); - ImGui::Text("Joystick Settings"); - ImGui::SliderFloat("Mouse Deadzone", &settings.mouseDeadzone, 0.0f, 1.0f, "%.2f"); + ImGui::Text("%s", T(TKEY("joystick_settings"), "Joystick Settings")); + ImGui::SliderFloat(T(TKEY("mouse_deadzone"), "Mouse Deadzone"), &settings.mouseDeadzone, 0.0f, 1.0f, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Thumbstick deadzone for joystick cursor movement"); + ImGui::Text("%s", T(TKEY("mouse_deadzone_tooltip"), "Thumbstick deadzone for joystick cursor movement")); } - ImGui::SliderFloat("Mouse Speed", &settings.mouseSpeed, 0.1f, 50.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("mouse_speed"), "Mouse Speed"), &settings.mouseSpeed, 0.1f, 50.0f, "%.2f"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Speed multiplier for joystick cursor movement"); + ImGui::Text("%s", T(TKEY("mouse_speed_tooltip"), "Speed multiplier for joystick cursor movement")); } } } @@ -527,24 +555,24 @@ namespace if (!vr.IsOpenVRCompatible()) return; VR::Settings& settings = vr.settings; - if (ImGui::CollapsingHeader("Drag Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::CollapsingHeader("Drag Instructions", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Overlay Positioning (Grip + Drag):"); - ImGui::BulletText("Fixed World Position: Any controller can drag (HMD-only mode) or attached controller only (Both modes)"); - ImGui::BulletText("HMD Relative: Any controller can drag (HMD-only mode) or attached controller only (Both modes)"); - ImGui::BulletText("Controller Attached: Only the opposite hand can drag the controller overlay"); + if (ImGui::CollapsingHeader(T(TKEY("drag_settings_header"), "Drag Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(T(TKEY("drag_instructions_header"), "Drag Instructions"), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::TextWrapped("%s", T(TKEY("drag_overlay_positioning"), "Overlay Positioning (Grip + Drag):")); + ImGui::BulletText("%s", T(TKEY("drag_fixed_world"), "Fixed World Position: Any controller can drag (HMD-only mode) or attached controller only (Both modes)")); + ImGui::BulletText("%s", T(TKEY("drag_hmd_relative"), "HMD Relative: Any controller can drag (HMD-only mode) or attached controller only (Both modes)")); + ImGui::BulletText("%s", T(TKEY("drag_controller_attached"), "Controller Attached: Only the opposite hand can drag the controller overlay")); ImGui::Spacing(); - ImGui::TextWrapped("Depth Adjustment (Grip + Thumbstick):"); - ImGui::BulletText("While gripping to drag, use the thumbstick on the same hand to adjust depth"); - ImGui::BulletText("Thumbstick forward: Push overlay farther away"); - ImGui::BulletText("Thumbstick back: Pull overlay closer"); + ImGui::TextWrapped("%s", T(TKEY("drag_depth_adjustment"), "Depth Adjustment (Grip + Thumbstick):")); + ImGui::BulletText("%s", T(TKEY("drag_depth_thumbstick"), "While gripping to drag, use the thumbstick on the same hand to adjust depth")); + ImGui::BulletText("%s", T(TKEY("drag_thumbstick_forward"), "Thumbstick forward: Push overlay farther away")); + ImGui::BulletText("%s", T(TKEY("drag_thumbstick_back"), "Thumbstick back: Pull overlay closer")); } - ImGui::Checkbox("Enable drag to reposition overlays", &settings.EnableDragToReposition); + ImGui::Checkbox(T(TKEY("enable_drag_reposition"), "Enable drag to reposition overlays"), &settings.EnableDragToReposition); ImGui::BeginDisabled(!settings.EnableDragToReposition); - ImGui::ColorEdit4("Drag Highlight Color", settings.dragHighlightColor.data()); + ImGui::ColorEdit4(T(TKEY("drag_highlight_color"), "Drag Highlight Color"), settings.dragHighlightColor.data()); ImGui::EndDisabled(); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Color used to highlight draggable overlays in VR."); + ImGui::Text("%s", T(TKEY("drag_highlight_color_tooltip"), "Color used to highlight draggable overlays in VR.")); } } } @@ -554,28 +582,28 @@ namespace auto& vr = globals::features::vr; auto& settings = vr.settings; - if (ImGui::CollapsingHeader("Combo Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("Combo Timeout", &settings.comboTimeout, 1.0f, 10.0f, "%.1f seconds"); + if (ImGui::CollapsingHeader(T(TKEY("combo_settings_header"), "Combo Settings"), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderFloat(T(TKEY("combo_timeout"), "Combo Timeout"), &settings.comboTimeout, 1.0f, 10.0f, "%.1f seconds"); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Time limit for recording button combinations."); + ImGui::Text("%s", T(TKEY("combo_timeout_tooltip"), "Time limit for recording button combinations.")); } } ImGui::Separator(); const char* comboTypes[] = { - "Open the Open Shaders Menu", - "Close the Open Shaders Menu", - "Open VR Overlay", - "Close VR Overlay" + T(TKEY("combo_type_open_menu"), "Open the Open Shaders Menu"), + T(TKEY("combo_type_close_menu"), "Close the Open Shaders Menu"), + T(TKEY("combo_type_open_overlay"), "Open VR Overlay"), + T(TKEY("combo_type_close_overlay"), "Close VR Overlay") }; static int selectedComboIndex = 0; - ImGui::Text("Select Combo to Record:"); + ImGui::Text("%s", T(TKEY("select_combo_to_record"), "Select Combo to Record:")); ImGui::SameLine(); if (ImGui::Combo("##ComboSelector", &selectedComboIndex, comboTypes, IM_ARRAYSIZE(comboTypes))) { vr.isCapturingCombo = false; vr.currentComboType = VR::ComboType::None; vr.recordedCombo.clear(); } - if (ImGui::Button("Record Selected Combo")) { + if (ImGui::Button(T(TKEY("record_selected_combo"), "Record Selected Combo"))) { vr.isCapturingCombo = true; vr.currentComboType = static_cast(selectedComboIndex + 1); vr.currentComboName = comboTypes[selectedComboIndex]; @@ -584,7 +612,7 @@ namespace vr.recordingButtonControllers.clear(); } ImGui::SameLine(); - if (ImGui::SmallButton("Clear")) { + if (ImGui::SmallButton(T(TKEY("clear_button"), "Clear"))) { switch (selectedComboIndex) { case 0: settings.VRMenuOpenKeys.clear(); @@ -601,15 +629,15 @@ namespace } } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Click to start recording a new button combination for the selected action."); + ImGui::Text("%s", T(TKEY("record_combo_tooltip"), "Click to start recording a new button combination for the selected action.")); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); if (ImGui::BeginTable("##VRBindingsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Action"); - ImGui::TableSetupColumn("Current Binding"); - ImGui::TableSetupColumn("Description"); + ImGui::TableSetupColumn(T(TKEY("bindings_col_action"), "Action")); + ImGui::TableSetupColumn(T(TKEY("bindings_col_current"), "Current Binding")); + ImGui::TableSetupColumn(T(TKEY("bindings_col_description"), "Description")); ImGui::TableHeadersRow(); struct VRKeyBindingConfig { @@ -619,10 +647,10 @@ namespace const char* controllerRequirement; }; std::vector keyBindingConfigs = { - { "Open the Open Shaders Menu", settings.VRMenuOpenKeys, "Button combination to open the Open Shaders menu", "Primary" }, - { "Close the Open Shaders Menu", settings.VRMenuCloseKeys, "Button combination to close the Open Shaders menu", "Both" }, - { "Open VR Overlay", settings.VROverlayOpenKeys, "Button combination to open the VR overlay", "Primary" }, - { "Close VR Overlay", settings.VROverlayCloseKeys, "Button combination to close the VR overlay", "Secondary" } + { T(TKEY("combo_type_open_menu"), "Open the Open Shaders Menu"), settings.VRMenuOpenKeys, T(TKEY("binding_desc_open_menu"), "Button combination to open the Open Shaders menu"), "Primary" }, + { T(TKEY("combo_type_close_menu"), "Close the Open Shaders Menu"), settings.VRMenuCloseKeys, T(TKEY("binding_desc_close_menu"), "Button combination to close the Open Shaders menu"), "Both" }, + { T(TKEY("combo_type_open_overlay"), "Open VR Overlay"), settings.VROverlayOpenKeys, T(TKEY("binding_desc_open_overlay"), "Button combination to open the VR overlay"), "Primary" }, + { T(TKEY("combo_type_close_overlay"), "Close VR Overlay"), settings.VROverlayCloseKeys, T(TKEY("binding_desc_close_overlay"), "Button combination to close the VR overlay"), "Secondary" } }; for (size_t row = 0; row < keyBindingConfigs.size(); ++row) { const auto& config = keyBindingConfigs[row]; @@ -648,7 +676,7 @@ namespace ImGui::EndTable(); } ImGui::Spacing(); - if (ImGui::Button("Reset to Defaults")) { + if (ImGui::Button(T(TKEY("reset_to_defaults"), "Reset to Defaults"))) { VR::Settings defaults; settings.VRMenuOpenKeys = defaults.VRMenuOpenKeys; settings.VRMenuCloseKeys = defaults.VRMenuCloseKeys; @@ -656,7 +684,7 @@ namespace settings.VROverlayCloseKeys = defaults.VROverlayCloseKeys; } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Reset all VR key bindings to their default values."); + ImGui::Text("%s", T(TKEY("reset_to_defaults_tooltip"), "Reset all VR key bindings to their default values.")); } } @@ -720,7 +748,7 @@ namespace ImGui::Dummy(padSize); ImGui::SetNextItemWidth(160.0f); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetTextLineHeight()); - ImGui::Text("X: %+1.3f Y: %+1.3f [%s]", x, y, RE::GetQuadrantName(x, y)); + ImGui::Text(T(TKEY("thumbstick_xy_quadrant"), "X: %+1.3f Y: %+1.3f [%s]"), x, y, RE::GetQuadrantName(x, y)); } void DrawDebugSection() @@ -729,40 +757,40 @@ namespace auto& settings = vr.settings; auto menu = globals::menu; - if (ImGui::CollapsingHeader("OpenVR Information", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(T(TKEY("openvr_info_header"), "OpenVR Information"), ImGuiTreeNodeFlags_DefaultOpen)) { auto& info = vr.openVRInfo; if (info.isAvailable) { if (vr.IsOpenVRCompatible()) { - ImGui::Text("OpenVR System: Active & Compatible"); + ImGui::Text("%s", T(TKEY("openvr_active_compatible"), "OpenVR System: Active & Compatible")); } else { - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "OpenVR System: Active but INCOMPATIBLE"); - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "VR overlay menus disabled."); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "%s", T(TKEY("openvr_active_incompatible"), "OpenVR System: Active but INCOMPATIBLE")); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "%s", T(TKEY("openvr_menus_disabled"), "VR overlay menus disabled.")); } - ImGui::Text("Runtime: %s", VRDetection::RuntimeTypeToString(info.runtimeType)); - ImGui::Text("DLL Path: %s", info.dllPath.c_str()); - ImGui::Text("DLL Version: %s", info.version.c_str()); - ImGui::Text("DLL Size: %llu bytes", info.fileSize); - ImGui::Text("Modified: %s", info.modificationTime.c_str()); + ImGui::Text(T(TKEY("openvr_runtime"), "Runtime: %s"), VRDetection::RuntimeTypeToString(info.runtimeType)); + ImGui::Text(T(TKEY("openvr_dll_path"), "DLL Path: %s"), info.dllPath.c_str()); + ImGui::Text(T(TKEY("openvr_dll_version"), "DLL Version: %s"), info.version.c_str()); + ImGui::Text(T(TKEY("openvr_dll_size"), "DLL Size: %llu bytes"), info.fileSize); + ImGui::Text(T(TKEY("openvr_modified"), "Modified: %s"), info.modificationTime.c_str()); ImGui::Separator(); - ImGui::Text("Detection Method:"); - ImGui::Text(" Interface Probing: %s", info.probingSucceeded ? "Passed" : "Failed"); - ImGui::Text(" IVROverlay_016: %s", info.hasOverlayInterface ? "OK" : "Missing"); - ImGui::Text(" IVRSystem_017: %s", info.hasSystemInterface ? "OK" : "Missing"); - ImGui::Text(" IVRCompositor_021: %s", info.hasCompositorInterface ? "OK" : "Missing"); - ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), " Rendering: In-scene overlay (submit hook)"); + ImGui::Text("%s", T(TKEY("openvr_detection_method"), "Detection Method:")); + ImGui::Text(T(TKEY("openvr_interface_probing"), " Interface Probing: %s"), info.probingSucceeded ? T(TKEY("openvr_passed"), "Passed") : T(TKEY("openvr_failed"), "Failed")); + ImGui::Text(T(TKEY("openvr_ivroverlay"), " IVROverlay_016: %s"), info.hasOverlayInterface ? T(TKEY("openvr_ok"), "OK") : T(TKEY("openvr_missing"), "Missing")); + ImGui::Text(T(TKEY("openvr_ivrsystem"), " IVRSystem_017: %s"), info.hasSystemInterface ? T(TKEY("openvr_ok"), "OK") : T(TKEY("openvr_missing"), "Missing")); + ImGui::Text(T(TKEY("openvr_ivrcompositor"), " IVRCompositor_021: %s"), info.hasCompositorInterface ? T(TKEY("openvr_ok"), "OK") : T(TKEY("openvr_missing"), "Missing")); + ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "%s", T(TKEY("openvr_rendering"), " Rendering: In-scene overlay (submit hook)")); } else { - ImGui::Text("OpenVR system not available"); + ImGui::Text("%s", T(TKEY("openvr_not_available"), "OpenVR system not available")); } } - if (ImGui::CollapsingHeader("Controller Diagnostics", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::Checkbox("Test Mode: Disable controller menu input (except scroll controller and triggers)", &settings.VRMenuControllerDiagnosticsTestMode)) { + if (ImGui::CollapsingHeader(T(TKEY("controller_diagnostics_header"), "Controller Diagnostics"), ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::Checkbox(T(TKEY("diagnostics_test_mode"), "Test Mode: Disable controller menu input (except scroll controller and triggers)"), &settings.VRMenuControllerDiagnosticsTestMode)) { ImGui::SetScrollHereY(0.0f); } - ImGui::SeparatorText("Button State"); + ImGui::SeparatorText(T(TKEY("diagnostics_button_state"), "Button State")); double nowSecs = Util::GetNowSecs(); ImVec4 highlightColor = menu->GetTheme().StatusPalette.InfoColor; ImU32 highlightColorU32 = ImGui::ColorConvertFloat4ToU32(highlightColor); @@ -770,34 +798,34 @@ namespace bool isLeftHanded = vr.lastKnownLeftHandedMode; if (ImGui::BeginTable("vr_input_state_table", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Button"); + ImGui::TableSetupColumn(T(TKEY("diag_col_button"), "Button")); if (isLeftHanded) { - ImGui::TableSetupColumn("Primary State"); - ImGui::TableSetupColumn("Primary Held (s)"); - ImGui::TableSetupColumn("Primary Type"); - ImGui::TableSetupColumn("Secondary State"); - ImGui::TableSetupColumn("Secondary Held (s)"); - ImGui::TableSetupColumn("Secondary Type"); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_state"), "Primary State")); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_held"), "Primary Held (s)")); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_type"), "Primary Type")); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_state"), "Secondary State")); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_held"), "Secondary Held (s)")); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_type"), "Secondary Type")); } else { - ImGui::TableSetupColumn("Secondary State"); - ImGui::TableSetupColumn("Secondary Held (s)"); - ImGui::TableSetupColumn("Secondary Type"); - ImGui::TableSetupColumn("Primary State"); - ImGui::TableSetupColumn("Primary Held (s)"); - ImGui::TableSetupColumn("Primary Type"); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_state"), "Secondary State")); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_held"), "Secondary Held (s)")); + ImGui::TableSetupColumn(T(TKEY("diag_col_secondary_type"), "Secondary Type")); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_state"), "Primary State")); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_held"), "Primary Held (s)")); + ImGui::TableSetupColumn(T(TKEY("diag_col_primary_type"), "Primary Type")); } ImGui::TableHeadersRow(); auto DrawButtonType = [](const RE::ButtonState& state) { if (!state.isPressed) { if (state.IsClick()) - ImGui::TextUnformatted("Click"); + ImGui::TextUnformatted(T(TKEY("diag_type_click"), "Click")); else if (state.IsHold()) - ImGui::TextUnformatted("Hold"); + ImGui::TextUnformatted(T(TKEY("diag_type_hold"), "Hold")); else - ImGui::TextUnformatted("-"); + ImGui::TextUnformatted(T(TKEY("diag_type_none"), "-")); } else { - ImGui::TextUnformatted("Held"); + ImGui::TextUnformatted(T(TKEY("diag_type_held"), "Held")); } }; @@ -808,7 +836,7 @@ namespace ImGui::TableSetColumnIndex(1); if (left.isPressed) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, highlightColorU32); - ImGui::TextUnformatted(left.isPressed ? "Pressed" : "Released"); + ImGui::TextUnformatted(left.isPressed ? T(TKEY("diag_pressed"), "Pressed") : T(TKEY("diag_released"), "Released")); ImGui::TableSetColumnIndex(2); if (left.isPressed) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, highlightColorU32); @@ -820,7 +848,7 @@ namespace ImGui::TableSetColumnIndex(4); if (right.isPressed) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, highlightColorU32); - ImGui::TextUnformatted(right.isPressed ? "Pressed" : "Released"); + ImGui::TextUnformatted(right.isPressed ? T(TKEY("diag_pressed"), "Pressed") : T(TKEY("diag_released"), "Released")); ImGui::TableSetColumnIndex(5); if (right.isPressed) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, highlightColorU32); @@ -841,26 +869,26 @@ namespace } }; - printRowWithHandedness("Trigger", RE::BSOpenVRControllerDevice::Keys::kTrigger); - printRowWithHandedness("Grip", RE::BSOpenVRControllerDevice::Keys::kGrip); - printRowWithHandedness("GripAlt", RE::BSOpenVRControllerDevice::Keys::kGripAlt); - printRowWithHandedness("Stick Click", RE::BSOpenVRControllerDevice::Keys::kJoystickTrigger); - printRowWithHandedness("Touchpad Click", RE::BSOpenVRControllerDevice::Keys::kTouchpadClick); - printRowWithHandedness("Touchpad Alt", RE::BSOpenVRControllerDevice::Keys::kTouchpadAlt); - printRowWithHandedness("B/Y", RE::BSOpenVRControllerDevice::Keys::kBY); - printRowWithHandedness("A/X", RE::BSOpenVRControllerDevice::Keys::kXA); + printRowWithHandedness(T(TKEY("diag_btn_trigger"), "Trigger"), RE::BSOpenVRControllerDevice::Keys::kTrigger); + printRowWithHandedness(T(TKEY("diag_btn_grip"), "Grip"), RE::BSOpenVRControllerDevice::Keys::kGrip); + printRowWithHandedness(T(TKEY("diag_btn_grip_alt"), "GripAlt"), RE::BSOpenVRControllerDevice::Keys::kGripAlt); + printRowWithHandedness(T(TKEY("diag_btn_stick_click"), "Stick Click"), RE::BSOpenVRControllerDevice::Keys::kJoystickTrigger); + printRowWithHandedness(T(TKEY("diag_btn_touchpad_click"), "Touchpad Click"), RE::BSOpenVRControllerDevice::Keys::kTouchpadClick); + printRowWithHandedness(T(TKEY("diag_btn_touchpad_alt"), "Touchpad Alt"), RE::BSOpenVRControllerDevice::Keys::kTouchpadAlt); + printRowWithHandedness(T(TKEY("diag_btn_by"), "B/Y"), RE::BSOpenVRControllerDevice::Keys::kBY); + printRowWithHandedness(T(TKEY("diag_btn_ax"), "A/X"), RE::BSOpenVRControllerDevice::Keys::kXA); ImGui::EndTable(); } - ImGui::SeparatorText("VR Thumbstick State"); + ImGui::SeparatorText(T(TKEY("thumbstick_state_section"), "VR Thumbstick State")); ImU32 highlightCol = ImGui::ColorConvertFloat4ToU32(menu->GetTheme().StatusPalette.InfoColor); if (ImGui::BeginTable("##VRThumbstickTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) { if (isLeftHanded) { - ImGui::TableSetupColumn("Primary Controller", ImGuiTableColumnFlags_WidthFixed, 200.0f); - ImGui::TableSetupColumn("Secondary Controller", ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn(T(TKEY("thumbstick_col_primary"), "Primary Controller"), ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn(T(TKEY("thumbstick_col_secondary"), "Secondary Controller"), ImGuiTableColumnFlags_WidthFixed, 200.0f); } else { - ImGui::TableSetupColumn("Secondary Controller", ImGuiTableColumnFlags_WidthFixed, 200.0f); - ImGui::TableSetupColumn("Primary Controller", ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn(T(TKEY("thumbstick_col_secondary"), "Secondary Controller"), ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn(T(TKEY("thumbstick_col_primary"), "Primary Controller"), ImGuiTableColumnFlags_WidthFixed, 200.0f); } ImGui::TableHeadersRow(); @@ -878,15 +906,15 @@ namespace ImGui::EndTable(); } - ImGui::SeparatorText("Recent VR Controller Events"); - ImGui::TextDisabled("Note: For thumbstick events, KeyCode/Value columns show X/Y floats."); + ImGui::SeparatorText(T(TKEY("events_section"), "Recent VR Controller Events")); + ImGui::TextDisabled("%s", T(TKEY("events_note"), "Note: For thumbstick events, KeyCode/Value columns show X/Y floats.")); if (ImGui::BeginTable("eventlog", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Device", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("KeyCode/X", ImGuiTableColumnFlags_WidthFixed, 80.0f); - ImGui::TableSetupColumn("Value/Y", ImGuiTableColumnFlags_WidthFixed, 80.0f); - ImGui::TableSetupColumn("Pressed", ImGuiTableColumnFlags_WidthFixed, 70.0f); - ImGui::TableSetupColumn("Known Mapping", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Event Type", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_device"), "Device"), ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_keycode_x"), "KeyCode/X"), ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_value_y"), "Value/Y"), ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_pressed"), "Pressed"), ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_known_mapping"), "Known Mapping"), ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn(T(TKEY("events_col_event_type"), "Event Type"), ImGuiTableColumnFlags_WidthFixed, 120.0f); ImGui::TableHeadersRow(); for (const auto& e : vr.vrControllerEventLog) { ImGui::TableNextRow(); @@ -905,7 +933,7 @@ namespace ImGui::Text("%d", e.value); } ImGui::TableSetColumnIndex(3); - ImGui::Text("%s", e.pressed ? "Pressed" : "Released"); + ImGui::Text("%s", e.pressed ? T(TKEY("diag_pressed"), "Pressed") : T(TKEY("diag_released"), "Released")); ImGui::TableSetColumnIndex(4); if (e.heldSource == "thumbstick") { ImGui::TextUnformatted(e.controllerRole.c_str()); @@ -914,23 +942,23 @@ namespace } ImGui::TableSetColumnIndex(5); if (e.heldSource == "thumbstick") { - ImGui::TextUnformatted("-"); + ImGui::TextUnformatted(T(TKEY("events_type_none"), "-")); } else { if (!e.pressed) { if (e.heldTime > 0.0) { if (e.heldTime < 0.5) { - ImGui::Text("Click (%.2fs)", e.heldTime); + ImGui::Text(T(TKEY("events_type_click"), "Click (%.2fs)"), e.heldTime); } else { - ImGui::Text("Hold (%.2fs)", e.heldTime); + ImGui::Text(T(TKEY("events_type_hold"), "Hold (%.2fs)"), e.heldTime); } } else { - ImGui::Text("Release"); + ImGui::Text("%s", T(TKEY("events_type_release"), "Release")); } } else if (e.pressed) { if (e.heldTime > 0.0) { - ImGui::Text("Held for %.2fs", e.heldTime); + ImGui::Text(T(TKEY("events_type_held_for"), "Held for %.2fs"), e.heldTime); } else { - ImGui::Text("Press"); + ImGui::Text("%s", T(TKEY("events_type_press"), "Press")); } } } @@ -938,49 +966,49 @@ namespace ImGui::EndTable(); } - ImGui::SeparatorText("Wand Pointing State"); + ImGui::SeparatorText(T(TKEY("wand_state_section"), "Wand Pointing State")); if (ImGui::BeginTable("##WandPointingState", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 200.0f); - ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(T(TKEY("wand_col_property"), "Property"), ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn(T(TKEY("wand_col_value"), "Value"), ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Wand Pointing Enabled"); + ImGui::Text("%s", T(TKEY("wand_pointing_enabled"), "Wand Pointing Enabled")); ImGui::TableSetColumnIndex(1); - ImGui::Text("%s", settings.EnableWandPointing ? "Yes" : "No"); + ImGui::Text("%s", settings.EnableWandPointing ? T(TKEY("wand_yes"), "Yes") : T(TKEY("wand_no"), "No")); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Intersecting Overlay"); + ImGui::Text("%s", T(TKEY("wand_intersecting_overlay"), "Intersecting Overlay")); ImGui::TableSetColumnIndex(1); if (vr.wandState.isIntersecting) { - ImGui::TextColored(menu->GetTheme().StatusPalette.InfoColor, "YES"); + ImGui::TextColored(menu->GetTheme().StatusPalette.InfoColor, "%s", T(TKEY("wand_yes_upper"), "YES")); } else { - ImGui::Text("No"); + ImGui::Text("%s", T(TKEY("wand_no"), "No")); } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("UV Coordinates"); + ImGui::Text("%s", T(TKEY("wand_uv_coordinates"), "UV Coordinates")); ImGui::TableSetColumnIndex(1); ImGui::Text("(%.3f, %.3f)", vr.wandState.uvCoordinates.x, vr.wandState.uvCoordinates.y); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Controller Index"); + ImGui::Text("%s", T(TKEY("wand_controller_index"), "Controller Index")); ImGui::TableSetColumnIndex(1); ImGui::Text("%u", vr.wandState.controllerIndex); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Ray Origin"); + ImGui::Text("%s", T(TKEY("wand_ray_origin"), "Ray Origin")); ImGui::TableSetColumnIndex(1); ImGui::Text("(%.2f, %.2f, %.2f)", vr.wandState.rayOrigin.x, vr.wandState.rayOrigin.y, vr.wandState.rayOrigin.z); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("Ray Direction"); + ImGui::Text("%s", T(TKEY("wand_ray_direction"), "Ray Direction")); ImGui::TableSetColumnIndex(1); ImGui::Text("(%.2f, %.2f, %.2f)", vr.wandState.rayDirection.x, vr.wandState.rayDirection.y, vr.wandState.rayDirection.z); @@ -988,7 +1016,7 @@ namespace } } - if (ImGui::CollapsingHeader("OpenVR Addresses")) { + if (ImGui::CollapsingHeader(T(TKEY("openvr_addresses_header"), "OpenVR Addresses"))) { auto openvr = RE::BSOpenVR::GetSingleton(); auto overlay = openvr ? RE::BSOpenVR::GetIVROverlayFromContext(&openvr->vrContext) : nullptr; auto vrSystem = openvr ? openvr->vrSystem : nullptr; @@ -1009,7 +1037,7 @@ void VR::DrawSettings() if (!menu) return; if (ImGui::BeginTabBar("##VRTabs", ImGuiTabBarFlags_None)) { - if (BeginTabItemWithFont("General", Menu::FontRole::Subheading)) { + if (BeginTabItemWithFont(T(TKEY("tab_general"), "General"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##VRGeneralFrame", { 0, 0 }, true)) { DrawGeneralVRSettings(); DrawControllerInputInstructions(); @@ -1021,7 +1049,7 @@ void VR::DrawSettings() ImGui::EndTabItem(); } - if (BeginTabItemWithFont("Stereo", Menu::FontRole::Subheading)) { + if (BeginTabItemWithFont(T(TKEY("tab_stereo"), "Stereo"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##VRStereoFrame", { 0, 0 }, true)) { DrawStereoSettings(); } @@ -1030,7 +1058,7 @@ void VR::DrawSettings() } if (IsOpenVRCompatible()) { - if (BeginTabItemWithFont("Bindings", Menu::FontRole::Subheading)) { + if (BeginTabItemWithFont(T(TKEY("tab_bindings"), "Bindings"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##VRBindingsFrame", { 0, 0 }, true)) { DrawKeyBindings(); } @@ -1039,7 +1067,7 @@ void VR::DrawSettings() } } - if (BeginTabItemWithFont("Debug", Menu::FontRole::Subheading)) { + if (BeginTabItemWithFont(T(TKEY("tab_debug"), "Debug"), Menu::FontRole::Subheading)) { if (ImGui::BeginChild("##VRDebugFrame", { 0, 0 }, true)) { DrawDebugSection(); } @@ -1073,10 +1101,10 @@ void VR::DrawSettings() } }; - ImGui::Text("Recording combo for: %s", this->currentComboName ? this->currentComboName : "Unknown"); + ImGui::Text(T(TKEY("popup_recording_for"), "Recording combo for: %s"), this->currentComboName ? this->currentComboName : T(TKEY("popup_unknown"), "Unknown")); ImGui::Spacing(); - ImGui::TextDisabled("(During recording, any controller's buttons can be used. Requirement is only enforced during use.)"); + ImGui::TextDisabled("%s", T(TKEY("popup_recording_note"), "(During recording, any controller's buttons can be used. Requirement is only enforced during use.)")); ImGui::Spacing(); @@ -1084,14 +1112,14 @@ void VR::DrawSettings() ImVec4 timerColor = remainingTime > 2.0 ? Util::Colors::GetTimerGood() : remainingTime > 1.0 ? Util::Colors::GetTimerWarning() : Util::Colors::GetTimerCritical(); - ImGui::TextColored(timerColor, "Time remaining: %.1f seconds", remainingTime); + ImGui::TextColored(timerColor, T(TKEY("popup_time_remaining"), "Time remaining: %.1f seconds"), remainingTime); ImGui::Spacing(); if (this->recordedCombo.empty()) { - ImGui::Text("Press buttons to record combo..."); + ImGui::Text("%s", T(TKEY("popup_press_buttons"), "Press buttons to record combo...")); } else { - ImGui::Text("Recorded buttons:"); + ImGui::Text("%s", T(TKEY("popup_recorded_buttons"), "Recorded buttons:")); std::vector sortedRecordedCombos; for (size_t i = 0; i < this->recordedCombo.size(); ++i) { sortedRecordedCombos.push_back(this->recordedCombo[i]); @@ -1108,7 +1136,7 @@ void VR::DrawSettings() ImGui::Separator(); ImGui::Spacing(); - ImGui::Text("Press ENTER to accept, ESC to cancel"); + ImGui::Text("%s", T(TKEY("popup_enter_esc"), "Press ENTER to accept, ESC to cancel")); // Handle button recording bool buttonPressed = false; @@ -1169,3 +1197,5 @@ void VR::DrawSettings() } } } + +#undef I18N_KEY_PREFIX diff --git a/src/Features/VolumetricLighting.cpp b/src/Features/VolumetricLighting.cpp index 0b3e50414e..a6c26d7401 100644 --- a/src/Features/VolumetricLighting.cpp +++ b/src/Features/VolumetricLighting.cpp @@ -40,7 +40,7 @@ void VolumetricLighting::DrawSettings() SetupVL(); if (globals::game::isVR) Util::UI::RestartGatedAnnotate(bootSnapshot, settings, &Settings::InteriorEnabled, - "Volumetric god-rays / fog scattering in interior cells."); + T(TKEY("enable_interiors_tooltip"), "Volumetric god-rays / fog scattering in interior cells.")); if (settings.InteriorEnabled) DrawVolumetricLightingSettings(settings.InteriorQuality, settings.InteriorCustomSize, true, inInterior); diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index f634439a3e..8a3f80b7cb 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -678,16 +678,16 @@ static void DrawRainTypeLabel(const char* prefix, float rate) { FLT_MAX, ImVec4(1.0f, 0.2f, 0.2f, 1.0f) } // Extreme (Red) }; if (rate < 2.5f) { - label = "Light Rain"; + label = T(TKEY("rain_type_light"), "Light Rain"); valueColor = config.thresholds[0].color; } else if (rate < 7.5f) { - label = "Moderate Rain"; + label = T(TKEY("rain_type_moderate"), "Moderate Rain"); valueColor = config.thresholds[1].color; } else if (rate < 15.0f) { - label = "Heavy Rain"; + label = T(TKEY("rain_type_heavy"), "Heavy Rain"); valueColor = config.thresholds[2].color; } else { - label = "Extreme Rain"; + label = T(TKEY("rain_type_extreme"), "Extreme Rain"); valueColor = config.thresholds[3].color; } // Print prefix (uncolored), then value (colored), then meteorological label (colored, after value) @@ -697,11 +697,11 @@ static void DrawRainTypeLabel(const char* prefix, float rate) ImGui::SameLine(); ImGui::TextColored(valueColor, "(%s)", label); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Meteorological rain types:"); - ImGui::BulletText("Light: <2.5 mm/hr"); - ImGui::BulletText("Moderate: 2.5 - 7.5 mm/hr"); - ImGui::BulletText("Heavy: 7.5 - 15 mm/hr"); - ImGui::BulletText("Extreme: >15 mm/hr"); + ImGui::Text("%s", T(TKEY("meteorological_rain_types"), "Meteorological rain types:")); + ImGui::BulletText("%s", T(TKEY("rain_type_light_range"), "Light: <2.5 mm/hr")); + ImGui::BulletText("%s", T(TKEY("rain_type_moderate_range"), "Moderate: 2.5 - 7.5 mm/hr")); + ImGui::BulletText("%s", T(TKEY("rain_type_heavy_range"), "Heavy: 7.5 - 15 mm/hr")); + ImGui::BulletText("%s", T(TKEY("rain_type_extreme_range"), "Extreme: >15 mm/hr")); } } @@ -1015,29 +1015,29 @@ void WetnessEffects::DrawWeatherAnalysis() const // const auto& climate = GetClimateSettings(climatePreset); // Unused, remove to fix warning treated as error const auto& presetInfo = CLIMATE_PRESET_INFO[static_cast(climatePreset)]; - ImGui::Text("Active Preset: %s", presetInfo.name); + ImGui::Text(T(TKEY("active_preset_format"), "Active Preset: %s"), presetInfo.name); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", presetInfo.shortDescription); } - ImGui::Text("Precipitation Rate Calculation"); + ImGui::Text("%s", T(TKEY("precipitation_rate_calculation"), "Precipitation Rate Calculation")); if (auto _tt = Util::HoverTooltipWrapper()) { - Util::DrawMultiLineTooltip({ "Precipitation rates are calculated using shader mechanics:", - "- Raindrop chance (probability per interval)", - "- Grid size (spatial density)", - "- Interval (time between attempts)", - "- All values reflect what is sent to the shader.", - "Rates are shown in mm/hr, based on drops/sec and grid size." }); + Util::DrawMultiLineTooltip({ T(TKEY("precip_calc_tooltip_0"), "Precipitation rates are calculated using shader mechanics:"), + T(TKEY("precip_calc_tooltip_1"), "- Raindrop chance (probability per interval)"), + T(TKEY("precip_calc_tooltip_2"), "- Grid size (spatial density)"), + T(TKEY("precip_calc_tooltip_3"), "- Interval (time between attempts)"), + T(TKEY("precip_calc_tooltip_4"), "- All values reflect what is sent to the shader."), + T(TKEY("precip_calc_tooltip_5"), "Rates are shown in mm/hr, based on drops/sec and grid size.") }); } // Show current preset-applied values vs defaults Settings defaultSettings{}; - ImGui::Text("Current Settings (applied from preset):"); + ImGui::Text("%s", T(TKEY("current_settings_from_preset"), "Current Settings (applied from preset):")); ImGui::Indent(); - ImGui::Text("Rain Wetness: %.2f (default %.2f × %.1fx)", settings.MaxRainWetness, defaultSettings.MaxRainWetness, presetInfo.settings.wetnessMultiplier); - ImGui::Text("Puddle Wetness: %.2f (default %.2f × %.1fx)", settings.MaxPuddleWetness, defaultSettings.MaxPuddleWetness, presetInfo.settings.puddleMultiplier); - ImGui::Text("Transition Speed: %.2f (default %.2f × %.1fx)", settings.WeatherTransitionSpeed, defaultSettings.WeatherTransitionSpeed, presetInfo.settings.transitionSpeed); - ImGui::Text("Raindrop Chance: %.1f%% (preset value)", settings.RaindropChance * 100.0f); + ImGui::Text(T(TKEY("rain_wetness_default_format"), "Rain Wetness: %.2f (default %.2f × %.1fx)"), settings.MaxRainWetness, defaultSettings.MaxRainWetness, presetInfo.settings.wetnessMultiplier); + ImGui::Text(T(TKEY("puddle_wetness_default_format"), "Puddle Wetness: %.2f (default %.2f × %.1fx)"), settings.MaxPuddleWetness, defaultSettings.MaxPuddleWetness, presetInfo.settings.puddleMultiplier); + ImGui::Text(T(TKEY("transition_speed_default_format"), "Transition Speed: %.2f (default %.2f × %.1fx)"), settings.WeatherTransitionSpeed, defaultSettings.WeatherTransitionSpeed, presetInfo.settings.transitionSpeed); + ImGui::Text(T(TKEY("raindrop_chance_preset_format"), "Raindrop Chance: %.1f%% (preset value)"), settings.RaindropChance * 100.0f); ImGui::Unindent(); } ImGui::Spacing(); @@ -1055,27 +1055,27 @@ void WetnessEffects::DrawWeatherAnalysis() const presetSettings.raindropChance, presetSettings.raindropGridSize, presetSettings.raindropInterval); if (ImGui::BeginTable("RainAnalysis", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) { - ImGui::TableSetupColumn("Current Shader State", ImGuiTableColumnFlags_WidthStretch, 0.5f); - ImGui::TableSetupColumn("Precipitation Analysis", ImGuiTableColumnFlags_WidthStretch, 0.5f); + ImGui::TableSetupColumn(T(TKEY("current_shader_state"), "Current Shader State"), ImGuiTableColumnFlags_WidthStretch, 0.5f); + ImGui::TableSetupColumn(T(TKEY("precipitation_analysis"), "Precipitation Analysis"), ImGuiTableColumnFlags_WidthStretch, 0.5f); ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); - Util::DrawColorCodedValue("Rain Intensity", frameData.Raining * 100.0f, std::format("{:.1f}%", frameData.Raining * 100.0f), Util::ColorCodedValueConfig::HighIsGood(10.0f, 50.0f, 80.0f)); - Util::DrawColorCodedValue("Wetness", frameData.Wetness * 100.0f, std::format("{:.1f}%", frameData.Wetness * 100.0f), Util::ColorCodedValueConfig::HighIsGood(25.0f, 60.0f, 85.0f)); - Util::DrawColorCodedValue("Puddle Wetness", frameData.PuddleWetness * 100.0f, std::format("{:.1f}%", frameData.PuddleWetness * 100.0f), Util::ColorCodedValueConfig::HighIsGood(15.0f, 40.0f, 70.0f)); - ImGui::Text("Puddle Formation: %.1f%% min wetness", frameData.settings.PuddleMinWetness * 100.0f); - ImGui::Text("Weather Transition: %.1f%%", sky->currentWeatherPct * 100.0f); - ImGui::Text("Raindrop Chance: %.1f%%", frameData.settings.RaindropChance * 100.0f); - ImGui::Text("Grid Size: %.2f m (%.1f units)", gridSizeMeters, gridSizeGameUnits); - ImGui::Text("Interval: %.1f sec", intervalSeconds); + Util::DrawColorCodedValue(T(TKEY("rain_intensity"), "Rain Intensity"), frameData.Raining * 100.0f, std::format("{:.1f}%", frameData.Raining * 100.0f), Util::ColorCodedValueConfig::HighIsGood(10.0f, 50.0f, 80.0f)); + Util::DrawColorCodedValue(T(TKEY("wetness"), "Wetness"), frameData.Wetness * 100.0f, std::format("{:.1f}%", frameData.Wetness * 100.0f), Util::ColorCodedValueConfig::HighIsGood(25.0f, 60.0f, 85.0f)); + Util::DrawColorCodedValue(T(TKEY("puddle_wetness"), "Puddle Wetness"), frameData.PuddleWetness * 100.0f, std::format("{:.1f}%", frameData.PuddleWetness * 100.0f), Util::ColorCodedValueConfig::HighIsGood(15.0f, 40.0f, 70.0f)); + ImGui::Text(T(TKEY("puddle_formation_format"), "Puddle Formation: %.1f%% min wetness"), frameData.settings.PuddleMinWetness * 100.0f); + ImGui::Text(T(TKEY("weather_transition_format"), "Weather Transition: %.1f%%"), sky->currentWeatherPct * 100.0f); + ImGui::Text(T(TKEY("raindrop_chance_format"), "Raindrop Chance: %.1f%%"), frameData.settings.RaindropChance * 100.0f); + ImGui::Text(T(TKEY("grid_size_format"), "Grid Size: %.2f m (%.1f units)"), gridSizeMeters, gridSizeGameUnits); + ImGui::Text(T(TKEY("interval_format"), "Interval: %.1f sec"), intervalSeconds); ImGui::TableNextColumn(); // Live (Current): - DrawRainTypeLabel("Current", actualRainRate); + DrawRainTypeLabel(T(TKEY("rain_label_current"), "Current"), actualRainRate); // Max (in Heavy Rain): - DrawRainTypeLabel("Max (in Heavy Rain)", theoreticalMaxRainRate); + DrawRainTypeLabel(T(TKEY("rain_label_max_heavy"), "Max (in Heavy Rain)"), theoreticalMaxRainRate); ImGui::EndTable(); } } From b0d9b8e4671378513093e10a7b7b85af19ed3b73 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 12:19:35 -0700 Subject: [PATCH 6/7] i18n: batch 2 reformat --- .../CommunityShaders/Translations/en.json | 768 +++++++++++++++++- src/CSEditor/EditorWindow.cpp | 5 +- src/CSEditor/InteriorOnlyPanel.cpp | 6 +- src/CSEditor/Weather/WeatherWidget.cpp | 24 +- src/Features/CSEditor.cpp | 8 +- src/Features/RemoteControl.cpp | 53 +- src/Features/RenderDoc.cpp | 9 +- src/Features/ScreenshotFeature.cpp | 8 +- src/Features/SkySync.cpp | 40 +- src/Features/TerrainShadows.cpp | 17 +- src/Features/VolumetricShadows.cpp | 15 +- src/Menu/OverlayRenderer.cpp | 28 +- src/Menu/SettingsTabRenderer.cpp | 10 +- src/Utils/Subrect.cpp | 20 +- src/Utils/UI.cpp | 48 +- src/Utils/VRUtils.cpp | 15 +- 16 files changed, 930 insertions(+), 144 deletions(-) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index ba4facffbf..3c4416db0e 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -55,8 +55,8 @@ "cs_editor.cloud_alpha": "Cloud Alpha", "cs_editor.cloud_color": "Cloud Color", "cs_editor.cloud_layer": "Cloud Layer {}", - "cs_editor.cloud_layer_speed_x": "Cloud Layer Speed X", - "cs_editor.cloud_layer_speed_y": "Cloud Layer Speed Y", + "cs_editor.cloud_layer_speed_x": "Cloud Layer Speed X##{}", + "cs_editor.cloud_layer_speed_y": "Cloud Layer Speed Y##{}", "cs_editor.color": "Color", "cs_editor.color_ambient": "Ambient", "cs_editor.color_cloud_lod_ambient": "Cloud LOD Ambient", @@ -105,6 +105,7 @@ "cs_editor.delete_all": "Delete All", "cs_editor.delete_json_file": "Delete JSON file", "cs_editor.delete_overwrite_file": "Delete overwrite file from disk", + "cs_editor.delete_overwrite_file_confirm": "Delete overwrite file '{}'?\nThis will permanently remove the file from disk.", "cs_editor.delete_saved_file": "Delete Saved File", "cs_editor.delete_saved_file_tooltip": "Delete saved file", "cs_editor.density_contribution": "Density Contribution", @@ -202,6 +203,7 @@ "cs_editor.inherit_from_parent_weather": "Inherit from parent weather", "cs_editor.inherit_light_fade_distances": "Inherit Light Fade Distances", "cs_editor.inherit_rotation": "Inherit Rotation", + "cs_editor.inherited_all_settings_from": "Inherited all settings from {}", "cs_editor.inherited_from_lighting_template": "Inherited from lighting template", "cs_editor.inherited_from_parent_weather": "Inherited from parent weather", "cs_editor.inheriting_from_parent": "Inheriting from parent", @@ -293,6 +295,7 @@ "cs_editor.record_precipitation": "Precipitation", "cs_editor.record_visual_effect": "Visual Effect", "cs_editor.record_volumetric_lighting": "Volumetric Lighting", + "cs_editor.references_missing_features": "Warning: {} references missing feature(s): {}", "cs_editor.remove": "Remove", "cs_editor.remove_from_palette": "Remove from palette", "cs_editor.remove_setting": "Remove this setting", @@ -331,6 +334,7 @@ "cs_editor.size_x": "Size X", "cs_editor.size_y": "Size Y", "cs_editor.snow": "Snow", + "cs_editor.some_values_failed_to_load": "Some values failed to load for {}", "cs_editor.start_rotation_range": "Start Rotation Range", "cs_editor.status": "Status", "cs_editor.subtextures": "Subtextures", @@ -369,6 +373,7 @@ "cs_editor.type": "Type", "cs_editor.ui_scale": "UI Scale", "cs_editor.undo_no_changes": "Undo (Ctrl+Z) - No changes to undo", + "cs_editor.undo_states": "Undo (Ctrl+Z) - {} states", "cs_editor.undone_changes_to": "Undone changes to {}", "cs_editor.unknown": "Unknown", "cs_editor.unlock": "Unlock", @@ -399,6 +404,7 @@ "cs_editor.volume": "Volume", "cs_editor.volumetric_lighting_label": "Volumetric Lighting:", "cs_editor.weather_lighting_browser": "CS Editor Browser", + "cs_editor.weather_references_unloaded_feature": "Weather '{}' contains settings for this feature, but the feature is not loaded. The weather-specific parameters will be ignored until the feature is installed and loaded.", "cs_editor.weathers_count": "Weathers: %d", "cs_editor.widget_type_cell_lighting": "Cell Lighting", "cs_editor.widget_type_imagespace": "ImageSpace", @@ -480,6 +486,7 @@ "feature.cs_editor.none_filter_tooltip_0": "Shows weathers that are not classified under any specific category.", "feature.cs_editor.none_filter_tooltip_1": "Includes weathers with no flags or only untracked flags.", "feature.cs_editor.none_filter_tooltip_2": "Categories tracked: Pleasant, Cloudy, Rainy, Snow, Aurora, Aurora Sun", + "feature.cs_editor.none_value": "None", "feature.cs_editor.open_editor": "Open CS Editor", "feature.cs_editor.particle_density": "Particle Density: %.3f", "feature.cs_editor.particle_texture": "Particle Texture: %s", @@ -530,6 +537,7 @@ "feature.cs_editor.transitioning_from": "Transitioning From: %s", "feature.cs_editor.unknown": "Unknown", "feature.cs_editor.unlock_weather": "Unlock Weather", + "feature.cs_editor.unnamed": "Unnamed", "feature.cs_editor.using_default_settings": "Using Default Settings", "feature.cs_editor.weather": "Weather", "feature.cs_editor.weather_controls": "Weather Controls", @@ -849,15 +857,124 @@ "feature.light_editor.sort_by": "Sort By", "feature.light_editor.spotlight_not_applicable": "Spotlight: ISL light type flags not applicable", "feature.light_editor.total_lights": "Total Lights: %u", + "feature.light_limit_fix.addr_click_to_copy": "Click to copy: %s", + "feature.light_limit_fix.addr_focus": "focus[%u]", + "feature.light_limit_fix.advanced": "Advanced##ShadowScheduling", + "feature.light_limit_fix.allow_immediate_draw_new_lights": "Allow Immediate Draw for New Lights", + "feature.light_limit_fix.allow_immediate_draw_new_lights_tooltip": "Allow a light just added to the active pool to render its shadow map this frame.\nPrevents a one-frame shadow-map gap when new lights enter view.", + "feature.light_limit_fix.available_variables": "Available Variables##FormulaVars", + "feature.light_limit_fix.avg_light_cost": "Avg light cost : %.2f ms", + "feature.light_limit_fix.avg_redraws_per_frame": "Avg redraws/frame : %.1f (cap: %d)", + "feature.light_limit_fix.avg_redraws_tooltip": "Rolling average over the last %d frames.", + "feature.light_limit_fix.billboard_brightness": "Billboard Brightness", + "feature.light_limit_fix.billboard_brightness_tooltip": "Intensity multiplier for billboard (single-quad) emitters such as candle flames.", + "feature.light_limit_fix.billboard_radius": "Billboard Radius", + "feature.light_limit_fix.billboard_radius_tooltip": "Radius multiplier for billboard emitters. Larger = light reaches further.", + "feature.light_limit_fix.budget_from_formula": "Budget from formula: %.2f ms", + "feature.light_limit_fix.budget_from_formula_tooltip": "Edit the Redraw Budget formula in the Advanced section below.", + "feature.light_limit_fix.budget_mode": "Budget Mode", + "feature.light_limit_fix.budget_mode_formula": "Formula", + "feature.light_limit_fix.budget_mode_formula_tooltip": "Formula: user-editable exprtk expression for per-frame budget.\nDefault expression matches Intellightent's original behaviour\n(1 ms outdoors, 2 ms indoors). Edit the expression in the\nAdvanced section below.\n\nCaveat: adaptive expressions referencing `frametime` tend to\nping-pong because rendering shadows raises frametime, removing\nthe headroom that allowed the budget. Stick to static or\nslowly-varying inputs (`isinterior`, `frametarget`).", + "feature.light_limit_fix.budget_mode_manual": "Manual", + "feature.light_limit_fix.budget_mode_manual_tooltip": "Manual (default): fixed per-frame GPU time budget for shadow re-renders.\nPredictable; doesn't oscillate. Adjust the slider to trade FPS for shadow quality.", + "feature.light_limit_fix.budget_usage_label": "Budget usage :", + "feature.light_limit_fix.budget_usage_warming_up": "Budget usage : (warming up)", + "feature.light_limit_fix.clear_all_btn": "Clear All", + "feature.light_limit_fix.clear_all_tooltip": "Reset every debug override:\n - clear suppression\n - clear shadow / convert pins\n - clear solo\nReturns the table to scheduler-auto behaviour.", + "feature.light_limit_fix.cluster_lights": "Cluster lights : %u / %u", + "feature.light_limit_fix.cluster_lights_overflow": "Cluster lights : %u / %u (overflow)", + "feature.light_limit_fix.col_address": "Address", + "feature.light_limit_fix.col_color": "Color", + "feature.light_limit_fix.col_description": "Description", + "feature.light_limit_fix.col_imp": "Imp", + "feature.light_limit_fix.col_mode": "Mode", + "feature.light_limit_fix.col_range": "Range", + "feature.light_limit_fix.col_solo": "Solo", + "feature.light_limit_fix.col_status": "Status", + "feature.light_limit_fix.col_type": "Type", + "feature.light_limit_fix.col_variable": "Variable", + "feature.light_limit_fix.contact_shadow_depth_fade": "Depth Fade", + "feature.light_limit_fix.contact_shadow_depth_fade_tooltip": "Depth-delta multiplier for shadow falloff. Larger = shadows truncate sooner behind thick occluders.", + "feature.light_limit_fix.contact_shadow_max_distance": "Max Distance", + "feature.light_limit_fix.contact_shadow_max_distance_tooltip": "View-space depth at which contact shadows fade to zero steps. Avoids paying for shadows on distant surfaces where they don't read.", + "feature.light_limit_fix.contact_shadow_max_steps": "Max Steps", + "feature.light_limit_fix.contact_shadow_max_steps_tooltip": "Raymarch steps at zero depth. Higher = longer / more accurate contact shadows, linearly more cost.\nVR users should consider 2 to halve per-eye cost.", + "feature.light_limit_fix.contact_shadow_min_intensity": "Min Light Intensity", + "feature.light_limit_fix.contact_shadow_min_intensity_tooltip": "Skip contact shadows for CLUSTERED lights whose normalized distance falloff `1 - (lightDist/radius)^2` at the pixel is below this threshold. Strict lights are always raymarched regardless of this threshold. Higher = larger perf win, may drop subtle shadows from weak lights at their reach edge.", + "feature.light_limit_fix.contact_shadow_stride": "Stride", + "feature.light_limit_fix.contact_shadow_stride_tooltip": "Per-step march length in view-space units at near depth (auto-scales linearly past ~100 units so far surfaces don't undersample). Larger = longer screen-space reach with coarser detail.", + "feature.light_limit_fix.contact_shadow_thickness": "Thickness", + "feature.light_limit_fix.contact_shadow_thickness_tooltip": "Depth-delta multiplier for shadow onset. Larger = darker contact at occluder edges.", + "feature.light_limit_fix.contact_shadow_tuning": "Contact Shadow Tuning", + "feature.light_limit_fix.contact_shadows_header": "Contact Shadows", + "feature.light_limit_fix.convert_excess_to_normal": "Convert Excess Lights to Normal", + "feature.light_limit_fix.convert_excess_to_normal_tooltip": "Shadow lights that exceed the active shadow caster limit are demoted to\nnormal (unshadowed) lights so they still contribute diffuse and specular\nlighting at no shadow-map cost. Lights that fail culling are dropped entirely.\nRequires a game restart to change.", + "feature.light_limit_fix.converted_shadow_slots": "Converted Shadow Slots", + "feature.light_limit_fix.converted_shadow_slots_tooltip": "Extra pool slots for lights converted to normal (unshadowed) mode.\nIncrease if Convert Excess Lights drops lights you expect to see.", "feature.light_limit_fix.debug": "Debug", "feature.light_limit_fix.description": "Light Limit Fix removes the vanilla game's 4-light limit, allowing unlimited dynamic lights in scenes. It also extends shadow support to all point and spot lights.", + "feature.light_limit_fix.dynamic_range": "Dynamic range: %.0fx (unimportant lights wait %.0fx longer)", + "feature.light_limit_fix.enable_contact_shadows": "Enable Contact Shadows", + "feature.light_limit_fix.enable_contact_shadows_tooltip": "All point lights (strict and clustered, except simple lights) cast short screen-space shadows. Performance impact.", "feature.light_limit_fix.enable_lights_vis": "Enable Lights Visualisation", "feature.light_limit_fix.enable_lights_vis_tooltip": "Enables visualization of the light limit\n", + "feature.light_limit_fix.enable_particle_contact_shadows": "Enable Particle Contact Shadows", + "feature.light_limit_fix.enable_particle_contact_shadows_tooltip": "Also cast contact shadows from particle lights. Larger performance impact in fire/magic-heavy scenes.", + "feature.light_limit_fix.enable_particle_culling": "Enable Culling", + "feature.light_limit_fix.enable_particle_culling_tooltip": "Significantly improves performance by not rendering empty textures. Only disable if you are encountering issues.", + "feature.light_limit_fix.enable_particle_detection": "Enable Detection", + "feature.light_limit_fix.enable_particle_detection_tooltip": "Adds particle lights to the player light level so that NPCs detect them for stealth and gameplay.", + "feature.light_limit_fix.enable_particle_lights": "Enable Particle Lights", + "feature.light_limit_fix.enable_particle_lights_tooltip": "Master toggle for the particle-light feature.", + "feature.light_limit_fix.enable_particle_optimization": "Enable Optimization", + "feature.light_limit_fix.enable_particle_optimization_tooltip": "Merges vertices which are close enough to each other to improve performance.", + "feature.light_limit_fix.enable_shadow_limit_fix": "Enable Shadow Limit Fix", + "feature.light_limit_fix.enable_shadow_limit_fix_tooltip": "Extends Skyrim's hard limit of 4 simultaneous shadow-casting lights.\nIntelligently selects which lights cast shadows each frame based on\ndistance, intensity, and a configurable priority formula.\n\nBased on Intellightent by meh321.\nhttps://www.nexusmods.com/skyrimspecialedition/mods/172423\n\nRestart required to take effect in either direction. The boot-time\npatches (extended atlas slices, depth buffer creation loop, color-mask\npass replacement) cannot be safely reversed at runtime -- vanilla\nshadow scheduling crashes when run on top of them. Toggle and restart.", + "feature.light_limit_fix.filter_hint": "filter (yes/conv/no/type/range/addr)", + "feature.light_limit_fix.filter_hint_scene_only": "filter (yes/conv/type/range/addr)", + "feature.light_limit_fix.force_portal_strict_all": "Force Enable Portal Strict (All)", + "feature.light_limit_fix.force_portal_strict_all_tooltip": "Master toggle for the three per-type rows below.\nChecked when all three are enforced, unchecked when none are,\nand rendered translucent when mixed.\nRequires a game restart to change.", + "feature.light_limit_fix.force_portal_strict_hemi": "Force Portal Strict on Hemisphere Lights", + "feature.light_limit_fix.force_portal_strict_hemi_tooltip": "Force-enable portal-strict on single-paraboloid (hemisphere)\nshadow casters. Recommended on -- behaves like the omni case\nunder portal culling.\nRequires a game restart to change.", + "feature.light_limit_fix.force_portal_strict_omni": "Force Portal Strict on Omni Lights", + "feature.light_limit_fix.force_portal_strict_omni_tooltip": "Force-enable portal-strict on dual-paraboloid (omnidirectional)\nshadow casters. Recommended on -- tightens portal-graph visibility\nculling for full-sphere shadow lights without side effects.\nRequires a game restart to change.", + "feature.light_limit_fix.force_portal_strict_spot": "Force Portal Strict on Spot Lights", + "feature.light_limit_fix.force_portal_strict_spot_tooltip": "Force-enable portal-strict on perspective (frustum/spot) shadow\ncasters. Off by default: the cone test rejects spots whose\norigin sits behind a portal even when their beam sweeps into a\nvisible room, which drops culled-but-visible spots entirely.\nEnable only for debugging.\nRequires a game restart to change.", + "feature.light_limit_fix.formula_editor": "Formula Editor##Formulas", + "feature.light_limit_fix.formula_redraw_budget": "Redraw Budget", + "feature.light_limit_fix.formula_redraw_budget_tooltip": "Per-frame redraw budget formula (ms). Empty = use the Redraw Budget (ms) slider value.", + "feature.light_limit_fix.formula_redraw_interval": "Redraw Interval", + "feature.light_limit_fix.formula_redraw_interval_tooltip": "Per-light redraw interval formula. Higher = less frequent shadow map updates.", + "feature.light_limit_fix.formula_score": "Score", + "feature.light_limit_fix.formula_score_tooltip": "Light priority scoring formula. Higher score = more likely to get a shadow slot.", + "feature.light_limit_fix.frame_diagnostic": "Frame: %.1f FPS (%.1f ms) | frametarget: %.0f FPS (%.1f ms) | headroom: %+.1f ms | %s", + "feature.light_limit_fix.frame_diagnostic_tooltip": "Live values of the exprtk variables exposed to the Redraw\nBudget formula. `frametarget` is the rolling 90th-percentile\nframe time, used as a self-measured ceiling -- not a vsync\ntarget. State indicator:\n steady -- within +/-%.1f ms of target\n growing -- frametime well below target; headroom available\n throttling -- frametime over target; expressions returning\n nonzero values here will keep frametime high", + "feature.light_limit_fix.frame_state_growing": "growing", + "feature.light_limit_fix.frame_state_steady": "steady", + "feature.light_limit_fix.frame_state_throttling": "throttling", + "feature.light_limit_fix.group_btn_all": "All", + "feature.light_limit_fix.group_btn_conv": "Conv", + "feature.light_limit_fix.group_btn_hemi": "Hemi", + "feature.light_limit_fix.group_btn_omni": "Omni", + "feature.light_limit_fix.group_btn_spot": "Spot", + "feature.light_limit_fix.group_tip_conv": "Toggle all lights currently demoted from shadow to normal\n(ConvertExcessToNormal). Hides their cluster-light contribution.", + "feature.light_limit_fix.group_tip_hemi": "Toggle all hemisphere shadow lights", + "feature.light_limit_fix.group_tip_omni": "Toggle all omni (paraboloid) shadow lights", + "feature.light_limit_fix.group_tip_spot": "Toggle all spot/frustum shadow lights", + "feature.light_limit_fix.high_importance_tooltip": "%u high-importance (near camera/player).", + "feature.light_limit_fix.importance_scheduling": "Importance Scheduling", + "feature.light_limit_fix.importance_tooltip": "Contribution importance score:\n luminance(diffuse * fade)\n * max(att_camera, att_player)\n where att = (1 - (dist/radius)^2)^2\n\nHigher = light strongly illuminates the viewer area.\nDrives interval multiplier (configurable in Advanced settings).\nDefault: 0 => x2.0, 0.5 => x0.32, 1 => x0.05\n\nRows tinted yellow are high-importance (>0.1)\n-- they deliver meaningful illumination near the camera\nor player and receive accelerated shadow redraw scheduling.", + "feature.light_limit_fix.json_intensity_scale": "Intensity Scale", + "feature.light_limit_fix.json_intensity_scale_tooltip": "Scales intensity for attached runtime lights generated from Light records.\nPrimarily targets Light Placer-style JSON lights.\nRequires Inverse Square Lighting runtime metadata.", + "feature.light_limit_fix.json_interiors_only": "Interiors Only", + "feature.light_limit_fix.json_portal_strict_only": "Portal Strict Only", + "feature.light_limit_fix.json_requires_isl": "Requires Inverse Square Lighting to identify JSON-placed runtime lights.", "feature.light_limit_fix.key_feature_1": "Removes 4-light limit", "feature.light_limit_fix.key_feature_2": "Unlimited dynamic lights", "feature.light_limit_fix.key_feature_3": "Shadow support for point and spot lights", "feature.light_limit_fix.key_feature_4": "Improved lighting quality", "feature.light_limit_fix.key_feature_5": "Particle lights from configurable INI", + "feature.light_limit_fix.light_conversion": "Light Conversion##LightConv", "feature.light_limit_fix.light_limit_vis": "Light Limit Visualization", "feature.light_limit_fix.lights_vis_mode": "Lights Visualisation Mode", "feature.light_limit_fix.lights_vis_mode_opt_clustered_lights_count": "Clustered Lights Count", @@ -871,10 +988,120 @@ "feature.light_limit_fix.lights_vis_mode_opt_strict_lights_count": "Strict Lights Count", "feature.light_limit_fix.lights_vis_mode_opt_unshadowed_point_lights": "Unshadowed Point Lights", "feature.light_limit_fix.lights_vis_mode_tooltip": "Light Limit: Red when the strict light limit is reached (>=7 portal-strict lights).\n\nStrict Lights Count: Heatmap of portal-strict lights per pixel (blue=0, red=15).\n\nClustered Lights Count: Heatmap of dynamic lights in each screen tile (blue=0, red=128).", + "feature.light_limit_fix.max_interval_scale": "Max Interval Scale", + "feature.light_limit_fix.max_interval_scale_tooltip": "Interval multiplier applied to unimportant lights (importance = 0).\nHigher values defer dim or distant lights more aggressively.\nDefault: 2.0", + "feature.light_limit_fix.max_particle_distance": "Max Particle Distance", + "feature.light_limit_fix.max_particle_distance_tooltip": "Particle lights beyond this distance from the camera are skipped entirely.\nLower = better performance, but distant effects won't contribute light.\nHigher = more distant particle lighting, but more cost.", + "feature.light_limit_fix.max_particles_per_emitter": "Max Particles per Emitter", + "feature.light_limit_fix.max_particles_per_emitter_tooltip": "Maximum number of particles sampled per emitter per frame.\nHigher = closer to the real particle system but more CPU work.\nLower = faster, especially for very dense effects.", + "feature.light_limit_fix.max_redraws_per_frame": "Max Redraws Per Frame", + "feature.light_limit_fix.max_redraws_per_frame_tooltip": "Hard cap on how many shadow lights may re-render their shadow maps in one frame.\nActs as a safety valve regardless of budget -- the budget controls time spent,\nthis controls count. The sun directional light always counts as one redraw.\nMinimum is %d (lower values cause shadow flicker as redraw rotation outpaces TAA).\nUpper bound tracks the number of active shadow lights this frame (%d).", + "feature.light_limit_fix.min_interval_scale": "Min Interval Scale", + "feature.light_limit_fix.min_interval_scale_tooltip": "Interval multiplier applied to high-importance lights (importance >= 1).\nLower values make bright/close lights update shadows more frequently.\nThe ratio Max/Min defines the scheduling dynamic range.\nDefault: 0.05 (40x range at default Max=2.0)", + "feature.light_limit_fix.mode3_b_unused": "(B = unused)", + "feature.light_limit_fix.mode3_g_channel": "G channel = directional detailed shadow", + "feature.light_limit_fix.mode3_r_channel": "R channel = directional soft shadow", + "feature.light_limit_fix.mode4_heatmap": "Pixel heatmap: 0=blue 8+=red", + "feature.light_limit_fix.mode5_lit_shadow": "White = fully lit, black = fully in shadow", + "feature.light_limit_fix.mode6_heatmap": "Pixel heatmap: 0=blue 8+=red (lights without shadow maps)", + "feature.light_limit_fix.mode7_cool": "Cool Turbo[0.0-0.3] = 1-4 shadows", + "feature.light_limit_fix.mode7_red": "Red = overflow", + "feature.light_limit_fix.mode7_warm": "Warm Turbo[0.3-0.8] = 5-%u shadows", + "feature.light_limit_fix.mode9_hemi": "G Hemisphere : %u", + "feature.light_limit_fix.mode9_omni": "B Omni (paraboloid): %u", + "feature.light_limit_fix.mode9_spot": "R Spot (frustum) : %u", + "feature.light_limit_fix.mode_eng": "eng", + "feature.light_limit_fix.mode_eng_tooltip": "Engine-controlled focus shadow; not pinnable/suppressible.", + "feature.light_limit_fix.mode_tip_auto": "Auto (scheduler decides)\nClick: pin as shadow caster", + "feature.light_limit_fix.mode_tip_pin_convert": "Pinned: forced converted (non-shadow)\nClick: suppress entirely", + "feature.light_limit_fix.mode_tip_pin_shadow": "Pinned: forced shadow caster\nClick: pin as converted (non-shadow)", + "feature.light_limit_fix.mode_tip_suppressed": "Suppressed (hidden)\nClick: return to auto", "feature.light_limit_fix.name": "Light Limit Fix", + "feature.light_limit_fix.no_shadow_slots_this_frame": "No shadow slots this frame.", + "feature.light_limit_fix.overlay_debug_label": "LLF DEBUG - %s", + "feature.light_limit_fix.overlay_shadow_suppression": "LLF - Shadow Suppression", + "feature.light_limit_fix.parse_error": "Parse error: %s", + "feature.light_limit_fix.particle_appearance": "Appearance##particles", + "feature.light_limit_fix.particle_brightness": "Particle Brightness", + "feature.light_limit_fix.particle_brightness_tooltip": "Intensity multiplier for particle-system emitters (fire, sparks, magic).", + "feature.light_limit_fix.particle_cluster_threshold": "Cluster Threshold", + "feature.light_limit_fix.particle_cluster_threshold_tooltip": "Distance+radius similarity threshold for merging particles into one light.\nHigher = more merging, better performance, blurrier lights.\nLower = less merging, more precise, more expensive.", + "feature.light_limit_fix.particle_lights_additive_note": "Particle lights are additive emitters and do NOT cast shadow-map shadows, so they never appear in the shadow caster table above. Turn on \"Enable Particle Contact Shadows\" in the Contact Shadows section for short screen-space contact shadows.", + "feature.light_limit_fix.particle_lights_header": "Particle Lights", + "feature.light_limit_fix.particle_lights_intro": "Turns configured particle effects (candles, braziers, torches, magic) into dynamic lights. Requires a particle-light config pack shipping Data\\ParticleLights\\*.ini (e.g. Embers HD, Lanterns of Skyrim); with no pack installed this section has no effect.", + "feature.light_limit_fix.particle_performance": "Performance##particles", + "feature.light_limit_fix.particle_radius": "Particle Radius", + "feature.light_limit_fix.particle_radius_tooltip": "Radius multiplier for particle-system emitters. Larger = light reaches further.", + "feature.light_limit_fix.particle_saturation": "Saturation", + "feature.light_limit_fix.particle_saturation_tooltip": "Color saturation of particle/billboard lights. 1.0 = source color; higher = more vivid.", + "feature.light_limit_fix.per_row_controls_help": "Per-row controls:\n * Cycle button (col 1): click to rotate this light through\n Auto -> Shadow pin (S) -> Convert pin (C) -> Suppress (X) -> Auto.\n * Solo button (col 2): isolate this light against a black scene.\n Click again to clear; only one light may be soloed at a time.\n * Hold Shift while hovering a row to highlight that light in the\n world with a pulsing magenta tint. Release Shift or move the\n cursor away to stop. Useful when you can't tell which entry\n corresponds to which physical light. Does not affect rendering\n when Shift is not held.\n\nGroup buttons toggle suppression for every matching row at once.\nClear All appears when any override is active and resets everything.", + "feature.light_limit_fix.placed_lights_json_header": "Placed Lights (JSON)", + "feature.light_limit_fix.placed_lights_json_intro": "Scales the intensity of runtime lights attached from Light records by Light Placer-style mods. Separate from particle lights; requires Inverse Square Lighting for the runtime metadata.", + "feature.light_limit_fix.portal_strict_enforcement": "Portal-Strict Enforcement", + "feature.light_limit_fix.projected_shadow_vram_label": "Projected shadow VRAM :", + "feature.light_limit_fix.projected_shadow_vram_overlay": "shadows %.0f -> %.0f MB (%d slots, %.0f MB free after restart)", + "feature.light_limit_fix.projected_shadow_vram_tooltip": "Stacked VRAM bar against DXGI budget.\n Grey block : process VRAM not counted as shadow array\n Blue block : current kSHADOWMAPS allocation this session\n Outlined block: what the slider's value would allocate\n after restart (colour reflects verdict)\n\nSolid colour past the blue: shadow array would GROW by that\namount. Dark stripe inside the blue: shadow array would\nSHRINK by that amount.\n\nSlots requested : %d (sun lives in kSHADOWMAPS_ESRAM)\nPer-slice cost : %.2f MB (%u x %u @ %u B/pixel)\nCurrent array : %.1f MB\nProjected array : %.1f MB\nFree after restart : %.1f MB / %.0f MB budget\n%s", + "feature.light_limit_fix.projected_vram_verdict_red": "\nRED: this projection won't fit in the current VRAM budget.\nThe driver will page or refuse the allocation, leaving the\nshadow array smaller than requested -- shadows will silently\nbreak. Lower the slot count or reduce iShadowMapResolution.", + "feature.light_limit_fix.projected_vram_verdict_yellow": "\nYELLOW: tight headroom. A driver or OS spike could push\nshadow allocation into paging. Safe for testing, risky for\nlong sessions or heavily-modded scenes.", + "feature.light_limit_fix.promote_normal_to_shadow": "Promote Normal Lights to Shadow Casters", + "feature.light_limit_fix.promote_normal_to_shadow_tooltip": "Experimental: elevate high-scoring unshadowed lights to shadow casters\nwhen shadow slots are available.\nRequires a game restart to change.", + "feature.light_limit_fix.redraw_budget_ms": "Redraw Budget (ms)", + "feature.light_limit_fix.redraw_budget_ms_tooltip": "Per-frame GPU time budget for shadow re-renders (milliseconds).\nLights whose estimated render cost exceeds the remaining budget are deferred.\nThe first eligible light always renders regardless of budget (starvation prevention).\n\nReference points:\n 1-2 ms: Intellightent's original (1 outdoors, 2 indoors)\n 5 ms : default — comfortable for typical scenes (~5-8 lights at ~1 ms each)\n 16 ms: full 60 fps frame; shadows can saturate the frame here\n 32 ms: extreme — only useful for very high light counts on fast GPUs\n\nHigher = more shadow lights redraw per frame, fewer stale shadow maps,\nat the cost of frametime. The Budget verdict in the Active Casters\nsection shows whether the current setting has headroom to spare.", + "feature.light_limit_fix.res_tier_custom": "Custom (%d)", + "feature.light_limit_fix.reset_importance_defaults": "Reset Importance Defaults", + "feature.light_limit_fix.restart_session_lights": "Restart required -- current session uses %d lights.", + "feature.light_limit_fix.restart_session_resolution": "Restart required -- current session uses %d px shadow maps.", + "feature.light_limit_fix.restart_session_state": "Restart required -- this session is %s.", + "feature.light_limit_fix.session_disabled": "disabled", + "feature.light_limit_fix.session_enabled": "enabled", + "feature.light_limit_fix.shadow_array_unverified_banner": "Shadow array not yet verified -- load a save to confirm allocation.", + "feature.light_limit_fix.shadow_array_unverified_tooltip": "kSHADOWMAPS isn't readable yet (main menu / loading screen).\nOnce you reach gameplay the scheduler verifies the actual\nslice count against your requested value. If they disagree\nthis banner turns red.", + "feature.light_limit_fix.shadow_light_count": "Shadow Light Count", + "feature.light_limit_fix.shadow_light_count_projection_tooltip": "%s\n\nProjected kSHADOWMAPS array at %d slots: %.1f MB\nPer-slice cost: %.2f MB (%u x %u, %u B/pixel)\nProjected free VRAM after restart: %.1f MB", + "feature.light_limit_fix.shadow_light_count_tooltip": "Maximum simultaneous shadow-casting point/spot lights (directional sun not counted).\n 0 = scheduler runs but selects no point lights (sun/directional unaffected).\n 4 = vanilla point light count with intelligent selection.\n >4 = extended mode; depth buffer expanded when >8. Max 127\n (VRAM is the practical limit -- watch the projected-VRAM bar).\nRequires a game restart to take effect.", + "feature.light_limit_fix.shadow_lights": "Shadow lights : %u / %u (%u wanted, 0 dropped, %zu converted)", + "feature.light_limit_fix.shadow_lights_dropped": "Shadow lights : %u / %u (%u wanted, %u dropped, %zu converted)", + "feature.light_limit_fix.shadow_limit_fix_active_casters": "Shadow Limit Fix -- Active Casters", + "feature.light_limit_fix.shadow_limit_fix_header": "Shadow Limit Fix", + "feature.light_limit_fix.shadow_map_resolution": "Shadow Map Resolution", + "feature.light_limit_fix.shadow_map_resolution_tooltip": "Drives iShadowMapResolution:Display in SkyrimPrefs.ini.\nAffects both omni/spot shadow slices and the sun cascade\ntexture; per-slice VRAM scales as resolution^2 * 4 bytes\n(4 / 16 / 64 / 256 MB at 1024 / 2048 / 4096 / 8192).\nRequires a game restart to take effect.", + "feature.light_limit_fix.shadow_slots_active": "Shadow slots: %u active", + "feature.light_limit_fix.shadow_vram_label": "Shadow VRAM :", + "feature.light_limit_fix.shadow_vram_overlay": "%.0f / %.0f MB - shadows %.0f MB (%u slices)", + "feature.light_limit_fix.shadow_vram_tooltip": "Bar fill = process VRAM usage / DXGI budget (same data the\nperformance overlay reports). Overlay text shows the shadow\narray's contribution to that usage.\n\nSlices : %u (sun lives in its own kSHADOWMAPS_ESRAM texture)\nPer slice : %.2f MB (%u x %u @ %u B/pixel)\nShadow array : %.1f MB\nFree in budget : %.1f MB\n\nGreen when free VRAM and shadow share are comfortable.\nYellow when free < 512 MB or shadow array > 25%% of budget.\nRed when free < 128 MB or shadow array > 50%% of budget --\nlower Shadow Light Count or iShadowMapResolution.", + "feature.light_limit_fix.show_shadow_overlay": "Show Shadow Overlay", + "feature.light_limit_fix.show_shadow_overlay_tooltip": "Pop out an always-visible overlay window with the shadow caster table.\nWithout this, the overlay only appears when a light is suppressed\nor a visualisation mode is active. Enable to access the table's\ndebug controls (cycle button, solo, Shift+hover pulse) any time.", + "feature.light_limit_fix.solo_tip": "Solo this light\n(suppresses every other light\nuntil cleared)", + "feature.light_limit_fix.solo_tip_active": "Solo: this light is shown alone\nClick: clear solo", "feature.light_limit_fix.stat_clustered_light_count": "Clustered Light Count : {}", "feature.light_limit_fix.stat_particle_lights_count": "Particle Lights Count : {}", "feature.light_limit_fix.statistics": "Statistics", + "feature.light_limit_fix.status_conv": "Conv", + "feature.light_limit_fix.status_conv_tooltip": "Demoted to a normal (non-shadow) light this frame.\nCluster lighting still illuminates it; no shadow-map cost.", + "feature.light_limit_fix.status_out": "Out", + "feature.light_limit_fix.status_out_tooltip": "Out of range / not active in the current frame.", + "feature.light_limit_fix.status_slot": "Slot %u", + "feature.light_limit_fix.status_slot_tooltip": "Casting shadows this frame in slot %u.", + "feature.light_limit_fix.status_suppr": "Suppr", + "feature.light_limit_fix.status_suppr_tooltip": "Suppressed by debug override.\nClick the Mode button to clear.", + "feature.light_limit_fix.suppressed_count": " %zu suppressed", + "feature.light_limit_fix.type_focus": "Focus", + "feature.light_limit_fix.type_focus_tooltip": "Engine-owned focus shadow slot.\nFocusShadowActors[%u] = high-res shadow for a tracked\nactor (player + dialog/combat NPCs). SCM reserves\nthis slot so the engine's focus render isn't trampled\nby point/spot lights.", + "feature.light_limit_fix.verdict_at_limits": "AT LIMITS", + "feature.light_limit_fix.verdict_at_limits_tip": "Both Max Redraws and Shadow Light Count are full. Enable Convert to Normal or raise Shadow Light Count.", + "feature.light_limit_fix.verdict_headroom": "HEADROOM", + "feature.light_limit_fix.verdict_headroom_tip": "Under half the Redraw Budget is being used. Raise Max Redraws or accept the slack.", + "feature.light_limit_fix.verdict_light_limited": "LIGHT LIMITED", + "feature.light_limit_fix.verdict_light_limited_tip": "Shadow Light Count is full. Enable Convert to Normal or raise Shadow Light Count.", + "feature.light_limit_fix.verdict_ok": "OK", + "feature.light_limit_fix.verdict_ok_tip": "Within Redraw Budget; no limits hit.", + "feature.light_limit_fix.verdict_over_budget": "OVER BUDGET", + "feature.light_limit_fix.verdict_over_budget_tip": "Shadow time exceeds Redraw Budget. Lower Max Redraws or raise Redraw Budget.", + "feature.light_limit_fix.verdict_redraw_limited": "REDRAW LIMITED", + "feature.light_limit_fix.verdict_redraw_limited_tip": "Hitting Max Redraws Per Frame. Raise it to spend the unused Redraw Budget.", + "feature.light_limit_fix.visualisation_tooltip_shadow_modes": "\nShadow Mask: R=directional soft shadow, G=directional detailed shadow.\n\nShadow Light Count: Heatmap of shadow-casting point/spot lights per pixel (blue=0, red=8+).\nUse to gauge shadow density; high counts indicate expensive shadow sampling.\n\nPoint Light Shadow Factor: Brightness shows the darkest shadow value from any point/spot\nlight. White=fully lit, black=fully shadowed. Shows where PCF/PCSS filtering is active.\n\nUnshadowed Point Lights: Heatmap of point/spot lights without shadow maps (blue=0, red=8+).\nHigh values where lights are bright indicate where the shadow slot limit is costing quality.\n\nShadow Caster Density: Custom Turbo ranges show how heavily shadow slots are used.\n Cool (Turbo 0.0-0.3): 1-4 shadow lights per pixel.\n Warm (Turbo 0.3-0.8): 5 to ShadowMapSlots lights (dynamic range).\n Bright red: overflow - a light wanted a shadow slot but none was available.\n\nShadow Slot Index Color: Assigns each shadow-map slot a unique high-contrast hue\n(golden-ratio sequence) so you can identify which slot is casting the primary shadow.\nFirst valid shadow light index per pixel is shown. Bright red = slot overflow.\n\nLight Type Visualization: RGB channels encode shadow light types per pixel.\n R = spot/frustum lights (ShadowParam.x == 0).\n G = hemisphere/paraboloid lights (ShadowParam.x == 1).\n B = omnidirectional/full-paraboloid lights (ShadowParam.x == 2).\n Dark grey = unshadowed lights only (no shadow maps assigned).\n Bright red = overflow (slot capacity exceeded).\nIntensity scales with count (up to 4); channels blend for mixed-type pixels.", + "feature.light_limit_fix.vram_exhausted_banner": "VRAM exhausted: requested %u slots, GPU allocated %u.", + "feature.light_limit_fix.vram_exhausted_tooltip": "The engine tried to create kSHADOWMAPS with %u slices but\nthe GPU / driver returned a smaller array (likely out of\nVRAM at the configured iShadowMapResolution). The scheduler\nhas clamped itself to the actual count so the existing %u\nslices work correctly, but to reach the requested %u you'll\nneed to free VRAM (lower resolution, other features, etc).", "feature.linear_lighting.ambient_gamma": "Ambient Gamma", "feature.linear_lighting.ambient_multiplier": "Ambient Multiplier", "feature.linear_lighting.blood_effects_multiplier": "Blood Effects Multiplier", @@ -925,21 +1152,105 @@ "feature.lod_blending.lod_terrain_brightness": "LOD Terrain Brightness", "feature.lod_blending.lod_terrain_gamma": "LOD Terrain Gamma", "feature.lod_blending.name": "LOD Blending", + "feature.perf_overlay.a_user_fps": "A (USER) FPS: %.2f", + "feature.perf_overlay.a_user_median_fps": "A (USER) Median FPS: {:.2f}", + "feature.perf_overlay.a_user_ms": "A (USER): %.3f ms", + "feature.perf_overlay.ableg_a_avg_desc": "A Avg (ms): Average frame time for Variant A (USER config).", + "feature.perf_overlay.ableg_a_median_desc": "A Median: Median frame time for Variant A (USER config).", + "feature.perf_overlay.ableg_b_avg_desc": "B Avg (ms): Average frame time for Variant B (TEST config).", + "feature.perf_overlay.ableg_b_median_desc": "B Median: Median frame time for Variant B (TEST config).", + "feature.perf_overlay.ableg_better_than_a": " Better (lower than A)", + "feature.perf_overlay.ableg_better_than_b": " Better (lower than B)", + "feature.perf_overlay.ableg_color_legend": "Color Legend:", + "feature.perf_overlay.ableg_color_vs_a": "Color Legend (compared to Variant A):", + "feature.perf_overlay.ableg_color_vs_a_median": "Color Legend (compared to Variant A median):", + "feature.perf_overlay.ableg_color_vs_b": "Color Legend (compared to Variant B):", + "feature.perf_overlay.ableg_color_vs_b_median": "Color Legend (compared to Variant B median):", + "feature.perf_overlay.ableg_delta_desc": "Delta (ms): Difference between Variant B and Variant A (B - A).", + "feature.perf_overlay.ableg_delta_neg": "Negative values indicate Variant B is better (lower frame time).", + "feature.perf_overlay.ableg_delta_percent": "Percentage shows relative performance difference.", + "feature.perf_overlay.ableg_delta_pos": "Positive values indicate Variant A is better (lower frame time).", + "feature.perf_overlay.ableg_median_delta_desc": "Median Delta: Difference between Variant B and Variant A medians (B - A).", + "feature.perf_overlay.ableg_median_delta_neg": "Negative values indicate Variant B is better (lower median).", + "feature.perf_overlay.ableg_median_delta_pos": "Positive values indicate Variant A is better (lower median).", + "feature.perf_overlay.ableg_median_outliers": "Median is less sensitive to outliers than average.", + "feature.perf_overlay.ableg_negative_b_better": " Negative (B better)", + "feature.perf_overlay.ableg_positive_a_better": " Positive (A better)", + "feature.perf_overlay.ableg_same_as_a": " Same as A", + "feature.perf_overlay.ableg_same_as_b": " Same as B", + "feature.perf_overlay.ableg_shader_type_desc": "Shader Type: The type of shader being measured.", + "feature.perf_overlay.ableg_shader_type_toggle": "Click to toggle shader on/off for performance testing.", + "feature.perf_overlay.ableg_worse_than_a": " Worse (higher than A)", + "feature.perf_overlay.ableg_worse_than_b": " Worse (higher than B)", + "feature.perf_overlay.ableg_zero_same": " Zero (same)", + "feature.perf_overlay.abtest_interval": "A/B Test Interval", + "feature.perf_overlay.abtest_tooltip": "A/B Testing compares two configurations by automatically swapping between them.\nWorkflow: Configure your test settings, then enable A/B testing.\n- Variant B (TEST) = Your current settings when you enable testing\n- Variant A (USER) = Your previously saved user configuration\nTesting starts with Variant B, then swaps every N seconds.\nSet to 0 to disable and restore TEST settings.", "feature.perf_overlay.appearance": "Appearance", + "feature.perf_overlay.b_test_fps": "B (TEST) FPS: %.2f", + "feature.perf_overlay.b_test_median_fps": "B (TEST) Median FPS: {:.2f}", + "feature.perf_overlay.b_test_ms": "B (TEST): %.3f ms", "feature.perf_overlay.bg_opacity": "Background Opacity", + "feature.perf_overlay.changes_from_user": "Changes from USER:", + "feature.perf_overlay.clear_abtest_results": "Clear A/B Test Results", "feature.perf_overlay.clear_test_data": "Clear Test Data", + "feature.perf_overlay.col_a_avg": "A Avg (ms)", + "feature.perf_overlay.col_a_median": "A Median (ms)", + "feature.perf_overlay.col_a_value": "A Value", + "feature.perf_overlay.col_b_avg": "B Avg (ms)", + "feature.perf_overlay.col_b_median": "B Median (ms)", + "feature.perf_overlay.col_b_value": "B Value", + "feature.perf_overlay.col_cost_per_call": "Cost/Call", + "feature.perf_overlay.col_delta": "Delta (ms)", + "feature.perf_overlay.col_draw_calls": "Draw Calls", + "feature.perf_overlay.col_frame_time": "Frame Time (%)", + "feature.perf_overlay.col_median_delta": "Median Delta (ms)", + "feature.perf_overlay.col_setting_path": "Setting Path", + "feature.perf_overlay.col_shader_type": "Shader Type", + "feature.perf_overlay.col_test_cost_per_call": "Test Cost/Call", + "feature.perf_overlay.col_test_frame_time": "Test Frame Time (%)", + "feature.perf_overlay.dcleg_better_than_live": " Better (lower than live)", + "feature.perf_overlay.dcleg_color_ms_call": "Color Legend (ms/call):", + "feature.perf_overlay.dcleg_color_vs_live": "Color Legend (compared to live data):", + "feature.perf_overlay.dcleg_cost_per_call_desc": "Cost/Call: Average time per draw call for this shader type.", + "feature.perf_overlay.dcleg_cpc_bad": " > 0.2 ms/call", + "feature.perf_overlay.dcleg_cpc_good": " <= 0.05 ms/call", + "feature.perf_overlay.dcleg_cpc_warn": " > 0.05 ms and <= 0.2 ms/call", + "feature.perf_overlay.dcleg_draw_calls_desc": "Draw Calls: Number of draw calls for this shader type in the current frame.", + "feature.perf_overlay.dcleg_ft_bad": " > 5 ms", + "feature.perf_overlay.dcleg_ft_good": " <= 2 ms", + "feature.perf_overlay.dcleg_ft_warn": " > 2 ms and <= 5 ms", + "feature.perf_overlay.dcleg_perf_color_ms": "Performance Color Legend (ms):", + "feature.perf_overlay.dcleg_same_as_live": " Same as live", + "feature.perf_overlay.dcleg_shader_type_desc": "Shader Type: The type of shader being measured.", + "feature.perf_overlay.dcleg_shader_type_toggle": "Click to toggle shader on/off for performance testing.", + "feature.perf_overlay.dcleg_worse_than_live": " Worse (higher than live)", + "feature.perf_overlay.delta_b_minus_a": "Delta (B - A):", + "feature.perf_overlay.diff_header": "Differences between USER (A) and TEST (B) configs:", + "feature.perf_overlay.diff_none": "No setting changes detected between USER (A) and TEST (B) configs.", "feature.perf_overlay.display_options": "Display Options", + "feature.perf_overlay.draw_calls_na": "Draw Calls: Not applicable for unmeasured GPU time.", "feature.perf_overlay.fps": "FPS:", + "feature.perf_overlay.fps_value": "FPS: %.2f", "feature.perf_overlay.frame_history_size": "Frame History Size", + "feature.perf_overlay.fsr_dlss_timing_tooltip": "AMD FSR Frame Generation uses calculated timing data (2x Pre-FG).\nNVIDIA DLSS Frame Generation provides measured timing data.", + "feature.perf_overlay.graph_overlay_fmt": "%s%.2f ms (%.1f FPS)", + "feature.perf_overlay.hide_settings_diff": "Hide Settings Diff", + "feature.perf_overlay.median_delta_b_minus_a": "Median Delta (B - A):", "feature.perf_overlay.overlay_title": "Performance Overlay", "feature.perf_overlay.position": "Position:", "feature.perf_overlay.post_fg_calculated": "Post-FG: Calculated timing (2x Pre-FG)", "feature.perf_overlay.post_fg_fps": "Post-FG FPS:", + "feature.perf_overlay.post_fg_graph_overlay_fmt": "Post-FG: %.2f ms (%.1f FPS)", "feature.perf_overlay.post_fg_graph_tooltip": "FSR Frame Generation uses calculated timing data (2x Pre-FG).\nDLSS Frame Generation provides measured timing data.", + "feature.perf_overlay.pre_fg_prefix": "Pre-FG: ", "feature.perf_overlay.raw_fps": "Raw FPS:", + "feature.perf_overlay.ref_120fps": "120 FPS: 8.3 ms", + "feature.perf_overlay.ref_30fps": "30 FPS: 33.3 ms", + "feature.perf_overlay.ref_60fps": "60 FPS: 16.7 ms", "feature.perf_overlay.reset_position": "Reset Position", "feature.perf_overlay.restore_defaults": "Restore Defaults", "feature.perf_overlay.restore_defaults_tooltip": "Restores Performance Overlay settings to defaults, including graphs, appearance, and update intervals.", + "feature.perf_overlay.settings_changed_count": "%zu settings changed", "feature.perf_overlay.show_border": "Show Border", "feature.perf_overlay.show_cs_passes": "Show CS Render Passes", "feature.perf_overlay.show_draw_calls": "Show Draw Calls", @@ -949,10 +1260,38 @@ "feature.perf_overlay.show_in_overlay_tooltip": "Opens performance overlay in a separate window that stays open\neven when the main menu is closed. ", "feature.perf_overlay.show_post_fg_graph": "Show Post-FG Frametime Graph", "feature.perf_overlay.show_pre_fg_graph": "Show Pre-FG Frametime Graph", + "feature.perf_overlay.show_settings_diff": "Show Settings Diff", "feature.perf_overlay.show_vram": "Show VRAM Usage", + "feature.perf_overlay.test_duration_line": "Test Duration: %.1f seconds | Valid Frames: %d/%d (%.1f%%) | Excluded: %d", + "feature.perf_overlay.testdata_ago_suffix": " ago.", + "feature.perf_overlay.testdata_from_manual_toggle": "Test data from manual shader toggle.\nLast updated: ", + "feature.perf_overlay.testdata_from_variant_b": "Test data from Test (Variant B).\nLast updated: ", + "feature.perf_overlay.testdata_none": "No test data available.", "feature.perf_overlay.text_size": "Text Size", + "feature.perf_overlay.tip_blood_splatter": "Draw calls for blood splatter effects.", + "feature.perf_overlay.tip_cs_passes": "GPU time spent in Community Shaders compute passes (profiled).", + "feature.perf_overlay.tip_distant_tree": "Draw calls for distant tree rendering (LOD vegetation).", + "feature.perf_overlay.tip_effect": "Draw calls for special effects, particles, and post-processing.", + "feature.perf_overlay.tip_generic_shader": "Draw calls for this shader type.", + "feature.perf_overlay.tip_grass": "Draw calls using the Grass shader. Typically many, but each is usually cheap.", + "feature.perf_overlay.tip_image_space": "Draw calls for image space post-processing effects.", + "feature.perf_overlay.tip_lighting": "Draw calls for dynamic and static lighting passes.", + "feature.perf_overlay.tip_other": "Frame time not attributed to any measured shader type or CS compute pass. This includes UI, post-processing, engine work, and any GPU activity not directly measured.", + "feature.perf_overlay.tip_other_abtest": "Frame time not attributed to any measured shader type. This includes UI, post-processing, engine work, and any GPU activity not directly measured by the overlay.", + "feature.perf_overlay.tip_particle": "Draw calls for particle systems (smoke, sparks, etc.).", + "feature.perf_overlay.tip_sky": "Draw calls for the sky dome, clouds, and related effects.", + "feature.perf_overlay.tip_total": "Total frame time.", + "feature.perf_overlay.tip_utility": "Draw calls for utility passes, such as shadow masks or G-buffer fills.", + "feature.perf_overlay.tip_water": "Draw calls for water surfaces and effects.", "feature.perf_overlay.toggle_with": "Toggle with ", "feature.perf_overlay.update_interval": "Update Interval", + "feature.perf_overlay.validity_insufficient": "Insufficient data for reliable results", + "feature.perf_overlay.validity_legend_body": "Valid frames are those not excluded as outliers.\nA low percentage may indicate instability or test interruptions.\nExcluded frames are those with frame times > 3x median or > 100ms.\nThis removes shader compilation spikes, JSON loading overhead, and other anomalies\nthat would skew the performance comparison.", + "feature.perf_overlay.validity_marginal_fmt": "Marginal validity (>%d samples, >%.0fs duration)", + "feature.perf_overlay.validity_valid_fmt": "Statistically valid (>%d samples, >%.0fs duration, >%.0f%% valid)", + "feature.perf_overlay.variant_a_user": "Variant A (USER)", + "feature.perf_overlay.variant_b_test": "Variant B (TEST)", + "feature.perf_overlay.variant_time_left_fmt": "{} : {:.1f}s left", "feature.perf_overlay.vram_not_available": "VRAM Usage: Not available", "feature.perf_overlay.vram_usage": "VRAM Usage:", "feature.performance_overlay.description": "Real-time performance monitoring system that displays FPS, frame times, draw calls, VRAM usage, and detailed shader performance analysis.", @@ -964,6 +1303,19 @@ "feature.performance_overlay.key_feature_6": "Color-coded performance metrics with customizable thresholds", "feature.performance_overlay.key_feature_7": "Movable overlay window with persistent positioning", "feature.performance_overlay.name": "Performance Overlay", + "feature.remote_control.bridge_disabled": "This build was compiled without the devbench bridge (DEVBENCH_BRIDGE=OFF). No tools are registered.", + "feature.remote_control.console_note": "Note: the console tool is provided by devbench itself, not this plugin.", + "feature.remote_control.description": "Registers graphics-feature, inspect, capture, shader-cache, and settings tools into the external devbench host so AI assistants (Claude Code, Cursor, etc.) can toggle features, inspect engine state, trigger captures, and save/load settings over MCP and REST. There is no in-game server — install the devbench SKSE plugin to enable the integration.", + "feature.remote_control.host_not_detected": "devbench host not detected. Install the devbench SKSE plugin; the tools register automatically once it is present.", + "feature.remote_control.host_present": "devbench host present (build %u)", + "feature.remote_control.port_bound": "Host bound on port %d (from %s)", + "feature.remote_control.port_unknown": "Port unknown — devbench writes it to %s once it binds.", + "feature.remote_control.tool_capture": "openshaders.capture — RenderDoc / screenshot capture", + "feature.remote_control.tool_feature": "openshaders.feature — list / get / set / reset / toggle features", + "feature.remote_control.tool_inspect": "openshaders.inspect — engine state and shader-cache status", + "feature.remote_control.tool_settings": "openshaders.settings — save / load / reset the global config", + "feature.remote_control.tool_shadercache": "openshaders.shadercache — clear / delete the compiled cache", + "feature.remote_control.tools_header": "Tools exposed through devbench:", "feature.render_doc.description": "In-application RenderDoc capture support and convenience UI.", "feature.render_doc.key_feature_1": "Attach comments to captures that appear in RenderDoc UI", "feature.render_doc.key_feature_2": "Open captures folder", @@ -1002,6 +1354,7 @@ "feature.renderdoc.not_enough_space": "Not enough free disk space to create a capture.", "feature.renderdoc.ok": "OK", "feature.renderdoc.open_capture_dir": "Open Capture Directory", + "feature.renderdoc.overlay_warning": "WARNING: RenderDoc capture is active, performance will be severely impacted.\nUpscaling and Framegeneration may be incompatible.\nPress F12, Print Screen or press the Capture button in the RenderDoc feature settings.\nDisable RenderDoc capture in the RenderDoc feature settings.", "feature.renderdoc.refresh_list": "Refresh List", "feature.renderdoc.restart_to_disable": "Performance will be severely impacted until the game is restarted.", "feature.renderdoc.space_required": "At least {} MB of free space is required.", @@ -1094,9 +1447,13 @@ "feature.screen_space_shadows.vr_stereo_sync_tooltip": "Synchronizes shadow data between left and right eyes via bilateral reprojection and applies a depth-weighted blur to reduce per-eye noise. Uses min-blend so if either eye detects an occluder, the shadow is preserved. ", "feature.screenshot.apply_crop": "Apply crop", "feature.screenshot.async_note": "Capture and save run asynchronously without stalling the game.", + "feature.screenshot.copy_to_clipboard": "Copy saved file to clipboard", + "feature.screenshot.copy_to_clipboard_tooltip": "Places the saved screenshot on the clipboard as a file (paste in Explorer or attach in chat apps).", "feature.screenshot.crop": "Crop", "feature.screenshot.folder": "Folder", "feature.screenshot.folder_tooltip": "Relative paths resolve against the Skyrim install dir.\nAbsolute paths (e.g. D:\\Captures) save there directly.", + "feature.screenshot.format_bmp": "BMP (lossless)", + "feature.screenshot.format_png": "PNG (lossless)", "feature.screenshot.hdr_bit_depth": "HDR PNG bit depth", "feature.screenshot.hdr_bit_depth_tooltip": "Quantization for the 48 bpp RGB PNG payload. 11-bit is a good default; higher values increase file size with diminishing returns.", "feature.screenshot.hdr_note": "HDR enabled: saves the displayed frame as PNG with HDR10 metadata (48 bpp RGB, cICP/cLLi). Use an HDR-aware viewer such as Windows Photos (HDR on) or Special K SKIF.", @@ -1170,11 +1527,25 @@ "feature.skin.wetness_perlin_noise_persistence": "Wetness Perlin Noise Persistence", "feature.skin.wetness_perlin_noise_scale": "Wetness Perlin Noise Scale", "feature.skin.width_of_the_sss_transmittance_effect": "Width of the SSS Transmittance effect", + "feature.sky_sync.caster_masser": "Masser", + "feature.sky_sync.caster_none": "None", + "feature.sky_sync.caster_secunda": "Secunda", + "feature.sky_sync.caster_sun": "Sun", + "feature.sky_sync.crescent_intensity": "Crescent Intensity", "feature.sky_sync.custom_angle": "Custom angle", "feature.sky_sync.custom_angle_tooltip": "Set a custom angle for the sun's trajectory.", + "feature.sky_sync.debug": "Debug", + "feature.sky_sync.debug_dim": "Dim: %.3f", + "feature.sky_sync.debug_no_transition": "No transition", + "feature.sky_sync.debug_shadow_dir": "Shadow dir: (%.2f, %.2f, %.2f)", + "feature.sky_sync.debug_shadow_target": "Shadow target: %s", + "feature.sky_sync.debug_transitioning": "Transitioning %.0f%%", "feature.sky_sync.description": "Synchronizes volumetric lighting and shadows with the actual sun and moon positions in the sky.", + "feature.sky_sync.dim_sunlight_under_horizon": "Dim Sunlight Under Horizon", + "feature.sky_sync.dim_sunlight_under_horizon_tooltip": "Fade directional light to zero as the sun goes below the horizon.", "feature.sky_sync.enabled": "Enabled", "feature.sky_sync.enabled_tooltip": "Enable or disable Sky Sync features.", + "feature.sky_sync.full_moon_intensity": "Full Moon Intensity", "feature.sky_sync.key_feature_1": "Fixes the mismatch between the positions of the sun and moons and the lighting direction", "feature.sky_sync.key_feature_2": "Includes a configurable alternative sun path for more realistic and dramatic lighting", "feature.sky_sync.key_feature_3": "Smoothly switches the light source between the sun and moons based on visibility", @@ -1189,6 +1560,18 @@ "feature.sky_sync.moon_light_source_secunda": "Secunda", "feature.sky_sync.moon_light_source_tooltip": "Select which moon casts shadows during the night.", "feature.sky_sync.name": "Sky Sync", + "feature.sky_sync.new_moon_intensity": "New Moon Intensity", + "feature.sky_sync.phase_full": "Full", + "feature.sky_sync.phase_new": "New", + "feature.sky_sync.phase_unknown": "Unknown", + "feature.sky_sync.phase_waning_crescent": "Waning Crescent", + "feature.sky_sync.phase_waning_gibbous": "Waning Gibbous", + "feature.sky_sync.phase_waning_quarter": "Waning Quarter", + "feature.sky_sync.phase_waxing_crescent": "Waxing Crescent", + "feature.sky_sync.phase_waxing_gibbous": "Waxing Gibbous", + "feature.sky_sync.phase_waxing_quarter": "Waxing Quarter", + "feature.sky_sync.shadow_transition_duration": "Shadow Transition Duration", + "feature.sky_sync.shadow_transition_duration_tooltip": "How long (in game-time units) the shadow direction takes to fade between sources. 100 = ~5 seconds at timescale 20.", "feature.sky_sync.sun_path": "Sun path", "feature.sky_sync.sun_path_custom": "Custom", "feature.sky_sync.sun_path_northern": "Northern Sky", @@ -1260,15 +1643,23 @@ "feature.terrain_helper.key_feature_5": "Compatibility layer for terrain enhancement mods", "feature.terrain_helper.name": "Terrain Helper", "feature.terrain_shadows.buffer_viewer": "Buffer Viewer", + "feature.terrain_shadows.current_worldspace": "Current worldspace: {} ({})", "feature.terrain_shadows.debug": "Debug", "feature.terrain_shadows.description": "Adds realistic shadow casting from terrain features using heightmap data to create accurate terrain shadows that enhance depth perception and visual realism.", "feature.terrain_shadows.enable_terrain_shadow": "Enable Terrain Shadow", + "feature.terrain_shadows.has_height_map": "Has height map: {}", "feature.terrain_shadows.key_feature_1": "Heightmap-based terrain shadow calculation", "feature.terrain_shadows.key_feature_2": "Dynamic shadow updates based on sun position", "feature.terrain_shadows.key_feature_3": "Support for custom heightmap files", "feature.terrain_shadows.key_feature_4": "Real-time shadow preprocessing and computation", "feature.terrain_shadows.key_feature_5": "Integration with existing shadow systems", + "feature.terrain_shadows.light_delta_z": "LightDeltaZ: ({}, {})", + "feature.terrain_shadows.light_px_dir": "LightPxDir: ({}, {})", "feature.terrain_shadows.name": "Terrain Shadows", + "feature.terrain_shadows.px_size": "PxSize: ({}, {})", + "feature.terrain_shadows.shadow_update_cb_data": "shadowUpdateCBData", + "feature.terrain_shadows.start_px_coord": "StartPxCoord: {}", + "feature.terrain_shadows.view_resize": "View Resize", "feature.terrain_variation.apply_to_lod_terrain": "Apply to LOD Terrain", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "Applies the tiling fix to LOD terrain objects.\nThis helps reduce the visible tiling effect on distant terrain.", "feature.terrain_variation.description": "Terrain Variation reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.", @@ -1341,8 +1732,61 @@ "feature.upscaling.dlss_model_preset_l": "Preset L", "feature.upscaling.dlss_model_preset_m": "Preset M", "feature.upscaling.dlss_model_preset_tooltip": "Choose which DLSS AI model preset to use.\nEach model offers different visual quality, performance, and motion stability.\nSet to 'Default' for automatic selection based on your Upscale Preset and hardware.", + "feature.upscaling.dlss_resolution_warning": "Warning: Requested resolution %.0f x %.0f exceeds maximum supported resolution %d x %d for DLSS.", + "feature.upscaling.dlss_will_not_function": "DLSS will not function. Lower your resolution or select a different upscaling method.", + "feature.upscaling.ffx_dll_table_title": "AMD FidelityFX DLLs (click to open folder)", + "feature.upscaling.fg_warn_fidelityfx_missing": "Warning: FidelityFX DLLs are not loaded", + "feature.upscaling.fg_warn_refresh_rate": "Warning: Requires a high refresh rate monitor or Force Enable Frame Generation", + "feature.upscaling.fg_warn_windowed": "Warning: Requires windowed mode", "feature.upscaling.force_enable_frame_generation": "Force Enable Frame Generation", "feature.upscaling.force_enable_frame_generation_tooltip": "Bypass the high-refresh-rate monitor check so Frame Generation can run on lower-Hz\ndisplays. Useful for laptops and older monitors at the cost of less headroom for the\ngenerated frames.", + "feature.upscaling.foveated_active": "Active: foveated subrect DLSS is enabled (skipped in menus / on preflight failure).", + "feature.upscaling.foveated_band_width": "Band Width", + "feature.upscaling.foveated_blend_dither": "Dither", + "feature.upscaling.foveated_blend_dither_desc": "Noise-dithered fade — more natural-looking than feather at large subrects.", + "feature.upscaling.foveated_blend_feather": "Feather", + "feature.upscaling.foveated_blend_feather_desc": "Smoothstep fade over N pixels at the boundary. Hides the seam.", + "feature.upscaling.foveated_blend_hard_copy": "Hard Copy", + "feature.upscaling.foveated_blend_hard_copy_desc": "Sharp seam at the subrect boundary. Lowest cost.", + "feature.upscaling.foveated_blur_radius": "Blur Radius", + "feature.upscaling.foveated_dlss_mode_default": "Default", + "feature.upscaling.foveated_dlss_mode_default_desc": "Per-eye isolation: 5 copies per frame, 2 DLSS evaluates. All presets.", + "feature.upscaling.foveated_dlss_mode_faster": "Faster", + "feature.upscaling.foveated_dlss_mode_faster_desc": "Viewport offset: 1 snapshot, 2 mask clears, 2 DLSS evaluates. Presets J/K unavailable.", + "feature.upscaling.foveated_dlss_mode_header": "VR DLSS Mode", + "feature.upscaling.foveated_dlss_mode_label": "DLSS Mode", + "feature.upscaling.foveated_dlss_mode_tooltip": "Default — highest quality. Each eye gets its own isolated copy of color/depth/motion\nvectors so DLSS can't sample across the stereo midline. 5 copies per eye per frame.\nAll DLSS presets supported. Best for screenshots or when Faster shows edge artifacts.\n\nFaster — lower overhead. DLSS reads directly from the frame buffer using a viewport\noffset instead of isolating each eye. 1 snapshot + 2 mask clears per frame.\nDLSS may sample 1-2 pixels from the neighboring eye near the stereo center — usually\ninvisible in motion. Presets J and K are incompatible and auto-clamp to L.", + "feature.upscaling.foveated_dlss_unavailable": "DLSS runtime not available. Enable is blocked.", + "feature.upscaling.foveated_edge_blend_label": "Edge Blend", + "feature.upscaling.foveated_enable": "Enable Foveated DLSS", + "feature.upscaling.foveated_feather_width": "Feather Width", + "feature.upscaling.foveated_noise_amount": "Noise Amount", + "feature.upscaling.foveated_overview": "Foveated subrect DLSS: only the user-selected region gets full DLSS upscaling, the periphery is cheaply stretched. Significant DLSS cost reduction at the cost of peripheral sharpness. VR + DLSS only.", + "feature.upscaling.foveated_pending_restart": "Pending restart: FoveatedRender will %s on next launch.", + "feature.upscaling.foveated_periphery_aa_label": "Periphery AA", + "feature.upscaling.foveated_periphery_aa_none": "None", + "feature.upscaling.foveated_periphery_aa_temporal": "Temporal Smooth", + "feature.upscaling.foveated_periphery_aa_temporal_desc": "Blends the stretched periphery with motion-reprojected history to reduce flicker.", + "feature.upscaling.foveated_periphery_header": "Periphery Rendering", + "feature.upscaling.foveated_periphery_tooltip": "The area outside your selected subrect is filled cheaply rather than running DLSS.\nThese settings control how that cheap fill looks and whether it flickers.\n\nStretch method: how pixels outside the subrect are reconstructed from the lower-res\nrender buffer. Does not affect the DLSS subrect region at all.\n\nPeriphery AA: reduces temporal flicker in the stretched area using motion-compensated\nhistory blending. Independent of the DLSS subrect.\n\nEdge Blend: controls how the DLSS subrect edge meets the stretched periphery.\nHard Copy leaves a sharp seam; Feather/Dither soften it. Only affects the boundary.", + "feature.upscaling.foveated_screenshot_subrect_note": "Screenshot has its own subrect; align them only if you want pixel-matched captures.", + "feature.upscaling.foveated_shared_panel_note": "Quality, Sharpness, and DLSS Preset are on the main Upscaling panel — changes there apply to foveated rendering too.", + "feature.upscaling.foveated_smoothing": "Smoothing", + "feature.upscaling.foveated_smoothing_tooltip": "Lower = more temporal history (smoother but may ghost). Higher = more responsive.", + "feature.upscaling.foveated_standing_by": "Standing by: only active while the Upscaling Method is DLSS. Inactive right now.", + "feature.upscaling.foveated_stretch_bilinear": "Bilinear", + "feature.upscaling.foveated_stretch_bilinear_desc": "Bilinear: smooth upscale of the render buffer. Looks soft but clean.", + "feature.upscaling.foveated_stretch_gaussian": "Gaussian Blur", + "feature.upscaling.foveated_stretch_gaussian_desc": "Gaussian: blurs the periphery further into soft focus. Good default for foveated use.", + "feature.upscaling.foveated_stretch_label": "Stretch", + "feature.upscaling.foveated_stretch_point": "Point", + "feature.upscaling.foveated_stretch_point_desc": "Point: cheapest, visibly pixelated. Good for benchmarking foveated savings.", + "feature.upscaling.foveated_subrect_region_desc": "Drag in the preview below to select the region that gets full DLSS upscaling. The rest is cheaply stretched — saves significant DLSS cost.", + "feature.upscaling.foveated_subrect_region_header": "Subrect Region", + "feature.upscaling.foveated_tuning": "Foveated DLSS — Tuning", + "feature.upscaling.foveated_visualize_regions": "Visualize regions", + "feature.upscaling.foveated_visualize_regions_tooltip": "Diagnostic: tint the cheap-stretched periphery red so the DLSS-reconstructed\nsubrect (un-tinted) pops visually in-game. Lets you confirm at a glance where\nDLSS is actually running vs where the cheap stretch is filling. No perf impact;\nruntime toggle, no restart needed.", + "feature.upscaling.foveated_vr_only": "VR only. Non-VR / FSR support pending future contributors.", "feature.upscaling.fps_limit": "FPS Limit", "feature.upscaling.fps_limit_tooltip_1": "Set your frame cap target.", "feature.upscaling.fps_limit_tooltip_2": "Start about 2-3 FPS below refresh rate (e.g. 117 for 120 Hz).", @@ -1354,6 +1798,7 @@ "feature.upscaling.frame_generation_in_menus_tooltip_2": "May feel smoother, but increases menu input latency.", "feature.upscaling.frame_generation_proxy_note": "Requires a D3D11 to D3D12 proxy which can create compatibility issues", "feature.upscaling.frame_generation_tech": "Uses AMD FSR Frame Generation technology", + "feature.upscaling.frame_generation_tooltip": "Interpolate real frames with generated ones for a smoother experience. Uses AMD FSR Frame\nGeneration. Requires a D3D11-to-D3D12 proxy swapchain which can introduce compatibility\nissues; in particular, frame generation works only in windowed mode.", "feature.upscaling.frame_limit_refresh_rate": "Allows frame generation to function on low refresh rate monitors. Detected: %.2f Hz", "feature.upscaling.frame_limit_vrr": "Frame Limit (Variable Refresh Rate)", "feature.upscaling.key_feature_1": "DLSS (Deep Learning Super Sampling) support", @@ -1368,11 +1813,14 @@ "feature.upscaling.low_latency_mode_tooltip_2": "Can reduce max FPS a little, but usually feels more responsive.", "feature.upscaling.marker_optimization_unavailable": "Marker optimization unavailable (PCL not loaded).", "feature.upscaling.method": "Method", + "feature.upscaling.method_locked_opencomposite": "Locked to None while OpenComposite has %s=true.", "feature.upscaling.method_none": "None", "feature.upscaling.method_taa": "TAA", + "feature.upscaling.method_tooltip": "Selects the upscaling backend.", "feature.upscaling.name": "Upscaling", "feature.upscaling.native_inputs": "Native Inputs", "feature.upscaling.nvidia_reflex": "NVIDIA Reflex", + "feature.upscaling.perfmode_active_note": "Render-at-upscaled-resolution is active: Method and Upscale Preset changes only take effect after a game restart. Sharpness / model preset / Reflex remain live.", "feature.upscaling.preset_balanced": "Balanced", "feature.upscaling.preset_dlaa": "DLAA", "feature.upscaling.preset_native_aa": "Native AA", @@ -1381,7 +1829,12 @@ "feature.upscaling.preset_ultra_performance": "Ultra Performance", "feature.upscaling.reflex_blocked_by_fg": "Reflex is unavailable while the DX12 frame-generation swapchain is active.", "feature.upscaling.reflex_not_available": "Reflex is not available. Ensure sl.reflex.dll is present and restart.", + "feature.upscaling.render_at_upscale_res": "Render engine at upscaled resolution", + "feature.upscaling.render_at_upscale_res_native_noop": "No effect at Native AA (1x) — renders at full resolution; raise the Upscale Preset to engage.", + "feature.upscaling.render_at_upscale_res_requires": "Render-at-upscaled-resolution requires DLSS or FSR — switch upscaler Method to activate.", + "feature.upscaling.render_at_upscale_res_tooltip": "On by default. The engine pipeline allocates render targets at the upscaled-render\nresolution instead of the HMD display resolution; the upscaler (DLSS or FSR) writes\nits output to a private DisplayRes texture. Substantial VRAM and bandwidth savings,\nespecially at high HMD resolutions.\n\nLocked to the Upscale Preset selected at launch: changing the preset (or this\ntoggle) takes effect after a game restart. At Native AA (1.0x) there is no\nrender-res reduction, so the lock stays off and preset changes apply live.\n\nRequires DLSS or FSR. Sharpness / model preset / Reflex remain live.", "feature.upscaling.sharpness": "Sharpness", + "feature.upscaling.sl_dll_table_title": "NVIDIA Streamline DLLs (click to open folder)", "feature.upscaling.streamline_log_level_default": "Default", "feature.upscaling.streamline_log_level_off": "Off", "feature.upscaling.streamline_log_level_verbose": "Verbose", @@ -1401,6 +1854,7 @@ "feature.volumetric_lighting.enable_exteriors": "Enable Volumetric Lighting in Exteriors", "feature.volumetric_lighting.enable_exteriors_tooltip": "Volumetric god-rays / fog scattering in exterior cells.", "feature.volumetric_lighting.enable_interiors": "Enable Volumetric Lighting in Interiors", + "feature.volumetric_lighting.enable_interiors_tooltip": "Volumetric god-rays / fog scattering in interior cells.", "feature.volumetric_lighting.exterior_depth": "Exterior Depth", "feature.volumetric_lighting.exterior_height": "Exterior Height", "feature.volumetric_lighting.exterior_quality": "Exterior Quality", @@ -1419,20 +1873,247 @@ "feature.volumetric_lighting.quality_high": "High", "feature.volumetric_lighting.quality_low": "Low", "feature.volumetric_lighting.quality_medium": "Medium", + "feature.volumetric_shadows.buffer_viewer": "Buffer Viewer", + "feature.volumetric_shadows.debug": "Debug", "feature.volumetric_shadows.description": "Volumetric Shadows provides downsampled VSM shadow maps for use by effects like particles and decals.\nThis improves shadow quality on transparent objects with minimal performance impact.", "feature.volumetric_shadows.key_feature_1": "Downsampled VSM shadows", "feature.volumetric_shadows.key_feature_2": "Gaussian blur filtering", "feature.volumetric_shadows.key_feature_3": "Multi-cascade support", "feature.volumetric_shadows.key_feature_4": "Optimized for effects rendering", "feature.volumetric_shadows.name": "Volumetric Shadows", + "feature.volumetric_shadows.view_resize": "View Resize", + "feature.volumetric_shadows.vsm_cascade_0": "VSM Cascade 0", + "feature.volumetric_shadows.vsm_cascade_1": "VSM Cascade 1", + "feature.vr.attach_controller_primary": "Primary Controller", + "feature.vr.attach_controller_secondary": "Secondary Controller", + "feature.vr.attach_mode": "Attach Mode", + "feature.vr.attach_mode_both": "Both", + "feature.vr.attach_mode_controller_only": "Controller Only", + "feature.vr.attach_mode_hmd_only": "HMD Only", + "feature.vr.attach_mode_none": "None (Disabled)", + "feature.vr.attach_to_controller": "Attach to Controller", + "feature.vr.auto_hide_timeout": "Auto-hide Welcome overlay timeout", + "feature.vr.auto_hide_timeout_tooltip": "Set to 0 to hide the overlay, or a positive value to show it for that many seconds", + "feature.vr.auto_reset_distance": "Auto Reset Distance (game units)", + "feature.vr.auto_reset_distance_tooltip": "If you move farther than this distance from the menu, it will automatically reset to your HMD position. %s", + "feature.vr.binding_desc_close_menu": "Button combination to close the Open Shaders menu", + "feature.vr.binding_desc_close_overlay": "Button combination to close the VR overlay", + "feature.vr.binding_desc_open_menu": "Button combination to open the Open Shaders menu", + "feature.vr.binding_desc_open_overlay": "Button combination to open the VR overlay", + "feature.vr.bindings_col_action": "Action", + "feature.vr.bindings_col_current": "Current Binding", + "feature.vr.bindings_col_description": "Description", + "feature.vr.clear_button": "Clear", + "feature.vr.combo_settings_header": "Combo Settings", + "feature.vr.combo_timeout": "Combo Timeout", + "feature.vr.combo_timeout_tooltip": "Time limit for recording button combinations.", + "feature.vr.combo_type_close_menu": "Close the Open Shaders Menu", + "feature.vr.combo_type_close_overlay": "Close VR Overlay", + "feature.vr.combo_type_open_menu": "Open the Open Shaders Menu", + "feature.vr.combo_type_open_overlay": "Open VR Overlay", + "feature.vr.controller_diagnostics_header": "Controller Diagnostics", + "feature.vr.controller_input_header": "Controller Input Instructions", + "feature.vr.controller_offset_settings": "Controller Offset Settings", + "feature.vr.controller_offset_x": "Controller Offset X", + "feature.vr.controller_offset_y": "Controller Offset Y", + "feature.vr.controller_offset_z": "Controller Offset Z", + "feature.vr.depth_culling_exteriors": "Enable Depth Buffer Culling in Exteriors", + "feature.vr.depth_culling_exteriors_tooltip": "Improves performance in exteriors, recommended ON.", + "feature.vr.depth_culling_interiors": "Enable Depth Buffer Culling in Interiors", + "feature.vr.depth_culling_interiors_tooltip": "Improves performance in interiors, recommended ON.", "feature.vr.description": "Provides VR-specific optimizations and enhancements for Open Shaders, improving performance and visual quality in virtual reality environments.", + "feature.vr.diag_btn_ax": "A/X", + "feature.vr.diag_btn_by": "B/Y", + "feature.vr.diag_btn_grip": "Grip", + "feature.vr.diag_btn_grip_alt": "GripAlt", + "feature.vr.diag_btn_stick_click": "Stick Click", + "feature.vr.diag_btn_touchpad_alt": "Touchpad Alt", + "feature.vr.diag_btn_touchpad_click": "Touchpad Click", + "feature.vr.diag_btn_trigger": "Trigger", + "feature.vr.diag_col_button": "Button", + "feature.vr.diag_col_primary_held": "Primary Held (s)", + "feature.vr.diag_col_primary_state": "Primary State", + "feature.vr.diag_col_primary_type": "Primary Type", + "feature.vr.diag_col_secondary_held": "Secondary Held (s)", + "feature.vr.diag_col_secondary_state": "Secondary State", + "feature.vr.diag_col_secondary_type": "Secondary Type", + "feature.vr.diag_pressed": "Pressed", + "feature.vr.diag_released": "Released", + "feature.vr.diag_type_click": "Click", + "feature.vr.diag_type_held": "Held", + "feature.vr.diag_type_hold": "Hold", + "feature.vr.diag_type_none": "-", + "feature.vr.diagnostics_button_state": "Button State", + "feature.vr.diagnostics_test_mode": "Test Mode: Disable controller menu input (except scroll controller and triggers)", + "feature.vr.drag_controller_attached": "Controller Attached: Only the opposite hand can drag the controller overlay", + "feature.vr.drag_depth_adjustment": "Depth Adjustment (Grip + Thumbstick):", + "feature.vr.drag_depth_thumbstick": "While gripping to drag, use the thumbstick on the same hand to adjust depth", + "feature.vr.drag_fixed_world": "Fixed World Position: Any controller can drag (HMD-only mode) or attached controller only (Both modes)", + "feature.vr.drag_highlight_color": "Drag Highlight Color", + "feature.vr.drag_highlight_color_tooltip": "Color used to highlight draggable overlays in VR.", + "feature.vr.drag_hmd_relative": "HMD Relative: Any controller can drag (HMD-only mode) or attached controller only (Both modes)", + "feature.vr.drag_instructions_header": "Drag Instructions", + "feature.vr.drag_overlay_positioning": "Overlay Positioning (Grip + Drag):", + "feature.vr.drag_settings_header": "Drag Settings", + "feature.vr.drag_thumbstick_back": "Thumbstick back: Pull overlay closer", + "feature.vr.drag_thumbstick_forward": "Thumbstick forward: Push overlay farther away", + "feature.vr.enable_drag_reposition": "Enable drag to reposition overlays", + "feature.vr.enable_wand_pointing": "Enable Wand Pointing", + "feature.vr.enable_wand_pointing_tooltip": "Use controller ray-casting to point at UI elements", + "feature.vr.events_col_device": "Device", + "feature.vr.events_col_event_type": "Event Type", + "feature.vr.events_col_keycode_x": "KeyCode/X", + "feature.vr.events_col_known_mapping": "Known Mapping", + "feature.vr.events_col_pressed": "Pressed", + "feature.vr.events_col_value_y": "Value/Y", + "feature.vr.events_note": "Note: For thumbstick events, KeyCode/Value columns show X/Y floats.", + "feature.vr.events_section": "Recent VR Controller Events", + "feature.vr.events_type_click": "Click (%.2fs)", + "feature.vr.events_type_held_for": "Held for %.2fs", + "feature.vr.events_type_hold": "Hold (%.2fs)", + "feature.vr.events_type_none": "-", + "feature.vr.events_type_press": "Press", + "feature.vr.events_type_release": "Release", + "feature.vr.fixed_world_pos_settings": "Fixed World Position Settings", + "feature.vr.foveated_effects_header": "Foveated Effects", + "feature.vr.foveated_hard_cutoff_tooltip": "Hard-skip SSR outside the center region instead of a feathered falloff.\nCheaper, but the transition edge may be visible. Default off (feathered).", + "feature.vr.foveated_requires_dlss": "Requires Foveated DLSS to be active (Upscaling settings).", + "feature.vr.foveated_requires_ssr": "Requires Screen Space Reflections (Dynamic Cubemaps).", + "feature.vr.foveated_ssr_raymarching": "Foveate SSR Raymarching", + "feature.vr.foveated_ssr_raymarching_tooltip": "Reduces screen-space reflection raymarching toward the periphery, using the\nactive Foveated DLSS region. Central reflections stay full quality; peripheral\npixels fall back to the cubemap / water reflection. VR only.", + "feature.vr.general_settings_header": "General Settings", + "feature.vr.hmd_offset_settings": "HMD Offset Settings", + "feature.vr.hmd_offset_x": "HMD Offset X", + "feature.vr.hmd_offset_y": "HMD Offset Y", + "feature.vr.hmd_offset_z": "HMD Offset Z", + "feature.vr.input_ax_both": "A/X (Both Controllers)", + "feature.vr.input_by_primary": "B/Y (Primary Controller)", + "feature.vr.input_by_secondary": "B/Y (Secondary Controller)", + "feature.vr.input_enter": "Enter", + "feature.vr.input_grip_both": "Grip (Both Controllers)", + "feature.vr.input_left_mouse_button": "Left mouse button", + "feature.vr.input_middle_mouse_button": "Middle mouse button", + "feature.vr.input_middle_mouse_button_2": "Middle mouse button", + "feature.vr.input_right_mouse_button": "Right mouse button", + "feature.vr.input_settings_header": "Input Settings", + "feature.vr.input_shift_tab": "Shift+Tab", + "feature.vr.input_stick_click_both": "Stick Click (Both Controllers)", + "feature.vr.input_tab": "Tab", + "feature.vr.input_touchpad_both": "Touchpad Click (Both Controllers)", + "feature.vr.input_trigger_both": "Trigger (Both Controllers)", + "feature.vr.instructions_close_menu": "Close the Open Shaders Menu:", + "feature.vr.instructions_close_overlay": "Close Overlay:", + "feature.vr.instructions_controller_input_section": "Menu Controller Input:", + "feature.vr.instructions_menu_section": "Menu (while in the main menu or tween menu):", + "feature.vr.instructions_open_menu": "Open the Open Shaders Menu:", + "feature.vr.instructions_open_overlay": "Open Overlay:", + "feature.vr.instructions_overlay_section": "Overlay (while in the main menu or tween menu):", + "feature.vr.joystick_settings": "Joystick Settings", "feature.vr.key_feature_1": "Depth buffer culling optimization for VR performance", "feature.vr.key_feature_2": "In-scene overlay menu with HMD/Controller/Fixed World attach modes", "feature.vr.key_feature_3": "VR controller input with customizable button mappings", "feature.vr.key_feature_4": "Grip-to-drag overlay positioning with depth control", "feature.vr.key_feature_5": "Configurable occlusion culling parameters", "feature.vr.key_feature_6": "Enhanced VR compatibility with SteamVR and OpenComposite", + "feature.vr.menu_pos_fixed_world": "Fixed World Position", + "feature.vr.menu_pos_hmd_relative": "HMD Relative", + "feature.vr.menu_positioning_method": "Menu Positioning Method", + "feature.vr.menu_scale": "Menu Scale", + "feature.vr.menu_settings_header": "Menu Settings", + "feature.vr.min_occludee_box_extent": "Min Occludee Box Extent", + "feature.vr.min_occludee_box_extent_tooltip": "Minimum bounding box dimensions for object occlusion culling. Lower values improve performance but may result in visual artifacts.", + "feature.vr.mouse_deadzone": "Mouse Deadzone", + "feature.vr.mouse_deadzone_tooltip": "Thumbstick deadzone for joystick cursor movement", + "feature.vr.mouse_speed": "Mouse Speed", + "feature.vr.mouse_speed_tooltip": "Speed multiplier for joystick cursor movement", "feature.vr.name": "VR", + "feature.vr.openvr_active_compatible": "OpenVR System: Active & Compatible", + "feature.vr.openvr_active_incompatible": "OpenVR System: Active but INCOMPATIBLE", + "feature.vr.openvr_addresses_header": "OpenVR Addresses", + "feature.vr.openvr_detection_method": "Detection Method:", + "feature.vr.openvr_dll_path": "DLL Path: %s", + "feature.vr.openvr_dll_size": "DLL Size: %llu bytes", + "feature.vr.openvr_dll_version": "DLL Version: %s", + "feature.vr.openvr_failed": "Failed", + "feature.vr.openvr_info_header": "OpenVR Information", + "feature.vr.openvr_interface_probing": " Interface Probing: %s", + "feature.vr.openvr_ivrcompositor": " IVRCompositor_021: %s", + "feature.vr.openvr_ivroverlay": " IVROverlay_016: %s", + "feature.vr.openvr_ivrsystem": " IVRSystem_017: %s", + "feature.vr.openvr_menus_disabled": "VR overlay menus disabled.", + "feature.vr.openvr_missing": "Missing", + "feature.vr.openvr_modified": "Modified: %s", + "feature.vr.openvr_not_available": "OpenVR system not available", + "feature.vr.openvr_ok": "OK", + "feature.vr.openvr_passed": "Passed", + "feature.vr.openvr_rendering": " Rendering: In-scene overlay (submit hook)", + "feature.vr.openvr_runtime": "Runtime: %s", + "feature.vr.overlay_auto_hide_countdown": "(This welcome message will auto-hide in %d seconds)", + "feature.vr.overlay_close_menu_label": "Close Menu: ", + "feature.vr.overlay_disable_location": "(Disable in: VR settings > Controller Input Instructions)", + "feature.vr.overlay_disable_tip": "Tip: Disable this VR overlay by setting Attach Mode to 'None' in VR settings.", + "feature.vr.overlay_grip_thumbstick_depth": "Grip + Thumbstick: Adjust overlay depth (closer/farther)", + "feature.vr.overlay_how_to_title": "How to Use VR Open Shaders Menu:", + "feature.vr.overlay_open_menu_first": "You must open the Main Menu or Tween Menu before VR controls work.", + "feature.vr.overlay_open_menu_label": "Open Menu: ", + "feature.vr.popup_enter_esc": "Press ENTER to accept, ESC to cancel", + "feature.vr.popup_press_buttons": "Press buttons to record combo...", + "feature.vr.popup_recorded_buttons": "Recorded buttons:", + "feature.vr.popup_recording_for": "Recording combo for: %s", + "feature.vr.popup_recording_note": "(During recording, any controller's buttons can be used. Requirement is only enforced during use.)", + "feature.vr.popup_time_remaining": "Time remaining: %.1f seconds", + "feature.vr.popup_unknown": "Unknown", + "feature.vr.record_combo_tooltip": "Click to start recording a new button combination for the selected action.", + "feature.vr.record_selected_combo": "Record Selected Combo", + "feature.vr.reset_menu_to_hmd": "Reset Menu to HMD Position", + "feature.vr.reset_to_defaults": "Reset to Defaults", + "feature.vr.reset_to_defaults_tooltip": "Reset all VR key bindings to their default values.", + "feature.vr.select_combo_to_record": "Select Combo to Record:", + "feature.vr.stereo_blend_color_threshold": "Color Difference Threshold", + "feature.vr.stereo_blend_color_threshold_tooltip": "Minimum luminance difference between eyes to trigger blending.\nSet to 0 to blend everywhere. Higher = more selective.\nDefault: 0.02", + "feature.vr.stereo_blend_depth_sigma": "Depth Sigma", + "feature.vr.stereo_blend_depth_sigma_tooltip": "Depth sensitivity for the bilateral weight.\nLower values are stricter -- only blend when depths match very closely.\nHigher values allow blending across slight depth differences.\nDefault: 0.01", + "feature.vr.stereo_blend_dev_mode": "Developer mode: no screen-space effects active.", + "feature.vr.stereo_blend_enable": "Enable Stereo Blend", + "feature.vr.stereo_blend_enable_tooltip": "Post-composite depth-aware bilateral blend between eyes.\nReduces stereo inconsistencies from screen-space effects (SSGI, SSR, etc.).\nEach pixel is reprojected to the other eye; blending is applied only where\ndepth agrees (same surface). Full-screen pass in VR.", + "feature.vr.stereo_blend_header": "Stereo Blend", + "feature.vr.stereo_blend_max_factor": "Max Blend Factor", + "feature.vr.stereo_blend_max_factor_tooltip": "Maximum blend strength between the two eyes.\nHigher values reduce screen-space effect flicker but destroy stereo depth.\nKeep below ~0.15 to preserve 3D parallax.\nDefault: 0.1", + "feature.vr.stereo_blend_requires_effect": "Requires an active screen-space effect (SSGI, SS Shadows, SSR).", + "feature.vr.stereo_debug_back_check": "Back-Check", + "feature.vr.stereo_debug_blend_weight": "Blend Weight", + "feature.vr.stereo_debug_edge_detection": "Edge Detection", + "feature.vr.stereo_debug_off": "Off", + "feature.vr.stereo_debug_overwrite": "Overwrite", + "feature.vr.stereo_debug_overwrite_eye1": "Overwrite Eye1", + "feature.vr.stereo_debug_view": "Debug View", + "feature.vr.stereo_debug_view_tooltip": "Selecting a debug mode auto-enables the required feature; setting back to Off restores it.\n\nOff: Normal rendering\nBack-Check: Round-trip reprojection validation (auto-enables Stereo Blend)\nBlend Weight: Heatmap of bilateral blend intensity (auto-enables Stereo Blend)\nEdge Detection: Highlights depth discontinuities (auto-enables Stereo Blend)\nOverwrite: Mode texture classification (auto-enables Reprojection -- restart required)\n Green=edge Pink=edge neighbour Blue=disoccluded Orange=full blend\nOverwrite Eye1: POM depth heatmap for Eye 1 (auto-enables Reprojection -- restart required)", + "feature.vr.stereo_reprojection_header": "Stereo Reprojection", + "feature.vr.tab_bindings": "Bindings", + "feature.vr.tab_debug": "Debug", + "feature.vr.tab_general": "General", + "feature.vr.tab_stereo": "Stereo", + "feature.vr.thumbstick_col_primary": "Primary Controller", + "feature.vr.thumbstick_col_secondary": "Secondary Controller", + "feature.vr.thumbstick_mouse_movement_attached": "Mouse movement (attached controller)", + "feature.vr.thumbstick_mouse_movement_hmd": "Mouse movement (HMD mode)", + "feature.vr.thumbstick_primary": "Primary Controller Thumbstick", + "feature.vr.thumbstick_scroll": "Scroll", + "feature.vr.thumbstick_secondary": "Secondary Controller Thumbstick", + "feature.vr.thumbstick_state_section": "VR Thumbstick State", + "feature.vr.thumbstick_xy_quadrant": "X: %+1.3f Y: %+1.3f [%s]", + "feature.vr.wand_col_property": "Property", + "feature.vr.wand_col_value": "Value", + "feature.vr.wand_controller_index": "Controller Index", + "feature.vr.wand_intersecting_overlay": "Intersecting Overlay", + "feature.vr.wand_no": "No", + "feature.vr.wand_pointing_enabled": "Wand Pointing Enabled", + "feature.vr.wand_ray_direction": "Ray Direction", + "feature.vr.wand_ray_origin": "Ray Origin", + "feature.vr.wand_state_section": "Wand Pointing State", + "feature.vr.wand_uv_coordinates": "UV Coordinates", + "feature.vr.wand_yes": "Yes", + "feature.vr.wand_yes_upper": "YES", "feature.vr_stereo.debug": "Debug", "feature.vr_stereo.debug_pom_depth": "Debug POM Depth", "feature.vr_stereo.disocclusion_depth_threshold": "Disocclusion Depth Threshold", @@ -1457,6 +2138,7 @@ "feature.water_effects.key_feature_4": "Improved water visual fidelity", "feature.water_effects.key_feature_5": "Atmospheric underwater effects", "feature.water_effects.name": "Water Effects", + "feature.wetness_effects.active_preset_format": "Active Preset: %s", "feature.wetness_effects.advanced": "Advanced", "feature.wetness_effects.breadth": "Breadth", "feature.wetness_effects.chance": "Chance", @@ -1523,6 +2205,8 @@ "feature.wetness_effects.climate_preset_unknown": "Unknown", "feature.wetness_effects.climate_presets": "Climate Presets", "feature.wetness_effects.current_climate_preset": "Current Climate Preset", + "feature.wetness_effects.current_settings_from_preset": "Current Settings (applied from preset):", + "feature.wetness_effects.current_shader_state": "Current Shader State", "feature.wetness_effects.custom_preset_tooltip_0": "Custom settings - you have modified the preset values.", "feature.wetness_effects.custom_preset_tooltip_1": "Select a preset above to apply predefined climate settings.", "feature.wetness_effects.debug": "Debug", @@ -1544,10 +2228,12 @@ "feature.wetness_effects.enable_wetness_override": "Enable Wetness Override", "feature.wetness_effects.enable_wetness_tooltip": "Enables a wetness effect near water and when it is raining.", "feature.wetness_effects.grid_size": "Grid Size", + "feature.wetness_effects.grid_size_format": "Grid Size: %.2f m (%.1f units)", "feature.wetness_effects.grid_size_tooltip_0": "Spatial grid size for raindrop placement (smaller = more grid cells, higher GPU cost)", "feature.wetness_effects.grid_size_tooltip_1": "This is the most performance-sensitive setting. Lower only if needed for realism.", "feature.wetness_effects.interior_exterior_override_tooltip": "If disabled, will only use the exterior value. ", "feature.wetness_effects.interval": "Interval", + "feature.wetness_effects.interval_format": "Interval: %.1f sec", "feature.wetness_effects.interval_tooltip": "How often raindrop effects are checked (lower = more frequent, moderate performance impact)", "feature.wetness_effects.key_feature_1": "Dynamic surface wetness based on weather conditions", "feature.wetness_effects.key_feature_2": "Realistic puddle formation and shore wetness effects", @@ -1556,6 +2242,7 @@ "feature.wetness_effects.key_feature_5": "Support for skin wetness and material-specific responses", "feature.wetness_effects.lifetime": "Lifetime", "feature.wetness_effects.max_radius": "Max Radius", + "feature.wetness_effects.meteorological_rain_types": "Meteorological rain types:", "feature.wetness_effects.meters_format": "{:.2f} meters", "feature.wetness_effects.min_radius": "Min Radius", "feature.wetness_effects.min_rain_wetness": "Min Rain Wetness", @@ -1564,6 +2251,15 @@ "feature.wetness_effects.open_weather_picker": "Open Weather Picker", "feature.wetness_effects.open_weather_picker_tooltip": "Open the Weather Picker in CS Utility", "feature.wetness_effects.portion_of_grid_size": "As portion of grid size.", + "feature.wetness_effects.precip_calc_tooltip_0": "Precipitation rates are calculated using shader mechanics:", + "feature.wetness_effects.precip_calc_tooltip_1": "- Raindrop chance (probability per interval)", + "feature.wetness_effects.precip_calc_tooltip_2": "- Grid size (spatial density)", + "feature.wetness_effects.precip_calc_tooltip_3": "- Interval (time between attempts)", + "feature.wetness_effects.precip_calc_tooltip_4": "- All values reflect what is sent to the shader.", + "feature.wetness_effects.precip_calc_tooltip_5": "Rates are shown in mm/hr, based on drops/sec and grid size.", + "feature.wetness_effects.precipitation_analysis": "Precipitation Analysis", + "feature.wetness_effects.precipitation_rate_calculation": "Precipitation Rate Calculation", + "feature.wetness_effects.puddle_formation_format": "Puddle Formation: %.1f%% min wetness", "feature.wetness_effects.puddle_max_angle": "Puddle Max Angle", "feature.wetness_effects.puddle_max_angle_tooltip": "How flat a surface needs to be for puddles to form on it.", "feature.wetness_effects.puddle_min_wetness": "Puddle Min Wetness", @@ -1571,11 +2267,26 @@ "feature.wetness_effects.puddle_radius": "Puddle Radius", "feature.wetness_effects.puddle_radius_tooltip": "The radius used to determine puddle size and location", "feature.wetness_effects.puddle_wetness": "Puddle Wetness", + "feature.wetness_effects.puddle_wetness_default_format": "Puddle Wetness: %.2f (default %.2f × %.1fx)", "feature.wetness_effects.puddle_wetness_in_exterior": "Puddle Wetness In/Exterior", "feature.wetness_effects.radius": "Radius", "feature.wetness_effects.rain_in_exterior": "Rain In/Exterior", + "feature.wetness_effects.rain_intensity": "Rain Intensity", + "feature.wetness_effects.rain_label_current": "Current", + "feature.wetness_effects.rain_label_max_heavy": "Max (in Heavy Rain)", "feature.wetness_effects.rain_system_state": "Rain System State", + "feature.wetness_effects.rain_type_extreme": "Extreme Rain", + "feature.wetness_effects.rain_type_extreme_range": "Extreme: >15 mm/hr", + "feature.wetness_effects.rain_type_heavy": "Heavy Rain", + "feature.wetness_effects.rain_type_heavy_range": "Heavy: 7.5 - 15 mm/hr", + "feature.wetness_effects.rain_type_light": "Light Rain", + "feature.wetness_effects.rain_type_light_range": "Light: <2.5 mm/hr", + "feature.wetness_effects.rain_type_moderate": "Moderate Rain", + "feature.wetness_effects.rain_type_moderate_range": "Moderate: 2.5 - 7.5 mm/hr", "feature.wetness_effects.rain_wetness": "Rain Wetness", + "feature.wetness_effects.rain_wetness_default_format": "Rain Wetness: %.2f (default %.2f × %.1fx)", + "feature.wetness_effects.raindrop_chance_format": "Raindrop Chance: %.1f%%", + "feature.wetness_effects.raindrop_chance_preset_format": "Raindrop Chance: %.1f%% (preset value)", "feature.wetness_effects.raindrop_effects": "Raindrop Effects", "feature.wetness_effects.raindrops": "Raindrops", "feature.wetness_effects.raindrops_help": "At every interval, a raindrop is placed within each grid cell.\nOnly a set portion of raindrops will actually trigger splashes and ripples.\n", @@ -1587,10 +2298,13 @@ "feature.wetness_effects.skin_wetness_tooltip": "How wet character skin and hair get during rain.", "feature.wetness_effects.splashes": "Splashes", "feature.wetness_effects.strength": "Strength", + "feature.wetness_effects.transition_speed_default_format": "Transition Speed: %.2f (default %.2f × %.1fx)", "feature.wetness_effects.vanilla_ripples_tooltip_0": "Enables default ripples (e.g., Ripples01).", "feature.wetness_effects.vanilla_ripples_tooltip_1": "Disabling may not take effect until the next weather change.", + "feature.wetness_effects.weather_transition_format": "Weather Transition: %.1f%%", "feature.wetness_effects.weather_transition_speed": "Weather transition speed", "feature.wetness_effects.weather_transition_speed_tooltip": "How fast wetness appears when raining and how quickly it dries after rain has stopped.", + "feature.wetness_effects.wetness": "Wetness", "feature.wetness_effects.wetness_effects": "Wetness Effects", "feature.wetness_effects.wetness_in_exterior": "Wetness In/Exterior", "menu.advanced.active_shaders_tooltip": "List of shaders that have been used in recent frames. Enable Shader Blocking above to use hotkeys to cycle through and block shaders for debugging. Shaders not used for ~1 second are removed from this list.", @@ -1996,7 +2710,10 @@ "menu.settings.file_label": "File: %s", "menu.settings.filter_colors": "Filter colors", "menu.settings.font": "Font", + "menu.settings.font_family_label": "{} Family##{}", "menu.settings.font_roles": "Font Roles", + "menu.settings.font_scale_label": "{} Scale##{}", + "menu.settings.font_style_label": "{} Style##{}", "menu.settings.frame_border_size": "Frame Border Size", "menu.settings.frame_padding": "Frame Padding", "menu.settings.frame_rounding": "Frame Rounding", @@ -2054,6 +2771,7 @@ "menu.settings.shader_failed": "Failed", "menu.settings.shader_fast": "Fast (<2s)", "menu.settings.shader_slow": "Slow (2-8s)", + "menu.settings.shader_thread_diagnostics": "Threads: %d compile, %d background, %d pool | P-cores: %d", "menu.settings.shader_very_slow": "Very slow (>=8s)", "menu.settings.show_footer": "Show Footer", "menu.settings.show_footer_tooltip": "Shows the footer with game version, swap chain, and GPU information at the bottom of the window", @@ -2123,16 +2841,60 @@ "menu.setup.press_to_close": "Press Escape or Enter to continue", "menu.toggle_error_message": "Toggle Error Message", "menu.toggle_error_message_tooltip": "Hide or show the shader failure message. Your installation is broken and will likely see errors in game. Please double check you have updated all features and that your load order is correct. See CommunityShaders.log for details and check the Nexus Mods page or Discord server.", + "overlay.background_prefix": "Background ", + "overlay.blocked_index": "Index: %zu/%zu", + "overlay.blocked_index_na": "Index: N/A (%zu active)", + "overlay.blocked_key": "Blocked: %s", + "overlay.blocked_shader_detail": "Type: %s | Class: %s | Descriptor: 0x%X", + "overlay.compiling_shaders": "{}Compiling Shaders: {}", "overlay.modified_features": "Features that may have modified shaders detected. Check Feature Issues in the Menu.", "overlay.shader_blocking_active": "Shader Blocking Active", + "overlay.shaders_failed": "ERROR: %llu shaders failed to compile. Check installation and CommunityShaders.log", + "overlay.skip_compilation": "Press {} to proceed without completing shader compilation. ", + "overlay.slow_shaders": "Slow shaders: %llu (very slow: %llu)", + "overlay.threads_status": "Threads: %d / %d limit | Heavy: %d / %d P-cores | %d workers", "overlay.uncompiled_warning": "WARNING: Uncompiled shaders will have visual errors or cause stuttering when loading.", "ui.cancel": "Cancel", "ui.clear_cache": "Clear Cache", "ui.clear_cache_confirm": "Are you sure you want to clear the shader cache?", "ui.clear_cache_desc": "This will clear all compiled shaders from memory and disk cache (if enabled). Shaders will be recompiled when the game next encounters them.", "ui.clear_shader_cache": "Clear Shader Cache?", + "ui.click_to_open_cs_editor": "Click to open CS Editor", + "ui.constraint.consider_disable": "Consider disabling this feature at boot for best compatibility.", + "ui.constraint.constrained_by": "This setting is constrained by:", + "ui.constraint.forced_value": "Forced value: %s", + "ui.constraint.setting_constrained": "Setting Constrained", "ui.copy": "Copy", "ui.dont_ask_again": "Don't ask me again", + "ui.input.clear_binding": "Clear Binding", + "ui.input.click_to_bind": "(Click to bind)", + "ui.input.indicator_both": "(Both)", + "ui.input.indicator_mixed": "(Mixed)", + "ui.input.indicator_primary": "(Primary)", + "ui.input.indicator_secondary": "(Secondary)", + "ui.input.record_hint": "Press any key combination.\nModifiers (Ctrl, Shift, Alt) are supported.\nPress Escape to cancel.", + "ui.input.recording": "Recording... (Esc to cancel)", "ui.search": "Search...", - "ui.search_features": "Search Features..." + "ui.search_features": "Search Features...", + "ui.subrect.crop_preset": "Crop Preset", + "ui.subrect.custom": "(Custom)", + "ui.subrect.delete_preset": "Delete Preset", + "ui.subrect.interactive_cropping": "Interactive Cropping (Drag on the image to select)", + "ui.subrect.position_uv": "Position UV (X, Y)", + "ui.subrect.preview_unavailable": "Preview unavailable.", + "ui.subrect.reset_crop": "Reset Crop", + "ui.subrect.save_as": "Save As", + "ui.subrect.save_preset": "Save Preset", + "ui.subrect.size_uv": "Size UV (W, H)", + "ui.table.dll_name": "DLL Name", + "ui.table.version": "Version", + "ui.vr.both_controllers": "(Both Controllers)", + "ui.vr.color_coding": "Color coding:", + "ui.vr.color_coding_both": "Green = Both controllers (Yellow + Blue)", + "ui.vr.color_coding_primary": "Yellow = Primary controller", + "ui.vr.color_coding_secondary": "Blue = Secondary controller", + "ui.vr.primary_controller": "(Primary Controller)", + "ui.vr.secondary_controller": "(Secondary Controller)", + "ui.weather_override_active": "Weather Override Active", + "ui.weather_setting_controlled": "This setting is controlled by the current weather (%s)." } diff --git a/src/CSEditor/EditorWindow.cpp b/src/CSEditor/EditorWindow.cpp index fce755896e..7af9dc9e92 100644 --- a/src/CSEditor/EditorWindow.cpp +++ b/src/CSEditor/EditorWindow.cpp @@ -669,7 +669,7 @@ void EditorWindow::ShowObjectsWindow() // Display current cell name const char* cellName = cell->GetName(); - std::string displayName = cellName && cellName[0] ? cellName : "[Unnamed Cell]"; + std::string displayName = cellName && cellName[0] ? cellName : T(TKEY("unnamed_cell"), "[Unnamed Cell]"); std::string label = displayName; // Highlight current cell (before TableRowSelectable so hover/active can override) @@ -1143,7 +1143,8 @@ void EditorWindow::RenderUI() ImGui::PopStyleColor(); } ImGui::PopStyleVar(2); - Util::AddTooltip(canUndo ? std::format("Undo (Ctrl+Z) - {} states", (int)undoStack.size()).c_str() : T(TKEY("undo_no_changes"), "Undo (Ctrl+Z) - No changes to undo")); + const int undoStateCount = (int)undoStack.size(); + Util::AddTooltip(canUndo ? std::vformat(T(TKEY("undo_states"), "Undo (Ctrl+Z) - {} states"), std::make_format_args(undoStateCount)).c_str() : T(TKEY("undo_no_changes"), "Undo (Ctrl+Z) - No changes to undo")); } // Right-aligned items — use SetCursorScreenPos to bypass menu bar GroupOffset diff --git a/src/CSEditor/InteriorOnlyPanel.cpp b/src/CSEditor/InteriorOnlyPanel.cpp index bd40c0f55d..47b95bec8d 100644 --- a/src/CSEditor/InteriorOnlyPanel.cpp +++ b/src/CSEditor/InteriorOnlyPanel.cpp @@ -204,9 +204,9 @@ namespace InteriorOnlyPanel if (Util::ErrorButton("X", ImVec2(C::SCENE_DELETE_BUTTON_WIDTH * scale, 0))) { if (entry.source == EntrySource::Overwrite) { pendingDeleteIndex = index; - deleteSingleOverwritePopup.message = std::format( - "Delete overwrite file '{}'?\nThis will permanently remove the file from disk.", - entry.sourceFilename); + deleteSingleOverwritePopup.message = std::vformat( + T(TKEY("delete_overwrite_file_confirm"), "Delete overwrite file '{}'?\nThis will permanently remove the file from disk."), + std::make_format_args(entry.sourceFilename)); deleteSingleOverwritePopup.Request(); } else { manager->RemoveSetting(kSceneType, index); diff --git a/src/CSEditor/Weather/WeatherWidget.cpp b/src/CSEditor/Weather/WeatherWidget.cpp index 19080c838e..08ec3ae176 100644 --- a/src/CSEditor/Weather/WeatherWidget.cpp +++ b/src/CSEditor/Weather/WeatherWidget.cpp @@ -420,6 +420,7 @@ void WeatherWidget::DrawWidget() void WeatherWidget::LoadSettings() { bool hadErrors = false; + std::string editorIdForNotify; // lvalue backing for std::make_format_args in failure notifications if (!js.empty()) { try { // Attempt to load settings from JSON @@ -442,8 +443,9 @@ void WeatherWidget::LoadSettings() if (hadErrors) { // Fallback to vanilla/game values settings = vanillaSettings; + editorIdForNotify = GetEditorID(); EditorWindow::GetSingleton()->ShowNotification( - std::format("Some values failed to load for {}", GetEditorID()), + std::vformat(T(TKEY("some_values_failed_to_load"), "Some values failed to load for {}"), std::make_format_args(editorIdForNotify)), Util::Colors::GetError(), 3.0f); } else { @@ -477,8 +479,9 @@ void WeatherWidget::LoadSettings() logger::error("Weather {}: Failed to deserialize settings from JSON: {}", GetEditorID(), e.what()); // Fallback to vanilla/game values on exception settings = vanillaSettings; + editorIdForNotify = GetEditorID(); EditorWindow::GetSingleton()->ShowNotification( - std::format("Some values failed to load for {}", GetEditorID()), + std::vformat(T(TKEY("some_values_failed_to_load"), "Some values failed to load for {}"), std::make_format_args(editorIdForNotify)), Util::Colors::GetError(), 3.0f); return; @@ -1139,10 +1142,10 @@ void WeatherWidget::DrawCloudSettings() ImGui::Spacing(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.3f); - if (WeatherUtils::DrawSliderInt8(std::format("Cloud Layer Speed Y##{}", layer), settings.clouds[i].cloudLayerSpeedY)) + if (WeatherUtils::DrawSliderInt8(std::vformat(T(TKEY("cloud_layer_speed_y"), "Cloud Layer Speed Y##{}"), std::make_format_args(layer)), settings.clouds[i].cloudLayerSpeedY)) changed = true; ImGui::Spacing(); - if (WeatherUtils::DrawSliderInt8(std::format("Cloud Layer Speed X##{}", layer), settings.clouds[i].cloudLayerSpeedX)) + if (WeatherUtils::DrawSliderInt8(std::vformat(T(TKEY("cloud_layer_speed_x"), "Cloud Layer Speed X##{}"), std::make_format_args(layer)), settings.clouds[i].cloudLayerSpeedX)) changed = true; ImGui::PopItemWidth(); @@ -1612,8 +1615,9 @@ void WeatherWidget::InheritAllFromParent() if (EditorWindow::GetSingleton()->settings.autoApplyChanges) ApplyChanges(); + const std::string parentId = parentWidget->GetEditorID(); EditorWindow::GetSingleton()->ShowNotification( - std::format("Inherited all settings from {}", parentWidget->GetEditorID()), + std::vformat(T(TKEY("inherited_all_settings_from"), "Inherited all settings from {}"), std::make_format_args(parentId)), Util::Colors::GetSuccess(), 3.0f); } @@ -1681,8 +1685,9 @@ void WeatherWidget::LoadFeatureSettings() } // Show notification + const std::string editorIdForWarn = GetEditorID(); EditorWindow::GetSingleton()->ShowNotification( - std::format("Warning: {} references missing feature(s): {}", GetEditorID(), missingList), + std::vformat(T(TKEY("references_missing_features"), "Warning: {} references missing feature(s): {}"), std::make_format_args(editorIdForWarn, missingList)), Util::Colors::GetWarning(), 5.0f); @@ -1694,9 +1699,10 @@ void WeatherWidget::LoadFeatureSettings() FeatureIssues::AddFeatureIssue( featureName, "", - std::format("Weather '{}' contains settings for this feature, but the feature is not loaded. " - "The weather-specific parameters will be ignored until the feature is installed and loaded.", - GetEditorID()), + std::vformat(T(TKEY("weather_references_unloaded_feature"), + "Weather '{}' contains settings for this feature, but the feature is not loaded. " + "The weather-specific parameters will be ignored until the feature is installed and loaded."), + std::make_format_args(editorIdForWarn)), FeatureIssues::FeatureIssueInfo::IssueType::UNKNOWN, fileInfo, ""); diff --git a/src/Features/CSEditor.cpp b/src/Features/CSEditor.cpp index 485386f731..72b97348c2 100644 --- a/src/Features/CSEditor.cpp +++ b/src/Features/CSEditor.cpp @@ -465,8 +465,8 @@ void CSEditor::DisplayWeatherBasicInfo(RE::TESWeather* weather, float weatherPct bool showTooltip = CSEditor::RenderMultiColorWeatherName(weather, weatherText); if (showTooltip) { ImGui::BeginTooltip(); - ImGui::Text(T(TKEY("tooltip_name"), "Name: %s"), weather->GetName() ? weather->GetName() : "Unnamed"); - ImGui::Text(T(TKEY("tooltip_editor_id_2"), "Editor ID: %s"), weather->GetFormEditorID() ? weather->GetFormEditorID() : "None"); + ImGui::Text(T(TKEY("tooltip_name"), "Name: %s"), weather->GetName() ? weather->GetName() : T(TKEY("unnamed"), "Unnamed")); + ImGui::Text(T(TKEY("tooltip_editor_id_2"), "Editor ID: %s"), weather->GetFormEditorID() ? weather->GetFormEditorID() : T(TKEY("none_value"), "None")); ImGui::Text(T(TKEY("tooltip_form_id_2"), "Form ID: 0x%08X"), weather->GetFormID()); auto flagNames = CSEditor::GetWeatherFlagNames(weather); if (!flagNames.empty()) { @@ -812,8 +812,8 @@ void CSEditor::RenderWeatherControls(RE::Sky* sky, bool showSectionHeader) if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::Text(T(TKEY("tooltip_weather_name"), "Weather: %s"), weather->GetName() ? weather->GetName() : "Unnamed"); - ImGui::Text(T(TKEY("tooltip_editor_id"), "Editor ID: %s"), weather->GetFormEditorID() ? weather->GetFormEditorID() : "None"); + ImGui::Text(T(TKEY("tooltip_weather_name"), "Weather: %s"), weather->GetName() ? weather->GetName() : T(TKEY("unnamed"), "Unnamed")); + ImGui::Text(T(TKEY("tooltip_editor_id"), "Editor ID: %s"), weather->GetFormEditorID() ? weather->GetFormEditorID() : T(TKEY("none_value"), "None")); ImGui::Text(T(TKEY("tooltip_form_id"), "Form ID: 0x%08X"), weather->GetFormID()); ImGui::EndTooltip(); } diff --git a/src/Features/RemoteControl.cpp b/src/Features/RemoteControl.cpp index da8542aadf..69b40a0ab5 100644 --- a/src/Features/RemoteControl.cpp +++ b/src/Features/RemoteControl.cpp @@ -10,6 +10,7 @@ #include "Features/RemoteControl/DevBenchBridge.h" #include "Globals.h" +#include "I18n/I18n.h" #include "Menu.h" #include @@ -18,6 +19,8 @@ #include #include +#define I18N_KEY_PREFIX "feature.remote_control." + using json = nlohmann::json; #ifdef DEVBENCH_BRIDGE_ENABLED @@ -68,18 +71,18 @@ void RemoteControl::DrawSettings() { const auto& theme = Menu::GetSingleton()->GetTheme().StatusPalette; - ImGui::TextWrapped( - "Registers graphics-feature, inspect, capture, shader-cache, and settings tools " - "into the external devbench host so AI assistants (Claude Code, Cursor, etc.) can " - "toggle features, inspect engine state, trigger captures, and save/load settings " - "over MCP and REST. There is no in-game server — install the devbench SKSE plugin " - "to enable the integration."); + ImGui::TextWrapped("%s", T(TKEY("description"), + "Registers graphics-feature, inspect, capture, shader-cache, and settings tools " + "into the external devbench host so AI assistants (Claude Code, Cursor, etc.) can " + "toggle features, inspect engine state, trigger captures, and save/load settings " + "over MCP and REST. There is no in-game server — install the devbench SKSE plugin " + "to enable the integration.")); ImGui::Spacing(); #ifdef DEVBENCH_BRIDGE_ENABLED auto* dvb = DevBenchAPI::GetDevBenchInterface001(); if (dvb) { - ImGui::TextColored(theme.SuccessColor, "devbench host present (build %u)", dvb->GetBuildNumber()); + ImGui::TextColored(theme.SuccessColor, T(TKEY("host_present"), "devbench host present (build %u)"), dvb->GetBuildNumber()); // Cache the port — runtime.json I/O + JSON parse every frame would hitch the UI while // the panel is open. Refresh on a coarse interval (devbench may bind after the panel @@ -94,30 +97,34 @@ void RemoteControl::DrawSettings() lastReadQpc = nowQpc.QuadPart; } if (cachedPort > 0) { - ImGui::Text("Host bound on port %d (from %s)", cachedPort, kRuntimeJsonPath); + ImGui::Text(T(TKEY("port_bound"), "Host bound on port %d (from %s)"), cachedPort, kRuntimeJsonPath); } else { ImGui::TextDisabled( - "Port unknown — devbench writes it to %s once it binds.", + T(TKEY("port_unknown"), "Port unknown — devbench writes it to %s once it binds."), kRuntimeJsonPath); } } else { - ImGui::TextColored(theme.Warning, - "devbench host not detected. Install the devbench SKSE plugin; " - "the tools register automatically once it is present."); + ImGui::TextColored(theme.Warning, "%s", + T(TKEY("host_not_detected"), + "devbench host not detected. Install the devbench SKSE plugin; " + "the tools register automatically once it is present.")); } ImGui::Separator(); - ImGui::TextUnformatted("Tools exposed through devbench:"); - ImGui::BulletText("openshaders.feature — list / get / set / reset / toggle features"); - ImGui::BulletText("openshaders.inspect — engine state and shader-cache status"); - ImGui::BulletText("openshaders.shadercache — clear / delete the compiled cache"); - ImGui::BulletText("openshaders.capture — RenderDoc / screenshot capture"); - ImGui::BulletText("openshaders.settings — save / load / reset the global config"); - ImGui::TextDisabled( - "Note: the console tool is provided by devbench itself, not this plugin."); + ImGui::TextUnformatted(T(TKEY("tools_header"), "Tools exposed through devbench:")); + ImGui::BulletText("%s", T(TKEY("tool_feature"), "openshaders.feature — list / get / set / reset / toggle features")); + ImGui::BulletText("%s", T(TKEY("tool_inspect"), "openshaders.inspect — engine state and shader-cache status")); + ImGui::BulletText("%s", T(TKEY("tool_shadercache"), "openshaders.shadercache — clear / delete the compiled cache")); + ImGui::BulletText("%s", T(TKEY("tool_capture"), "openshaders.capture — RenderDoc / screenshot capture")); + ImGui::BulletText("%s", T(TKEY("tool_settings"), "openshaders.settings — save / load / reset the global config")); + ImGui::TextDisabled("%s", + T(TKEY("console_note"), "Note: the console tool is provided by devbench itself, not this plugin.")); #else - ImGui::TextColored(theme.Warning, - "This build was compiled without the devbench bridge " - "(DEVBENCH_BRIDGE=OFF). No tools are registered."); + ImGui::TextColored(theme.Warning, "%s", + T(TKEY("bridge_disabled"), + "This build was compiled without the devbench bridge " + "(DEVBENCH_BRIDGE=OFF). No tools are registered.")); #endif } + +#undef I18N_KEY_PREFIX diff --git a/src/Features/RenderDoc.cpp b/src/Features/RenderDoc.cpp index 6b7527ec70..8e43b112bf 100644 --- a/src/Features/RenderDoc.cpp +++ b/src/Features/RenderDoc.cpp @@ -953,10 +953,11 @@ void RenderDoc::ApplyAutomaticCommentsToNewCaptures() std::string RenderDoc::GetOverlayWarningMessage() const { - return "WARNING: RenderDoc capture is active, performance will be severely impacted.\n" - "Upscaling and Framegeneration may be incompatible.\n" - "Press F12, Print Screen or press the Capture button in the RenderDoc feature settings.\n" - "Disable RenderDoc capture in the RenderDoc feature settings."; + return T(TKEY("overlay_warning"), + "WARNING: RenderDoc capture is active, performance will be severely impacted.\n" + "Upscaling and Framegeneration may be incompatible.\n" + "Press F12, Print Screen or press the Capture button in the RenderDoc feature settings.\n" + "Disable RenderDoc capture in the RenderDoc feature settings."); } void RenderDoc::ClearFailedDeletions() diff --git a/src/Features/ScreenshotFeature.cpp b/src/Features/ScreenshotFeature.cpp index 7296e2bfc4..89e0a8e238 100644 --- a/src/Features/ScreenshotFeature.cpp +++ b/src/Features/ScreenshotFeature.cpp @@ -638,15 +638,15 @@ void ScreenshotFeature::DrawSettings() ImGui::SeparatorText(T(TKEY("output"), "Output")); - ImGui::Checkbox("Copy saved file to clipboard", ©ToClipboard); + ImGui::Checkbox(T(TKEY("copy_to_clipboard"), "Copy saved file to clipboard"), ©ToClipboard); if (auto _tt = Util::HoverTooltipWrapper()) - ImGui::Text("Places the saved screenshot on the clipboard as a file (paste in Explorer or attach in chat apps)."); + ImGui::Text("%s", T(TKEY("copy_to_clipboard_tooltip"), "Places the saved screenshot on the clipboard as a file (paste in Explorer or attach in chat apps).")); if (!hdrCaptureAvailable) { int sdrFormat = sdrUsePng ? 1 : 0; - ImGui::RadioButton("BMP (lossless)", &sdrFormat, 0); + ImGui::RadioButton(T(TKEY("format_bmp"), "BMP (lossless)"), &sdrFormat, 0); ImGui::SameLine(); - ImGui::RadioButton("PNG (lossless)", &sdrFormat, 1); + ImGui::RadioButton(T(TKEY("format_png"), "PNG (lossless)"), &sdrFormat, 1); sdrUsePng = sdrFormat != 0; } diff --git a/src/Features/SkySync.cpp b/src/Features/SkySync.cpp index 81063d16f6..fac2ee0461 100644 --- a/src/Features/SkySync.cpp +++ b/src/Features/SkySync.cpp @@ -67,32 +67,32 @@ void SkySync::DrawSettings() ImGui::Text("%s", T(TKEY("min_shadow_elevation_tooltip"), "The minimum angle sunlight will set to. Caps shadow length. Higher = shorter shadows at sunset/sunrise.")); } - ImGui::SliderFloat("Shadow Transition Duration", &settings.ShadowTransitionDuration, 0.0f, 500.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("shadow_transition_duration"), "Shadow Transition Duration"), &settings.ShadowTransitionDuration, 0.0f, 500.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("How long (in game-time units) the shadow direction takes to fade between sources. 100 = ~5 seconds at timescale 20."); + ImGui::TextUnformatted(T(TKEY("shadow_transition_duration_tooltip"), "How long (in game-time units) the shadow direction takes to fade between sources. 100 = ~5 seconds at timescale 20.")); } - ImGui::Checkbox("Dim Sunlight Under Horizon", &settings.DimSunlightUnderHorizon); + ImGui::Checkbox(T(TKEY("dim_sunlight_under_horizon"), "Dim Sunlight Under Horizon"), &settings.DimSunlightUnderHorizon); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::TextUnformatted("Fade directional light to zero as the sun goes below the horizon."); + ImGui::TextUnformatted(T(TKEY("dim_sunlight_under_horizon_tooltip"), "Fade directional light to zero as the sun goes below the horizon.")); } - ImGui::SliderFloat("New Moon Intensity", &settings.NewMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); - ImGui::SliderFloat("Crescent Intensity", &settings.CrescentMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); - ImGui::SliderFloat("Full Moon Intensity", &settings.FullMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("new_moon_intensity"), "New Moon Intensity"), &settings.NewMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("crescent_intensity"), "Crescent Intensity"), &settings.CrescentMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("full_moon_intensity"), "Full Moon Intensity"), &settings.FullMoonIntensity, 0.0f, 1.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp); - if (ImGui::TreeNodeEx("Debug", ImGuiTreeNodeFlags_None)) { - static constexpr const char* CasterNames[] = { "Sun", "Masser", "Secunda", "None" }; - static constexpr const char* PhaseNames[] = { "Full", "Waning Gibbous", "Waning Quarter", "Waning Crescent", "New", "Waxing Crescent", "Waxing Quarter", "Waxing Gibbous" }; + if (ImGui::TreeNodeEx(T(TKEY("debug"), "Debug"), ImGuiTreeNodeFlags_None)) { + const char* CasterNames[] = { T(TKEY("caster_sun"), "Sun"), T(TKEY("caster_masser"), "Masser"), T(TKEY("caster_secunda"), "Secunda"), T(TKEY("caster_none"), "None") }; + const char* PhaseNames[] = { T(TKEY("phase_full"), "Full"), T(TKEY("phase_waning_gibbous"), "Waning Gibbous"), T(TKEY("phase_waning_quarter"), "Waning Quarter"), T(TKEY("phase_waning_crescent"), "Waning Crescent"), T(TKEY("phase_new"), "New"), T(TKEY("phase_waxing_crescent"), "Waxing Crescent"), T(TKEY("phase_waxing_quarter"), "Waxing Quarter"), T(TKEY("phase_waxing_gibbous"), "Waxing Gibbous") }; - auto getPhase = [](const RE::Moon* moon) -> const char* { + auto getPhase = [&](const RE::Moon* moon) -> const char* { if (!moon || !moon->moonMesh) - return "Unknown"; + return T(TKEY("phase_unknown"), "Unknown"); if (const auto prop = skyrim_cast(moon->moonMesh->GetGeometryRuntimeData().shaderProperty.get())) { if (auto tex = prop->GetBaseTexture()) return PhaseNames[static_cast(Util::Moon::GetPhaseFromTexture(tex->name.c_str()))]; } - return "Unknown"; + return T(TKEY("phase_unknown"), "Unknown"); }; auto drawMoonEntry = [&](const char* label, Caster caster, const char* phase) { @@ -104,24 +104,24 @@ void SkySync::DrawSettings() }; const auto sky = globals::game::sky; - drawMoonEntry("Masser", Caster::Masser, sky ? getPhase(sky->masser) : "Unknown"); - drawMoonEntry("Secunda", Caster::Secunda, sky ? getPhase(sky->secunda) : "Unknown"); + drawMoonEntry(T(TKEY("caster_masser"), "Masser"), Caster::Masser, sky ? getPhase(sky->masser) : T(TKEY("phase_unknown"), "Unknown")); + drawMoonEntry(T(TKEY("caster_secunda"), "Secunda"), Caster::Secunda, sky ? getPhase(sky->secunda) : T(TKEY("phase_unknown"), "Unknown")); - ImGui::Text("Dim: %.3f", currentDim); + ImGui::Text(T(TKEY("debug_dim"), "Dim: %.3f"), currentDim); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - ImGui::Text("Shadow target: %s", CasterNames[static_cast(shadowFader.target)]); - ImGui::Text("Shadow dir: (%.2f, %.2f, %.2f)", shadowFader.currentDir.x, shadowFader.currentDir.y, shadowFader.currentDir.z); + ImGui::Text(T(TKEY("debug_shadow_target"), "Shadow target: %s"), CasterNames[static_cast(shadowFader.target)]); + ImGui::Text(T(TKEY("debug_shadow_dir"), "Shadow dir: (%.2f, %.2f, %.2f)"), shadowFader.currentDir.x, shadowFader.currentDir.y, shadowFader.currentDir.z); if (shadowFader.transitioning) { const float t = settings.ShadowTransitionDuration > 0.0f ? shadowFader.fadeTimer / settings.ShadowTransitionDuration : 1.0f; ImGui::ProgressBar(t, { -1.0f, 0.0f }, ""); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("Transitioning %.0f%%", t * 100.0f); + ImGui::Text(T(TKEY("debug_transitioning"), "Transitioning %.0f%%"), t * 100.0f); } else { - ImGui::TextDisabled("No transition"); + ImGui::TextDisabled(T(TKEY("debug_no_transition"), "No transition")); } ImGui::TreePop(); diff --git a/src/Features/TerrainShadows.cpp b/src/Features/TerrainShadows.cpp index 6a07ac802b..83f380fbd6 100644 --- a/src/Features/TerrainShadows.cpp +++ b/src/Features/TerrainShadows.cpp @@ -38,24 +38,25 @@ void TerrainShadows::DrawSettings() curr_worldspace_name = worldspace->GetName(); } } - ImGui::Text(fmt::format("Current worldspace: {} ({})", curr_worldspace, curr_worldspace_name).c_str()); - ImGui::Text(fmt::format("Has height map: {}", heightmaps.contains(curr_worldspace)).c_str()); + bool hasHeightMap = heightmaps.contains(curr_worldspace); + ImGui::Text(std::vformat(T(TKEY("current_worldspace"), "Current worldspace: {} ({})"), std::make_format_args(curr_worldspace, curr_worldspace_name)).c_str()); + ImGui::Text(std::vformat(T(TKEY("has_height_map"), "Has height map: {}"), std::make_format_args(hasHeightMap)).c_str()); ImGui::Separator(); - ImGui::BulletText("shadowUpdateCBData"); + ImGui::BulletText(T(TKEY("shadow_update_cb_data"), "shadowUpdateCBData")); ImGui::Indent(); { - ImGui::Text(fmt::format("LightPxDir: ({}, {})", shadowUpdateCBData.LightPxDir.x, shadowUpdateCBData.LightPxDir.y).c_str()); - ImGui::Text(fmt::format("LightDeltaZ: ({}, {})", shadowUpdateCBData.LightDeltaZ.x, shadowUpdateCBData.LightDeltaZ.y).c_str()); - ImGui::Text(fmt::format("StartPxCoord: {}", shadowUpdateCBData.StartPxCoord).c_str()); - ImGui::Text(fmt::format("PxSize: ({}, {})", shadowUpdateCBData.PxSize.x, shadowUpdateCBData.PxSize.y).c_str()); + ImGui::Text(std::vformat(T(TKEY("light_px_dir"), "LightPxDir: ({}, {})"), std::make_format_args(shadowUpdateCBData.LightPxDir.x, shadowUpdateCBData.LightPxDir.y)).c_str()); + ImGui::Text(std::vformat(T(TKEY("light_delta_z"), "LightDeltaZ: ({}, {})"), std::make_format_args(shadowUpdateCBData.LightDeltaZ.x, shadowUpdateCBData.LightDeltaZ.y)).c_str()); + ImGui::Text(std::vformat(T(TKEY("start_px_coord"), "StartPxCoord: {}"), std::make_format_args(shadowUpdateCBData.StartPxCoord)).c_str()); + ImGui::Text(std::vformat(T(TKEY("px_size"), "PxSize: ({}, {})"), std::make_format_args(shadowUpdateCBData.PxSize.x, shadowUpdateCBData.PxSize.y)).c_str()); } ImGui::Unindent(); if (ImGui::TreeNode(T(TKEY("buffer_viewer"), "Buffer Viewer"))) { static float debugRescale = .1f; - ImGui::SliderFloat("View Resize", &debugRescale, 0.f, 1.f); + ImGui::SliderFloat(T(TKEY("view_resize"), "View Resize"), &debugRescale, 0.f, 1.f); if (texShadowHeight) { BUFFER_VIEWER_NODE_BULLET(texShadowHeight, debugRescale) diff --git a/src/Features/VolumetricShadows.cpp b/src/Features/VolumetricShadows.cpp index 34459618ed..90a4f1d49a 100644 --- a/src/Features/VolumetricShadows.cpp +++ b/src/Features/VolumetricShadows.cpp @@ -1,9 +1,12 @@ #include "VolumetricShadows.h" #include "Globals.h" +#include "I18n/I18n.h" #include "State.h" #include "Utils/D3D.h" +#define I18N_KEY_PREFIX "feature.volumetric_shadows." + void VolumetricShadows::SetupResources() { auto device = globals::d3d::device; @@ -317,11 +320,11 @@ void VolumetricShadows::SetSharedShadowMapSRV(ID3D11DeviceContext* a_context, ID void VolumetricShadows::DrawSettings() { - ImGui::SeparatorText("Debug"); + ImGui::SeparatorText(T(TKEY("debug"), "Debug")); - if (ImGui::TreeNode("Buffer Viewer")) { + if (ImGui::TreeNode(T(TKEY("buffer_viewer"), "Buffer Viewer"))) { static float debugRescale = .3f; - ImGui::SliderFloat("View Resize", &debugRescale, 0.f, 1.f); + ImGui::SliderFloat(T(TKEY("view_resize"), "View Resize"), &debugRescale, 0.f, 1.f); auto DisplayRT = [&](const char* label, ID3D11Texture2D* tex, ID3D11ShaderResourceView* srv) { if (srv && tex) { @@ -336,8 +339,8 @@ void VolumetricShadows::DrawSettings() } }; - DisplayRT("VSM Cascade 0", shadowCopyTexture, shadowCopyMip0SRV); - DisplayRT("VSM Cascade 1", shadowCopyTexture, shadowCopyMip1SRV); + DisplayRT(T(TKEY("vsm_cascade_0"), "VSM Cascade 0"), shadowCopyTexture, shadowCopyMip0SRV); + DisplayRT(T(TKEY("vsm_cascade_1"), "VSM Cascade 1"), shadowCopyTexture, shadowCopyMip1SRV); ImGui::TreePop(); } @@ -379,3 +382,5 @@ bool VolumetricShadows::HasShaderDefine(RE::BSShader::Type) { return true; } + +#undef I18N_KEY_PREFIX diff --git a/src/Menu/OverlayRenderer.cpp b/src/Menu/OverlayRenderer.cpp index 416c587e9c..b56ee7d1eb 100644 --- a/src/Menu/OverlayRenderer.cpp +++ b/src/Menu/OverlayRenderer.cpp @@ -42,7 +42,7 @@ namespace void DrawShaderCompilationFailures(uint64_t failed, const Menu::ThemeSettings& themeSettings) { ImGui::TextColored(themeSettings.StatusPalette.Error, - "ERROR: %llu shaders failed to compile. Check installation and CommunityShaders.log", + T("overlay.shaders_failed", "ERROR: %llu shaders failed to compile. Check installation and CommunityShaders.log"), static_cast(failed)); if (FeatureIssues::HasPotentialShaderModifyingFeatures()) { @@ -271,9 +271,10 @@ void OverlayRenderer::RenderShaderCompilationStatus(const std::functionIsAvailable(); const auto renderDocInformation = renderDoc->GetOverlayWarningMessage(); - auto progressTitle = fmt::format("{}Compiling Shaders: {}", - shaderCache->backgroundCompilation ? "Background " : "", - shaderCache->GetShaderStatsString(!state->IsDeveloperMode()).c_str()); + std::string compilePrefix = shaderCache->backgroundCompilation ? T("overlay.background_prefix", "Background ") : ""; + std::string shaderStats = shaderCache->GetShaderStatsString(!state->IsDeveloperMode()); + auto progressTitle = std::vformat(T("overlay.compiling_shaders", "{}Compiling Shaders: {}"), + std::make_format_args(compilePrefix, shaderStats)); auto percent = (float)compiledShaders / (float)totalShaders; auto progressOverlay = fmt::format("{}/{} ({:2.1f}%)", compiledShaders, totalShaders, 100 * percent); @@ -292,20 +293,21 @@ void OverlayRenderer::RenderShaderCompilationStatus(const std::function(Util::GetPerformanceCoreCount()); uint64_t slow = shaderCache->GetSlowTasks(); uint64_t verySlow = shaderCache->GetVerySlowTasks(); - ImGui::Text("Threads: %d / %d limit | Heavy: %d / %d P-cores | %d workers", + ImGui::Text(T("overlay.threads_status", "Threads: %d / %d limit | Heavy: %d / %d P-cores | %d workers"), compilationRunning, threadLimit, heavyInFlight, heavyLimit, (int)shaderCache->compilationPool.get_thread_count()); if (slow > 0) { - ImGui::Text("Slow shaders: %llu (very slow: %llu)", slow, verySlow); + ImGui::Text(T("overlay.slow_shaders", "Slow shaders: %llu (very slow: %llu)"), slow, verySlow); } } if (!shaderCache->backgroundCompilation && shaderCache->menuLoaded) { - auto skipShadersText = fmt::format( - "Press {} to proceed without completing shader compilation. ", - keyIdToString(Menu::GetSingleton()->GetSettings().SkipCompilationKey)); + const char* skipKeyName = keyIdToString(Menu::GetSingleton()->GetSettings().SkipCompilationKey); + auto skipShadersText = std::vformat( + T("overlay.skip_compilation", "Press {} to proceed without completing shader compilation. "), + std::make_format_args(skipKeyName)); ImGui::TextUnformatted(skipShadersText.c_str()); ImGui::TextUnformatted(T("overlay.uncompiled_warning", "WARNING: Uncompiled shaders will have visual errors or cause stuttering when loading.")); } @@ -439,7 +441,7 @@ void OverlayRenderer::RenderShaderBlockingStatus() } Util::Text::Error(T("overlay.shader_blocking_active", "Shader Blocking Active")); - ImGui::Text("Blocked: %s", shaderCache->blockedKey.c_str()); + ImGui::Text(T("overlay.blocked_key", "Blocked: %s"), shaderCache->blockedKey.c_str()); // Try to get more details from active shaders auto activeShaders = shaderCache->GetActiveShaders(); @@ -456,14 +458,14 @@ void OverlayRenderer::RenderShaderBlockingStatus() } if (foundBlocked) { - ImGui::Text("Index: %zu/%zu", blockedIndex, activeShaders.size()); + ImGui::Text(T("overlay.blocked_index", "Index: %zu/%zu"), blockedIndex, activeShaders.size()); } else { - ImGui::Text("Index: N/A (%zu active)", activeShaders.size()); + ImGui::Text(T("overlay.blocked_index_na", "Index: N/A (%zu active)"), activeShaders.size()); } for (const auto& shader : activeShaders) { if (shader.key == shaderCache->blockedKey) { - ImGui::Text("Type: %s | Class: %s | Descriptor: 0x%X", + ImGui::Text(T("overlay.blocked_shader_detail", "Type: %s | Class: %s | Descriptor: 0x%X"), magic_enum::enum_name(shader.shaderType).data(), magic_enum::enum_name(shader.shaderClass).data(), shader.descriptor); diff --git a/src/Menu/SettingsTabRenderer.cpp b/src/Menu/SettingsTabRenderer.cpp index d4f8961094..94036c92e6 100644 --- a/src/Menu/SettingsTabRenderer.cpp +++ b/src/Menu/SettingsTabRenderer.cpp @@ -333,7 +333,7 @@ void SettingsTabRenderer::RenderShadersTab() auto state = globals::state; if (state->IsDeveloperMode()) { - ImGui::Text("Threads: %d compile, %d background, %d pool | P-cores: %d", + ImGui::Text(T("menu.settings.shader_thread_diagnostics", "Threads: %d compile, %d background, %d pool | P-cores: %d"), (int)shaderCache->compilationThreadCount, (int)shaderCache->backgroundCompilationThreadCount, (int)shaderCache->compilationPool.get_thread_count(), @@ -1005,7 +1005,7 @@ void SettingsTabRenderer::RenderFontsTab() } const char* familyPreview = fontCatalog.families.empty() ? T("menu.settings.no_families", "No families") : fontCatalog.families[familyIndex].displayName.c_str(); - std::string familyLabel = std::format("{} Family##{}", descriptor.displayName, roleIndex); + std::string familyLabel = std::vformat(T("menu.settings.font_family_label", "{} Family##{}"), std::make_format_args(descriptor.displayName, roleIndex)); { FontRoleGuard familyComboFont(Menu::FontRole::Body); if (ImGui::BeginCombo(familyLabel.c_str(), familyPreview)) { @@ -1057,7 +1057,7 @@ void SettingsTabRenderer::RenderFontsTab() styleIndex = 0; } const char* stylePreview = selectedFamily->styles.empty() ? T("menu.settings.no_styles", "No styles") : selectedFamily->styles[styleIndex].displayName.c_str(); - std::string styleLabel = std::format("{} Style##{}", descriptor.displayName, roleIndex); + std::string styleLabel = std::vformat(T("menu.settings.font_style_label", "{} Style##{}"), std::make_format_args(descriptor.displayName, roleIndex)); { FontRoleGuard styleComboFont(Menu::FontRole::Body); if (ImGui::BeginCombo(styleLabel.c_str(), stylePreview)) { @@ -1086,12 +1086,12 @@ void SettingsTabRenderer::RenderFontsTab() ImGui::TextDisabled(T("menu.settings.file_label", "File: %s"), roleSettings.File.c_str()); - std::string scaleLabel = std::format("{} Scale##{}", descriptor.displayName, roleIndex); + std::string scaleLabel = std::vformat(T("menu.settings.font_scale_label", "{} Scale##{}"), std::make_format_args(descriptor.displayName, roleIndex)); if (ImGui::SliderFloat(scaleLabel.c_str(), &roleSettings.SizeScale, 0.5f, 2.5f, "%.2fx", ImGuiSliderFlags_AlwaysClamp)) { menuInstance->pendingFontReload = true; } ImGui::SameLine(); - std::string resetLabel = std::format("Reset##Scale{}", roleIndex); + std::string resetLabel = std::string(T("menu.settings.reset", "Reset")) + "##Scale" + std::to_string(roleIndex); if (ImGui::Button(resetLabel.c_str())) { roleSettings.SizeScale = Menu::GetFontRoleDefaultScale(role); menuInstance->pendingFontReload = true; diff --git a/src/Utils/Subrect.cpp b/src/Utils/Subrect.cpp index 07eafdef81..4c4238e0d9 100644 --- a/src/Utils/Subrect.cpp +++ b/src/Utils/Subrect.cpp @@ -226,9 +226,9 @@ namespace Util::Subrect } std::string currentPreview = - (selectedPresetIndex >= 0 && selectedPresetIndex < static_cast(presets.size())) ? presets[selectedPresetIndex].name : "(Custom)"; + (selectedPresetIndex >= 0 && selectedPresetIndex < static_cast(presets.size())) ? presets[selectedPresetIndex].name : T("ui.subrect.custom", "(Custom)"); - if (ImGui::BeginCombo("Crop Preset", currentPreview.c_str())) { + if (ImGui::BeginCombo(T("ui.subrect.crop_preset", "Crop Preset"), currentPreview.c_str())) { for (int i = 0; i < static_cast(presets.size()); ++i) { const bool isSelected = selectedPresetIndex == i; if (ImGui::Selectable(presets[i].name.c_str(), isSelected)) { @@ -241,9 +241,9 @@ namespace Util::Subrect ImGui::EndCombo(); } - ImGui::InputText("Save As", newPresetName, sizeof(newPresetName)); + ImGui::InputText(T("ui.subrect.save_as", "Save As"), newPresetName, sizeof(newPresetName)); ImGui::SameLine(); - if (ImGui::Button("Save Preset")) { + if (ImGui::Button(T("ui.subrect.save_preset", "Save Preset"))) { std::string presetName = newPresetName; if (!presetName.empty()) { // Preserve the right-eye UV only when stereo is on. In mono @@ -264,22 +264,22 @@ namespace Util::Subrect if (selectedPresetIndex > 0) { ImGui::SameLine(); - if (ImGui::Button("Delete Preset")) { + if (ImGui::Button(T("ui.subrect.delete_preset", "Delete Preset"))) { presets.erase(presets.begin() + selectedPresetIndex); ApplyPreset(0); } } ImGui::SameLine(); - if (ImGui::Button("Reset Crop")) { + if (ImGui::Button(T("ui.subrect.reset_crop", "Reset Crop"))) { ApplyPreset(0); } ImGui::Spacing(); ImGui::PushItemWidth(250.0f); bool changed = false; - changed |= ImGui::SliderFloat2("Position UV (X, Y)", ¤tUV.x, 0.0f, 1.0f, "%.3f"); - changed |= ImGui::SliderFloat2("Size UV (W, H)", ¤tUV.w, 0.01f, 1.0f, "%.3f"); + changed |= ImGui::SliderFloat2(T("ui.subrect.position_uv", "Position UV (X, Y)"), ¤tUV.x, 0.0f, 1.0f, "%.3f"); + changed |= ImGui::SliderFloat2(T("ui.subrect.size_uv", "Size UV (W, H)"), ¤tUV.w, 0.01f, 1.0f, "%.3f"); ImGui::PopItemWidth(); if (changed) { @@ -291,10 +291,10 @@ namespace Util::Subrect } ImGui::Spacing(); - ImGui::Text("Interactive Cropping (Drag on the image to select)"); + ImGui::Text("%s", T("ui.subrect.interactive_cropping", "Interactive Cropping (Drag on the image to select)")); if (!previewSrv || !previewTexture) { - ImGui::TextDisabled("Preview unavailable."); + ImGui::TextDisabled("%s", T("ui.subrect.preview_unavailable", "Preview unavailable.")); return; } diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index bd20907d3d..f8f166c1b6 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -1460,7 +1460,7 @@ namespace Util auto realPath = Util::PathHelpers::GetRealPathFromDataRelative(pluginDir); ShellExecuteW(nullptr, L"open", realPath.empty() ? pluginDir : realPath.c_str(), nullptr, nullptr, SW_SHOWNORMAL); } - std::vector headers = { "DLL Name", "Version" }; + std::vector headers = { T("ui.table.dll_name", "DLL Name"), T("ui.table.version", "Version") }; std::vector> rows; rows.reserve(dllVersions.size()); for (const auto& [name, version] : dllVersions) @@ -2204,11 +2204,11 @@ namespace Util auto* weatherManager = WeatherManager::GetSingleton(); auto currentWeathers = weatherManager->GetCurrentWeathers(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Weather Override Active"); - ImGui::TextWrapped("This setting is controlled by the current weather (%s).", + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", T("ui.weather_override_active", "Weather Override Active")); + ImGui::TextWrapped(T("ui.weather_setting_controlled", "This setting is controlled by the current weather (%s)."), currentWeathers.currentWeather ? currentWeathers.currentWeather->GetFormEditorID() : "Unknown"); ImGui::Separator(); - ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "Click to open CS Editor"); + ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "%s", T("ui.click_to_open_cs_editor", "Click to open CS Editor")); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -2258,11 +2258,11 @@ namespace Util auto* weatherManager = WeatherManager::GetSingleton(); auto currentWeathers = weatherManager->GetCurrentWeathers(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Weather Override Active"); - ImGui::TextWrapped("This setting is controlled by the current weather (%s).", + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", T("ui.weather_override_active", "Weather Override Active")); + ImGui::TextWrapped(T("ui.weather_setting_controlled", "This setting is controlled by the current weather (%s)."), currentWeathers.currentWeather ? currentWeathers.currentWeather->GetFormEditorID() : "Unknown"); ImGui::Separator(); - ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "Click to open CS Editor"); + ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "%s", T("ui.click_to_open_cs_editor", "Click to open CS Editor")); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -2309,11 +2309,11 @@ namespace Util auto* weatherManager = WeatherManager::GetSingleton(); auto currentWeathers = weatherManager->GetCurrentWeathers(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Weather Override Active"); - ImGui::TextWrapped("This setting is controlled by the current weather (%s).", + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", T("ui.weather_override_active", "Weather Override Active")); + ImGui::TextWrapped(T("ui.weather_setting_controlled", "This setting is controlled by the current weather (%s)."), currentWeathers.currentWeather ? currentWeathers.currentWeather->GetFormEditorID() : "Unknown"); ImGui::Separator(); - ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "Click to open CS Editor"); + ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "%s", T("ui.click_to_open_cs_editor", "Click to open CS Editor")); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -2360,11 +2360,11 @@ namespace Util auto* weatherManager = WeatherManager::GetSingleton(); auto currentWeathers = weatherManager->GetCurrentWeathers(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Weather Override Active"); - ImGui::TextWrapped("This setting is controlled by the current weather (%s).", + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", T("ui.weather_override_active", "Weather Override Active")); + ImGui::TextWrapped(T("ui.weather_setting_controlled", "This setting is controlled by the current weather (%s)."), currentWeathers.currentWeather ? currentWeathers.currentWeather->GetFormEditorID() : "Unknown"); ImGui::Separator(); - ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "Click to open CS Editor"); + ImGui::TextColored(ImVec4(0.6f, 0.9f, 0.6f, 1.0f), "%s", T("ui.click_to_open_cs_editor", "Click to open CS Editor")); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -2401,7 +2401,7 @@ namespace Util if (!combo.empty()) { buttonText = Util::Input::KeyIdToString(combo) + "..."; // Indicate it's still capturing } else { - buttonText = "Recording... (Esc to cancel)"; + buttonText = T("ui.input.recording", "Recording... (Esc to cancel)"); } if (ImGui::Button(buttonText.c_str(), ImVec2(0, 0))) { @@ -2412,7 +2412,7 @@ namespace Util // Add tooltip explaining how to record if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Press any key combination.\nModifiers (Ctrl, Shift, Alt) are supported.\nPress Escape to cancel."); + ImGui::SetTooltip("%s", T("ui.input.record_hint", "Press any key combination.\nModifiers (Ctrl, Shift, Alt) are supported.\nPress Escape to cancel.")); } } else { // Display current binding with unique button ID @@ -2424,7 +2424,7 @@ namespace Util // Context menu for clearing if (ImGui::BeginPopupContextItem()) { - if (ImGui::Selectable("Clear Binding")) { + if (ImGui::Selectable(T("ui.input.clear_binding", "Clear Binding"))) { combo.clear(); changed = true; } @@ -2434,7 +2434,7 @@ namespace Util // First run / empty state hint if (combo.empty()) { ImGui::SameLine(); - ImGui::TextDisabled("(Click to bind)"); + ImGui::TextDisabled("%s", T("ui.input.click_to_bind", "(Click to bind)")); } } @@ -2468,13 +2468,13 @@ namespace Util if (hasBoth || (hasPrimary && hasSecondary)) { indicatorColor = GetControllerBothColor(); - indicatorText = hasBoth ? "(Both)" : "(Mixed)"; + indicatorText = hasBoth ? T("ui.input.indicator_both", "(Both)") : T("ui.input.indicator_mixed", "(Mixed)"); } else if (hasPrimary) { indicatorColor = GetControllerPrimaryColor(); - indicatorText = "(Primary)"; + indicatorText = T("ui.input.indicator_primary", "(Primary)"); } else if (hasSecondary) { indicatorColor = GetControllerSecondaryColor(); - indicatorText = "(Secondary)"; + indicatorText = T("ui.input.indicator_secondary", "(Secondary)"); } if (indicatorText[0] != '\0') { @@ -2497,8 +2497,8 @@ namespace Util ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Setting Constrained"); - ImGui::Text("This setting is constrained by:"); + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", T("ui.constraint.setting_constrained", "Setting Constrained")); + ImGui::Text("%s", T("ui.constraint.constrained_by", "This setting is constrained by:")); ImGui::Spacing(); for (const auto& src : constraint.sources) { ImGui::BulletText("%s", src.featureName.c_str()); @@ -2506,12 +2506,12 @@ namespace Util ImGui::TextWrapped("%s", src.reason.c_str()); if (src.recommendDisableAtBoot) { ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), - "Consider disabling this feature at boot for best compatibility."); + "%s", T("ui.constraint.consider_disable", "Consider disabling this feature at boot for best compatibility.")); } ImGui::Unindent(); } ImGui::Separator(); - ImGui::Text("Forced value: %s", FeatureConstraints::FormatConstraintValue(constraint.forcedValue).c_str()); + ImGui::Text(T("ui.constraint.forced_value", "Forced value: %s"), FeatureConstraints::FormatConstraintValue(constraint.forcedValue).c_str()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } diff --git a/src/Utils/VRUtils.cpp b/src/Utils/VRUtils.cpp index 23ac293156..bcb939bcc1 100644 --- a/src/Utils/VRUtils.cpp +++ b/src/Utils/VRUtils.cpp @@ -1,4 +1,5 @@ #include "VRUtils.h" +#include "../I18n/I18n.h" #include "Features/VR.h" // For ButtonCombo and ControllerDevice definitions #include "RE/B/BSOpenVR.h" #include "UI.h" @@ -42,15 +43,15 @@ namespace Util const char* label = ""; switch (combo[i].GetDevice()) { case InputDeviceType::Primary: - label = "(Primary Controller)"; + label = T("ui.vr.primary_controller", "(Primary Controller)"); labelColor = Util::GetControllerPrimaryColor(); break; case InputDeviceType::Secondary: - label = "(Secondary Controller)"; + label = T("ui.vr.secondary_controller", "(Secondary Controller)"); labelColor = Util::GetControllerSecondaryColor(); break; case InputDeviceType::Both: - label = "(Both Controllers)"; + label = T("ui.vr.both_controllers", "(Both Controllers)"); labelColor = Util::GetControllerBothColor(); break; default: @@ -63,10 +64,10 @@ namespace Util } if (anyDrawn) { if (auto _tt = Util::HoverTooltipWrapper()) { - Util::DrawColoredMultiLineTooltip({ { "Color coding:", Util::GetControllerDefaultColor() }, - { "Yellow = Primary controller", Util::GetControllerPrimaryColor() }, - { "Blue = Secondary controller", Util::GetControllerSecondaryColor() }, - { "Green = Both controllers (Yellow + Blue)", Util::GetControllerBothColor() } }); + Util::DrawColoredMultiLineTooltip({ { T("ui.vr.color_coding", "Color coding:"), Util::GetControllerDefaultColor() }, + { T("ui.vr.color_coding_primary", "Yellow = Primary controller"), Util::GetControllerPrimaryColor() }, + { T("ui.vr.color_coding_secondary", "Blue = Secondary controller"), Util::GetControllerSecondaryColor() }, + { T("ui.vr.color_coding_both", "Green = Both controllers (Yellow + Blue)"), Util::GetControllerBothColor() } }); } } } From 4206e6a3f743a187f10a73d78bc831b50294464a Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 8 Jun 2026 12:24:14 -0700 Subject: [PATCH 7/7] test: stub I18n::Get for cpp_tests link Subrect.cpp (compiled directly into cpp_tests) now calls T() after the i18n wrap pass, pulling in I18n::Get. Stub it to return the inline English default so the test binary links without the translation loader (file I/O + spdlog). Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/cpp/CMakeLists.txt | 3 +++ tests/cpp/i18n_stub.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/cpp/i18n_stub.cpp diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index c57b60168e..ea4b05c4c2 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -52,6 +52,9 @@ add_executable(cpp_tests test_isl_radiusmath.cpp test_shadowcaster_math.cpp test_llf_sanitize.cpp + # Stub I18n::Get so units-under-test that call T() (Subrect's DrawEditor) + # link without pulling the translation loader into the test binary. + i18n_stub.cpp # Compile the unit-under-test directly into the test binary so we don't # depend on the plugin DLL build (which pulls in FFX/Streamline/etc.). "${CMAKE_SOURCE_DIR}/src/Utils/Subrect.cpp" diff --git a/tests/cpp/i18n_stub.cpp b/tests/cpp/i18n_stub.cpp new file mode 100644 index 0000000000..2332121265 --- /dev/null +++ b/tests/cpp/i18n_stub.cpp @@ -0,0 +1,14 @@ +// Test-only stub for I18n::Get. +// +// Some units-under-test compiled directly into cpp_tests (e.g. Subrect.cpp's +// DrawEditor) now call T(), which resolves to I18n::GetSingleton()->Get(...). +// Linking the real src/I18n/I18n.cpp would drag the translation loader (file +// I/O, locale discovery, spdlog) into the test binary. The tests only exercise +// non-UI logic, so stub Get to return the inline English default -- identical +// to the runtime fallback when no translation is loaded. +#include "I18n/I18n.h" + +const char* I18n::Get(std::string_view, const char* defaultText) const +{ + return defaultText ? defaultText : ""; +}