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 Lock _lockerSupportedCultures = new(); private readonly Lock _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, Lock 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); } } } } } }