using System.Globalization; using System.Linq; namespace QRRapidoApp.Middleware { public class LanguageRedirectionMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly string[] _supportedCultures = { "pt-BR", "es-PY" }; private readonly Dictionary _cultureAliases = new(StringComparer.OrdinalIgnoreCase) { { "pt", "pt-BR" }, { "pt-br", "pt-BR" }, { "es", "es-PY" } }; private const string DefaultCulture = "pt-BR"; public LanguageRedirectionMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var path = context.Request.Path.Value?.TrimStart('/') ?? ""; if (TryHandleCultureAlias(context, path)) { return; } if (HasCultureInPath(path) || IsSpecialRoute(path)) { await _next(context); return; } var detectedCulture = DetectBrowserLanguage(context); // If the detected culture is the default, do not redirect. if (detectedCulture == DefaultCulture) { await _next(context); return; } var redirectUrl = $"/{detectedCulture}"; if (!string.IsNullOrEmpty(path)) { redirectUrl += $"/{path}"; } if (context.Request.QueryString.HasValue) { redirectUrl += context.Request.QueryString.Value; } _logger.LogInformation("Redirecting to localized URL: {RedirectUrl} (detected culture: {Culture})", redirectUrl, detectedCulture); context.Response.Redirect(redirectUrl, permanent: false); } private bool HasCultureInPath(string path) { if (string.IsNullOrEmpty(path)) return false; var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); if (segments.Length == 0) return false; return _supportedCultures.Contains(segments[0]); } private bool TryHandleCultureAlias(HttpContext context, string path) { if (string.IsNullOrEmpty(path)) { return false; } var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); if (segments.Length == 0) { return false; } var firstSegment = segments[0]; if (_cultureAliases.TryGetValue(firstSegment, out var mappedCulture)) { var remainingSegments = segments.Length > 1 ? "/" + string.Join('/', segments.Skip(1)) : string.Empty; var redirectUrl = $"/{mappedCulture}{remainingSegments}"; if (context.Request.QueryString.HasValue) { redirectUrl += context.Request.QueryString.Value; } _logger.LogInformation("Redirecting alias '{Alias}' to canonical culture URL: {RedirectUrl}", firstSegment, redirectUrl); context.Response.Redirect(redirectUrl, permanent: true); return true; } return false; } private bool IsSpecialRoute(string path) { var specialRoutes = new[] { "api/", "health", "_framework/", "lib/", "css/", "js/", "images/", "favicon.ico", "robots.txt", "sitemap.xml", "signin-microsoft", "signin-google", "signout-callback-oidc", "Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout", "Pagamento/StripeWebhook", "api/QR", "Home/Error", "ads.txt" }; return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase)); } private string DetectBrowserLanguage(HttpContext context) { var acceptLanguage = context.Request.GetTypedHeaders().AcceptLanguage; if (acceptLanguage != null && acceptLanguage.Any()) { foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0)) { var langCode = lang.Value.Value; // Exact match for es-PY if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase)) { return "es-PY"; } // Generic 'es' maps to 'es-PY' if (langCode.StartsWith("es-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "es", StringComparison.OrdinalIgnoreCase)) { return "es-PY"; } // Check for pt-BR if (langCode.StartsWith("pt-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "pt", StringComparison.OrdinalIgnoreCase)) { return "pt-BR"; } } } return DefaultCulture; } } }