refactor: hoist Moscow offset + sport labels into shared helpers (HIGH)
* New Marathon.Domain.ValueObjects.MoscowTime with Offset, Now, and EndOfMoscowDay(DateOnly) — replaces ~15 inline TimeSpan.FromHours(3) literals across Domain/Application/Infrastructure/UI. * New Marathon.UI.Services.SportLabels.Resolve(IStringLocalizer, int) — replaces 6 near-identical SportLabel switch bodies in EventListShell, Events/Detail, Anomalies/AnomalyFeed, Results/ResultsList, Results/ResultsLoader, and AnomalyCard. Single source of truth for the 6/11/22723/43658 sport-code mapping. Pages keep a one-liner wrapper so the call sites stay terse.
This commit is contained in:
@@ -2,6 +2,7 @@ using Marathon.Application.Abstractions;
|
||||
using Marathon.Application.Configuration;
|
||||
using Marathon.Domain.AnomalyDetection;
|
||||
using Marathon.Domain.Entities;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -24,7 +25,6 @@ namespace Marathon.Application.UseCases;
|
||||
/// </remarks>
|
||||
public sealed class DetectAnomaliesUseCase
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
private static readonly TimeSpan SnapshotLookback = TimeSpan.FromHours(24);
|
||||
|
||||
// Dedup window: two anomalies for the same event within this window are considered duplicates.
|
||||
@@ -67,7 +67,7 @@ public sealed class DetectAnomaliesUseCase
|
||||
var events = await _eventRepo.ListAsync(ct);
|
||||
int newAnomalyCount = 0;
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToOffset(MoscowOffset);
|
||||
var now = MoscowTime.Now;
|
||||
var from = now - SnapshotLookback;
|
||||
|
||||
// Hoisted outside the per-event loop: load existing anomalies ONCE per cycle
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace Marathon.Domain.AnomalyDetection;
|
||||
/// </summary>
|
||||
public sealed class AnomalyDetector
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private readonly int _suspensionGapSeconds;
|
||||
private readonly decimal _oddsFlipThreshold;
|
||||
private readonly int _minSnapshotCount;
|
||||
@@ -156,7 +154,7 @@ public sealed class AnomalyDetector
|
||||
return new Anomaly(
|
||||
Id: Guid.NewGuid(),
|
||||
EventId: eventId,
|
||||
DetectedAt: DateTimeOffset.UtcNow.ToOffset(MoscowOffset),
|
||||
DetectedAt: MoscowTime.Now,
|
||||
Kind: AnomalyKind.SuspensionFlip,
|
||||
Score: clampedScore,
|
||||
EvidenceJson: evidenceJson);
|
||||
|
||||
@@ -21,8 +21,6 @@ public sealed record Event(
|
||||
string Side1Name,
|
||||
string Side2Name)
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
public EventId Id { get; } = Id ?? throw new ArgumentNullException(nameof(Id));
|
||||
|
||||
public SportCode Sport { get; } = Sport ?? throw new ArgumentNullException(nameof(Sport));
|
||||
@@ -37,7 +35,7 @@ public sealed record Event(
|
||||
|
||||
public string Category { get; } = Category ?? string.Empty;
|
||||
|
||||
public DateTimeOffset ScheduledAt { get; } = ScheduledAt.Offset == MoscowOffset
|
||||
public DateTimeOffset ScheduledAt { get; } = ScheduledAt.Offset == MoscowTime.Offset
|
||||
? ScheduledAt
|
||||
: throw new ArgumentException(
|
||||
$"ScheduledAt must be in Europe/Moscow time (UTC+03:00). " +
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Marathon.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Single source of truth for the Moscow timezone offset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// marathonbet.by serves all timestamps in Moscow time (UTC+3) and the domain
|
||||
/// invariant on <see cref="Marathon.Domain.Entities.Event.ScheduledAt"/>
|
||||
/// rejects any other offset. Code that constructs <see cref="DateTimeOffset"/>
|
||||
/// values for events, results, snapshots, or test fixtures MUST use this
|
||||
/// constant rather than re-deriving <c>TimeSpan.FromHours(3)</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class MoscowTime
|
||||
{
|
||||
/// <summary>The Moscow time offset (UTC+3).</summary>
|
||||
public static readonly TimeSpan Offset = TimeSpan.FromHours(3);
|
||||
|
||||
/// <summary>Current Moscow time.</summary>
|
||||
public static DateTimeOffset Now => DateTimeOffset.UtcNow.ToOffset(Offset);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the inclusive end-of-day for the given Moscow date — i.e.,
|
||||
/// the moment one second before the next day starts. Used by date-range
|
||||
/// filters where the user picks "to: 2026-05-09" meaning "through the
|
||||
/// rest of that day."
|
||||
/// </summary>
|
||||
public static DateTimeOffset EndOfMoscowDay(DateOnly date) =>
|
||||
new DateTimeOffset(date.ToDateTime(TimeOnly.MinValue), Offset)
|
||||
.AddDays(1).AddSeconds(-1);
|
||||
}
|
||||
@@ -17,8 +17,6 @@ namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
/// </summary>
|
||||
public abstract class EventListingParserBase
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
protected readonly IServerTimeProvider ServerTimeProvider;
|
||||
protected readonly ILogger Logger;
|
||||
|
||||
@@ -36,7 +34,7 @@ public abstract class EventListingParserBase
|
||||
CancellationToken ct)
|
||||
{
|
||||
var serverTime = ServerTimeProvider.ExtractServerTime(html)
|
||||
?? DateTimeOffset.UtcNow.ToOffset(MoscowOffset);
|
||||
?? MoscowTime.Now;
|
||||
|
||||
var config = AngleSharpConfig.Default;
|
||||
using var context = BrowsingContext.New(config);
|
||||
|
||||
@@ -18,8 +18,6 @@ namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
/// </summary>
|
||||
public sealed partial class EventOddsParser : IEventOddsParser
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private readonly IServerTimeProvider _serverTime;
|
||||
private readonly PeriodScopeMapper _periodMapper;
|
||||
private readonly ILogger<EventOddsParser> _logger;
|
||||
@@ -69,7 +67,7 @@ public sealed partial class EventOddsParser : IEventOddsParser
|
||||
ArgumentNullException.ThrowIfNull(html);
|
||||
|
||||
var capturedAt = _serverTime.ExtractServerTime(html)
|
||||
?? DateTimeOffset.UtcNow.ToOffset(MoscowOffset);
|
||||
?? MoscowTime.Now;
|
||||
|
||||
var config = AngleSharpConfig.Default;
|
||||
using var context = BrowsingContext.New(config);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
|
||||
@@ -13,8 +14,6 @@ namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
/// </summary>
|
||||
public static partial class MoscowDateParser
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
// Matches "HH:MM"
|
||||
[GeneratedRegex(@"^\s*(\d{1,2}):(\d{2})\s*$", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex TimeOnlyRegex();
|
||||
@@ -70,7 +69,7 @@ public static partial class MoscowDateParser
|
||||
var scheduled = new DateTimeOffset(
|
||||
today.Year, today.Month, today.Day,
|
||||
hour, minute, 0,
|
||||
MoscowOffset);
|
||||
MoscowTime.Offset);
|
||||
|
||||
// If the computed time is already in the past (same day but earlier),
|
||||
// that's fine — the event may have already started (live) or the listing
|
||||
@@ -98,7 +97,7 @@ public static partial class MoscowDateParser
|
||||
if (candidate < DateOnly.FromDateTime(anchorDate))
|
||||
year++; // e.g., anchor is Dec 2026 and event is in Jan 2027
|
||||
|
||||
return new DateTimeOffset(year, month, day, hour, minute, 0, MoscowOffset);
|
||||
return new DateTimeOffset(year, month, day, hour, minute, 0, MoscowTime.Offset);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -21,8 +21,6 @@ namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
/// </remarks>
|
||||
public sealed partial class ResultsParser : IResultsParser
|
||||
{
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private readonly ILogger<ResultsParser> _logger;
|
||||
|
||||
// Matches score patterns like "2:1", "2:1 (1:1)", "2:1 (0:0) (2:1)"
|
||||
@@ -101,7 +99,7 @@ public sealed partial class ResultsParser : IResultsParser
|
||||
if (string.IsNullOrWhiteSpace(eventIdRaw))
|
||||
return null;
|
||||
|
||||
var completedAt = DateTimeOffset.UtcNow.ToOffset(MoscowOffset);
|
||||
var completedAt = MoscowTime.Now;
|
||||
|
||||
return new EventResult(
|
||||
new DomainEventId(eventIdRaw),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
@@ -17,8 +18,6 @@ public sealed partial class ServerTimeProvider : IServerTimeProvider
|
||||
RegexOptions.CultureInvariant)]
|
||||
private static partial Regex ServerTimeRegex();
|
||||
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private readonly ILogger<ServerTimeProvider> _logger;
|
||||
|
||||
public ServerTimeProvider(ILogger<ServerTimeProvider> logger)
|
||||
@@ -47,6 +46,6 @@ public sealed partial class ServerTimeProvider : IServerTimeProvider
|
||||
var minute = int.Parse(match.Groups[5].Value);
|
||||
var second = int.Parse(match.Groups[6].Value);
|
||||
|
||||
return new DateTimeOffset(year, month, day, hour, minute, second, MoscowOffset);
|
||||
return new DateTimeOffset(year, month, day, hour, minute, second, MoscowTime.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,14 +215,7 @@
|
||||
_ => kind.ToString(),
|
||||
};
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => string.Format(System.Globalization.CultureInfo.InvariantCulture, "Sport {0}", code),
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
private static string FormatRate(decimal? r) => r is { } v
|
||||
? v.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var moscow = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(3));
|
||||
var moscow = MoscowTime.Now;
|
||||
_from = InitialFrom ?? moscow.AddDays(-1).Date;
|
||||
_to = InitialTo ?? moscow.AddDays(1).Date;
|
||||
_kind = InitialKind;
|
||||
@@ -140,10 +140,9 @@
|
||||
try
|
||||
{
|
||||
// Use Moscow offset to match domain ScheduledAt invariant.
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
var range = new AppDateRange(
|
||||
new DateTimeOffset(_from.Value.Date, moscow),
|
||||
new DateTimeOffset(_to.Value.Date.AddDays(1).AddSeconds(-1), moscow));
|
||||
new DateTimeOffset(_from.Value.Date, MoscowTime.Offset),
|
||||
MoscowTime.EndOfMoscowDay(DateOnly.FromDateTime(_to.Value.Date)));
|
||||
|
||||
var path = await ExportUseCase.ExecuteAsync(range, _kind, CancellationToken.None);
|
||||
Dialog.Close(DialogResult.Ok(path));
|
||||
|
||||
@@ -252,8 +252,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
await UpdateFilter(_filter with { From = new DateTimeOffset(v.Date, moscow) });
|
||||
await UpdateFilter(_filter with { From = new DateTimeOffset(v.Date, MoscowTime.Offset) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,8 +260,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
await UpdateFilter(_filter with { To = new DateTimeOffset(v.Date, moscow).AddDays(1).AddSeconds(-1) });
|
||||
await UpdateFilter(_filter with { To = MoscowTime.EndOfMoscowDay(DateOnly.FromDateTime(v.Date)) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,14 +281,7 @@
|
||||
_ => L["Anomaly.Severity.Low"],
|
||||
};
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => string.Format(System.Globalization.CultureInfo.InvariantCulture, "Sport {0}", code),
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
private static string FormatDate(DateTimeOffset? value)
|
||||
=> value?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||||
|
||||
@@ -306,14 +306,7 @@
|
||||
_ => "—",
|
||||
};
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => $"Sport {code}",
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
private string BetTypeLabel(BetType t) => t switch
|
||||
{
|
||||
|
||||
@@ -164,8 +164,6 @@
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private DateTimeOffset _from;
|
||||
private DateTimeOffset _to;
|
||||
private string _searchInput = string.Empty;
|
||||
@@ -181,7 +179,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var todayMoscow = new DateTimeOffset(DateTime.UtcNow.Date, TimeSpan.Zero).ToOffset(MoscowOffset);
|
||||
var todayMoscow = new DateTimeOffset(DateTime.UtcNow.Date, TimeSpan.Zero).ToOffset(MoscowTime.Offset);
|
||||
_from = todayMoscow.AddDays(-30);
|
||||
_to = todayMoscow.AddDays(1).AddSeconds(-1);
|
||||
|
||||
@@ -246,7 +244,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
_from = new DateTimeOffset(v.Date, MoscowOffset);
|
||||
_from = new DateTimeOffset(v.Date, MoscowTime.Offset);
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
@@ -255,7 +253,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
_to = new DateTimeOffset(v.Date, MoscowOffset).AddDays(1).AddSeconds(-1);
|
||||
_to = new DateTimeOffset(v.Date, MoscowTime.Offset).AddDays(1).AddSeconds(-1);
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
@@ -311,14 +309,7 @@
|
||||
_ => "draw",
|
||||
};
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => $"Sport {code}",
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -204,8 +204,6 @@
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
|
||||
|
||||
private DateTimeOffset _from;
|
||||
private DateTimeOffset _to;
|
||||
private LoaderMode _mode = LoaderMode.AllInRange;
|
||||
@@ -234,7 +232,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var todayMoscow = new DateTimeOffset(DateTime.UtcNow.Date, TimeSpan.Zero).ToOffset(MoscowOffset);
|
||||
var todayMoscow = new DateTimeOffset(DateTime.UtcNow.Date, TimeSpan.Zero).ToOffset(MoscowTime.Offset);
|
||||
_from = todayMoscow.AddDays(-7);
|
||||
_to = todayMoscow.AddDays(1).AddSeconds(-1);
|
||||
await ReloadCandidatesAsync();
|
||||
@@ -347,7 +345,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
_from = new DateTimeOffset(v.Date, MoscowOffset);
|
||||
_from = new DateTimeOffset(v.Date, MoscowTime.Offset);
|
||||
await ReloadCandidatesAsync();
|
||||
}
|
||||
}
|
||||
@@ -356,7 +354,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
_to = new DateTimeOffset(v.Date, MoscowOffset).AddDays(1).AddSeconds(-1);
|
||||
_to = new DateTimeOffset(v.Date, MoscowTime.Offset).AddDays(1).AddSeconds(-1);
|
||||
await ReloadCandidatesAsync();
|
||||
}
|
||||
}
|
||||
@@ -400,14 +398,7 @@
|
||||
_ => o.ToString(),
|
||||
};
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => $"Sport {code}",
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
private static string FormatDate(DateTimeOffset value) => value.ToString("yyyy-MM-dd");
|
||||
|
||||
|
||||
@@ -437,8 +437,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
await UpdateFilter(_filter with { From = new DateTimeOffset(v.Date, moscow) });
|
||||
await UpdateFilter(_filter with { From = new DateTimeOffset(v.Date, MoscowTime.Offset) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,8 +445,7 @@
|
||||
{
|
||||
if (DateTimeOffset.TryParse(e.Value?.ToString(), out var v))
|
||||
{
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
await UpdateFilter(_filter with { To = new DateTimeOffset(v.Date, moscow).AddDays(1).AddSeconds(-1) });
|
||||
await UpdateFilter(_filter with { To = MoscowTime.EndOfMoscowDay(DateOnly.FromDateTime(v.Date)) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,14 +517,7 @@
|
||||
private static string FormatDate(DateTimeOffset value)
|
||||
=> value.ToString("yyyy-MM-dd");
|
||||
|
||||
private string SportLabel(int code) => code switch
|
||||
{
|
||||
6 => L["Sport.Basketball"],
|
||||
11 => L["Sport.Football"],
|
||||
22723 => L["Sport.Tennis"],
|
||||
43658 => L["Sport.Hockey"],
|
||||
_ => $"Sport {code}",
|
||||
};
|
||||
private string SportLabel(int code) => SportLabels.Resolve(L, code);
|
||||
|
||||
private readonly record struct RatesSnap(decimal? Win1, decimal? Draw, decimal? Win2);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
@@ -54,8 +56,7 @@ public sealed class EventBrowsingState
|
||||
public static PageFilter Default(DateTime nowUtc)
|
||||
{
|
||||
// Default window: -1d .. +7d in Moscow time, the same TZ events use.
|
||||
var moscow = TimeSpan.FromHours(3);
|
||||
var midnight = new DateTimeOffset(nowUtc.Date, TimeSpan.Zero).ToOffset(moscow);
|
||||
var midnight = new DateTimeOffset(nowUtc.Date, TimeSpan.Zero).ToOffset(MoscowTime.Offset);
|
||||
return new PageFilter(
|
||||
From: midnight.AddDays(-1),
|
||||
To: midnight.AddDays(7),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Globalization;
|
||||
using Marathon.UI.Resources;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace Marathon.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Single source of truth for the sport-code → display-label mapping that
|
||||
/// every list/detail page in the RCL needs. Resolves the four canonical
|
||||
/// data-sport-treeId values from the marathonbet.by spike (6 = basketball,
|
||||
/// 11 = football, 22723 = tennis, 43658 = hockey) against shared
|
||||
/// localization resources, with a stable invariant fallback for unknown
|
||||
/// codes so the UI degrades gracefully if the bookmaker adds a new sport.
|
||||
/// </summary>
|
||||
public static class SportLabels
|
||||
{
|
||||
public static string Resolve(IStringLocalizer<SharedResource> localizer, int code) =>
|
||||
code switch
|
||||
{
|
||||
6 => localizer["Sport.Basketball"],
|
||||
11 => localizer["Sport.Football"],
|
||||
22723 => localizer["Sport.Tennis"],
|
||||
43658 => localizer["Sport.Hockey"],
|
||||
_ => string.Format(CultureInfo.InvariantCulture, "Sport {0}", code),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user