Enhance internationalization support by integrating language selection for school names, updating UI components for localization, and revising documentation. Implement locale-aware formatting for dates and strings, ensuring a seamless user experience in both Russian and English. Update tests to cover new language features.
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
namespace HSchool.Simulation;
|
||||
|
||||
/// <summary>Which word list the random-name button draws from.</summary>
|
||||
public enum SchoolNameLanguage
|
||||
{
|
||||
Russian = 0,
|
||||
English = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suggestions for the "random name" button. Lives here rather than in the client because the
|
||||
/// server is the one that knows which names are already taken.
|
||||
@@ -8,13 +15,35 @@ public sealed class SchoolNameGenerator(Random? random = null)
|
||||
{
|
||||
private const int AttemptsBeforeNumbering = 24;
|
||||
|
||||
private static readonly string[] Kinds = ["Школа", "Гимназия", "Лицей", "Школа-интернат"];
|
||||
private readonly record struct Catalog(
|
||||
string[] Kinds,
|
||||
string[] Epithets,
|
||||
string NumberSign,
|
||||
bool NumberSpace,
|
||||
bool EpithetFirst,
|
||||
string FallbackKind);
|
||||
|
||||
private static readonly string[] Epithets =
|
||||
[
|
||||
"Северная", "Приморская", "Заречная", "Нагорная", "Слободская", "Озёрная",
|
||||
"Кленовая", "Рябиновая", "Солнечная", "Луговая", "Тихая", "Ясная",
|
||||
];
|
||||
private static readonly Catalog Russian = new(
|
||||
["Школа", "Гимназия", "Лицей", "Школа-интернат"],
|
||||
[
|
||||
"Северная", "Приморская", "Заречная", "Нагорная", "Слободская", "Озёрная",
|
||||
"Кленовая", "Рябиновая", "Солнечная", "Луговая", "Тихая", "Ясная",
|
||||
],
|
||||
"№",
|
||||
false,
|
||||
false,
|
||||
"Школа");
|
||||
|
||||
private static readonly Catalog English = new(
|
||||
["School", "Academy", "Grammar School", "High School"],
|
||||
[
|
||||
"Northern", "Seaside", "Riverside", "Hillside", "Maple", "Rowan",
|
||||
"Sunny", "Meadow", "Quiet", "Clear", "Oak", "Pine",
|
||||
],
|
||||
"No.",
|
||||
true,
|
||||
true,
|
||||
"School");
|
||||
|
||||
private readonly Random _random = random ?? Random.Shared;
|
||||
|
||||
@@ -22,13 +51,14 @@ public sealed class SchoolNameGenerator(Random? random = null)
|
||||
/// A name that is not in <paramref name="taken"/>. Falls back to a numbered name so the
|
||||
/// button always produces something, even when the pool is exhausted.
|
||||
/// </summary>
|
||||
public string Next(IEnumerable<string> taken)
|
||||
public string Next(IEnumerable<string> taken, SchoolNameLanguage language = SchoolNameLanguage.Russian)
|
||||
{
|
||||
var catalog = language == SchoolNameLanguage.English ? English : Russian;
|
||||
var used = new HashSet<string>(taken, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var attempt = 0; attempt < AttemptsBeforeNumbering; attempt++)
|
||||
{
|
||||
var candidate = Compose();
|
||||
var candidate = Compose(catalog);
|
||||
if (used.Add(candidate))
|
||||
{
|
||||
return candidate;
|
||||
@@ -37,7 +67,7 @@ public sealed class SchoolNameGenerator(Random? random = null)
|
||||
|
||||
for (var number = 1; ; number++)
|
||||
{
|
||||
var candidate = $"Школа №{number}";
|
||||
var candidate = Numbered(catalog, catalog.FallbackKind, number);
|
||||
if (!used.Contains(candidate))
|
||||
{
|
||||
return candidate;
|
||||
@@ -45,13 +75,24 @@ public sealed class SchoolNameGenerator(Random? random = null)
|
||||
}
|
||||
}
|
||||
|
||||
private string Compose()
|
||||
private string Compose(Catalog catalog)
|
||||
{
|
||||
var kind = Kinds[_random.Next(Kinds.Length)];
|
||||
var kind = catalog.Kinds[_random.Next(catalog.Kinds.Length)];
|
||||
|
||||
// Half the names are numbered, half are named — both read like a real school.
|
||||
return _random.Next(2) == 0
|
||||
? $"{kind} №{_random.Next(1, 100)}"
|
||||
: $"{kind} «{Epithets[_random.Next(Epithets.Length)]}»";
|
||||
if (_random.Next(2) == 0)
|
||||
{
|
||||
return Numbered(catalog, kind, _random.Next(1, 100));
|
||||
}
|
||||
|
||||
var epithet = catalog.Epithets[_random.Next(catalog.Epithets.Length)];
|
||||
return catalog.EpithetFirst
|
||||
? $"{epithet} {kind}"
|
||||
: $"{kind} «{epithet}»";
|
||||
}
|
||||
|
||||
private static string Numbered(Catalog catalog, string kind, int number) =>
|
||||
catalog.NumberSpace
|
||||
? $"{kind} {catalog.NumberSign} {number}"
|
||||
: $"{kind} {catalog.NumberSign}{number}";
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ public sealed class SchoolRegistry : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>A name the player has not used yet, for the "random" button in the creation form.</summary>
|
||||
public string SuggestName() => NameGenerator.Next(_schools.Select(school => school.Name));
|
||||
public string SuggestName(SchoolNameLanguage language = SchoolNameLanguage.Russian) =>
|
||||
NameGenerator.Next(_schools.Select(school => school.Name), language);
|
||||
|
||||
/// <summary>Trims, strips control characters and enforces the length limit.</summary>
|
||||
public static bool TryNormalizeName(string? name, out string normalized)
|
||||
|
||||
Reference in New Issue
Block a user