168 lines
6.6 KiB
C#
168 lines
6.6 KiB
C#
using System.Globalization;
|
|
using System.Linq;
|
|
|
|
namespace QRRapidoApp.Middleware
|
|
{
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
public class LanguageRedirectionMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
private readonly ILogger<LanguageRedirectionMiddleware> _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<LanguageRedirectionMiddleware> 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));
|
|
}
|
|
}
|
|
}
|