From ab122e19df67b2d58760bb7fa46e1775f89f65fb Mon Sep 17 00:00:00 2001 From: cxllxc Date: Fri, 12 Dec 2025 12:54:06 +0000 Subject: [PATCH] feat: Add internationalization support and introduce a new PDF multi-tool. --- index.html | 4 +-- public/locales/de/common.json | 43 ++++++++++++++++++++++++++ public/locales/en/common.json | 43 ++++++++++++++++++++++++++ public/locales/zh/common.json | 43 ++++++++++++++++++++++++++ src/js/logic/pdf-multi-tool.ts | 28 +++++++++-------- src/js/ui.ts | 17 ++++++----- src/pages/pdf-multi-tool.html | 56 ++++++++++++++++++---------------- 7 files changed, 186 insertions(+), 48 deletions(-) diff --git a/index.html b/index.html index e959273..b549ea7 100644 --- a/index.html +++ b/index.html @@ -397,8 +397,8 @@

- Press and hold keys to set a shortcut. Changes are auto-saved. -
⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N + Press and hold keys to set a shortcut. Changes are auto-saved. +
⚠️ Avoid common browser shortcuts (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N etc.) as they may not work reliably.

diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 2b50a01..cf78551 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -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" } } \ No newline at end of file diff --git a/public/locales/en/common.json b/public/locales/en/common.json index fdd3741..6ea63e2 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -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" } } \ No newline at end of file diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index c588ca9..8d78ebc 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -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": "加载失败" } } \ No newline at end of file diff --git a/src/js/logic/pdf-multi-tool.ts b/src/js/logic/pdf-multi-tool.ts index 99d6711..92c4e7d 100644 --- a/src/js/logic/pdf-multi-tool.ts +++ b/src/js/logic/pdf-multi-tool.ts @@ -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) { @@ -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 = ` - Loading... + ${t('common.loading')} `; 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 = ''; - 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 = ''; - 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 = ''; - 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 = ''; - splitBtn.title = 'Toggle split after this page'; + splitBtn.title = t('multiTool.actions.toggleSplit'); splitBtn.onclick = (e) => { e.stopPropagation(); snapshot(); diff --git a/src/js/ui.ts b/src/js/ui.ts index ed5c1f1..dda62cb 100644 --- a/src/js/ui.ts +++ b/src/js/ui.ts @@ -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 = {}) => {
-

Click to select a file or drag and drop

-

${multiple ? 'PDFs or Images' : 'A single PDF file'}

-

Your files never leave your device.

+

${t('upload.clickToSelect')} ${t('upload.orDragAndDrop')}

+

${multiple ? t('upload.pdfOrImages') : 'A single PDF file'}

+

${t('upload.filesNeverLeave')}

@@ -430,10 +431,10 @@ const createFileInputHTML = (options = {}) => { ` diff --git a/src/pages/pdf-multi-tool.html b/src/pages/pdf-multi-tool.html index 2054df1..eecf788 100644 --- a/src/pages/pdf-multi-tool.html +++ b/src/pages/pdf-multi-tool.html @@ -57,7 +57,7 @@
@@ -75,8 +75,8 @@
@@ -86,107 +86,107 @@
- Edit: + Edit:
- Selection: + Selection:
- Rotate: + Rotate:
- Transform: + Transform:
- Clear: + Clear:
- Download: + Download: @@ -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()"> -

Upload PDF Files

-

Drag and drop PDF files here, or click to select

+

Upload PDF Files +

+

Drag and drop PDF + files here, or click to select

@@ -217,7 +220,8 @@
-

Rendering pages...

+

Rendering + pages...