Browse Source

separate current culture (formatting) and ui culture (language)

pull/2809/head
Jason Dove 6 months ago
parent
commit
daba15aacc
No known key found for this signature in database
  1. 4
      ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs
  2. 7
      ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs
  3. 4
      ErsatzTV.Application/Configuration/UiSettingsViewModel.cs
  4. 2
      ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs
  5. 1
      ErsatzTV.Core/Domain/ConfigElementKey.cs
  6. 5
      ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs
  7. 27
      ErsatzTV/DatabaseRequestCultureProvider.cs
  8. 5
      ErsatzTV/Locals/Resources.cs
  9. 17
      ErsatzTV/Locals/Resources.pt-br.resx
  10. 6
      ErsatzTV/Locals/Resources.resx
  11. 4
      ErsatzTV/Pages/DecoTemplateEditor.razor
  12. 2
      ErsatzTV/Pages/Logs.razor
  13. 2
      ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor
  14. 2
      ErsatzTV/Pages/PlayoutTemplatesEditor.razor
  15. 2
      ErsatzTV/Pages/Playouts.razor
  16. 17
      ErsatzTV/Pages/Settings/UISettings.razor
  17. 4
      ErsatzTV/Pages/TemplateEditor.razor
  18. 2
      ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor
  19. 2
      ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor
  20. 22
      ErsatzTV/Shared/MainLayout.razor
  21. 9
      ErsatzTV/Startup.cs

4
ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs

@ -18,9 +18,11 @@ public class UpdateUiSettingsHandler(IConfigElementRepository configElementRepos
{ {
await configElementRepository.Upsert( await configElementRepository.Upsert(
ConfigElementKey.PagesIsDarkMode, ConfigElementKey.PagesIsDarkMode,
uiSettings.PagesIsDarkMode, uiSettings.IsDarkMode,
cancellationToken); cancellationToken);
await configElementRepository.Upsert(ConfigElementKey.PagesLanguage, uiSettings.Language, cancellationToken);
return Unit.Default; return Unit.Default;
} }
} }

7
ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs

@ -12,9 +12,14 @@ public class GetUiSettingsHandler(IConfigElementRepository configElementReposito
ConfigElementKey.PagesIsDarkMode, ConfigElementKey.PagesIsDarkMode,
cancellationToken); cancellationToken);
Option<string> pagesLanguage = await configElementRepository.GetValue<string>(
ConfigElementKey.PagesLanguage,
cancellationToken);
return new UiSettingsViewModel return new UiSettingsViewModel
{ {
PagesIsDarkMode = await pagesIsDarkMode.IfNoneAsync(true) IsDarkMode = await pagesIsDarkMode.IfNoneAsync(true),
Language = await pagesLanguage.IfNoneAsync("en")
}; };
} }
} }

4
ErsatzTV.Application/Configuration/UiSettingsViewModel.cs

@ -2,5 +2,7 @@ namespace ErsatzTV.Application.Configuration;
public class UiSettingsViewModel public class UiSettingsViewModel
{ {
public bool PagesIsDarkMode { get; set; } public bool IsDarkMode { get; set; }
public string Language { get; set; }
} }

2
ErsatzTV.Application/Playouts/Queries/GetFuturePlayoutItemsByIdHandler.cs

@ -112,7 +112,7 @@ public class GetFuturePlayoutItemsByIdHandler(IDbContextFactory<TvContext> dbCon
gap.FinishOffset, gap.FinishOffset,
TimeSpan.FromSeconds(Math.Round(gapDuration.TotalSeconds)).ToString( TimeSpan.FromSeconds(Math.Round(gapDuration.TotalSeconds)).ToString(
gapDuration.TotalHours >= 1 ? @"h\:mm\:ss" : @"mm\:ss", gapDuration.TotalHours >= 1 ? @"h\:mm\:ss" : @"mm\:ss",
CultureInfo.CurrentUICulture.DateTimeFormat), CultureInfo.CurrentCulture.DateTimeFormat),
None None
); );
}).ToList(); }).ToList();

1
ErsatzTV.Core/Domain/ConfigElementKey.cs

@ -32,6 +32,7 @@ public class ConfigElementKey
public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count");
public static ConfigElementKey HDHRUUID => new("hdhr.uuid"); public static ConfigElementKey HDHRUUID => new("hdhr.uuid");
public static ConfigElementKey PagesIsDarkMode => new("pages.is_dark_mode"); 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 ChannelsPageSize => new("pages.channels.page_size");
public static ConfigElementKey ChannelsShowDisabled => new("pages.channels.show_disabled"); public static ConfigElementKey ChannelsShowDisabled => new("pages.channels.show_disabled");
public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size"); public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size");

5
ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs

@ -1,5 +0,0 @@
using MediatR;
namespace ErsatzTV.Core.Notifications;
public record ThemeUpdatedNotification : INotification;

27
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<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var configElementRepository = httpContext.RequestServices.GetService<IConfigElementRepository>();
var defaultResult = await defaultProvider.DetermineProviderCultureResult(httpContext);
string configuredLanguage = await configElementRepository.GetValue<string>(
ConfigElementKey.PagesLanguage,
CancellationToken.None)
.IfNoneAsync("en");
if (defaultResult != null)
{
return new ProviderCultureResult(defaultResult.Cultures, [configuredLanguage]);
}
return new ProviderCultureResult(configuredLanguage);
}
}

5
ErsatzTV/Locals/Resources.cs

@ -0,0 +1,5 @@
namespace ErsatzTV;
public class Resources
{
}

17
ErsatzTV/Locals/Resources.pt-br.resx

@ -0,0 +1,17 @@
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ButtonMainMenuChannels" xml:space="preserve">
<value>Canais</value>
</data>
</root>

6
ErsatzTV/Locals/Resources.resx

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="ButtonMainMenuChannels" xml:space="preserve">
<value>Channels</value>
</data>
</root>

4
ErsatzTV/Pages/DecoTemplateEditor.razor

@ -61,7 +61,7 @@
@foreach (DateTime startTime in _startTimes) @foreach (DateTime startTime in _startTimes)
{ {
<MudSelectItem Value="@startTime"> <MudSelectItem Value="@startTime">
@startTime.ToString(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern) @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern)
</MudSelectItem> </MudSelectItem>
} }
</MudSelect> </MudSelect>
@ -129,7 +129,7 @@
ShowDatePicker="false" ShowDatePicker="false"
ShowTodayButton="false" ShowTodayButton="false"
DayTimeInterval="CalendarTimeInterval.Minutes10" DayTimeInterval="CalendarTimeInterval.Minutes10"
Use24HourClock="@(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern.Contains("H"))" Use24HourClock="@(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains("H"))"
EnableDragItems="true" EnableDragItems="true"
EnableResizeItems="false" EnableResizeItems="false"
ItemChanged="@(ci => CalendarItemChanged(ci))"/> ItemChanged="@(ci => CalendarItemChanged(ci))"/>

2
ErsatzTV/Pages/Logs.razor

@ -58,7 +58,7 @@
@code { @code {
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
private MudTable<LogEntryViewModel> _table; private MudTable<LogEntryViewModel> _table;
private int _rowsPerPage = 10; private int _rowsPerPage = 10;

2
ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor

@ -224,7 +224,7 @@
@code { @code {
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
[Parameter] [Parameter]
public int Id { get; set; } public int Id { get; set; }

2
ErsatzTV/Pages/PlayoutTemplatesEditor.razor

@ -273,7 +273,7 @@
@code { @code {
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
[Parameter] [Parameter]
public int Id { get; set; } public int Id { get; set; }

2
ErsatzTV/Pages/Playouts.razor

@ -263,7 +263,7 @@
@code { @code {
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
private readonly List<PlayoutNameViewModel> _currentPage = []; private readonly List<PlayoutNameViewModel> _currentPage = [];
private MudTable<PlayoutNameViewModel> _table; private MudTable<PlayoutNameViewModel> _table;

17
ErsatzTV/Pages/Settings/UISettings.razor

@ -4,6 +4,7 @@
@implements IDisposable @implements IDisposable
@inject IMediator Mediator @inject IMediator Mediator
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject NavigationManager NavigationManager
@inject ILogger<UISettings> Logger @inject ILogger<UISettings> Logger
<MudForm Style="max-height: 100%"> <MudForm Style="max-height: 100%">
@ -16,13 +17,22 @@
<MudDivider Class="mb-6"/> <MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Default Theme</MudText> <MudText>Theme</MudText>
</div> </div>
<MudSelect @bind-Value="_uiSettings.PagesIsDarkMode"> <MudSelect @bind-Value="_uiSettings.IsDarkMode">
<MudSelectItem Value="@true">Dark</MudSelectItem> <MudSelectItem Value="@true">Dark</MudSelectItem>
<MudSelectItem Value="@false">Light</MudSelectItem> <MudSelectItem Value="@false">Light</MudSelectItem>
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Language</MudText>
</div>
<MudSelect @bind-Value="_uiSettings.Language">
<MudSelectItem Value="@("en-us")">English</MudSelectItem>
<MudSelectItem Value="@("pt-br")">Português (Brasil)</MudSelectItem>
</MudSelect>
</MudStack>
</MudContainer> </MudContainer>
</div> </div>
</MudForm> </MudForm>
@ -67,8 +77,7 @@
if (result.IsRight) if (result.IsRight)
{ {
Snackbar.Add("Successfully saved UI settings", Severity.Success); NavigationManager.Refresh(true);
await Mediator.Publish(new ThemeUpdatedNotification(), _cts.Token);
} }
} }

4
ErsatzTV/Pages/TemplateEditor.razor

@ -61,7 +61,7 @@
@foreach (DateTime startTime in _startTimes) @foreach (DateTime startTime in _startTimes)
{ {
<MudSelectItem Value="@startTime"> <MudSelectItem Value="@startTime">
@startTime.ToString(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern) @startTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern)
</MudSelectItem> </MudSelectItem>
} }
</MudSelect> </MudSelect>
@ -104,7 +104,7 @@
ShowDatePicker="false" ShowDatePicker="false"
ShowTodayButton="false" ShowTodayButton="false"
DayTimeInterval="CalendarTimeInterval.Minutes10" DayTimeInterval="CalendarTimeInterval.Minutes10"
Use24HourClock="@(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern.Contains("H"))" Use24HourClock="@(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains("H"))"
EnableDragItems="true" EnableDragItems="true"
EnableResizeItems="false" EnableResizeItems="false"
ItemChanged="@(ci => CalendarItemChanged(ci))"/> ItemChanged="@(ci => CalendarItemChanged(ci))"/>

2
ErsatzTV/Pages/Troubleshooting/BlockPlayoutHistory.razor

@ -91,7 +91,7 @@
public int? BlockId { get; set; } public int? BlockId { get; set; }
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
private int _rowsPerPage = 10; private int _rowsPerPage = 10;
private int? _selectedPlayoutHistoryId; private int? _selectedPlayoutHistoryId;
private PlayoutHistoryDetailsViewModel _decodedHistory; private PlayoutHistoryDetailsViewModel _decodedHistory;

2
ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor

@ -193,7 +193,7 @@
@code { @code {
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat; private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentCulture.DateTimeFormat;
private readonly List<FFmpegProfileViewModel> _ffmpegProfiles = []; private readonly List<FFmpegProfileViewModel> _ffmpegProfiles = [];
private readonly List<string> _streamSelectors = []; private readonly List<string> _streamSelectors = [];
private readonly List<WatermarkViewModel> _watermarks = []; private readonly List<WatermarkViewModel> _watermarks = [];

22
ErsatzTV/Shared/MainLayout.razor

@ -8,6 +8,7 @@
@using ErsatzTV.Core.Notifications @using ErsatzTV.Core.Notifications
@using ErsatzTV.Extensions @using ErsatzTV.Extensions
@using MediatR.Courier @using MediatR.Courier
@using Microsoft.Extensions.Localization
@implements IDisposable @implements IDisposable
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IMediator Mediator @inject IMediator Mediator
@ -15,6 +16,7 @@
@inject ISearchTargets SearchTargets; @inject ISearchTargets SearchTargets;
@inject ICourier Courier @inject ICourier Courier
@inject IHealthCheckService HealthCheckService; @inject IHealthCheckService HealthCheckService;
@inject IStringLocalizer<Resources> StringLocalizer;
<MudThemeProvider Theme="ErsatzTvTheme" IsDarkMode="_isDarkMode"/> <MudThemeProvider Theme="ErsatzTvTheme" IsDarkMode="_isDarkMode"/>
<MudDialogProvider BackdropClick="false"/> <MudDialogProvider BackdropClick="false"/>
@ -116,7 +118,7 @@
{ {
<MudDrawer @bind-Open="@_drawerIsOpen" Elevation="2" ClipMode="DrawerClipMode.Always"> <MudDrawer @bind-Open="@_drawerIsOpen" Elevation="2" ClipMode="DrawerClipMode.Always">
<MudNavMenu> <MudNavMenu>
<MudNavLink Href="channels">Channels</MudNavLink> <MudNavLink Href="channels">@StringLocalizer["ButtonMainMenuChannels"]</MudNavLink>
<MudNavLink Href="ffmpeg">FFmpeg Profiles</MudNavLink> <MudNavLink Href="ffmpeg">FFmpeg Profiles</MudNavLink>
<MudNavLink Href="watermarks">Watermarks</MudNavLink> <MudNavLink Href="watermarks">Watermarks</MudNavLink>
<MudNavGroup Title="Media Sources"> <MudNavGroup Title="Media Sources">
@ -286,7 +288,6 @@
Courier.Subscribe<HealthCheckSummary>(HandleHealthCheckSummary); Courier.Subscribe<HealthCheckSummary>(HandleHealthCheckSummary);
Courier.Subscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated); Courier.Subscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated);
Courier.Subscribe<ThemeUpdatedNotification>(HandleThemeUpdated);
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@ -304,7 +305,6 @@
Courier.UnSubscribe<HealthCheckSummary>(HandleHealthCheckSummary); Courier.UnSubscribe<HealthCheckSummary>(HandleHealthCheckSummary);
Courier.UnSubscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated); Courier.UnSubscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated);
Courier.UnSubscribe<ThemeUpdatedNotification>(HandleThemeUpdated);
_cts?.Cancel(); _cts?.Cancel();
_cts?.Dispose(); _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
}
}
} }

9
ErsatzTV/Startup.cs

@ -87,6 +87,7 @@ using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@ -604,9 +605,13 @@ public class Startup
{ {
CultureInfo[] cinfo = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures); CultureInfo[] cinfo = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);
string[] supportedCultures = cinfo.Select(t => t.Name).Distinct().ToArray(); string[] supportedCultures = cinfo.Select(t => t.Name).Distinct().ToArray();
string[] supportedUiCultures = ["en-us", "pt-br"];
options.AddSupportedCultures(supportedCultures) options.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures) .AddSupportedUICultures(supportedUiCultures)
.SetDefaultCulture("en-US"); .SetDefaultCulture("en-us");
options.AddInitialRequestCultureProvider(
new DatabaseRequestCultureProvider(
new AcceptLanguageHeaderRequestCultureProvider()));
}); });
app.UseStaticFiles(); app.UseStaticFiles();

Loading…
Cancel
Save