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", "es" };
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 pathString = context.Request.Path.Value ?? "";
// Skip API routes immediately using the raw path
if (pathString.StartsWith("/api", StringComparison.OrdinalIgnoreCase))
{
await _next(context);
return;
}
// Skip redirection for non-GET requests (POST/PUT/PATCH/DELETE).
// A 301 redirect converts POST to GET, breaking form submissions.
// Language canonicalization only matters for crawlers (GET).
if (!HttpMethods.IsGet(context.Request.Method) && !HttpMethods.IsHead(context.Request.Method))
{
await _next(context);
return;
}
var path = pathString.TrimStart('/') ?? "";
// Skip other special routes
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 segment (supported or unsupported)
if (IsCultureLikeSegment(firstSegment))
{
// Supported: pt-BR (Default/Canonical)
if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase))
{
RedirectToCanonical(context, segments);
return;
}
// Supported: es-PY
if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase))
{
SetCulture(context, "es-PY");
await _next(context);
return;
}
// Supported: es (Spanish neutral)
if (string.Equals(firstSegment, "es", StringComparison.OrdinalIgnoreCase))
{
SetCulture(context, "es");
await _next(context);
return;
}
// Handle aliases or unsupported cultures (e.g. /en/, /pt/)
if (TryHandleCultureAliasOrUnknown(context, firstSegment, segments))
{
return;
}
}
// No culture prefix or unknown segment -> Serve in Portuguese (default)
SetCulture(context, DefaultCulture);
await _next(context);
}
private bool IsCultureLikeSegment(string segment)
{
if (string.IsNullOrEmpty(segment)) return false;
// Matches xx (like en, pt, es) or xx-XX (like pt-BR, en-US)
return segment.Length == 2 || (segment.Length == 5 && segment[2] == '-');
}
private bool TryHandleCultureAliasOrUnknown(HttpContext context, string firstSegment, string[] segments)
{
// Map known aliases to canonical forms
if (string.Equals(firstSegment, "es-py", StringComparison.OrdinalIgnoreCase))
{
return RedirectToLanguage(context, "es-PY", segments);
}
// For anything else that looks like a culture (en, pt, fr, etc.)
// but isn't explicitly es-PY, we redirect to the canonical (no prefix)
// This prevents 404s for /en/, /pt-BR/ (redirects to /), etc.
return RedirectToCanonical(context, segments);
}
private bool RedirectToLanguage(HttpContext context, string culture, string[] segments)
{
var remainingPath = segments.Length > 1
? "/" + string.Join('/', segments.Skip(1))
: "";
var redirectUrl = "/" + culture + remainingPath;
return ExecuteRedirect(context, redirectUrl);
}
private bool RedirectToCanonical(HttpContext context, string[] segments)
{
var remainingPath = segments.Length > 1
? "/" + string.Join('/', segments.Skip(1))
: "/";
return ExecuteRedirect(context, remainingPath);
}
private bool ExecuteRedirect(HttpContext context, string url)
{
if (context.Request.QueryString.HasValue)
{
url += context.Request.QueryString.Value;
}
_logger.LogInformation("SEO Redirect: {Source} -> {Dest}", context.Request.Path, url);
context.Response.Redirect(url, permanent: true);
return true;
}
private void SetCulture(HttpContext context, string culture)
{
var cultureInfo = new CultureInfo(culture);
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
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", "ads.txt",
"signin-", "signout-", "Account/", "Pagamento/", "Home/Error"
};
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
}
}
}