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));
}
}
}