using System.Globalization; using System.Linq; namespace QRRapidoApp.Middleware { /// /// Middleware de redirecionamento de idioma otimizado para SEO. /// /// Comportamento: /// - "/" → Retorna 200 OK em Português (canonical) /// - "/pt-BR" ou "/pt-BR/*" → Redireciona 301 para "/" ou "/*" (sem prefixo) /// - "/es-PY" ou "/es-PY/*" → Retorna 200 OK em Espanhol (mantém URL) /// - "/pix", "/wifi", etc. → Retorna 200 OK em Português (sem redirect) /// public class LanguageRedirectionMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly string[] _supportedCultures = { "pt-BR", "es-PY" }; private const string DefaultCulture = "pt-BR"; private const string CookieName = ".AspNetCore.Culture"; public LanguageRedirectionMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var path = context.Request.Path.Value?.TrimStart('/') ?? ""; // Skip special routes (static files, API, auth callbacks, etc.) if (IsSpecialRoute(path)) { await _next(context); return; } var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); var firstSegment = segments.Length > 0 ? segments[0] : ""; // Check if URL starts with a culture prefix if (IsCultureSegment(firstSegment)) { // /pt-BR/* → Redirect 301 to /* (remove pt-BR prefix, it's the default) if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase)) { var remainingPath = segments.Length > 1 ? "/" + string.Join('/', segments.Skip(1)) : "/"; if (context.Request.QueryString.HasValue) { remainingPath += context.Request.QueryString.Value; } _logger.LogInformation("Redirecting /pt-BR{Path} to {RedirectUrl} (canonical)", remainingPath == "/" ? "" : remainingPath, remainingPath); context.Response.Redirect(remainingPath, permanent: true); return; } // /es-PY/* → Continue normally (Spanish has its own URLs) if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase)) { // Set culture for the request SetCulture(context, "es-PY"); await _next(context); return; } // Handle lowercase aliases: /pt-br → /pt-BR, /es-py → /es-PY if (TryHandleCultureAlias(context, firstSegment, segments)) { return; } } // No culture prefix → Serve in Portuguese (default) // URLs like /, /pix, /wifi, /vcard etc. SetCulture(context, DefaultCulture); await _next(context); } private bool IsCultureSegment(string segment) { if (string.IsNullOrEmpty(segment)) return false; // Check exact matches and aliases return string.Equals(segment, "pt-BR", StringComparison.OrdinalIgnoreCase) || string.Equals(segment, "es-PY", StringComparison.OrdinalIgnoreCase) || string.Equals(segment, "pt", StringComparison.OrdinalIgnoreCase) || string.Equals(segment, "es", StringComparison.OrdinalIgnoreCase); } private bool TryHandleCultureAlias(HttpContext context, string firstSegment, string[] segments) { string? targetCulture = null; string? targetPrefix = null; // Map aliases to canonical forms if (string.Equals(firstSegment, "pt", StringComparison.OrdinalIgnoreCase) || (string.Equals(firstSegment, "pt-br", StringComparison.Ordinal) && firstSegment != "pt-BR")) { // /pt/* or /pt-br/* → Redirect to /* (Portuguese is canonical without prefix) targetCulture = "pt-BR"; targetPrefix = ""; // No prefix for Portuguese } else if (string.Equals(firstSegment, "es", StringComparison.OrdinalIgnoreCase) || (string.Equals(firstSegment, "es-py", StringComparison.OrdinalIgnoreCase) && firstSegment != "es-PY")) { // /es/* or /es-py/* → Redirect to /es-PY/* targetCulture = "es-PY"; targetPrefix = "/es-PY"; } if (targetCulture != null) { var remainingPath = segments.Length > 1 ? "/" + string.Join('/', segments.Skip(1)) : ""; var redirectUrl = targetPrefix + remainingPath; if (string.IsNullOrEmpty(redirectUrl)) redirectUrl = "/"; if (context.Request.QueryString.HasValue) { redirectUrl += context.Request.QueryString.Value; } _logger.LogInformation("Redirecting alias '{Alias}' to canonical URL: {RedirectUrl}", firstSegment, redirectUrl); context.Response.Redirect(redirectUrl, permanent: true); return true; } return false; } private void SetCulture(HttpContext context, string culture) { var cultureInfo = new CultureInfo(culture); CultureInfo.CurrentCulture = cultureInfo; CultureInfo.CurrentUICulture = cultureInfo; // Store in HttpContext for downstream use context.Items["Culture"] = culture; } 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)); } } }