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...", "prompt.press_key": "Press any key to continue...",
"box.opening": "Opening {0}...", "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": "You found: {0}!",
"box.found_box": "Inside was... another box! {0}!", "box.found_box": "Inside was... another box! {0}!",
"box.empty": "The box is empty! How philosophical.", "box.empty": "The box is empty! How philosophical.",
"box.no_boxes": "You have no boxes. How did you manage that?", "box.no_boxes": "You have no boxes. How did you manage that?",
"box.auto_open": "{0} opens automatically!", "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": "Starter Box",
"box.starter.desc": "Your first box. The beginning of everything. Or nothing. Probably something though.", "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...", "prompt.press_key": "Appuie sur une touche pour continuer...",
"box.opening": "Ouverture de {0}...", "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": "Tu as trouve : {0} !",
"box.found_box": "A l'interieur il y avait... une autre boite ! {0} !", "box.found_box": "A l'interieur il y avait... une autre boite ! {0} !",
"box.empty": "La boite est vide ! Philosophique.", "box.empty": "La boite est vide ! Philosophique.",
"box.no_boxes": "Tu n'as aucune boite. Comment t'as fait ?", "box.no_boxes": "Tu n'as aucune boite. Comment t'as fait ?",
"box.auto_open": "{0} s'ouvre automatiquement !", "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": "Boite de depart",
"box.starter.desc": "Ta premiere boite. Le debut de tout. Ou de rien. Probablement de quelque chose quand meme.", "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(); _saveManager = new SaveManager();
_loc = new LocalizationManager(Locale.EN); _loc = new LocalizationManager(Locale.EN);
_renderContext = new RenderContext(); _renderContext = new RenderContext();
_renderer = RendererFactory.Create(_renderContext); _renderer = RendererFactory.Create(_renderContext, _loc);
await MainMenuLoop(); await MainMenuLoop();
} }
@ -127,7 +127,7 @@ public static class Program
); );
_simulation = new GameSimulation(_registry); _simulation = new GameSimulation(_registry);
_renderContext = RenderContext.FromGameState(_state); _renderContext = RenderContext.FromGameState(_state);
_renderer = RendererFactory.Create(_renderContext); _renderer = RendererFactory.Create(_renderContext, _loc);
} }
private static void ChangeLanguage() private static void ChangeLanguage()
@ -141,7 +141,7 @@ public static class Program
if (_state != null) if (_state != null)
_state.CurrentLocale = newLocale; _state.CurrentLocale = newLocale;
_renderer = RendererFactory.Create(_renderContext); _renderer = RendererFactory.Create(_renderContext, _loc);
} }
private static async Task GameLoop() private static async Task GameLoop()
@ -264,7 +264,7 @@ public static class Program
case UIFeatureUnlockedEvent uiEvt: case UIFeatureUnlockedEvent uiEvt:
_renderContext.Unlock(uiEvt.Feature); _renderContext.Unlock(uiEvt.Feature);
_renderer = RendererFactory.Create(_renderContext); _renderer = RendererFactory.Create(_renderContext, _loc);
var featureKey = $"meta.{uiEvt.Feature.ToString().ToLower()}"; var featureKey = $"meta.{uiEvt.Feature.ToString().ToLower()}";
_renderer.ShowUIFeatureUnlocked( _renderer.ShowUIFeatureUnlocked(
_loc.Get("meta.unlocked", _loc.Get(featureKey))); _loc.Get("meta.unlocked", _loc.Get(featureKey)));

View file

@ -1,4 +1,5 @@
using OpenTheBox.Core; using OpenTheBox.Core;
using OpenTheBox.Localization;
namespace OpenTheBox.Rendering; 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, /// No colors, no frames, no fancy stuff. This is the "stone age" of the UI,
/// deliberately ugly and minimal. /// deliberately ugly and minimal.
/// </summary> /// </summary>
public sealed class BasicRenderer : IRenderer public sealed class BasicRenderer(LocalizationManager loc) : IRenderer
{ {
public void ShowMessage(string message) public void ShowMessage(string message)
{ {
@ -21,15 +22,15 @@ public sealed class BasicRenderer : IRenderer
public void ShowBoxOpening(string boxName, string rarity) public void ShowBoxOpening(string boxName, string rarity)
{ {
Console.WriteLine($"Opening {boxName}..."); Console.WriteLine(loc.Get("box.opening", boxName));
Console.WriteLine("..."); Console.WriteLine("...");
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) 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++) for (int i = 0; i < items.Count; i++)
{ {
var (name, rarity, category) = items[i]; var (name, rarity, category) = items[i];
@ -53,7 +54,7 @@ public sealed class BasicRenderer : IRenderer
{ {
return choice - 1; 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) 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++) for (int i = 0; i < options.Count; i++)
{ {
Console.WriteLine($" {i + 1}. {options[i]}"); Console.WriteLine($" {i + 1}. {options[i]}");
@ -94,14 +95,14 @@ public sealed class BasicRenderer : IRenderer
{ {
return choice - 1; 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) public void ShowUIFeatureUnlocked(string featureName)
{ {
Console.WriteLine("========================================"); Console.WriteLine("========================================");
Console.WriteLine($" NEW FEATURE UNLOCKED: {featureName}"); Console.WriteLine($" {loc.Get("ui.feature_unlocked", featureName)}");
Console.WriteLine("========================================"); Console.WriteLine("========================================");
} }
@ -112,7 +113,7 @@ public sealed class BasicRenderer : IRenderer
public void WaitForKeyPress(string? message = null) 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.ReadKey(intercept: true);
Console.WriteLine(); Console.WriteLine();
} }

View file

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