feat: Historico e localização!
This commit is contained in:
parent
c80b73e32f
commit
b189ea7275
@ -4,7 +4,8 @@
|
|||||||
"Bash(dotnet new:*)",
|
"Bash(dotnet new:*)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)",
|
||||||
"Bash(dotnet build:*)",
|
"Bash(dotnet build:*)",
|
||||||
"Bash(timeout:*)"
|
"Bash(timeout:*)",
|
||||||
|
"Bash(rm:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,12 +15,15 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly AdDisplayService _adDisplayService;
|
private readonly AdDisplayService _adDisplayService;
|
||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
public AccountController(IUserService userService, AdDisplayService adDisplayService, ILogger<AccountController> logger)
|
public AccountController(IUserService userService, AdDisplayService adDisplayService,
|
||||||
|
ILogger<AccountController> logger, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_adDisplayService = adDisplayService;
|
_adDisplayService = adDisplayService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -33,9 +36,10 @@ namespace QRRapidoApp.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult LoginGoogle(string returnUrl = "/")
|
public IActionResult LoginGoogle(string returnUrl = "/")
|
||||||
{
|
{
|
||||||
|
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||||
var properties = new AuthenticationProperties
|
var properties = new AuthenticationProperties
|
||||||
{
|
{
|
||||||
RedirectUri = Url.Action("GoogleCallback"),
|
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
||||||
Items = { { "returnUrl", returnUrl } }
|
Items = { { "returnUrl", returnUrl } }
|
||||||
};
|
};
|
||||||
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
||||||
@ -44,11 +48,8 @@ namespace QRRapidoApp.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
||||||
{
|
{
|
||||||
var properties = new AuthenticationProperties
|
var redirectUrl = Url.Action("MicrosoftCallback", "Account", new { returnUrl });
|
||||||
{
|
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
|
||||||
RedirectUri = Url.Action("MicrosoftCallback"),
|
|
||||||
Items = { { "returnUrl", returnUrl } }
|
|
||||||
};
|
|
||||||
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
120
Controllers/PagamentoController.cs
Normal file
120
Controllers/PagamentoController.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using QRRapidoApp.Services;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QRRapidoApp.Models.ViewModels;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Controllers
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
public class PagamentoController : Controller
|
||||||
|
{
|
||||||
|
private readonly IPlanService _planService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly StripeService _stripeService;
|
||||||
|
private readonly ILogger<PagamentoController> _logger;
|
||||||
|
|
||||||
|
public PagamentoController(IPlanService planService, IUserService userService, StripeService stripeService, ILogger<PagamentoController> logger)
|
||||||
|
{
|
||||||
|
_planService = planService;
|
||||||
|
_userService = userService;
|
||||||
|
_stripeService = stripeService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> SelecaoPlano()
|
||||||
|
{
|
||||||
|
var plans = await _planService.GetActivePlansAsync();
|
||||||
|
var countryCode = GetUserCountryCode(); // Implement this method based on your needs
|
||||||
|
|
||||||
|
var model = new SelecaoPlanoViewModel
|
||||||
|
{
|
||||||
|
Plans = plans,
|
||||||
|
CountryCode = countryCode
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateCheckout(string planId)
|
||||||
|
{
|
||||||
|
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
return Json(new { success = false, error = "User not authenticated" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var plan = await _planService.GetPlanByIdAsync(planId);
|
||||||
|
if (plan == null)
|
||||||
|
{
|
||||||
|
return Json(new { success = false, error = "Plan not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var countryCode = GetUserCountryCode();
|
||||||
|
var priceId = plan.PricesByCountry.ContainsKey(countryCode)
|
||||||
|
? plan.PricesByCountry[countryCode].StripePriceId
|
||||||
|
: plan.StripePriceId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId);
|
||||||
|
return Json(new { success = true, url = checkoutUrl });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error creating checkout session for user {userId} and plan {planId}");
|
||||||
|
return Json(new { success = false, error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Sucesso()
|
||||||
|
{
|
||||||
|
ViewBag.SuccessMessage = "Pagamento concluído com sucesso! Bem-vindo ao Premium.";
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Cancelar()
|
||||||
|
{
|
||||||
|
ViewBag.CancelMessage = "O pagamento foi cancelado. Você pode tentar novamente a qualquer momento.";
|
||||||
|
return View("SelecaoPlano");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> StripeWebhook()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(HttpContext.Request.Body);
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
var signature = Request.Headers["Stripe-Signature"].FirstOrDefault();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(signature))
|
||||||
|
{
|
||||||
|
return BadRequest("Missing Stripe signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _stripeService.HandleWebhookAsync(json, signature);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error processing Stripe webhook");
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUserCountryCode()
|
||||||
|
{
|
||||||
|
// Prioritize Cloudflare header, fallback to a default or other methods
|
||||||
|
return HttpContext.Request.Headers["CF-IPCountry"].FirstOrDefault() ?? "BR";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,111 +25,9 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Upgrade()
|
public IActionResult Upgrade()
|
||||||
{
|
{
|
||||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
return RedirectToAction("SelecaoPlano", "Pagamento");
|
||||||
if (string.IsNullOrEmpty(userId))
|
|
||||||
{
|
|
||||||
return RedirectToAction("Login", "Account");
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userService.GetUserAsync(userId);
|
|
||||||
if (user?.IsPremium == true)
|
|
||||||
{
|
|
||||||
return RedirectToAction("Dashboard");
|
|
||||||
}
|
|
||||||
|
|
||||||
var model = new UpgradeViewModel
|
|
||||||
{
|
|
||||||
CurrentPlan = "Free",
|
|
||||||
PremiumPrice = _config.GetValue<decimal>("Premium:PremiumPrice"),
|
|
||||||
Features = _config.GetSection("Premium:Features").Get<Dictionary<string, bool>>() ?? new(),
|
|
||||||
RemainingQRs = await _userService.GetDailyQRCountAsync(userId),
|
|
||||||
IsAdFreeActive = !await _adDisplayService.ShouldShowAds(userId),
|
|
||||||
DaysUntilAdExpiry = (int)((await _adDisplayService.GetAdFreeExpiryDate(userId) - DateTime.UtcNow)?.TotalDays ?? 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
return View(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<IActionResult> CreateCheckout()
|
|
||||||
{
|
|
||||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
||||||
if (string.IsNullOrEmpty(userId))
|
|
||||||
{
|
|
||||||
return Json(new { success = false, error = "User not authenticated" });
|
|
||||||
}
|
|
||||||
|
|
||||||
var priceId = _config["Stripe:PriceId"];
|
|
||||||
if (string.IsNullOrEmpty(priceId))
|
|
||||||
{
|
|
||||||
return Json(new { success = false, error = "Stripe not configured" });
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var checkoutUrl = await _stripeService.CreateCheckoutSessionAsync(userId, priceId);
|
|
||||||
return Json(new { success = true, url = checkoutUrl });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error creating checkout session for user {userId}");
|
|
||||||
return Json(new { success = false, error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> Success(string session_id)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(session_id))
|
|
||||||
{
|
|
||||||
return RedirectToAction("Upgrade");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ViewBag.Success = true;
|
|
||||||
ViewBag.SessionId = session_id;
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error processing successful payment for session {session_id}");
|
|
||||||
return RedirectToAction("Upgrade");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public IActionResult Cancel()
|
|
||||||
{
|
|
||||||
ViewBag.Cancelled = true;
|
|
||||||
return View("Upgrade");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<IActionResult> StripeWebhook()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var reader = new StreamReader(HttpContext.Request.Body);
|
|
||||||
var json = await reader.ReadToEndAsync();
|
|
||||||
var signature = Request.Headers["Stripe-Signature"].FirstOrDefault();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(signature))
|
|
||||||
{
|
|
||||||
return BadRequest("Missing Stripe signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _stripeService.HandleWebhookAsync(json, signature);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error processing Stripe webhook");
|
|
||||||
return BadRequest(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@ -30,8 +30,9 @@ namespace QRRapidoApp.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMongoCollection<User>? Users => _isConnected ? _database?.GetCollection<User>("users") : null;
|
public IMongoCollection<User> Users => _database.GetCollection<User>("users");
|
||||||
public IMongoCollection<QRCodeHistory>? QRCodeHistory => _isConnected ? _database?.GetCollection<QRCodeHistory>("qr_codes") : null;
|
public IMongoCollection<QRCodeHistory> QRCodeHistory => _database.GetCollection<QRCodeHistory>("qrCodeHistory");
|
||||||
|
public IMongoCollection<Plan> Plans => _database.GetCollection<Plan>("plans");
|
||||||
public IMongoCollection<AdFreeSession>? AdFreeSessions => _isConnected ? _database?.GetCollection<AdFreeSession>("ad_free_sessions") : null;
|
public IMongoCollection<AdFreeSession>? AdFreeSessions => _isConnected ? _database?.GetCollection<AdFreeSession>("ad_free_sessions") : null;
|
||||||
|
|
||||||
public IMongoDatabase? Database => _isConnected ? _database : null;
|
public IMongoDatabase? Database => _isConnected ? _database : null;
|
||||||
|
|||||||
111
Middleware/LanguageRedirectionMiddleware.cs
Normal file
111
Middleware/LanguageRedirectionMiddleware.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Middleware
|
||||||
|
{
|
||||||
|
public class LanguageRedirectionMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<LanguageRedirectionMiddleware> _logger;
|
||||||
|
private readonly string[] _supportedCultures = { "pt-BR", "es", "en" };
|
||||||
|
private const string DefaultCulture = "pt-BR";
|
||||||
|
|
||||||
|
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 if already has culture in path, or if it's an API, static file, or special route
|
||||||
|
if (HasCultureInPath(path) || IsSpecialRoute(path))
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect browser language
|
||||||
|
var detectedCulture = DetectBrowserLanguage(context);
|
||||||
|
|
||||||
|
// Build redirect URL with culture
|
||||||
|
var redirectUrl = $"/{detectedCulture}";
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
redirectUrl += $"/{path}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add query string if present
|
||||||
|
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;
|
||||||
|
|
||||||
|
return _supportedCultures.Contains(segments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSpecialRoute(string path)
|
||||||
|
{
|
||||||
|
var specialRoutes = new[]
|
||||||
|
{
|
||||||
|
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
||||||
|
"favicon.ico", "robots.txt", "sitemap.xml"
|
||||||
|
};
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
// Check for exact matches first
|
||||||
|
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
|
||||||
|
{
|
||||||
|
var langCode = lang.Value.Value;
|
||||||
|
|
||||||
|
// Special case: es-PY should redirect to pt-BR
|
||||||
|
if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return DefaultCulture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check exact match
|
||||||
|
if (_supportedCultures.Contains(langCode))
|
||||||
|
{
|
||||||
|
return langCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check language part only (e.g., 'es' from 'es-AR')
|
||||||
|
var languagePart = langCode.Split('-')[0];
|
||||||
|
var matchingCulture = _supportedCultures.FirstOrDefault(c => c.StartsWith(languagePart + "-") || c == languagePart);
|
||||||
|
if (matchingCulture != null)
|
||||||
|
{
|
||||||
|
return matchingCulture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
return DefaultCulture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Models/Extensions/PlanExtensions.cs
Normal file
58
Models/Extensions/PlanExtensions.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Models.Extensions
|
||||||
|
{
|
||||||
|
public static class PlanExtensions
|
||||||
|
{
|
||||||
|
public static string GetLocalizedName(this Plan plan, string languageCode)
|
||||||
|
{
|
||||||
|
if (plan.Name.TryGetValue(languageCode, out var name))
|
||||||
|
return name;
|
||||||
|
|
||||||
|
// Fallback to Portuguese if language not found
|
||||||
|
if (plan.Name.TryGetValue("pt-BR", out var ptName))
|
||||||
|
return ptName;
|
||||||
|
|
||||||
|
// Final fallback to first available language
|
||||||
|
return plan.Name.Values.FirstOrDefault() ?? "Plan";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetLocalizedDescription(this Plan plan, string languageCode)
|
||||||
|
{
|
||||||
|
if (plan.Description.TryGetValue(languageCode, out var description))
|
||||||
|
return description;
|
||||||
|
|
||||||
|
// Fallback to Portuguese if language not found
|
||||||
|
if (plan.Description.TryGetValue("pt-BR", out var ptDescription))
|
||||||
|
return ptDescription;
|
||||||
|
|
||||||
|
// Final fallback to first available language
|
||||||
|
return plan.Description.Values.FirstOrDefault() ?? "Premium plan description";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> GetLocalizedFeatures(this Plan plan, string languageCode)
|
||||||
|
{
|
||||||
|
if (plan.Features.TryGetValue(languageCode, out var features))
|
||||||
|
return features;
|
||||||
|
|
||||||
|
// Fallback to Portuguese if language not found
|
||||||
|
if (plan.Features.TryGetValue("pt-BR", out var ptFeatures))
|
||||||
|
return ptFeatures;
|
||||||
|
|
||||||
|
// Final fallback to first available language
|
||||||
|
return plan.Features.Values.FirstOrDefault() ?? new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetLanguageCode(string culture)
|
||||||
|
{
|
||||||
|
return culture switch
|
||||||
|
{
|
||||||
|
"pt-BR" or "pt" => "pt-BR",
|
||||||
|
"es" or "es-ES" => "es",
|
||||||
|
"en" or "en-US" => "en",
|
||||||
|
_ => "pt-BR" // Default to Portuguese
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Models/Plan.cs
Normal file
47
Models/Plan.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Models
|
||||||
|
{
|
||||||
|
public class Plan
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[BsonElement("name")]
|
||||||
|
public Dictionary<string, string> Name { get; set; } = new(); // e.g., {"pt": "Premium Mensal", "en": "Premium Monthly", "es": "Premium Mensual"}
|
||||||
|
|
||||||
|
[BsonElement("description")]
|
||||||
|
public Dictionary<string, string> Description { get; set; } = new(); // Multilingual descriptions
|
||||||
|
|
||||||
|
[BsonElement("features")]
|
||||||
|
public Dictionary<string, List<string>> Features { get; set; } = new(); // Multilingual feature lists
|
||||||
|
|
||||||
|
[BsonElement("interval")]
|
||||||
|
public string Interval { get; set; } = string.Empty; // e.g., "month", "year"
|
||||||
|
|
||||||
|
[BsonElement("stripePriceId")]
|
||||||
|
public string StripePriceId { get; set; } = string.Empty; // Default Price ID
|
||||||
|
|
||||||
|
[BsonElement("pricesByCountry")]
|
||||||
|
public Dictionary<string, PriceInfo> PricesByCountry { get; set; } = new();
|
||||||
|
|
||||||
|
[BsonElement("isActive")]
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PriceInfo
|
||||||
|
{
|
||||||
|
[BsonElement("amount")]
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
[BsonElement("currency")]
|
||||||
|
public string Currency { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[BsonElement("stripePriceId")]
|
||||||
|
public string StripePriceId { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/ViewModels/SelecaoPlanoViewModel.cs
Normal file
12
Models/ViewModels/SelecaoPlanoViewModel.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
using QRRapidoApp.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Models.ViewModels
|
||||||
|
{
|
||||||
|
public class SelecaoPlanoViewModel
|
||||||
|
{
|
||||||
|
public List<Plan> Plans { get; set; } = new();
|
||||||
|
public string CountryCode { get; set; } = "BR";
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Program.cs
67
Program.cs
@ -17,6 +17,7 @@ using System.Globalization;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Serilog.Sinks.SystemConsole.Themes;
|
using Serilog.Sinks.SystemConsole.Themes;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -28,33 +29,28 @@ Log.Logger = new LoggerConfiguration()
|
|||||||
.Enrich.WithProcessId()
|
.Enrich.WithProcessId()
|
||||||
.Enrich.WithThreadId()
|
.Enrich.WithThreadId()
|
||||||
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido")
|
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido")
|
||||||
|
.Enrich.WithProperty("Environment", "Dev")
|
||||||
.WriteTo.Async(a => a.Console(
|
.WriteTo.Async(a => a.Console(
|
||||||
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
|
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
|
||||||
theme: AnsiConsoleTheme.Code))
|
theme: AnsiConsoleTheme.Code))
|
||||||
.WriteTo.Async(a => {
|
.WriteTo.Async(a => a.Seq(builder.Configuration["Serilog:SeqUrl"],
|
||||||
var seqUrl = builder.Configuration["Serilog:SeqUrl"];
|
apiKey: builder.Configuration["Serilog:ApiKey"]=="" ? null : builder.Configuration["Serilog:ApiKey"]))
|
||||||
var apiKey = builder.Configuration["Serilog:ApiKey"];
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(seqUrl))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Temporarily skip Seq until packages are installed
|
|
||||||
Console.WriteLine($"Seq configured for: {seqUrl}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Failed to configure Seq sink: {ex.Message}");
|
|
||||||
// Continue without Seq - will still log to console
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
// Add services to the container
|
// Add services to the container
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews()
|
||||||
|
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
|
||||||
|
.AddDataAnnotationsLocalization();
|
||||||
|
|
||||||
|
builder.Services.AddDistributedMemoryCache(); // Armazena os dados da sess<73>o na mem<65>ria
|
||||||
|
builder.Services.AddSession(options =>
|
||||||
|
{
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.Cookie.IsEssential = true;
|
||||||
|
options.IdleTimeout = TimeSpan.FromMinutes(30); // Tempo de expira<72><61>o
|
||||||
|
});
|
||||||
|
|
||||||
// Add HttpClient for health checks
|
// Add HttpClient for health checks
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
@ -132,7 +128,8 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
|||||||
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
|
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
builder.Services.AddLocalization(options => options.ResourcesPath = "");
|
||||||
|
//builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||||
{
|
{
|
||||||
var supportedCultures = new[]
|
var supportedCultures = new[]
|
||||||
@ -142,17 +139,24 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|||||||
new CultureInfo("en")
|
new CultureInfo("en")
|
||||||
};
|
};
|
||||||
|
|
||||||
options.DefaultRequestCulture = new RequestCulture("pt-BR");
|
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||||
options.SupportedCultures = supportedCultures;
|
options.SupportedCultures = supportedCultures;
|
||||||
options.SupportedUICultures = supportedCultures;
|
options.SupportedUICultures = supportedCultures;
|
||||||
|
|
||||||
options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider());
|
options.FallBackToParentCultures = true;
|
||||||
options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider());
|
options.FallBackToParentUICultures = true;
|
||||||
|
|
||||||
|
// Clear default providers and add custom ones in priority order
|
||||||
|
options.RequestCultureProviders.Clear();
|
||||||
|
options.RequestCultureProviders.Add(new RouteDataRequestCultureProvider());
|
||||||
|
options.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
|
||||||
|
options.RequestCultureProviders.Add(new CookieRequestCultureProvider());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Services
|
// Custom Services
|
||||||
builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
|
builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
|
||||||
builder.Services.AddScoped<IUserService, UserService>();
|
builder.Services.AddScoped<IUserService, UserService>();
|
||||||
|
builder.Services.AddScoped<IPlanService, QRRapidoApp.Services.PlanService>();
|
||||||
builder.Services.AddScoped<AdDisplayService>();
|
builder.Services.AddScoped<AdDisplayService>();
|
||||||
builder.Services.AddScoped<StripeService>();
|
builder.Services.AddScoped<StripeService>();
|
||||||
|
|
||||||
@ -206,6 +210,9 @@ app.UseHttpsRedirection();
|
|||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
// Language redirection middleware (before routing)
|
||||||
|
app.UseMiddleware<LanguageRedirectionMiddleware>();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseCors("AllowSpecificOrigins");
|
app.UseCors("AllowSpecificOrigins");
|
||||||
@ -216,26 +223,28 @@ app.UseRequestLocalization();
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseSession();
|
||||||
|
|
||||||
// Custom middleware
|
// Custom middleware
|
||||||
app.UseMiddleware<LastLoginUpdateMiddleware>();
|
app.UseMiddleware<LastLoginUpdateMiddleware>();
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
app.MapHealthChecks("/health");
|
app.MapHealthChecks("/health");
|
||||||
|
|
||||||
// Controller routes
|
// Language routes (must be first)
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "default",
|
name: "localized",
|
||||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "api",
|
name: "api",
|
||||||
pattern: "api/{controller}/{action=Index}/{id?}");
|
pattern: "api/{controller}/{action=Index}/{id?}");
|
||||||
|
|
||||||
// Language routes
|
// Default fallback route (for development/testing without culture)
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "localized",
|
name: "default",
|
||||||
pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}");
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
<PackageReference Include="Stripe.net" Version="43.15.0" />
|
<PackageReference Include="Stripe.net" Version="43.15.0" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" />
|
||||||
@ -34,7 +35,6 @@
|
|||||||
<Folder Include="wwwroot\images\" />
|
<Folder Include="wwwroot\images\" />
|
||||||
<Folder Include="wwwroot\css\" />
|
<Folder Include="wwwroot\css\" />
|
||||||
<Folder Include="wwwroot\js\" />
|
<Folder Include="wwwroot\js\" />
|
||||||
<Folder Include="Resources\" />
|
|
||||||
<Folder Include="Data\" />
|
<Folder Include="Data\" />
|
||||||
<Folder Include="Services\" />
|
<Folder Include="Services\" />
|
||||||
<Folder Include="Models\" />
|
<Folder Include="Models\" />
|
||||||
@ -42,4 +42,28 @@
|
|||||||
<Folder Include="Tests\" />
|
<Folder Include="Tests\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Resources\SharedResource.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>SharedResource.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Resources\SharedResource.en.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Resources\SharedResource.es.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Resources\SharedResource.pt-BR.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Resources\SharedResource.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>SharedResource.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
693
Resources/SharedResource.Designer.cs
generated
Normal file
693
Resources/SharedResource.Designer.cs
generated
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Resources {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
public class SharedResource {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal SharedResource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QRRapidoApp.Resources.SharedResource", typeof(SharedResource).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Unlock all premium features!.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdFreeOffer {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AdFreeOffer", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Advanced Customization.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdvancedCustomization {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AdvancedCustomization", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Background Color.
|
||||||
|
/// </summary>
|
||||||
|
public static string BackgroundColor {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("BackgroundColor", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Back to generator.
|
||||||
|
/// </summary>
|
||||||
|
public static string BackToGenerator {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("BackToGenerator", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Classic.
|
||||||
|
/// </summary>
|
||||||
|
public static string Classic {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Classic", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Classic.
|
||||||
|
/// </summary>
|
||||||
|
public static string ClassicStyle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ClassicStyle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Colorful.
|
||||||
|
/// </summary>
|
||||||
|
public static string Colorful {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Colorful", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Colorful.
|
||||||
|
/// </summary>
|
||||||
|
public static string ColorfulStyle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ColorfulStyle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Content.
|
||||||
|
/// </summary>
|
||||||
|
public static string Content {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Content", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Content hints.
|
||||||
|
/// </summary>
|
||||||
|
public static string ContentHints {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ContentHints", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enter your QR code content here....
|
||||||
|
/// </summary>
|
||||||
|
public static string ContentPlaceholder {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ContentPlaceholder", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Corner Style.
|
||||||
|
/// </summary>
|
||||||
|
public static string CornerStyle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CornerStyle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Create QR Code Quickly.
|
||||||
|
/// </summary>
|
||||||
|
public static string CreateQRCodeQuickly {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CreateQRCodeQuickly", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Download PDF.
|
||||||
|
/// </summary>
|
||||||
|
public static string DownloadPDF {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DownloadPDF", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Download PNG.
|
||||||
|
/// </summary>
|
||||||
|
public static string DownloadPNG {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DownloadPNG", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Download SVG (Vector).
|
||||||
|
/// </summary>
|
||||||
|
public static string DownloadSVG {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DownloadSVG", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Dynamic QR (Premium).
|
||||||
|
/// </summary>
|
||||||
|
public static string DynamicQRPremium {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DynamicQRPremium", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Dynamic QR (Premium).
|
||||||
|
/// </summary>
|
||||||
|
public static string DynamicType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DynamicType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Email.
|
||||||
|
/// </summary>
|
||||||
|
public static string EmailType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EmailType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enter your QR code content here....
|
||||||
|
/// </summary>
|
||||||
|
public static string EnterQRCodeContent {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnterQRCodeContent", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generation error. Try again..
|
||||||
|
/// </summary>
|
||||||
|
public static string Error {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Error", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Fast generation!.
|
||||||
|
/// </summary>
|
||||||
|
public static string Fast {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Fast", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generated in.
|
||||||
|
/// </summary>
|
||||||
|
public static string GeneratedIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("GeneratedIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generate QR Code.
|
||||||
|
/// </summary>
|
||||||
|
public static string GenerateQR {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("GenerateQR", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generate QR Code Rapidly.
|
||||||
|
/// </summary>
|
||||||
|
public static string GenerateRapidly {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("GenerateRapidly", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Google.
|
||||||
|
/// </summary>
|
||||||
|
public static string Google {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Google", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Login.
|
||||||
|
/// </summary>
|
||||||
|
public static string Login {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Login", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoginBenefits {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoginBenefits", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Login to save to history.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoginToSave {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoginToSave", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Login with.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoginWith {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoginWith", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logo/Icon.
|
||||||
|
/// </summary>
|
||||||
|
public static string Logo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Logo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Margin.
|
||||||
|
/// </summary>
|
||||||
|
public static string Margin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Margin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Microsoft.
|
||||||
|
/// </summary>
|
||||||
|
public static string Microsoft {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Microsoft", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Modern.
|
||||||
|
/// </summary>
|
||||||
|
public static string Modern {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Modern", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Modern.
|
||||||
|
/// </summary>
|
||||||
|
public static string ModernStyle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ModernStyle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No Ads • History • Unlimited QR.
|
||||||
|
/// </summary>
|
||||||
|
public static string NoAdsHistoryUnlimitedQR {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoAdsHistoryUnlimitedQR", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Normal generation.
|
||||||
|
/// </summary>
|
||||||
|
public static string Normal {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Normal", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to QR Rapido Premium.
|
||||||
|
/// </summary>
|
||||||
|
public static string PremiumTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PremiumTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Premium User Active.
|
||||||
|
/// </summary>
|
||||||
|
public static string PremiumUserActive {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PremiumUserActive", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Preview.
|
||||||
|
/// </summary>
|
||||||
|
public static string Preview {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Preview", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Your QR code will appear here in seconds.
|
||||||
|
/// </summary>
|
||||||
|
public static string PreviewPlaceholder {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PreviewPlaceholder", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Primary Color.
|
||||||
|
/// </summary>
|
||||||
|
public static string PrimaryColor {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PrimaryColor", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Privacy Policy.
|
||||||
|
/// </summary>
|
||||||
|
public static string Privacy {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Privacy", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to QR codes remaining.
|
||||||
|
/// </summary>
|
||||||
|
public static string QRCodesRemaining {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("QRCodesRemaining", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to QR Code Type.
|
||||||
|
/// </summary>
|
||||||
|
public static string QRCodeType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("QRCodeType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to QR Code Type.
|
||||||
|
/// </summary>
|
||||||
|
public static string QRType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("QRType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Quick Style.
|
||||||
|
/// </summary>
|
||||||
|
public static string QuickStyle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("QuickStyle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Save to History.
|
||||||
|
/// </summary>
|
||||||
|
public static string SaveToHistory {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SaveToHistory", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to s.
|
||||||
|
/// </summary>
|
||||||
|
public static string Seconds {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Seconds", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select type.
|
||||||
|
/// </summary>
|
||||||
|
public static string SelectType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SelectType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Simple Text.
|
||||||
|
/// </summary>
|
||||||
|
public static string SimpleText {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SimpleText", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Size.
|
||||||
|
/// </summary>
|
||||||
|
public static string Size {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Size", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to SMS.
|
||||||
|
/// </summary>
|
||||||
|
public static string SMSType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SMSType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Premium Offer!.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpecialOffer {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpecialOffer", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Short URLs generate faster.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedTip1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedTip1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Less text = higher speed.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedTip2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedTip2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Solid colors optimize the process.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedTip3 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedTip3", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Smaller sizes speed up downloads.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedTip4 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedTip4", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Tips for Faster QR.
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedTipsTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedTipsTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to QR Code saved to history!.
|
||||||
|
/// </summary>
|
||||||
|
public static string Success {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Success", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Generate QR codes in seconds!.
|
||||||
|
/// </summary>
|
||||||
|
public static string Tagline {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Tagline", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Plain Text.
|
||||||
|
/// </summary>
|
||||||
|
public static string TextType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TextType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Ultra fast generation!.
|
||||||
|
/// </summary>
|
||||||
|
public static string UltraFast {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UltraFast", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Ultra-fast generation guaranteed.
|
||||||
|
/// </summary>
|
||||||
|
public static string UltraFastGeneration {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UltraFastGeneration", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Unlimited today.
|
||||||
|
/// </summary>
|
||||||
|
public static string UnlimitedToday {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UnlimitedToday", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URL/Link.
|
||||||
|
/// </summary>
|
||||||
|
public static string URLLink {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("URLLink", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URL/Link.
|
||||||
|
/// </summary>
|
||||||
|
public static string URLType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("URLType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Business Card.
|
||||||
|
/// </summary>
|
||||||
|
public static string VCard {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VCard", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Business Card.
|
||||||
|
/// </summary>
|
||||||
|
public static string VCardType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VCardType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to WiFi.
|
||||||
|
/// </summary>
|
||||||
|
public static string WiFiType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WiFiType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,28 +59,28 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Tagline" xml:space="preserve">
|
<data name="Tagline" xml:space="preserve">
|
||||||
<value>Gere QR codes em segundos!</value>
|
<value>Generate QR codes in seconds!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GenerateQR" xml:space="preserve">
|
<data name="GenerateQR" xml:space="preserve">
|
||||||
<value>Gerar QR Code</value>
|
<value>Generate QR Code</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="QRType" xml:space="preserve">
|
<data name="QRType" xml:space="preserve">
|
||||||
<value>Tipo de QR Code</value>
|
<value>QR Code Type</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Content" xml:space="preserve">
|
<data name="Content" xml:space="preserve">
|
||||||
<value>Conteúdo</value>
|
<value>Content</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="URLType" xml:space="preserve">
|
<data name="URLType" xml:space="preserve">
|
||||||
<value>URL/Link</value>
|
<value>URL/Link</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TextType" xml:space="preserve">
|
<data name="TextType" xml:space="preserve">
|
||||||
<value>Texto Simples</value>
|
<value>Plain Text</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="WiFiType" xml:space="preserve">
|
<data name="WiFiType" xml:space="preserve">
|
||||||
<value>WiFi</value>
|
<value>WiFi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VCardType" xml:space="preserve">
|
<data name="VCardType" xml:space="preserve">
|
||||||
<value>Cartão de Visita</value>
|
<value>Business Card</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SMSType" xml:space="preserve">
|
<data name="SMSType" xml:space="preserve">
|
||||||
<value>SMS</value>
|
<value>SMS</value>
|
||||||
@ -89,94 +89,94 @@
|
|||||||
<value>Email</value>
|
<value>Email</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DynamicType" xml:space="preserve">
|
<data name="DynamicType" xml:space="preserve">
|
||||||
<value>QR Dinâmico (Premium)</value>
|
<value>Dynamic QR (Premium)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="QuickStyle" xml:space="preserve">
|
<data name="QuickStyle" xml:space="preserve">
|
||||||
<value>Estilo Rápido</value>
|
<value>Quick Style</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClassicStyle" xml:space="preserve">
|
<data name="ClassicStyle" xml:space="preserve">
|
||||||
<value>Clássico</value>
|
<value>Classic</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ModernStyle" xml:space="preserve">
|
<data name="ModernStyle" xml:space="preserve">
|
||||||
<value>Moderno</value>
|
<value>Modern</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ColorfulStyle" xml:space="preserve">
|
<data name="ColorfulStyle" xml:space="preserve">
|
||||||
<value>Colorido</value>
|
<value>Colorful</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ContentPlaceholder" xml:space="preserve">
|
<data name="ContentPlaceholder" xml:space="preserve">
|
||||||
<value>Digite o conteúdo do seu QR code aqui...</value>
|
<value>Enter your QR code content here...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedCustomization" xml:space="preserve">
|
<data name="AdvancedCustomization" xml:space="preserve">
|
||||||
<value>Personalização Avançada</value>
|
<value>Advanced Customization</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PrimaryColor" xml:space="preserve">
|
<data name="PrimaryColor" xml:space="preserve">
|
||||||
<value>Cor Principal</value>
|
<value>Primary Color</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BackgroundColor" xml:space="preserve">
|
<data name="BackgroundColor" xml:space="preserve">
|
||||||
<value>Cor de Fundo</value>
|
<value>Background Color</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Size" xml:space="preserve">
|
<data name="Size" xml:space="preserve">
|
||||||
<value>Tamanho</value>
|
<value>Size</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Margin" xml:space="preserve">
|
<data name="Margin" xml:space="preserve">
|
||||||
<value>Margem</value>
|
<value>Margin</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Logo" xml:space="preserve">
|
<data name="Logo" xml:space="preserve">
|
||||||
<value>Logo/Ícone</value>
|
<value>Logo/Icon</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CornerStyle" xml:space="preserve">
|
<data name="CornerStyle" xml:space="preserve">
|
||||||
<value>Estilo das Bordas</value>
|
<value>Corner Style</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GenerateRapidly" xml:space="preserve">
|
<data name="GenerateRapidly" xml:space="preserve">
|
||||||
<value>Gerar QR Code Rapidamente</value>
|
<value>Generate QR Code Rapidly</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Preview" xml:space="preserve">
|
<data name="Preview" xml:space="preserve">
|
||||||
<value>Preview</value>
|
<value>Preview</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PreviewPlaceholder" xml:space="preserve">
|
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||||
<value>Seu QR code aparecerá aqui em segundos</value>
|
<value>Your QR code will appear here in seconds</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UltraFastGeneration" xml:space="preserve">
|
<data name="UltraFastGeneration" xml:space="preserve">
|
||||||
<value>Geração ultra-rápida garantida</value>
|
<value>Ultra-fast generation guaranteed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DownloadPNG" xml:space="preserve">
|
<data name="DownloadPNG" xml:space="preserve">
|
||||||
<value>Download PNG</value>
|
<value>Download PNG</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DownloadSVG" xml:space="preserve">
|
<data name="DownloadSVG" xml:space="preserve">
|
||||||
<value>Download SVG (Vetorial)</value>
|
<value>Download SVG (Vector)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DownloadPDF" xml:space="preserve">
|
<data name="DownloadPDF" xml:space="preserve">
|
||||||
<value>Download PDF</value>
|
<value>Download PDF</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SaveToHistory" xml:space="preserve">
|
<data name="SaveToHistory" xml:space="preserve">
|
||||||
<value>Salvar no Histórico</value>
|
<value>Save to History</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginToSave" xml:space="preserve">
|
<data name="LoginToSave" xml:space="preserve">
|
||||||
<value>Faça login para salvar no histórico</value>
|
<value>Login to save to history</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumTitle" xml:space="preserve">
|
<data name="PremiumTitle" xml:space="preserve">
|
||||||
<value>QR Rapido Premium</value>
|
<value>QR Rapido Premium</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||||
<value>Dicas para QR Mais Rápidos</value>
|
<value>Tips for Faster QR</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip1" xml:space="preserve">
|
<data name="SpeedTip1" xml:space="preserve">
|
||||||
<value>URLs curtas geram mais rápido</value>
|
<value>Short URLs generate faster</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip2" xml:space="preserve">
|
<data name="SpeedTip2" xml:space="preserve">
|
||||||
<value>Menos texto = maior velocidade</value>
|
<value>Less text = higher speed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip3" xml:space="preserve">
|
<data name="SpeedTip3" xml:space="preserve">
|
||||||
<value>Cores sólidas otimizam o processo</value>
|
<value>Solid colors optimize the process</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip4" xml:space="preserve">
|
<data name="SpeedTip4" xml:space="preserve">
|
||||||
<value>Tamanhos menores aceleram o download</value>
|
<value>Smaller sizes speed up downloads</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Login" xml:space="preserve">
|
<data name="Login" xml:space="preserve">
|
||||||
<value>Login</value>
|
<value>Login</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginWith" xml:space="preserve">
|
<data name="LoginWith" xml:space="preserve">
|
||||||
<value>Entrar com</value>
|
<value>Login with</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Google" xml:space="preserve">
|
<data name="Google" xml:space="preserve">
|
||||||
<value>Google</value>
|
<value>Google</value>
|
||||||
@ -185,39 +185,39 @@
|
|||||||
<value>Microsoft</value>
|
<value>Microsoft</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdFreeOffer" xml:space="preserve">
|
<data name="AdFreeOffer" xml:space="preserve">
|
||||||
<value>Login = 30 dias sem anúncios!</value>
|
<value>Unlock all premium features!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpecialOffer" xml:space="preserve">
|
<data name="SpecialOffer" xml:space="preserve">
|
||||||
<value>Oferta Especial!</value>
|
<value>Premium Offer!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginBenefits" xml:space="preserve">
|
<data name="LoginBenefits" xml:space="preserve">
|
||||||
<value>Ao fazer login, você ganha automaticamente 30 dias sem anúncios e pode gerar até 50 QR codes por dia gratuitamente.</value>
|
<value>Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Privacy" xml:space="preserve">
|
<data name="Privacy" xml:space="preserve">
|
||||||
<value>Política de Privacidade</value>
|
<value>Privacy Policy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BackToGenerator" xml:space="preserve">
|
<data name="BackToGenerator" xml:space="preserve">
|
||||||
<value>Voltar ao gerador</value>
|
<value>Back to generator</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GeneratedIn" xml:space="preserve">
|
<data name="GeneratedIn" xml:space="preserve">
|
||||||
<value>Gerado em</value>
|
<value>Generated in</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Seconds" xml:space="preserve">
|
<data name="Seconds" xml:space="preserve">
|
||||||
<value>s</value>
|
<value>s</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UltraFast" xml:space="preserve">
|
<data name="UltraFast" xml:space="preserve">
|
||||||
<value>Geração ultra rápida!</value>
|
<value>Ultra fast generation!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Fast" xml:space="preserve">
|
<data name="Fast" xml:space="preserve">
|
||||||
<value>Geração rápida!</value>
|
<value>Fast generation!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Normal" xml:space="preserve">
|
<data name="Normal" xml:space="preserve">
|
||||||
<value>Geração normal</value>
|
<value>Normal generation</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error" xml:space="preserve">
|
<data name="Error" xml:space="preserve">
|
||||||
<value>Erro na geração. Tente novamente.</value>
|
<value>Generation error. Try again.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Success" xml:space="preserve">
|
<data name="Success" xml:space="preserve">
|
||||||
<value>QR Code salvo no histórico!</value>
|
<value>QR Code saved to history!</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -59,13 +59,13 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Tagline" xml:space="preserve">
|
<data name="Tagline" xml:space="preserve">
|
||||||
<value>¡Genera códigos QR en segundos!</value>
|
<value>Genera codigos QR en segundos!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GenerateQR" xml:space="preserve">
|
<data name="GenerateQR" xml:space="preserve">
|
||||||
<value>Generar Código QR</value>
|
<value>Generar Codigo QR</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="QRType" xml:space="preserve">
|
<data name="QRType" xml:space="preserve">
|
||||||
<value>Tipo de Código QR</value>
|
<value>Tipo de Codigo QR</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Content" xml:space="preserve">
|
<data name="Content" xml:space="preserve">
|
||||||
<value>Contenido</value>
|
<value>Contenido</value>
|
||||||
@ -89,13 +89,13 @@
|
|||||||
<value>Email</value>
|
<value>Email</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DynamicType" xml:space="preserve">
|
<data name="DynamicType" xml:space="preserve">
|
||||||
<value>QR Dinámico (Premium)</value>
|
<value>QR Dinamico (Premium)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="QuickStyle" xml:space="preserve">
|
<data name="QuickStyle" xml:space="preserve">
|
||||||
<value>Estilo Rápido</value>
|
<value>Estilo Rapido</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClassicStyle" xml:space="preserve">
|
<data name="ClassicStyle" xml:space="preserve">
|
||||||
<value>Clásico</value>
|
<value>Clasico</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ModernStyle" xml:space="preserve">
|
<data name="ModernStyle" xml:space="preserve">
|
||||||
<value>Moderno</value>
|
<value>Moderno</value>
|
||||||
@ -104,10 +104,10 @@
|
|||||||
<value>Colorido</value>
|
<value>Colorido</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ContentPlaceholder" xml:space="preserve">
|
<data name="ContentPlaceholder" xml:space="preserve">
|
||||||
<value>Escribe el contenido de tu código QR aquí...</value>
|
<value>Ingrese el contenido de su codigo QR aqui...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedCustomization" xml:space="preserve">
|
<data name="AdvancedCustomization" xml:space="preserve">
|
||||||
<value>Personalización Avanzada</value>
|
<value>Personalizacion Avanzada</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PrimaryColor" xml:space="preserve">
|
<data name="PrimaryColor" xml:space="preserve">
|
||||||
<value>Color Principal</value>
|
<value>Color Principal</value>
|
||||||
@ -116,7 +116,7 @@
|
|||||||
<value>Color de Fondo</value>
|
<value>Color de Fondo</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Size" xml:space="preserve">
|
<data name="Size" xml:space="preserve">
|
||||||
<value>Tamaño</value>
|
<value>Tamano</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Margin" xml:space="preserve">
|
<data name="Margin" xml:space="preserve">
|
||||||
<value>Margen</value>
|
<value>Margen</value>
|
||||||
@ -125,19 +125,19 @@
|
|||||||
<value>Logo/Icono</value>
|
<value>Logo/Icono</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CornerStyle" xml:space="preserve">
|
<data name="CornerStyle" xml:space="preserve">
|
||||||
<value>Estilo de Bordes</value>
|
<value>Estilo de Esquinas</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GenerateRapidly" xml:space="preserve">
|
<data name="GenerateRapidly" xml:space="preserve">
|
||||||
<value>Generar Código QR Rápidamente</value>
|
<value>Generar Codigo QR Rapidamente</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Preview" xml:space="preserve">
|
<data name="Preview" xml:space="preserve">
|
||||||
<value>Vista Previa</value>
|
<value>Vista Previa</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PreviewPlaceholder" xml:space="preserve">
|
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||||
<value>Tu código QR aparecerá aquí en segundos</value>
|
<value>Su codigo QR aparecera aqui en segundos</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UltraFastGeneration" xml:space="preserve">
|
<data name="UltraFastGeneration" xml:space="preserve">
|
||||||
<value>Generación ultra-rápida garantizada</value>
|
<value>Generacion ultra-rapida garantizada</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DownloadPNG" xml:space="preserve">
|
<data name="DownloadPNG" xml:space="preserve">
|
||||||
<value>Descargar PNG</value>
|
<value>Descargar PNG</value>
|
||||||
@ -152,31 +152,31 @@
|
|||||||
<value>Guardar en Historial</value>
|
<value>Guardar en Historial</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginToSave" xml:space="preserve">
|
<data name="LoginToSave" xml:space="preserve">
|
||||||
<value>Inicia sesión para guardar en el historial</value>
|
<value>Inicie sesion para guardar en el historial</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumTitle" xml:space="preserve">
|
<data name="PremiumTitle" xml:space="preserve">
|
||||||
<value>QR Rapido Premium</value>
|
<value>QR Rapido Premium</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||||
<value>Consejos para QR Más Rápidos</value>
|
<value>Consejos para QR Mas Rapidos</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip1" xml:space="preserve">
|
<data name="SpeedTip1" xml:space="preserve">
|
||||||
<value>URLs cortas se generan más rápido</value>
|
<value>URLs cortas se generan mas rapido</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip2" xml:space="preserve">
|
<data name="SpeedTip2" xml:space="preserve">
|
||||||
<value>Menos texto = mayor velocidad</value>
|
<value>Menos texto = mayor velocidad</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip3" xml:space="preserve">
|
<data name="SpeedTip3" xml:space="preserve">
|
||||||
<value>Colores sólidos optimizan el proceso</value>
|
<value>Colores solidos optimizan el proceso</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedTip4" xml:space="preserve">
|
<data name="SpeedTip4" xml:space="preserve">
|
||||||
<value>Tamaños menores aceleran la descarga</value>
|
<value>Tamanos menores aceleran la descarga</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Login" xml:space="preserve">
|
<data name="Login" xml:space="preserve">
|
||||||
<value>Iniciar Sesión</value>
|
<value>Iniciar Sesion</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginWith" xml:space="preserve">
|
<data name="LoginWith" xml:space="preserve">
|
||||||
<value>Iniciar sesión con</value>
|
<value>Iniciar con</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Google" xml:space="preserve">
|
<data name="Google" xml:space="preserve">
|
||||||
<value>Google</value>
|
<value>Google</value>
|
||||||
@ -185,16 +185,16 @@
|
|||||||
<value>Microsoft</value>
|
<value>Microsoft</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdFreeOffer" xml:space="preserve">
|
<data name="AdFreeOffer" xml:space="preserve">
|
||||||
<value>¡Login = 30 días sin anuncios!</value>
|
<value>Desbloquea todas las funciones premium!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpecialOffer" xml:space="preserve">
|
<data name="SpecialOffer" xml:space="preserve">
|
||||||
<value>¡Oferta Especial!</value>
|
<value>Oferta Premium!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginBenefits" xml:space="preserve">
|
<data name="LoginBenefits" xml:space="preserve">
|
||||||
<value>Al iniciar sesión, ganas automáticamente 30 días sin anuncios y puedes generar hasta 50 códigos QR por día gratis.</value>
|
<value>Elimina anuncios, accede a analisis detallados y mucho mas por solo $12.90/mes o $129.00/ano. Inicia sesion y suscribete ahora!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Privacy" xml:space="preserve">
|
<data name="Privacy" xml:space="preserve">
|
||||||
<value>Política de Privacidad</value>
|
<value>Politica de Privacidad</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BackToGenerator" xml:space="preserve">
|
<data name="BackToGenerator" xml:space="preserve">
|
||||||
<value>Volver al generador</value>
|
<value>Volver al generador</value>
|
||||||
@ -206,18 +206,66 @@
|
|||||||
<value>s</value>
|
<value>s</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UltraFast" xml:space="preserve">
|
<data name="UltraFast" xml:space="preserve">
|
||||||
<value>¡Generación ultra rápida!</value>
|
<value>Generacion ultra rapida!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Fast" xml:space="preserve">
|
<data name="Fast" xml:space="preserve">
|
||||||
<value>¡Generación rápida!</value>
|
<value>Generacion rapida!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Normal" xml:space="preserve">
|
<data name="Normal" xml:space="preserve">
|
||||||
<value>Generación normal</value>
|
<value>Generacion normal</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error" xml:space="preserve">
|
<data name="Error" xml:space="preserve">
|
||||||
<value>Error en la generación. Inténtalo de nuevo.</value>
|
<value>Error en la generacion. Intentelo de nuevo.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Success" xml:space="preserve">
|
<data name="Success" xml:space="preserve">
|
||||||
<value>¡Código QR guardado en el historial!</value>
|
<value>Codigo QR guardado en el historial!</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateQRCodeQuickly" xml:space="preserve">
|
||||||
|
<value>Crear Codigo QR Rapidamente</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumUserActive" xml:space="preserve">
|
||||||
|
<value>Usuario Premium Activo</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
|
||||||
|
<value>Sin Anuncios • Historial • QR Ilimitado</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnlimitedToday" xml:space="preserve">
|
||||||
|
<value>Ilimitado hoy</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodesRemaining" xml:space="preserve">
|
||||||
|
<value>Codigos QR restantes</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeType" xml:space="preserve">
|
||||||
|
<value>Tipo de Codigo QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectType" xml:space="preserve">
|
||||||
|
<value>Seleccionar tipo</value>
|
||||||
|
</data>
|
||||||
|
<data name="URLLink" xml:space="preserve">
|
||||||
|
<value>URL/Enlace</value>
|
||||||
|
</data>
|
||||||
|
<data name="SimpleText" xml:space="preserve">
|
||||||
|
<value>Texto Simple</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCard" xml:space="preserve">
|
||||||
|
<value>Tarjeta de Visita</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicQRPremium" xml:space="preserve">
|
||||||
|
<value>QR Dinamico (Premium)</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRCodeContent" xml:space="preserve">
|
||||||
|
<value>Ingrese el contenido de su codigo QR aqui...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentHints" xml:space="preserve">
|
||||||
|
<value>Sugerencias de contenido</value>
|
||||||
|
</data>
|
||||||
|
<data name="Classic" xml:space="preserve">
|
||||||
|
<value>Clasico</value>
|
||||||
|
</data>
|
||||||
|
<data name="Modern" xml:space="preserve">
|
||||||
|
<value>Moderno</value>
|
||||||
|
</data>
|
||||||
|
<data name="Colorful" xml:space="preserve">
|
||||||
|
<value>Colorido</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
271
Resources/SharedResource.pt-BR.resx
Normal file
271
Resources/SharedResource.pt-BR.resx
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Tagline" xml:space="preserve">
|
||||||
|
<value>Gere QR codes em segundos!</value>
|
||||||
|
</data>
|
||||||
|
<data name="GenerateQR" xml:space="preserve">
|
||||||
|
<value>Gerar QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRType" xml:space="preserve">
|
||||||
|
<value>Tipo de QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="Content" xml:space="preserve">
|
||||||
|
<value>Conteudo</value>
|
||||||
|
</data>
|
||||||
|
<data name="URLType" xml:space="preserve">
|
||||||
|
<value>URL/Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextType" xml:space="preserve">
|
||||||
|
<value>Texto Simples</value>
|
||||||
|
</data>
|
||||||
|
<data name="WiFiType" xml:space="preserve">
|
||||||
|
<value>WiFi</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCardType" xml:space="preserve">
|
||||||
|
<value>Cartao de Visita</value>
|
||||||
|
</data>
|
||||||
|
<data name="SMSType" xml:space="preserve">
|
||||||
|
<value>SMS</value>
|
||||||
|
</data>
|
||||||
|
<data name="EmailType" xml:space="preserve">
|
||||||
|
<value>Email</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicType" xml:space="preserve">
|
||||||
|
<value>QR Dinamico (Premium)</value>
|
||||||
|
</data>
|
||||||
|
<data name="QuickStyle" xml:space="preserve">
|
||||||
|
<value>Estilo Rapido</value>
|
||||||
|
</data>
|
||||||
|
<data name="ClassicStyle" xml:space="preserve">
|
||||||
|
<value>Classico</value>
|
||||||
|
</data>
|
||||||
|
<data name="ModernStyle" xml:space="preserve">
|
||||||
|
<value>Moderno</value>
|
||||||
|
</data>
|
||||||
|
<data name="ColorfulStyle" xml:space="preserve">
|
||||||
|
<value>Colorido</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentPlaceholder" xml:space="preserve">
|
||||||
|
<value>Digite o conteudo do seu QR code aqui...</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedCustomization" xml:space="preserve">
|
||||||
|
<value>Personalizacao Avancada</value>
|
||||||
|
</data>
|
||||||
|
<data name="PrimaryColor" xml:space="preserve">
|
||||||
|
<value>Cor Principal</value>
|
||||||
|
</data>
|
||||||
|
<data name="BackgroundColor" xml:space="preserve">
|
||||||
|
<value>Cor de Fundo</value>
|
||||||
|
</data>
|
||||||
|
<data name="Size" xml:space="preserve">
|
||||||
|
<value>Tamanho</value>
|
||||||
|
</data>
|
||||||
|
<data name="Margin" xml:space="preserve">
|
||||||
|
<value>Margem</value>
|
||||||
|
</data>
|
||||||
|
<data name="Logo" xml:space="preserve">
|
||||||
|
<value>Logo/Icone</value>
|
||||||
|
</data>
|
||||||
|
<data name="CornerStyle" xml:space="preserve">
|
||||||
|
<value>Estilo das Bordas</value>
|
||||||
|
</data>
|
||||||
|
<data name="GenerateRapidly" xml:space="preserve">
|
||||||
|
<value>Gerar QR Code Rapidamente</value>
|
||||||
|
</data>
|
||||||
|
<data name="Preview" xml:space="preserve">
|
||||||
|
<value>Preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||||
|
<value>Seu QR code aparecera aqui em segundos</value>
|
||||||
|
</data>
|
||||||
|
<data name="UltraFastGeneration" xml:space="preserve">
|
||||||
|
<value>Geracao ultra-rapida garantida</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadPNG" xml:space="preserve">
|
||||||
|
<value>Download PNG</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadSVG" xml:space="preserve">
|
||||||
|
<value>Download SVG (Vetorial)</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadPDF" xml:space="preserve">
|
||||||
|
<value>Download PDF</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveToHistory" xml:space="preserve">
|
||||||
|
<value>Salvar no Historico</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginToSave" xml:space="preserve">
|
||||||
|
<value>Faca login para salvar no historico</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumTitle" xml:space="preserve">
|
||||||
|
<value>QR Rapido Premium</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||||
|
<value>Dicas para QR Mais Rapidos</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip1" xml:space="preserve">
|
||||||
|
<value>URLs curtas geram mais rapido</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip2" xml:space="preserve">
|
||||||
|
<value>Menos texto = maior velocidade</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip3" xml:space="preserve">
|
||||||
|
<value>Cores solidas otimizam o processo</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip4" xml:space="preserve">
|
||||||
|
<value>Tamanhos menores aceleram o download</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginWith" xml:space="preserve">
|
||||||
|
<value>Entrar com</value>
|
||||||
|
</data>
|
||||||
|
<data name="Google" xml:space="preserve">
|
||||||
|
<value>Google</value>
|
||||||
|
</data>
|
||||||
|
<data name="Microsoft" xml:space="preserve">
|
||||||
|
<value>Microsoft</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdFreeOffer" xml:space="preserve">
|
||||||
|
<value>Desbloqueie todos os recursos premium!</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpecialOffer" xml:space="preserve">
|
||||||
|
<value>Oferta Premium!</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginBenefits" xml:space="preserve">
|
||||||
|
<value>Remova anuncios, acesse analytics detalhados e muito mais por apenas R$ 12,90/mes ou R$ 129,00/ano. Faca login e assine agora!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Privacy" xml:space="preserve">
|
||||||
|
<value>Politica de Privacidade</value>
|
||||||
|
</data>
|
||||||
|
<data name="BackToGenerator" xml:space="preserve">
|
||||||
|
<value>Voltar ao gerador</value>
|
||||||
|
</data>
|
||||||
|
<data name="GeneratedIn" xml:space="preserve">
|
||||||
|
<value>Gerado em</value>
|
||||||
|
</data>
|
||||||
|
<data name="Seconds" xml:space="preserve">
|
||||||
|
<value>s</value>
|
||||||
|
</data>
|
||||||
|
<data name="UltraFast" xml:space="preserve">
|
||||||
|
<value>Geracao ultra rapida!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fast" xml:space="preserve">
|
||||||
|
<value>Geracao rapida!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Normal" xml:space="preserve">
|
||||||
|
<value>Geracao normal</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Erro na geracao. Tente novamente.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success" xml:space="preserve">
|
||||||
|
<value>QR Code salvo no historico!</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateQRCodeQuickly" xml:space="preserve">
|
||||||
|
<value>Criar QR Code Rapidamente</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumUserActive" xml:space="preserve">
|
||||||
|
<value>Usuario Premium Ativo</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
|
||||||
|
<value>Sem Anuncios • Historico • QR Ilimitado</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnlimitedToday" xml:space="preserve">
|
||||||
|
<value>Ilimitado hoje</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodesRemaining" xml:space="preserve">
|
||||||
|
<value>QR codes restantes</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeType" xml:space="preserve">
|
||||||
|
<value>Tipo de QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectType" xml:space="preserve">
|
||||||
|
<value>Selecionar tipo</value>
|
||||||
|
</data>
|
||||||
|
<data name="URLLink" xml:space="preserve">
|
||||||
|
<value>URL/Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="SimpleText" xml:space="preserve">
|
||||||
|
<value>Texto Simples</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCard" xml:space="preserve">
|
||||||
|
<value>Cartao de Visita</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicQRPremium" xml:space="preserve">
|
||||||
|
<value>QR Dinamico (Premium)</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRCodeContent" xml:space="preserve">
|
||||||
|
<value>Digite o conteudo do seu QR code aqui...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentHints" xml:space="preserve">
|
||||||
|
<value>Dicas de conteudo</value>
|
||||||
|
</data>
|
||||||
|
<data name="Classic" xml:space="preserve">
|
||||||
|
<value>Classico</value>
|
||||||
|
</data>
|
||||||
|
<data name="Modern" xml:space="preserve">
|
||||||
|
<value>Moderno</value>
|
||||||
|
</data>
|
||||||
|
<data name="Colorful" xml:space="preserve">
|
||||||
|
<value>Colorido</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
271
Resources/SharedResource.resx
Normal file
271
Resources/SharedResource.resx
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Tagline" xml:space="preserve">
|
||||||
|
<value>Generate QR codes in seconds!</value>
|
||||||
|
</data>
|
||||||
|
<data name="GenerateQR" xml:space="preserve">
|
||||||
|
<value>Generate QR Code</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRType" xml:space="preserve">
|
||||||
|
<value>QR Code Type</value>
|
||||||
|
</data>
|
||||||
|
<data name="Content" xml:space="preserve">
|
||||||
|
<value>Content</value>
|
||||||
|
</data>
|
||||||
|
<data name="URLType" xml:space="preserve">
|
||||||
|
<value>URL/Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextType" xml:space="preserve">
|
||||||
|
<value>Plain Text</value>
|
||||||
|
</data>
|
||||||
|
<data name="WiFiType" xml:space="preserve">
|
||||||
|
<value>WiFi</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCardType" xml:space="preserve">
|
||||||
|
<value>Business Card</value>
|
||||||
|
</data>
|
||||||
|
<data name="SMSType" xml:space="preserve">
|
||||||
|
<value>SMS</value>
|
||||||
|
</data>
|
||||||
|
<data name="EmailType" xml:space="preserve">
|
||||||
|
<value>Email</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicType" xml:space="preserve">
|
||||||
|
<value>Dynamic QR (Premium)</value>
|
||||||
|
</data>
|
||||||
|
<data name="QuickStyle" xml:space="preserve">
|
||||||
|
<value>Quick Style</value>
|
||||||
|
</data>
|
||||||
|
<data name="ClassicStyle" xml:space="preserve">
|
||||||
|
<value>Classic</value>
|
||||||
|
</data>
|
||||||
|
<data name="ModernStyle" xml:space="preserve">
|
||||||
|
<value>Modern</value>
|
||||||
|
</data>
|
||||||
|
<data name="ColorfulStyle" xml:space="preserve">
|
||||||
|
<value>Colorful</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentPlaceholder" xml:space="preserve">
|
||||||
|
<value>Enter your QR code content here...</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedCustomization" xml:space="preserve">
|
||||||
|
<value>Advanced Customization</value>
|
||||||
|
</data>
|
||||||
|
<data name="PrimaryColor" xml:space="preserve">
|
||||||
|
<value>Primary Color</value>
|
||||||
|
</data>
|
||||||
|
<data name="BackgroundColor" xml:space="preserve">
|
||||||
|
<value>Background Color</value>
|
||||||
|
</data>
|
||||||
|
<data name="Size" xml:space="preserve">
|
||||||
|
<value>Size</value>
|
||||||
|
</data>
|
||||||
|
<data name="Margin" xml:space="preserve">
|
||||||
|
<value>Margin</value>
|
||||||
|
</data>
|
||||||
|
<data name="Logo" xml:space="preserve">
|
||||||
|
<value>Logo/Icon</value>
|
||||||
|
</data>
|
||||||
|
<data name="CornerStyle" xml:space="preserve">
|
||||||
|
<value>Corner Style</value>
|
||||||
|
</data>
|
||||||
|
<data name="GenerateRapidly" xml:space="preserve">
|
||||||
|
<value>Generate QR Code Rapidly</value>
|
||||||
|
</data>
|
||||||
|
<data name="Preview" xml:space="preserve">
|
||||||
|
<value>Preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||||
|
<value>Your QR code will appear here in seconds</value>
|
||||||
|
</data>
|
||||||
|
<data name="UltraFastGeneration" xml:space="preserve">
|
||||||
|
<value>Ultra-fast generation guaranteed</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadPNG" xml:space="preserve">
|
||||||
|
<value>Download PNG</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadSVG" xml:space="preserve">
|
||||||
|
<value>Download SVG (Vector)</value>
|
||||||
|
</data>
|
||||||
|
<data name="DownloadPDF" xml:space="preserve">
|
||||||
|
<value>Download PDF</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveToHistory" xml:space="preserve">
|
||||||
|
<value>Save to History</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginToSave" xml:space="preserve">
|
||||||
|
<value>Login to save to history</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumTitle" xml:space="preserve">
|
||||||
|
<value>QR Rapido Premium</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||||
|
<value>Tips for Faster QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip1" xml:space="preserve">
|
||||||
|
<value>Short URLs generate faster</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip2" xml:space="preserve">
|
||||||
|
<value>Less text = higher speed</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip3" xml:space="preserve">
|
||||||
|
<value>Solid colors optimize the process</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedTip4" xml:space="preserve">
|
||||||
|
<value>Smaller sizes speed up downloads</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Login</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginWith" xml:space="preserve">
|
||||||
|
<value>Login with</value>
|
||||||
|
</data>
|
||||||
|
<data name="Google" xml:space="preserve">
|
||||||
|
<value>Google</value>
|
||||||
|
</data>
|
||||||
|
<data name="Microsoft" xml:space="preserve">
|
||||||
|
<value>Microsoft</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdFreeOffer" xml:space="preserve">
|
||||||
|
<value>Unlock all premium features!</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpecialOffer" xml:space="preserve">
|
||||||
|
<value>Premium Offer!</value>
|
||||||
|
</data>
|
||||||
|
<data name="LoginBenefits" xml:space="preserve">
|
||||||
|
<value>Remove ads, access detailed analytics and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Privacy" xml:space="preserve">
|
||||||
|
<value>Privacy Policy</value>
|
||||||
|
</data>
|
||||||
|
<data name="BackToGenerator" xml:space="preserve">
|
||||||
|
<value>Back to generator</value>
|
||||||
|
</data>
|
||||||
|
<data name="GeneratedIn" xml:space="preserve">
|
||||||
|
<value>Generated in</value>
|
||||||
|
</data>
|
||||||
|
<data name="Seconds" xml:space="preserve">
|
||||||
|
<value>s</value>
|
||||||
|
</data>
|
||||||
|
<data name="UltraFast" xml:space="preserve">
|
||||||
|
<value>Ultra fast generation!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Fast" xml:space="preserve">
|
||||||
|
<value>Fast generation!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Normal" xml:space="preserve">
|
||||||
|
<value>Normal generation</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Generation error. Try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success" xml:space="preserve">
|
||||||
|
<value>QR Code saved to history!</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateQRCodeQuickly" xml:space="preserve">
|
||||||
|
<value>Create QR Code Quickly</value>
|
||||||
|
</data>
|
||||||
|
<data name="PremiumUserActive" xml:space="preserve">
|
||||||
|
<value>Premium User Active</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
|
||||||
|
<value>No Ads • History • Unlimited QR</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnlimitedToday" xml:space="preserve">
|
||||||
|
<value>Unlimited today</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodesRemaining" xml:space="preserve">
|
||||||
|
<value>QR codes remaining</value>
|
||||||
|
</data>
|
||||||
|
<data name="QRCodeType" xml:space="preserve">
|
||||||
|
<value>QR Code Type</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectType" xml:space="preserve">
|
||||||
|
<value>Select type</value>
|
||||||
|
</data>
|
||||||
|
<data name="URLLink" xml:space="preserve">
|
||||||
|
<value>URL/Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="SimpleText" xml:space="preserve">
|
||||||
|
<value>Simple Text</value>
|
||||||
|
</data>
|
||||||
|
<data name="VCard" xml:space="preserve">
|
||||||
|
<value>Business Card</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicQRPremium" xml:space="preserve">
|
||||||
|
<value>Dynamic QR (Premium)</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnterQRCodeContent" xml:space="preserve">
|
||||||
|
<value>Enter your QR code content here...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ContentHints" xml:space="preserve">
|
||||||
|
<value>Content hints</value>
|
||||||
|
</data>
|
||||||
|
<data name="Classic" xml:space="preserve">
|
||||||
|
<value>Classic</value>
|
||||||
|
</data>
|
||||||
|
<data name="Modern" xml:space="preserve">
|
||||||
|
<value>Modern</value>
|
||||||
|
</data>
|
||||||
|
<data name="Colorful" xml:space="preserve">
|
||||||
|
<value>Colorful</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
14
Services/IPlanService.cs
Normal file
14
Services/IPlanService.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
using QRRapidoApp.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Services
|
||||||
|
{
|
||||||
|
public interface IPlanService
|
||||||
|
{
|
||||||
|
Task<List<Plan>> GetActivePlansAsync();
|
||||||
|
Task<List<Plan>> GetPlansByLanguageAsync(string languageCode, string countryCode = "BRL");
|
||||||
|
Task<Plan?> GetPlanByIdAsync(string id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,10 @@ namespace QRRapidoApp.Services
|
|||||||
Task<User?> GetUserByProviderAsync(string provider, string providerId);
|
Task<User?> GetUserByProviderAsync(string provider, string providerId);
|
||||||
Task<User> CreateUserAsync(string email, string name, string provider, string providerId);
|
Task<User> CreateUserAsync(string email, string name, string provider, string providerId);
|
||||||
Task UpdateLastLoginAsync(string userId);
|
Task UpdateLastLoginAsync(string userId);
|
||||||
|
Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate);
|
||||||
|
Task DeactivatePremiumStatus(string stripeSubscriptionId);
|
||||||
|
Task UpdateUserStripeCustomerIdAsync(string userId, string stripeCustomerId);
|
||||||
|
Task<User?> GetUserByStripeCustomerIdAsync(string customerId);
|
||||||
Task<bool> UpdateUserAsync(User user);
|
Task<bool> UpdateUserAsync(User user);
|
||||||
Task<int> GetDailyQRCountAsync(string? userId);
|
Task<int> GetDailyQRCountAsync(string? userId);
|
||||||
Task<int> DecrementDailyQRCountAsync(string userId);
|
Task<int> DecrementDailyQRCountAsync(string userId);
|
||||||
|
|||||||
35
Services/PlanService.cs
Normal file
35
Services/PlanService.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using QRRapidoApp.Data;
|
||||||
|
using QRRapidoApp.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Services
|
||||||
|
{
|
||||||
|
public class PlanService : IPlanService
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<Plan> _plans;
|
||||||
|
|
||||||
|
public PlanService(MongoDbContext context)
|
||||||
|
{
|
||||||
|
_plans = context.Plans;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Plan>> GetActivePlansAsync()
|
||||||
|
{
|
||||||
|
return await _plans.Find(p => p.IsActive).SortBy(p => p.PricesByCountry["BRL"].Amount).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Plan>> GetPlansByLanguageAsync(string languageCode, string countryCode = "BRL")
|
||||||
|
{
|
||||||
|
var plans = await _plans.Find(p => p.IsActive).SortBy(p => p.PricesByCountry[countryCode].Amount).ToListAsync();
|
||||||
|
return plans;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Plan?> GetPlanByIdAsync(string id)
|
||||||
|
{
|
||||||
|
return await _plans.Find(p => p.Id == id).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Services/RouteDataRequestCultureProvider.cs
Normal file
27
Services/RouteDataRequestCultureProvider.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Localization;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Services
|
||||||
|
{
|
||||||
|
public class RouteDataRequestCultureProvider : RequestCultureProvider
|
||||||
|
{
|
||||||
|
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (httpContext?.Request?.RouteValues == null)
|
||||||
|
return Task.FromResult<ProviderCultureResult?>(null);
|
||||||
|
|
||||||
|
var routeValues = httpContext.Request.RouteValues;
|
||||||
|
if (!routeValues.ContainsKey("culture"))
|
||||||
|
return Task.FromResult<ProviderCultureResult?>(null);
|
||||||
|
|
||||||
|
var culture = routeValues["culture"]?.ToString();
|
||||||
|
if (string.IsNullOrEmpty(culture))
|
||||||
|
return Task.FromResult<ProviderCultureResult?>(null);
|
||||||
|
|
||||||
|
var supportedCultures = new[] { "pt-BR", "es", "en" };
|
||||||
|
if (!supportedCultures.Contains(culture))
|
||||||
|
return Task.FromResult<ProviderCultureResult?>(null);
|
||||||
|
|
||||||
|
return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(culture));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,12 @@
|
|||||||
|
|
||||||
using Stripe;
|
using Stripe;
|
||||||
using Stripe.Checkout;
|
using Stripe.Checkout;
|
||||||
using QRRapidoApp.Models;
|
using QRRapidoApp.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace QRRapidoApp.Services
|
namespace QRRapidoApp.Services
|
||||||
{
|
{
|
||||||
@ -15,253 +21,128 @@ namespace QRRapidoApp.Services
|
|||||||
_config = config;
|
_config = config;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
StripeConfiguration.ApiKey = _config["Stripe:SecretKey"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
|
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
|
||||||
{
|
{
|
||||||
try
|
var user = await _userService.GetUserAsync(userId);
|
||||||
|
if (user == null)
|
||||||
{
|
{
|
||||||
var options = new SessionCreateOptions
|
throw new Exception("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var customerId = user.StripeCustomerId;
|
||||||
|
if (string.IsNullOrEmpty(customerId))
|
||||||
|
{
|
||||||
|
var customerOptions = new CustomerCreateOptions
|
||||||
{
|
{
|
||||||
PaymentMethodTypes = new List<string> { "card" },
|
Email = user.Email,
|
||||||
Mode = "subscription",
|
Name = user.Name,
|
||||||
LineItems = new List<SessionLineItemOptions>
|
Metadata = new Dictionary<string, string> { { "app_user_id", user.Id } }
|
||||||
{
|
|
||||||
new SessionLineItemOptions
|
|
||||||
{
|
|
||||||
Price = priceId,
|
|
||||||
Quantity = 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ClientReferenceId = userId,
|
|
||||||
SuccessUrl = $"{_config["App:BaseUrl"]}/Premium/Success?session_id={{CHECKOUT_SESSION_ID}}",
|
|
||||||
CancelUrl = $"{_config["App:BaseUrl"]}/Premium/Cancel",
|
|
||||||
CustomerEmail = await _userService.GetUserEmailAsync(userId),
|
|
||||||
AllowPromotionCodes = true,
|
|
||||||
BillingAddressCollection = "auto",
|
|
||||||
Metadata = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "userId", userId },
|
|
||||||
{ "product", "QR Rapido Premium" }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
var customerService = new CustomerService();
|
||||||
var service = new SessionService();
|
var customer = await customerService.CreateAsync(customerOptions);
|
||||||
var session = await service.CreateAsync(options);
|
customerId = customer.Id;
|
||||||
|
await _userService.UpdateUserStripeCustomerIdAsync(userId, customerId);
|
||||||
_logger.LogInformation($"Created Stripe checkout session for user {userId}: {session.Id}");
|
|
||||||
|
|
||||||
return session.Url;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
var options = new SessionCreateOptions
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"Error creating Stripe checkout session for user {userId}: {ex.Message}");
|
PaymentMethodTypes = new List<string> { "card" },
|
||||||
throw;
|
Mode = "subscription",
|
||||||
}
|
LineItems = new List<SessionLineItemOptions>
|
||||||
|
{
|
||||||
|
new SessionLineItemOptions { Price = priceId, Quantity = 1 }
|
||||||
|
},
|
||||||
|
Customer = customerId,
|
||||||
|
ClientReferenceId = userId,
|
||||||
|
SuccessUrl = $"{_config["App:BaseUrl"]}/Pagamento/Sucesso",
|
||||||
|
CancelUrl = $"{_config["App:BaseUrl"]}/Pagamento/Cancelar",
|
||||||
|
AllowPromotionCodes = true,
|
||||||
|
Metadata = new Dictionary<string, string> { { "user_id", userId } }
|
||||||
|
};
|
||||||
|
|
||||||
|
var service = new SessionService();
|
||||||
|
var session = await service.CreateAsync(options);
|
||||||
|
_logger.LogInformation($"Created Stripe checkout session {session.Id} for user {userId}");
|
||||||
|
return session.Url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleWebhookAsync(string json, string signature)
|
public async Task HandleWebhookAsync(string json, string signature)
|
||||||
{
|
{
|
||||||
var webhookSecret = _config["Stripe:WebhookSecret"];
|
var webhookSecret = _config["Stripe:WebhookSecret"];
|
||||||
|
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
|
||||||
|
|
||||||
try
|
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
|
||||||
|
|
||||||
|
switch (stripeEvent.Type)
|
||||||
{
|
{
|
||||||
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
|
case Events.CheckoutSessionCompleted:
|
||||||
|
var session = stripeEvent.Data.Object as Session;
|
||||||
|
if (session?.SubscriptionId != null)
|
||||||
|
{
|
||||||
|
var subscriptionService = new SubscriptionService();
|
||||||
|
var subscription = await subscriptionService.GetAsync(session.SubscriptionId);
|
||||||
|
await ProcessSubscriptionActivation(session.ClientReferenceId, subscription);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
|
case Events.InvoicePaymentSucceeded:
|
||||||
|
var invoice = stripeEvent.Data.Object as Invoice;
|
||||||
switch (stripeEvent.Type)
|
if (invoice?.SubscriptionId != null)
|
||||||
{
|
{
|
||||||
case "checkout.session.completed":
|
var subscriptionService = new SubscriptionService();
|
||||||
var session = stripeEvent.Data.Object as Session;
|
var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);
|
||||||
if (session != null)
|
var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId);
|
||||||
|
if (user != null)
|
||||||
{
|
{
|
||||||
await ActivatePremiumAsync(session.ClientReferenceId, session.CustomerId, session.SubscriptionId);
|
await ProcessSubscriptionActivation(user.Id, subscription);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "invoice.payment_succeeded":
|
case Events.CustomerSubscriptionDeleted:
|
||||||
var invoice = stripeEvent.Data.Object as Invoice;
|
var deletedSubscription = stripeEvent.Data.Object as Subscription;
|
||||||
if (invoice != null && invoice.SubscriptionId != null)
|
if (deletedSubscription != null)
|
||||||
{
|
{
|
||||||
await RenewPremiumSubscriptionAsync(invoice.SubscriptionId);
|
await _userService.DeactivatePremiumStatus(deletedSubscription.Id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "invoice.payment_failed":
|
default:
|
||||||
var failedInvoice = stripeEvent.Data.Object as Invoice;
|
_logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
|
||||||
if (failedInvoice != null && failedInvoice.SubscriptionId != null)
|
break;
|
||||||
{
|
|
||||||
await HandleFailedPaymentAsync(failedInvoice.SubscriptionId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "customer.subscription.deleted":
|
|
||||||
var deletedSubscription = stripeEvent.Data.Object as Subscription;
|
|
||||||
if (deletedSubscription != null)
|
|
||||||
{
|
|
||||||
await DeactivatePremiumAsync(deletedSubscription);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "customer.subscription.updated":
|
|
||||||
var updatedSubscription = stripeEvent.Data.Object as Subscription;
|
|
||||||
if (updatedSubscription != null)
|
|
||||||
{
|
|
||||||
await UpdateSubscriptionAsync(updatedSubscription);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
_logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (StripeException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Stripe webhook error: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error processing Stripe webhook: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ActivatePremiumAsync(string? userId, string? customerId, string? subscriptionId)
|
private async Task ProcessSubscriptionActivation(string userId, Subscription subscription)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(userId)) return;
|
if (string.IsNullOrEmpty(userId) || subscription == null)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserAsync(userId);
|
_logger.LogWarning("Could not process subscription activation due to missing userId or subscription data.");
|
||||||
if (user == null)
|
return;
|
||||||
{
|
|
||||||
_logger.LogWarning($"User not found for premium activation: {userId}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.IsPremium = true;
|
|
||||||
user.StripeCustomerId = customerId;
|
|
||||||
user.StripeSubscriptionId = subscriptionId;
|
|
||||||
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32); // Buffer for billing cycles
|
|
||||||
|
|
||||||
await _userService.UpdateUserAsync(user);
|
|
||||||
|
|
||||||
_logger.LogInformation($"Activated premium for user {userId}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
var user = await _userService.GetUserAsync(userId);
|
||||||
|
if (user == null)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"Error activating premium for user {userId}: {ex.Message}");
|
_logger.LogWarning($"User not found for premium activation: {userId}");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RenewPremiumSubscriptionAsync(string subscriptionId)
|
if (string.IsNullOrEmpty(user.StripeCustomerId))
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Find user by subscription ID
|
await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId);
|
||||||
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
|
|
||||||
if (user == null) return;
|
|
||||||
|
|
||||||
// Extend premium expiry
|
|
||||||
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32);
|
|
||||||
await _userService.UpdateUserAsync(user);
|
|
||||||
|
|
||||||
_logger.LogInformation($"Renewed premium subscription for user {user.Id}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error renewing premium subscription {subscriptionId}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleFailedPaymentAsync(string subscriptionId)
|
await _userService.ActivatePremiumStatus(userId, subscription.Id, subscription.CurrentPeriodEnd);
|
||||||
{
|
_logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}.");
|
||||||
try
|
|
||||||
{
|
|
||||||
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
|
|
||||||
if (user == null) return;
|
|
||||||
|
|
||||||
// Don't immediately deactivate - Stripe will retry
|
|
||||||
_logger.LogWarning($"Payment failed for user {user.Id}, subscription {subscriptionId}");
|
|
||||||
|
|
||||||
// Could send notification email here
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error handling failed payment for subscription {subscriptionId}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeactivatePremiumAsync(Subscription subscription)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
|
|
||||||
if (user == null) return;
|
|
||||||
|
|
||||||
// ADICIONAR: marcar data de cancelamento
|
|
||||||
await _userService.MarkPremiumCancelledAsync(user.Id, DateTime.UtcNow);
|
|
||||||
|
|
||||||
_logger.LogInformation($"Deactivated premium for user {user.Id}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error deactivating premium for subscription {subscription.Id}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateSubscriptionAsync(Subscription subscription)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
|
|
||||||
if (user == null) return;
|
|
||||||
|
|
||||||
// Update based on subscription status
|
|
||||||
if (subscription.Status == "active")
|
|
||||||
{
|
|
||||||
user.IsPremium = true;
|
|
||||||
user.PremiumExpiresAt = subscription.CurrentPeriodEnd.AddDays(2); // Small buffer
|
|
||||||
}
|
|
||||||
else if (subscription.Status == "canceled" || subscription.Status == "unpaid")
|
|
||||||
{
|
|
||||||
user.IsPremium = false;
|
|
||||||
user.PremiumExpiresAt = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userService.UpdateUserAsync(user);
|
|
||||||
|
|
||||||
_logger.LogInformation($"Updated subscription for user {user.Id}: {subscription.Status}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error updating subscription {subscription.Id}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<User?> FindUserBySubscriptionIdAsync(string subscriptionId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// This would require implementing a method in UserService to find by subscription ID
|
|
||||||
// For now, we'll leave this as a placeholder
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error finding user by subscription ID {subscriptionId}: {ex.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId)
|
public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(subscriptionId))
|
if (string.IsNullOrEmpty(subscriptionId)) return "None";
|
||||||
return "None";
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var service = new SubscriptionService();
|
var service = new SubscriptionService();
|
||||||
@ -270,7 +151,7 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"Error getting subscription status for {subscriptionId}: {ex.Message}");
|
_logger.LogError(ex, $"Error getting subscription status for {subscriptionId}");
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,18 +161,13 @@ namespace QRRapidoApp.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var service = new SubscriptionService();
|
var service = new SubscriptionService();
|
||||||
var subscription = await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions
|
await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions());
|
||||||
{
|
_logger.LogInformation($"Canceled subscription {subscriptionId} via API.");
|
||||||
InvoiceNow = false,
|
|
||||||
Prorate = false
|
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation($"Canceled subscription {subscriptionId}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}: {ex.Message}");
|
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,9 +83,6 @@ namespace QRRapidoApp.Services
|
|||||||
await _context.Users.InsertOneAsync(user);
|
await _context.Users.InsertOneAsync(user);
|
||||||
_logger.LogInformation($"Created new user: {email} via {provider}");
|
_logger.LogInformation($"Created new user: {email} via {provider}");
|
||||||
|
|
||||||
// Create initial ad-free session for new users
|
|
||||||
await CreateAdFreeSessionAsync(user.Id, "Login");
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,9 +94,6 @@ namespace QRRapidoApp.Services
|
|||||||
.Set(u => u.LastLoginAt, DateTime.UtcNow);
|
.Set(u => u.LastLoginAt, DateTime.UtcNow);
|
||||||
|
|
||||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||||
|
|
||||||
// Create new ad-free session if needed
|
|
||||||
await CreateAdFreeSessionAsync(userId, "Login");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -352,31 +346,37 @@ namespace QRRapidoApp.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateAdFreeSessionAsync(string userId, string sessionType, int? customMinutes = null)
|
public async Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate)
|
||||||
{
|
{
|
||||||
try
|
var update = Builders<User>.Update
|
||||||
{
|
.Set(u => u.IsPremium, true)
|
||||||
var durationMinutes = customMinutes ?? _config.GetValue<int>("AdFree:LoginMinutes", 43200);
|
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
||||||
|
.Set(u => u.PremiumExpiresAt, expiryDate)
|
||||||
|
.Unset(u => u.PremiumCancelledAt);
|
||||||
|
|
||||||
var session = new AdFreeSession
|
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||||
{
|
_logger.LogInformation($"Activated premium for user {userId}");
|
||||||
UserId = userId,
|
}
|
||||||
StartedAt = DateTime.UtcNow,
|
|
||||||
ExpiresAt = DateTime.UtcNow.AddMinutes(durationMinutes),
|
|
||||||
IsActive = true,
|
|
||||||
SessionType = sessionType,
|
|
||||||
DurationMinutes = durationMinutes,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
await _context.AdFreeSessions.InsertOneAsync(session);
|
public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
|
||||||
|
{
|
||||||
|
var update = Builders<User>.Update
|
||||||
|
.Set(u => u.IsPremium, false)
|
||||||
|
.Set(u => u.PremiumCancelledAt, DateTime.UtcNow);
|
||||||
|
|
||||||
_logger.LogInformation($"Created {sessionType} ad-free session for user {userId} - {durationMinutes} minutes");
|
await _context.Users.UpdateOneAsync(u => u.StripeSubscriptionId == stripeSubscriptionId, update);
|
||||||
}
|
_logger.LogInformation($"Deactivated premium for subscription {stripeSubscriptionId}");
|
||||||
catch (Exception ex)
|
}
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"Error creating ad-free session for user {userId}: {ex.Message}");
|
public async Task<User?> GetUserByStripeCustomerIdAsync(string customerId)
|
||||||
}
|
{
|
||||||
|
return await _context.Users.Find(u => u.StripeCustomerId == customerId).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUserStripeCustomerIdAsync(string userId, string stripeCustomerId)
|
||||||
|
{
|
||||||
|
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
|
||||||
|
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
158
Views/Account/History.cshtml
Normal file
158
Views/Account/History.cshtml
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
@model List<QRRapidoApp.Models.QRCodeHistory>
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Histórico de QR Codes";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2><i class="fas fa-history text-primary"></i> Histórico de QR Codes</h2>
|
||||||
|
<p class="text-muted">Seus QR codes gerados ficam salvos aqui para download futuro</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus"></i> Gerar Novo QRCode
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model != null && Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var qr in Model)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<img src="data:image/png;base64,@qr.QRCodeBase64"
|
||||||
|
alt="QR Code"
|
||||||
|
class="img-fluid border"
|
||||||
|
style="max-width: 150px; max-height: 150px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<small class="text-muted">Tipo:</small>
|
||||||
|
<span class="badge bg-secondary">@qr.Type</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<small class="text-muted">Conteúdo:</small>
|
||||||
|
<p class="small mb-0" style="word-break: break-all;">
|
||||||
|
@if (qr.Content.Length > 50)
|
||||||
|
{
|
||||||
|
@(qr.Content.Substring(0, 50) + "...")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@qr.Content
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<small class="text-muted">Criado em:</small>
|
||||||
|
<br>
|
||||||
|
<small>@qr.CreatedAt.ToString("dd/MM/yyyy HH:mm")</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer bg-light">
|
||||||
|
<div class="btn-group w-100" role="group">
|
||||||
|
<a href="/api/QR/Download/@qr.Id?format=png"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
title="Download PNG">
|
||||||
|
<i class="fas fa-download"></i> PNG
|
||||||
|
</a>
|
||||||
|
<a href="/api/QR/Download/@qr.Id?format=svg"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
title="Download SVG">
|
||||||
|
<i class="fas fa-download"></i> SVG
|
||||||
|
</a>
|
||||||
|
<a href="/api/QR/Download/@qr.Id?format=pdf"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
title="Download PDF">
|
||||||
|
<i class="fas fa-download"></i> PDF
|
||||||
|
</a>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="regenerateQR('@qr.Id')"
|
||||||
|
title="Regenerar">
|
||||||
|
<i class="fas fa-redo"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.Count == 50)
|
||||||
|
{
|
||||||
|
<div class="alert alert-info text-center">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Mostrando os 50 QR codes mais recentes. Os mais antigos são removidos automaticamente.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-qrcode fa-4x text-muted mb-3"></i>
|
||||||
|
<h4 class="text-muted">Nenhum QR Code encontrado</h4>
|
||||||
|
<p class="text-muted">
|
||||||
|
Quando você gerar QR codes estando logado, eles aparecerão aqui para download futuro.
|
||||||
|
</p>
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus"></i> Gerar Primeiro QRCode
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function regenerateQR(qrId) {
|
||||||
|
// Get QR data from history and regenerate
|
||||||
|
fetch(`/api/QR/History`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const qrData = data.find(q => q.id === qrId);
|
||||||
|
if (qrData) {
|
||||||
|
// Parse customization settings and redirect to home with parameters
|
||||||
|
const settings = JSON.parse(qrData.customizationSettings || '{}');
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
content: qrData.content,
|
||||||
|
type: qrData.type,
|
||||||
|
size: settings.size || 300,
|
||||||
|
primaryColor: settings.primaryColor || '#000000',
|
||||||
|
backgroundColor: settings.backgroundColor || '#FFFFFF'
|
||||||
|
});
|
||||||
|
|
||||||
|
window.location.href = `/?${params.toString()}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error regenerating QR:', error);
|
||||||
|
alert('Erro ao regenerar QR Code. Tente novamente.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-refresh the page periodically to show new QR codes
|
||||||
|
setInterval(() => {
|
||||||
|
// Only refresh if user is still on this page and there are QR codes
|
||||||
|
if (document.visibilityState === 'visible' && document.querySelector('.card')) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}, 300000); // Refresh every 5 minutes
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -1,11 +1,15 @@
|
|||||||
@using QRRapidoApp.Services
|
@using QRRapidoApp.Services
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject AdDisplayService AdService
|
@inject AdDisplayService AdService
|
||||||
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Home";
|
ViewData["Title"] = "Home";
|
||||||
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
var userId = User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- QR Generator Form -->
|
<!-- QR Generator Form -->
|
||||||
@ -13,7 +17,7 @@
|
|||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-primary text-white">
|
<div class="card-header bg-primary text-white">
|
||||||
<h3 class="h5 mb-0">
|
<h3 class="h5 mb-0">
|
||||||
<i class="fas fa-qrcode"></i> Criar QR Code Rapidamente
|
<i class="fas fa-qrcode"></i> @Localizer["CreateQRCodeQuickly"]
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -25,8 +29,8 @@
|
|||||||
{
|
{
|
||||||
<div class="alert alert-success border-0">
|
<div class="alert alert-success border-0">
|
||||||
<i class="fas fa-crown text-warning"></i>
|
<i class="fas fa-crown text-warning"></i>
|
||||||
<strong>Usuário Premium ativo!</strong>
|
<strong>@Localizer["PremiumUserActive"]</strong>
|
||||||
<span class="badge bg-success">Sem anúncios + Histórico + QR ilimitados</span>
|
<span class="badge bg-success">@Localizer["NoAdsHistoryUnlimitedQR"]</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="speed-badge d-none">
|
<div class="speed-badge d-none">
|
||||||
<span class="badge bg-success">
|
<span class="badge bg-success">
|
||||||
<i class="fas fa-bolt"></i> Geração ultra rápida!
|
<i class="fas fa-bolt"></i> @Localizer["UltraFastGeneration"]
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,13 +55,13 @@
|
|||||||
@if (User.Identity.IsAuthenticated)
|
@if (User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<span class="qr-counter">Ilimitado hoje</span>
|
<span class="qr-counter">@Localizer["UnlimitedToday"]</span>
|
||||||
</small>
|
</small>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<span class="qr-counter">10 QR codes restantes</span>
|
<span class="qr-counter">@Localizer["QRCodesRemaining"]</span>
|
||||||
</small>
|
</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -66,50 +70,50 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-list"></i> Tipo de QR Code
|
<i class="fas fa-list"></i> @Localizer["QRCodeType"]
|
||||||
</label>
|
</label>
|
||||||
<select id="qr-type" class="form-select" required>
|
<select id="qr-type" class="form-select" required>
|
||||||
<option value="">Selecione o tipo...</option>
|
<option value="">@Localizer["SelectType"]</option>
|
||||||
<option value="url">🌐 URL/Link</option>
|
<option value="url">🌐 @Localizer["URLLink"]</option>
|
||||||
<option value="text">📝 Texto Simples</option>
|
<option value="text">📝 @Localizer["SimpleText"]</option>
|
||||||
<option value="wifi">📶 WiFi</option>
|
<option value="wifi">📶 @Localizer["WiFi"]</option>
|
||||||
<option value="vcard">👤 Cartão de Visita</option>
|
<option value="vcard">👤 @Localizer["VCard"]</option>
|
||||||
<option value="sms">💬 SMS</option>
|
<option value="sms">💬 @Localizer["SMS"]</option>
|
||||||
<option value="email">📧 Email</option>
|
<option value="email">📧 @Localizer["Email"]</option>
|
||||||
@if (User.Identity.IsAuthenticated)
|
@if (User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
<option value="dynamic">⚡ QR Dinâmico (Premium)</option>
|
<option value="dynamic">⚡ @Localizer["DynamicQRPremium"]</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-palette"></i> Estilo Rápido
|
<i class="fas fa-palette"></i> @Localizer["QuickStyle"]
|
||||||
</label>
|
</label>
|
||||||
<div class="btn-group w-100" role="group">
|
<div class="btn-group w-100" role="group">
|
||||||
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked>
|
<input type="radio" class="btn-check" name="quick-style" id="style-classic" value="classic" checked>
|
||||||
<label class="btn btn-outline-secondary" for="style-classic">Clássico</label>
|
<label class="btn btn-outline-secondary" for="style-classic">@Localizer["Classic"]</label>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern">
|
<input type="radio" class="btn-check" name="quick-style" id="style-modern" value="modern">
|
||||||
<label class="btn btn-outline-secondary" for="style-modern">Moderno</label>
|
<label class="btn btn-outline-secondary" for="style-modern">@Localizer["Modern"]</label>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful">
|
<input type="radio" class="btn-check" name="quick-style" id="style-colorful" value="colorful">
|
||||||
<label class="btn btn-outline-secondary" for="style-colorful">Colorido</label>
|
<label class="btn btn-outline-secondary" for="style-colorful">@Localizer["Colorful"]</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">
|
<label class="form-label fw-semibold">
|
||||||
<i class="fas fa-edit"></i> Conteúdo
|
<i class="fas fa-edit"></i> @Localizer["Content"]
|
||||||
</label>
|
</label>
|
||||||
<textarea id="qr-content"
|
<textarea id="qr-content"
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Digite o conteúdo do seu QR code aqui..."
|
placeholder="@Localizer["EnterQRCodeContent"]"
|
||||||
required></textarea>
|
required></textarea>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<span id="content-hints">Dicas aparecerão aqui baseadas no tipo selecionado</span>
|
<span id="content-hints">@Localizer["ContentHints"]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -118,22 +122,22 @@
|
|||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
||||||
<i class="fas fa-sliders-h me-2"></i> Personalização Avançada
|
<i class="fas fa-sliders-h me-2"></i> @Localizer["AdvancedCustomization"]
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="customization-panel" class="accordion-collapse collapse">
|
<div id="customization-panel" class="accordion-collapse collapse">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">Cor Principal</label>
|
<label class="form-label">@Localizer["PrimaryColor"]</label>
|
||||||
<input type="color" id="primary-color" class="form-control form-control-color" value="#007BFF">
|
<input type="color" id="primary-color" class="form-control form-control-color" value="#007BFF">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">Cor de Fundo</label>
|
<label class="form-label">@Localizer["BackgroundColor"]</label>
|
||||||
<input type="color" id="bg-color" class="form-control form-control-color" value="#FFFFFF">
|
<input type="color" id="bg-color" class="form-control form-control-color" value="#FFFFFF">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">Tamanho</label>
|
<label class="form-label">@Localizer["Size"]</label>
|
||||||
<select id="qr-size" class="form-select">
|
<select id="qr-size" class="form-select">
|
||||||
<option value="200">Pequeno (200px)</option>
|
<option value="200">Pequeno (200px)</option>
|
||||||
<option value="300" selected>Médio (300px)</option>
|
<option value="300" selected>Médio (300px)</option>
|
||||||
|
|||||||
12
Views/Pagamento/Cancelar.cshtml
Normal file
12
Views/Pagamento/Cancelar.cshtml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Pagamento Cancelado";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container text-center mt-5">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h4 class="alert-heading">Pagamento cancelado.</h4>
|
||||||
|
<p>@ViewBag.CancelMessage</p>
|
||||||
|
</div>
|
||||||
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-primary">Ver Planos</a>
|
||||||
|
</div>
|
||||||
116
Views/Pagamento/SelecaoPlano.cshtml
Normal file
116
Views/Pagamento/SelecaoPlano.cshtml
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
@model QRRapidoApp.Models.ViewModels.SelecaoPlanoViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Escolha seu Plano Premium";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
var monthlyPlan = Model.Plans.FirstOrDefault(p => p.Interval == "month");
|
||||||
|
var yearlyPlan = Model.Plans.FirstOrDefault(p => p.Interval == "year");
|
||||||
|
var monthlyPrice = monthlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
||||||
|
var yearlyPrice = yearlyPlan?.PricesByCountry.GetValueOrDefault(Model.CountryCode)?.Amount ?? 0;
|
||||||
|
var yearlySavings = (monthlyPrice * 12) - yearlyPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="display-4">Desbloqueie o Poder Total do QRRápido</h1>
|
||||||
|
<p class="lead text-muted">Acesso sem limites, sem anúncios e com recursos exclusivos para máxima produtividade.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center g-4">
|
||||||
|
<!-- Plano Mensal -->
|
||||||
|
@if (monthlyPlan != null)
|
||||||
|
{
|
||||||
|
<div class="col-lg-4 col-md-6">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h3 class="card-title text-center">Plano Mensal</h3>
|
||||||
|
<div class="text-center my-4">
|
||||||
|
<span class="display-4 fw-bold">R$ @monthlyPrice.ToString("0.00")</span>
|
||||||
|
<span class="text-muted">/mês</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-muted">Ideal para começar a explorar os recursos premium.</p>
|
||||||
|
<button class="btn btn-outline-primary mt-auto checkout-btn" data-plan-id="@monthlyPlan.Id">Assinar Agora</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Plano Anual -->
|
||||||
|
@if (yearlyPlan != null)
|
||||||
|
{
|
||||||
|
<div class="col-lg-4 col-md-6">
|
||||||
|
<div class="card h-100 shadow border-primary">
|
||||||
|
<div class="card-header bg-primary text-white text-center">
|
||||||
|
<h3 class="card-title mb-0">Plano Anual</h3>
|
||||||
|
<p class="mb-0">Recomendado</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="text-center my-4">
|
||||||
|
<span class="display-4 fw-bold">R$ @yearlyPrice.ToString("0.00")</span>
|
||||||
|
<span class="text-muted">/ano</span>
|
||||||
|
</div>
|
||||||
|
@if (yearlySavings > 0)
|
||||||
|
{
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<span class="badge bg-success">Economize R$ @yearlySavings.ToString("0.00")!</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<p class="text-center text-muted">O melhor custo-benefício para usuários frequentes.</p>
|
||||||
|
<button class="btn btn-primary mt-auto checkout-btn" data-plan-id="@yearlyPlan.Id">Assinar Plano Anual</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de Recursos -->
|
||||||
|
<div class="row justify-content-center mt-5">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<h3 class="text-center mb-4">Todos os planos incluem:</h3>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>QR codes ilimitados</li>
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Sem anúncios</li>
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>QR codes dinâmicos</li>
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Analytics em tempo real</li>
|
||||||
|
<li class="list-group-item border-0"><i class="fas fa-check-circle text-success me-2"></i>Suporte prioritário</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.checkout-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', async function() {
|
||||||
|
const planId = this.dataset.planId;
|
||||||
|
this.disabled = true;
|
||||||
|
this.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Redirecionando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/Pagamento/CreateCheckout', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: `planId=${planId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
window.location.href = result.url;
|
||||||
|
} else {
|
||||||
|
alert('Erro: ' + result.error);
|
||||||
|
this.disabled = false;
|
||||||
|
this.innerHTML = 'Assinar Agora'; // Reset button text
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Checkout error:', error);
|
||||||
|
alert('Ocorreu um erro ao iniciar o pagamento. Tente novamente.');
|
||||||
|
this.disabled = false;
|
||||||
|
this.innerHTML = 'Assinar Agora'; // Reset button text
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
12
Views/Pagamento/Sucesso.cshtml
Normal file
12
Views/Pagamento/Sucesso.cshtml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Sucesso";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container text-center mt-5">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<h4 class="alert-heading">Pagamento bem-sucedido!</h4>
|
||||||
|
<p>@ViewBag.SuccessMessage</p>
|
||||||
|
</div>
|
||||||
|
<a href="/" class="btn btn-primary">Voltar ao Início</a>
|
||||||
|
</div>
|
||||||
@ -1,6 +1,8 @@
|
|||||||
@using QRRapidoApp.Services
|
@using QRRapidoApp.Services
|
||||||
@using Microsoft.AspNetCore.Http.Extensions
|
@using Microsoft.AspNetCore.Http.Extensions
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject AdDisplayService AdService
|
@inject AdDisplayService AdService
|
||||||
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="pt-BR">
|
<html lang="pt-BR">
|
||||||
<head>
|
<head>
|
||||||
@ -40,37 +42,37 @@
|
|||||||
|
|
||||||
<!-- Structured Data Schema.org -->
|
<!-- Structured Data Schema.org -->
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@@context": "https://schema.org",
|
"@@context": "https://schema.org",
|
||||||
"@@type": "WebApplication",
|
"@@type": "WebApplication",
|
||||||
"name": "QR Rapido",
|
"name": "QR Rapido",
|
||||||
"description": "Gerador de QR Code ultrarrápido em português e espanhol",
|
"description": "Gerador de QR Code ultrarrápido em português e espanhol",
|
||||||
"url": "https://qrrapido.site",
|
"url": "https://qrrapido.site",
|
||||||
"applicationCategory": "UtilityApplication",
|
"applicationCategory": "UtilityApplication",
|
||||||
"operatingSystem": "Web",
|
"operatingSystem": "Web",
|
||||||
"author": {
|
"author": {
|
||||||
"@@type": "Organization",
|
"@@type": "Organization",
|
||||||
"name": "QR Rapido"
|
"name": "QR Rapido"
|
||||||
},
|
},
|
||||||
"offers": {
|
"offers": {
|
||||||
"@@type": "Offer",
|
"@@type": "Offer",
|
||||||
"price": "0",
|
"price": "0",
|
||||||
"priceCurrency": "BRL",
|
"priceCurrency": "BRL",
|
||||||
"description": "Geração gratuita de QR codes"
|
"description": "Geração gratuita de QR codes"
|
||||||
},
|
},
|
||||||
"aggregateRating": {
|
"aggregateRating": {
|
||||||
"@@type": "AggregateRating",
|
"@@type": "AggregateRating",
|
||||||
"ratingValue": "4.8",
|
"ratingValue": "4.8",
|
||||||
"reviewCount": "2547"
|
"reviewCount": "2547"
|
||||||
},
|
},
|
||||||
"featureList": [
|
"featureList": [
|
||||||
"Geração em segundos",
|
"Geração em segundos",
|
||||||
"Suporte multilíngue",
|
"Suporte multilíngue",
|
||||||
"Sem cadastro obrigatório",
|
"Sem cadastro obrigatório",
|
||||||
"30 dias sem anúncios",
|
"30 dias sem anúncios",
|
||||||
"Download múltiplos formatos"
|
"Download múltiplos formatos"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Google Analytics 4 -->
|
<!-- Google Analytics 4 -->
|
||||||
@ -80,44 +82,44 @@
|
|||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
gtag('config', 'GA_MEASUREMENT_ID', {
|
gtag('config', 'GA_MEASUREMENT_ID', {
|
||||||
send_page_view: false
|
send_page_view: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom events for QR Rapido
|
// Custom events for QR Rapido
|
||||||
window.trackQRGeneration = function(type, time, isPremium) {
|
window.trackQRGeneration = function(type, time, isPremium) {
|
||||||
gtag('event', 'qr_generated', {
|
gtag('event', 'qr_generated', {
|
||||||
'event_category': 'QR Generation',
|
'event_category': 'QR Generation',
|
||||||
'event_label': type,
|
'event_label': type,
|
||||||
'value': Math.round(parseFloat(time) * 1000),
|
'value': Math.round(parseFloat(time) * 1000),
|
||||||
'custom_parameters': {
|
'custom_parameters': {
|
||||||
'generation_time': parseFloat(time),
|
'generation_time': parseFloat(time),
|
||||||
'user_type': isPremium ? 'premium' : 'free',
|
'user_type': isPremium ? 'premium' : 'free',
|
||||||
'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal'
|
'speed_category': time < 1.0 ? 'ultra_fast' : time < 2.0 ? 'fast' : 'normal'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.trackSpeedComparison = function(ourTime, competitorAvg) {
|
window.trackSpeedComparison = function(ourTime, competitorAvg) {
|
||||||
gtag('event', 'speed_comparison', {
|
gtag('event', 'speed_comparison', {
|
||||||
'event_category': 'Performance',
|
'event_category': 'Performance',
|
||||||
'our_time': parseFloat(ourTime),
|
'our_time': parseFloat(ourTime),
|
||||||
'competitor_avg': parseFloat(competitorAvg),
|
'competitor_avg': parseFloat(competitorAvg),
|
||||||
'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime)
|
'speed_advantage': parseFloat(competitorAvg) - parseFloat(ourTime)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.trackLanguageChange = function(from, to) {
|
window.trackLanguageChange = function(from, to) {
|
||||||
gtag('event', 'language_change', {
|
gtag('event', 'language_change', {
|
||||||
'event_category': 'Localization',
|
'event_category': 'Localization',
|
||||||
'previous_language': from,
|
'previous_language': from,
|
||||||
'new_language': to
|
'new_language': to
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- AdSense -->
|
<!-- AdSense -->
|
||||||
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX"
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Bootstrap 5 -->
|
<!-- Bootstrap 5 -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
@ -151,7 +153,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h4 mb-0 text-primary fw-bold">QR Rapido</h1>
|
<h1 class="h4 mb-0 text-primary fw-bold">QR Rapido</h1>
|
||||||
<small class="text-muted" id="tagline">Gere QR codes em segundos!</small>
|
<small class="text-muted" id="tagline">@Localizer["Tagline"]</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -183,6 +185,9 @@
|
|||||||
<i class="fas fa-user"></i> @User.Identity.Name
|
<i class="fas fa-user"></i> @User.Identity.Name
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="/">
|
||||||
|
<i class="fas fa-qrcode"></i> Gerar QRCode
|
||||||
|
</a></li>
|
||||||
<li><a class="dropdown-item" href="/Account/Profile">
|
<li><a class="dropdown-item" href="/Account/Profile">
|
||||||
<i class="fas fa-user-cog"></i> Perfil
|
<i class="fas fa-user-cog"></i> Perfil
|
||||||
</a></li>
|
</a></li>
|
||||||
@ -219,7 +224,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a href="/Account/Login" class="btn btn-primary btn-sm">
|
<a href="/Account/Login" class="btn btn-primary btn-sm">
|
||||||
<i class="fas fa-sign-in-alt"></i> Login
|
<i class="fas fa-sign-in-alt"></i> @Localizer["Login"]
|
||||||
</a>
|
</a>
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-block">
|
||||||
<small class="text-success">
|
<small class="text-success">
|
||||||
@ -287,6 +292,7 @@
|
|||||||
<!-- Custom JS -->
|
<!-- Custom JS -->
|
||||||
<script src="~/js/test.js" asp-append-version="true"></script>
|
<script src="~/js/test.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/qr-speed-generator.js" asp-append-version="true"></script>
|
<script src="~/js/qr-speed-generator.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/language-switcher.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"Version": "1.0.0"
|
"Version": "1.0.0"
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
|
"MongoDB": "mongodb://localhost:27017/QrRapido"
|
||||||
},
|
},
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
"Google": {
|
"Google": {
|
||||||
@ -15,8 +16,8 @@
|
|||||||
"ClientSecret": "your-google-client-secret"
|
"ClientSecret": "your-google-client-secret"
|
||||||
},
|
},
|
||||||
"Microsoft": {
|
"Microsoft": {
|
||||||
"ClientId": "your-microsoft-client-id",
|
"ClientId": "9bec3835-acdb-4c5a-8668-6b90955c6ad2",
|
||||||
"ClientSecret": "your-microsoft-client-secret"
|
"ClientSecret": "Oe38Q~FsZ3X5ouptAB6oYyX7MXaGUvxXcqT.aaT9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Stripe": {
|
"Stripe": {
|
||||||
@ -56,6 +57,7 @@
|
|||||||
"KeywordsEN": "fast qr, quick qr generator, rapid qr code, qr code generator"
|
"KeywordsEN": "fast qr, quick qr generator, rapid qr code, qr code generator"
|
||||||
},
|
},
|
||||||
"ApplicationName": "QRRapido",
|
"ApplicationName": "QRRapido",
|
||||||
|
"Environment": "Personal",
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"SeqUrl": "http://localhost:5341",
|
"SeqUrl": "http://localhost:5341",
|
||||||
"ApiKey": "",
|
"ApiKey": "",
|
||||||
@ -84,7 +86,7 @@
|
|||||||
"DatabaseSizeErrorMB": 5120,
|
"DatabaseSizeErrorMB": 5120,
|
||||||
"GrowthRateWarningMBPerHour": 100,
|
"GrowthRateWarningMBPerHour": 100,
|
||||||
"IncludeCollectionStats": true,
|
"IncludeCollectionStats": true,
|
||||||
"CollectionsToMonitor": ["Users", "QRCodeHistory", "AdFreeSessions"]
|
"CollectionsToMonitor": [ "Users", "QRCodeHistory", "AdFreeSessions" ]
|
||||||
},
|
},
|
||||||
"HealthChecks": {
|
"HealthChecks": {
|
||||||
"MongoDB": {
|
"MongoDB": {
|
||||||
|
|||||||
144
wwwroot/js/language-switcher.js
Normal file
144
wwwroot/js/language-switcher.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Language switching functionality for QR Rapido
|
||||||
|
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
|
||||||
|
function getCurrentCulture() {
|
||||||
|
const pathSegments = window.location.pathname.split('/').filter(segment => segment);
|
||||||
|
const supportedCultures = ['pt-BR', 'es', 'en'];
|
||||||
|
|
||||||
|
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
|
||||||
|
return pathSegments[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'pt-BR';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current language display
|
||||||
|
function updateCurrentLanguageDisplay(culture) {
|
||||||
|
const langMap = {
|
||||||
|
'pt-BR': 'PT',
|
||||||
|
'es': 'ES',
|
||||||
|
'en': 'EN'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentLangSpan) {
|
||||||
|
currentLangSpan.textContent = langMap[culture] || 'PT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new URL with selected culture
|
||||||
|
function buildLocalizedUrl(newCulture) {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
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', 'en'];
|
||||||
|
|
||||||
|
// Remove current culture if it's the first segment
|
||||||
|
if (pathSegments.length > 0 && supportedCultures.includes(pathSegments[0])) {
|
||||||
|
pathSegments.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new path with selected culture
|
||||||
|
const newPath = '/' + newCulture + (pathSegments.length > 0 ? '/' + pathSegments.join('/') : '');
|
||||||
|
|
||||||
|
return newPath + queryString + hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle language selection
|
||||||
|
// Handle language selection
|
||||||
|
languageDropdownItems.forEach(item => {
|
||||||
|
item.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const selectedLang = this.getAttribute('data-lang');
|
||||||
|
currentCulture = getCurrentCulture();
|
||||||
|
|
||||||
|
// 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`;
|
||||||
|
|
||||||
|
// Clear any cache and force full reload
|
||||||
|
if ('caches' in window) {
|
||||||
|
caches.keys().then(names => {
|
||||||
|
names.forEach(name => caches.delete(name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force complete page reload with cache busting
|
||||||
|
const newUrl = buildLocalizedUrl(selectedLang);
|
||||||
|
// Na função de troca de idioma, substitua:
|
||||||
|
window.location.href = newUrl;
|
||||||
|
|
||||||
|
// Por:
|
||||||
|
window.location.replace(newUrl);
|
||||||
|
// OU
|
||||||
|
window.location.href = newUrl + (newUrl.includes('?') ? '&' : '?') + '_t=' + Date.now();
|
||||||
|
|
||||||
|
|
||||||
|
//window.location.replace(newUrl + '?_refresh=' + Date.now());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize current language display
|
||||||
|
currentCulture = getCurrentCulture();
|
||||||
|
updateCurrentLanguageDisplay(currentCulture);
|
||||||
|
|
||||||
|
// Store current culture in localStorage if not already set
|
||||||
|
if (!localStorage.getItem('preferredLanguage')) {
|
||||||
|
localStorage.setItem('preferredLanguage', currentCulture);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Utility function to get user's preferred language
|
||||||
|
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',
|
||||||
|
'es-PY': 'pt-BR', // Special case: Paraguay Spanish -> Portuguese
|
||||||
|
'en': 'en'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user