fix: banners entre sites

This commit is contained in:
Ricardo Carneiro 2026-01-25 13:50:49 -03:00
parent 5db9199705
commit 3b0c93a35e
20 changed files with 2481 additions and 33 deletions

View File

@ -17,7 +17,10 @@
"WebFetch(domain:github.com)",
"WebFetch(domain:gist.github.com)",
"Bash(dotnet list:*)",
"Bash(dotnet build:*)"
"Bash(dotnet build:*)",
"Bash(rm:*)",
"Bash(dotnet add package:*)",
"Bash(dir:*)"
],
"deny": [],
"ask": []

View File

@ -0,0 +1,319 @@
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Exceptions;
namespace Convert_It_Online.Areas.DocumentConverters.Controllers
{
[Area("DocumentConverters")]
public class PdfBarcodeLineController : Controller
{
private readonly IStringLocalizer<SharedResource> _localizer;
private readonly ILogger<PdfBarcodeLineController> _logger;
private const long MaxPreviewSize = 5 * 1024 * 1024; // 5MB
private static readonly Regex CandidatePattern = new(@"[0-9\s\.-]{44,70}", RegexOptions.Compiled);
public PdfBarcodeLineController(IStringLocalizer<SharedResource> localizer, ILogger<PdfBarcodeLineController> logger)
{
_localizer = localizer;
_logger = logger;
}
private void SetCommonViewBagProperties()
{
ViewBag.HomeLink = _localizer["HomeLink"];
ViewBag.TextMenuTitle = _localizer["TextMenuTitle"];
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
ViewBag.Terms = _localizer["Terms"];
}
private void PrepareIndexView()
{
SetCommonViewBagProperties();
ViewBag.PageTitle = _localizer["PdfBarcodeConverterPageTitle"];
ViewBag.PageDescription = _localizer["PdfBarcodeConverterPageDescription"];
ViewBag.PdfBarcodeDetectTabTitle = _localizer["PdfBarcodeDetectTabTitle"];
ViewBag.PdfBarcodeDownloadTabTitle = _localizer["PdfBarcodeDownloadTabTitle"];
ViewBag.PdfBarcodeFileInputLabel = _localizer["PdfBarcodeFileInputLabel"];
ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"];
ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"];
ViewBag.PdfBarcodePasswordHint = _localizer["PdfBarcodePasswordHint"];
ViewBag.DetectBarcodeButton = _localizer["DetectBarcodeButton"];
ViewBag.DownloadBarcodeButton = _localizer["DownloadBarcodeButton"];
ViewBag.PdfBarcodePreviewTitle = _localizer["PdfBarcodePreviewTitle"];
ViewBag.SelectFileError = _localizer["SelectFileError"];
ViewBag.CopyButtonLabel = _localizer["CopyButtonLabel"];
ViewBag.CopyButtonSuccess = _localizer["CopyButtonSuccess"];
ViewBag.FaqWhatTitle = _localizer["PdfBarcodeFaqWhatTitle"];
ViewBag.FaqWhatContent = _localizer["PdfBarcodeFaqWhatContent"];
ViewBag.FaqHowTitle = _localizer["PdfBarcodeFaqHowTitle"];
ViewBag.FaqHowContent = _localizer["PdfBarcodeFaqHowContent"];
ViewBag.FaqWhyTitle = _localizer["PdfBarcodeFaqWhyTitle"];
ViewBag.FaqWhyContent = _localizer["PdfBarcodeFaqWhyContent"];
ViewBag.FaqSecurityTitle = _localizer["PdfBarcodeFaqSecurityTitle"];
ViewBag.FaqSecurityContent = _localizer["PdfBarcodeFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["PdfBarcodeFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["PdfBarcodeFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageDescription;
}
public IActionResult Index()
{
PrepareIndexView();
return View();
}
[HttpPost]
public async Task<IActionResult> DetectLine(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleDetection(pdfFile, password, preview, forDownload: false);
}
[HttpPost]
public async Task<IActionResult> DownloadLine(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleDetection(pdfFile, password, preview, forDownload: true);
}
private async Task<IActionResult> HandleDetection(IFormFile? pdfFile, string? password, bool preview, bool forDownload)
{
if (pdfFile == null || pdfFile.Length == 0)
{
_logger.LogWarning("[PDF-BARCODE] Attempt without file");
if (preview)
{
return Json(new { success = false, message = _localizer["SelectFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]);
PrepareIndexView();
return View("Index");
}
if (!IsValidPdf(pdfFile))
{
_logger.LogWarning("[PDF-BARCODE] Invalid file type: {ContentType}", pdfFile.ContentType);
if (preview)
{
return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]);
PrepareIndexView();
return View("Index");
}
if (preview && pdfFile.Length > MaxPreviewSize)
{
return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value });
}
var extraction = await TryExtractTextAsync(pdfFile, password);
if (!extraction.Success)
{
var message = _localizer[extraction.ErrorKey].Value;
if (preview)
{
return Json(new { success = false, message });
}
ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]);
ViewBag.ConversionError = message;
PrepareIndexView();
return View("Index");
}
var lineResult = FindBarcodeLine(extraction.Content ?? string.Empty);
if (lineResult == null)
{
var message = _localizer["PdfNoBarcodeFound"].Value;
if (preview)
{
return Json(new { success = false, message });
}
ModelState.AddModelError("pdfFile", _localizer["PdfNoBarcodeFound"]);
ViewBag.ConversionError = message;
PrepareIndexView();
return View("Index");
}
if (preview)
{
return Json(new
{
success = true,
content = lineResult,
format = "digit-line"
});
}
var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName);
var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? "linha-digitavel.txt" : fileBaseName + "-linha-digitavel.txt";
var payload = Encoding.UTF8.GetBytes(lineResult + Environment.NewLine);
return File(payload, "text/plain", downloadFileName);
}
private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password)
{
try
{
await using var memoryStream = new MemoryStream();
await pdfFile.CopyToAsync(memoryStream);
var pdfBytes = memoryStream.ToArray();
// Try multiple password encodings
var passwords = new List<byte[]?> { null }; // Start with no password
if (!string.IsNullOrEmpty(password))
{
passwords.Add(System.Text.Encoding.UTF8.GetBytes(password));
passwords.Add(System.Text.Encoding.ASCII.GetBytes(password));
passwords.Add(System.Text.Encoding.Latin1.GetBytes(password));
}
foreach (var pwd in passwords)
{
try
{
ReaderProperties readerProperties = new ReaderProperties();
if (pwd != null)
{
readerProperties.SetPassword(pwd);
}
using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties);
using var pdfDocument = new PdfDocument(pdfReader);
var builder = new StringBuilder();
int numberOfPages = pdfDocument.GetNumberOfPages();
for (int i = 1; i <= numberOfPages; i++)
{
var page = pdfDocument.GetPage(i);
// Try multiple extraction strategies for barcode detection
var strategies = new ITextExtractionStrategy[]
{
new LocationTextExtractionStrategy(),
new SimpleTextExtractionStrategy()
};
foreach (var strategy in strategies)
{
try
{
var pageText = PdfTextExtractor.GetTextFromPage(page, strategy);
if (!string.IsNullOrWhiteSpace(pageText))
{
builder.AppendLine(pageText);
break;
}
}
catch
{
continue;
}
}
}
var result = builder.ToString();
if (!string.IsNullOrWhiteSpace(result))
{
return (true, result, string.Empty);
}
}
catch (BadPasswordException) when (pwd != null)
{
continue; // Try next password encoding
}
catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password))
{
break;
}
}
if (!string.IsNullOrEmpty(password))
{
return (false, null, "PdfInvalidPassword");
}
return (false, null, "InvalidPdfFileError");
}
catch (BadPasswordException)
{
return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword");
}
catch (Exception ex)
{
_logger.LogError(ex, "[PDF-BARCODE] Failed to extract text from {FileName}", pdfFile.FileName);
return (false, null, "InvalidPdfFileError");
}
}
private static bool IsValidPdf(IFormFile file)
{
if (file == null)
{
return false;
}
var contentType = file.ContentType?.ToLowerInvariant();
if (contentType == "application/pdf" || contentType == "application/x-pdf")
{
return true;
}
return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
}
private static string? FindBarcodeLine(string rawText)
{
if (string.IsNullOrWhiteSpace(rawText))
{
return null;
}
foreach (Match match in CandidatePattern.Matches(rawText))
{
var digits = Regex.Replace(match.Value, "[^0-9]", string.Empty);
if (digits.Length == 47 || digits.Length == 48 || digits.Length == 44)
{
return digits;
}
}
// fallback: check full text digits
var fullDigits = Regex.Replace(rawText, "[^0-9]", string.Empty);
if (fullDigits.Length >= 44)
{
var candidates = new[] { 48, 47, 44 };
foreach (var length in candidates)
{
if (fullDigits.Length >= length)
{
return fullDigits.Substring(0, length);
}
}
}
return null;
}
}
}

View File

@ -0,0 +1,318 @@
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Exceptions;
namespace Convert_It_Online.Areas.DocumentConverters.Controllers
{
[Area("DocumentConverters")]
public class PdfToTextController : Controller
{
private readonly IStringLocalizer<SharedResource> _localizer;
private readonly ILogger<PdfToTextController> _logger;
private const long MaxPreviewSize = 10 * 1024 * 1024; // 10MB
public PdfToTextController(IStringLocalizer<SharedResource> localizer, ILogger<PdfToTextController> logger)
{
_localizer = localizer;
_logger = logger;
}
private void SetCommonViewBagProperties()
{
ViewBag.HomeLink = _localizer["HomeLink"];
ViewBag.TextMenuTitle = _localizer["TextMenuTitle"];
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
ViewBag.Terms = _localizer["Terms"];
}
private void PrepareIndexView()
{
SetCommonViewBagProperties();
ViewBag.PageTitle = _localizer["PdfTextConverterPageTitle"];
ViewBag.PageDescription = _localizer["PdfTextConverterPageDescription"];
ViewBag.PdfPlainTextTabTitle = _localizer["PdfPlainTextTabTitle"];
ViewBag.PdfMarkdownTabTitle = _localizer["PdfMarkdownTabTitle"];
ViewBag.PdfFileInputLabel = _localizer["PdfFileInputLabel"];
ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"];
ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"];
ViewBag.PdfPasswordHint = _localizer["PdfPasswordHint"];
ViewBag.ExtractPlainTextButton = _localizer["ExtractPlainTextButton"];
ViewBag.ExtractMarkdownButton = _localizer["ExtractMarkdownButton"];
ViewBag.DownloadPlainTextButton = _localizer["DownloadPlainTextButton"];
ViewBag.DownloadMarkdownButton = _localizer["DownloadMarkdownButton"];
ViewBag.PdfTextPreviewTitle = _localizer["PdfTextPreviewTitle"];
ViewBag.SelectFileError = _localizer["SelectFileError"];
ViewBag.FaqWhatTitle = _localizer["PdfTextFaqWhatTitle"];
ViewBag.FaqWhatContent = _localizer["PdfTextFaqWhatContent"];
ViewBag.FaqHowTitle = _localizer["PdfTextFaqHowTitle"];
ViewBag.FaqHowContent = _localizer["PdfTextFaqHowContent"];
ViewBag.FaqWhyTitle = _localizer["PdfTextFaqWhyTitle"];
ViewBag.FaqWhyContent = _localizer["PdfTextFaqWhyContent"];
ViewBag.FaqSecurityTitle = _localizer["PdfTextFaqSecurityTitle"];
ViewBag.FaqSecurityContent = _localizer["PdfTextFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["PdfTextFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["PdfTextFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageDescription;
}
public IActionResult Index()
{
PrepareIndexView();
return View();
}
[HttpGet]
public IActionResult Test()
{
return Json(new { success = true, message = "Roteamento funcionando!", timestamp = DateTime.Now });
}
[HttpPost]
public async Task<IActionResult> ExtractPlainText(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleExtraction(pdfFile, password, preview, toMarkdown: false);
}
[HttpPost]
public async Task<IActionResult> ExtractMarkdown(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleExtraction(pdfFile, password, preview, toMarkdown: true);
}
private async Task<IActionResult> HandleExtraction(IFormFile? pdfFile, string? password, bool preview, bool toMarkdown)
{
if (pdfFile == null || pdfFile.Length == 0)
{
_logger.LogWarning("[PDF-TEXT] Attempt without file");
if (preview)
{
return Json(new { success = false, message = _localizer["SelectFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]);
PrepareIndexView();
return View("Index");
}
if (!IsValidPdf(pdfFile))
{
_logger.LogWarning("[PDF-TEXT] Invalid file type: {ContentType}", pdfFile.ContentType);
if (preview)
{
return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]);
PrepareIndexView();
return View("Index");
}
if (preview && pdfFile.Length > MaxPreviewSize)
{
if (preview)
{
return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value });
}
}
var extraction = await TryExtractTextAsync(pdfFile, password);
if (!extraction.Success)
{
var message = _localizer[extraction.ErrorKey].Value;
if (preview)
{
return Json(new { success = false, message });
}
ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]);
ViewBag.ConversionError = message;
PrepareIndexView();
return View("Index");
}
var textContent = extraction.Content ?? string.Empty;
if (toMarkdown)
{
textContent = ToMarkdown(textContent);
}
var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName);
var extension = toMarkdown ? ".md" : ".txt";
var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? (toMarkdown ? "resultado.md" : "resultado.txt") : fileBaseName + extension;
var contentType = toMarkdown ? "text/markdown" : "text/plain";
if (preview)
{
return Json(new
{
success = true,
content = textContent,
filename = downloadFileName,
format = toMarkdown ? "markdown" : "text"
});
}
var payload = Encoding.UTF8.GetBytes(textContent);
return File(payload, contentType, downloadFileName);
}
private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password)
{
try
{
await using var memoryStream = new MemoryStream();
await pdfFile.CopyToAsync(memoryStream);
var pdfBytes = memoryStream.ToArray();
// Try multiple password encodings and extraction strategies
var passwords = new List<byte[]?> { null }; // Start with no password
if (!string.IsNullOrEmpty(password))
{
passwords.Add(System.Text.Encoding.UTF8.GetBytes(password));
passwords.Add(System.Text.Encoding.ASCII.GetBytes(password));
passwords.Add(System.Text.Encoding.Latin1.GetBytes(password));
}
foreach (var pwd in passwords)
{
try
{
ReaderProperties readerProperties = new ReaderProperties();
if (pwd != null)
{
readerProperties.SetPassword(pwd);
}
using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties);
using var pdfDocument = new PdfDocument(pdfReader);
var builder = new StringBuilder();
int numberOfPages = pdfDocument.GetNumberOfPages();
for (int i = 1; i <= numberOfPages; i++)
{
var page = pdfDocument.GetPage(i);
// Try multiple extraction strategies
var strategies = new ITextExtractionStrategy[]
{
new LocationTextExtractionStrategy(),
new SimpleTextExtractionStrategy()
};
string pageText = "";
foreach (var strategy in strategies)
{
try
{
pageText = PdfTextExtractor.GetTextFromPage(page, strategy);
if (!string.IsNullOrWhiteSpace(pageText))
break;
}
catch
{
continue;
}
}
if (!string.IsNullOrWhiteSpace(pageText))
{
builder.AppendLine(pageText);
builder.AppendLine();
}
else
{
builder.AppendLine($"[Página {i} - texto não detectado ou pode conter apenas imagens]");
builder.AppendLine();
}
}
var result = builder.ToString().Trim();
if (!string.IsNullOrWhiteSpace(result))
{
return (true, result, string.Empty);
}
}
catch (BadPasswordException) when (pwd != null)
{
continue; // Try next password encoding
}
catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password))
{
// Document requires password but none worked
break;
}
}
// If we get here, either password was wrong or no text found
if (!string.IsNullOrEmpty(password))
{
return (false, null, "PdfInvalidPassword");
}
return (false, null, "InvalidPdfFileError");
}
catch (BadPasswordException)
{
return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword");
}
catch (Exception ex)
{
_logger.LogError(ex, "[PDF-TEXT] Failed to extract text from {FileName}", pdfFile.FileName);
return (false, null, "InvalidPdfFileError");
}
}
private static bool IsValidPdf(IFormFile file)
{
if (file == null)
{
return false;
}
var contentType = file.ContentType?.ToLowerInvariant();
if (contentType == "application/pdf" || contentType == "application/x-pdf")
{
return true;
}
return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
}
private static string ToMarkdown(string text)
{
var normalized = text.Replace("\r\n", "\n").Trim();
if (string.IsNullOrWhiteSpace(normalized))
{
return string.Empty;
}
var builder = new StringBuilder();
var lines = normalized.Split('\n');
foreach (var line in lines)
{
var trimmed = line.TrimEnd();
builder.AppendLine(trimmed);
}
return builder.ToString().Trim();
}
}
}

View File

@ -0,0 +1,354 @@
@{
ViewData["Title"] = ViewBag.PageTitle;
Layout = "_Layout";
}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="text-center mb-4">
<h1 class="display-5">@ViewBag.PageTitle</h1>
<p class="lead text-muted">@ViewBag.PageDescription</p>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-10 col-xl-8">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="bi bi-upc-scan me-2"></i>@ViewBag.DocumentMenuTitle
</h5>
</div>
<div class="card-body">
@if (ViewBag.ConversionError != null)
{
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
@ViewBag.ConversionError
</div>
}
<ul class="nav nav-tabs mb-4" id="barcodeTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="detect-tab" data-bs-toggle="tab" data-bs-target="#detect" type="button" role="tab" aria-controls="detect" aria-selected="true">
<i class="bi bi-eyeglasses me-2"></i>@ViewBag.PdfBarcodeDetectTabTitle
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="download-tab" data-bs-toggle="tab" data-bs-target="#download" type="button" role="tab" aria-controls="download" aria-selected="false">
<i class="bi bi-download me-2"></i>@ViewBag.PdfBarcodeDownloadTabTitle
</button>
</li>
</ul>
<div class="tab-content" id="barcodeTabContent">
<div class="tab-pane fade show active" id="detect" role="tabpanel" aria-labelledby="detect-tab">
<form id="detectForm" action="/pt-BR/DocumentConverters/PdfBarcodeLine/DetectLine" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-4">
<label for="detectPdfFile" class="form-label fw-semibold">@ViewBag.PdfBarcodeFileInputLabel</label>
<input class="form-control" type="file" id="detectPdfFile" name="pdfFile" accept=".pdf" required>
<div class="invalid-feedback">
@ViewBag.SelectFileError
</div>
</div>
<div class="mb-4">
<label for="detectPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
<input class="form-control" type="password" id="detectPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfBarcodePasswordHint
</div>
</div>
<div class="d-grid gap-2">
<button type="button" id="previewDetectBtn" class="btn btn-primary">
<i class="bi bi-upc-scan me-2"></i>@ViewBag.DetectBarcodeButton
</button>
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-download me-2"></i>@ViewBag.DownloadBarcodeButton
</button>
</div>
</form>
</div>
<div class="tab-pane fade" id="download" role="tabpanel" aria-labelledby="download-tab">
<form id="downloadForm" action="/pt-BR/DocumentConverters/PdfBarcodeLine/DownloadLine" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-4">
<label for="downloadPdfFile" class="form-label fw-semibold">@ViewBag.PdfBarcodeFileInputLabel</label>
<input class="form-control" type="file" id="downloadPdfFile" name="pdfFile" accept=".pdf" required>
<div class="invalid-feedback">
@ViewBag.SelectFileError
</div>
</div>
<div class="mb-4">
<label for="downloadPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
<input class="form-control" type="password" id="downloadPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfBarcodePasswordHint
</div>
</div>
<div class="d-grid gap-2">
<button type="button" id="previewDownloadBtn" class="btn btn-primary">
<i class="bi bi-eye me-2"></i>@ViewBag.DetectBarcodeButton
</button>
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-download me-2"></i>@ViewBag.DownloadBarcodeButton
</button>
</div>
</form>
</div>
</div>
<div id="barcodeLoading" class="text-center my-4" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Carregando...</span>
</div>
</div>
<div id="barcodePreview" class="mt-4" style="display: none;">
<h5 class="mb-3">@ViewBag.PdfBarcodePreviewTitle</h5>
<div class="input-group">
<input type="text" id="barcodeResult" class="form-control" readonly>
<button id="copyBarcodeBtn" class="btn btn-outline-success" type="button">
<i class="bi bi-clipboard-check me-1"></i>@ViewBag.CopyButtonLabel
</button>
</div>
<div class="text-end mt-3">
<button id="downloadBarcodeBtn" class="btn btn-success btn-sm">
<i class="bi bi-box-arrow-down me-2"></i>@ViewBag.DownloadBarcodeButton
</button>
</div>
</div>
<div id="barcodeError" class="mt-4" style="display: none;">
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="barcodeErrorMessage"></span>
<button id="fallbackBarcodeBtn" class="btn btn-outline-primary btn-sm ms-2">
@ViewBag.DownloadBarcodeButton
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-lg-10 mx-auto">
<div class="converter-faq">
<h3 class="h4 mb-3 text-center">Perguntas Frequentes</h3>
<div class="accordion" id="barcodeFaqAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="barcodeFaqWhat">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseWhat" aria-expanded="false" aria-controls="barcodeCollapseWhat">
<i class="bi bi-question-circle me-2"></i>@ViewBag.FaqWhatTitle
</button>
</h2>
<div id="barcodeCollapseWhat" class="accordion-collapse collapse" aria-labelledby="barcodeFaqWhat" data-bs-parent="#barcodeFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqWhatContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="barcodeFaqHow">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseHow" aria-expanded="false" aria-controls="barcodeCollapseHow">
<i class="bi bi-gear me-2"></i>@ViewBag.FaqHowTitle
</button>
</h2>
<div id="barcodeCollapseHow" class="accordion-collapse collapse" aria-labelledby="barcodeFaqHow" data-bs-parent="#barcodeFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqHowContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="barcodeFaqWhy">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseWhy" aria-expanded="false" aria-controls="barcodeCollapseWhy">
<i class="bi bi-lightbulb me-2"></i>@ViewBag.FaqWhyTitle
</button>
</h2>
<div id="barcodeCollapseWhy" class="accordion-collapse collapse" aria-labelledby="barcodeFaqWhy" data-bs-parent="#barcodeFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqWhyContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="barcodeFaqSecurity">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseSecurity" aria-expanded="false" aria-controls="barcodeCollapseSecurity">
<i class="bi bi-shield-check me-2"></i>@ViewBag.FaqSecurityTitle
</button>
</h2>
<div id="barcodeCollapseSecurity" class="accordion-collapse collapse" aria-labelledby="barcodeFaqSecurity" data-bs-parent="#barcodeFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqSecurityContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="barcodeFaqLimits">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseLimits" aria-expanded="false" aria-controls="barcodeCollapseLimits">
<i class="bi bi-exclamation-triangle me-2"></i>@ViewBag.FaqLimitsTitle
</button>
</h2>
<div id="barcodeCollapseLimits" class="accordion-collapse collapse" aria-labelledby="barcodeFaqLimits" data-bs-parent="#barcodeFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqLimitsContent
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
#barcodeResult {
font-family: monospace;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
</style>
@section Scripts {
<script>
(function () {
const detectForm = document.getElementById('detectForm');
const downloadForm = document.getElementById('downloadForm');
const previewDetectBtn = document.getElementById('previewDetectBtn');
const previewDownloadBtn = document.getElementById('previewDownloadBtn');
const downloadBarcodeBtn = document.getElementById('downloadBarcodeBtn');
const fallbackBarcodeBtn = document.getElementById('fallbackBarcodeBtn');
const copyBarcodeBtn = document.getElementById('copyBarcodeBtn');
const barcodeLoading = document.getElementById('barcodeLoading');
const barcodePreview = document.getElementById('barcodePreview');
const barcodeError = document.getElementById('barcodeError');
const barcodeResult = document.getElementById('barcodeResult');
const barcodeErrorMessage = document.getElementById('barcodeErrorMessage');
let currentLine = '';
let currentFileName = 'linha-digitavel.txt';
const forms = document.querySelectorAll('.needs-validation');
Array.from(forms).forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
});
});
previewDetectBtn.addEventListener('click', function () {
handlePreview(detectForm);
});
previewDownloadBtn.addEventListener('click', function () {
handlePreview(downloadForm);
});
downloadBarcodeBtn.addEventListener('click', function () {
if (!currentLine) {
return;
}
const blob = new Blob([currentLine + '\n'], { type: 'text/plain;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = currentFileName;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
window.URL.revokeObjectURL(url);
});
fallbackBarcodeBtn.addEventListener('click', function () {
const activeTab = document.querySelector('.nav-link.active');
const currentForm = activeTab.id === 'detect-tab' ? detectForm : downloadForm;
currentForm.submit();
});
copyBarcodeBtn.addEventListener('click', function () {
if (!currentLine) {
return;
}
navigator.clipboard.writeText(currentLine).then(function () {
copyBarcodeBtn.innerHTML = '<i class="bi bi-clipboard-check-fill me-1"></i>@ViewBag.CopyButtonSuccess';
setTimeout(function () {
copyBarcodeBtn.innerHTML = '<i class="bi bi-clipboard-check me-1"></i>@ViewBag.CopyButtonLabel';
}, 1500);
});
});
function handlePreview(form) {
if (!form.checkValidity()) {
form.classList.add('was-validated');
return;
}
showLoading();
const formData = new FormData(form);
fetch(form.action + '?preview=true', {
method: 'POST',
body: formData
}).then(async response => {
if (!response.ok) {
const text = await response.text();
throw new Error(text || response.statusText);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || 'Erro na conversão');
}
currentLine = result.content;
currentFileName = (form === downloadForm ? 'linha-digitavel-download.txt' : 'linha-digitavel.txt');
barcodeResult.value = currentLine;
showPreview();
}).catch(error => {
showError(error.message || 'Erro inesperado.');
});
}
function showLoading() {
barcodeLoading.style.display = 'block';
barcodePreview.style.display = 'none';
barcodeError.style.display = 'none';
}
function showPreview() {
barcodeLoading.style.display = 'none';
barcodeError.style.display = 'none';
barcodePreview.style.display = 'block';
}
function showError(message) {
barcodeLoading.style.display = 'none';
barcodePreview.style.display = 'none';
barcodeErrorMessage.textContent = message;
barcodeError.style.display = 'block';
}
})();
</script>
}

View File

@ -0,0 +1,350 @@
@{
ViewData["Title"] = ViewBag.PageTitle;
Layout = "_Layout";
}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="text-center mb-4">
<h1 class="display-5">@ViewBag.PageTitle</h1>
<p class="lead text-muted">@ViewBag.PageDescription</p>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-10 col-xl-8">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="bi bi-file-earmark-text me-2"></i>@ViewBag.DocumentMenuTitle
</h5>
</div>
<div class="card-body">
@if (ViewBag.ConversionError != null)
{
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
@ViewBag.ConversionError
</div>
}
<ul class="nav nav-tabs mb-4" id="pdfTextTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="plain-text-tab" data-bs-toggle="tab" data-bs-target="#plain-text" type="button" role="tab" aria-controls="plain-text" aria-selected="true">
<i class="bi bi-file-text me-2"></i>@ViewBag.PdfPlainTextTabTitle
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="markdown-tab" data-bs-toggle="tab" data-bs-target="#markdown" type="button" role="tab" aria-controls="markdown" aria-selected="false">
<i class="bi bi-markdown me-2"></i>@ViewBag.PdfMarkdownTabTitle
</button>
</li>
</ul>
<div class="tab-content" id="pdfTextTabContent">
<div class="tab-pane fade show active" id="plain-text" role="tabpanel" aria-labelledby="plain-text-tab">
<form id="plainTextForm" action="/pt-BR/DocumentConverters/PdfToText/ExtractPlainText" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-4">
<label for="plainTextPdfFile" class="form-label fw-semibold">@ViewBag.PdfFileInputLabel</label>
<input class="form-control" type="file" id="plainTextPdfFile" name="pdfFile" accept=".pdf" required>
<div class="invalid-feedback">
@ViewBag.SelectFileError
</div>
</div>
<div class="mb-4">
<label for="plainTextPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
<input class="form-control" type="password" id="plainTextPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfPasswordHint
</div>
</div>
<div class="d-grid gap-2">
<button type="button" id="previewPlainTextBtn" class="btn btn-primary">
<i class="bi bi-eye me-2"></i>@ViewBag.ExtractPlainTextButton
</button>
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-download me-2"></i>@ViewBag.DownloadPlainTextButton
</button>
</div>
</form>
</div>
<div class="tab-pane fade" id="markdown" role="tabpanel" aria-labelledby="markdown-tab">
<form id="markdownForm" action="/pt-BR/DocumentConverters/PdfToText/ExtractMarkdown" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-4">
<label for="markdownPdfFile" class="form-label fw-semibold">@ViewBag.PdfFileInputLabel</label>
<input class="form-control" type="file" id="markdownPdfFile" name="pdfFile" accept=".pdf" required>
<div class="invalid-feedback">
@ViewBag.SelectFileError
</div>
</div>
<div class="mb-4">
<label for="markdownPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
<input class="form-control" type="password" id="markdownPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfPasswordHint
</div>
</div>
<div class="d-grid gap-2">
<button type="button" id="previewMarkdownBtn" class="btn btn-primary">
<i class="bi bi-eye me-2"></i>@ViewBag.ExtractMarkdownButton
</button>
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-download me-2"></i>@ViewBag.DownloadMarkdownButton
</button>
</div>
</form>
</div>
</div>
<div id="loadingSpinner" class="text-center my-4" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Carregando...</span>
</div>
</div>
<div id="previewArea" class="mt-4" style="display: none;">
<h5 class="mb-3">@ViewBag.PdfTextPreviewTitle</h5>
<textarea id="previewTextArea" class="form-control" rows="12" readonly></textarea>
<div class="text-end mt-3">
<button id="downloadPreviewBtn" class="btn btn-success btn-sm">
<i class="bi bi-box-arrow-down me-2"></i>@ViewBag.DownloadPlainTextButton
</button>
</div>
</div>
<div id="errorArea" class="mt-4" style="display: none;">
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="errorMessage"></span>
<button id="fallbackDownloadBtn" class="btn btn-outline-primary btn-sm ms-2">
@ViewBag.DownloadPlainTextButton
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-lg-10 mx-auto">
<div class="converter-faq">
<h3 class="h4 mb-3 text-center">Perguntas Frequentes</h3>
<div class="accordion" id="pdfTextFaqAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="faqWhat">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhat" aria-expanded="false" aria-controls="collapseWhat">
<i class="bi bi-question-circle me-2"></i>@ViewBag.FaqWhatTitle
</button>
</h2>
<div id="collapseWhat" class="accordion-collapse collapse" aria-labelledby="faqWhat" data-bs-parent="#pdfTextFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqWhatContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHow">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseHow" aria-expanded="false" aria-controls="collapseHow">
<i class="bi bi-gear me-2"></i>@ViewBag.FaqHowTitle
</button>
</h2>
<div id="collapseHow" class="accordion-collapse collapse" aria-labelledby="faqHow" data-bs-parent="#pdfTextFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqHowContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqWhy">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhy" aria-expanded="false" aria-controls="collapseWhy">
<i class="bi bi-lightbulb me-2"></i>@ViewBag.FaqWhyTitle
</button>
</h2>
<div id="collapseWhy" class="accordion-collapse collapse" aria-labelledby="faqWhy" data-bs-parent="#pdfTextFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqWhyContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqSecurity">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSecurity" aria-expanded="false" aria-controls="collapseSecurity">
<i class="bi bi-shield-check me-2"></i>@ViewBag.FaqSecurityTitle
</button>
</h2>
<div id="collapseSecurity" class="accordion-collapse collapse" aria-labelledby="faqSecurity" data-bs-parent="#pdfTextFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqSecurityContent
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqLimits">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLimits" aria-expanded="false" aria-controls="collapseLimits">
<i class="bi bi-exclamation-triangle me-2"></i>@ViewBag.FaqLimitsTitle
</button>
</h2>
<div id="collapseLimits" class="accordion-collapse collapse" aria-labelledby="faqLimits" data-bs-parent="#pdfTextFaqAccordion">
<div class="accordion-body">
@ViewBag.FaqLimitsContent
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
#previewTextArea {
font-family: monospace;
background-color: #f8f9fa;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
</style>
@section Scripts {
<script>
(function () {
const plainForm = document.getElementById('plainTextForm');
const markdownForm = document.getElementById('markdownForm');
const previewPlainBtn = document.getElementById('previewPlainTextBtn');
const previewMarkdownBtn = document.getElementById('previewMarkdownBtn');
const downloadPreviewBtn = document.getElementById('downloadPreviewBtn');
const fallbackDownloadBtn = document.getElementById('fallbackDownloadBtn');
const loadingSpinner = document.getElementById('loadingSpinner');
const previewArea = document.getElementById('previewArea');
const errorArea = document.getElementById('errorArea');
const previewTextArea = document.getElementById('previewTextArea');
const errorMessage = document.getElementById('errorMessage');
const previewTitleButton = document.getElementById('downloadPreviewBtn');
let previewContent = '';
let previewFileName = 'resultado.txt';
let previewMime = 'text/plain';
const forms = document.querySelectorAll('.needs-validation');
Array.from(forms).forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
});
});
previewPlainBtn.addEventListener('click', function () {
handlePreview(plainForm, 'text/plain');
});
previewMarkdownBtn.addEventListener('click', function () {
handlePreview(markdownForm, 'text/markdown');
});
downloadPreviewBtn.addEventListener('click', function () {
if (!previewContent) {
return;
}
const blob = new Blob([previewContent], { type: previewMime + ';charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = previewFileName;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
window.URL.revokeObjectURL(url);
});
fallbackDownloadBtn.addEventListener('click', function () {
const activeTab = document.querySelector('.nav-link.active');
const currentForm = activeTab.id === 'plain-text-tab' ? plainForm : markdownForm;
currentForm.submit();
});
function handlePreview(form, mimeType) {
if (!form.checkValidity()) {
form.classList.add('was-validated');
return;
}
showLoading();
const formData = new FormData(form);
const requestUrl = form.action + '?preview=true';
console.log('DEBUG: Requesting URL:', requestUrl);
console.log('DEBUG: Form action:', form.action);
fetch(requestUrl, {
method: 'POST',
body: formData
}).then(async response => {
console.log('DEBUG: Response status:', response.status);
console.log('DEBUG: Response headers:', response.headers.get('content-type'));
const responseText = await response.text();
console.log('DEBUG: Response text (first 200 chars):', responseText.substring(0, 200));
if (!response.ok) {
throw new Error(responseText || response.statusText);
}
const result = JSON.parse(responseText);
if (!result.success) {
throw new Error(result.message || 'Erro na conversão');
}
previewContent = result.content;
previewFileName = result.filename;
previewMime = mimeType;
previewTextArea.value = result.content;
previewTitleButton.innerHTML = `<i class="bi bi-box-arrow-down me-2"></i>${result.format === 'markdown' ? '@ViewBag.DownloadMarkdownButton' : '@ViewBag.DownloadPlainTextButton'}`;
showPreview();
}).catch(error => {
showError(error.message || 'Erro inesperado.');
});
}
function showLoading() {
loadingSpinner.style.display = 'block';
previewArea.style.display = 'none';
errorArea.style.display = 'none';
}
function showPreview() {
loadingSpinner.style.display = 'none';
errorArea.style.display = 'none';
previewArea.style.display = 'block';
}
function showError(message) {
loadingSpinner.style.display = 'none';
previewArea.style.display = 'none';
errorMessage.textContent = message;
errorArea.style.display = 'block';
}
})();
</script>
}

View File

@ -27,6 +27,9 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
@ -56,6 +59,7 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
ViewBag.FaqSecurityContent = _localizer["HeicFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["HeicFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["HeicFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageDescription;
}
public IActionResult Index()

View File

@ -25,6 +25,10 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
@ -54,6 +58,7 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
ViewBag.FaqSecurityContent = _localizer["JpgWebpFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["JpgWebpFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["JpgWebpFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageDescription;
}
public IActionResult Index()

View File

@ -37,6 +37,10 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
@ -66,6 +70,7 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
ViewBag.FaqSecurityContent = _localizer["CaseFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["CaseFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["CaseFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageTitle;
var model = new CaseConverterViewModel();
return View(model);
@ -83,6 +88,7 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
ViewBag.ToLowerButton = _localizer["ToLowerButton"];
ViewBag.ToSentenceCaseButton = _localizer["ToSentenceCaseButton"];
ViewBag.ResultTitle = _localizer["ResultTitle"];
ViewBag.MetaDescription = ViewBag.PageTitle;
if (!ModelState.IsValid || string.IsNullOrEmpty(model.InputText))
{

View File

@ -13,10 +13,12 @@ Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gr
**Português (pt-BR):**
- Ferramentas de Texto: `/pt-BR/ferramentas-de-texto/conversor-de-maiusculas-minusculas`
- Conversores de Imagem: `/pt-BR/conversores-de-imagem/jpg-para-webp`
- Conversores de Documento: `/pt-BR/conversores-de-documento/pdf-para-texto`
**Espanhol (es-MX, es-CL, es-PY):**
- Herramientas de Texto: `/es-MX/herramientas-de-texto/conversor-de-mayusculas-minusculas`
- Convertidores de Imagen: `/es-MX/convertidores-de-imagen/jpg-a-webp`
- Convertidores de Documento: `/es-MX/convertidores-de-documento/pdf-a-texto`
### Arquitetura do Sistema de URLs
@ -53,6 +55,12 @@ Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gr
- **URL PT**: `/pt-BR/conversores-de-imagem/jpg-para-webp`
- **URL ES**: `/es-MX/convertidores-de-imagen/jpg-a-webp`
### DocumentConverters
- **Controllers**: `PdfToTextController`, `PdfBarcodeLineController`
- **Funcionalidades**: Extrair texto/Markdown de PDFs e detectar linha digitável de boletos
- **URL PT**: `/pt-BR/conversores-de-documento/pdf-para-texto` e `/pt-BR/conversores-de-documento/linha-digitavel-do-pdf`
- **URL ES**: `/es-MX/convertidores-de-documento/pdf-a-texto` e `/es-MX/convertidores-de-documento/linea-digitada-desde-pdf`
## Desenvolvimento Guidelines
### Adicionando Novos Conversores

View File

@ -32,6 +32,9 @@ namespace Convert_It_Online.Controllers
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
@ -51,6 +54,7 @@ namespace Convert_It_Online.Controllers
ViewBag.SecurityContent = _localizer["SecurityContent"];
ViewBag.AllConvertersTitle = _localizer["AllConvertersTitle"];
ViewBag.UseConverterButton = _localizer["UseConverterButton"];
ViewBag.MetaDescription = ViewBag.Subtitle;
var individualConverters = new List<ToolViewModel>
{
@ -77,6 +81,22 @@ namespace Convert_It_Online.Controllers
Area = "ImageConverters",
Controller = "HeicToJpg",
IconClass = "bi-phone"
},
new ToolViewModel
{
Title = _localizer["PdfToTextIndividualTitle"],
Description = _localizer["PdfToTextIndividualDescription"],
Area = "DocumentConverters",
Controller = "PdfToText",
IconClass = "bi-file-text"
},
new ToolViewModel
{
Title = _localizer["PdfBarcodeIndividualTitle"],
Description = _localizer["PdfBarcodeIndividualDescription"],
Area = "DocumentConverters",
Controller = "PdfBarcodeLine",
IconClass = "bi-upc-scan"
}
};
@ -97,6 +117,7 @@ namespace Convert_It_Online.Controllers
ViewBag.Feature3 = _localizer["Feature3"];
ViewBag.Feature4 = _localizer["Feature4"];
ViewBag.StartConverting = _localizer["StartConverting"];
ViewBag.MetaDescription = ViewBag.WhatIsContent;
return View();
}
@ -109,6 +130,7 @@ namespace Convert_It_Online.Controllers
ViewBag.SupportTitle = _localizer["SupportTitle"];
ViewBag.SupportTime = _localizer["SupportTime"];
ViewBag.FaqTitle = _localizer["FaqTitle"];
ViewBag.MetaDescription = ViewBag.ContactIntro;
return View();
}
@ -129,6 +151,7 @@ namespace Convert_It_Online.Controllers
ViewBag.ChangesContent = _localizer["ChangesContent"];
ViewBag.LastUpdated = _localizer["LastUpdated"];
ViewBag.BackToHome = _localizer["BackToHome"];
ViewBag.MetaDescription = ViewBag.TermsIntro;
return View();
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@ -11,6 +11,7 @@
<PackageReference Include="HeyRed.ImageSharp.Heif" Version="2.1.3" />
<PackageReference Include="LibHeif.Native.win-x64" Version="1.15.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="itext7" Version="8.0.5" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />

View File

@ -35,6 +35,14 @@ namespace Convert_It_Online.Middleware
var translatedController = match.Groups[3].Value;
var action = match.Groups[4].Success ? match.Groups[4].Value : "Index";
// Skip if this is already an internal area/controller name (starts with capital letter)
if (char.IsUpper(translatedArea[0]) || char.IsUpper(translatedController[0]))
{
_logger.LogInformation($"Skipping internal path: {path}");
await _next(context);
return;
}
_logger.LogInformation($"Matched: culture={cultureName}, area={translatedArea}, controller={translatedController}, action={action}");
try

View File

@ -16,22 +16,26 @@ namespace Convert_It_Online.Services
["pt-BR"] = new Dictionary<string, string>
{
["TextTools"] = "ferramentas-de-texto",
["ImageConverters"] = "conversores-de-imagem"
["ImageConverters"] = "conversores-de-imagem",
["DocumentConverters"] = "conversores-de-documento"
},
["es-MX"] = new Dictionary<string, string>
{
["TextTools"] = "herramientas-de-texto",
["ImageConverters"] = "convertidores-de-imagen"
["ImageConverters"] = "convertidores-de-imagen",
["DocumentConverters"] = "convertidores-de-documento"
},
["es-CL"] = new Dictionary<string, string>
{
["TextTools"] = "herramientas-de-texto",
["ImageConverters"] = "convertidores-de-imagen"
["ImageConverters"] = "convertidores-de-imagen",
["DocumentConverters"] = "convertidores-de-documento"
},
["es-PY"] = new Dictionary<string, string>
{
["TextTools"] = "herramientas-de-texto",
["ImageConverters"] = "convertidores-de-imagen"
["ImageConverters"] = "convertidores-de-imagen",
["DocumentConverters"] = "convertidores-de-documento"
}
};
@ -41,25 +45,33 @@ namespace Convert_It_Online.Services
{
["CaseConverter"] = "conversor-de-maiusculas-minusculas",
["JpgToWebp"] = "jpg-para-webp",
["HeicToJpg"] = "heic-para-jpg"
["HeicToJpg"] = "heic-para-jpg",
["PdfToText"] = "pdf-para-texto",
["PdfBarcodeLine"] = "linha-digitavel-do-pdf"
},
["es-MX"] = new Dictionary<string, string>
{
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
["JpgToWebp"] = "jpg-a-webp",
["HeicToJpg"] = "heic-a-jpg"
["HeicToJpg"] = "heic-a-jpg",
["PdfToText"] = "pdf-a-texto",
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
},
["es-CL"] = new Dictionary<string, string>
{
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
["JpgToWebp"] = "jpg-a-webp",
["HeicToJpg"] = "heic-a-jpg"
["HeicToJpg"] = "heic-a-jpg",
["PdfToText"] = "pdf-a-texto",
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
},
["es-PY"] = new Dictionary<string, string>
{
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
["JpgToWebp"] = "jpg-a-webp",
["HeicToJpg"] = "heic-a-jpg"
["HeicToJpg"] = "heic-a-jpg",
["PdfToText"] = "pdf-a-texto",
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
}
};

View File

@ -51,12 +51,21 @@
<data name="ImageMenuTitle" xml:space="preserve">
<value>Imagen</value>
</data>
<data name="DocumentMenuTitle" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="CaseConverterTitle" xml:space="preserve">
<value>Convertidor de Mayúsculas/Minúsculas</value>
</data>
<data name="JpgToWebpTitle" xml:space="preserve">
<value>JPG a WebP</value>
</data>
<data name="PdfToTextTitle" xml:space="preserve">
<value>PDF a Texto/Markdown</value>
</data>
<data name="PdfBarcodeTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<!-- Footer -->
<data name="FooterText" xml:space="preserve">
@ -113,12 +122,24 @@
<data name="UrlImageConverters" xml:space="preserve">
<value>convertidores-de-imagen</value>
</data>
<data name="UrlDocumentConverters" xml:space="preserve">
<value>convertidores-de-documento</value>
</data>
<data name="UrlCaseConverter" xml:space="preserve">
<value>conversor-de-mayusculas-minusculas</value>
</data>
<data name="UrlJpgToWebp" xml:space="preserve">
<value>jpg-a-webp</value>
</data>
<data name="UrlHeicToJpg" xml:space="preserve">
<value>heic-a-jpg</value>
</data>
<data name="UrlPdfToText" xml:space="preserve">
<value>pdf-a-texto</value>
</data>
<data name="UrlPdfBarcode" xml:space="preserve">
<value>linea-digitada-desde-pdf</value>
</data>
<!-- New individual converter resources - Mexican Spanish -->
<data name="AllConvertersTitle" xml:space="preserve">
@ -142,6 +163,18 @@
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
</data>
<data name="PdfToTextIndividualTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfToTextIndividualDescription" xml:space="preserve">
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
</data>
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
</data>
<!-- HEIC converter menu item -->
<data name="HeicToJpgTitle" xml:space="preserve">
@ -204,16 +237,167 @@
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
</data>
<!-- PDF to Text Converter Resources -->
<data name="PdfTextConverterPageTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfTextConverterPageDescription" xml:space="preserve">
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
</data>
<data name="PdfPlainTextTabTitle" xml:space="preserve">
<value>PDF → Texto simple</value>
</data>
<data name="PdfMarkdownTabTitle" xml:space="preserve">
<value>PDF → Markdown</value>
</data>
<data name="PdfFileInputLabel" xml:space="preserve">
<value>Seleccionar archivo PDF</value>
</data>
<data name="PdfPasswordLabel" xml:space="preserve">
<value>Contraseña del PDF</value>
</data>
<data name="PdfPasswordPlaceholder" xml:space="preserve">
<value>Ingresa la contraseña si el PDF está protegido</value>
</data>
<data name="PdfPasswordHint" xml:space="preserve">
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
</data>
<data name="ExtractPlainTextButton" xml:space="preserve">
<value>Extraer y visualizar</value>
</data>
<data name="ExtractMarkdownButton" xml:space="preserve">
<value>Extraer en Markdown</value>
</data>
<data name="DownloadPlainTextButton" xml:space="preserve">
<value>Descargar texto</value>
</data>
<data name="DownloadMarkdownButton" xml:space="preserve">
<value>Descargar Markdown</value>
</data>
<data name="PdfTextPreviewTitle" xml:space="preserve">
<value>Vista previa del contenido</value>
</data>
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
<value>¿Qué hace este conversor?</value>
</data>
<data name="PdfTextFaqWhatContent" xml:space="preserve">
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
</data>
<data name="PdfTextFaqHowTitle" xml:space="preserve">
<value>¿Cómo usar el conversor?</value>
</data>
<data name="PdfTextFaqHowContent" xml:space="preserve">
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
</data>
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
<value>¿Por qué convertir PDF a texto o Markdown?</value>
</data>
<data name="PdfTextFaqWhyContent" xml:space="preserve">
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
</data>
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
<value>¿Es seguro subir mis PDFs?</value>
</data>
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
</data>
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
<value>¿Existen límites de tamaño?</value>
</data>
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
</data>
<!-- PDF Barcode Converter Resources -->
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
<value>Extraer línea digitada de PDF</value>
</data>
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
</data>
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
<value>PDF → Ver línea digitada</value>
</data>
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
<value>PDF → Descargar línea digitada</value>
</data>
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
<value>Seleccionar PDF del boleto</value>
</data>
<data name="PdfBarcodePasswordHint" xml:space="preserve">
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
</data>
<data name="DetectBarcodeButton" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="DownloadBarcodeButton" xml:space="preserve">
<value>Descargar línea digitada</value>
</data>
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
<value>Línea digitada detectada</value>
</data>
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
<value>¿Qué es la línea digitada?</value>
</data>
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
</data>
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
<value>¿Cómo funciona el conversor?</value>
</data>
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
</data>
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
<value>¿Cuándo usar este recurso?</value>
</data>
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
</data>
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
<value>¿Mis datos están seguros?</value>
</data>
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
</data>
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
<value>¿Existen limitaciones?</value>
</data>
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
</data>
<!-- Error Messages -->
<data name="SelectFileError" xml:space="preserve">
<value>Por favor, selecciona un archivo para convertir.</value>
</data>
<data name="CopyButtonLabel" xml:space="preserve">
<value>Copiar</value>
</data>
<data name="CopyButtonSuccess" xml:space="preserve">
<value>¡Copiado!</value>
</data>
<data name="InvalidHeicFileError" xml:space="preserve">
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
</data>
<data name="InvalidJpgFileError" xml:space="preserve">
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
</data>
<data name="InvalidPdfFileError" xml:space="preserve">
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
</data>
<data name="PdfPasswordRequired" xml:space="preserve">
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
</data>
<data name="PdfInvalidPassword" xml:space="preserve">
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
</data>
<data name="PdfNoBarcodeFound" xml:space="preserve">
<value>No encontramos una línea digitada en este PDF.</value>
</data>
<data name="PdfPreviewTooLarge" xml:space="preserve">
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
</data>
<!-- JPG to WebP Converter Resources -->
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
<value>Convertidor JPG ↔ WebP Online Gratis</value>

View File

@ -51,12 +51,21 @@
<data name="ImageMenuTitle" xml:space="preserve">
<value>Imagen</value>
</data>
<data name="DocumentMenuTitle" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="CaseConverterTitle" xml:space="preserve">
<value>Convertidor de Mayúsculas/Minúsculas</value>
</data>
<data name="JpgToWebpTitle" xml:space="preserve">
<value>JPG a WebP</value>
</data>
<data name="PdfToTextTitle" xml:space="preserve">
<value>PDF a Texto/Markdown</value>
</data>
<data name="PdfBarcodeTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<!-- Footer -->
<data name="FooterText" xml:space="preserve">
@ -113,12 +122,21 @@
<data name="UrlImageConverters" xml:space="preserve">
<value>convertidores-de-imagen</value>
</data>
<data name="UrlDocumentConverters" xml:space="preserve">
<value>convertidores-de-documento</value>
</data>
<data name="UrlCaseConverter" xml:space="preserve">
<value>conversor-de-mayusculas-minusculas</value>
</data>
<data name="UrlJpgToWebp" xml:space="preserve">
<value>jpg-a-webp</value>
</data>
<data name="UrlPdfToText" xml:space="preserve">
<value>pdf-a-texto</value>
</data>
<data name="UrlPdfBarcode" xml:space="preserve">
<value>linea-digitada-desde-pdf</value>
</data>
<!-- New individual converter resources - Mexican Spanish -->
<data name="AllConvertersTitle" xml:space="preserve">
@ -142,6 +160,18 @@
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
</data>
<data name="PdfToTextIndividualTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfToTextIndividualDescription" xml:space="preserve">
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
</data>
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
</data>
<!-- HEIC converter menu item -->
<data name="HeicToJpgTitle" xml:space="preserve">
@ -204,16 +234,167 @@
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
</data>
<!-- PDF to Text Converter Resources -->
<data name="PdfTextConverterPageTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfTextConverterPageDescription" xml:space="preserve">
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
</data>
<data name="PdfPlainTextTabTitle" xml:space="preserve">
<value>PDF → Texto simple</value>
</data>
<data name="PdfMarkdownTabTitle" xml:space="preserve">
<value>PDF → Markdown</value>
</data>
<data name="PdfFileInputLabel" xml:space="preserve">
<value>Seleccionar archivo PDF</value>
</data>
<data name="PdfPasswordLabel" xml:space="preserve">
<value>Contraseña del PDF</value>
</data>
<data name="PdfPasswordPlaceholder" xml:space="preserve">
<value>Ingresa la contraseña si el PDF está protegido</value>
</data>
<data name="PdfPasswordHint" xml:space="preserve">
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
</data>
<data name="ExtractPlainTextButton" xml:space="preserve">
<value>Extraer y visualizar</value>
</data>
<data name="ExtractMarkdownButton" xml:space="preserve">
<value>Extraer en Markdown</value>
</data>
<data name="DownloadPlainTextButton" xml:space="preserve">
<value>Descargar texto</value>
</data>
<data name="DownloadMarkdownButton" xml:space="preserve">
<value>Descargar Markdown</value>
</data>
<data name="PdfTextPreviewTitle" xml:space="preserve">
<value>Vista previa del contenido</value>
</data>
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
<value>¿Qué hace este conversor?</value>
</data>
<data name="PdfTextFaqWhatContent" xml:space="preserve">
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
</data>
<data name="PdfTextFaqHowTitle" xml:space="preserve">
<value>¿Cómo usar el conversor?</value>
</data>
<data name="PdfTextFaqHowContent" xml:space="preserve">
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
</data>
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
<value>¿Por qué convertir PDF a texto o Markdown?</value>
</data>
<data name="PdfTextFaqWhyContent" xml:space="preserve">
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
</data>
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
<value>¿Es seguro subir mis PDFs?</value>
</data>
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
</data>
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
<value>¿Existen límites de tamaño?</value>
</data>
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
</data>
<!-- PDF Barcode Converter Resources -->
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
<value>Extraer línea digitada de PDF</value>
</data>
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
</data>
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
<value>PDF → Ver línea digitada</value>
</data>
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
<value>PDF → Descargar línea digitada</value>
</data>
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
<value>Seleccionar PDF del boleto</value>
</data>
<data name="PdfBarcodePasswordHint" xml:space="preserve">
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
</data>
<data name="DetectBarcodeButton" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="DownloadBarcodeButton" xml:space="preserve">
<value>Descargar línea digitada</value>
</data>
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
<value>Línea digitada detectada</value>
</data>
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
<value>¿Qué es la línea digitada?</value>
</data>
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
</data>
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
<value>¿Cómo funciona el conversor?</value>
</data>
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
</data>
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
<value>¿Cuándo usar este recurso?</value>
</data>
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
</data>
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
<value>¿Mis datos están seguros?</value>
</data>
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
</data>
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
<value>¿Existen limitaciones?</value>
</data>
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
</data>
<!-- Error Messages -->
<data name="SelectFileError" xml:space="preserve">
<value>Por favor, selecciona un archivo para convertir.</value>
</data>
<data name="CopyButtonLabel" xml:space="preserve">
<value>Copiar</value>
</data>
<data name="CopyButtonSuccess" xml:space="preserve">
<value>¡Copiado!</value>
</data>
<data name="InvalidHeicFileError" xml:space="preserve">
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
</data>
<data name="InvalidJpgFileError" xml:space="preserve">
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
</data>
<data name="InvalidPdfFileError" xml:space="preserve">
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
</data>
<data name="PdfPasswordRequired" xml:space="preserve">
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
</data>
<data name="PdfInvalidPassword" xml:space="preserve">
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
</data>
<data name="PdfNoBarcodeFound" xml:space="preserve">
<value>No encontramos una línea digitada en este PDF.</value>
</data>
<data name="PdfPreviewTooLarge" xml:space="preserve">
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
</data>
<!-- JPG to WebP Converter Resources -->
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
<value>Convertidor JPG ↔ WebP Online Gratis</value>

View File

@ -51,12 +51,21 @@
<data name="ImageMenuTitle" xml:space="preserve">
<value>Imagen</value>
</data>
<data name="DocumentMenuTitle" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="CaseConverterTitle" xml:space="preserve">
<value>Convertidor de Mayúsculas/Minúsculas</value>
</data>
<data name="JpgToWebpTitle" xml:space="preserve">
<value>JPG a WebP</value>
</data>
<data name="PdfToTextTitle" xml:space="preserve">
<value>PDF a Texto/Markdown</value>
</data>
<data name="PdfBarcodeTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<!-- Footer -->
<data name="FooterText" xml:space="preserve">
@ -113,12 +122,21 @@
<data name="UrlImageConverters" xml:space="preserve">
<value>convertidores-de-imagen</value>
</data>
<data name="UrlDocumentConverters" xml:space="preserve">
<value>convertidores-de-documento</value>
</data>
<data name="UrlCaseConverter" xml:space="preserve">
<value>conversor-de-mayusculas-minusculas</value>
</data>
<data name="UrlJpgToWebp" xml:space="preserve">
<value>jpg-a-webp</value>
</data>
<data name="UrlPdfToText" xml:space="preserve">
<value>pdf-a-texto</value>
</data>
<data name="UrlPdfBarcode" xml:space="preserve">
<value>linea-digitada-desde-pdf</value>
</data>
<!-- New individual converter resources - Mexican Spanish -->
<data name="AllConvertersTitle" xml:space="preserve">
@ -142,6 +160,18 @@
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
</data>
<data name="PdfToTextIndividualTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfToTextIndividualDescription" xml:space="preserve">
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
</data>
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
</data>
<!-- HEIC converter menu item -->
<data name="HeicToJpgTitle" xml:space="preserve">
@ -204,16 +234,167 @@
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
</data>
<!-- PDF to Text Converter Resources -->
<data name="PdfTextConverterPageTitle" xml:space="preserve">
<value>Extraer texto de PDF</value>
</data>
<data name="PdfTextConverterPageDescription" xml:space="preserve">
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
</data>
<data name="PdfPlainTextTabTitle" xml:space="preserve">
<value>PDF → Texto simple</value>
</data>
<data name="PdfMarkdownTabTitle" xml:space="preserve">
<value>PDF → Markdown</value>
</data>
<data name="PdfFileInputLabel" xml:space="preserve">
<value>Seleccionar archivo PDF</value>
</data>
<data name="PdfPasswordLabel" xml:space="preserve">
<value>Contraseña del PDF</value>
</data>
<data name="PdfPasswordPlaceholder" xml:space="preserve">
<value>Ingresa la contraseña si el PDF está protegido</value>
</data>
<data name="PdfPasswordHint" xml:space="preserve">
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
</data>
<data name="ExtractPlainTextButton" xml:space="preserve">
<value>Extraer y visualizar</value>
</data>
<data name="ExtractMarkdownButton" xml:space="preserve">
<value>Extraer en Markdown</value>
</data>
<data name="DownloadPlainTextButton" xml:space="preserve">
<value>Descargar texto</value>
</data>
<data name="DownloadMarkdownButton" xml:space="preserve">
<value>Descargar Markdown</value>
</data>
<data name="PdfTextPreviewTitle" xml:space="preserve">
<value>Vista previa del contenido</value>
</data>
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
<value>¿Qué hace este conversor?</value>
</data>
<data name="PdfTextFaqWhatContent" xml:space="preserve">
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
</data>
<data name="PdfTextFaqHowTitle" xml:space="preserve">
<value>¿Cómo usar el conversor?</value>
</data>
<data name="PdfTextFaqHowContent" xml:space="preserve">
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
</data>
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
<value>¿Por qué convertir PDF a texto o Markdown?</value>
</data>
<data name="PdfTextFaqWhyContent" xml:space="preserve">
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
</data>
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
<value>¿Es seguro subir mis PDFs?</value>
</data>
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
</data>
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
<value>¿Existen límites de tamaño?</value>
</data>
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
</data>
<!-- PDF Barcode Converter Resources -->
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
<value>Extraer línea digitada de PDF</value>
</data>
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
</data>
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
<value>PDF → Ver línea digitada</value>
</data>
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
<value>PDF → Descargar línea digitada</value>
</data>
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
<value>Seleccionar PDF del boleto</value>
</data>
<data name="PdfBarcodePasswordHint" xml:space="preserve">
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
</data>
<data name="DetectBarcodeButton" xml:space="preserve">
<value>Detectar línea digitada</value>
</data>
<data name="DownloadBarcodeButton" xml:space="preserve">
<value>Descargar línea digitada</value>
</data>
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
<value>Línea digitada detectada</value>
</data>
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
<value>¿Qué es la línea digitada?</value>
</data>
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
</data>
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
<value>¿Cómo funciona el conversor?</value>
</data>
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
</data>
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
<value>¿Cuándo usar este recurso?</value>
</data>
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
</data>
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
<value>¿Mis datos están seguros?</value>
</data>
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
</data>
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
<value>¿Existen limitaciones?</value>
</data>
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
</data>
<!-- Error Messages -->
<data name="SelectFileError" xml:space="preserve">
<value>Por favor, selecciona un archivo para convertir.</value>
</data>
<data name="CopyButtonLabel" xml:space="preserve">
<value>Copiar</value>
</data>
<data name="CopyButtonSuccess" xml:space="preserve">
<value>¡Copiado!</value>
</data>
<data name="InvalidHeicFileError" xml:space="preserve">
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
</data>
<data name="InvalidJpgFileError" xml:space="preserve">
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
</data>
<data name="InvalidPdfFileError" xml:space="preserve">
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
</data>
<data name="PdfPasswordRequired" xml:space="preserve">
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
</data>
<data name="PdfInvalidPassword" xml:space="preserve">
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
</data>
<data name="PdfNoBarcodeFound" xml:space="preserve">
<value>No encontramos una línea digitada en este PDF.</value>
</data>
<data name="PdfPreviewTooLarge" xml:space="preserve">
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
</data>
<!-- JPG to WebP Converter Resources -->
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
<value>Convertidor JPG ↔ WebP Online Gratis</value>

View File

@ -183,12 +183,21 @@
<data name="ImageMenuTitle" xml:space="preserve">
<value>Imagem</value>
</data>
<data name="DocumentMenuTitle" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="CaseConverterTitle" xml:space="preserve">
<value>Conversor de Maiúsculas/Minúsculas</value>
</data>
<data name="JpgToWebpTitle" xml:space="preserve">
<value>JPG para WebP</value>
</data>
<data name="PdfToTextTitle" xml:space="preserve">
<value>PDF para Texto/Markdown</value>
</data>
<data name="PdfBarcodeTitle" xml:space="preserve">
<value>Extrair Linha Digitável</value>
</data>
<!-- Footer Resources -->
<data name="FooterText" xml:space="preserve">
@ -211,12 +220,24 @@
<data name="UrlImageConverters" xml:space="preserve">
<value>conversores-de-imagem</value>
</data>
<data name="UrlDocumentConverters" xml:space="preserve">
<value>conversores-de-documento</value>
</data>
<data name="UrlCaseConverter" xml:space="preserve">
<value>conversor-de-maiusculas-minusculas</value>
</data>
<data name="UrlJpgToWebp" xml:space="preserve">
<value>jpg-para-webp</value>
</data>
<data name="UrlHeicToJpg" xml:space="preserve">
<value>heic-para-jpg</value>
</data>
<data name="UrlPdfToText" xml:space="preserve">
<value>pdf-para-texto</value>
</data>
<data name="UrlPdfBarcode" xml:space="preserve">
<value>linha-digitavel-do-pdf</value>
</data>
<!-- FAQ Resources for Contact Page -->
<data name="Faq1Question" xml:space="preserve">
@ -254,6 +275,18 @@
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
<value>Converta fotos HEIC do iPhone para o formato JPG universalmente compatível.</value>
</data>
<data name="PdfToTextIndividualTitle" xml:space="preserve">
<value>Extrair Texto de PDF</value>
</data>
<data name="PdfToTextIndividualDescription" xml:space="preserve">
<value>Transforme PDFs protegidos ou não em texto simples ou Markdown em poucos segundos.</value>
</data>
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
<value>Detectar Linha Digitável</value>
</data>
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
<value>Identifique a linha digitável de boletos diretamente do PDF, mesmo quando há proteção por senha.</value>
</data>
<!-- HEIC converter menu item -->
<data name="HeicToJpgTitle" xml:space="preserve">
@ -316,6 +349,136 @@
<value>Não há limites para a quantidade de conversões. Para garantir um bom desempenho, recomendamos arquivos de até 10MB. O processamento é mais rápido em computadores modernos.</value>
</data>
<!-- PDF to Text Converter Resources -->
<data name="PdfTextConverterPageTitle" xml:space="preserve">
<value>Extrair Texto de PDF</value>
</data>
<data name="PdfTextConverterPageDescription" xml:space="preserve">
<value>Converta seus PDFs em texto simples ou Markdown, com suporte a arquivos protegidos por senha.</value>
</data>
<data name="PdfPlainTextTabTitle" xml:space="preserve">
<value>PDF → Texto Simples</value>
</data>
<data name="PdfMarkdownTabTitle" xml:space="preserve">
<value>PDF → Markdown</value>
</data>
<data name="PdfFileInputLabel" xml:space="preserve">
<value>Selecionar arquivo PDF</value>
</data>
<data name="PdfPasswordLabel" xml:space="preserve">
<value>Senha do PDF</value>
</data>
<data name="PdfPasswordPlaceholder" xml:space="preserve">
<value>Informe a senha, se o PDF estiver protegido</value>
</data>
<data name="PdfPasswordHint" xml:space="preserve">
<value>Se o PDF exigir senha de abertura, digite-a abaixo. Caso contrário, deixe em branco.</value>
</data>
<data name="ExtractPlainTextButton" xml:space="preserve">
<value>Extrair e Visualizar</value>
</data>
<data name="ExtractMarkdownButton" xml:space="preserve">
<value>Extrair em Markdown</value>
</data>
<data name="DownloadPlainTextButton" xml:space="preserve">
<value>Baixar Texto</value>
</data>
<data name="DownloadMarkdownButton" xml:space="preserve">
<value>Baixar Markdown</value>
</data>
<data name="PdfTextPreviewTitle" xml:space="preserve">
<value>Pré-visualização do Conteúdo</value>
</data>
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
<value>O que faz este conversor PDF para Texto?</value>
</data>
<data name="PdfTextFaqWhatContent" xml:space="preserve">
<value>Ele extrai o conteúdo textual do seu PDF e o disponibiliza como texto simples ou Markdown, preservando quebras de linha e seções sempre que possível.</value>
</data>
<data name="PdfTextFaqHowTitle" xml:space="preserve">
<value>Como usar o conversor?</value>
</data>
<data name="PdfTextFaqHowContent" xml:space="preserve">
<value>Selecione o arquivo PDF, informe a senha se necessário e escolha a aba desejada. Visualize o resultado online ou faça o download direto.</value>
</data>
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
<value>Por que converter PDF para texto ou Markdown?</value>
</data>
<data name="PdfTextFaqWhyContent" xml:space="preserve">
<value>Texto simples facilita edições rápidas, enquanto Markdown permite reaproveitar o conteúdo em blogs, documentações e geradores de site estático.</value>
</data>
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
<value>É seguro enviar meus PDFs?</value>
</data>
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
<value>Sim. O processamento ocorre seguindo políticas rígidas de privacidade e os arquivos são descartados após a conversão.</value>
</data>
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
<value>Existem limites de tamanho?</value>
</data>
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
<value>Para pré-visualização recomendamos PDFs de até 10MB. Conteúdos maiores podem ser baixados diretamente em texto ou Markdown.</value>
</data>
<!-- PDF Barcode Converter Resources -->
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
<value>Extrair Linha Digitável de PDF</value>
</data>
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
<value>Detecte a linha digitável de boletos ou faturas diretamente a partir do PDF, com suporte a arquivos protegidos.</value>
</data>
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
<value>PDF → Visualizar Linha Digitável</value>
</data>
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
<value>PDF → Baixar Linha Digitável</value>
</data>
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
<value>Selecionar PDF do boleto</value>
</data>
<data name="PdfBarcodePasswordHint" xml:space="preserve">
<value>Caso o PDF esteja protegido por senha, informe-a abaixo para permitir a leitura.</value>
</data>
<data name="DetectBarcodeButton" xml:space="preserve">
<value>Detectar Linha Digitável</value>
</data>
<data name="DownloadBarcodeButton" xml:space="preserve">
<value>Baixar Linha Digitável</value>
</data>
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
<value>Linha Digitável Detectada</value>
</data>
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
<value>O que é a linha digitável?</value>
</data>
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
<value>É a sequência numérica que representa o código de barras de boletos e faturas, usada para pagamentos em bancos e aplicativos.</value>
</data>
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
<value>Como o conversor funciona?</value>
</data>
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
<value>O sistema extrai o texto do PDF, identifica padrões numéricos de boletos (47 ou 48 dígitos) e exibe a linha digitável pronta para copiar ou baixar.</value>
</data>
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
<value>Quando usar este recurso?</value>
</data>
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
<value>Ideal para boletos sem código de barras legível ou quando você precisa copiar a linha digitável para pagamento online rapidamente.</value>
</data>
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
<value>Meus dados ficam seguros?</value>
</data>
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
<value>Sim. O PDF é processado apenas para extrair a linha digitável e o conteúdo é descartado logo após a operação.</value>
</data>
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
<value>Existe algum limite?</value>
</data>
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
<value>Para melhores resultados, utilize PDFs com texto selecionável. Boletos apenas com imagem podem exigir uma solução OCR externa.</value>
</data>
<!-- JPG to WebP Converter Resources -->
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
<value>Conversor JPG ↔ WebP Online Grátis</value>
@ -370,6 +533,12 @@
<data name="UseConverterButton" xml:space="preserve">
<value>Acessar Conversor</value>
</data>
<data name="CopyButtonLabel" xml:space="preserve">
<value>Copiar</value>
</data>
<data name="CopyButtonSuccess" xml:space="preserve">
<value>Copiado!</value>
</data>
<!-- Error Messages -->
<data name="SelectFileError" xml:space="preserve">
@ -384,6 +553,21 @@
<data name="JpgToHeicNotAvailableError" xml:space="preserve">
<value>A conversão de JPG para HEIC ainda não está disponível. Esta funcionalidade será implementada em breve.</value>
</data>
<data name="InvalidPdfFileError" xml:space="preserve">
<value>Arquivo PDF inválido, corrompido ou protegido por recursos não suportados.</value>
</data>
<data name="PdfPasswordRequired" xml:space="preserve">
<value>Este PDF está protegido. Informe a senha para continuar.</value>
</data>
<data name="PdfInvalidPassword" xml:space="preserve">
<value>A senha informada não conseguiu desbloquear o PDF.</value>
</data>
<data name="PdfNoBarcodeFound" xml:space="preserve">
<value>Não encontramos uma linha digitável neste PDF.</value>
</data>
<data name="PdfPreviewTooLarge" xml:space="preserve">
<value>O conteúdo é muito grande para pré-visualização. Faça o download direto.</value>
</data>
<data name="ProcessingError" xml:space="preserve">
<value>Ocorreu um erro ao processar sua solicitação. Tente novamente mais tarde.</value>
</data>

View File

@ -1,11 +1,21 @@
@using Microsoft.AspNetCore.Localization
@using Microsoft.Extensions.Options
@using Microsoft.Extensions.Configuration
@inject IOptions<RequestLocalizationOptions> LocOptions
@inject IConfiguration Configuration
@{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var supportedCultures = LocOptions.Value.SupportedCultures;
// Configuração de Ads
var adSection = Configuration.GetSection("Ads");
var adEnabled = adSection.GetValue<bool>("Enabled");
var adProvider = adSection.GetValue<string>("Provider"); // "House" ou "Google"
var googlePublisherId = adSection.GetValue<string>("GooglePublisherId");
var slotTop = adSection.GetValue<string>("Slots:Top");
var slotSide = adSection.GetValue<string>("Slots:Side");
}
<!DOCTYPE html>
@ -13,11 +23,18 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="@(ViewBag.MetaDescription ?? ViewBag.PageDescription ?? ViewBag.Subtitle ?? ViewBag.PageTitle ?? "Convert-It Online")" />
<title>@ViewData["Title"] - Convert-It Online</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="~/css/site.css" />
@if (adEnabled && adProvider == "Google" && !string.IsNullOrEmpty(googlePublisherId))
{
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@googlePublisherId"
crossorigin="anonymous"></script>
}
</head>
<body>
<header>
@ -42,6 +59,19 @@
</a></li>
</ul>
</li>
<li class="nav-item dropdown mx-2">
<a class="nav-link dropdown-toggle" href="#" id="documentToolsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-file-earmark-text me-1"></i>@ViewBag.DocumentMenuTitle
</a>
<ul class="dropdown-menu" aria-labelledby="documentToolsDropdown">
<li><a class="dropdown-item" href="@Html.LocalizedUrl("DocumentConverters", "PdfToText")">
<i class="bi bi-file-text me-2"></i>@ViewBag.PdfToTextTitle
</a></li>
<li><a class="dropdown-item" href="@Html.LocalizedUrl("DocumentConverters", "PdfBarcodeLine")">
<i class="bi bi-upc-scan me-2"></i>@ViewBag.PdfBarcodeTitle
</a></li>
</ul>
</li>
<li class="nav-item dropdown mx-2">
<a class="nav-link dropdown-toggle" href="#" id="imageToolsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-image-alt me-1"></i>@ViewBag.ImageMenuTitle
@ -84,17 +114,98 @@
<div class="container mt-4">
<div class="row">
<div class="col-lg-9">
<div class="ad-placeholder ad-top d-none d-lg-flex">
<span>Ad Placeholder (Top)</span>
<!-- AD TOP -->
@if (adEnabled)
{
@if (adProvider == "House")
{
<a href="https://qrrapido.site" target="_blank" class="house-ad-top text-decoration-none d-none d-lg-block">
<div class="ad-content">
<div class="qr-animation">
<div class="qr-box"></div>
<div class="scan-line"></div>
</div>
<div class="ad-text">
<span class="badge bg-warning text-dark mb-1">NOVO</span>
<h5>Gerador de QR Code Ultrarrápido</h5>
<p>Crie QR Codes para WiFi, Links e Pix em 1 segundo. Grátis!</p>
</div>
<div class="cta-button">
Criar Agora <i class="bi bi-arrow-right"></i>
</div>
</div>
</a>
}
else if (adProvider == "Google" && !string.IsNullOrEmpty(slotTop))
{
<div class="d-none d-lg-block mb-4" style="min-height: 90px; text-align:center;">
<!-- Topo -->
<ins class="adsbygoogle"
style="display:block"
data-ad-client="@googlePublisherId"
data-ad-slot="@slotTop"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
</div>
}
else
{
<!-- Placeholder visível apenas se não houver ad configurado e estiver em debug/dev se desejado -->
<!-- <div class="ad-placeholder ad-top d-none d-lg-flex"><span>Ad Space</span></div> -->
}
}
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<div class="col-lg-3 d-none d-lg-block">
<div class="ad-placeholder ad-side">
<span>Ad Placeholder (Side)</span>
<!-- AD SIDE -->
@if (adEnabled)
{
@if (adProvider == "House")
{
<a href="https://jobmaker.com.br" target="_blank" class="house-ad-side text-decoration-none">
<div class="terminal-header">
<span class="dot red"></span><span class="dot yellow"></span><span class="dot green"></span>
</div>
<div class="ad-body">
<div class="code-typing">
<span class="comment">// Precisa integrar sistemas?</span><br>
<span class="keyword">await</span> JobMaker.<span class="method">Automate</span>(legacy_sys);<br>
<span class="success">>> Processo Otimizado!</span>
</div>
<div class="mt-4">
<h5>Especialistas em .NET e Integrações</h5>
<ul class="list-unstyled text-small text-muted mt-2">
<li><i class="bi bi-check2"></i> APIs & Microserviços</li>
<li><i class="bi bi-check2"></i> Robôs de Dados</li>
<li><i class="bi bi-check2"></i> Modernização de Legado</li>
</ul>
<button class="btn btn-outline-primary btn-sm w-100 mt-3">Fale Conosco</button>
</div>
</div>
</a>
}
else if (adProvider == "Google" && !string.IsNullOrEmpty(slotSide))
{
<div class="mt-4 sticky-top" style="top: 2rem; min-height: 600px;">
<!-- Lateral -->
<ins class="adsbygoogle"
style="display:block"
data-ad-client="@googlePublisherId"
data-ad-slot="@slotSide"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
</div>
}
else
{
<!-- <div class="ad-placeholder ad-side"><span>Ad Space</span></div> -->
}
}
</div>
</div>
</div>

View File

@ -9,5 +9,14 @@
"AllowedHosts": "*",
"Serilog": {
"OpenSearchUrl": ""
},
"Ads": {
"Enabled": true,
"Provider": "House",
"GooglePublisherId": "pub-3475956393038764",
"Slots": {
"Top": "0000000000",
"Side": "0000000000"
}
}
}

View File

@ -431,3 +431,190 @@ h1, h2, h3, h4, h5, h6, p, div, span, a {
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* ========== HOUSE ADS (BANNERS PRÓPRIOS) ========== */
/* Banner Topo - QR Rápido */
.house-ad-top {
display: block;
background: linear-gradient(135deg, #6610f2 0%, #d63384 100%);
border-radius: 1rem;
padding: 1rem 2rem;
color: white !important;
position: relative;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
margin-bottom: 2rem;
border: none;
text-decoration: none !important;
}
.house-ad-top:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 16, 242, 0.3);
color: white !important;
}
.house-ad-top .ad-content {
display: flex;
align-items: center;
justify-content: space-between;
z-index: 2;
position: relative;
}
.house-ad-top .qr-animation {
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1.5rem;
}
.house-ad-top .qr-box {
width: 40px;
height: 40px;
border: 3px solid white;
border-radius: 4px;
position: relative;
}
.house-ad-top .qr-box::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16px;
height: 16px;
background: white;
}
.house-ad-top .scan-line {
position: absolute;
width: 100%;
height: 2px;
background: #00ff00;
top: 10%;
left: 0;
animation: scanMove 1.5s infinite alternate ease-in-out;
box-shadow: 0 0 4px #00ff00;
}
@keyframes scanMove {
0% { top: 10%; }
100% { top: 90%; }
}
.house-ad-top .ad-text h5 {
margin: 0;
font-weight: 700;
font-size: 1.25rem;
color: white !important;
}
.house-ad-top .ad-text p {
margin: 0;
font-size: 0.9rem;
opacity: 0.9;
color: white !important;
}
.house-ad-top .cta-button {
background: white;
color: #6610f2;
padding: 0.5rem 1rem;
border-radius: 2rem;
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Banner Lateral - JobMaker */
.house-ad-side {
display: block;
background-color: #0f172a; /* Slate 900 */
border-radius: 1rem;
border: 1px solid #1e293b;
color: #e2e8f0 !important;
overflow: hidden;
position: sticky;
top: 2rem;
text-decoration: none !important;
transition: border-color 0.3s ease;
min-height: auto; /* Remove min-height fixo se necessário */
}
.house-ad-side:hover {
border-color: #3b82f6;
color: #e2e8f0 !important;
}
.house-ad-side .terminal-header {
background: #1e293b;
padding: 0.75rem 1rem;
display: flex;
gap: 6px;
border-bottom: 1px solid #334155;
}
.house-ad-side .dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.house-ad-side .dot.red { background: #ef4444; }
.house-ad-side .dot.yellow { background: #f59e0b; }
.house-ad-side .dot.green { background: #22c55e; }
.house-ad-side .ad-body {
padding: 1.5rem;
font-family: 'Consolas', 'Monaco', monospace;
}
.house-ad-side .code-typing {
font-size: 0.85rem;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.house-ad-side .comment { color: #64748b; }
.house-ad-side .keyword { color: #c084fc; }
.house-ad-side .method { color: #60a5fa; }
.house-ad-side .success { color: #4ade80; display: block; margin-top: 0.5rem; font-weight: bold;}
.house-ad-side h5 {
font-family: system-ui, -apple-system, sans-serif;
color: white !important;
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.house-ad-side .text-muted {
color: #94a3b8 !important; /* Slate 400 */
}
.house-ad-side .btn-outline-primary {
border-color: #3b82f6;
color: #3b82f6;
}
.house-ad-side .btn-outline-primary:hover {
background-color: #3b82f6;
color: white;
}
/* Responsividade para o House Ad Top */
@media (max-width: 992px) {
.house-ad-top {
display: none;
}
}