diff --git a/ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs b/ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs index 6eabeeef0..5cd471d4c 100644 --- a/ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs +++ b/ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs @@ -18,9 +18,11 @@ public class UpdateUiSettingsHandler(IConfigElementRepository configElementRepos { await configElementRepository.Upsert( ConfigElementKey.PagesIsDarkMode, - uiSettings.PagesIsDarkMode, + uiSettings.IsDarkMode, cancellationToken); + await configElementRepository.Upsert(ConfigElementKey.PagesLanguage, uiSettings.Language, cancellationToken); + return Unit.Default; } } diff --git a/ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs b/ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs index e074d71ae..ce07bc0c0 100644 --- a/ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs +++ b/ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs @@ -12,9 +12,14 @@ public class GetUiSettingsHandler(IConfigElementRepository configElementReposito ConfigElementKey.PagesIsDarkMode, cancellationToken); + Option pagesLanguage = await configElementRepository.GetValue( + ConfigElementKey.PagesLanguage, + cancellationToken); + return new UiSettingsViewModel { - PagesIsDarkMode = await pagesIsDarkMode.IfNoneAsync(true) + IsDarkMode = await pagesIsDarkMode.IfNoneAsync(true), + Language = await pagesLanguage.IfNoneAsync("en") }; } } diff --git a/ErsatzTV.Application/Configuration/UiSettingsViewModel.cs b/ErsatzTV.Application/Configuration/UiSettingsViewModel.cs index 101fc74c9..bfa3bab24 100644 --- a/ErsatzTV.Application/Configuration/UiSettingsViewModel.cs +++ b/ErsatzTV.Application/Configuration/UiSettingsViewModel.cs @@ -2,5 +2,7 @@ namespace ErsatzTV.Application.Configuration; public class UiSettingsViewModel { - public bool PagesIsDarkMode { get; set; } + public bool IsDarkMode { get; set; } + + public string Language { get; set; } } diff --git a/ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs b/ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs index 6e26de7ae..468f173f1 100644 --- a/ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs +++ b/ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs @@ -112,7 +112,7 @@ public class GetFuturePlayoutItemsByIdHandler(IDbContextFactory dbCon gap.FinishOffset, TimeSpan.FromSeconds(Math.Round(gapDuration.TotalSeconds)).ToString( gapDuration.TotalHours >= 1 ? @"h\:mm\:ss" : @"mm\:ss", - CultureInfo.CurrentUICulture.DateTimeFormat), + CultureInfo.CurrentCulture.DateTimeFormat), None ); }).ToList(); diff --git a/ErsatzTV.Core/Domain/ConfigElementKey.cs b/ErsatzTV.Core/Domain/ConfigElementKey.cs index c1f89cdbf..181aa8a99 100644 --- a/ErsatzTV.Core/Domain/ConfigElementKey.cs +++ b/ErsatzTV.Core/Domain/ConfigElementKey.cs @@ -32,6 +32,7 @@ public class ConfigElementKey public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey HDHRUUID => new("hdhr.uuid"); public static ConfigElementKey PagesIsDarkMode => new("pages.is_dark_mode"); + public static ConfigElementKey PagesLanguage => new("pages.language"); public static ConfigElementKey ChannelsPageSize => new("pages.channels.page_size"); public static ConfigElementKey ChannelsShowDisabled => new("pages.channels.show_disabled"); public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size"); diff --git a/ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs b/ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs deleted file mode 100644 index 24506f2de..000000000 --- a/ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs +++ /dev/null @@ -1,5 +0,0 @@ -using MediatR; - -namespace ErsatzTV.Core.Notifications; - -public record ThemeUpdatedNotification : INotification; diff --git a/ErsatzTV/DatabaseRequestCultureProvider.cs b/ErsatzTV/DatabaseRequestCultureProvider.cs new file mode 100644 index 000000000..6bebd6a58 --- /dev/null +++ b/ErsatzTV/DatabaseRequestCultureProvider.cs @@ -0,0 +1,27 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using Microsoft.AspNetCore.Localization; + +namespace ErsatzTV; + +public class DatabaseRequestCultureProvider(AcceptLanguageHeaderRequestCultureProvider defaultProvider) + : RequestCultureProvider +{ + public override async Task DetermineProviderCultureResult(HttpContext httpContext) + { + var configElementRepository = httpContext.RequestServices.GetService(); + + var defaultResult = await defaultProvider.DetermineProviderCultureResult(httpContext); + string configuredLanguage = await configElementRepository.GetValue( + ConfigElementKey.PagesLanguage, + CancellationToken.None) + .IfNoneAsync("en"); + + if (defaultResult != null) + { + return new ProviderCultureResult(defaultResult.Cultures, [configuredLanguage]); + } + + return new ProviderCultureResult(configuredLanguage); + } +} diff --git a/ErsatzTV/Locals/Resources.cs b/ErsatzTV/Locals/Resources.cs new file mode 100644 index 000000000..bf1e68b9a --- /dev/null +++ b/ErsatzTV/Locals/Resources.cs @@ -0,0 +1,5 @@ +namespace ErsatzTV; + +public class Resources +{ +} diff --git a/ErsatzTV/Locals/Resources.pt-br.resx b/ErsatzTV/Locals/Resources.pt-br.resx new file mode 100644 index 000000000..02845abc7 --- /dev/null +++ b/ErsatzTV/Locals/Resources.pt-br.resx @@ -0,0 +1,17 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Canais + + \ No newline at end of file diff --git a/ErsatzTV/Locals/Resources.resx b/ErsatzTV/Locals/Resources.resx new file mode 100644 index 000000000..6dcb7c059 --- /dev/null +++ b/ErsatzTV/Locals/Resources.resx @@ -0,0 +1,6 @@ + + + + Channels + + diff --git a/ErsatzTV/Pages/DecoTemplateEditor.razor b/ErsatzTV/Pages/DecoTemplateEditor.razor index 078f92b35..1cfbd5fdd 100644 --- a/ErsatzTV/Pages/DecoTemplateEditor.razor +++ b/ErsatzTV/Pages/DecoTemplateEditor.razor @@ -61,7 +61,7 @@ @foreach (DateTime startTime in _startTimes) { - @startTime.ToString(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern) + @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern) } @@ -129,7 +129,7 @@ ShowDatePicker="false" ShowTodayButton="false" DayTimeInterval="CalendarTimeInterval.Minutes10" - Use24HourClock="@(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern.Contains("H"))" + Use24HourClock="@(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains("H"))" EnableDragItems="true" EnableResizeItems="false" ItemChanged="@(ci => CalendarItemChanged(ci))"/> diff --git a/ErsatzTV/Pages/Logs.razor b/ErsatzTV/Pages/Logs.razor index 4e2a07cc7..4ce5f7b3c 100644 --- a/ErsatzTV/Pages/Logs.razor +++ b/ErsatzTV/Pages/Logs.razor @@ -58,7 +58,7 @@ @code { private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; private MudTable _table; private int _rowsPerPage = 10; diff --git a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor index 41b279a18..0752f5ebc 100644 --- a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor +++ b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor @@ -224,7 +224,7 @@ @code { private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; [Parameter] public int Id { get; set; } diff --git a/ErsatzTV/Pages/PlayoutTemplatesEditor.razor b/ErsatzTV/Pages/PlayoutTemplatesEditor.razor index 73c4ed049..350d58fce 100644 --- a/ErsatzTV/Pages/PlayoutTemplatesEditor.razor +++ b/ErsatzTV/Pages/PlayoutTemplatesEditor.razor @@ -273,7 +273,7 @@ @code { private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; [Parameter] public int Id { get; set; } diff --git a/ErsatzTV/Pages/Playouts.razor b/ErsatzTV/Pages/Playouts.razor index b6ba0d239..3889254a1 100644 --- a/ErsatzTV/Pages/Playouts.razor +++ b/ErsatzTV/Pages/Playouts.razor @@ -263,7 +263,7 @@ @code { private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; private readonly List _currentPage = []; private MudTable _table; diff --git a/ErsatzTV/Pages/Settings/UISettings.razor b/ErsatzTV/Pages/Settings/UISettings.razor index 4cd908eae..d6067bcdc 100644 --- a/ErsatzTV/Pages/Settings/UISettings.razor +++ b/ErsatzTV/Pages/Settings/UISettings.razor @@ -4,6 +4,7 @@ @implements IDisposable @inject IMediator Mediator @inject ISnackbar Snackbar +@inject NavigationManager NavigationManager @inject ILogger Logger @@ -16,13 +17,22 @@
- Default Theme + Theme
- + Dark Light
+ +
+ Language +
+ + English + Português (Brasil) + +
@@ -67,8 +77,7 @@ if (result.IsRight) { - Snackbar.Add("Successfully saved UI settings", Severity.Success); - await Mediator.Publish(new ThemeUpdatedNotification(), _cts.Token); + NavigationManager.Refresh(true); } } diff --git a/ErsatzTV/Pages/TemplateEditor.razor b/ErsatzTV/Pages/TemplateEditor.razor index 504a67b70..6f3c8b190 100644 --- a/ErsatzTV/Pages/TemplateEditor.razor +++ b/ErsatzTV/Pages/TemplateEditor.razor @@ -61,7 +61,7 @@ @foreach (DateTime startTime in _startTimes) { - @startTime.ToString(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern) + @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern) } @@ -104,7 +104,7 @@ ShowDatePicker="false" ShowTodayButton="false" DayTimeInterval="CalendarTimeInterval.Minutes10" - Use24HourClock="@(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern.Contains("H"))" + Use24HourClock="@(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains("H"))" EnableDragItems="true" EnableResizeItems="false" ItemChanged="@(ci => CalendarItemChanged(ci))"/> diff --git a/ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor b/ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor index 85f182aef..3b9cd729a 100644 --- a/ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor +++ b/ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor @@ -91,7 +91,7 @@ public int? BlockId { get; set; } private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; private int _rowsPerPage = 10; private int? _selectedPlayoutHistoryId; private PlayoutHistoryDetailsViewModel _decodedHistory; diff --git a/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor b/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor index 4713c4765..c77676d26 100644 --- a/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor +++ b/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor @@ -193,7 +193,7 @@ @code { private CancellationTokenSource _cts; - private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; + private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat; private readonly List _ffmpegProfiles = []; private readonly List _streamSelectors = []; private readonly List _watermarks = []; diff --git a/ErsatzTV/Shared/MainLayout.razor b/ErsatzTV/Shared/MainLayout.razor index 03e45128b..a89d90ae0 100644 --- a/ErsatzTV/Shared/MainLayout.razor +++ b/ErsatzTV/Shared/MainLayout.razor @@ -8,6 +8,7 @@ @using ErsatzTV.Core.Notifications @using ErsatzTV.Extensions @using MediatR.Courier +@using Microsoft.Extensions.Localization @implements IDisposable @inject NavigationManager NavigationManager @inject IMediator Mediator @@ -15,6 +16,7 @@ @inject ISearchTargets SearchTargets; @inject ICourier Courier @inject IHealthCheckService HealthCheckService; +@inject IStringLocalizer StringLocalizer; @@ -116,7 +118,7 @@ { - Channels + @StringLocalizer["ButtonMainMenuChannels"] FFmpeg Profiles Watermarks @@ -286,7 +288,6 @@ Courier.Subscribe(HandleHealthCheckSummary); Courier.Subscribe(HandlePlayoutUpdated); - Courier.Subscribe(HandleThemeUpdated); } protected override async Task OnInitializedAsync() @@ -304,7 +305,6 @@ Courier.UnSubscribe(HandleHealthCheckSummary); Courier.UnSubscribe(HandlePlayoutUpdated); - Courier.UnSubscribe(HandleThemeUpdated); _cts?.Cancel(); _cts?.Dispose(); @@ -520,20 +520,4 @@ } } - private async Task HandleThemeUpdated(ThemeUpdatedNotification _, CancellationToken cancellationToken) - { - try - { - _isDarkMode = await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.PagesIsDarkMode), cancellationToken) - .MapT(result => !bool.TryParse(result.Value, out bool value) || value) - .IfNoneAsync(true); - - await InvokeAsync(StateHasChanged); - } - catch (Exception) - { - // do nothing - } - } - } diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index d09a766a5..b33410376 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -87,6 +87,7 @@ using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; @@ -604,9 +605,13 @@ public class Startup { CultureInfo[] cinfo = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures); string[] supportedCultures = cinfo.Select(t => t.Name).Distinct().ToArray(); + string[] supportedUiCultures = ["en-us", "pt-br"]; options.AddSupportedCultures(supportedCultures) - .AddSupportedUICultures(supportedCultures) - .SetDefaultCulture("en-US"); + .AddSupportedUICultures(supportedUiCultures) + .SetDefaultCulture("en-us"); + options.AddInitialRequestCultureProvider( + new DatabaseRequestCultureProvider( + new AcceptLanguageHeaderRequestCultureProvider())); }); app.UseStaticFiles();