Localize all hardcoded English strings in renderers

Renderers now use LocalizationManager for all UI text: box opening,
loot reveal, feature unlock, prompts, and error messages. Added
missing localization keys to both en.json and fr.json.
This commit is contained in:
Samuel Bouchet 2026-03-10 20:31:46 +01:00
parent 2825621d0a
commit c580e1cf2e
6 changed files with 68 additions and 38 deletions

View file

@ -27,11 +27,22 @@
"prompt.press_key": "Press any key to continue...",
"box.opening": "Opening {0}...",
"box.opened": "{0} opened! (Rarity: {1})",
"box.opened_short": "{0} opened!",
"box.shimmer": "Something shimmers...",
"box.found": "You found: {0}!",
"box.found_box": "Inside was... another box! {0}!",
"box.empty": "The box is empty! How philosophical.",
"box.no_boxes": "You have no boxes. How did you manage that?",
"box.auto_open": "{0} opens automatically!",
"loot.received": "You received:",
"loot.title": "Loot!",
"loot.name": "Name",
"loot.rarity": "Rarity",
"loot.category": "Category",
"ui.feature_unlocked": "NEW FEATURE UNLOCKED: {0}",
"prompt.what_do": "What do you do?",
"prompt.invalid_choice": "Please enter a number between 1 and {0}.",
"box.starter": "Starter Box",
"box.starter.desc": "Your first box. The beginning of everything. Or nothing. Probably something though.",

View file

@ -27,11 +27,22 @@
"prompt.press_key": "Appuie sur une touche pour continuer...",
"box.opening": "Ouverture de {0}...",
"box.opened": "{0} ouverte ! (Rarete : {1})",
"box.opened_short": "{0} ouverte !",
"box.shimmer": "Quelque chose scintille...",
"box.found": "Tu as trouve : {0} !",
"box.found_box": "A l'interieur il y avait... une autre boite ! {0} !",
"box.empty": "La boite est vide ! Philosophique.",
"box.no_boxes": "Tu n'as aucune boite. Comment t'as fait ?",
"box.auto_open": "{0} s'ouvre automatiquement !",
"loot.received": "Tu as recu :",
"loot.title": "Butin !",
"loot.name": "Nom",
"loot.rarity": "Rarete",
"loot.category": "Categorie",
"ui.feature_unlocked": "NOUVELLE FONCTIONNALITE : {0}",
"prompt.what_do": "Que fais-tu ?",
"prompt.invalid_choice": "Entre un nombre entre 1 et {0}.",
"box.starter": "Boite de depart",
"box.starter.desc": "Ta premiere boite. Le debut de tout. Ou de rien. Probablement de quelque chose quand meme.",

View file

@ -28,7 +28,7 @@ public static class Program
_saveManager = new SaveManager();
_loc = new LocalizationManager(Locale.EN);
_renderContext = new RenderContext();
_renderer = RendererFactory.Create(_renderContext);
_renderer = RendererFactory.Create(_renderContext, _loc);
await MainMenuLoop();
}
@ -127,7 +127,7 @@ public static class Program
);
_simulation = new GameSimulation(_registry);
_renderContext = RenderContext.FromGameState(_state);
_renderer = RendererFactory.Create(_renderContext);
_renderer = RendererFactory.Create(_renderContext, _loc);
}
private static void ChangeLanguage()
@ -141,7 +141,7 @@ public static class Program
if (_state != null)
_state.CurrentLocale = newLocale;
_renderer = RendererFactory.Create(_renderContext);
_renderer = RendererFactory.Create(_renderContext, _loc);
}
private static async Task GameLoop()
@ -264,7 +264,7 @@ public static class Program
case UIFeatureUnlockedEvent uiEvt:
_renderContext.Unlock(uiEvt.Feature);
_renderer = RendererFactory.Create(_renderContext);
_renderer = RendererFactory.Create(_renderContext, _loc);
var featureKey = $"meta.{uiEvt.Feature.ToString().ToLower()}";
_renderer.ShowUIFeatureUnlocked(
_loc.Get("meta.unlocked", _loc.Get(featureKey)));

View file

@ -1,4 +1,5 @@
using OpenTheBox.Core;
using OpenTheBox.Localization;
namespace OpenTheBox.Rendering;
@ -7,7 +8,7 @@ namespace OpenTheBox.Rendering;
/// No colors, no frames, no fancy stuff. This is the "stone age" of the UI,
/// deliberately ugly and minimal.
/// </summary>
public sealed class BasicRenderer : IRenderer
public sealed class BasicRenderer(LocalizationManager loc) : IRenderer
{
public void ShowMessage(string message)
{
@ -21,15 +22,15 @@ public sealed class BasicRenderer : IRenderer
public void ShowBoxOpening(string boxName, string rarity)
{
Console.WriteLine($"Opening {boxName}...");
Console.WriteLine(loc.Get("box.opening", boxName));
Console.WriteLine("...");
Console.WriteLine("......");
Console.WriteLine($"Box opened! (Rarity: {rarity})");
Console.WriteLine(loc.Get("box.opened", boxName, rarity));
}
public void ShowLootReveal(List<(string name, string rarity, string category)> items)
{
Console.WriteLine("You received:");
Console.WriteLine(loc.Get("loot.received"));
for (int i = 0; i < items.Count; i++)
{
var (name, rarity, category) = items[i];
@ -53,7 +54,7 @@ public sealed class BasicRenderer : IRenderer
{
return choice - 1;
}
Console.WriteLine($"Please enter a number between 1 and {options.Count}.");
Console.WriteLine(loc.Get("prompt.invalid_choice", options.Count));
}
}
@ -80,7 +81,7 @@ public sealed class BasicRenderer : IRenderer
public int ShowAdventureChoice(List<string> options)
{
Console.WriteLine("What do you do?");
Console.WriteLine(loc.Get("prompt.what_do"));
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine($" {i + 1}. {options[i]}");
@ -94,14 +95,14 @@ public sealed class BasicRenderer : IRenderer
{
return choice - 1;
}
Console.WriteLine($"Please enter a number between 1 and {options.Count}.");
Console.WriteLine(loc.Get("prompt.invalid_choice", options.Count));
}
}
public void ShowUIFeatureUnlocked(string featureName)
{
Console.WriteLine("========================================");
Console.WriteLine($" NEW FEATURE UNLOCKED: {featureName}");
Console.WriteLine($" {loc.Get("ui.feature_unlocked", featureName)}");
Console.WriteLine("========================================");
}
@ -112,7 +113,7 @@ public sealed class BasicRenderer : IRenderer
public void WaitForKeyPress(string? message = null)
{
Console.WriteLine(message ?? "Press any key to continue...");
Console.WriteLine(message ?? loc.Get("prompt.press_key"));
Console.ReadKey(intercept: true);
Console.WriteLine();
}

View file

@ -1,3 +1,5 @@
using OpenTheBox.Localization;
namespace OpenTheBox.Rendering;
/// <summary>
@ -11,7 +13,7 @@ public static class RendererFactory
/// If the context has any Spectre-capable feature unlocked, a <see cref="SpectreRenderer"/>
/// is returned; otherwise the plain <see cref="BasicRenderer"/> is used.
/// </summary>
public static IRenderer Create(RenderContext context)
public static IRenderer Create(RenderContext context, LocalizationManager loc)
{
bool hasAnySpectreFeature =
context.HasColors ||
@ -29,9 +31,9 @@ public static class RendererFactory
if (hasAnySpectreFeature)
{
return new SpectreRenderer(context);
return new SpectreRenderer(context, loc);
}
return new BasicRenderer();
return new BasicRenderer(loc);
}
}

View file

@ -1,6 +1,7 @@
using OpenTheBox.Core;
using OpenTheBox.Core.Characters;
using OpenTheBox.Core.Enums;
using OpenTheBox.Localization;
using OpenTheBox.Rendering.Panels;
using Spectre.Console;
@ -48,26 +49,26 @@ public sealed class SpectreRenderer : IRenderer
AnsiConsole.Status()
.Spinner(Spinner.Known.Star)
.SpinnerStyle(new Style(RarityColorValue(rarity)))
.Start($"Opening [bold {color}]{Markup.Escape(boxName)}[/]...", ctx =>
.Start(Markup.Escape(_loc.Get("box.opening", boxName)), ctx =>
{
Thread.Sleep(1500);
ctx.Status($"[bold {color}]Something shimmers...[/]");
ctx.Status($"[bold {color}]{Markup.Escape(_loc.Get("box.shimmer"))}[/]");
Thread.Sleep(1000);
});
AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(boxName)}[/] opened!");
AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(_loc.Get("box.opened_short", boxName))}[/]");
}
else if (_context.HasColors)
{
string color = RarityColor(rarity);
AnsiConsole.MarkupLine($"Opening [bold {color}]{Markup.Escape(boxName)}[/]...");
AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(_loc.Get("box.opening", boxName))}[/]");
Thread.Sleep(800);
AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(boxName)}[/] opened!");
AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(_loc.Get("box.opened_short", boxName))}[/]");
}
else
{
Console.WriteLine($"Opening {boxName}...");
Console.WriteLine(_loc.Get("box.opening", boxName));
Thread.Sleep(500);
Console.WriteLine($"{boxName} opened! (Rarity: {rarity})");
Console.WriteLine(_loc.Get("box.opened", boxName, rarity));
}
}
@ -79,10 +80,10 @@ public sealed class SpectreRenderer : IRenderer
{
var table = new Table()
.Border(TableBorder.Rounded)
.Title("[bold yellow]Loot![/]")
.AddColumn(new TableColumn("[bold]Name[/]").Centered())
.AddColumn(new TableColumn("[bold]Rarity[/]").Centered())
.AddColumn(new TableColumn("[bold]Category[/]").Centered());
.Title($"[bold yellow]{Markup.Escape(_loc.Get("loot.title"))}[/]")
.AddColumn(new TableColumn($"[bold]{Markup.Escape(_loc.Get("loot.name"))}[/]").Centered())
.AddColumn(new TableColumn($"[bold]{Markup.Escape(_loc.Get("loot.rarity"))}[/]").Centered())
.AddColumn(new TableColumn($"[bold]{Markup.Escape(_loc.Get("loot.category"))}[/]").Centered());
foreach (var (name, rarity, category) in items)
{
@ -97,7 +98,7 @@ public sealed class SpectreRenderer : IRenderer
}
else if (_context.HasColors)
{
AnsiConsole.MarkupLine("[bold yellow]You received:[/]");
AnsiConsole.MarkupLine($"[bold yellow]{Markup.Escape(_loc.Get("loot.received"))}[/]");
foreach (var (name, rarity, category) in items)
{
string color = RarityColor(rarity);
@ -106,7 +107,7 @@ public sealed class SpectreRenderer : IRenderer
}
else
{
Console.WriteLine("You received:");
Console.WriteLine(_loc.Get("loot.received"));
foreach (var (name, rarity, category) in items)
{
Console.WriteLine($" - {name} [{rarity}] ({category})");
@ -155,13 +156,14 @@ public sealed class SpectreRenderer : IRenderer
return choice - 1;
}
string errorMsg = _loc.Get("prompt.invalid_choice", options.Count);
if (_context.HasColors)
{
AnsiConsole.MarkupLine($"[red]Please enter a number between 1 and {options.Count}.[/]");
AnsiConsole.MarkupLine($"[red]{Markup.Escape(errorMsg)}[/]");
}
else
{
Console.WriteLine($"Please enter a number between 1 and {options.Count}.");
Console.WriteLine(errorMsg);
}
}
}
@ -222,14 +224,14 @@ public sealed class SpectreRenderer : IRenderer
{
string selected = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("[bold yellow]What do you do?[/]")
.Title($"[bold yellow]{Markup.Escape(_loc.Get("prompt.what_do"))}[/]")
.PageSize(10)
.AddChoices(options));
return options.IndexOf(selected);
}
return ShowSelection("What do you do?", options);
return ShowSelection(_loc.Get("prompt.what_do"), options);
}
// ── UI feature unlock announcement ──────────────────────────────────
@ -238,14 +240,14 @@ public sealed class SpectreRenderer : IRenderer
{
if (_context.HasColors)
{
AnsiConsole.Write(new Rule($"[bold yellow]NEW FEATURE UNLOCKED[/]").RuleStyle("yellow"));
AnsiConsole.Write(new Rule($"[bold yellow]{Markup.Escape(_loc.Get("ui.feature_unlocked", featureName))}[/]").RuleStyle("yellow"));
AnsiConsole.Write(new FigletText(featureName).Color(Color.Yellow).Centered());
AnsiConsole.Write(new Rule().RuleStyle("yellow"));
}
else
{
Console.WriteLine("========================================");
Console.WriteLine($" NEW FEATURE UNLOCKED: {featureName}");
Console.WriteLine($" {_loc.Get("ui.feature_unlocked", featureName)}");
Console.WriteLine("========================================");
}
}
@ -268,13 +270,14 @@ public sealed class SpectreRenderer : IRenderer
public void WaitForKeyPress(string? message = null)
{
string text = message ?? _loc.Get("prompt.press_key");
if (_context.HasColors)
{
AnsiConsole.MarkupLine($"[dim]{Markup.Escape(message ?? "Press any key to continue...")}[/]");
AnsiConsole.MarkupLine($"[dim]{Markup.Escape(text)}[/]");
}
else
{
Console.WriteLine(message ?? "Press any key to continue...");
Console.WriteLine(text);
}
Console.ReadKey(intercept: true);
@ -290,10 +293,12 @@ public sealed class SpectreRenderer : IRenderer
// ── Construction ────────────────────────────────────────────────────
private RenderContext _context;
private readonly LocalizationManager _loc;
public SpectreRenderer(RenderContext context)
public SpectreRenderer(RenderContext context, LocalizationManager loc)
{
_context = context;
_loc = loc;
}
/// <summary>