diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor
index 52a958c2..fedc045b 100644
--- a/Oqtane.Client/Modules/Admin/Languages/Add.razor
+++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor
@@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
-@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
diff --git a/Oqtane.Client/Modules/Admin/Languages/Edit.razor b/Oqtane.Client/Modules/Admin/Languages/Edit.razor
index 7aa074c7..3a4060ac 100644
--- a/Oqtane.Client/Modules/Admin/Languages/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/Languages/Edit.razor
@@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
-@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor
index ffa2f1e3..4693891e 100644
--- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor
@@ -1,7 +1,6 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Globalization
-@using Microsoft.AspNetCore.Localization
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject ILanguageService LanguageService
diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj
index 63a3743c..5860982c 100644
--- a/Oqtane.Client/Oqtane.Client.csproj
+++ b/Oqtane.Client/Oqtane.Client.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs
index 9f5ed2ff..d71131cb 100644
--- a/Oqtane.Client/Program.cs
+++ b/Oqtane.Client/Program.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
@@ -13,13 +12,13 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
-using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Services;
+using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Client
@@ -258,7 +257,7 @@ namespace Oqtane.Client
var jsRuntime = serviceProvider.GetRequiredService();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
- var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
+ var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICulture.Name;
var localizationService = serviceProvider.GetRequiredService();
var cultures = await localizationService.GetCulturesAsync(false);
diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor
index 78a6a99c..807ac9de 100644
--- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor
+++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor
@@ -1,7 +1,6 @@
@using System.Globalization
-@using Microsoft.AspNetCore.Localization
-@using Microsoft.AspNetCore.Http
@using Oqtane.Models
+@using Microsoft.AspNetCore.Http
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject ILanguageService LanguageService
diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor
index 35d71322..d5eb3a3b 100644
--- a/Oqtane.Server/Components/App.razor
+++ b/Oqtane.Server/Components/App.razor
@@ -196,7 +196,7 @@
_bodyResources += ParseScripts(site.BodyContent);
// set culture if not specified
- string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
+ string culture = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (culture == null)
{
// get default language for site
@@ -613,8 +613,8 @@
};
Context.Response.Cookies.Append(
- CookieRequestCultureProvider.DefaultCookieName,
- CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
+ Shared.CookieRequestCultureProvider.DefaultCookieName,
+ Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(culture)),
cookieOptions
);
}
diff --git a/Oqtane.Shared/Models/RequestCulture.cs b/Oqtane.Shared/Models/RequestCulture.cs
new file mode 100644
index 00000000..12eaaa7e
--- /dev/null
+++ b/Oqtane.Shared/Models/RequestCulture.cs
@@ -0,0 +1,67 @@
+using System.Globalization;
+using System;
+
+namespace Oqtane.Models
+{
+ ///
+ /// Culture information describing a Culture
+ ///
+ public class RequestCulture
+ {
+ ///
+ /// Creates a new object with its and
+ /// properties set to the same value.
+ ///
+ /// The for the request.
+ public RequestCulture(CultureInfo culture)
+ : this(culture, culture)
+ {
+ }
+
+ ///
+ /// Creates a new object with its and
+ /// properties set to the same value.
+ ///
+ /// The culture for the request.
+ public RequestCulture(string culture)
+ : this(culture, culture)
+ {
+ }
+
+ ///
+ /// Creates a new object with its and
+ /// properties set to the respective values provided.
+ ///
+ /// The culture for the request to be used for formatting.
+ /// The culture for the request to be used for text, i.e. language.
+ public RequestCulture(string culture, string uiCulture)
+ : this(new CultureInfo(culture), new CultureInfo(uiCulture))
+ {
+ }
+
+ ///
+ /// Creates a new object with its and
+ /// properties set to the respective values provided.
+ ///
+ /// The for the request to be used for formatting.
+ /// The for the request to be used for text, i.e. language.
+ public RequestCulture(CultureInfo culture, CultureInfo uiCulture)
+ {
+ ArgumentNullException.ThrowIfNull(culture);
+ ArgumentNullException.ThrowIfNull(uiCulture);
+
+ Culture = culture;
+ UICulture = uiCulture;
+ }
+
+ ///
+ /// Gets the for the request to be used for formatting.
+ ///
+ public CultureInfo Culture { get; }
+
+ ///
+ /// Gets the for the request to be used for text, i.e. language;
+ ///
+ public CultureInfo UICulture { get; }
+ }
+}
diff --git a/Oqtane.Shared/Shared/CookieRequestCultureProvider.cs b/Oqtane.Shared/Shared/CookieRequestCultureProvider.cs
new file mode 100644
index 00000000..040d43b3
--- /dev/null
+++ b/Oqtane.Shared/Shared/CookieRequestCultureProvider.cs
@@ -0,0 +1,89 @@
+using System;
+using Oqtane.Models;
+
+namespace Oqtane.Shared
+{
+ public class CookieRequestCultureProvider
+ {
+ private const char _cookieSeparator = '|';
+ private const string _culturePrefix = "c=";
+ private const string _uiCulturePrefix = "uic=";
+
+ ///
+ /// Represent the default cookie name used to track the user's preferred culture information, which is ".AspNetCore.Culture".
+ ///
+ public static readonly string DefaultCookieName = ".AspNetCore.Culture";
+
+ ///
+ /// The name of the cookie that contains the user's preferred culture information.
+ /// Defaults to .
+ ///
+ public string CookieName { get; set; } = DefaultCookieName;
+
+ ///
+ /// Creates a string representation of a for placement in a cookie.
+ ///
+ /// The .
+ /// The cookie value.
+ public static string MakeCookieValue(RequestCulture requestCulture)
+ {
+ ArgumentNullException.ThrowIfNull(requestCulture);
+
+ return string.Join(_cookieSeparator,
+ $"{_culturePrefix}{requestCulture.Culture.Name}",
+ $"{_uiCulturePrefix}{requestCulture.UICulture.Name}");
+ }
+
+ ///
+ /// Parses a from the specified cookie value.
+ /// Returns null if parsing fails.
+ ///
+ /// The cookie value to parse.
+ /// The or null if parsing fails.
+ public static RequestCulture ParseCookieValue(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ Span parts = stackalloc Range[3];
+ var valueSpan = value.AsSpan();
+ if (valueSpan.Split(parts, _cookieSeparator, StringSplitOptions.RemoveEmptyEntries) != 2)
+ {
+ return null;
+ }
+
+ var potentialCultureName = valueSpan[parts[0]];
+ var potentialUICultureName = valueSpan[parts[1]];
+
+ if (!potentialCultureName.StartsWith(_culturePrefix, StringComparison.Ordinal) || !
+ potentialUICultureName.StartsWith(_uiCulturePrefix, StringComparison.Ordinal))
+ {
+ return null;
+ }
+
+ var cultureName = potentialCultureName.Slice(_culturePrefix.Length);
+ var uiCultureName = potentialUICultureName.Slice(_uiCulturePrefix.Length);
+
+ if (cultureName.IsEmpty && uiCultureName.IsEmpty)
+ {
+ // No values specified for either so no match
+ return null;
+ }
+
+ if (!cultureName.IsEmpty && uiCultureName.IsEmpty)
+ {
+ // Value for culture but not for UI culture so default to culture value for both
+ uiCultureName = cultureName;
+ }
+ else if (cultureName.IsEmpty && !uiCultureName.IsEmpty)
+ {
+ // Value for UI culture but not for culture so default to UI culture value for both
+ cultureName = uiCultureName;
+ }
+
+ return new RequestCulture(cultureName.ToString(), uiCultureName.ToString());
+ }
+ }
+}