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; }
+ }
+}