diff --git a/index.html b/index.html index faa5239..c2f8211 100644 --- a/index.html +++ b/index.html @@ -398,8 +398,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/nginx.conf b/nginx.conf index 76985be..2f0e743 100644 --- a/nginx.conf +++ b/nginx.conf @@ -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; diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 7862fa8..092348e 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 bc5df0b..9a89952 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 new file mode 100644 index 0000000..8d78ebc --- /dev/null +++ b/public/locales/zh/common.json @@ -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": "确定要将所有快捷键重置为默认值吗?

此操作无法撤销。", + "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": "加载失败" + } +} \ No newline at end of file diff --git a/public/locales/zh/tools.json b/public/locales/zh/tools.json new file mode 100644 index 0000000..a64ed0f --- /dev/null +++ b/public/locales/zh/tools.json @@ -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 上的用户权限。" + } +} \ No newline at end of file diff --git a/src/js/i18n/i18n.ts b/src/js/i18n/i18n.ts index 2648e24..f29f7d6 100644 --- a/src/js/i18n/i18n.ts +++ b/src/js/i18n/i18n.ts @@ -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 = { 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; 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 135e3fd..b5be7c7 100644 --- a/src/pages/pdf-multi-tool.html +++ b/src/pages/pdf-multi-tool.html @@ -58,7 +58,7 @@
@@ -76,8 +76,8 @@
@@ -87,107 +87,107 @@
- Edit: + Edit:
- Selection: + Selection:
- Rotate: + Rotate:
- Transform: + Transform:
- Clear: + Clear:
- Download: + Download: @@ -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()"> -

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

@@ -218,7 +221,8 @@
-

Rendering pages...

+

Rendering + pages...

diff --git a/vite.config.ts b/vite.config.ts index e3a2b10..262383c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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] || '/';