fix: PIX + idioma espanhol e SEO
This commit is contained in:
parent
bdf78ed418
commit
162e28ae5a
BIN
AoSelecionarES.png
Normal file
BIN
AoSelecionarES.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
@ -33,8 +33,12 @@ namespace QRRapidoApp.Controllers
|
||||
_markdownService = markdownService;
|
||||
}
|
||||
|
||||
// Default fallback route handled by Program.cs map
|
||||
// Home page routes
|
||||
// "/" → Portuguese (canonical)
|
||||
// "/es-PY" → Spanish
|
||||
[HttpGet]
|
||||
[Route("/")]
|
||||
[Route("es-PY")]
|
||||
public async Task<IActionResult> Index(string? qrType = null)
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
@ -106,37 +110,42 @@ namespace QRRapidoApp.Controllers
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
// Dedicated SEO Routes - These act as virtual pages
|
||||
// Dedicated SEO Routes - Landing pages for each QR type
|
||||
// Portuguese (default): /pix, /wifi, etc. (no culture prefix)
|
||||
// Spanish: /es-PY/pix, /es-PY/wifi, etc.
|
||||
|
||||
[Route("pix")]
|
||||
[Route("{culture}/pix")]
|
||||
[Route("es-PY/pix")]
|
||||
public async Task<IActionResult> Pix() => await Index("pix");
|
||||
|
||||
[Route("wifi")]
|
||||
[Route("{culture}/wifi")]
|
||||
[Route("es-PY/wifi")]
|
||||
public async Task<IActionResult> Wifi() => await Index("wifi");
|
||||
|
||||
[Route("vcard")]
|
||||
[Route("{culture}/vcard")]
|
||||
[Route("es-PY/vcard")]
|
||||
public async Task<IActionResult> VCard() => await Index("vcard");
|
||||
|
||||
[Route("whatsapp")]
|
||||
[Route("{culture}/whatsapp")]
|
||||
[Route("es-PY/whatsapp")]
|
||||
public async Task<IActionResult> WhatsApp() => await Index("whatsapp");
|
||||
|
||||
[Route("email")]
|
||||
[Route("{culture}/email")]
|
||||
[Route("es-PY/email")]
|
||||
public async Task<IActionResult> Email() => await Index("email");
|
||||
|
||||
[Route("sms")]
|
||||
[Route("{culture}/sms")]
|
||||
[Route("es-PY/sms")]
|
||||
public async Task<IActionResult> Sms() => await Index("sms");
|
||||
|
||||
[Route("texto")]
|
||||
[Route("text")]
|
||||
[Route("{culture}/text")]
|
||||
[Route("es-PY/texto")]
|
||||
[Route("es-PY/text")]
|
||||
public async Task<IActionResult> Text() => await Index("text");
|
||||
|
||||
[Route("url")]
|
||||
[Route("{culture}/url")]
|
||||
[Route("es-PY/url")]
|
||||
public async Task<IActionResult> UrlType() => await Index("url");
|
||||
|
||||
public IActionResult Privacy()
|
||||
@ -315,43 +324,46 @@ namespace QRRapidoApp.Controllers
|
||||
}
|
||||
|
||||
// Core entry points
|
||||
// "/" is canonical for Portuguese, "/es-PY" for Spanish
|
||||
AppendUrl("/", "daily", "1.0");
|
||||
AppendUrl("/pt-BR", "daily", "0.9");
|
||||
AppendUrl("/es-PY", "daily", "0.9");
|
||||
|
||||
// Tools (Virtual Pages)
|
||||
var tools = new[] { "pix", "wifi", "vcard", "whatsapp", "email", "sms", "text", "url" };
|
||||
var cultures = new[] { "pt-BR", "es-PY" };
|
||||
// Tools (Landing Pages) - Portuguese without prefix, Spanish with /es-PY
|
||||
var tools = new[] { "pix", "wifi", "vcard", "whatsapp", "email", "sms", "texto", "url" };
|
||||
|
||||
foreach (var culture in cultures)
|
||||
// Portuguese tools (canonical, no prefix)
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
AppendUrl($"/{culture}/{tool}", "weekly", "0.9");
|
||||
}
|
||||
AppendUrl($"/{tool}", "weekly", "0.9");
|
||||
}
|
||||
|
||||
// Spanish tools (with /es-PY prefix)
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
AppendUrl($"/es-PY/{tool}", "weekly", "0.8");
|
||||
}
|
||||
|
||||
// Informational pages
|
||||
var informationalPages = new[]
|
||||
{
|
||||
new { Path = "Home/About", ChangeFreq = "monthly", Priority = "0.8" },
|
||||
new { Path = "Home/Contact", ChangeFreq = "monthly", Priority = "0.8" },
|
||||
new { Path = "Home/FAQ", ChangeFreq = "weekly", Priority = "0.9" },
|
||||
new { Path = "Home/About", ChangeFreq = "monthly", Priority = "0.7" },
|
||||
new { Path = "Home/Contact", ChangeFreq = "monthly", Priority = "0.7" },
|
||||
new { Path = "Home/FAQ", ChangeFreq = "weekly", Priority = "0.8" },
|
||||
new { Path = "Home/HowToUse", ChangeFreq = "weekly", Priority = "0.8" },
|
||||
new { Path = "Home/Privacy", ChangeFreq = "monthly", Priority = "0.5" },
|
||||
new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.5" }
|
||||
new { Path = "Home/Privacy", ChangeFreq = "monthly", Priority = "0.4" },
|
||||
new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.4" }
|
||||
};
|
||||
|
||||
// Portuguese informational pages (no prefix)
|
||||
foreach (var page in informationalPages)
|
||||
{
|
||||
AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority);
|
||||
}
|
||||
|
||||
foreach (var culture in cultures)
|
||||
// Spanish informational pages
|
||||
foreach (var page in informationalPages)
|
||||
{
|
||||
foreach (var page in informationalPages)
|
||||
{
|
||||
AppendUrl($"/{culture}/{page.Path}", page.ChangeFreq, page.Priority);
|
||||
}
|
||||
AppendUrl($"/es-PY/{page.Path}", page.ChangeFreq, page.Priority);
|
||||
}
|
||||
|
||||
// Dynamic tutorial pages
|
||||
@ -368,7 +380,12 @@ namespace QRRapidoApp.Controllers
|
||||
var encodedSlug = System.Uri.EscapeDataString(slug);
|
||||
var lastMod = article.LastMod.ToString("yyyy-MM-dd");
|
||||
|
||||
AppendUrl($"/{article.Culture}/tutoriais/{encodedSlug}", "weekly", "0.8", lastMod);
|
||||
// pt-BR tutorials go under /tutoriais/, es-PY under /es-PY/tutoriais/
|
||||
var tutorialPath = article.Culture == "pt-BR"
|
||||
? $"/tutoriais/{encodedSlug}"
|
||||
: $"/{article.Culture}/tutoriais/{encodedSlug}";
|
||||
|
||||
AppendUrl(tutorialPath, "weekly", "0.8", lastMod);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count);
|
||||
|
||||
@ -27,11 +27,19 @@ namespace QRRapidoApp.Controllers
|
||||
_config = config;
|
||||
}
|
||||
|
||||
[Route("{culture:regex(^(pt-BR|es-PY)$)}/tutoriais/{slug}")]
|
||||
public async Task<IActionResult> Article(string slug, string culture)
|
||||
// Portuguese: /tutoriais/{slug} (canonical, no prefix)
|
||||
// Spanish: /es-PY/tutoriais/{slug}
|
||||
[Route("tutoriais/{slug}")]
|
||||
[Route("es-PY/tutoriais/{slug}")]
|
||||
public async Task<IActionResult> Article(string slug, string? culture = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine culture from URL path if not provided
|
||||
culture ??= Request.Path.Value?.StartsWith("/es-PY", StringComparison.OrdinalIgnoreCase) == true
|
||||
? "es-PY"
|
||||
: "pt-BR";
|
||||
|
||||
var article = await _markdownService.GetArticleAsync(slug, culture);
|
||||
|
||||
if (article == null)
|
||||
@ -77,11 +85,19 @@ namespace QRRapidoApp.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[Route("{culture:regex(^(pt-BR|es-PY)$)}/tutoriais")]
|
||||
public async Task<IActionResult> Index(string culture)
|
||||
// Portuguese: /tutoriais (canonical, no prefix)
|
||||
// Spanish: /es-PY/tutoriais
|
||||
[Route("tutoriais")]
|
||||
[Route("es-PY/tutoriais")]
|
||||
public async Task<IActionResult> Index(string? culture = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine culture from URL path if not provided
|
||||
culture ??= Request.Path.Value?.StartsWith("/es-PY", StringComparison.OrdinalIgnoreCase) == true
|
||||
? "es-PY"
|
||||
: "pt-BR";
|
||||
|
||||
var articles = await _markdownService.GetAllArticlesAsync(culture);
|
||||
|
||||
// Set ViewBag
|
||||
|
||||
@ -3,19 +3,22 @@ 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 readonly Dictionary<string, string> _cultureAliases = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "pt", "pt-BR" },
|
||||
{ "pt-br", "pt-BR" },
|
||||
{ "es", "es-PY" },
|
||||
{ "es-py", "es-PY" }
|
||||
};
|
||||
private const string DefaultCulture = "pt-BR";
|
||||
private const string CookieName = ".AspNetCore.Culture";
|
||||
|
||||
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
|
||||
{
|
||||
@ -27,84 +30,109 @@ namespace QRRapidoApp.Middleware
|
||||
{
|
||||
var path = context.Request.Path.Value?.TrimStart('/') ?? "";
|
||||
|
||||
if (TryHandleCultureAlias(context, path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasCultureInPath(path) || IsSpecialRoute(path))
|
||||
// Skip special routes (static files, API, auth callbacks, etc.)
|
||||
if (IsSpecialRoute(path))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var detectedCulture = DetectBrowserLanguage(context);
|
||||
|
||||
// ALWAYS Redirect to include culture in path for consistency and SEO
|
||||
// This ensures /pix becomes /pt-BR/pix or /es-PY/pix
|
||||
|
||||
var redirectUrl = $"/{detectedCulture}";
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
redirectUrl += $"/{path}";
|
||||
}
|
||||
|
||||
if (context.Request.QueryString.HasValue)
|
||||
{
|
||||
redirectUrl += context.Request.QueryString.Value;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Redirecting to localized URL: {RedirectUrl} (detected culture: {Culture})",
|
||||
redirectUrl, detectedCulture);
|
||||
|
||||
context.Response.Redirect(redirectUrl, permanent: false);
|
||||
}
|
||||
|
||||
private bool HasCultureInPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return false;
|
||||
|
||||
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (segments.Length == 0)
|
||||
return false;
|
||||
var firstSegment = segments.Length > 0 ? segments[0] : "";
|
||||
|
||||
return _supportedCultures.Contains(segments[0]);
|
||||
}
|
||||
|
||||
private bool TryHandleCultureAlias(HttpContext context, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
// Check if URL starts with a culture prefix
|
||||
if (IsCultureSegment(firstSegment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (segments.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstSegment = segments[0];
|
||||
if (_cultureAliases.TryGetValue(firstSegment, out var mappedCulture))
|
||||
{
|
||||
// Don't redirect if already using the canonical culture (case-sensitive check)
|
||||
if (firstSegment == mappedCulture)
|
||||
// /pt-BR/* → Redirect 301 to /* (remove pt-BR prefix, it's the default)
|
||||
if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
var remainingSegments = segments.Length > 1
|
||||
? "/" + string.Join('/', segments.Skip(1))
|
||||
: string.Empty;
|
||||
// /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 = "/";
|
||||
|
||||
var redirectUrl = $"/{mappedCulture}{remainingSegments}";
|
||||
if (context.Request.QueryString.HasValue)
|
||||
{
|
||||
redirectUrl += context.Request.QueryString.Value;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Redirecting alias '{Alias}' to canonical culture URL: {RedirectUrl}", firstSegment, redirectUrl);
|
||||
_logger.LogInformation("Redirecting alias '{Alias}' to canonical URL: {RedirectUrl}",
|
||||
firstSegment, redirectUrl);
|
||||
|
||||
context.Response.Redirect(redirectUrl, permanent: true);
|
||||
return true;
|
||||
}
|
||||
@ -112,6 +140,16 @@ namespace QRRapidoApp.Middleware
|
||||
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[]
|
||||
@ -125,38 +163,5 @@ namespace QRRapidoApp.Middleware
|
||||
|
||||
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private string DetectBrowserLanguage(HttpContext context)
|
||||
{
|
||||
var acceptLanguage = context.Request.GetTypedHeaders().AcceptLanguage;
|
||||
|
||||
if (acceptLanguage != null && acceptLanguage.Any())
|
||||
{
|
||||
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
|
||||
{
|
||||
var langCode = lang.Value.Value;
|
||||
|
||||
// Exact match for es-PY
|
||||
if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "es-PY";
|
||||
}
|
||||
|
||||
// Generic 'es' maps to 'es-PY'
|
||||
if (langCode.StartsWith("es-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "es", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "es-PY";
|
||||
}
|
||||
|
||||
// Check for pt-BR
|
||||
if (langCode.StartsWith("pt-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "pt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "pt-BR";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultCulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,17 @@ namespace QRRapidoApp.Providers
|
||||
{
|
||||
public Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
|
||||
{
|
||||
// First check if the middleware has already determined the culture (e.g. for static routes like /es-PY/pix)
|
||||
if (httpContext.Items.TryGetValue("Culture", out var cultureItem) && cultureItem is string cultureFromMiddleware)
|
||||
{
|
||||
var supportedCultures = new[] { "pt-BR", "es-PY" };
|
||||
if (supportedCultures.Contains(cultureFromMiddleware))
|
||||
{
|
||||
return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(cultureFromMiddleware, cultureFromMiddleware));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to route data (standard routing)
|
||||
var routeValues = httpContext.GetRouteData()?.Values;
|
||||
if (routeValues != null && routeValues.TryGetValue("culture", out var cultureValue))
|
||||
{
|
||||
|
||||
@ -2150,4 +2150,16 @@
|
||||
<data name="FAQ_StaticQRNote" xml:space="preserve">
|
||||
<value>Los códigos QR estáticos son ideales para información permanente como WiFi, tarjetas de visita, URLs de sitios web y contactos.</value>
|
||||
</data>
|
||||
<data name="PixGeneratorTitle" xml:space="preserve">
|
||||
<value>Generador de PIX</value>
|
||||
</data>
|
||||
<data name="PixGeneratorDesc" xml:space="preserve">
|
||||
<value>Cree códigos QR para recibir pagos instantáneos. Compatible con todos los bancos brasileños.</value>
|
||||
</data>
|
||||
<data name="SellManyProductsTitle" xml:space="preserve">
|
||||
<value>¿Vende muchos productos?</value>
|
||||
</data>
|
||||
<data name="SellManyProductsDesc" xml:space="preserve">
|
||||
<value>Suscríbase a nuestro plan mensual y gestione códigos QR exclusivos para cada producto de su catálogo.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@ -2303,4 +2303,16 @@
|
||||
<data name="FAQ_StaticQRNote" xml:space="preserve">
|
||||
<value>QR codes estáticos são ideais para informações permanentes como WiFi, cartões de visita, URLs de sites, e contatos.</value>
|
||||
</data>
|
||||
<data name="PixGeneratorTitle" xml:space="preserve">
|
||||
<value>Gerador de PIX</value>
|
||||
</data>
|
||||
<data name="PixGeneratorDesc" xml:space="preserve">
|
||||
<value>Crie QR Codes para receber pagamentos instantâneos. Compatível com todos os bancos brasileiros.</value>
|
||||
</data>
|
||||
<data name="SellManyProductsTitle" xml:space="preserve">
|
||||
<value>Vende muitos produtos?</value>
|
||||
</data>
|
||||
<data name="SellManyProductsDesc" xml:space="preserve">
|
||||
<value>Assine nosso plano mensal e gerencie QR Codes exclusivos para cada produto do seu catálogo.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@ -7,6 +7,34 @@
|
||||
ViewData["Title"] = "Home";
|
||||
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
|
||||
// Server-Side Rendering: Determine which accordion to open based on qrType
|
||||
var selectedQRType = (ViewBag.SelectedQRType as string)?.ToLowerInvariant() ?? "";
|
||||
|
||||
// Map qrType to accordion ID suffix (e.g., "pix" -> "pixQR")
|
||||
var accordionIdMap = new Dictionary<string, string>
|
||||
{
|
||||
{ "url", "url" },
|
||||
{ "text", "text" },
|
||||
{ "texto", "text" },
|
||||
{ "whatsapp", "whatsapp" },
|
||||
{ "email", "email" },
|
||||
{ "pix", "pix" },
|
||||
{ "sms", "sms" },
|
||||
{ "wifi", "wifi" },
|
||||
{ "vcard", "vcard" }
|
||||
};
|
||||
|
||||
// Helper function to determine if this accordion should be open
|
||||
string GetAccordionClass(string accordionType) =>
|
||||
accordionIdMap.TryGetValue(selectedQRType, out var mapped) && mapped == accordionType
|
||||
? "accordion-collapse collapse show"
|
||||
: "accordion-collapse collapse";
|
||||
|
||||
string GetButtonClass(string accordionType) =>
|
||||
accordionIdMap.TryGetValue(selectedQRType, out var mapped) && mapped == accordionType
|
||||
? "accordion-button"
|
||||
: "accordion-button collapsed";
|
||||
}
|
||||
|
||||
|
||||
@ -512,8 +540,8 @@
|
||||
<div class="d-flex">
|
||||
<div class="me-3 display-4"><i class="fas fa-qrcode"></i></div>
|
||||
<div>
|
||||
<h5 class="alert-heading fw-bold">Gerador de PIX</h5>
|
||||
<p class="mb-0">Crie QR Codes para receber pagamentos instantâneos. Compatível com todos os bancos brasileiros.</p>
|
||||
<h5 class="alert-heading fw-bold">@Localizer["PixGeneratorTitle"]</h5>
|
||||
<p class="mb-0">@Localizer["PixGeneratorDesc"]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -521,8 +549,8 @@
|
||||
<div class="alert alert-warning border-warning d-flex align-items-center mb-3">
|
||||
<i class="fas fa-store fa-2x me-3 text-warning"></i>
|
||||
<div>
|
||||
<strong>Vende muitos produtos?</strong>
|
||||
<p class="mb-0 small">Assine nosso <a href="/Pagamento/SelecaoPlano" class="alert-link">plano mensal</a> e gerencie QR Codes exclusivos para cada produto do seu catálogo.</p>
|
||||
<strong>@Localizer["SellManyProductsTitle"]</strong>
|
||||
<p class="mb-0 small">Assine nosso plano mensal e gerencie QR Codes exclusivos para cada produto do seu catálogo.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -867,12 +895,12 @@
|
||||
<!-- URL/Link QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#urlQR">
|
||||
<button class="@GetButtonClass("url")" type="button" data-bs-toggle="collapse" data-bs-target="#urlQR">
|
||||
<i class="fas fa-link text-primary me-2"></i>
|
||||
<strong>@Localizer["URLLink"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="urlQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="urlQR" class="@GetAccordionClass("url")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -910,12 +938,12 @@
|
||||
<!-- Text QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#textQR">
|
||||
<button class="@GetButtonClass("text")" type="button" data-bs-toggle="collapse" data-bs-target="#textQR">
|
||||
<i class="fas fa-align-left text-primary me-2"></i>
|
||||
<strong>@Localizer["SimpleText"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="textQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="textQR" class="@GetAccordionClass("text")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -951,12 +979,12 @@
|
||||
<!-- WhatsApp QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#whatsappQR">
|
||||
<button class="@GetButtonClass("whatsapp")" type="button" data-bs-toggle="collapse" data-bs-target="#whatsappQR">
|
||||
<i class="fab fa-whatsapp text-success me-2"></i>
|
||||
<strong>WhatsApp</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="whatsappQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="whatsappQR" class="@GetAccordionClass("whatsapp")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -991,12 +1019,12 @@
|
||||
<!-- Email QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#emailQR">
|
||||
<button class="@GetButtonClass("email")" type="button" data-bs-toggle="collapse" data-bs-target="#emailQR">
|
||||
<i class="fas fa-envelope text-primary me-2"></i>
|
||||
<strong>@Localizer["Email"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="emailQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="emailQR" class="@GetAccordionClass("email")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -1031,12 +1059,12 @@
|
||||
<!-- PIX QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pixQR">
|
||||
<button class="@GetButtonClass("pix")" type="button" data-bs-toggle="collapse" data-bs-target="#pixQR">
|
||||
<i class="fas fa-qrcode text-success me-2"></i>
|
||||
<strong>@Localizer["PIX"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="pixQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="pixQR" class="@GetAccordionClass("pix")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -1070,12 +1098,12 @@
|
||||
<!-- SMS QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#smsQR">
|
||||
<button class="@GetButtonClass("sms")" type="button" data-bs-toggle="collapse" data-bs-target="#smsQR">
|
||||
<i class="fas fa-sms text-primary me-2"></i>
|
||||
<strong>@Localizer["SMS"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="smsQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="smsQR" class="@GetAccordionClass("sms")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -1110,12 +1138,12 @@
|
||||
<!-- WiFi QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#wifiQR">
|
||||
<button class="@GetButtonClass("wifi")" type="button" data-bs-toggle="collapse" data-bs-target="#wifiQR">
|
||||
<i class="fas fa-wifi text-primary me-2"></i>
|
||||
<strong>@Localizer["WiFi"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="wifiQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="wifiQR" class="@GetAccordionClass("wifi")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@ -1151,12 +1179,12 @@
|
||||
<!-- vCard QR Code -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#vcardQR">
|
||||
<button class="@GetButtonClass("vcard")" type="button" data-bs-toggle="collapse" data-bs-target="#vcardQR">
|
||||
<i class="fas fa-address-card text-primary me-2"></i>
|
||||
<strong>@Localizer["VCard"]</strong>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="vcardQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||
<div id="vcardQR" class="@GetAccordionClass("vcard")" data-bs-parent="#qrTypesAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
@ -18,6 +18,34 @@
|
||||
var appEnvironment = Configuration["App:Environment"] ?? HostEnvironment?.EnvironmentName ?? "Unknown";
|
||||
var secretsLoaded = Configuration.GetValue<bool>("App:SecretsLoaded");
|
||||
|
||||
// SEO: Determine if current page is Spanish (for URL building)
|
||||
var requestPath = Context.Request.Path.Value ?? "/";
|
||||
var isSpanish = requestPath.StartsWith("/es-PY", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// Get path without culture prefix for building alternate URLs
|
||||
var pathWithoutCulture = requestPath;
|
||||
if (isSpanish && requestPath.Length > 6)
|
||||
{
|
||||
pathWithoutCulture = requestPath.Substring(6); // Remove "/es-PY"
|
||||
}
|
||||
else if (isSpanish)
|
||||
{
|
||||
pathWithoutCulture = "/";
|
||||
}
|
||||
if (string.IsNullOrEmpty(pathWithoutCulture)) pathWithoutCulture = "/";
|
||||
|
||||
// Canonical URL - for Portuguese it's without prefix, for Spanish it's with /es-PY
|
||||
var canonicalUrl = isSpanish
|
||||
? $"https://qrrapido.site/es-PY{(pathWithoutCulture == "/" ? "" : pathWithoutCulture)}"
|
||||
: $"https://qrrapido.site{pathWithoutCulture}";
|
||||
|
||||
// Alternate URLs for hreflang
|
||||
var ptUrl = $"https://qrrapido.site{pathWithoutCulture}";
|
||||
var esUrl = $"https://qrrapido.site/es-PY{(pathWithoutCulture == "/" ? "" : pathWithoutCulture)}";
|
||||
|
||||
// Culture prefix for internal links
|
||||
var culturePrefix = isSpanish ? "/es-PY" : "";
|
||||
|
||||
if (User?.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
@ -47,27 +75,27 @@
|
||||
<meta http-equiv="Expires" content="0">
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta name="description" content="@Localizer["QRGenerateDescription"]">
|
||||
<meta name="keywords" content="qr rapido, gerador qr rapido, qr code rapido, codigo qr rapido, qr gratis rapido, generador qr rapido, qr ultrarapido">
|
||||
<meta name="description" content="@(ViewBag.Description ?? Localizer["QRGenerateDescription"])">
|
||||
<meta name="keywords" content="@(ViewBag.Keywords ?? "qr rapido, gerador qr rapido, qr code rapido, codigo qr rapido, qr gratis rapido, generador qr rapido, qr ultrarapido")">
|
||||
<meta name="author" content="QR Rapido">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="@Context.Request.GetDisplayUrl()">
|
||||
<link rel="canonical" href="@canonicalUrl">
|
||||
|
||||
<!-- Hreflang for multilingual -->
|
||||
<link rel="alternate" hreflang="pt-BR" href="https://qrrapido.site/pt-BR/">
|
||||
<link rel="alternate" hreflang="es-PY" href="https://qrrapido.site/es-PY/">
|
||||
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
|
||||
<!-- Hreflang for multilingual SEO -->
|
||||
<link rel="alternate" hreflang="pt-BR" href="@ptUrl">
|
||||
<link rel="alternate" hreflang="es-PY" href="@esUrl">
|
||||
<link rel="alternate" hreflang="x-default" href="@ptUrl">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="QR Rapido - @Localizer["FastestQRGeneratorWeb"]">
|
||||
<meta property="og:description" content="@Localizer["QRGenerateDescription"]">
|
||||
<meta property="og:title" content="@(ViewBag.Title ?? "QR Rapido") - @Localizer["FastestQRGeneratorWeb"]">
|
||||
<meta property="og:description" content="@(ViewBag.Description ?? Localizer["QRGenerateDescription"])">
|
||||
<meta property="og:image" content="https://qrrapido.site/images/qrrapido-og-image.png">
|
||||
<meta property="og:url" content="@Context.Request.GetDisplayUrl()">
|
||||
<meta property="og:url" content="@canonicalUrl">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="QR Rapido">
|
||||
<meta property="og:locale" content="pt_BR">
|
||||
<meta property="og:locale" content="@(isSpanish ? "es_PY" : "pt_BR")">
|
||||
|
||||
<!-- Twitter Cards -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
@ -378,11 +406,8 @@
|
||||
<p class="mb-0 opacity-75">
|
||||
@Localizer["AverageTimePrefix"] <strong>@Localizer["AverageTimeValue"]</strong> @Localizer["AverageTimeSuffix"]
|
||||
</p>
|
||||
@{
|
||||
var currentCulture = System.Globalization.CultureInfo.CurrentUICulture.Name;
|
||||
}
|
||||
<div class="mt-3">
|
||||
<a href="/@currentCulture/tutoriais" class="btn btn-sm btn-light">
|
||||
<a href="@(culturePrefix)/tutoriais" class="btn btn-sm btn-light">
|
||||
<i class="fas fa-graduation-cap"></i> @Localizer["ViewTutorials"]
|
||||
</a>
|
||||
</div>
|
||||
@ -400,18 +425,30 @@
|
||||
<footer class="bg-dark text-light py-4 mt-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-5">
|
||||
<h5>QR Rapido</h5>
|
||||
<p class="small">@Localizer["FastestQRGeneratorDescription"]</p>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-3">
|
||||
<h6>@Localizer["Tools"]</h6>
|
||||
<ul class="list-unstyled small">
|
||||
<li><a href="/pix" class="text-light text-decoration-none">Gerador de PIX</a></li>
|
||||
<li><a href="/wifi" class="text-light text-decoration-none">QR Code WiFi</a></li>
|
||||
<li><a href="/whatsapp" class="text-light text-decoration-none">Link WhatsApp</a></li>
|
||||
<li><a href="/vcard" class="text-light text-decoration-none">Cartão Digital</a></li>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<ul class="list-unstyled small">
|
||||
<li><a href="@(culturePrefix)/pix" class="text-light text-decoration-none">@Localizer["PixGenerator"]</a></li>
|
||||
<li><a href="@(culturePrefix)/wifi" class="text-light text-decoration-none">QR Code WiFi</a></li>
|
||||
<li><a href="@(culturePrefix)/whatsapp" class="text-light text-decoration-none">Link WhatsApp</a></li>
|
||||
<li><a href="@(culturePrefix)/vcard" class="text-light text-decoration-none">@Localizer["DigitalCard"]</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<ul class="list-unstyled small">
|
||||
<li><a href="@(culturePrefix)/email" class="text-light text-decoration-none">QR Code Email</a></li>
|
||||
<li><a href="@(culturePrefix)/sms" class="text-light text-decoration-none">QR Code SMS</a></li>
|
||||
<li><a href="@(culturePrefix)/texto" class="text-light text-decoration-none">@Localizer["TextQR"]</a></li>
|
||||
<li><a href="@(culturePrefix)/url" class="text-light text-decoration-none">QR Code URL</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<h6>@Localizer["UsefulLinks"]</h6>
|
||||
|
||||
@ -1,33 +1,30 @@
|
||||
// Language switching functionality for QR Rapido
|
||||
// SEO Strategy:
|
||||
// - Portuguese (pt-BR): URLs without prefix (/, /pix, /wifi, etc.) - canonical
|
||||
// - Spanish (es-PY): URLs with /es-PY prefix (/es-PY, /es-PY/pix, etc.)
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// FORCE: Respect the URL culture above all else
|
||||
let currentCulture = getCurrentCulture();
|
||||
|
||||
console.log('Current culture:', currentCulture);
|
||||
|
||||
localStorage.setItem('preferredLanguage', currentCulture);
|
||||
|
||||
const languageDropdownItems = document.querySelectorAll('.dropdown-item[data-lang]');
|
||||
const currentLangSpan = document.getElementById('current-lang');
|
||||
|
||||
// Get current culture from URL or default to pt-BR
|
||||
// Get current culture from URL
|
||||
function getCurrentCulture() {
|
||||
const pathSegments = window.location.pathname.split('/').filter(segment => segment);
|
||||
const supportedCultures = ['pt-BR', 'es-PY', 'es'];
|
||||
|
||||
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
|
||||
return pathSegments[0];
|
||||
// Check if first segment is es-PY (Spanish)
|
||||
if (pathSegments.length > 0 && pathSegments[0].toLowerCase() === 'es-py') {
|
||||
return 'es-PY';
|
||||
}
|
||||
|
||||
// Default is Portuguese (no prefix in URL)
|
||||
return 'pt-BR';
|
||||
}
|
||||
|
||||
// Update current language display
|
||||
// Update current language display in header
|
||||
function updateCurrentLanguageDisplay(culture) {
|
||||
const langMap = {
|
||||
'pt-BR': 'PT',
|
||||
'es-PY': 'ES',
|
||||
'es': 'ES'
|
||||
'es-PY': 'ES'
|
||||
};
|
||||
|
||||
if (currentLangSpan) {
|
||||
@ -41,40 +38,60 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const queryString = window.location.search;
|
||||
const hash = window.location.hash;
|
||||
|
||||
// Remove existing culture from path if present
|
||||
const pathSegments = currentPath.split('/').filter(segment => segment);
|
||||
const supportedCultures = ['pt-BR', 'es-PY', 'es'];
|
||||
// Get path segments, removing any culture prefix
|
||||
let pathSegments = currentPath.split('/').filter(segment => segment);
|
||||
|
||||
// Remove current culture if it's the first segment
|
||||
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
|
||||
pathSegments.shift();
|
||||
// Remove existing culture prefix if present (es-PY or pt-BR)
|
||||
if (pathSegments.length > 0) {
|
||||
const firstSegment = pathSegments[0].toLowerCase();
|
||||
if (firstSegment === 'es-py' || firstSegment === 'pt-br') {
|
||||
pathSegments.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// Build new path with selected culture
|
||||
const newPath = '/' + newCulture + (pathSegments.length > 0 ? '/' + pathSegments.join('/') : '');
|
||||
// Build new path based on selected culture
|
||||
let newPath;
|
||||
if (newCulture === 'pt-BR') {
|
||||
// Portuguese: no prefix (canonical URLs)
|
||||
newPath = pathSegments.length > 0 ? '/' + pathSegments.join('/') : '/';
|
||||
} else {
|
||||
// Spanish: add /es-PY prefix
|
||||
newPath = '/es-PY' + (pathSegments.length > 0 ? '/' + pathSegments.join('/') : '');
|
||||
}
|
||||
|
||||
return newPath + queryString + hash;
|
||||
}
|
||||
|
||||
// Handle language selection
|
||||
// Set culture cookie
|
||||
function setCultureCookie(culture) {
|
||||
// ASP.NET Core culture cookie format
|
||||
const cookieValue = `c=${culture}|uic=${culture}`;
|
||||
document.cookie = `.AspNetCore.Culture=${encodeURIComponent(cookieValue)}; path=/; max-age=31536000; SameSite=Lax`;
|
||||
|
||||
// Also store in localStorage for quick access
|
||||
localStorage.setItem('preferredLanguage', culture);
|
||||
}
|
||||
|
||||
// Handle language selection
|
||||
languageDropdownItems.forEach(item => {
|
||||
item.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const selectedLang = this.getAttribute('data-lang');
|
||||
currentCulture = getCurrentCulture();
|
||||
const currentCulture = getCurrentCulture();
|
||||
|
||||
// Don't do anything if same language selected
|
||||
if (selectedLang === currentCulture) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track language change for analytics
|
||||
if (typeof window.trackLanguageChange === 'function') {
|
||||
window.trackLanguageChange(currentCulture, selectedLang);
|
||||
}
|
||||
|
||||
// Store language preference in localStorage
|
||||
localStorage.setItem('preferredLanguage', selectedLang);
|
||||
|
||||
// Set culture cookie for server-side processing
|
||||
document.cookie = `culture=${selectedLang}; path=/; max-age=31536000; SameSite=Lax`;
|
||||
// Save preference
|
||||
setCultureCookie(selectedLang);
|
||||
|
||||
// Navigate to new URL with selected language
|
||||
const newUrl = buildLocalizedUrl(selectedLang);
|
||||
@ -82,46 +99,19 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize current language display
|
||||
currentCulture = getCurrentCulture();
|
||||
// Initialize
|
||||
const currentCulture = getCurrentCulture();
|
||||
updateCurrentLanguageDisplay(currentCulture);
|
||||
|
||||
// Store current culture in localStorage if not already set
|
||||
if (!localStorage.getItem('preferredLanguage')) {
|
||||
localStorage.setItem('preferredLanguage', currentCulture);
|
||||
}
|
||||
// Ensure cookie matches URL culture
|
||||
setCultureCookie(currentCulture);
|
||||
});
|
||||
|
||||
// Utility function to get user's preferred language
|
||||
// Utility function to get user's preferred language (for external use)
|
||||
function getUserPreferredLanguage() {
|
||||
// Check localStorage first
|
||||
const storedLang = localStorage.getItem('preferredLanguage');
|
||||
if (storedLang) {
|
||||
return storedLang;
|
||||
}
|
||||
|
||||
// Check browser language
|
||||
const browserLang = navigator.language || navigator.userLanguage;
|
||||
|
||||
// Map browser languages to supported cultures
|
||||
const langMap = {
|
||||
'pt': 'pt-BR',
|
||||
'pt-BR': 'pt-BR',
|
||||
'es': 'es-PY',
|
||||
'es-PY': 'es-PY'
|
||||
};
|
||||
|
||||
// Check exact match first
|
||||
if (langMap[browserLang]) {
|
||||
return langMap[browserLang];
|
||||
}
|
||||
|
||||
// Check language part only (e.g., 'es' from 'es-AR')
|
||||
const langPart = browserLang.split('-')[0];
|
||||
if (langMap[langPart]) {
|
||||
return langMap[langPart];
|
||||
}
|
||||
|
||||
return 'pt-BR'; // Default
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user