Merge pull request #256 from Cxllxc/main
(Bug/Feature)Add Chinese language support and fix Multi Tool localization
This commit is contained in:
@@ -398,8 +398,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>
|
||||
|
||||
@@ -23,7 +23,7 @@ http {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
rewrite ^/(en|de)/(.*)$ /$2 last;
|
||||
rewrite ^/(en|de|zh)/(.*)$ /$2 last;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
318
public/locales/zh/common.json
Normal file
318
public/locales/zh/common.json
Normal file
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "首页",
|
||||
"about": "关于我们",
|
||||
"contact": "联系我们",
|
||||
"licensing": "许可",
|
||||
"allTools": "所有工具",
|
||||
"openMainMenu": "打开主菜单",
|
||||
"language": "语言"
|
||||
},
|
||||
"hero": {
|
||||
"title": "专为隐私打造的",
|
||||
"pdfToolkit": "PDF 工具箱",
|
||||
"builtForPrivacy": " ",
|
||||
"noSignups": "无需注册",
|
||||
"unlimitedUse": "无限使用",
|
||||
"worksOffline": "离线可用",
|
||||
"startUsing": "立即开始"
|
||||
},
|
||||
"usedBy": {
|
||||
"title": "被众多公司和个人信赖,包括"
|
||||
},
|
||||
"features": {
|
||||
"title": "为什么选择",
|
||||
"bentoPdf": "BentoPDF?",
|
||||
"noSignup": {
|
||||
"title": "无需注册",
|
||||
"description": "即刻开始,无需账户或电子邮件。"
|
||||
},
|
||||
"noUploads": {
|
||||
"title": "无需上传",
|
||||
"description": "100% 客户端处理,您的文件从未离开您的设备。"
|
||||
},
|
||||
"foreverFree": {
|
||||
"title": "永久免费",
|
||||
"description": "所有工具免费,无试用期,无付费墙。"
|
||||
},
|
||||
"noLimits": {
|
||||
"title": "无限制",
|
||||
"description": "随心使用,无任何隐形限制。"
|
||||
},
|
||||
"batchProcessing": {
|
||||
"title": "批量处理",
|
||||
"description": "一次处理无限数量的 PDF 文件。"
|
||||
},
|
||||
"lightningFast": {
|
||||
"title": "极速处理",
|
||||
"description": "瞬间处理 PDF,无需等待。"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"title": "开始使用",
|
||||
"toolsLabel": "工具",
|
||||
"subtitle": "点击工具以打开文件上传",
|
||||
"searchPlaceholder": "搜索工具 (例如 '合并', '分割'...)",
|
||||
"backToTools": "返回工具列表"
|
||||
},
|
||||
"upload": {
|
||||
"clickToSelect": "点击选择文件",
|
||||
"orDragAndDrop": "或将文件拖放到此处",
|
||||
"pdfOrImages": "PDF 或图片",
|
||||
"filesNeverLeave": "您的文件从未离开您的设备。",
|
||||
"addMore": "添加更多文件",
|
||||
"clearAll": "清空所有"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "处理中..."
|
||||
},
|
||||
"alert": {
|
||||
"title": "提示",
|
||||
"ok": "确定"
|
||||
},
|
||||
"preview": {
|
||||
"title": "文档预览",
|
||||
"downloadAsPdf": "下载 PDF",
|
||||
"close": "关闭"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"shortcuts": "快捷键",
|
||||
"preferences": "偏好设置",
|
||||
"displayPreferences": "显示设置",
|
||||
"searchShortcuts": "搜索快捷键...",
|
||||
"shortcutsInfo": "按下并按住按键以设置快捷键。更改将自动保存。",
|
||||
"shortcutsWarning": "⚠️ 避免使用通用的浏览器快捷键 (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N 等),因为它们可能无法稳定工作。",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"resetToDefaults": "恢复默认",
|
||||
"fullWidthMode": "全宽模式",
|
||||
"fullWidthDescription": "使用全屏宽度展示所有工具,而非居中显示",
|
||||
"settingsAutoSaved": "设置已自动保存",
|
||||
"clickToSet": "点击设置",
|
||||
"pressKeys": "按下按键...",
|
||||
"warnings": {
|
||||
"alreadyInUse": "快捷键已被使用",
|
||||
"assignedTo": "已分配给:",
|
||||
"chooseDifferent": "请选择其他快捷键。",
|
||||
"reserved": "保留快捷键警告",
|
||||
"commonlyUsed": "通常用于:",
|
||||
"unreliable": "此快捷键可能无法稳定工作,或与浏览器/系统快捷键冲突。",
|
||||
"useAnyway": "仍要使用吗?",
|
||||
"resetTitle": "重置快捷键",
|
||||
"resetMessage": "确定要将所有快捷键重置为默认值吗?<br><br>此操作无法撤销。",
|
||||
"importSuccessTitle": "导入成功",
|
||||
"importSuccessMessage": "快捷键导入成功!",
|
||||
"importFailTitle": "导入失败",
|
||||
"importFailMessage": "导入快捷键失败。文件格式无效。"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"title": "警告",
|
||||
"cancel": "取消",
|
||||
"proceed": "继续"
|
||||
},
|
||||
"compliance": {
|
||||
"title": "您的数据从未离开您的设备",
|
||||
"weKeep": "我们会保持",
|
||||
"yourInfoSafe": "您的信息安全",
|
||||
"byFollowingStandards": ",遵循全球安全标准。",
|
||||
"processingLocal": "所有处理都在您的设备上本地进行。",
|
||||
"gdpr": {
|
||||
"title": "GDPR 合规",
|
||||
"description": "保护欧盟境内个人的个人数据和隐私。"
|
||||
},
|
||||
"ccpa": {
|
||||
"title": "CCPA 合规",
|
||||
"description": "赋予加州居民对其个人信息的收集、使用和共享的权利。"
|
||||
},
|
||||
"hipaa": {
|
||||
"title": "HIPAA 合规",
|
||||
"description": "制定美国医疗系统中处理敏感健康信息的保障措施。"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "常见",
|
||||
"questions": "问题",
|
||||
"isFree": {
|
||||
"question": "BentoPDF 真的免费吗?",
|
||||
"answer": "是的,完全免费。BentoPDF 上的所有工具均可 100% 免费使用,没有文件限制,无需注册,也没有水印。我们相信每个人都应该能够使用简单、强大的 PDF 工具,而无需通过付费墙。"
|
||||
},
|
||||
"areFilesSecure": {
|
||||
"question": "我的文件安全吗?它们在哪里处理?",
|
||||
"answer": "您的文件非常安全,因为它们从未离开您的电脑。所有处理都直接在您的网络浏览器(客户端)中进行。我们从不将您的文件上传到服务器,因此您可以对文档保持完全的隐私和控制。"
|
||||
},
|
||||
"platforms": {
|
||||
"question": "它适用于 Mac、Windows 和手机吗?",
|
||||
"answer": "是的!由于 BentoPDF 完全在您的浏览器中运行,因此它适用于任何带有现代网络浏览器的操作系统,包括 Windows、macOS、Linux、iOS 和 Android。"
|
||||
},
|
||||
"gdprCompliant": {
|
||||
"question": "BentoPDF 符合 GDPR 吗?",
|
||||
"answer": "是的。BentoPDF 完全符合 GDPR。由于所有文件处理都在您的浏览器中本地进行,并且我们从不收集或将您的文件传输到任何服务器,因此我们无法访问您的数据。这确保您始终掌控您的文档。"
|
||||
},
|
||||
"dataStorage": {
|
||||
"question": "你们会存储或跟踪我的任何文件吗?",
|
||||
"answer": "不。我们从不存储、跟踪或记录您的文件。您在 BentoPDF 上所做的所有操作都在您的浏览器内存中进行,并在您关闭页面后消失。没有上传,没有历史记录,也没有涉及服务器。"
|
||||
},
|
||||
"different": {
|
||||
"question": "BentoPDF 与其他 PDF 工具有何不同?",
|
||||
"answer": "大多数 PDF 工具将您的文件上传到服务器进行处理。BentoPDF 从不这样做。我们使用安全、现代的网络技术直接在您的浏览器中处理您的文件。这意味着更快的性能、更强的隐私和完全的安心。"
|
||||
},
|
||||
"browserBased": {
|
||||
"question": "基于浏览器的处理如何保护我的安全?",
|
||||
"answer": "通过完全在您的浏览器内部运行,BentoPDF 确保您的文件从未离开您的设备。这消除了服务器黑客攻击、数据泄露或未经授权访问的风险。您的文件始终属于您。"
|
||||
},
|
||||
"analytics": {
|
||||
"question": "你们使用 Cookie 或分析来跟踪我吗?",
|
||||
"answer": "我们关心您的隐私。BentoPDF 不会跟踪个人信息。我们仅使用 Simple Analytics 查看匿名访问计数。这意味着我们可以知道有多少用户访问我们的网站,但我们永远不知道您是谁。Simple Analytics 完全符合 GDPR 并尊重您的隐私。"
|
||||
}
|
||||
},
|
||||
"testimonials": {
|
||||
"title": "我们的",
|
||||
"users": "用户",
|
||||
"say": "评价"
|
||||
},
|
||||
"support": {
|
||||
"title": "喜欢这个项目?",
|
||||
"description": "BentoPDF 是一个充满激情的项目,旨在为每个人提供免费、私密且强大的 PDF 工具箱。如果您觉得它有用,请考虑支持它的开发。每一杯咖啡都是莫大的支持!",
|
||||
"buyMeCoffee": "请我喝杯咖啡"
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "© 2025 BentoPDF. 保留所有权利。",
|
||||
"version": "版本",
|
||||
"company": "公司",
|
||||
"aboutUs": "关于我们",
|
||||
"faqLink": "常见问题",
|
||||
"contactUs": "联系我们",
|
||||
"legal": "法律",
|
||||
"termsAndConditions": "服务条款",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"followUs": "关注我们"
|
||||
},
|
||||
"merge": {
|
||||
"title": "合并 PDF",
|
||||
"description": "合并整个文件,或选择特定页面合并到新文档中。",
|
||||
"fileMode": "文件模式",
|
||||
"pageMode": "页面模式",
|
||||
"howItWorks": "使用说明:",
|
||||
"fileModeInstructions": [
|
||||
"点击并拖动图标以更改文件的顺序。",
|
||||
"在每个文件的 '页码' 框中,您可以指定范围(例如 '1-3, 5')以仅合并这些页面。",
|
||||
"将 '页码' 框留空以包含该文件的所有页面。"
|
||||
],
|
||||
"pageModeInstructions": [
|
||||
"您上传的 PDF 的所有页面显示在下方。",
|
||||
"只需拖放单个页面缩略图,即可为您新文件对页面进行排序。"
|
||||
],
|
||||
"mergePdfs": "合并 PDF"
|
||||
},
|
||||
"common": {
|
||||
"page": "页",
|
||||
"pages": "页",
|
||||
"of": " / ",
|
||||
"download": "下载",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"add": "添加",
|
||||
"remove": "移除",
|
||||
"loading": "加载中...",
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
"file": "文件",
|
||||
"files": "文件"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
"title": "我们相信 PDF 工具应该是",
|
||||
"subtitle": "快速、私密且免费的。",
|
||||
"noCompromises": "绝不妥协。"
|
||||
},
|
||||
"mission": {
|
||||
"title": "我们的使命",
|
||||
"description": "提供最全面的 PDF 工具箱,尊重您的隐私,且永不收费。我们相信基本的文档工具应该对所有人、在任何地方都触手可及,没有任何障碍。"
|
||||
},
|
||||
"philosophy": {
|
||||
"label": "我们的核心理念",
|
||||
"title": "隐私至上。始终如一。",
|
||||
"description": "在数据被商品化的时代,我们采取不同的方式。BentoPDF 工具的所有处理都在您的浏览器本地进行。这意味着您的文件从未接触我们的服务器,我们从不查看您的文档,也不跟踪您的操作。您的文档保持绝对的私密性。这不仅是一项功能;这是我们的基石。"
|
||||
},
|
||||
"whyBentopdf": {
|
||||
"title": "为什么选择",
|
||||
"speed": {
|
||||
"title": "为速度而生",
|
||||
"description": "无需等待上传或从服务器下载。通过使用 WebAssembly 等现代网络技术直接在您的浏览器中处理文件,我们为所有工具提供了无与伦比的速度。"
|
||||
},
|
||||
"free": {
|
||||
"title": "完全免费",
|
||||
"description": "无试用,无订阅,无隐藏费用,也没有被锁定的 '高级' 功能。我们相信强大的 PDF 工具应该是一种公共设施,而不是盈利中心。"
|
||||
},
|
||||
"noAccount": {
|
||||
"title": "无需账户",
|
||||
"description": "立即开始使用任何工具。我们不需要您的电子邮件、密码或任何个人信息。您的工作流程应该是无摩擦且匿名的。"
|
||||
},
|
||||
"openSource": {
|
||||
"title": "开源精神",
|
||||
"description": "以透明度为核心构建。我们利用了像 PDF-lib 和 PDF.js 这样优秀的开源库,并相信社区驱动的力量能使强大的工具惠及每一个人。"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "准备好开始了吗?",
|
||||
"description": "加入成千上万信任 BentoPDF 满足日常文档需求的用户。体验隐私和性能带来的不同。",
|
||||
"button": "探索所有工具"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系我们",
|
||||
"subtitle": "我们很乐意听到您的声音。无论您有问题、反馈还是功能请求,请随时联系我们。",
|
||||
"email": "您可以直接通过电子邮件联系我们:"
|
||||
},
|
||||
"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": "加载失败"
|
||||
}
|
||||
}
|
||||
279
public/locales/zh/tools.json
Normal file
279
public/locales/zh/tools.json
Normal file
@@ -0,0 +1,279 @@
|
||||
{
|
||||
"categories": {
|
||||
"popularTools": "热门工具",
|
||||
"editAnnotate": "编辑与注释",
|
||||
"convertToPdf": "转换为 PDF",
|
||||
"convertFromPdf": "从 PDF 转换",
|
||||
"organizeManage": "组织与管理",
|
||||
"optimizeRepair": "优化与修复",
|
||||
"securePdf": "安全 PDF"
|
||||
},
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF 多功能工具",
|
||||
"subtitle": "在一个统一的界面中合并、分割、组织、删除、旋转、添加空白页、提取和复制。"
|
||||
},
|
||||
"mergePdf": {
|
||||
"name": "合并 PDF",
|
||||
"subtitle": "将多个 PDF 合并为一个文件。保留书签。"
|
||||
},
|
||||
"splitPdf": {
|
||||
"name": "分割 PDF",
|
||||
"subtitle": "将指定范围的页面提取到新 PDF 中。"
|
||||
},
|
||||
"compressPdf": {
|
||||
"name": "压缩 PDF",
|
||||
"subtitle": "减小您的 PDF 文件大小。"
|
||||
},
|
||||
"pdfEditor": {
|
||||
"name": "PDF 编辑器",
|
||||
"subtitle": "注释、高亮、涂黑、评论、添加形状/图片、搜索和查看 PDF。"
|
||||
},
|
||||
"jpgToPdf": {
|
||||
"name": "JPG 转 PDF",
|
||||
"subtitle": "从一张或多张 JPG 图片创建 PDF。"
|
||||
},
|
||||
"signPdf": {
|
||||
"name": "签署 PDF",
|
||||
"subtitle": "绘制、键入或上传您的签名。"
|
||||
},
|
||||
"cropPdf": {
|
||||
"name": "裁剪 PDF",
|
||||
"subtitle": "修剪 PDF 中每一页的边距。"
|
||||
},
|
||||
"extractPages": {
|
||||
"name": "提取页面",
|
||||
"subtitle": "将选定的页面保存为新文件。"
|
||||
},
|
||||
"duplicateOrganize": {
|
||||
"name": "复制与组织",
|
||||
"subtitle": "复制、重新排序和删除页面。"
|
||||
},
|
||||
"deletePages": {
|
||||
"name": "删除页面",
|
||||
"subtitle": "自您的文档中移除特定页面。"
|
||||
},
|
||||
"editBookmarks": {
|
||||
"name": "编辑书签",
|
||||
"subtitle": "添加、编辑、导入、删除和提取 PDF 书签。"
|
||||
},
|
||||
"tableOfContents": {
|
||||
"name": "目录",
|
||||
"subtitle": "根据 PDF 书签生成目录页。"
|
||||
},
|
||||
"pageNumbers": {
|
||||
"name": "页码",
|
||||
"subtitle": "将页码插入到您的文档中。"
|
||||
},
|
||||
"addWatermark": {
|
||||
"name": "添加水印",
|
||||
"subtitle": "在您的 PDF 页面上添加文字或图片水印。"
|
||||
},
|
||||
"headerFooter": {
|
||||
"name": "页眉和页脚",
|
||||
"subtitle": "在页面顶部和底部添加文字。"
|
||||
},
|
||||
"invertColors": {
|
||||
"name": "反转颜色",
|
||||
"subtitle": "创建您的 PDF 的“暗黑模式”版本。"
|
||||
},
|
||||
"backgroundColor": {
|
||||
"name": "背景颜色",
|
||||
"subtitle": "更改您的 PDF 的背景颜色。"
|
||||
},
|
||||
"changeTextColor": {
|
||||
"name": "更改文本颜色",
|
||||
"subtitle": "更改您 PDF 中文本的颜色。"
|
||||
},
|
||||
"addStamps": {
|
||||
"name": "添加印章",
|
||||
"subtitle": "使用注释工具栏向您的 PDF 添加图片印章。"
|
||||
},
|
||||
"removeAnnotations": {
|
||||
"name": "移除注释",
|
||||
"subtitle": "移除评论、高亮和链接。"
|
||||
},
|
||||
"pdfFormFiller": {
|
||||
"name": "PDF 表单填写器",
|
||||
"subtitle": "直接在浏览器中填写表单。也支持 XFA 表单。"
|
||||
},
|
||||
"createPdfForm": {
|
||||
"name": "创建 PDF 表单",
|
||||
"subtitle": "使用拖放文本字段创建可填写的 PDF 表单。"
|
||||
},
|
||||
"removeBlankPages": {
|
||||
"name": "移除空白页",
|
||||
"subtitle": "自动检测并删除空白页。"
|
||||
},
|
||||
"imageToPdf": {
|
||||
"name": "图片转 PDF",
|
||||
"subtitle": "将 JPG, PNG, WebP, BMP, TIFF, SVG, HEIC 转换为 PDF。"
|
||||
},
|
||||
"pngToPdf": {
|
||||
"name": "PNG 转 PDF",
|
||||
"subtitle": "从一张或多张 PNG 图片创建 PDF。"
|
||||
},
|
||||
"webpToPdf": {
|
||||
"name": "WebP 转 PDF",
|
||||
"subtitle": "从一张或多张 WebP 图片创建 PDF。"
|
||||
},
|
||||
"svgToPdf": {
|
||||
"name": "SVG 转 PDF",
|
||||
"subtitle": "从一张或多张 SVG 图片创建 PDF。"
|
||||
},
|
||||
"bmpToPdf": {
|
||||
"name": "BMP 转 PDF",
|
||||
"subtitle": "从一张或多张 BMP 图片创建 PDF。"
|
||||
},
|
||||
"heicToPdf": {
|
||||
"name": "HEIC 转 PDF",
|
||||
"subtitle": "从一张或多张 HEIC 图片创建 PDF。"
|
||||
},
|
||||
"tiffToPdf": {
|
||||
"name": "TIFF 转 PDF",
|
||||
"subtitle": "从一张或多张 TIFF 图片创建 PDF。"
|
||||
},
|
||||
"textToPdf": {
|
||||
"name": "文本转 PDF",
|
||||
"subtitle": "将纯文本文件转换为 PDF。"
|
||||
},
|
||||
"jsonToPdf": {
|
||||
"name": "JSON 转 PDF",
|
||||
"subtitle": "将 JSON 文件转换为 PDF 格式。"
|
||||
},
|
||||
"pdfToJpg": {
|
||||
"name": "PDF 转 JPG",
|
||||
"subtitle": "将每一页 PDF 转换为 JPG 图片。"
|
||||
},
|
||||
"pdfToPng": {
|
||||
"name": "PDF 转 PNG",
|
||||
"subtitle": "将每一页 PDF 转换为 PNG 图片。"
|
||||
},
|
||||
"pdfToWebp": {
|
||||
"name": "PDF 转 WebP",
|
||||
"subtitle": "将每一页 PDF 转换为 WebP 图片。"
|
||||
},
|
||||
"pdfToBmp": {
|
||||
"name": "PDF 转 BMP",
|
||||
"subtitle": "将每一页 PDF 转换为 BMP 图片。"
|
||||
},
|
||||
"pdfToTiff": {
|
||||
"name": "PDF 转 TIFF",
|
||||
"subtitle": "将每一页 PDF 转换为 TIFF 图片。"
|
||||
},
|
||||
"pdfToGreyscale": {
|
||||
"name": "PDF 转 灰度",
|
||||
"subtitle": "将所有颜色转换为黑白。"
|
||||
},
|
||||
"pdfToJson": {
|
||||
"name": "PDF 转 JSON",
|
||||
"subtitle": "将 PDF 文件转换为 JSON 格式。"
|
||||
},
|
||||
"ocrPdf": {
|
||||
"name": "OCR PDF",
|
||||
"subtitle": "使 PDF 可搜索和可复制。"
|
||||
},
|
||||
"alternateMix": {
|
||||
"name": "交替混合页面",
|
||||
"subtitle": "通过交替每个 PDF 的页面来合并 PDF。保留书签。"
|
||||
},
|
||||
"addAttachments": {
|
||||
"name": "添加附件",
|
||||
"subtitle": "将一个或多个文件嵌入到您的 PDF 中。"
|
||||
},
|
||||
"extractAttachments": {
|
||||
"name": "提取附件",
|
||||
"subtitle": "从 PDF 中提取所有嵌入的文件为 ZIP。"
|
||||
},
|
||||
"editAttachments": {
|
||||
"name": "编辑附件",
|
||||
"subtitle": "查看或移除 PDF 中的附件。"
|
||||
},
|
||||
"dividePages": {
|
||||
"name": "分割页面",
|
||||
"subtitle": "水平或垂直分割页面。"
|
||||
},
|
||||
"addBlankPage": {
|
||||
"name": "添加空白页",
|
||||
"subtitle": "在 PDF 的任意位置插入空白页。"
|
||||
},
|
||||
"reversePages": {
|
||||
"name": "反转页面",
|
||||
"subtitle": "反转文档中所有页面的顺序。"
|
||||
},
|
||||
"rotatePdf": {
|
||||
"name": "旋转 PDF",
|
||||
"subtitle": "以 90 度增量旋转页面。"
|
||||
},
|
||||
"nUpPdf": {
|
||||
"name": "N-Up PDF",
|
||||
"subtitle": "将多页排列在单张纸上。"
|
||||
},
|
||||
"combineToSinglePage": {
|
||||
"name": "合并为单页",
|
||||
"subtitle": "将所有页面拼接成一个连续的滚动页面。"
|
||||
},
|
||||
"viewMetadata": {
|
||||
"name": "查看元数据",
|
||||
"subtitle": "检查 PDF 的隐藏属性。"
|
||||
},
|
||||
"editMetadata": {
|
||||
"name": "编辑元数据",
|
||||
"subtitle": "更改作者、标题和其他属性。"
|
||||
},
|
||||
"pdfsToZip": {
|
||||
"name": "PDF 转 ZIP",
|
||||
"subtitle": "将多个 PDF 文件打包成一个 ZIP 归档。"
|
||||
},
|
||||
"comparePdfs": {
|
||||
"name": "比较 PDF",
|
||||
"subtitle": "并排比较两个 PDF。"
|
||||
},
|
||||
"posterizePdf": {
|
||||
"name": "海报化 PDF",
|
||||
"subtitle": "将大页面分割成多个小页面。"
|
||||
},
|
||||
"fixPageSize": {
|
||||
"name": "修复页面尺寸",
|
||||
"subtitle": "将所有页面标准化为统一尺寸。"
|
||||
},
|
||||
"linearizePdf": {
|
||||
"name": "线性化 PDF",
|
||||
"subtitle": "优化 PDF 以便快速网络查看。"
|
||||
},
|
||||
"pageDimensions": {
|
||||
"name": "页面尺寸",
|
||||
"subtitle": "分析页面大小、方向和单位。"
|
||||
},
|
||||
"removeRestrictions": {
|
||||
"name": "移除限制",
|
||||
"subtitle": "移除与数字签名 PDF 文件相关的密码保护和安全限制。"
|
||||
},
|
||||
"repairPdf": {
|
||||
"name": "修复 PDF",
|
||||
"subtitle": "从损坏的 PDF 文件中恢复数据。"
|
||||
},
|
||||
"encryptPdf": {
|
||||
"name": "加密 PDF",
|
||||
"subtitle": "通过添加密码锁定您的 PDF。"
|
||||
},
|
||||
"sanitizePdf": {
|
||||
"name": "清理 PDF",
|
||||
"subtitle": "移除元数据、注释、脚本等。"
|
||||
},
|
||||
"decryptPdf": {
|
||||
"name": "解密 PDF",
|
||||
"subtitle": "通过移除密码保护解锁 PDF。"
|
||||
},
|
||||
"flattenPdf": {
|
||||
"name": "扁平化 PDF",
|
||||
"subtitle": "使表单字段和注释不可编辑。"
|
||||
},
|
||||
"removeMetadata": {
|
||||
"name": "移除元数据",
|
||||
"subtitle": "从 PDF 中剥离隐藏数据。"
|
||||
},
|
||||
"changePermissions": {
|
||||
"name": "更改权限",
|
||||
"subtitle": "设置或更改 PDF 上的用户权限。"
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,19 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
|
||||
// Supported languages
|
||||
export const supportedLanguages = ['en', 'de', 'vi'] as const;
|
||||
export const supportedLanguages = ['en', 'de', 'zh', 'vi'] as const;
|
||||
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
||||
|
||||
export const languageNames: Record<SupportedLanguage, string> = {
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
zh: '中文',
|
||||
vi: 'Tiếng Việt',
|
||||
};
|
||||
|
||||
export const getLanguageFromUrl = (): SupportedLanguage => {
|
||||
const path = window.location.pathname;
|
||||
const langMatch = path.match(/^\/(en|de|vi)(?:\/|$)/);
|
||||
const langMatch = path.match(/^\/(en|de|zh|vi)(?:\/|$)/);
|
||||
if (langMatch && supportedLanguages.includes(langMatch[1] as SupportedLanguage)) {
|
||||
return langMatch[1] as SupportedLanguage;
|
||||
}
|
||||
@@ -70,9 +71,9 @@ export const changeLanguage = (lang: SupportedLanguage): void => {
|
||||
const currentLang = getLanguageFromUrl();
|
||||
|
||||
let newPath: string;
|
||||
if (currentPath.match(/^\/(en|de|vi)\//)) {
|
||||
newPath = currentPath.replace(/^\/(en|de|vi)\//, `/${lang}/`);
|
||||
} else if (currentPath.match(/^\/(en|de|vi)$/)) {
|
||||
if (currentPath.match(/^\/(en|de|zh|vi)\//)) {
|
||||
newPath = currentPath.replace(/^\/(en|de|zh|vi)\//, `/${lang}/`);
|
||||
} else if (currentPath.match(/^\/(en|de|zh|vi)$/)) {
|
||||
newPath = `/${lang}`;
|
||||
} else {
|
||||
newPath = `/${lang}${currentPath}`;
|
||||
@@ -134,7 +135,7 @@ export const rewriteLinks = (): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (href.match(/^\/(en|de|vi)\//)) {
|
||||
if (href.match(/^\/(en|de|zh|vi)\//)) {
|
||||
return;
|
||||
}
|
||||
let newHref: string;
|
||||
|
||||
@@ -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();
|
||||
|
||||
17
src/js/ui.ts
17
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 = {}) => {
|
||||
<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>
|
||||
`
|
||||
|
||||
@@ -58,7 +58,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>
|
||||
@@ -76,8 +76,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>
|
||||
@@ -87,107 +87,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>
|
||||
@@ -198,11 +198,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>
|
||||
@@ -218,7 +221,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>
|
||||
|
||||
@@ -11,7 +11,7 @@ function pagesRewritePlugin(): Plugin {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
const url = req.url?.split('?')[0] || '';
|
||||
|
||||
const langMatch = url.match(/^\/(en|de)(\/.*)?$/);
|
||||
const langMatch = url.match(/^\/(en|de|zh)(\/.*)?$/);
|
||||
if (langMatch) {
|
||||
const lang = langMatch[1];
|
||||
const restOfPath = langMatch[2] || '/';
|
||||
|
||||
Reference in New Issue
Block a user