Add mod documentation in seven languages

This commit is contained in:
cthangover committed 2026-10-29 09:26:04 +01:00
1 parent 9f22af960c
commit b06f8ce990
43 files changed
+1828 -20

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
{
"name": "Mod documentation test",
"depends": ["menu_base>=1"],
"sources": ["src/*.cs"]
}
@@ -6,13 +6,19 @@ using Cthangover.Core.Localization;
using Cthangover.Core.Mods;
using Cthangover.Core.UI.Menu;
using Cthangover.Core.Utils;
using Cthangover.Core.Settings;
using Cthangover.BaseMenu;
using Cthangover.Mods.Interface.Menu;
using Cthangover.Mods.Interface.View;
using Godot;
namespace Mods.ModManager.DocumentationTest
{
/// <summary>Opens music_pack through the hub and exercises language selection on the actual page.</summary>
/// <summary>
/// Exercises the reference page, or reviews a real English page with
/// --doc-mod=id. --doc-settings additionally captures the real Sound tab;
/// the original settings file and language are restored even after failure.
/// </summary>
public partial class DocumentationTestDriver : Node
{
private double elapsed;
@@ -24,6 +30,12 @@ namespace Mods.ModManager.DocumentationTest
private ModHubScreen hub;
private ModManagerScreen manager;
private ModDetailView detail;
private readonly string reviewMod = ReviewMod();
private string reviewTitle;
private SettingsMenu settings;
private string settingsPath;
private byte[] settingsBackup;
private string settingsLanguage;
public override void _Process(double delta)
{
@@ -40,7 +52,10 @@ namespace Mods.ModManager.DocumentationTest
stepAt = elapsed;
try
{
RunStep();
if (string.IsNullOrEmpty(reviewMod))
RunStep();
else
RunReviewStep();
}
catch (Exception ex)
{
@@ -82,7 +97,7 @@ namespace Mods.ModManager.DocumentationTest
Check(Body().GetContentHeight() > 600, "long document lays out");
var codeFont = Body().GetThemeFont("mono_font");
Check(codeFont.HasChar('└') && codeFont.HasChar('─') && codeFont.HasChar('├'), "bundled monospace font covers tree glyphs");
Check(ModDocumentation.Discover(ModRegistry.Instance.AllDiscoveredMods["music_pack"].FileProvider).Count == 2, "two bundled languages");
Check(ModDocumentation.Discover(ModRegistry.Instance.AllDiscoveredMods["music_pack"].FileProvider).Count == LanguageCode.Supported.Length, "every project language is bundled");
Shot("music_pack_ru");
SelectLanguage("EN");
break;
@@ -100,16 +115,21 @@ namespace Mods.ModManager.DocumentationTest
LocaleLoader.LoadLocale("ja");
break;
case 6:
Check(Body().GetParsedText().Contains("a soundtrack for your game"), "missing Japanese falls back to English after locale change");
Check(detail.GetNode<Label>("Scroll/Content/DocumentationBox/Source").Text.Contains("EN"), "actual fallback language is visible");
manager.CloseDetail();
manager.SetSearch("audio_base");
Check(Body().GetParsedText().Contains("あなたのゲームのためのサウンドトラック"), "auto language follows the interface language (Japanese)");
Check(detail.GetNode<Label>("Scroll/Content/DocumentationBox/Source").Text.Contains("JA"), "actual document language is visible");
LocaleLoader.LoadLocale("de");
break;
case 7:
var legacyCard = hub.FindChildren("*", "", true, false).OfType<ModCard>().First(IsVisibleCard);
legacyCard.EmitSignal(Control.SignalName.GuiInput, new InputEventMouseButton { ButtonIndex = MouseButton.Left, Pressed = true });
Check(Body().GetParsedText().Contains("a soundtrack for your game"), "unbundled German falls back to English after locale change");
LocaleLoader.LoadLocale("en");
break;
case 8:
Check(detail.GetNode<Label>("Scroll/Content/DocumentationBox/Source").Text.Contains("EN"), "document language is visible for a bundled UI language");
// A controlled legacy record stays valid as documentation
// is gradually added to every real mod in the project.
detail.Open(new ModCardData { Id = "documentation_legacy_fixture", Title = "Legacy fixture", Description = "Manifest-only description" });
break;
case 9:
Check(!detail.GetNode<Control>("Scroll/Content/DocumentationBox").Visible, "legacy mod keeps manifest description");
Check(!string.IsNullOrWhiteSpace(detail.GetNode<Label>("Scroll/Content/DescriptionBox/Text").Text), "legacy description remains readable");
Finish(null);
@@ -118,9 +138,95 @@ namespace Mods.ModManager.DocumentationTest
step++;
}
private static string ReviewMod()
{
foreach (var arg in OS.GetCmdlineUserArgs())
if (arg.StartsWith("--doc-mod=", StringComparison.Ordinal))
return arg.Substring("--doc-mod=".Length);
return null;
}
private void RunReviewStep()
{
switch (step)
{
case 0:
if (!Boot.IsReady)
return;
if (GetTree().CurrentScene?.FindChild("LauncherPlay", true, false) is Button launcherPlay)
{
launcherPlay.EmitSignal(BaseButton.SignalName.Pressed);
return;
}
if (GetTree().CurrentScene?.FindChild("play", true, false) is not Button)
return;
originalLocale = TranslationServer.GetLocale();
settingsLanguage = GameData.Instance.Settings.Language;
settingsPath = ProjectSettings.GlobalizePath("user://settings.json");
settingsBackup = File.Exists(settingsPath) ? File.ReadAllBytes(settingsPath) : null;
settings = GetTree().CurrentScene.GetNode<SettingsMenu>("SettingsPanel");
settings.Open();
var language = settings.GetNode<OptionButton>("Window/Column/Body/Pages/General/LangRow/LangSelect");
var english = Array.IndexOf(LanguageCode.Supported, "en");
language.Select(english);
language.EmitSignal(OptionButton.SignalName.ItemSelected, (long)english);
settings.ShowTab(1);
break;
case 1:
Check(TranslationServer.GetLocale().StartsWith("en", StringComparison.Ordinal), "English UI selected through Settings");
if (OS.GetCmdlineUserArgs().Contains("--doc-settings") || OS.GetCmdlineUserArgs().Contains("--doc-settings-only"))
{
var audio = settings.GetNode<Control>("Window/Column/Body/Pages/Audio");
Check(audio.IsVisibleInTree(), "actual Sound tab visible");
Shot(reviewMod + "_sound_settings");
}
if (OS.GetCmdlineUserArgs().Contains("--doc-settings-only"))
{
Finish(null);
return;
}
settings.Visible = false;
var entry = MenuRegistry.GetButtonById("mods_hub");
hub = MenuBuilder.OpenScene((Control)GetTree().CurrentScene, entry.ScenePath, "mods_hub") as ModHubScreen;
Check(hub != null, "hub opens for documentation review");
break;
case 2:
manager = hub.FindChildren("*", "", true, false).OfType<ModManagerScreen>().First();
manager.SetSearch(reviewMod);
break;
case 3:
// The search also matches test suites such as mod_manager__documentation; open the reviewed mod itself.
reviewTitle = ModRegistry.Instance.AllDiscoveredMods[reviewMod].DisplayTitle ?? reviewMod;
var card = hub.FindChildren("*", "", true, false).OfType<ModCard>().First(IsReviewedCard);
card.EmitSignal(Control.SignalName.GuiInput, new InputEventMouseButton { ButtonIndex = MouseButton.Left, Pressed = true });
break;
case 4:
detail = hub.FindChildren("*", "", true, false).OfType<ModDetailView>().First(IsVisibleDetail);
var provider = ModRegistry.Instance.AllDiscoveredMods[reviewMod].FileProvider;
var document = ModDocumentation.Select(ModDocumentation.Discover(provider), "en");
Check(document?.Path == "docs/en.md", "English entry point exists for " + reviewMod);
var heading = document.Text.Split('\n')[0].TrimStart('#', ' ', '\uFEFF').TrimEnd('\r');
var shown = Body().GetParsedText();
Check(shown.Contains(heading), "actual page displays the selected English document; heading '" + heading + "', shown '" + shown.Substring(0, Math.Min(160, shown.Length)) + "'");
Check(!Body().GetParsedText().Contains(TranslationServer.Translate("mod_manager/docs_image_unavailable").ToString()), "all page images resolve");
Check(Body().GetContentHeight() > 300, "page lays out for scrolling");
Shot(reviewMod + "_documentation_en");
LocaleLoader.LoadLocale("ru");
break;
case 5:
Check(detail.GetNode<Label>("Scroll/Content/DocumentationBox/Source").Text.Contains("EN"), "actual English fallback is identified");
Check(!string.IsNullOrWhiteSpace(Body().GetParsedText()), "fallback document remains readable");
Shot(reviewMod + "_documentation_fallback");
Finish(null);
break;
}
step++;
}
private ModMarkdownView Body() => detail.GetNode<ModMarkdownView>("Scroll/Content/DocumentationBox/Body");
private OptionButton Languages() => detail.GetNode<OptionButton>("Scroll/Content/DocumentationBox/Header/Languages");
private static bool IsVisibleCard(ModCard card) => card.IsVisibleInTree();
private bool IsReviewedCard(ModCard card) => IsVisibleCard(card) && card.GetNode<Label>("VBox/Body/Box/Title").Text == reviewTitle;
private static bool IsVisibleDetail(ModDetailView page) => page.IsVisibleInTree();
private static bool IsScreenshotArg(string arg) => arg.StartsWith("--shots=");
@@ -175,6 +281,14 @@ namespace Mods.ModManager.DocumentationTest
return;
done = true;
hub?.QueueFree();
if (settingsPath != null)
{
GameData.Instance.Settings.Language = settingsLanguage;
if (settingsBackup == null)
File.Delete(settingsPath);
else
File.WriteAllBytes(settingsPath, settingsBackup);
}
if (originalLocale != null)
LocaleLoader.LoadLocale(originalLocale);
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);