Enhance school creation and staffing management with native language support
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 14s

- Updated protocol documentation to include `nativeLanguages` in `nameSets` and added `nativeLanguage` to school creation options.
- Enhanced the UI for school creation to allow selection of native languages, improving user experience.
- Revised API interfaces to accommodate new native language features, ensuring proper data handling.
- Improved localization strings to support new native language functionalities in both English and Russian.
- Updated tests to validate the new native language features and ensure robust functionality in staffing scenarios.
This commit is contained in:
Leonid Pershin
2026-08-19 22:22:31 +03:00
parent 21d79cb49b
commit 5a00ad7746
48 changed files with 2064 additions and 312 deletions
+23 -2
View File
@@ -86,7 +86,7 @@ internal sealed record CatalogResponse(
IReadOnlyList<DefInfoResponse> Floors,
IReadOnlyList<RoomInfoResponse> Rooms,
IReadOnlyList<DefInfoResponse> Things,
IReadOnlyList<DefInfoResponse> NameSets,
IReadOnlyList<NameSetInfoResponse> NameSets,
IReadOnlyList<SubjectInfoResponse> Subjects,
DayFrameResponse? DayFrame,
IReadOnlyList<HolidayInfoResponse> Holidays,
@@ -99,7 +99,7 @@ internal sealed record CatalogResponse(
Placeable(catalog.Floors.Values, catalog, locale),
PlaceableRooms(catalog, locale),
PlaceableThings(catalog, locale),
Placeable(catalog.NameSets.Values, catalog, locale),
PlaceableNameSets(catalog, locale),
PlaceableSubjects(catalog, locale),
MapDayFrame(catalog, locale),
PlaceableHolidays(catalog, locale),
@@ -113,6 +113,22 @@ internal sealed record CatalogResponse(
.Select(def => new DefInfoResponse(def.DefName, catalog.Label(locale, def)))
.ToArray();
private static IReadOnlyList<NameSetInfoResponse> PlaceableNameSets(DefCatalog catalog, string locale) =>
catalog.NameSets.Values
.Where(def => !def.Abstract)
.OrderBy(def => def.DefName, StringComparer.Ordinal)
.Select(def => new NameSetInfoResponse(
def.DefName,
catalog.Label(locale, def),
def.Spoken
.Select(skill => new DefInfoResponse(
skill,
catalog.Skills.TryGetValue(skill, out var language)
? catalog.Label(locale, language)
: skill))
.ToArray()))
.ToArray();
private static IReadOnlyList<DefInfoResponse> PlaceableThings(DefCatalog catalog, string locale) =>
catalog.Things.Values
.Where(def => !def.Abstract)
@@ -178,6 +194,11 @@ internal sealed record CatalogResponse(
internal sealed record DefInfoResponse(string DefName, string Label, int PupilSlots = 0);
internal sealed record NameSetInfoResponse(
string DefName,
string Label,
IReadOnlyList<DefInfoResponse> NativeLanguages);
internal sealed record RoomInfoResponse(
string DefName,
string Label,
+16 -4
View File
@@ -206,7 +206,8 @@ internal sealed record ApplicantResponse(
bool IsParent,
float HourlyWageAsk,
float MonthlyBase,
IReadOnlyList<LabeledStatResponse> Skills);
IReadOnlyList<LabeledStatResponse> Skills,
IReadOnlyList<DefLabelResponse> Traits);
internal sealed record StaffMemberResponse(
string Id,
@@ -257,7 +258,8 @@ internal static class StaffingMapper
applicant.Person.IsParent,
applicant.HourlyWageAsk,
rules is null ? 0f : Staffing.MonthlyBase(rules, applicant.HourlyWageAsk),
StrongSkills(applicant.Person, catalog, locale)))
SkillsOf(applicant.Person, catalog, locale),
TraitsOf(applicant.Person, catalog, locale)))
.ToArray();
var staff = roster.People
@@ -290,12 +292,11 @@ internal static class StaffingMapper
return new StaffingResponse(allocated, payroll, remaining, uncovered, applicants, staff, positions, subjects);
}
private static IReadOnlyList<LabeledStatResponse> StrongSkills(Person person, DefCatalog? catalog, string locale)
private static IReadOnlyList<LabeledStatResponse> SkillsOf(Person person, DefCatalog? catalog, string locale)
{
return person.Skills
.OrderByDescending(pair => pair.Value)
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
.Take(3)
.Select(pair => new LabeledStatResponse(
pair.Key,
catalog is not null && catalog.Skills.TryGetValue(pair.Key, out var def)
@@ -305,6 +306,17 @@ internal static class StaffingMapper
.ToArray();
}
private static IReadOnlyList<DefLabelResponse> TraitsOf(Person person, DefCatalog? catalog, string locale)
{
return person.Traits
.Select(name => new DefLabelResponse(
name,
catalog is not null && catalog.Traits.TryGetValue(name, out var def)
? catalog.Label(locale, def)
: name))
.ToArray();
}
private static StaffMemberResponse Member(
Person person,
Roster roster,
+5 -1
View File
@@ -54,6 +54,7 @@ internal static class SchoolEndpoints
request.ModIds,
request.Map,
request.NameSetId,
request.NativeLanguage,
NewCompletion<SchoolCreationOutcome>());
commands.Enqueue(command);
@@ -77,6 +78,8 @@ internal static class SchoolEndpoints
Problem(StatusCodes.Status400BadRequest, "invalid-catalog", "The selected packs could not be loaded."),
SchoolCreationError.UnknownNameSet =>
Problem(StatusCodes.Status400BadRequest, "unknown-name-set", "The selected name set is not in the catalog."),
SchoolCreationError.UnknownNativeLanguage =>
Problem(StatusCodes.Status400BadRequest, "unknown-native-language", "The selected native language is not in that name set."),
_ => Results.Problem("Unknown error."),
};
})
@@ -486,7 +489,8 @@ internal sealed record CreateSchoolRequest(
DateTime StartDate,
IReadOnlyList<string>? ModIds,
MapLayout? Map,
string? NameSetId);
string? NameSetId,
string? NativeLanguage);
internal sealed record SchoolResponse(int Id, string Name, DateTime GameTime, bool Running, byte SpeedIndex)
{