// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Localization { /// /// Enables automatic setting of the culture for s based on information /// sent by the client in headers and logic provided by the application. /// public class RequestLocalizationMiddleware { private static readonly int MaxCultureFallbackDepth = 5; private readonly RequestDelegate _next; private readonly RequestLocalizationOptions _options; /// /// Creates a new . /// /// The representing the next middleware in the pipeline. /// The representing the options for the /// . public RequestLocalizationMiddleware(RequestDelegate next, IOptions options) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next; _options = options.Value; } /// /// Invokes the logic of the middleware. /// /// The . /// A that completes when the middleware has completed processing. public async Task Invoke(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var requestCulture = _options.DefaultRequestCulture; IRequestCultureProvider winningProvider = null; if (_options.RequestCultureProviders != null) { foreach (var provider in _options.RequestCultureProviders) { var providerResultCulture = await provider.DetermineProviderCultureResult(context); if (providerResultCulture != null) { var cultures = providerResultCulture.Cultures; var uiCultures = providerResultCulture.UICultures; CultureInfo cultureInfo = null; CultureInfo uiCultureInfo = null; if (_options.SupportedCultures != null) { cultureInfo = GetCultureInfo( cultures, _options.SupportedCultures, _options.FallBackToParentCultures); } if (_options.SupportedUICultures != null) { uiCultureInfo = GetCultureInfo( uiCultures, _options.SupportedUICultures, _options.FallBackToParentUICultures); } if (cultureInfo == null && uiCultureInfo == null) { continue; } if (cultureInfo == null && uiCultureInfo != null) { cultureInfo = _options.DefaultRequestCulture.Culture; } if (cultureInfo != null && uiCultureInfo == null) { uiCultureInfo = _options.DefaultRequestCulture.UICulture; } var result = new RequestCulture(cultureInfo, uiCultureInfo); if (result != null) { requestCulture = result; winningProvider = provider; break; } } } } context.Features.Set(new RequestCultureFeature(requestCulture, winningProvider)); SetCurrentThreadCulture(requestCulture); await _next(context); } private static void SetCurrentThreadCulture(RequestCulture requestCulture) { CultureInfo.CurrentCulture = requestCulture.Culture; CultureInfo.CurrentUICulture = requestCulture.UICulture; } private static CultureInfo GetCultureInfo( IList cultureNames, IList supportedCultures, bool fallbackToParentCultures) { foreach (var cultureName in cultureNames) { // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in // the CultureInfo ctor if (cultureName != null) { var cultureInfo = GetCultureInfo(cultureName, supportedCultures, fallbackToParentCultures, currentDepth: 0); if (cultureInfo != null) { return cultureInfo; } } } return null; } private static CultureInfo GetCultureInfo(string name, IList supportedCultures) { // Allow only known culture names as this API is called with input from users (HTTP requests) and // creating CultureInfo objects is expensive and we don't want it to throw either. if (name == null || supportedCultures == null) { return null; } var culture = supportedCultures.FirstOrDefault( supportedCulture => string.Equals(supportedCulture.Name, name, StringComparison.OrdinalIgnoreCase)); if (culture == null) { return null; } return CultureInfo.ReadOnly(culture); } private static CultureInfo GetCultureInfo( string cultureName, IList supportedCultures, bool fallbackToParentCultures, int currentDepth) { var culture = GetCultureInfo(cultureName, supportedCultures); if (culture == null && fallbackToParentCultures && currentDepth < MaxCultureFallbackDepth) { var lastIndexOfHyphen = cultureName.LastIndexOf('-'); if (lastIndexOfHyphen > 0) { // Trim the trailing section from the culture name, e.g. "fr-FR" becomes "fr" var parentCultureName = cultureName.Substring(0, lastIndexOfHyphen); culture = GetCultureInfo(parentCultureName, supportedCultures, fallbackToParentCultures, currentDepth + 1); } } return culture; } } }