diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
index 2b8294e8db..7e66a05b2e 100644
--- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs
+++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
@@ -7,26 +7,56 @@ using System.Security.Principal;
namespace Umbraco.Extensions;
+///
+/// Extension methods for .
+///
public static class AuthenticationExtensions
{
///
- /// Ensures that the thread culture is set based on the back office user's culture
+ /// Ensures that the thread culture is set based on the back office user's culture.
///
+ /// The identity.
public static void EnsureCulture(this IIdentity identity)
{
CultureInfo? culture = GetCulture(identity);
- if (!(culture is null))
+ if (culture is not null)
{
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
}
}
+ ///
+ /// Gets the culture string from the back office user.
+ ///
+ /// The identity.
+ ///
+ /// The culture string.
+ ///
+ public static string? GetCultureString(this IIdentity identity)
+ {
+ if (identity is ClaimsIdentity umbIdentity &&
+ umbIdentity.VerifyBackOfficeIdentity(out _) &&
+ umbIdentity.IsAuthenticated)
+ {
+ return umbIdentity.GetCultureString();
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the culture from the back office user.
+ ///
+ /// The identity.
+ ///
+ /// The culture.
+ ///
public static CultureInfo? GetCulture(this IIdentity identity)
{
- if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) &&
- umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null)
+ string? culture = identity.GetCultureString();
+ if (!string.IsNullOrEmpty(culture))
{
- return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!);
+ return CultureInfo.GetCultureInfo(culture);
}
return null;
diff --git a/src/Umbraco.Web.Common/Localization/DynamicRequestCultureProviderBase.cs b/src/Umbraco.Web.Common/Localization/DynamicRequestCultureProviderBase.cs
new file mode 100644
index 0000000000..84bf1531b7
--- /dev/null
+++ b/src/Umbraco.Web.Common/Localization/DynamicRequestCultureProviderBase.cs
@@ -0,0 +1,86 @@
+using System.Globalization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Localization;
+using Microsoft.Extensions.Primitives;
+
+namespace Umbraco.Cms.Web.Common.Localization;
+
+///
+/// Base implementation that dynamically adds the determined cultures to the supported cultures.
+///
+public abstract class DynamicRequestCultureProviderBase : RequestCultureProvider
+{
+ private readonly RequestLocalizationOptions _options;
+ private readonly object _lockerSupportedCultures = new();
+ private readonly object _lockerSupportedUICultures = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The request localization options.
+ protected DynamicRequestCultureProviderBase(RequestLocalizationOptions requestLocalizationOptions)
+ => _options = Options = requestLocalizationOptions;
+
+ ///
+ public override Task DetermineProviderCultureResult(HttpContext httpContext)
+ {
+ ProviderCultureResult? result = GetProviderCultureResult(httpContext);
+ if (result is not null)
+ {
+ // We need to dynamically change the supported cultures since we won't ever know what languages are used since
+ // they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
+ // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo
+ // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165
+ TryAddLocked(_options.SupportedCultures, result.Cultures, _lockerSupportedCultures, (culture) =>
+ {
+ _options.SupportedCultures ??= new List();
+ _options.SupportedCultures.Add(CultureInfo.GetCultureInfo(culture.ToString()));
+ });
+
+ TryAddLocked(_options.SupportedUICultures, result.UICultures, _lockerSupportedUICultures, (culture) =>
+ {
+ _options.SupportedUICultures ??= new List();
+ _options.SupportedUICultures.Add(CultureInfo.GetCultureInfo(culture.ToString()));
+ });
+
+ return Task.FromResult(result);
+ }
+
+ return NullProviderCultureResult;
+ }
+
+ ///
+ /// Gets the provider culture result.
+ ///
+ /// The HTTP context.
+ ///
+ /// The provider culture result.
+ ///
+ protected abstract ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext);
+
+ ///
+ /// Executes the within a double-checked lock when the a culture in does not exist in .
+ ///
+ /// The supported cultures.
+ /// The cultures.
+ /// The locker object to use.
+ /// The add action to execute.
+ private static void TryAddLocked(IEnumerable? supportedCultures, IEnumerable cultures, object locker, Action addAction)
+ {
+ foreach (StringSegment culture in cultures)
+ {
+ Func predicate = x => culture.Equals(x.Name, StringComparison.OrdinalIgnoreCase);
+ if (supportedCultures?.Any(predicate) is not true)
+ {
+ lock (locker)
+ {
+ if (supportedCultures?.Any(predicate) is not true)
+ {
+ addAction(culture);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs
index 3e84774c7b..95b5a8d13e 100644
--- a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs
+++ b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs
@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
@@ -10,50 +9,21 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Web.Common.Localization;
///
-/// Sets the request culture to the culture of the back office user if one is determined to be in the request
+/// Sets the request culture to the culture of the back office user, if one is determined to be in the request.
///
-public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider
+public class UmbracoBackOfficeIdentityCultureProvider : DynamicRequestCultureProviderBase
{
- private readonly RequestLocalizationOptions _localizationOptions;
- private readonly object _locker = new();
-
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public UmbracoBackOfficeIdentityCultureProvider(RequestLocalizationOptions localizationOptions) =>
- _localizationOptions = localizationOptions;
+ /// The localization options.
+ public UmbracoBackOfficeIdentityCultureProvider(RequestLocalizationOptions localizationOptions)
+ : base(localizationOptions)
+ { }
///
- public override Task DetermineProviderCultureResult(HttpContext httpContext)
- {
- CultureInfo? culture = httpContext.User.Identity?.GetCulture();
-
- if (culture is null)
- {
- return NullProviderCultureResult;
- }
-
- lock (_locker)
- {
- // We need to dynamically change the supported cultures since we won't ever know what languages are used since
- // they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
- var cultureExists = _localizationOptions.SupportedCultures?.Contains(culture) ?? false;
-
- if (!cultureExists)
- {
- // add this as a supporting culture
- _localizationOptions.SupportedCultures?.Add(culture);
- }
-
- var uiCultureExists = _localizationOptions.SupportedCultures?.Contains(culture) ?? false;
-
- if (!uiCultureExists)
- {
- // add this as a supporting culture
- _localizationOptions.SupportedUICultures?.Add(culture);
- }
-
- return Task.FromResult(new ProviderCultureResult(culture.Name));
- }
- }
+ protected sealed override ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext)
+ => httpContext.User.Identity?.GetCultureString() is string culture
+ ? new ProviderCultureResult(culture)
+ : null;
}
diff --git a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs
index a3252c66e6..c2ad4805c0 100644
--- a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs
+++ b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs
@@ -1,69 +1,27 @@
-using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
-using Microsoft.Extensions.Primitives;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common.Routing;
namespace Umbraco.Cms.Web.Common.Localization;
///
-/// Sets the request culture to the culture of the if one is found in the request
+/// Sets the request culture to the culture of the , if one is found in the request.
///
-public class UmbracoPublishedContentCultureProvider : RequestCultureProvider
+public class UmbracoPublishedContentCultureProvider : DynamicRequestCultureProviderBase
{
- private readonly RequestLocalizationOptions _localizationOptions;
- private readonly object _locker = new();
-
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public UmbracoPublishedContentCultureProvider(RequestLocalizationOptions localizationOptions) =>
- _localizationOptions = localizationOptions;
+ /// The localization options.
+ public UmbracoPublishedContentCultureProvider(RequestLocalizationOptions localizationOptions)
+ : base(localizationOptions)
+ { }
///
- public override Task DetermineProviderCultureResult(HttpContext httpContext)
- {
- UmbracoRouteValues? routeValues = httpContext.Features.Get();
- if (routeValues != null)
- {
- var culture = routeValues.PublishedRequest.Culture;
- if (culture != null)
- {
- lock (_locker)
- {
- // We need to dynamically change the supported cultures since we won't ever know what languages are used since
- // they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
- // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo
- // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165
- CultureInfo? existingCulture = _localizationOptions.SupportedCultures?.FirstOrDefault(
- supportedCulture =>
- StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase));
-
- if (existingCulture == null)
- {
- // add this as a supporting culture
- var ci = CultureInfo.GetCultureInfo(culture);
- _localizationOptions.SupportedCultures?.Add(ci);
- }
-
- CultureInfo? existingUICulture = _localizationOptions.SupportedUICultures?.FirstOrDefault(
- supportedCulture =>
- StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase));
-
- if (existingUICulture == null)
- {
- // add this as a supporting culture
- var ci = CultureInfo.GetCultureInfo(culture);
- _localizationOptions.SupportedUICultures?.Add(ci);
- }
- }
-
- return Task.FromResult(new ProviderCultureResult(culture));
- }
- }
-
- return NullProviderCultureResult;
- }
+ protected sealed override ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext)
+ => httpContext.Features.Get()?.PublishedRequest.Culture is string culture
+ ? new ProviderCultureResult(culture)
+ : null;
}
diff --git a/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs
index 802a68607d..59c11dc986 100644
--- a/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs
+++ b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs
@@ -1,28 +1,28 @@
using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Web.Common.Localization;
///
-/// Custom Umbraco options configuration for
+/// Custom Umbraco options configuration for .
///
public class UmbracoRequestLocalizationOptions : IConfigureOptions
{
private readonly GlobalSettings _globalSettings;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public UmbracoRequestLocalizationOptions(IOptions globalSettings) =>
- _globalSettings = globalSettings.Value;
+ /// The global settings.
+ public UmbracoRequestLocalizationOptions(IOptions globalSettings)
+ => _globalSettings = globalSettings.Value;
///
public void Configure(RequestLocalizationOptions options)
{
- // set the default culture to what is in config
- options.DefaultRequestCulture = new RequestCulture(_globalSettings.DefaultUILanguage);
+ // Set the default culture to what is in config
+ options.SetDefaultCulture(_globalSettings.DefaultUILanguage);
options.RequestCultureProviders.Insert(0, new UmbracoBackOfficeIdentityCultureProvider(options));
options.RequestCultureProviders.Insert(1, new UmbracoPublishedContentCultureProvider(options));