147 lines
5.5 KiB
C#
147 lines
5.5 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 segment (supported or unsupported)
|
|
if (IsCultureLikeSegment(firstSegment))
|
|
{
|
|
// Supported: pt-BR (Default/Canonical)
|
|
if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return RedirectToCanonical(context, segments);
|
|
}
|
|
|
|
// Supported: es-PY
|
|
if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
SetCulture(context, "es-PY");
|
|
await _next(context);
|
|
return;
|
|
}
|
|
|
|
// Handle aliases or unsupported cultures (e.g. /en/, /pt/, /es/)
|
|
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", StringComparison.OrdinalIgnoreCase) ||
|
|
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));
|
|
}
|
|
}
|
|
}
|