diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 3ce83439..72024f75 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -17,6 +17,7 @@ https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.0 Oqtane true + true @@ -30,6 +31,7 @@ + diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index af283cec..4c2d1201 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -1,19 +1,23 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Reflection; -using System.Threading.Tasks; using System.Runtime.Loader; +using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; +using Microsoft.JSInterop; using Oqtane.Modules; using Oqtane.Providers; -using Oqtane.Shared; using Oqtane.Services; +using Oqtane.Shared; +using Oqtane.UI; namespace Oqtane.Client { @@ -62,6 +66,7 @@ namespace Oqtane.Client builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); await LoadClientAssemblies(httpClient); @@ -88,6 +93,19 @@ namespace Oqtane.Client } var host = builder.Build(); + var jsRuntime = host.Services.GetRequiredService(); + var interop = new Interop(jsRuntime); + var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName); + var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie).UICultures[0].Value; + var localizationService = host.Services.GetRequiredService(); + var cultures = await localizationService.GetCulturesAsync(); + + if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase))) + { + culture = cultures.Single(c => c.IsDefault).Name; + } + + SetCulture(culture); ServiceActivator.Configure(host.Services); @@ -142,5 +160,12 @@ namespace Oqtane.Client } } } + + private static void SetCulture(string culture) + { + var cultureInfo = CultureInfo.GetCultureInfo(culture); + CultureInfo.DefaultThreadCurrentCulture = cultureInfo; + CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; + } } } diff --git a/Oqtane.Client/Services/Interfaces/ILocalizationService.cs b/Oqtane.Client/Services/Interfaces/ILocalizationService.cs new file mode 100644 index 00000000..b6cd7da0 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ILocalizationService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Services +{ + public interface ILocalizationService + { + Task> GetCulturesAsync(); + } +} diff --git a/Oqtane.Client/Services/LocalizationService.cs b/Oqtane.Client/Services/LocalizationService.cs new file mode 100644 index 00000000..b6c56bca --- /dev/null +++ b/Oqtane.Client/Services/LocalizationService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class LocalizationService : ServiceBase, ILocalizationService + { + private readonly SiteState _siteState; + + public LocalizationService(HttpClient http, SiteState siteState) : base(http) + { + _siteState = siteState; + } + + private string Apiurl => CreateApiUrl(_siteState.Alias, "Localization"); + + public async Task> GetCulturesAsync() => await GetJsonAsync>(Apiurl); + } +} diff --git a/Oqtane.Client/Themes/Controls/ControlPanel.razor b/Oqtane.Client/Themes/Controls/ControlPanel.razor index 02ae862c..0ea20a8a 100644 --- a/Oqtane.Client/Themes/Controls/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/ControlPanel.razor @@ -198,6 +198,8 @@ } + + @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) || (PageState.Page.IsPersonalizable && PageState.User != null)) { if (PageState.EditMode) diff --git a/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor new file mode 100644 index 00000000..6f748e88 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/LanguageSwitcher.razor @@ -0,0 +1,46 @@ +@namespace Oqtane.Themes.Controls +@inherits ThemeControlBase +@using System.Globalization +@using Microsoft.AspNetCore.Localization; +@using Oqtane.Models +@inject ILocalizationService LocalizationService +@inject NavigationManager NavigationManager + +@if (_supportedCultures != null && Visible) +{ +
+ + +
+} + +@code{ + private IEnumerable _supportedCultures; + + [Parameter] + public bool Visible { get; set; } = true; + + protected override async Task OnParametersSetAsync() + { + _supportedCultures = await LocalizationService.GetCulturesAsync(); + } + + private async Task SetCultureAsync(string culture) + { + if (culture != CultureInfo.CurrentUICulture.Name) + { + var interop = new Interop(JSRuntime); + var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); + + NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + } + } +} diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 53b8df4f..3964b070 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -1,5 +1,4 @@ -using Microsoft.JSInterop; -using Oqtane.Models; +using Microsoft.JSInterop; using System.Threading.Tasks; namespace Oqtane.UI @@ -233,6 +232,5 @@ namespace Oqtane.UI return Task.CompletedTask; } } - } } diff --git a/Oqtane.Server/Controllers/LocalizationController.cs b/Oqtane.Server/Controllers/LocalizationController.cs new file mode 100644 index 00000000..84b54862 --- /dev/null +++ b/Oqtane.Server/Controllers/LocalizationController.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.Default)] + public class LocalizationController : Controller + { + private readonly ILocalizationManager _localizationManager; + + public LocalizationController(ILocalizationManager localizationManager) + { + _localizationManager = localizationManager; + } + + // GET: api/localization + [HttpGet()] + public IEnumerable Get() + => _localizationManager.GetSupportedCultures().Select(c => new Culture { + Name = CultureInfo.GetCultureInfo(c).Name, + DisplayName = CultureInfo.GetCultureInfo(c).DisplayName, + IsDefault = _localizationManager.GetDefaultCulture() + .Equals(CultureInfo.GetCultureInfo(c).Name, StringComparison.OrdinalIgnoreCase) + }); + } +} diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index 67676d16..4f43068e 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Builder; @@ -31,11 +30,9 @@ namespace Oqtane.Extensions var defaultCulture = localizationManager.GetDefaultCulture(); var supportedCultures = localizationManager.GetSupportedCultures(); - CultureInfo.CurrentUICulture = new CultureInfo(defaultCulture); - app.UseRequestLocalization(options => { options.SetDefaultCulture(defaultCulture) - .AddSupportedUICultures(supportedCultures) + .AddSupportedCultures(supportedCultures) .AddSupportedUICultures(supportedCultures); }); diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index 2aa0dd9d..cf2d69f0 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -1,17 +1,9 @@ -@page "/" +@page "/" @namespace Oqtane.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@using System.Globalization -@using Microsoft.AspNetCore.Localization @using Microsoft.Extensions.Configuration @inject IConfiguration Configuration @model Oqtane.Pages.HostModel - -@{ - // Set localization cookie - var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture)); - HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue); -} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index b008ee30..b134a19e 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -127,6 +127,7 @@ namespace Oqtane services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); diff --git a/Oqtane.Shared/Models/Culture.cs b/Oqtane.Shared/Models/Culture.cs new file mode 100644 index 00000000..d426410d --- /dev/null +++ b/Oqtane.Shared/Models/Culture.cs @@ -0,0 +1,11 @@ +namespace Oqtane.Models +{ + public class Culture + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public bool IsDefault { get; set; } + } +}