feat: Add internationalization support and introduce a new PDF multi-tool.

This commit is contained in:
cxllxc
2025-12-12 12:54:06 +00:00
parent 3c0fbc8f8f
commit ab122e19df
7 changed files with 186 additions and 48 deletions

View File

@@ -397,8 +397,8 @@
<div class="mb-4 p-3 bg-indigo-900/20 border border-indigo-500/30 rounded-lg flex items-start gap-3">
<i data-lucide="info" class="w-5 h-5 text-indigo-400 flex-shrink-0 mt-0.5"></i>
<p class="text-sm text-indigo-200">
Press and hold keys to set a shortcut. Changes are <strong>auto-saved</strong>.
<br><span class="text-yellow-300">⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N
<span data-i18n="settings.shortcutsInfo">Press and hold keys to set a shortcut. Changes are <strong>auto-saved</strong>.</span>
<br><span class="text-yellow-300" data-i18n="settings.shortcutsWarning">⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N
etc.) as they
may not work reliably.</span>
</p>

View File

@@ -271,5 +271,48 @@
"licensing": {
"title": "Lizenzierung für",
"subtitle": "Wählen Sie die Lizenz, die Ihren Anforderungen entspricht."
},
"multiTool": {
"uploadPdfs": "PDFs hochladen",
"upload": "Hochladen",
"addBlankPage": "Leere Seite hinzufügen",
"edit": "Bearbeiten:",
"undo": "Rückgängig",
"redo": "Wiederholen",
"reset": "Zurücksetzen",
"selection": "Auswahl:",
"selectAll": "Alles auswählen",
"deselectAll": "Auswahl aufheben",
"rotate": "Drehen:",
"rotateLeft": "Links",
"rotateRight": "Rechts",
"transform": "Transformieren:",
"duplicate": "Duplizieren",
"split": "Teilen",
"clear": "Löschen:",
"delete": "Entfernen",
"download": "Download:",
"downloadSelected": "Auswahl herunterladen",
"exportPdf": "PDF exportieren",
"uploadPdfFiles": "PDF-Dateien hochladen",
"dragAndDrop": "PDF-Dateien hierhin ziehen oder klicken zum Auswählen",
"selectFiles": "Dateien auswählen",
"renderingPages": "Seiten werden gerendert...",
"actions": {
"duplicatePage": "Diese Seite duplizieren",
"deletePage": "Diese Seite löschen",
"insertPdf": "PDF nach dieser Seite einfügen",
"toggleSplit": "Trennung nach dieser Seite umschalten"
},
"pleaseWait": "Bitte warten",
"pagesRendering": "Seiten werden noch gerendert. Bitte warten...",
"noPagesSelected": "Keine Seiten ausgewählt",
"selectOnePage": "Bitte wählen Sie mindestens eine Seite zum Herunterladen aus.",
"noPages": "Keine Seiten",
"noPagesToExport": "Keine Seiten zum Exportieren vorhanden.",
"renderingTitle": "Seiten-Vorschau wird gerendert",
"errorRendering": "Fehler beim Rendern der Seitenvorschau",
"error": "Fehler",
"failedToLoad": "Laden fehlgeschlagen"
}
}

View File

@@ -271,5 +271,48 @@
"licensing": {
"title": "Licensing for",
"subtitle": "Choose the license that fits your needs."
},
"multiTool": {
"uploadPdfs": "Upload PDFs",
"upload": "Upload",
"addBlankPage": "Add Blank Page",
"edit": "Edit:",
"undo": "Undo",
"redo": "Redo",
"reset": "Reset",
"selection": "Selection:",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"rotate": "Rotate:",
"rotateLeft": "Left",
"rotateRight": "Right",
"transform": "Transform:",
"duplicate": "Duplicate",
"split": "Split",
"clear": "Clear:",
"delete": "Delete",
"download": "Download:",
"downloadSelected": "Download Selected",
"exportPdf": "Export PDF",
"uploadPdfFiles": "Upload PDF Files",
"dragAndDrop": "Drag and drop PDF files here, or click to select",
"selectFiles": "Select Files",
"renderingPages": "Rendering pages...",
"actions": {
"duplicatePage": "Duplicate this page",
"deletePage": "Delete this page",
"insertPdf": "Insert PDF after this page",
"toggleSplit": "Toggle split after this page"
},
"pleaseWait": "Please Wait",
"pagesRendering": "Pages are still being rendered. Please wait...",
"noPagesSelected": "No Pages Selected",
"selectOnePage": "Please select at least one page to download.",
"noPages": "No Pages",
"noPagesToExport": "There are no pages to export.",
"renderingTitle": "Rendering page previews",
"errorRendering": "Failed to render page thumbnails",
"error": "Error",
"failedToLoad": "Failed to load"
}
}

View File

@@ -271,5 +271,48 @@
"licensing": {
"title": "许可适用",
"subtitle": "选择适合您需求的许可。"
},
"multiTool": {
"uploadPdfs": "上传 PDF",
"upload": "上传",
"addBlankPage": "添加空白页",
"edit": "编辑:",
"undo": "撤销",
"redo": "重做",
"reset": "重置",
"selection": "选择:",
"selectAll": "全选",
"deselectAll": "取消全选",
"rotate": "旋转:",
"rotateLeft": "向左",
"rotateRight": "向右",
"transform": "变换:",
"duplicate": "复制",
"split": "拆分",
"clear": "清除:",
"delete": "删除",
"download": "下载:",
"downloadSelected": "下载选中",
"exportPdf": "导出 PDF",
"uploadPdfFiles": "上传 PDF 文件",
"dragAndDrop": "将 PDF 文件拖放到此处,或点击选择",
"selectFiles": "选择文件",
"renderingPages": "正在渲染页面...",
"actions": {
"duplicatePage": "复制此页",
"deletePage": "删除此页",
"insertPdf": "在此页后插入 PDF",
"toggleSplit": "在此页后切换拆分"
},
"pleaseWait": "请稍候",
"pagesRendering": "页面正在渲染中,请稍候...",
"noPagesSelected": "未选择页面",
"selectOnePage": "请至少选择一页以进行下载。",
"noPages": "没有页面",
"noPagesToExport": "没有可导出的页面。",
"renderingTitle": "正在渲染页面预览",
"errorRendering": "渲染页面缩略图失败",
"error": "错误",
"failedToLoad": "加载失败"
}
}

View File

@@ -13,6 +13,8 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
import.meta.url
).toString();
import { t } from '../i18n/i18n';
interface PageData {
id: string; // Unique ID for DOM reconciliation
pdfIndex: number;
@@ -107,7 +109,7 @@ function showLoading(current: number, total: number) {
loader.classList.remove('hidden');
const percentage = Math.round((current / total) * 100);
progress.style.width = `${percentage}%`;
text.textContent = `Rendering pages...`;
text.textContent = t('multiTool.renderingPages');
}
async function withButtonLoading(buttonId: string, action: () => Promise<void>) {
@@ -158,7 +160,7 @@ function initializeTool() {
document.getElementById('upload-pdfs-btn')?.addEventListener('click', () => {
console.log('Upload button clicked, isRendering:', isRendering);
if (isRendering) {
showModal('Please Wait', 'Pages are still being rendered. Please wait...', 'info');
showModal(t('multiTool.pleaseWait'), t('multiTool.pagesRendering'), 'info');
return;
}
document.getElementById('pdf-file-input')?.click();
@@ -193,9 +195,10 @@ function initializeTool() {
bulkSplit();
});
document.getElementById('bulk-download-btn')?.addEventListener('click', () => {
if (isRendering) return;
if (isRendering) return;
if (selectedPages.size === 0) {
showModal('No Pages Selected', 'Please select at least one page to download.', 'info');
showModal(t('multiTool.noPagesSelected'), t('multiTool.selectOnePage'), 'info');
return;
}
withButtonLoading('bulk-download-btn', async () => {
@@ -216,9 +219,10 @@ function initializeTool() {
});
document.getElementById('export-pdf-btn')?.addEventListener('click', () => {
if (isRendering) return;
if (isRendering) return;
if (allPages.length === 0) {
showModal('No Pages', 'There are no pages to export.', 'info');
showModal(t('multiTool.noPages'), t('multiTool.noPagesToExport'), 'info');
return;
}
withButtonLoading('export-pdf-btn', async () => {
@@ -329,7 +333,7 @@ async function handlePdfUpload(e: Event) {
async function loadPdfs(files: File[]) {
if (isRendering) {
showModal('Please Wait', 'Pages are still being rendered. Please wait...', 'info');
showModal(t('multiTool.pleaseWait'), t('multiTool.pagesRendering'), 'info');
return;
}
@@ -424,7 +428,7 @@ async function loadPdfs(files: File[]) {
} catch (e) {
console.error(`Failed to load PDF ${file.name}:`, e);
showModal('Error', `Failed to load ${file.name}. The file may be corrupted.`, 'error');
showModal(t('multiTool.error'), `${t('multiTool.failedToLoad')} ${file.name}.`, 'error');
}
}
@@ -501,7 +505,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
loading.className = 'flex flex-col items-center justify-center text-gray-400';
loading.innerHTML = `
<i data-lucide="loader" class="w-8 h-8 animate-spin mb-2"></i>
<span class="text-xs">Loading...</span>
<span class="text-xs">${t('common.loading')}</span>
`;
preview.appendChild(loading);
preview.classList.add('bg-gray-700'); // Darker background for loading
@@ -510,7 +514,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
// Page info
const info = document.createElement('div');
info.className = 'text-xs text-gray-400 text-center mb-2';
info.textContent = `Page ${index + 1}`;
info.textContent = `${t('common.page')} ${index + 1}`;
// Actions toolbar
const actions = document.createElement('div');
@@ -551,7 +555,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
const duplicateBtn = document.createElement('button');
duplicateBtn.className = 'p-1 rounded hover:bg-gray-700';
duplicateBtn.innerHTML = '<i data-lucide="copy" class="w-4 h-4 text-gray-300"></i>';
duplicateBtn.title = 'Duplicate this page';
duplicateBtn.title = t('multiTool.actions.duplicatePage');
duplicateBtn.onclick = (e) => {
e.stopPropagation();
snapshot();
@@ -562,7 +566,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
const deleteBtn = document.createElement('button');
deleteBtn.className = 'p-1 rounded hover:bg-gray-700';
deleteBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4 text-red-400"></i>';
deleteBtn.title = 'Delete this page';
deleteBtn.title = t('multiTool.actions.deletePage');
deleteBtn.onclick = (e) => {
e.stopPropagation();
snapshot();
@@ -573,7 +577,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
const insertBtn = document.createElement('button');
insertBtn.className = 'p-1 rounded hover:bg-gray-700';
insertBtn.innerHTML = '<i data-lucide="file-plus" class="w-4 h-4 text-gray-300"></i>';
insertBtn.title = 'Insert PDF after this page';
insertBtn.title = t('multiTool.actions.insertPdf');
insertBtn.onclick = (e) => {
e.stopPropagation();
snapshot();
@@ -584,7 +588,7 @@ function createPageElement(canvas: HTMLCanvasElement | null, index: number): HTM
const splitBtn = document.createElement('button');
splitBtn.className = 'p-1 rounded hover:bg-gray-700';
splitBtn.innerHTML = '<i data-lucide="scissors" class="w-4 h-4 text-gray-300"></i>';
splitBtn.title = 'Toggle split after this page';
splitBtn.title = t('multiTool.actions.toggleSplit');
splitBtn.onclick = (e) => {
e.stopPropagation();
snapshot();

View File

@@ -6,6 +6,7 @@ import { icons, createIcons } from 'lucide';
import Sortable from 'sortablejs';
import { getRotationState, updateRotationState } from './utils/rotation-state.js';
import * as pdfjsLib from 'pdfjs-dist';
import { t } from './i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
@@ -43,7 +44,7 @@ export const dom = {
warningConfirmBtn: document.getElementById('warning-confirm-btn'),
};
export const showLoader = (text = 'Processing...') => {
export const showLoader = (text = t('common.loading')) => {
if (dom.loaderText) dom.loaderText.textContent = text;
if (dom.loaderModal) dom.loaderModal.classList.remove('hidden');
};
@@ -152,7 +153,7 @@ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => {
const currentRenderId = Date.now();
container.dataset.renderId = currentRenderId.toString();
showLoader('Rendering page previews...');
showLoader(t('multiTool.renderingTitle'));
const pdfData = await pdfDoc.save();
const pdf = await getPDFDocument({ data: pdfData }).promise;
@@ -373,7 +374,7 @@ export const renderPageThumbnails = async (toolId: any, pdfDoc: any) => {
createIcons({ icons });
} catch (error) {
console.error('Error rendering page thumbnails:', error);
showAlert('Error', 'Failed to render page thumbnails');
showAlert(t('multiTool.error'), t('multiTool.errorRendering'));
} finally {
hideLoader();
}
@@ -418,9 +419,9 @@ const createFileInputHTML = (options = {}) => {
<div id="drop-zone" class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-900 hover:bg-gray-700 transition-colors duration-300">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<i data-lucide="upload-cloud" class="w-10 h-10 mb-3 text-gray-400"></i>
<p class="mb-2 text-sm text-gray-400"><span class="font-semibold">Click to select a file</span> or drag and drop</p>
<p class="text-xs text-gray-500">${multiple ? 'PDFs or Images' : 'A single PDF file'}</p>
<p class="text-xs text-gray-500">Your files never leave your device.</p>
<p class="mb-2 text-sm text-gray-400"><span class="font-semibold">${t('upload.clickToSelect')}</span> ${t('upload.orDragAndDrop')}</p>
<p class="text-xs text-gray-500">${multiple ? t('upload.pdfOrImages') : 'A single PDF file'}</p>
<p class="text-xs text-gray-500">${t('upload.filesNeverLeave')}</p>
</div>
<input id="file-input" type="file" class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" ${multiple} accept="${acceptedFiles}">
</div>
@@ -430,10 +431,10 @@ const createFileInputHTML = (options = {}) => {
<!-- NEW: Add control buttons for multi-file uploads -->
<div id="file-controls" class="hidden mt-4 flex gap-3">
<button id="add-more-btn" class="btn bg-indigo-600 hover:bg-indigo-700 text-white font-semibold px-4 py-2 rounded-lg flex items-center gap-2">
<i data-lucide="plus"></i> Add More Files
<i data-lucide="plus"></i> ${t('upload.addMore')}
</button>
<button id="clear-files-btn" class="btn bg-red-600 hover:bg-red-700 text-white font-semibold px-4 py-2 rounded-lg flex items-center gap-2">
<i data-lucide="x"></i> Clear All
<i data-lucide="x"></i> ${t('upload.clearAll')}
</button>
</div>
`

View File

@@ -57,7 +57,7 @@
</div>
<button id="close-tool-btn"
class="flex items-center gap-1 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="x" class="w-4 h-4 sm:w-5 sm:h-5"></i> Close
<i data-lucide="x" class="w-4 h-4 sm:w-5 sm:h-5"></i> <span data-i18n="common.close">Close</span>
</button>
</div>
</div>
@@ -75,8 +75,8 @@
<button id="upload-pdfs-btn"
class="flex items-center gap-1 sm:gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="upload" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden sm:inline">Upload PDFs</span>
<span class="sm:hidden">Upload</span>
<span class="hidden sm:inline" data-i18n="multiTool.uploadPdfs">Upload PDFs</span>
<span class="sm:hidden" data-i18n="multiTool.upload">Upload</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
@@ -86,107 +86,107 @@
<button id="add-blank-page-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="file-plus" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden sm:inline">Add Blank Page</span>
<span class="hidden sm:inline" data-i18n="multiTool.addBlankPage">Add Blank Page</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<span class="text-gray-400 text-xs sm:text-sm md:inline">Edit:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.edit">Edit:</span>
<!-- Undo / Redo / Reset -->
<button id="undo-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="undo-2" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden lg:inline">Undo</span>
<span class="hidden lg:inline" data-i18n="multiTool.undo">Undo</span>
</button>
<button id="redo-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="redo-2" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden lg:inline">Redo</span>
<span class="hidden lg:inline" data-i18n="multiTool.redo">Redo</span>
</button>
<button id="reset-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="refresh-ccw" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden lg:inline">Reset</span>
<span class="hidden lg:inline" data-i18n="multiTool.reset">Reset</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<!-- Selection -->
<span class="text-gray-400 text-xs sm:text-sm md:inline">Selection:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.selection">Selection:</span>
<button id="select-all-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="check-square" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden lg:inline">Select All</span>
<span class="hidden lg:inline" data-i18n="multiTool.selectAll">Select All</span>
</button>
<button id="deselect-all-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="square" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden lg:inline">Deselect All</span>
<span class="hidden lg:inline" data-i18n="multiTool.deselectAll">Deselect All</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<!-- Rotate -->
<span class="text-gray-400 text-xs sm:text-sm md:inline">Rotate:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.rotate">Rotate:</span>
<button id="bulk-rotate-left-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="rotate-ccw" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Left</span>
<span class="hidden xl:inline" data-i18n="multiTool.rotateLeft">Left</span>
</button>
<button id="bulk-rotate-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="rotate-cw" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Right</span>
<span class="hidden xl:inline" data-i18n="multiTool.rotateRight">Right</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<span class="text-gray-400 text-xs sm:text-sm md:inline">Transform:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.transform">Transform:</span>
<!-- Duplicate / Split -->
<button id="bulk-duplicate-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="copy" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Duplicate</span>
<span class="hidden xl:inline" data-i18n="multiTool.duplicate">Duplicate</span>
</button>
<button id="bulk-split-btn"
class="flex items-center gap-1 sm:gap-2 bg-gray-700 hover:bg-gray-600 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="scissors" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Split</span>
<span class="hidden xl:inline" data-i18n="multiTool.split">Split</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<span class="text-gray-400 text-xs sm:text-sm md:inline">Clear:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.clear">Clear:</span>
<!-- Delete -->
<button id="bulk-delete-btn"
class="flex items-center gap-1 sm:gap-2 bg-red-500 hover:bg-red-700 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="trash-2" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Delete</span>
<span class="hidden xl:inline" data-i18n="multiTool.delete">Delete</span>
</button>
<div class="border-l border-gray-600 h-5 sm:h-6 mx-1"></div>
<!-- Download -->
<span class="text-gray-400 text-xs sm:text-sm md:inline">Download:</span>
<span class="text-gray-400 text-xs sm:text-sm md:inline" data-i18n="multiTool.download">Download:</span>
<button id="bulk-download-btn"
class="flex items-center gap-1 sm:gap-2 bg-green-600 hover:bg-green-700 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="download" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Download Selected</span>
<span class="hidden xl:inline" data-i18n="multiTool.downloadSelected">Download Selected</span>
</button>
<button id="export-pdf-btn"
class="flex items-center gap-1 sm:gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 rounded text-xs sm:text-sm md:text-base">
<i data-lucide="download" class="w-3 h-3 sm:w-4 sm:h-4"></i>
<span class="hidden xl:inline">Export PDF</span>
<span class="hidden xl:inline" data-i18n="multiTool.exportPdf">Export PDF</span>
</button>
</div>
</div>
@@ -197,11 +197,14 @@
class="hidden border-2 border-dashed border-gray-600 rounded-lg p-6 sm:p-12 text-center max-w-full cursor-pointer"
onclick="document.getElementById('pdf-file-input').click()">
<i data-lucide="upload-cloud" class="w-12 h-12 sm:w-16 sm:h-16 mx-auto text-gray-400 mb-3 sm:mb-4"></i>
<p class="text-gray-300 text-base sm:text-lg mb-1 sm:mb-2">Upload PDF Files</p>
<p class="text-gray-400 text-xs sm:text-sm mb-3 sm:mb-4">Drag and drop PDF files here, or click to select</p>
<p class="text-gray-300 text-base sm:text-lg mb-1 sm:mb-2" data-i18n="multiTool.uploadPdfFiles">Upload PDF Files
</p>
<p class="text-gray-400 text-xs sm:text-sm mb-3 sm:mb-4" data-i18n="multiTool.dragAndDrop">Drag and drop PDF
files here, or click to select</p>
<input type="file" id="pdf-file-input" accept="application/pdf" multiple class="hidden">
<button onclick="event.stopPropagation(); document.getElementById('pdf-file-input').click()"
class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 sm:px-6 py-2 rounded text-sm sm:text-base">
class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 sm:px-6 py-2 rounded text-sm sm:text-base"
data-i18n="multiTool.selectFiles">
Select Files
</button>
</div>
@@ -217,7 +220,8 @@
<div class="flex items-center justify-center mb-4">
<i data-lucide="loader" class="w-12 h-12 text-indigo-400 animate-spin"></i>
</div>
<p id="loading-text" class="text-white text-center text-lg mb-4">Rendering pages...</p>
<p id="loading-text" class="text-white text-center text-lg mb-4" data-i18n="multiTool.renderingPages">Rendering
pages...</p>
<div class="w-full bg-gray-700 rounded-full h-2.5">
<div id="loading-progress" class="bg-indigo-600 h-2.5 rounded-full transition-all duration-300"
style="width: 0%"></div>