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