Merge branch 'main' into main

This commit is contained in:
Alam
2026-01-31 14:00:46 +05:30
committed by GitHub
285 changed files with 63028 additions and 47213 deletions

View File

@@ -1,80 +0,0 @@
---
name: '🐛 Bug / 💡 Feature / ❓ Question'
about: 'Report a bug, request a feature, or ask a question about BentoPDF'
title: '(Bug) <short title>, (Feature) <short title>, or (Question) <short title>'
labels: ['needs triage']
assignees: []
---
## Type of Issue
Please check one:
- [ ] 🐛 Bug Report <!-- Label: bug -->
- [ ] 💡 Feature Request <!-- Label: feature -->
- [ ] ❓ Question / Help <!-- Label: question -->
---
## Description
Provide a clear and concise description of the issue, feature request, or question.
---
## Steps to Reproduce (for Bugs)
1. Go to '...'
2. Run '...'
3. Observe error: '...'
**Expected Behavior:**
Describe what you expected BentoPDF to do.
**Actual Behavior:**
Describe what actually happened, including error messages.
---
## Feature Request Details (if applicable)
- What functionality are you requesting?
- Why is this useful?
- Any example or context to illustrate it?
---
## Question Details (if applicable)
- What is your question?
- What have you tried so far?
- Any relevant code snippet or scenario?
---
## Screenshots / Logs (if applicable)
Attach any screenshots, logs, or stack traces that help explain the problem or question.
---
## Environment
- **OS:** (e.g., macOS 14.0 / Ubuntu 22.04 / Windows 11)
- **Dependencies / setup details (if any):**
---
## 💭 Additional Context
Any other information, suggestions, or references that might help maintainers.
---
**Title Format Reminder:**
- `(Bug) Text alignment incorrect on multi-line paragraphs`
- `(Feature) Add support for custom PDF metadata`
- `(Question) How to embed custom fonts?`
---

122
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,122 @@
name: 🐛 Bug Report
description: Report a bug in BentoPDF
title: "(Bug) "
labels: ["bug", "needs triage"]
body:
- type: markdown
attributes:
value: |
## ⚠️ Important Notice
**Bug reports without logs or a sample file demonstrating the issue will not be investigated.**
Please help us help you by providing the information needed to reproduce and fix the problem.
- type: textarea
id: description
attributes:
label: Description
description: Provide a clear and concise description of the bug.
placeholder: What happened? What did you expect to happen?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Go to '...'
2. Click on '...'
3. Upload file '...'
4. See error
validations:
required: true
- type: textarea
id: console-logs
attributes:
label: Console Logs
description: Open browser DevTools (F12 → Console tab) and paste any errors here.
placeholder: Paste console logs here...
render: shell
validations:
required: true
- type: textarea
id: sample-file
attributes:
label: Sample PDF or File
description: |
Attach a sample PDF that reproduces the issue, or describe how to create one.
If you cannot share the original, create a minimal example that shows the problem.
placeholder: Drag and drop your file here, or describe how to reproduce with any PDF...
validations:
required: true
- type: dropdown
id: browser
attributes:
label: Browser
description: Which browser are you using?
options:
- Chrome
- Firefox
- Safari
- Edge
- Brave
- Other
validations:
required: true
- type: input
id: browser-version
attributes:
label: Browser Version
description: e.g., Chrome 120, Firefox 121
placeholder: "120"
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- macOS
- Windows
- Linux
- iOS
- Android
- Other
validations:
required: true
- type: input
id: bentopdf-version
attributes:
label: BentoPDF Version
description: Check the footer or package.json
placeholder: "1.15.4"
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Any other information that might help us debug this issue.
placeholder: Screenshots, network errors, stack traces, etc.
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Pre-submission Checklist
options:
- label: I have included console logs from the browser DevTools
required: true
- label: I have attached a sample file or described how to reproduce the issue
required: true
- label: I have searched existing issues to ensure this is not a duplicate
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community
url: https://discord.gg/Bgq3Ay3f2w
about: Join our Discord for quick questions and community support
- name: 📖 Documentation
url: https://github.com/nicholaschen09/BentoPDF#readme
about: Check the README for setup and usage instructions

View File

@@ -0,0 +1,39 @@
name: 💡 Feature Request
description: Suggest a new feature for BentoPDF
title: "(Feature) "
labels: ["enhancement", "needs triage"]
body:
- type: textarea
id: description
attributes:
label: Feature Description
description: What functionality are you requesting?
placeholder: Describe the feature you'd like to see...
validations:
required: true
- type: textarea
id: use-case
attributes:
label: Use Case
description: Why is this feature useful? What problem does it solve?
placeholder: Explain why you need this feature...
validations:
required: true
- type: textarea
id: examples
attributes:
label: Examples
description: Any examples, mockups, or references to illustrate the feature?
placeholder: Links to similar features, screenshots, etc.
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Any other information about the feature request.
validations:
required: false

30
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: ❓ Question
description: Ask a question about BentoPDF
title: "(Question) "
labels: ["question"]
body:
- type: textarea
id: question
attributes:
label: Question
description: What would you like to know?
placeholder: Your question here...
validations:
required: true
- type: textarea
id: tried
attributes:
label: What have you tried?
description: What solutions have you already attempted?
placeholder: Describe what you've tried so far...
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional Context
description: Any relevant code snippets, screenshots, or scenarios.
validations:
required: false

View File

@@ -51,11 +51,12 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker-build-and-push: # Build each platform natively in parallel, then merge manifests
build-amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'alam00000/bentopdf' if: github.repository == 'alam00000/bentopdf'
permissions: permissions:
contents: write contents: read
packages: write packages: write
strategy: strategy:
matrix: matrix:
@@ -70,18 +71,9 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -89,7 +81,7 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version and determine release type - name: Extract version
id: version id: version
run: | run: |
if [[ $GITHUB_REF == refs/tags/v* ]]; then if [[ $GITHUB_REF == refs/tags/v* ]]; then
@@ -104,8 +96,7 @@ jobs:
echo "is_release=false" >> $GITHUB_OUTPUT echo "is_release=false" >> $GITHUB_OUTPUT
fi fi
# Build and push for releases (with 'latest' tag) - name: Build and push amd64 ${{ matrix.mode.name }} (release)
- name: Build and push ${{ matrix.mode.name }} image (release)
if: steps.version.outputs.is_release == 'true' if: steps.version.outputs.is_release == 'true'
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -113,18 +104,12 @@ jobs:
build-args: | build-args: |
SIMPLE_MODE=${{ matrix.mode.simple_mode }} SIMPLE_MODE=${{ matrix.mode.simple_mode }}
tags: | tags: |
bentopdf/bentopdf${{ matrix.mode.suffix }}:latest ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} platforms: linux/amd64
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} cache-from: type=gha,scope=amd64-${{ matrix.mode.name }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest cache-to: type=gha,mode=max,scope=amd64-${{ matrix.mode.name }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
# Build and push for main branch (with 'edge' tag) - name: Build and push amd64 ${{ matrix.mode.name }} (edge)
- name: Build and push ${{ matrix.mode.name }} image (edge)
if: steps.version.outputs.is_release == 'false' if: steps.version.outputs.is_release == 'false'
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -132,10 +117,208 @@ jobs:
build-args: | build-args: |
SIMPLE_MODE=${{ matrix.mode.simple_mode }} SIMPLE_MODE=${{ matrix.mode.simple_mode }}
tags: | tags: |
bentopdf/bentopdf${{ matrix.mode.suffix }}:edge ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64
bentopdf/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} platforms: linux/amd64
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge cache-from: type=gha,scope=amd64-${{ matrix.mode.name }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} cache-to: type=gha,mode=max,scope=amd64-${{ matrix.mode.name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha build-arm64:
cache-to: type=gha,mode=max runs-on: ubuntu-24.04-arm # Native ARM64 runner
permissions:
contents: read
packages: write
strategy:
matrix:
mode:
- name: default
simple_mode: false
suffix: ""
- name: simple
simple_mode: true
suffix: "-simple"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version
id: version
run: |
if [[ $GITHUB_REF == refs/tags/v* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
else
SHORT_SHA=${GITHUB_SHA::7}
echo "version=edge" >> $GITHUB_OUTPUT
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Build and push arm64 ${{ matrix.mode.name }} (release)
if: steps.version.outputs.is_release == 'true'
uses: docker/build-push-action@v6
with:
push: true
build-args: |
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
tags: |
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
platforms: linux/arm64
cache-from: type=gha,scope=arm64-${{ matrix.mode.name }}
cache-to: type=gha,mode=max,scope=arm64-${{ matrix.mode.name }}
- name: Build and push arm64 ${{ matrix.mode.name }} (edge)
if: steps.version.outputs.is_release == 'false'
uses: docker/build-push-action@v6
with:
push: true
build-args: |
SIMPLE_MODE=${{ matrix.mode.simple_mode }}
tags: |
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
platforms: linux/arm64
cache-from: type=gha,scope=arm64-${{ matrix.mode.name }}
cache-to: type=gha,mode=max,scope=arm64-${{ matrix.mode.name }}
# Merge GHCR manifests after both platforms are built
merge-manifests-ghcr:
runs-on: ubuntu-latest
needs: [build-amd64, build-arm64]
permissions:
packages: write
strategy:
matrix:
mode:
- name: default
suffix: ""
- name: simple
suffix: "-simple"
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version
id: version
run: |
if [[ $GITHUB_REF == refs/tags/v* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
else
SHORT_SHA=${GITHUB_SHA::7}
echo "version=edge" >> $GITHUB_OUTPUT
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Create and push GHCR manifest (release)
if: steps.version.outputs.is_release == 'true'
run: |
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
- name: Create and push GHCR manifest (edge)
if: steps.version.outputs.is_release == 'false'
run: |
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
# Copy images from GHCR to DockerHub
push-to-dockerhub:
runs-on: ubuntu-latest
needs: [merge-manifests-ghcr]
continue-on-error: true
permissions:
contents: read
packages: read
strategy:
matrix:
mode:
- name: default
suffix: ""
- name: simple
suffix: "-simple"
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Extract version
id: version
run: |
if [[ $GITHUB_REF == refs/tags/v* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
else
SHORT_SHA=${GITHUB_SHA::7}
echo "version=edge" >> $GITHUB_OUTPUT
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Copy images to DockerHub (release)
if: steps.version.outputs.is_release == 'true'
run: |
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:latest \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}-arm64
- name: Copy images to DockerHub (edge)
if: steps.version.outputs.is_release == 'false'
run: |
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:edge \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64
docker buildx imagetools create -t bentopdfteam/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-amd64 \
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge-arm64

View File

@@ -59,6 +59,9 @@ jobs:
with: with:
# Upload entire repository # Upload entire repository
path: dist path: dist
name: github-pages-deployment
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v4
with:
artifact_name: github-pages-deployment

6
.gitignore vendored
View File

@@ -33,3 +33,9 @@ coverage/
#backup #backup
.seo-backup .seo-backup
libreoffice-wasm-package libreoffice-wasm-package
# helm chart
bentopdf-*.tgz
# test
dist-test

139
.htaccess Normal file
View File

@@ -0,0 +1,139 @@
RewriteEngine On
RewriteBase /
# ============================================
# 1. SECURITY HEADERS (UPDATED FOR CDN COMPATIBILITY)
# ============================================
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
# ============================================
# 2. BROWSER CACHING
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType application/gzip "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
# ============================================
# 3. COMPRESSION (STANDARD)
# ============================================
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
SetEnvIfNoCase Request_URI "\.br$" no-gzip
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/ttf
</IfModule>
# ============================================
# 4. MIME TYPES & SPECIAL FILE HANDLING
# ============================================
AddType application/javascript .js .mjs
AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType font/woff .woff
AddType image/webp .webp
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header append Vary Accept-Encoding
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
Header append Vary Accept-Encoding
</FilesMatch>
# ============================================
# 5. REDIRECTS & ROUTING
# ============================================
# Canonical WWW
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# 5.1. LANGUAGE ROUTES (MUST BE BEFORE .html extension rule)
# ============================================
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# 5.5. DOCS ROUTING (VitePress)
# ============================================
RewriteCond %{REQUEST_URI} ^/docs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L]
# ============================================
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html

288
404.html
View File

@@ -62,125 +62,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="/index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="/index.html" class="nav-link" data-i18n="nav.home">Home</a>
<a href="/about.html" class="nav-link" data-i18n="nav.about"
>About</a
>
<a href="/contact.html" class="nav-link" data-i18n="nav.contact"
>Contact</a
>
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing"
>Licensing</a
>
<a
href="/index.html#tools-header"
class="nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="/index.html" class="mobile-nav-link" data-i18n="nav.home"
>Home</a
>
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about"
>About</a
>
<a
href="/contact.html"
class="mobile-nav-link"
data-i18n="nav.contact"
>Contact</a
>
<a
href="/licensing.html"
class="mobile-nav-link"
data-i18n="nav.licensing"
>Licensing</a
>
<a
href="/index.html#tools-header"
class="mobile-nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8"> <div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
<section id="404-content" class="text-center py-20 md:py-32"> <section id="404-content" class="text-center py-20 md:py-32">
@@ -210,7 +92,7 @@
class="flex flex-col sm:flex-row gap-4 justify-center items-center" class="flex flex-col sm:flex-row gap-4 justify-center items-center"
> >
<a <a
href="/index.html" href="index.html"
class="inline-flex items-center gap-3 pl-8 pr-2 py-2 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1" class="inline-flex items-center gap-3 pl-8 pr-2 py-2 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1"
> >
<span>Back to Home</span> <span>Back to Home</span>
@@ -274,171 +156,7 @@
</section> </section>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
<span data-i18n="footer.version">Version</span>
<span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">
Company
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/about.html"
class="hover:text-indigo-400"
data-i18n="footer.aboutUs"
>About Us</a
>
</li>
<li>
<a
href="/faq.html"
class="hover:text-indigo-400"
data-i18n="footer.faqLink"
>FAQ</a
>
</li>
<li>
<a
href="/contact.html"
class="hover:text-indigo-400"
data-i18n="footer.contactUs"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">
Legal
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/licensing.html"
class="hover:text-indigo-400"
data-i18n="nav.licensing"
>Licensing</a
>
</li>
<li>
<a
href="/terms.html"
class="hover:text-indigo-400"
data-i18n="footer.termsAndConditions"
>Terms and Conditions</a
>
</li>
<li>
<a
href="/privacy.html"
class="hover:text-indigo-400"
data-i18n="footer.privacyPolicy"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">
Follow Us
</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

View File

@@ -3,12 +3,16 @@
ARG BASE_URL= ARG BASE_URL=
# Build stage # Build stage
FROM node:20-alpine AS builder FROM public.ecr.aws/docker/library/node:20-alpine AS builder
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
COPY vendor ./vendor COPY vendor ./vendor
ENV HUSKY=0 ENV HUSKY=0
RUN npm ci RUN npm config set fetch-retries 5 && \
npm config set fetch-retry-mintimeout 60000 && \
npm config set fetch-retry-maxtimeout 300000 && \
npm config set fetch-timeout 600000 && \
npm ci
COPY . . COPY . .
# Build without type checking (vite build only) # Build without type checking (vite build only)
@@ -18,18 +22,16 @@ ENV SIMPLE_MODE=$SIMPLE_MODE
ARG COMPRESSION_MODE=all ARG COMPRESSION_MODE=all
ENV COMPRESSION_MODE=$COMPRESSION_MODE ENV COMPRESSION_MODE=$COMPRESSION_MODE
# global arg to local arg # global arg to local arg - BASE_URL is read from env by vite.config.ts
ARG BASE_URL ARG BASE_URL
ENV BASE_URL=$BASE_URL ENV BASE_URL=$BASE_URL
RUN if [ -z "$BASE_URL" ]; then \ ENV NODE_OPTIONS="--max-old-space-size=3072"
npm run build -- --mode production; \
else \ RUN npm run build:with-docs
npm run build -- --base=${BASE_URL} --mode production; \
fi
# Production stage # Production stage
FROM nginxinc/nginx-unprivileged:stable-alpine-slim FROM quay.io/nginx/nginx-unprivileged:stable-alpine-slim
LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf" LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf"
LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf" LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf"

290
README.md
View File

@@ -1,15 +1,54 @@
<p align="center"><img src="public/images/favicon-no-bg.svg" width="80"></p> <p align="center"><img src="public/images/favicon-no-bg.svg" width="80"></p>
<h1 align="center">BentoPDF</h1> <h1 align="center">BentoPDF</h1>
<p align="center">
<a href="https://www.digitalocean.com/?refcode=d93c189ef6d0&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge">
<img src="https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%203.svg" alt="DigitalOcean Referral Badge">
</a>
</p>
**BentoPDF** is a powerful, privacy-first, client-side PDF toolkit that is self hostable and allows you to manipulate, edit, merge, and process PDF files directly in your browser. No server-side processing is required, ensuring your files remain secure and private. **BentoPDF** is a powerful, privacy-first, client-side PDF toolkit that is self hostable and allows you to manipulate, edit, merge, and process PDF files directly in your browser. No server-side processing is required, ensuring your files remain secure and private.
![Docker Pulls](https://img.shields.io/docker/pulls/bentopdf/bentopdf) [![Ko-fi](https://img.shields.io/badge/Buy%20me%20a%20Coffee-yellow?logo=kofi&style=flat-square)](https://ko-fi.com/alio01) ![GitHub Stars](https://img.shields.io/github/stars/alam00000/bentopdf?style=social) ![Docker Pulls](https://img.shields.io/docker/pulls/bentopdfteam/bentopdf) [![Ko-fi](https://img.shields.io/badge/Buy%20me%20a%20Coffee-yellow?logo=kofi&style=flat-square)](https://ko-fi.com/alio01) ![GitHub Stars](https://img.shields.io/github/stars/alam00000/bentopdf?style=social)
[![Sponsor me on GitHub](https://img.shields.io/badge/Sponsor-%E2%9D%A4-ff69b4)](https://github.com/sponsors/alam00000) [![Sponsor me on GitHub](https://img.shields.io/badge/Sponsor-%E2%9D%A4-ff69b4)](https://github.com/sponsors/alam00000)
![BentoPDF Tools](public/images/bentopdf-tools.png) ![BentoPDF Tools](public/images/bentopdf-tools.png)
--- ---
## Table of Contents
- [Join Us on Discord](#-join-us-on-discord)
- [Documentation](#-documentation)
- [Licensing](#-licensing)
- [Stargazers over time](#-stargazers-over-time)
- [Thank You to Our Sponsors](#-thank-you-to-our-sponsors)
- [Why BentoPDF?](#-why-bentopdf)
- [Features / Tools Supported](#-features--tools-supported)
- [Organize & Manage PDFs](#organize--manage-pdfs)
- [Edit & Modify PDFs](#edit--modify-pdfs)
- [Convert to PDF](#convert-to-pdf)
- [Convert from PDF](#convert-from-pdf)
- [Secure & Optimize PDFs](#secure--optimize-pdfs)
- [Translations](#-translations)
- [Getting Started](#-getting-started)
- [Prerequisites](#prerequisites)
- [Quick Start](#-quick-start)
- [Static Hosting](#static-hosting-using-netlify-vercel-and-github-pages)
- [Self-Hosting Locally](#-self-hosting-locally)
- [Docker Compose / Podman Compose](#-run-with-docker-compose--podman-compose-recommended)
- [Podman Quadlet](#-podman-quadlet-systemd-integration)
- [Simple Mode](#-simple-mode-for-internal-use)
- [Security Features](#-security-features)
- [Digital Signature CORS Proxy](#digital-signature-cors-proxy-required)
- [Version Management](#-version-management)
- [Development Setup](#-development-setup)
- [Tech Stack & Background](#-tech-stack--background)
- [Roadmap](#-roadmap)
- [Contributing](#-contributing)
- [Special Thanks](#special-thanks)
---
## 📢 Join Us on Discord ## 📢 Join Us on Discord
[![Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/Bgq3Ay3f2w) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/Bgq3Ay3f2w)
@@ -23,6 +62,7 @@ Have questions, feature requests, or want to chat with the community? Join our D
[![Documentation](https://img.shields.io/badge/Docs-VitePress-646cff?style=for-the-badge&logo=vite&logoColor=white)](https://bentopdf.com/docs/) [![Documentation](https://img.shields.io/badge/Docs-VitePress-646cff?style=for-the-badge&logo=vite&logoColor=white)](https://bentopdf.com/docs/)
Visit our [Documentation](https://bentopdf.com/docs/) for: Visit our [Documentation](https://bentopdf.com/docs/) for:
- **Getting Started** guide - **Getting Started** guide
- **Tools Reference** (50+ tools) - **Tools Reference** (50+ tools)
- **Self-Hosting** guides (Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache) - **Self-Hosting** guides (Docker, Vercel, Netlify, Cloudflare, AWS, Hostinger, Nginx, Apache)
@@ -33,12 +73,40 @@ Visit our [Documentation](https://bentopdf.com/docs/) for:
## 📜 Licensing ## 📜 Licensing
BentoPDF is dual-licensed: BentoPDF is **dual-licensed** to fit your needs:
- **AGPL-3.0** for open-source projects where you share your full source code publicly | License | Best For | Price |
- **Commercial License** for proprietary/closed-source applications - **[Get Lifetime License for $49](https://ko-fi.com/s/f32ca4cb75)** (a one-time lifetime purchase, includes all feature updates forever) | -------------- | -------------------------------------------- | ------------------ |
| **AGPL-3.0** | Open-source projects with public source code | **Free** |
| **Commercial** | Proprietary / closed-source applications | **$49** (lifetime) |
For more details, see our [Licensing Page](https://bentopdf.com/licensing.html) <p align="center">
<a href="https://buy.polar.sh/polar_cl_ThDfffbl733x7oAodcIryCzhlO57ZtcWPq6HJ1qMChd">
<img src="https://img.shields.io/badge/🚀_Get_Commercial_License-$49_Lifetime-6366f1?style=for-the-badge&labelColor=1f2937" alt="Get Commercial License">
</a>
</p>
> **One-time purchase** · **Unlimited devices & users** · **Lifetime updates** · **No AGPL obligations**
📖 For more details, see our [Licensing Page](https://bentopdf.com/licensing.html)
### AGPL Components (Not Bundled)
BentoPDF does **not** bundle AGPL-licensed processing libraries. The following components must be configured separately via **Advanced Settings** if you wish to use their features:
| Component | License | Features Enabled |
| ---------------------- | -------- | --------------------------------------------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | PDF to Text/Markdown/SVG/DOCX, Extract Images/Tables, EPUB/MOBI/XPS conversion, Compression, Deskew |
| **Ghostscript** | AGPL-3.0 | PDF/A Conversion, Font to Outline |
| **CoherentPDF (CPDF)** | AGPL-3.0 | Merge, Split by Bookmarks, Table of Contents, PDF to/from JSON, Attachments |
> **Why?** This separation ensures clear legal boundaries. Users who need these features can configure their own WASM sources or use our optional [WASM Proxy](cloudflare/WASM-PROXY.md) to load them from external URLs.
**To enable these features:**
1. Navigate to **Advanced Settings** in BentoPDF
2. Configure the URL for each WASM module you need
3. The modules will be loaded dynamically when required
<hr> <hr>
@@ -76,18 +144,20 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
### Organize & Manage PDFs ### Organize & Manage PDFs
| Tool Name | Description | | Tool Name | Description |
| :------------------------ | :------------------------------------------------------------------------- | | :--------------------------- | :------------------------------------------------------------------------------------------------------ |
| **Merge PDFs** | Combine multiple PDF files into one. | | **Merge PDFs** | Combine multiple PDF files into one. Preserves Bookmarks. |
| **Split PDFs** | Extract specific pages or divide a document into smaller files. | | **Split PDFs** | Extract specific pages or divide a document into smaller files. |
| **Organize Pages** | Reorder, duplicate, or delete pages with a simple drag-and-drop interface. | | **Organize Pages** | Reorder, duplicate, or delete pages with a simple drag-and-drop interface. |
| **Extract Pages** | Save a specific range of pages as a new PDF. | | **Extract Pages** | Save a specific range of pages as a new PDF. |
| **Delete Pages** | Remove unwanted pages from your document. | | **Delete Pages** | Remove unwanted pages from your document. |
| **Rotate PDF** | Rotate individual or all pages in a document. | | **Rotate PDF** | Rotate individual or all pages in a document. |
| **Rotate by Custom Degrees** | Rotate pages by any custom angle. |
| **N-Up PDF** | Combine multiple pages onto a single page. | | **N-Up PDF** | Combine multiple pages onto a single page. |
| **View PDF** | A powerful, integrated PDF viewer. | | **View PDF** | A powerful, integrated PDF viewer. |
| **Alternate & Mix pages** | Merge pages by alternating pages from each PDF. | | **Alternate & Mix Pages** | Merge pages by alternating pages from each PDF. Preserves Bookmarks. |
| **Posterize PDF** | Split a PDF into multiple smaller pages for print. | | **Posterize PDF** | Split a PDF into multiple smaller pages for print. |
| **PDF Multi Tool** | Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface. | | **PDF Multi Tool** | Merge, Split, Organize, Delete, Rotate, Add Blank Pages, Extract and Duplicate in an unified interface. |
| **PDF Booklet** | Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet. |
| **Add Attachments** | Embed one or more files into your PDF. | | **Add Attachments** | Embed one or more files into your PDF. |
| **Extract Attachments** | Extract all embedded files from PDF(s) as a ZIP. | | **Extract Attachments** | Extract all embedded files from PDF(s) as a ZIP. |
| **Edit Attachments** | View or remove attachments in your PDF. | | **Edit Attachments** | View or remove attachments in your PDF. |
@@ -102,17 +172,19 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
### Edit & Modify PDFs ### Edit & Modify PDFs
| Tool Name | Description | | Tool Name | Description |
| :--------------------- | :---------------------------------------------------------- | | :------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **PDF Editor** | A comprehensive editor to modify your PDFs. | | **PDF Editor** | Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs. |
| **Create Fillable Forms** | Create professional fillable PDF forms with text fields, checkboxes, dropdowns, radio buttons, signatures, and more. Fully compliant with PDF standards for compatibility with all PDF viewers. | | **Create Fillable Forms** | Create professional fillable PDF forms with text fields, checkboxes, dropdowns, radio buttons, signatures, and more. Fully compliant with PDF standards for compatibility with all PDF viewers. |
| **PDF Form Filler** | Fill in forms directly in the browser. Also supports XFA forms. |
| **Add Page Numbers** | Easily add page numbers with customizable formatting. | | **Add Page Numbers** | Easily add page numbers with customizable formatting. |
| **Add Watermark** | Add text or image watermarks to protect your documents. | | **Add Watermark** | Add text or image watermarks to protect your documents. |
| **Header & Footer** | Add customizable headers and footers. | | **Header & Footer** | Add customizable headers and footers. |
| **Crop PDF** | Crop specific pages or the entire document. | | **Crop PDF** | Crop specific pages or the entire document. |
| **Deskew PDF** | Automatically straighten tilted scanned pages using OpenCV. |
| **Font to Outline** | Convert all fonts to vector outlines for consistent rendering across all devices. |
| **Invert Colors** | Invert the colors of your PDF pages for better readability. | | **Invert Colors** | Invert the colors of your PDF pages for better readability. |
| **Change Background** | Modify the background color of your PDF. | | **Change Background** | Modify the background color of your PDF. |
| **Change Text Color** | Change the color of text content within the PDF. | | **Change Text Color** | Change the color of text content within the PDF. |
| **Fill Forms** | Fill out PDF forms directly in your browser. |
| **Flatten PDF** | Flatten form fields and annotations into static content. | | **Flatten PDF** | Flatten form fields and annotations into static content. |
| **Remove Annotations** | Remove comments, highlights, and other annotations. | | **Remove Annotations** | Remove comments, highlights, and other annotations. |
| **Remove Blank Pages** | Auto detect and remove blank pages in a PDF. | | **Remove Blank Pages** | Auto detect and remove blank pages in a PDF. |
@@ -124,19 +196,40 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
### Convert to PDF ### Convert to PDF
| Tool Name | Description | | Tool Name | Description |
| :------------------ | :-------------------------------------------------------------- | | :-------------------- | :----------------------------------------------------------------------------------------------------- |
| **Image to PDF** | Convert JPG, PNG, WebP, SVG, BMP, HEIC, and TIFF images to PDF. | | **Image to PDF** | Convert JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP to PDF. |
| **JPG to PDF** | Convert JPG images to PDF. | | **JPG to PDF** | Convert JPG, JPEG, and JPEG2000 (JP2/JPX) images to PDF. |
| **PNG to PDF** | Convert PNG images to PDF. | | **PNG to PDF** | Convert PNG images to PDF. |
| **WebP to PDF** | Convert WebP images to PDF. | | **WebP to PDF** | Convert WebP images to PDF. |
| **SVG to PDF** | Convert SVG images to PDF. | | **SVG to PDF** | Convert SVG images to PDF. |
| **BMP to PDF** | Convert BMP images to PDF. | | **BMP to PDF** | Convert BMP images to PDF. |
| **HEIC to PDF** | Convert HEIC images to PDF. | | **HEIC to PDF** | Convert HEIC images to PDF. |
| **TIFF to PDF** | Convert TIFF images to PDF. | | **TIFF to PDF** | Convert TIFF images to PDF. |
| **Markdown to PDF** | Convert `.md` files into professional PDF documents. | | **PSD to PDF** | Convert Adobe Photoshop (PSD) files to PDF. |
| **Word to PDF** | Convert Word documents (DOCX, DOC, ODT, RTF) to PDF. |
| **Excel to PDF** | Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF. |
| **PowerPoint to PDF** | Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF. |
| **ODT to PDF** | Convert OpenDocument Text files to PDF. |
| **ODS to PDF** | Convert OpenDocument Spreadsheet (ODS) files to PDF. |
| **ODP to PDF** | Convert OpenDocument Presentation (ODP) files to PDF. |
| **ODG to PDF** | Convert OpenDocument Graphics (ODG) files to PDF. |
| **RTF to PDF** | Convert Rich Text Format documents to PDF. |
| **CSV to PDF** | Convert CSV spreadsheet files to PDF. |
| **Markdown to PDF** | Write or paste Markdown and export it as a beautifully formatted PDF. |
| **Text to PDF** | Convert plain text files into a PDF. | | **Text to PDF** | Convert plain text files into a PDF. |
| **EPUB to PDF** | Convert EPUB e-books to PDF format. | | **JSON to PDF** | Convert JSON files to PDF. |
| **JSON to PDF** | Convert JSON to PDF. | | **XML to PDF** | Convert XML documents to PDF. |
| **EPUB to PDF** | Convert EPUB e-books to PDF. |
| **MOBI to PDF** | Convert MOBI e-books to PDF. |
| **FB2 to PDF** | Convert FictionBook (FB2) e-books to PDF. |
| **CBZ to PDF** | Convert comic book archives (CBZ/CBR) to PDF. |
| **XPS to PDF** | Convert XPS/OXPS documents to PDF. |
| **Email to PDF** | Convert email files (EML, MSG) to PDF. Supports Outlook exports. |
| **Pages to PDF** | Convert Apple Pages documents to PDF. |
| **WPD to PDF** | Convert WordPerfect documents (WPD) to PDF. |
| **WPS to PDF** | Convert WPS Office documents to PDF. |
| **PUB to PDF** | Convert Microsoft Publisher (PUB) files to PDF. |
| **VSD to PDF** | Convert Microsoft Visio (VSD, VSDX) files to PDF. |
### Convert from PDF ### Convert from PDF
@@ -148,27 +241,32 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
| **PDF to WebP** | Convert each PDF page into a WebP image. | | **PDF to WebP** | Convert each PDF page into a WebP image. |
| **PDF to BMP** | Convert each PDF page into a BMP image. | | **PDF to BMP** | Convert each PDF page into a BMP image. |
| **PDF to TIFF** | Convert each PDF page into a TIFF image. | | **PDF to TIFF** | Convert each PDF page into a TIFF image. |
| **PDF to SVG** | Convert each page into a scalable vector graphic (SVG) for perfect quality. |
| **PDF to Greyscale** | Convert a color PDF into a black-and-white version. | | **PDF to Greyscale** | Convert a color PDF into a black-and-white version. |
| **OCR PDF** | Make scanned PDFs searchable and copyable using Optical Character Recognition. | | **PDF to Text** | Extract text from PDF files and save as plain text (.txt). |
| **PDF to JSON** | Convert PDF files to JSON format. | | **PDF to JSON** | Convert PDF files to JSON format. |
| **PDF to CSV** | Extract tables from PDF and convert to CSV format. |
| **PDF to Excel** | Extract tables from PDF and convert to Excel (XLSX) format. |
| **Extract Tables** | Extract tables from PDF files and export as CSV, JSON, or Markdown. |
| **OCR PDF** | Make scanned PDFs searchable and copyable using Optical Character Recognition. |
### Secure & Optimize PDFs ### Secure & Optimize PDFs
| Tool Name | Description | | Tool Name | Description |
| :--------------------- | :----------------------------------------------------------------- | | :---------------------- | :--------------------------------------------------------------------------------------------------------- |
| **Compress PDF** | Reduce file size while maintaining quality. | | **Compress PDF** | Reduce file size while maintaining quality. |
| **Repair PDF** | Attempt to repair and recover data from a corrupted PDF. | | **Repair PDF** | Attempt to repair and recover data from a corrupted PDF. |
| **Encrypt PDF** | Add a password to protect your PDF from unauthorized access. | | **Encrypt PDF** | Add a password to protect your PDF from unauthorized access. |
| **Decrypt PDF** | Remove password protection from a PDF (password required). | | **Decrypt PDF** | Remove password protection from a PDF (password required). |
| **Change Permissions** | Set or modify user permissions for printing, copying, and editing. | | **Change Permissions** | Set or modify user permissions for printing, copying, and editing. |
| **Sign PDF** | Add your digital signature to a document. | | **Sign PDF** | Draw, type, or upload your signature. |
| **Digital Signature** | Add cryptographic digital signatures using X.509 certificates (PFX/PEM). | | **Digital Signature** | Add cryptographic digital signatures using X.509 certificates (PFX/PEM). Private key never leaves browser. |
| **Validate Signature** | Verify digital signatures and view certificate details. | | **Validate Signature** | Verify digital signatures, check certificate validity, and confirm document integrity. |
| **Redact Content** | Permanently remove sensitive content from your PDFs. | | **Redact Content** | Permanently remove sensitive content from your PDFs. |
| **Edit Metadata** | View and modify PDF metadata (author, title, keywords, etc.). | | **Edit Metadata** | View and modify PDF metadata (author, title, keywords, etc.). |
| **Remove Metadata** | Strip all metadata from your PDF for privacy. | | **Remove Metadata** | Strip all metadata from your PDF for privacy. |
| **Linearize PDF** | Optimize PDF for fast web view. | | **Linearize PDF** | Optimize PDF for fast web viewing. |
| **Sanitize PDF** | Remove potentially unwanted or malicous files from PDF. | | **Sanitize PDF** | Remove metadata, annotations, scripts, and more. |
| **Fix Page Size** | Standardize all pages to a uniform size. | | **Fix Page Size** | Standardize all pages to a uniform size. |
| **Page Dimensions** | Analyze page size, orientation, and units. | | **Page Dimensions** | Analyze page size, orientation, and units. |
| **Remove Restrictions** | Remove password protection and security restrictions associated with digitally signed PDF files. | | **Remove Restrictions** | Remove password protection and security restrictions associated with digitally signed PDF files. |
@@ -180,11 +278,17 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs.
BentoPDF is available in multiple languages: BentoPDF is available in multiple languages:
| Language | Status | | Language | Status |
|------------|--------| | ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| English | [![English](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/en/common.json) | | English | [![English](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/en/common.json) |
| German | [![German](https://img.shields.io/badge/In_Progress-yellow?style=flat-square)](public/locales/de/common.json) | | Chinese | [![Chinese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/zh/common.json) |
| Vietnamese | [![Vietnamese](https://img.shields.io/badge/In_Progress-yellow?style=flat-square)](public/locales/vi/common.json) | | Traditional Chinese | [![Traditional Chinese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/zh-TW/common.json) |
| Chinese | [![Chinese](https://img.shields.io/badge/In_Progress-yellow?style=flat-square)](public/locales/zh/common.json) | | French | [![French](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/fr/common.json) |
| German | [![German](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/de/common.json) |
| Indonesian | [![Indonesian](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/id/common.json) |
| Italian | [![Italian](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/it/common.json) |
| Portuguese | [![Portuguese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/pt/common.json) |
| Turkish | [![Turkish](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/tr/common.json) |
| Vietnamese | [![Vietnamese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/vi/common.json) |
Want to help translate BentoPDF into your language? Check out our [Translation Guide](TRANSLATION.md)! Want to help translate BentoPDF into your language? Check out our [Translation Guide](TRANSLATION.md)!
@@ -200,22 +304,9 @@ You can run BentoPDF locally for development or personal use.
- [npm](https://www.npmjs.com/) (or yarn/pnpm) - [npm](https://www.npmjs.com/) (or yarn/pnpm)
- [Docker](https://www.docker.com/) & [Docker Compose](https://docs.docker.com/compose/install/) (for containerized setup) - [Docker](https://www.docker.com/) & [Docker Compose](https://docs.docker.com/compose/install/) (for containerized setup)
### 🚀 Quick Start with Docker ### 🚀 Quick Start
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/K4AU2B) Run BentoPDF instantly from GitHub Container Registry (Recommended):
You can run BentoPDF directly from Docker Hub or GitHub Container Registry without cloning the repository:
You can also watch the video on how to set it up 👉
[BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link)
**Using Docker Hub:**
```bash
docker run -p 3000:8080 bentopdf/bentopdf:latest
```
**Using GitHub Container Registry:**
```bash ```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
@@ -223,11 +314,34 @@ docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
Open your browser at: http://localhost:3000 Open your browser at: http://localhost:3000
This is the fastest way to try BentoPDF without setting up a development environment. <details>
<summary><b>Alternative: Using Docker Hub or Podman</b></summary>
**Docker Hub:**
```bash
docker run -p 3000:8080 bentopdfteam/bentopdf:latest
```
**Podman (GHCR):**
```bash
podman run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Podman (Docker Hub):**
```bash
podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf:latest
```
> **Note:** All `docker` commands in this documentation work with Podman by replacing `docker` with `podman`.
</details>
### Static Hosting using Netlify, Vercel, and GitHub Pages ### Static Hosting using Netlify, Vercel, and GitHub Pages
It is very straightforward to host your own instance of BentoPDF using a static web page hosting service. Plus, services such as Netlify, Vercel, and GitHub Pages all offer a free tier for getting started. See [Static Hosting](https://github.com/alam00000/bentopdf/blob/main/STATIC-HOSTING.md)) for details. It is very straightforward to host your own instance of BentoPDF using a static web page hosting service. Plus, services such as Netlify, Vercel, and GitHub Pages all offer a free tier for getting started. See [Static Hosting](https://github.com/alam00000/bentopdf/blob/main/STATIC-HOSTING.md) for details.
### 🏠 Self-Hosting Locally ### 🏠 Self-Hosting Locally
@@ -304,7 +418,7 @@ docker build --build-arg COMPRESSION_MODE=all -t bentopdf:all .
``` ```
| Mode | Files Kept | Use Case | | Mode | Files Kept | Use Case |
|------|------------|----------| | ----- | ----------- | --------------------------------- |
| `g` | `.gz` only | Standard nginx or minimal size | | `g` | `.gz` only | Standard nginx or minimal size |
| `b` | `.br` only | Modern CDN with Brotli support | | `b` | `.br` only | Modern CDN with Brotli support |
| `o` | originals | Development or custom compression | | `o` | originals | Development or custom compression |
@@ -323,6 +437,7 @@ npm run build
``` ```
**How it works:** **How it works:**
- When `VITE_USE_CDN=true`: Browser loads WASM files from jsDelivr CDN (fast, global delivery) - When `VITE_USE_CDN=true`: Browser loads WASM files from jsDelivr CDN (fast, global delivery)
- Local files are **always included** as automatic fallback - Local files are **always included** as automatic fallback
- If CDN fails then it falls back to local files - If CDN fails then it falls back to local files
@@ -348,7 +463,7 @@ cp -r dist/* serve-test/tools/bentopdf/
npx serve serve-test npx serve serve-test
``` ```
The website can be accessible at: ```http://localhost:3000/tools/bentopdf/``` The website can be accessible at: `http://localhost:3000/tools/bentopdf/`
The `npm run package` command creates a `dist-{version}.zip` file that you can use for self-hosting. The `npm run package` command creates a `dist-{version}.zip` file that you can use for self-hosting.
@@ -379,10 +494,11 @@ docker run -p 3000:8080 bentopdf-simple
``` ```
> **Important**: > **Important**:
>
> - Always include trailing slashes in `BASE_URL` (e.g., `/bentopdf/` not `/bentopdf`) > - Always include trailing slashes in `BASE_URL` (e.g., `/bentopdf/` not `/bentopdf`)
> - The default value is `/` for root deployment > - The default value is `/` for root deployment
### 🚀 Run with Docker Compose (Recommended) ### 🚀 Run with Docker Compose / Podman Compose (Recommended)
For a more robust setup with auto-restart capabilities: For a more robust setup with auto-restart capabilities:
@@ -391,7 +507,8 @@ For a more robust setup with auto-restart capabilities:
```yaml ```yaml
services: services:
bentopdf: bentopdf:
image: bentopdf/bentopdf:latest image: ghcr.io/alam00000/bentopdf:latest # Recommended
# image: bentopdfteam/bentopdf:latest # Alternative: Docker Hub
container_name: bentopdf container_name: bentopdf
ports: ports:
- '3000:8080' - '3000:8080'
@@ -401,11 +518,48 @@ services:
2. **Start the application**: 2. **Start the application**:
```bash ```bash
# Docker Compose
docker-compose up -d docker-compose up -d
# Podman Compose
podman-compose up -d
``` ```
The application will be available at `http://localhost:3000`. The application will be available at `http://localhost:3000`.
### 🐧 Podman Quadlet (Systemd Integration)
For Linux production deployments, you can run BentoPDF as a systemd service using [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html).
Create `~/.config/containers/systemd/bentopdf.container`:
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
Then enable and start:
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
For detailed Quadlet configuration, see [Self-Hosting Docker Guide](https://bentopdf.com/docs/self-hosting/docker).
### 🏢 Simple Mode for Internal Use ### 🏢 Simple Mode for Internal Use
For organizations that want a clean, distraction-free interface focused solely on PDF tools, BentoPDF supports a **Simple Mode** that hides all branding and marketing content. For organizations that want a clean, distraction-free interface focused solely on PDF tools, BentoPDF supports a **Simple Mode** that hides all branding and marketing content.
@@ -441,6 +595,7 @@ For detailed security configuration, see [SECURITY.md](SECURITY.md).
The **Digital Signature** tool uses a signing library that may need to fetch certificate chain data from certificate authority provider. Since many certificate servers don't include CORS headers, a proxy is required for this feature to work in the browser. The **Digital Signature** tool uses a signing library that may need to fetch certificate chain data from certificate authority provider. Since many certificate servers don't include CORS headers, a proxy is required for this feature to work in the browser.
**When is the proxy needed?** **When is the proxy needed?**
- Only when using the Digital Signature tool - Only when using the Digital Signature tool
- Only if your certificate requires fetching issuer certificates from external URLs - Only if your certificate requires fetching issuer certificates from external URLs
- Self-signed certificates typically don't need this - Self-signed certificates typically don't need this
@@ -448,16 +603,19 @@ The **Digital Signature** tool uses a signing library that may need to fetch cer
**Deploying the CORS Proxy (Cloudflare Workers):** **Deploying the CORS Proxy (Cloudflare Workers):**
1. **Navigate to the cloudflare directory:** 1. **Navigate to the cloudflare directory:**
```bash ```bash
cd cloudflare cd cloudflare
``` ```
2. **Login to Cloudflare (if not already):** 2. **Login to Cloudflare (if not already):**
```bash ```bash
npx wrangler login npx wrangler login
``` ```
3. **Deploy the worker:** 3. **Deploy the worker:**
```bash ```bash
npx wrangler deploy npx wrangler deploy
``` ```
@@ -474,7 +632,7 @@ The **Digital Signature** tool uses a signing library that may need to fetch cer
The CORS proxy includes several security measures: The CORS proxy includes several security measures:
| Feature | Description | | Feature | Description |
|---------|-------------| | ----------------------- | ------------------------------------------------------------------------- |
| **URL Restrictions** | Only allows certificate URLs (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`) | | **URL Restrictions** | Only allows certificate URLs (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`) |
| **Private IP Blocking** | Blocks requests to localhost, 10.x, 192.168.x, 172.16-31.x | | **Private IP Blocking** | Blocks requests to localhost, 10.x, 192.168.x, 172.16-31.x |
| **File Size Limit** | Rejects files larger than 10MB | | **File Size Limit** | Rejects files larger than 10MB |
@@ -521,20 +679,20 @@ VITE_CORS_PROXY_SECRET=your-secret npm run build
### 📦 Version Management ### 📦 Version Management
BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry: BentoPDF supports semantic versioning with multiple container tags available:
**Docker Hub:** **GitHub Container Registry (Recommended):**
- **Latest**: `bentopdf/bentopdf:latest`
- **Specific Version**: `bentopdf/bentopdf:1.0.0`
- **Version with Prefix**: `bentopdf/bentopdf:v1.0.0`
**GitHub Container Registry:**
- **Latest**: `ghcr.io/alam00000/bentopdf:latest` - **Latest**: `ghcr.io/alam00000/bentopdf:latest`
- **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0` - **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0`
- **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0` - **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0`
**Docker Hub:**
- **Latest**: `bentopdfteam/bentopdf:latest`
- **Specific Version**: `bentopdfteam/bentopdf:1.0.0`
- **Version with Prefix**: `bentopdfteam/bentopdf:v1.0.0`
#### Quick Release #### Quick Release
```bash ```bash
@@ -657,6 +815,7 @@ npm run docs:preview
``` ```
Documentation files are in the `docs/` folder: Documentation files are in the `docs/` folder:
- `docs/index.md` - Home page - `docs/index.md` - Home page
- `docs/getting-started.md` - Getting started guide - `docs/getting-started.md` - Getting started guide
- `docs/tools/` - Tools reference - `docs/tools/` - Tools reference
@@ -670,6 +829,8 @@ Documentation files are in the `docs/` folder:
BentoPDF wouldn't be possible without the amazing open-source tools and libraries that power it. We'd like to extend our heartfelt thanks to the creators and maintainers of: BentoPDF wouldn't be possible without the amazing open-source tools and libraries that power it. We'd like to extend our heartfelt thanks to the creators and maintainers of:
**Bundled Libraries:**
- **[PDFLib.js](https://pdf-lib.js.org/)** For enabling powerful client-side PDF manipulation. - **[PDFLib.js](https://pdf-lib.js.org/)** For enabling powerful client-side PDF manipulation.
- **[PDF.js](https://mozilla.github.io/pdf.js/)** For the robust PDF rendering engine in the browser. - **[PDF.js](https://mozilla.github.io/pdf.js/)** For the robust PDF rendering engine in the browser.
- **[PDFKit](https://pdfkit.org/)** For creating and editing PDFs with ease. - **[PDFKit](https://pdfkit.org/)** For creating and editing PDFs with ease.
@@ -677,10 +838,15 @@ BentoPDF wouldn't be possible without the amazing open-source tools and librarie
- **[Cropper.js](https://fengyuanchen.github.io/cropperjs/)** For intuitive image cropping functionality. - **[Cropper.js](https://fengyuanchen.github.io/cropperjs/)** For intuitive image cropping functionality.
- **[Vite](https://vitejs.dev/)** For lightning-fast development and build tooling. - **[Vite](https://vitejs.dev/)** For lightning-fast development and build tooling.
- **[Tailwind CSS](https://tailwindcss.com/)** For rapid, flexible, and beautiful UI styling. - **[Tailwind CSS](https://tailwindcss.com/)** For rapid, flexible, and beautiful UI styling.
- **[qpdf](https://github.com/qpdf/qpdf)** and **[qpdf-wasm](https://github.com/neslinesli93/qpdf-wasm)** A powerful command-line tool and library for inspecting, repairing, and transforming PDF file ported to wasm - **[qpdf](https://github.com/qpdf/qpdf)** and **[qpdf-wasm](https://github.com/neslinesli93/qpdf-wasm)** For inspecting, repairing, and transforming PDF files.
- **[cpdf](https://www.coherentpdf.com/)** For content preserving pdf operations.
- **[LibreOffice](https://www.libreoffice.org/)** For powerful document conversion capabilities. - **[LibreOffice](https://www.libreoffice.org/)** For powerful document conversion capabilities.
- **[PyMuPDF](https://github.com/pymupdf/PyMuPDF)** For high-performance PDF manipulation and data extraction.
- **[Ghostscript(GhostPDL)](https://github.com/ArtifexSoftware/ghostpdl)** Needs no Introduction. **AGPL Libraries (Not Bundled - User Configured):**
- **[CoherentPDF (cpdf)](https://www.coherentpdf.com/)** For content-preserving PDF operations. _(AGPL-3.0)_
- **[PyMuPDF](https://github.com/pymupdf/PyMuPDF)** For high-performance PDF manipulation and data extraction. _(AGPL-3.0)_
- **[Ghostscript (GhostPDL)](https://github.com/ArtifexSoftware/ghostpdl)** For PDF/A conversion and font outlining. _(AGPL-3.0)_
> **Note:** AGPL-licensed libraries are not bundled with BentoPDF. Users can optionally configure these via Advanced Settings to enable additional features.
Your work inspires and empowers developers everywhere. Thank you for making open-source amazing! Your work inspires and empowers developers everywhere. Thank you for making open-source amazing!

View File

@@ -215,16 +215,16 @@ git reset --hard HEAD~1
1. **GitHub Actions Triggered**: Workflow starts building Docker image 1. **GitHub Actions Triggered**: Workflow starts building Docker image
2. **Docker Build**: Multi-architecture image created 2. **Docker Build**: Multi-architecture image created
3. **Docker Push**: Images pushed to Docker Hub with tags: 3. **Docker Push**: Images pushed to Docker Hub with tags:
- `bentopdf/bentopdf:latest` - `bentopdfteam/bentopdf:latest`
- `bentopdf/bentopdf:1.0.1` - `bentopdfteam/bentopdf:1.0.1`
- `bentopdf/bentopdf:v1.0.1` - `bentopdfteam/bentopdf:v1.0.1`
### **End Result:** ### **End Result:**
Users can immediately pull your new version: Users can immediately pull your new version:
```bash ```bash
docker pull bentopdf/bentopdf:1.0.1 docker pull bentopdfteam/bentopdf:1.0.1
``` ```
--- ---

View File

@@ -23,27 +23,35 @@ When enabled, Simple Mode will:
Use the pre-built Simple Mode image directly: Use the pre-built Simple Mode image directly:
**Using GitHub Container Registry (Recommended):**
```bash
# Docker
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
**Using Docker Hub:** **Using Docker Hub:**
```bash ```bash
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest # Docker
docker run -p 3000:8080 bentopdfteam/bentopdf-simple:latest
# Podman
podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf-simple:latest
``` ```
**Using GitHub Container Registry:** Or with Docker Compose / Podman Compose:
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
Or with Docker Compose:
```yaml ```yaml
services: services:
bentopdf: bentopdf:
# Using Docker Hub # Using GitHub Container Registry (Recommended)
image: bentopdf/bentopdf-simple:latest image: ghcr.io/alam00000/bentopdf-simple:latest
# Or using GitHub Container Registry # Or using Docker Hub
# image: ghcr.io/alam00000/bentopdf-simple:latest # image: bentopdfteam/bentopdf-simple:latest
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -105,9 +113,13 @@ This automatically builds and serves Simple Mode on `http://localhost:3000`.
### Method 2: Using Pre-built Image (Easiest for Production) ### Method 2: Using Pre-built Image (Easiest for Production)
```bash ```bash
# Pull and run the Simple Mode image # Docker - Pull and run the Simple Mode image
docker pull bentopdf/bentopdf-simple:latest docker pull ghcr.io/alam00000/bentopdf-simple:latest
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman pull ghcr.io/alam00000/bentopdf-simple:latest
podman run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
``` ```
Open `http://localhost:3000` in your browser. Open `http://localhost:3000` in your browser.
@@ -127,11 +139,13 @@ Open `http://localhost:3000` in your browser.
### Method 4: Compare Both Modes ### Method 4: Compare Both Modes
```bash ```bash
# Test Normal Mode # Test Normal Mode (Docker)
docker run -p 3000:8080 bentopdf/bentopdf:latest docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
# Test Simple Mode # Test Simple Mode (Docker)
docker run -p 3001:8080 bentopdf/bentopdf-simple:latest docker run -p 3001:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman users: replace 'docker' with 'podman'
``` ```
- Normal Mode: `http://localhost:3000` - Normal Mode: `http://localhost:3000`
@@ -149,52 +163,82 @@ When Simple Mode is working correctly, you should see:
- ❌ No hero section with "The PDF Toolkit built for privacy" - ❌ No hero section with "The PDF Toolkit built for privacy"
- ❌ No features, FAQ, testimonials, or footer sections - ❌ No features, FAQ, testimonials, or footer sections
## 📦 Available Docker Images ## 📦 Available Container Images
### Normal Mode (Full Branding) ### Normal Mode (Full Branding)
**Docker Hub:** **GitHub Container Registry (Recommended):**
- `bentopdf/bentopdf:latest`
- `bentopdf/bentopdf:v1.0.0` (versioned)
**GitHub Container Registry:**
- `ghcr.io/alam00000/bentopdf:latest` - `ghcr.io/alam00000/bentopdf:latest`
- `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned) - `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned)
### Simple Mode (Clean Interface)
**Docker Hub:** **Docker Hub:**
- `bentopdf/bentopdf-simple:latest` - `bentopdfteam/bentopdf:latest`
- `bentopdf/bentopdf-simple:v1.0.0` (versioned) - `bentopdfteam/bentopdf:v1.0.0` (versioned)
**GitHub Container Registry:** ### Simple Mode (Clean Interface)
**GitHub Container Registry (Recommended):**
- `ghcr.io/alam00000/bentopdf-simple:latest` - `ghcr.io/alam00000/bentopdf-simple:latest`
- `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned) - `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned)
**Docker Hub:**
- `bentopdfteam/bentopdf-simple:latest`
- `bentopdfteam/bentopdf-simple:v1.0.0` (versioned)
## 🚀 Production Deployment Examples ## 🚀 Production Deployment Examples
### Internal Company Tool ### Docker Compose / Podman Compose
```yaml ```yaml
services: services:
bentopdf: bentopdf:
image: bentopdf/bentopdf-simple:latest image: ghcr.io/alam00000/bentopdf-simple:latest # Recommended
# image: bentopdfteam/bentopdf-simple:latest # Alternative: Docker Hub
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
ports: ports:
- '80:80' - '80:8080'
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
``` ```
### Podman Quadlet (Linux Systemd)
Create `~/.config/containers/systemd/bentopdf-simple.container`:
```ini
[Unit]
Description=BentoPDF Simple Mode
After=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf-simple
PublishPort=80:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
Enable and start:
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf-simple
```
## ⚠️ Important Notes ## ⚠️ Important Notes
- **Pre-built images**: Use `bentopdf/bentopdf-simple:latest` for Simple Mode - **Pre-built images**: Use `ghcr.io/alam00000/bentopdf-simple:latest` for Simple Mode (recommended)
- **Environment variables**: `SIMPLE_MODE=true` only works during build, not runtime - **Environment variables**: `SIMPLE_MODE=true` only works during build, not runtime
- **Build-time optimization**: Simple Mode uses dead code elimination for smaller bundles - **Build-time optimization**: Simple Mode uses dead code elimination for smaller bundles
- **Same functionality**: All PDF tools work identically in both modes - **Same functionality**: All PDF tools work identically in both modes

View File

@@ -20,16 +20,32 @@ This guide will help you add new languages or improve existing translations for
BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages: BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages:
- **English** (`en`) - Default - **English** (`en`) - Default
- **Belarusian** (`be`)
- **German** (`de`) - **German** (`de`)
- **Spanish** (`es`)
- **French** (`fr`)
- **Italian** (`it`)
- **Portuguese** (`pt`)
- **Turkish** (`tr`)
- **Vietnamese** (`vi`) - **Vietnamese** (`vi`)
- **Indonesian** (`id`) - **Indonesian** (`id`)
- **Chinese** (`zh`)
- **Traditional Chinese (Taiwan)** (`zh-TW`)
The app automatically detects the language from the URL path: The app automatically detects the language from the URL path:
- `/en/` → English - `/` or `/en/` → English (default)
- `/de/` → German - `/de/` → German
- `/vi/`Vietnamese - `/fr/`French
- `/id/` → Indonesian - etc.
### Architecture
BentoPDF uses a **static pre-rendering** approach for SEO-optimized i18n:
1. **Build time**: `scripts/generate-i18n-pages.mjs` generates localized HTML files in `dist/{lang}/`
2. **Dev/Preview**: `languageRouterPlugin` in `vite.config.ts` handles URL rewriting
3. **Production**: Nginx serves static files directly from language directories
--- ---
@@ -37,50 +53,52 @@ The app automatically detects the language from the URL path:
**To improve existing translations:** **To improve existing translations:**
1. Navigate to `public/locales/{language}/common.json` 1. Navigate to `public/locales/{language}/common.json` and `public/locales/{language}/tools.json`
2. Find the key you want to update 2. Find the key you want to update
3. Change the translation value 3. Change the translation value
4. Save and test 4. Save and test
**To add a new language (e.g., Spanish):** **To add a new language (e.g., Japanese `ja`):**
1. Copy `public/locales/en/common.json` to `public/locales/es/common.json` 1. Copy `public/locales/en/` to `public/locales/ja/`
2. Translate all values in `es/common.json` 2. Translate all values in both `ja/common.json` and `ja/tools.json`
3. Add Spanish to `supportedLanguages` in `src/js/i18n/i18n.ts` 3. Add Japanese to `supportedLanguages` and `languageNames` in `src/js/i18n/i18n.ts`
4. Add Spanish name to `languageNames` in `src/js/i18n/i18n.ts` 4. Add `'ja'` to `SUPPORTED_LANGUAGES` in `vite.config.ts`
5. Test thoroughly 5. Restart the dev server
6. Run `npm run build` to generate static language pages
7. Test thoroughly
--- ---
## Adding a New Language ## Adding a New Language
Let's add **French** as an example: Let's add **Spanish** as an example:
### Step 1: Create Translation File ### Step 1: Create Translation Files
```bash ```bash
# Create the directory # Create the directory
mkdir -p public/locales/fr mkdir -p public/locales/es
# Copy the English template # Copy the English template
cp public/locales/en/common.json public/locales/fr/common.json cp public/locales/en/common.json public/locales/es/common.json
``` ```
### Step 2: Translate the JSON File ### Step 2: Translate the JSON Files
Open `public/locales/fr/common.json` and translate all the values: Open `public/locales/es/common.json` and translate all the values:
```json ```json
{ {
"nav": { "nav": {
"home": "Accueil", "home": "Inicio",
"about": "À propos", "about": "Acerca de",
"contact": "Contact", "contact": "Contacto",
"allTools": "Tous les outils" "allTools": "Todas las herramientas"
}, },
"hero": { "hero": {
"title": "Votre boîte à outils PDF gratuite et sécurisée", "title": "Tu conjunto de herramientas PDF gratuito y seguro",
"subtitle": "Fusionnez, divisez, compressez et modifiez des PDF directement dans votre navigateur." "subtitle": "Combina, divide, comprime y edita archivos PDF directamente en tu navegador."
} }
// ... continue translating all keys // ... continue translating all keys
} }
@@ -91,22 +109,24 @@ Open `public/locales/fr/common.json` and translate all the values:
**Correct:** **Correct:**
```json ```json
"home": "Accueil" "home": "Inicio"
``` ```
**Wrong:** **Wrong:**
```json ```json
"accueil": "Accueil" "inicio": "Inicio"
``` ```
Then do the same for `public/locales/fr/tools.json` to translate all tool names and descriptions.
### Step 3: Register the Language ### Step 3: Register the Language
Edit `src/js/i18n/i18n.ts`: Edit `src/js/i18n/i18n.ts`:
```typescript ```typescript
// Add 'fr' to supported languages // Add 'fr' to supported languages
export const supportedLanguages = ['en', 'de', 'fr'] as const; export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi'] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number]; export type SupportedLanguage = (typeof supportedLanguages)[number];
// Add French display name // Add French display name
@@ -119,21 +139,46 @@ export const languageNames: Record<SupportedLanguage, string> = {
### Step 4: Update Vite Configuration ### Step 4: Update Vite Configuration
In `vite.config.ts`, ensure the new language is included in the build: In `vite.config.ts`, add your language to the `SUPPORTED_LANGUAGES` array:
```typescript ```typescript
// Add 'fr' to the language regex const SUPPORTED_LANGUAGES = [
const langMatch = url.match(/^\/(en|de|zh|vi|it|fr)(\/.*)?$/); 'en',
'de',
'es',
'zh',
'zh-TW',
'vi',
'it',
'id',
'tr',
'fr',
'pt',
'ja',
] as const;
``` ```
> **Important**: This is required for both dev server routing and the build-time i18n generation.
### Step 5: Test Your Translation ### Step 5: Test Your Translation
```bash ```bash
# Start the dev server # Restart the dev server
npm run dev npm run dev
# Visit the French version # Visit the Japanese version
# http://localhost:5173/fr/ # http://localhost:5173/ja/
```
### Step 6: Build and Verify Static Files
```bash
# Run build (includes i18n page generation)
npm run build
# Verify files were created
ls dist/ja/
# Should show: index.html, merge-pdf.html, etc.
``` ```
--- ---
@@ -283,7 +328,10 @@ In `common.json`:
- German: `http://localhost:5173/de/` - German: `http://localhost:5173/de/`
- Vietnamese: `http://localhost:5173/vi/` - Vietnamese: `http://localhost:5173/vi/`
- Indonesian: `http://localhost:5173/id/` - Indonesian: `http://localhost:5173/id/`
- Your new language: `http://localhost:5173/fr/` - Chinese: `http://localhost:5173/zh/`
- Traditional Chinese (Taiwan): `http://localhost:5173/zh-TW/`
- French: `http://localhost:5173/fr/`
- Your new language: `http://localhost:5173/es/`
3. **Check these pages:** 3. **Check these pages:**
- Homepage (`/`) - Homepage (`/`)
@@ -445,14 +493,48 @@ SyntaxError: Unexpected token } in JSON at position 1234
Make sure you added the language to both arrays in `i18n.ts`: Make sure you added the language to both arrays in `i18n.ts`:
```typescript ```typescript
export const supportedLanguages = ['en', 'de', 'fr']; // ← Add here export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi']; // ← Add here
export const languageNames = { export const languageNames = {
en: 'English', en: 'English',
de: 'Deutsch', de: 'Deutsch',
es: 'Español',
fr: 'Français', // ← And here fr: 'Français', // ← And here
zh: '中文',
vi: 'Tiếng Việt',
}; };
``` ```
### Issue: 404 Error When Accessing Language Pages
**Symptoms:**
Visiting `http://localhost:5173/ja/about.html` shows a 404 error page.
**Solution:**
You need to add your language code to `SUPPORTED_LANGUAGES` in `vite.config.ts`:
```typescript
const SUPPORTED_LANGUAGES = [
'en',
'de',
'es',
'zh',
'zh-TW',
'vi',
'it',
'id',
'tr',
'fr',
'pt',
'ja',
] as const;
```
After updating, restart the dev server:
```bash
npm run dev
```
--- ---
## File Checklist ## File Checklist
@@ -462,10 +544,13 @@ When adding a new language, make sure these files are updated:
- [ ] `public/locales/{lang}/common.json` - Main translation file - [ ] `public/locales/{lang}/common.json` - Main translation file
- [ ] `public/locales/{lang}/tools.json` - Tools translation file - [ ] `public/locales/{lang}/tools.json` - Tools translation file
- [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames` - [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames`
- [ ] `vite.config.ts` - Add to `SUPPORTED_LANGUAGES` array
- [ ] Test all pages: homepage, about, contact, FAQ, tool pages - [ ] Test all pages: homepage, about, contact, FAQ, tool pages
- [ ] Test settings modal and shortcuts - [ ] Test settings modal and shortcuts
- [ ] Test language switcher in footer - [ ] Test language switcher in footer
- [ ] Verify URL routing works (`/{lang}/`) - [ ] Verify URL routing works (`/{lang}/`)
- [ ] Run `npm run build` and verify `dist/{lang}/` folder is created
- [ ] Test that all tools load correctly
--- ---
@@ -502,13 +587,20 @@ Thank you for contributing to BentoPDF! 🎉
Current translation coverage: Current translation coverage:
| Language | Code | Status | Maintainer | | Language | Code | Status | Maintainer |
| ------------- | ---- | -------------- | ---------- | | ------------------- | ------- | -------------- | ---------- |
| English | `en` | ✅ Complete | Core team | | English | `en` | ✅ Complete | Core team |
| German | `de` | 🚧 In Progress | Core team | | German | `de` | ✅ Complete | Community |
| Spanish | `es` | ✅ Complete | Community |
| French | `fr` | ✅ Complete | Community |
| Italian | `it` | ✅ Complete | Community |
| Portuguese | `pt` | ✅ Complete | Community |
| Turkish | `tr` | ✅ Complete | Community |
| Vietnamese | `vi` | ✅ Complete | Community | | Vietnamese | `vi` | ✅ Complete | Community |
| Indonesian | `id` | ✅ Complete | Community | | Indonesian | `id` | ✅ Complete | Community |
| Chinese | `zh` | ✅ Complete | Community |
| Traditional Chinese | `zh-TW` | ✅ Complete | Community |
| Your Language | `??` | 🚧 In Progress | You? | | Your Language | `??` | 🚧 In Progress | You? |
--- ---
**Last Updated**: December 2025 **Last Updated**: January 2026

View File

@@ -78,125 +78,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
<a href="/about.html" class="nav-link" data-i18n="nav.about"
>About</a
>
<a href="/contact.html" class="nav-link" data-i18n="nav.contact"
>Contact</a
>
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home"
>Home</a
>
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about"
>About</a
>
<a
href="/contact.html"
class="mobile-nav-link"
data-i18n="nav.contact"
>Contact</a
>
<a
href="/licensing.html"
class="mobile-nav-link"
data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="mobile-nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8"> <div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
<section id="about-hero" class="text-center py-16 md:py-24"> <section id="about-hero" class="text-center py-16 md:py-24">
@@ -412,171 +294,7 @@
</section> </section>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
<span data-i18n="footer.version">Version</span>
<span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">
Company
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/about.html"
class="hover:text-indigo-400"
data-i18n="footer.aboutUs"
>About Us</a
>
</li>
<li>
<a
href="/faq.html"
class="hover:text-indigo-400"
data-i18n="footer.faqLink"
>FAQ</a
>
</li>
<li>
<a
href="/contact.html"
class="hover:text-indigo-400"
data-i18n="footer.contactUs"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">
Legal
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/licensing.html"
class="hover:text-indigo-400"
data-i18n="nav.licensing"
>Licensing</a
>
</li>
<li>
<a
href="/terms.html"
class="hover:text-indigo-400"
data-i18n="footer.termsAndConditions"
>Terms and Conditions</a
>
</li>
<li>
<a
href="/privacy.html"
class="hover:text-indigo-400"
data-i18n="footer.privacyPolicy"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">
Follow Us
</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

23
chart/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

16
chart/Chart.yaml Normal file
View File

@@ -0,0 +1,16 @@
apiVersion: v2
name: bentopdf
description: BentoPDF static frontend served by NGINX
icon: https://raw.githubusercontent.com/spwoodcock/bentopdf/refs/heads/main/public/favicon.ico
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.15.4"

114
chart/README.md Normal file
View File

@@ -0,0 +1,114 @@
# BentoPDF Helm Chart
Deploys **BentoPDF** as a **single NGINX container** serving the static frontend.
## Prereqs
- Kubernetes cluster
- Helm v3 with OCI support
- An image that serves BentoPDF via nginx (default chart expects the repo image, which listens on **8080** inside the container)
## Quickstart (ClusterIP + port-forward)
```bash
helm install bentopdf ./chart
kubectl port-forward deploy/bentopdf 8080:8080
# open http://127.0.0.1:8080
```
## Configuration
### Image
- **`image.repository`**: container image repo (default `bentopdf/bentopdf`)
- **`image.tag`**: image tag (default: `Chart.appVersion`)
- **`image.pullPolicy`**: default `IfNotPresent`
### Ports
- **`containerPort`**: container listen port (**8080** for the BentoPDF nginx image)
- **`service.port`**: Service port exposed in-cluster (default **80**)
### Environment variables
Use **`env`** for the container.
Example (IPv4-only environments):
```yaml
env:
- name: DISABLE_IPV6
value: "true"
```
### Ingress (optional)
Enable the built-in Kubernetes Ingress:
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: bentopdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API: Gateway + HTTPRoute (optional)
This chart can optionally:
- Create a **Gateway** (`gateway.enabled=true`)
- Create an **HTTPRoute** (`httpRoute.enabled=true`) that points at the chart Service
If your cluster uses a shared Gateway created elsewhere, set `gateway.enabled=false` and point `httpRoute.parentRefs` to that Gateway.
Example (create both Gateway + HTTPRoute):
```yaml
gateway:
enabled: true
gatewayClassName: cloudflare # or nginx, istio, etc (controller-specific)
listeners:
- name: http
protocol: HTTP
port: 80
httpRoute:
enabled: true
hostnames:
- bentopdf.example.com
parentRefs:
- name: "" # default: release fullname (or gateway.name if set)
sectionName: http
rules:
- matches:
- path:
type: PathPrefix
value: /
```
## Publish this chart to GHCR (OCI) for testing/deploying
### Build And Push OCI
```bash
echo "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USERNAME" --password-stdin
cd chart
helm package .
# produces bentopdf-<version>.tgz
helm push bentopdf-*.tgz oci://ghcr.io/$GHCR_USERNAME/charts
```
This could be automated as part of a Github workflow.
### Deploy
```bash
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf --version 0.1.0
```

35
chart/templates/NOTES.txt Normal file
View File

@@ -0,0 +1,35 @@
1. Get the application URL by running these commands:
{{- if .Values.httpRoute.enabled }}
{{- if .Values.httpRoute.hostnames }}
export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }}
{{- else }}
export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}")
{{- end }}
{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }}
echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application"
NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules.
The rules can be set for path, method, header and query parameters.
You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml'
{{- end }}
{{- else if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "chart.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "chart.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,52 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "chart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "chart.labels" -}}
helm.sh/chart: {{ include "chart.chart" . }}
{{ include "chart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "chart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "chart.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.containerPort }}
protocol: TCP
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}

View File

@@ -0,0 +1,17 @@
{{- if .Values.gateway.enabled -}}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: {{ default (include "chart.fullname" .) .Values.gateway.name }}
namespace: {{ default .Release.Namespace .Values.gateway.namespace }}
labels:
{{- include "chart.labels" . | nindent 4 }}
{{- with .Values.gateway.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
gatewayClassName: {{ required "values.gateway.gatewayClassName is required when gateway.enabled=true" .Values.gateway.gatewayClassName }}
listeners:
{{- toYaml .Values.gateway.listeners | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,51 @@
{{- if .Values.httpRoute.enabled -}}
{{- $fullName := include "chart.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- $defaultGatewayName := (default $fullName .Values.gateway.name) -}}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ $fullName }}
labels:
{{- include "chart.labels" . | nindent 4 }}
{{- with .Values.httpRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- range $ref := .Values.httpRoute.parentRefs }}
- name: {{ default $defaultGatewayName $ref.name | quote }}
{{- with $ref.sectionName }}
sectionName: {{ . | quote }}
{{- end }}
{{- with $ref.namespace }}
namespace: {{ . | quote }}
{{- end }}
{{- with $ref.kind }}
kind: {{ . | quote }}
{{- end }}
{{- with $ref.group }}
group: {{ . | quote }}
{{- end }}
{{- end }}
{{- with .Values.httpRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.httpRoute.rules }}
{{- with .matches }}
- matches:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 8 }}
{{- end }}
backendRefs:
- name: {{ $fullName }}
port: {{ $svcPort }}
weight: 1
{{- end }}
{{- end }}

View File

@@ -0,0 +1,43 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "chart.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "chart.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,4 @@
# The service account was removed, as it's not necessary for a chart like this.
#
# There are no calls to the Kubernetes API - routing between services.
# It's a simple nginx static website deployment only.

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "chart.fullname" . }}-test-connection"
labels:
{{- include "chart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['-qO-', 'http://{{ include "chart.fullname" . }}:{{ .Values.service.port }}/']
restartPolicy: Never

72
chart/values.yaml Normal file
View File

@@ -0,0 +1,72 @@
# Default values for the BentoPDF chart (single nginx static frontend).
replicaCount: 1
image:
# Image built from this repo's `Dockerfile` (nginx serving static frontend).
repository: alam00000/bentopdf-simple
pullPolicy: IfNotPresent
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
podAnnotations: {}
podLabels: {}
service:
type: ClusterIP
port: 80
# Container listen port (BentoPDF nginx image listens on 8080).
containerPort: 8080
env: []
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: bentopdf.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# Gateway API (optional)
gateway:
enabled: false
name: "" # default: release fullname
namespace: "" # default: release namespace
annotations: {}
gatewayClassName: "" # required when enabled=true
listeners:
- name: http
protocol: HTTP
port: 80
httpRoute:
enabled: false
annotations: {}
parentRefs:
- name: "" # default: gateway.name (if set) else release fullname
sectionName: http
hostnames:
- bentopdf.local
rules:
- matches:
- path:
type: PathPrefix
value: /
resources: {}
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http

92
cloudflare/WASM-PROXY.md Normal file
View File

@@ -0,0 +1,92 @@
# WASM Proxy Setup Guide
BentoPDF uses a Cloudflare Worker to proxy WASM library requests, bypassing CORS restrictions when loading AGPL-licensed components (PyMuPDF, Ghostscript, CoherentPDF) from external sources.
## Quick Start
### 1. Deploy the Worker
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy -c wasm-wrangler.toml
```
### 2. Configure Source URLs
Set environment secrets with the base URLs for your WASM files:
```bash
# Option A: Interactive prompts
npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml
npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml
npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml
# Option B: Set via Cloudflare Dashboard
# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables
```
**Recommended Source URLs:**
- PYMUPDF_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/`
- GS_SOURCE: `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/`
- CPDF_SOURCE: `https://cdn.jsdelivr.net/npm/coherentpdf/dist/`
> **Note:** You can use your own hosted WASM files instead of the recommended URLs. Just ensure your files match the expected directory structure and file names that BentoPDF expects for each module.
### 3. Configure BentoPDF
In BentoPDF's Advanced Settings (wasm-settings.html), enter:
| Module | URL |
| ----------- | ------------------------------------------------------------------- |
| PyMuPDF | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/pymupdf/` |
| Ghostscript | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/gs/` |
| CoherentPDF | `https://bentopdf-wasm-proxy.<your-subdomain>.workers.dev/cpdf/` |
## Custom Domain (Optional)
To use a custom domain like `wasm.bentopdf.com`:
1. Add route in `wasm-wrangler.toml`:
```toml
routes = [
{ pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" }
]
```
2. Add DNS record in Cloudflare:
- Type: AAAA
- Name: wasm
- Content: 100::
- Proxied: Yes
3. Redeploy:
```bash
npx wrangler deploy -c wasm-wrangler.toml
```
## Security Features
- **Origin validation**: Only allows requests from configured origins
- **Rate limiting**: 100 requests/minute per IP (requires KV namespace)
- **File type restrictions**: Only WASM-related files (.js, .wasm, .data, etc.)
- **Size limits**: Max 100MB per file
- **Caching**: Reduces origin requests and improves performance
## Self-Hosting Notes
1. Update `ALLOWED_ORIGINS` in `wasm-proxy-worker.js` to include your domain
2. Host your WASM files on any origin (R2, S3, or any CDN)
3. Set source URLs as secrets in your worker
## Endpoints
| Endpoint | Description |
| ------------ | -------------------------------------- |
| `/` | Health check, shows configured modules |
| `/pymupdf/*` | PyMuPDF WASM files |
| `/gs/*` | Ghostscript WASM files |
| `/cpdf/*` | CoherentPDF files |

View File

@@ -0,0 +1,356 @@
/**
* BentoPDF WASM Proxy Worker
*
* This Cloudflare Worker proxies WASM module requests to bypass CORS restrictions.
* It fetches WASM libraries (PyMuPDF, Ghostscript, CoherentPDF) from configured sources
* and serves them with proper CORS headers.
*
* Endpoints:
* - /pymupdf/* - Proxies to PyMuPDF WASM source
* - /gs/* - Proxies to Ghostscript WASM source
* - /cpdf/* - Proxies to CoherentPDF WASM source
*
* Deploy: cd cloudflare && npx wrangler deploy -c wasm-wrangler.toml
*
* Required Environment Variables (set in Cloudflare dashboard):
* - PYMUPDF_SOURCE: Base URL for PyMuPDF WASM files (e.g., https://cdn.example.com/pymupdf)
* - GS_SOURCE: Base URL for Ghostscript WASM files (e.g., https://cdn.example.com/gs)
* - CPDF_SOURCE: Base URL for CoherentPDF files (e.g., https://cdn.example.com/cpdf)
*/
const ALLOWED_ORIGINS = ['https://www.bentopdf.com', 'https://bentopdf.com'];
const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
const RATE_LIMIT_MAX_REQUESTS = 100;
const RATE_LIMIT_WINDOW_MS = 60 * 1000;
const CACHE_TTL_SECONDS = 604800;
const ALLOWED_EXTENSIONS = [
'.js',
'.mjs',
'.wasm',
'.data',
'.py',
'.so',
'.zip',
'.json',
'.mem',
'.asm.js',
'.worker.js',
'.html',
];
function isAllowedOrigin(origin) {
if (!origin) return true; // Allow no-origin requests (e.g., direct browser navigation)
return ALLOWED_ORIGINS.some((allowed) =>
origin.startsWith(allowed.replace(/\/$/, ''))
);
}
function isAllowedFile(pathname) {
const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase();
if (ALLOWED_EXTENSIONS.includes(ext)) return true;
if (!pathname.includes('.') || pathname.endsWith('/')) return true;
return false;
}
function corsHeaders(origin) {
return {
'Access-Control-Allow-Origin': origin || '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Range, Cache-Control',
'Access-Control-Expose-Headers':
'Content-Length, Content-Range, Content-Type',
'Access-Control-Max-Age': '86400',
};
}
function handleOptions(request) {
const origin = request.headers.get('Origin');
return new Response(null, {
status: 204,
headers: corsHeaders(origin),
});
}
function getContentType(pathname) {
const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase();
const contentTypes = {
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.wasm': 'application/wasm',
'.json': 'application/json',
'.data': 'application/octet-stream',
'.py': 'text/x-python',
'.so': 'application/octet-stream',
'.zip': 'application/zip',
'.mem': 'application/octet-stream',
'.html': 'text/html',
};
return contentTypes[ext] || 'application/octet-stream';
}
async function proxyRequest(request, env, sourceBaseUrl, subpath, origin) {
if (!sourceBaseUrl) {
return new Response(
JSON.stringify({
error: 'Source not configured',
message: 'This WASM module source URL has not been configured.',
}),
{
status: 503,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
const normalizedBase = sourceBaseUrl.endsWith('/')
? sourceBaseUrl.slice(0, -1)
: sourceBaseUrl;
const normalizedPath = subpath.startsWith('/') ? subpath : `/${subpath}`;
const targetUrl = `${normalizedBase}${normalizedPath}`;
if (!isAllowedFile(normalizedPath)) {
return new Response(
JSON.stringify({
error: 'Forbidden file type',
message: 'Only WASM-related file types are allowed.',
}),
{
status: 403,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
try {
const cacheKey = new Request(targetUrl, request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
response = await fetch(targetUrl, {
headers: {
'User-Agent': 'BentoPDF-WASM-Proxy/1.0',
Accept: '*/*',
},
});
if (!response.ok) {
return new Response(
JSON.stringify({
error: 'Failed to fetch resource',
status: response.status,
statusText: response.statusText,
targetUrl: targetUrl,
}),
{
status: response.status,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
const contentLength = parseInt(
response.headers.get('Content-Length') || '0',
10
);
if (contentLength > MAX_FILE_SIZE_BYTES) {
return new Response(
JSON.stringify({
error: 'File too large',
message: `File exceeds maximum size of ${MAX_FILE_SIZE_BYTES / 1024 / 1024}MB`,
}),
{
status: 413,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
response = new Response(response.body, response);
response.headers.set(
'Cache-Control',
`public, max-age=${CACHE_TTL_SECONDS}`
);
if (response.status === 200) {
await cache.put(cacheKey, response.clone());
}
}
const bodyData = await response.arrayBuffer();
return new Response(bodyData, {
status: 200,
headers: {
...corsHeaders(origin),
'Content-Type': getContentType(normalizedPath),
'Content-Length': bodyData.byteLength.toString(),
'Cache-Control': `public, max-age=${CACHE_TTL_SECONDS}`,
'X-Proxied-From': new URL(targetUrl).hostname,
},
});
} catch (error) {
return new Response(
JSON.stringify({
error: 'Proxy error',
message: error.message,
}),
{
status: 500,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
}
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const pathname = url.pathname;
const origin = request.headers.get('Origin');
if (request.method === 'OPTIONS') {
return handleOptions(request);
}
if (!isAllowedOrigin(origin)) {
return new Response(
JSON.stringify({
error: 'Forbidden',
message:
'Origin not allowed. Add your domain to ALLOWED_ORIGINS if self-hosting.',
}),
{
status: 403,
headers: {
'Content-Type': 'application/json',
...corsHeaders(origin),
},
}
);
}
if (request.method !== 'GET' && request.method !== 'HEAD') {
return new Response('Method not allowed', {
status: 405,
headers: corsHeaders(origin),
});
}
if (env.RATE_LIMIT_KV) {
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
const rateLimitKey = `wasm-ratelimit:${clientIP}`;
const now = Date.now();
const rateLimitData = await env.RATE_LIMIT_KV.get(rateLimitKey, {
type: 'json',
});
const requests = rateLimitData?.requests || [];
const recentRequests = requests.filter(
(t) => now - t < RATE_LIMIT_WINDOW_MS
);
if (recentRequests.length >= RATE_LIMIT_MAX_REQUESTS) {
return new Response(
JSON.stringify({
error: 'Rate limit exceeded',
message: `Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute.`,
}),
{
status: 429,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
'Retry-After': '60',
},
}
);
}
recentRequests.push(now);
await env.RATE_LIMIT_KV.put(
rateLimitKey,
JSON.stringify({ requests: recentRequests }),
{
expirationTtl: 120,
}
);
}
if (pathname.startsWith('/pymupdf/')) {
const subpath = pathname.replace('/pymupdf', '');
return proxyRequest(request, env, env.PYMUPDF_SOURCE, subpath, origin);
}
if (pathname.startsWith('/gs/')) {
const subpath = pathname.replace('/gs', '');
return proxyRequest(request, env, env.GS_SOURCE, subpath, origin);
}
if (pathname.startsWith('/cpdf/')) {
const subpath = pathname.replace('/cpdf', '');
return proxyRequest(request, env, env.CPDF_SOURCE, subpath, origin);
}
if (pathname === '/' || pathname === '/health') {
return new Response(
JSON.stringify({
service: 'BentoPDF WASM Proxy',
version: '1.0.0',
endpoints: {
pymupdf: '/pymupdf/*',
gs: '/gs/*',
cpdf: '/cpdf/*',
},
configured: {
pymupdf: !!env.PYMUPDF_SOURCE,
gs: !!env.GS_SOURCE,
cpdf: !!env.CPDF_SOURCE,
},
}),
{
status: 200,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
}
return new Response(
JSON.stringify({
error: 'Not Found',
message: 'Use /pymupdf/*, /gs/*, or /cpdf/* endpoints',
}),
{
status: 404,
headers: {
...corsHeaders(origin),
'Content-Type': 'application/json',
},
}
);
},
};

View File

@@ -0,0 +1,69 @@
name = "bentopdf-wasm-proxy"
main = "wasm-proxy-worker.js"
compatibility_date = "2024-01-01"
# =============================================================================
# DEPLOYMENT
# =============================================================================
# Deploy this worker:
# cd cloudflare
# npx wrangler deploy -c wasm-wrangler.toml
#
# Set environment secrets (one of the following methods):
# Option A: Cloudflare Dashboard
# Go to Workers & Pages > bentopdf-wasm-proxy > Settings > Variables
# Add: PYMUPDF_SOURCE, GS_SOURCE, CPDF_SOURCE
#
# Option B: Wrangler CLI
# npx wrangler secret put PYMUPDF_SOURCE -c wasm-wrangler.toml
# npx wrangler secret put GS_SOURCE -c wasm-wrangler.toml
# npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml
# =============================================================================
# WASM SOURCE URLS
# =============================================================================
# Set these as secrets in the Cloudflare dashboard or via wrangler:
#
# PYMUPDF_SOURCE: Base URL to PyMuPDF WASM files
# Example: https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm/assets
# https://your-bucket.r2.cloudflarestorage.com/pymupdf
#
# GS_SOURCE: Base URL to Ghostscript WASM files
# Example: https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets
# https://your-bucket.r2.cloudflarestorage.com/gs
#
# CPDF_SOURCE: Base URL to CoherentPDF files
# Example: https://cdn.jsdelivr.net/npm/coherentpdf/cpdf
# https://your-bucket.r2.cloudflarestorage.com/cpdf
# =============================================================================
# USAGE FROM BENTOPDF
# =============================================================================
# In BentoPDF's WASM Settings page, configure URLs like:
# PyMuPDF: https://wasm.bentopdf.com/pymupdf/
# Ghostscript: https://wasm.bentopdf.com/gs/
# CoherentPDF: https://wasm.bentopdf.com/cpdf/
# =============================================================================
# RATE LIMITING (Optional but recommended)
# =============================================================================
# Create KV namespace:
# npx wrangler kv namespace create "RATE_LIMIT_KV"
#
# Then uncomment and update the ID below:
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "<YOUR_KV_NAMESPACE_ID>"
# Use the same KV namespace as the CORS proxy if you want shared rate limiting
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "b88e030b308941118cd484e3fcb3ae49"
# =============================================================================
# CUSTOM DOMAIN (Optional)
# =============================================================================
# If you want a custom domain like wasm.bentopdf.com:
# routes = [
# { pattern = "wasm.bentopdf.com/*", zone_name = "bentopdf.com" }
# ]

View File

@@ -78,125 +78,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
<a href="/about.html" class="nav-link" data-i18n="nav.about"
>About</a
>
<a href="/contact.html" class="nav-link" data-i18n="nav.contact"
>Contact</a
>
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home"
>Home</a
>
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about"
>About</a
>
<a
href="/contact.html"
class="mobile-nav-link"
data-i18n="nav.contact"
>Contact</a
>
<a
href="/licensing.html"
class="mobile-nav-link"
data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="mobile-nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8"> <div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
<section id="contact-hero" class="text-center py-16 md:py-24"> <section id="contact-hero" class="text-center py-16 md:py-24">
@@ -229,171 +111,7 @@
</div> </div>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
<span data-i18n="footer.version">Version</span>
<span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">
Company
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/about.html"
class="hover:text-indigo-400"
data-i18n="footer.aboutUs"
>About Us</a
>
</li>
<li>
<a
href="/faq.html"
class="hover:text-indigo-400"
data-i18n="footer.faqLink"
>FAQ</a
>
</li>
<li>
<a
href="/contact.html"
class="hover:text-indigo-400"
data-i18n="footer.contactUs"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">
Legal
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/licensing.html"
class="hover:text-indigo-400"
data-i18n="nav.licensing"
>Licensing</a
>
</li>
<li>
<a
href="/terms.html"
class="hover:text-indigo-400"
data-i18n="footer.termsAndConditions"
>Terms and Conditions</a
>
</li>
<li>
<a
href="/privacy.html"
class="hover:text-indigo-400"
data-i18n="footer.privacyPolicy"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">
Follow Us
</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

View File

@@ -1,8 +1,12 @@
services: services:
bentopdf: bentopdf:
# simple mode - bentopdf/bentopdf-simple:latest # GitHub Container Registry (Recommended)
# default mode - bentopdf/bentopdf:latest # simple mode - ghcr.io/alam00000/bentopdf-simple:latest
image: bentopdf/bentopdf-simple:latest # default mode - ghcr.io/alam00000/bentopdf:latest
# Docker Hub (Alternative)
# simple mode - bentopdfteam/bentopdf-simple:latest
# default mode - bentopdfteam/bentopdf:latest
image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf container_name: bentopdf
restart: unless-stopped restart: unless-stopped
ports: ports:

View File

@@ -13,7 +13,7 @@ For complete licensing information, delivery details, AGPL component notices, an
## When Do You Need a Commercial License? ## When Do You Need a Commercial License?
| Use Case | License Required | | Use Case | License Required |
|----------|------------------| | ------------------------------------------- | ---------------------- |
| Open-source project with public source code | AGPL-3.0 (Free) | | Open-source project with public source code | AGPL-3.0 (Free) |
| Internal company tool (not distributed) | AGPL-3.0 (Free) | | Internal company tool (not distributed) | AGPL-3.0 (Free) |
| Proprietary/closed-source application | **Commercial License** | | Proprietary/closed-source application | **Commercial License** |
@@ -30,12 +30,32 @@ For complete licensing information, delivery details, AGPL component notices, an
## Important Notice on Third-Party Components ## Important Notice on Third-Party Components
::: warning AGPL Components ::: warning AGPL Components - Not Bundled
This software includes components licensed under the **GNU AGPL v3**, such as CPDF. BentoPDF **does not bundle** AGPL-licensed processing libraries. The following components are loaded separately by users who configure them via **Advanced Settings**:
- This commercial license **does not** grant rights to use AGPL components in a closed-source manner. | Component | License | Status |
- Users must comply with the AGPL v3 terms for these components. | --------------- | -------- | ----------------------------- |
- Source code for all AGPL components is included in the distribution. | **PyMuPDF** | AGPL-3.0 | Not bundled - user configured |
| **Ghostscript** | AGPL-3.0 | Not bundled - user configured |
| **CoherentPDF** | AGPL-3.0 | Not bundled - user configured |
**Why are AGPL binaries not included?**
To maintain clear legal separation, BentoPDF does not distribute AGPL-licensed binaries. Users who need features powered by these libraries can:
1. Configure their own WASM sources in Advanced Settings
2. Host their own WASM proxy to serve these files
3. Use any compatible CDN that hosts these packages
This approach ensures:
- BentoPDF's core code remains under its dual-license (AGPL-3.0 / Commercial)
- Users make an informed choice when enabling AGPL features
- Clear compliance boundaries for commercial users
:::
::: tip Commercial License & AGPL Features
The commercial license covers BentoPDF's own code. If you configure and use AGPL components (PyMuPDF, Ghostscript, CoherentPDF), you must still comply with their respective AGPL-3.0 license terms, which may require source code disclosure if you distribute modified versions.
::: :::
## Invoicing ## Invoicing
@@ -48,7 +68,7 @@ This software includes components licensed under the **GNU AGPL v3**, such as CP
## What's Included ## What's Included
| Feature | Included | | Feature | Included |
|---------|----------| | ----------------------------- | -------------- |
| Full source code | ✅ | | Full source code | ✅ |
| All 50+ PDF tools | ✅ | | All 50+ PDF tools | ✅ |
| Self-hosting rights | ✅ | | Self-hosting rights | ✅ |
@@ -69,7 +89,7 @@ Yes, with a commercial license. Without it, you must comply with AGPL-3.0, which
### What about the AGPL components? ### What about the AGPL components?
Components like CPDF are licensed under AGPL v3 and remain under that license. The commercial license covers BentoPDF's own code but does not override third-party AGPL obligations. Components like CoherentPDF are licensed under AGPL v3 and remain under that license. The commercial license covers BentoPDF's own code but does not override third-party AGPL obligations.
### How do I get an invoice? ### How do I get an invoice?

View File

@@ -73,22 +73,46 @@ Create `/etc/apache2/sites-available/bentopdf.conf`:
</VirtualHost> </VirtualHost>
``` ```
## Step 4: .htaccess for SPA Routing ## Step 4: .htaccess for Routing
Create `/var/www/bentopdf/.htaccess`: Create `/var/www/bentopdf/.htaccess`:
```apache ```apache
<IfModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
RewriteBase / RewriteBase /
# Don't rewrite files or directories # Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# ADD .HTML EXTENSION (ROOT LEVEL)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# Rewrite everything else to index.html ErrorDocument 404 /404.html
RewriteRule ^ index.html [L]
</IfModule>
``` ```
## Step 5: Enable Required Modules ## Step 5: Enable Required Modules
@@ -122,14 +146,31 @@ BASE_URL=/pdf/ npm run build
2. Update `.htaccess`: 2. Update `.htaccess`:
```apache ```apache
<IfModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
RewriteBase /pdf/ RewriteBase /pdf/
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Language routes
RewriteRule ^en/?$ /pdf/ [R=301,L]
RewriteRule ^en/(.+)$ /pdf/$1 [R=301,L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf/$1/index.html [L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L] RewriteCond %{REQUEST_FILENAME}.html -f
</IfModule> RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf/404.html
``` ```
## Troubleshooting ## Troubleshooting

View File

@@ -1,4 +1,4 @@
# Deploy with Docker # Deploy with Docker / Podman
The easiest way to self-host BentoPDF in a production environment. The easiest way to self-host BentoPDF in a production environment.
@@ -6,22 +6,33 @@ The easiest way to self-host BentoPDF in a production environment.
> **Required Headers for Office File Conversion** > **Required Headers for Office File Conversion**
> >
> LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support: > LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support:
>
> - `Cross-Origin-Opener-Policy: same-origin` > - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp` > - `Cross-Origin-Embedder-Policy: require-corp`
> >
> The official Docker images include these headers. If using a reverse proxy (Traefik, Caddy, etc.), ensure these headers are preserved or added. > The official container images include these headers. If using a reverse proxy (Traefik, Caddy, etc.), ensure these headers are preserved or added.
> [!TIP]
> **Podman Users:** All `docker` commands work with Podman by replacing `docker` with `podman` and `docker-compose` with `podman-compose`.
## Quick Start ## Quick Start
```bash ```bash
# Docker
docker run -d \ docker run -d \
--name bentopdf \ --name bentopdf \
-p 3000:8080 \ -p 3000:8080 \
--restart unless-stopped \ --restart unless-stopped \
ghcr.io/alam00000/bentopdf:latest ghcr.io/alam00000/bentopdf:latest
# Podman
podman run -d \
--name bentopdf \
-p 3000:8080 \
ghcr.io/alam00000/bentopdf:latest
``` ```
## Docker Compose ## Docker Compose / Podman Compose
Create `docker-compose.yml`: Create `docker-compose.yml`:
@@ -31,10 +42,10 @@ services:
image: ghcr.io/alam00000/bentopdf:latest image: ghcr.io/alam00000/bentopdf:latest
container_name: bentopdf container_name: bentopdf
ports: ports:
- "3000:8080" - '3000:8080'
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"] test: ['CMD', 'curl', '-f', 'http://localhost:8080']
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -43,7 +54,11 @@ services:
Run: Run:
```bash ```bash
# Docker Compose
docker compose up -d docker compose up -d
# Podman Compose
podman-compose up -d
``` ```
## Build Your Own Image ## Build Your Own Image
@@ -74,7 +89,7 @@ docker run -d -p 3000:8080 bentopdf:custom
## Environment Variables ## Environment Variables
| Variable | Description | Default | | Variable | Description | Default |
|----------|-------------|---------| | ------------- | ------------------------------- | ------- |
| `SIMPLE_MODE` | Build without LibreOffice tools | `false` | | `SIMPLE_MODE` | Build without LibreOffice tools | `false` |
| `BASE_URL` | Deploy to subdirectory | `/` | | `BASE_URL` | Deploy to subdirectory | `/` |
@@ -94,15 +109,15 @@ services:
traefik: traefik:
image: traefik:v2.10 image: traefik:v2.10
command: command:
- "--providers.docker=true" - '--providers.docker=true'
- "--entrypoints.web.address=:80" - '--entrypoints.web.address=:80'
- "--entrypoints.websecure.address=:443" - '--entrypoints.websecure.address=:443'
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com" - '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
ports: ports:
- "80:80" - '80:80'
- "443:443" - '443:443'
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt - ./letsencrypt:/letsencrypt
@@ -110,15 +125,15 @@ services:
bentopdf: bentopdf:
image: ghcr.io/alam00000/bentopdf:latest image: ghcr.io/alam00000/bentopdf:latest
labels: labels:
- "traefik.enable=true" - 'traefik.enable=true'
- "traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)" - 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
- "traefik.http.routers.bentopdf.entrypoints=websecure" - 'traefik.http.routers.bentopdf.entrypoints=websecure'
- "traefik.http.routers.bentopdf.tls.certresolver=letsencrypt" - 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
- "traefik.http.services.bentopdf.loadbalancer.server.port=8080" - 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
# Required headers for SharedArrayBuffer (LibreOffice WASM) # Required headers for SharedArrayBuffer (LibreOffice WASM)
- "traefik.http.routers.bentopdf.middlewares=bentopdf-headers" - 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
- "traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin" - 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
- "traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp" - 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
restart: unless-stopped restart: unless-stopped
``` ```
@@ -129,8 +144,8 @@ services:
caddy: caddy:
image: caddy:2 image: caddy:2
ports: ports:
- "80:80" - '80:80'
- "443:443" - '443:443'
volumes: volumes:
- ./Caddyfile:/etc/caddy/Caddyfile - ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data - caddy_data:/data
@@ -169,6 +184,141 @@ services:
memory: 128M memory: 128M
``` ```
## Podman Quadlet (Systemd Integration)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) allows you to run Podman containers as systemd services. This is ideal for production deployments on Linux systems.
### Basic Quadlet Setup
Create a container unit file at `~/.config/containers/systemd/bentopdf.container` (user) or `/etc/containers/systemd/bentopdf.container` (system):
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Enable and Start
```bash
# Reload systemd to detect new unit
systemctl --user daemon-reload
# Start the service
systemctl --user start bentopdf
# Enable on boot
systemctl --user enable bentopdf
# Check status
systemctl --user status bentopdf
# View logs
journalctl --user -u bentopdf -f
```
> [!TIP]
> For system-wide deployment, use `systemctl` without `--user` flag and place the file in `/etc/containers/systemd/`.
### Simple Mode Quadlet
For Simple Mode deployment, create `bentopdf-simple.container`:
```ini
[Unit]
Description=BentoPDF Simple Mode - Clean PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf-simple
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Quadlet with Health Check
```ini
[Unit]
Description=BentoPDF with health monitoring
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
HealthCmd=curl -f http://localhost:8080 || exit 1
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Auto-Update with Quadlet
Podman can automatically update containers when new images are available:
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### Quadlet Network Configuration
For custom network configuration, create a network file `bentopdf.network`:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Then reference it in your container file:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Updating ## Updating
```bash ```bash

View File

@@ -141,7 +141,7 @@ AddType image/webp .webp
# ============================================ # ============================================
# 5. REDIRECTS & ROUTING # 5. REDIRECTS & ROUTING
# ============================================ # ============================================
# Canonical WWW # Canonical WWW (update domain as needed)
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC] RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301] RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
@@ -149,19 +149,34 @@ RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
RewriteCond %{HTTPS} off RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash # Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$ RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L] RewriteRule ^ %1 [R=301,L]
# Existing files/dirs # Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L] RewriteRule ^ - [L]
# Language routes # ============================================
RewriteRule ^(en|de|zh|vi|it|id)/(.*)$ /$2 [L] # 5.1. LANGUAGE ROUTES
RewriteRule ^(en|de|zh|vi|it|id)/?$ / [L] # ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root (for SEO consistency)
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================ # ============================================
# 5.5. DOCS ROUTING (VitePress) # 5.5. DOCS ROUTING (VitePress)
@@ -172,15 +187,17 @@ RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L] RewriteRule ^(.*)$ $1.html [L]
# ============================================ # ============================================
# 6. SPA FALLBACK # 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================ # ============================================
# SPA Fallback (exclude /docs)
RewriteCond %{REQUEST_URI} !^/docs
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /index.html [L] RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /index.html # ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html
``` ```
## Subdirectory .htaccess Example ## Subdirectory .htaccess Example
@@ -190,12 +207,23 @@ For `yourdomain.com/pdf-tools/`, update these lines:
```apache ```apache
RewriteBase /pdf-tools/ RewriteBase /pdf-tools/
# ... (same content) ... # ... (same content as above, but update paths) ...
# SPA Fallback - update path # Language prefix root
RewriteRule ^ /pdf-tools/index.html [L] RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf-tools/$1/index.html [L]
ErrorDocument 404 /pdf-tools/index.html # Language prefix with path
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf-tools/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf-tools/404.html
``` ```
## Troubleshooting ## Troubleshooting
@@ -221,12 +249,16 @@ If headers aren't being applied, contact Hostinger support to enable `mod_header
### 404 Errors on Page Refresh ### 404 Errors on Page Refresh
Make sure the SPA fallback rule is at the end of your `.htaccess`: Make sure the `.html` extension rule and language routes are correctly configured. BentoPDF uses static HTML files, not SPA routing:
```apache ```apache
RewriteCond %{REQUEST_FILENAME} !-f # Language routes serve actual files from language directories
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^ /index.html [L] RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# Root level pages
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
``` ```
### File Upload Limits ### File Upload Limits

View File

@@ -2,15 +2,19 @@
BentoPDF can be self-hosted on your own infrastructure. This guide covers various deployment options. BentoPDF can be self-hosted on your own infrastructure. This guide covers various deployment options.
## Quick Start with Docker ## Quick Start with Docker / Podman
The fastest way to self-host BentoPDF: The fastest way to self-host BentoPDF:
```bash ```bash
# Docker
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf:latest docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
# Podman
podman run -d -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
``` ```
Or with Docker Compose: Or with Docker Compose / Podman Compose:
```yaml ```yaml
# docker-compose.yml # docker-compose.yml
@@ -18,14 +22,43 @@ services:
bentopdf: bentopdf:
image: ghcr.io/alam00000/bentopdf:latest image: ghcr.io/alam00000/bentopdf:latest
ports: ports:
- "3000:8080" - '3000:8080'
restart: unless-stopped restart: unless-stopped
``` ```
```bash ```bash
# Docker Compose
docker compose up -d docker compose up -d
# Podman Compose
podman-compose up -d
``` ```
## Podman Quadlet (Linux Systemd)
Run BentoPDF as a systemd service. Create `~/.config/containers/systemd/bentopdf.container`:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
See [Docker deployment guide](/self-hosting/docker) for full Quadlet documentation.
## Building from Source ## Building from Source
```bash ```bash
@@ -45,6 +78,7 @@ npm run build
Simple Mode is designed for internal organizational use where you want to hide all branding and marketing content, showing only the essential PDF tools. Simple Mode is designed for internal organizational use where you want to hide all branding and marketing content, showing only the essential PDF tools.
**What Simple Mode hides:** **What Simple Mode hides:**
- Navigation bar - Navigation bar
- Hero section with marketing content - Hero section with marketing content
- Features, FAQ, testimonials sections - Features, FAQ, testimonials sections
@@ -56,7 +90,7 @@ Simple Mode is designed for internal organizational use where you want to hide a
SIMPLE_MODE=true npm run build SIMPLE_MODE=true npm run build
# Or use the pre-built Docker image # Or use the pre-built Docker image
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest docker run -p 3000:8080 bentopdfteam/bentopdf-simple:latest
``` ```
See [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) for full details. See [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) for full details.
@@ -81,13 +115,50 @@ Choose your platform:
- [Nginx](/self-hosting/nginx) - [Nginx](/self-hosting/nginx)
- [Apache](/self-hosting/apache) - [Apache](/self-hosting/apache)
- [Docker](/self-hosting/docker) - [Docker](/self-hosting/docker)
- [Kubernetes](/self-hosting/kubernetes)
- [CORS Proxy](/self-hosting/cors-proxy) - Required for digital signatures - [CORS Proxy](/self-hosting/cors-proxy) - Required for digital signatures
## Configuring AGPL WASM Components
BentoPDF **does not bundle** AGPL-licensed processing libraries. Some advanced features require you to configure WASM modules separately.
::: warning AGPL Components Not Included
The following WASM modules are **not bundled** with BentoPDF and must be configured by users who want to use features powered by these libraries:
| Component | License | Features |
| --------------- | -------- | ---------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | EPUB/MOBI/FB2/XPS conversion, image extraction, table extraction |
| **Ghostscript** | AGPL-3.0 | PDF/A conversion, compression, deskewing, rasterization |
| **CoherentPDF** | AGPL-3.0 | Table of contents, attachments, PDF merge with bookmarks |
:::
### How to Configure WASM Sources
1. Navigate to **Advanced Settings** in the BentoPDF interface
2. Enter the URLs for the WASM modules you want to use
3. You can use:
- Your own hosted WASM files
- A [WASM proxy](/self-hosting/cors-proxy) you deploy (handles CORS)
- Any compatible CDN hosting these packages
### Hosting Your Own WASM Proxy
If you need to serve AGPL WASM files with proper CORS headers, you can deploy a simple proxy. See the [Cloudflare WASM Proxy guide](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) for an example implementation.
::: tip Why Separate?
This separation ensures:
- Clear legal compliance for commercial users
- Users make informed choices when enabling AGPL features
- BentoPDF's core remains under its dual-license (AGPL-3.0 / Commercial)
:::
## System Requirements ## System Requirements
| Requirement | Minimum | | Requirement | Minimum |
|-------------|---------| | ----------- | ----------------------------------- |
| Storage | ~500 MB (with all WASM modules) | | Storage | ~100 MB (core without AGPL modules) |
| RAM | 512 MB | | RAM | 512 MB |
| CPU | Any modern processor | | CPU | Any modern processor |

View File

@@ -0,0 +1,157 @@
# Deploy with Kubernetes
Kubernetes may be overkill for a static site, but it can be a great fit if you already standardize on Helm + GitOps.
> [!IMPORTANT]
> **Required Headers for Office File Conversion**
>
> LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support:
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> The official BentoPDF nginx images include these headers. In Kubernetes, **Ingress/Gateway controllers are also reverse proxies**, so ensure these headers are preserved (or add them at the edge).
## Prereqs
- Kubernetes cluster
- Helm v3
- A BentoPDF nginx image (e.g. `ghcr.io/alam00000/bentopdf:<tag>`) that serves on **port 8080**
## Deploy with Helm
### Install from this repo (local chart)
```bash
kubectl create namespace bentopdf
helm upgrade --install bentopdf /path/to/bentopdf/chart \
--namespace bentopdf \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
### Install from GHCR (OCI chart)
If the chart is published to GHCR as an OCI artifact:
```bash
export GHCR_USERNAME="<github-org-or-user>"
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf \
--namespace bentopdf \
--create-namespace \
--version 0.1.0 \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
## Expose it
### Port-forward (quick test)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (optional)
Enable Ingress (example for nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (optional)
This chart supports Gateway API `Gateway` + `HTTPRoute`.
Example (Cloudflare Gateway API operator):
```yaml
gateway:
enabled: true
name: bento-tunnel
namespace: bentopdf
gatewayClassName: cloudflare
httpRoute:
enabled: true
parentRefs:
- name: bento-tunnel
namespace: bentopdf
sectionName: http
hostnames:
- pdfs.example.com
```
## Ensuring the SharedArrayBuffer headers still work (Ingress/Gateway)
### What "should" happen
BentoPDFs nginx config sets the required response headers. Most Ingress/Gateway controllers **pass upstream response headers through unchanged**.
### What can break it
- A controller/edge policy that **overrides** or **strips** response headers
- A "security headers" middleware that sets different COOP/COEP values
### How to verify
Run this against your public endpoint:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
You should see:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### If your Ingress controller does not preserve them
Add the headers at the edge (controller-specific). Example for **nginx-ingress**:
```yaml
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
```
### If youre using Gateway API and want to force-add headers
Gateway API supports a `ResponseHeaderModifier` filter. You can attach it in `httpRoute.rules[*].filters`:
```yaml
httpRoute:
enabled: true
hostnames: [pdf.example.com]
parentRefs:
- name: bento-tunnel
namespace: misc
sectionName: http
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Cross-Origin-Opener-Policy
value: same-origin
- name: Cross-Origin-Embedder-Policy
value: require-corp
```
Support for specific filters depends on your Gateway controller; if a filter is ignored, add headers at the edge/controller layer instead.

229
faq.html
View File

@@ -77,97 +77,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/licensing.html" class="nav-link">Licensing</a>
<a href="index.html#tools-header" class="nav-link">All Tools</a>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/licensing.html" class="mobile-nav-link">Licensing</a>
<a href="index.html#tools-header" class="mobile-nav-link"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8"> <div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
<section id="faq-hero" class="text-center py-16 md:py-24"> <section id="faq-hero" class="text-center py-16 md:py-24">
@@ -378,142 +288,7 @@
</section> </section>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li>
<a href="/faq.html" class="hover:text-indigo-400">FAQ</a>
</li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

View File

@@ -99,167 +99,7 @@
</head> </head>
<body class="antialiased"> <body class="antialiased">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
<a href="/about.html" class="nav-link" data-i18n="nav.about"
>About</a
>
<a href="/contact.html" class="nav-link" data-i18n="nav.contact"
>Contact</a
>
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
<a
href="https://github.com/alam00000/bentopdf/"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 text-sm font-medium bg-gray-800 text-gray-200 border border-gray-600 pl-2.5 pr-3 py-1 rounded-full transition-colors duration-200 shadow-sm hover:shadow-md hover:bg-gray-700"
>
<svg
class="w-4 h-4 flex-shrink-0 text-gray-200"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
<span id="github-stars-desktop">-</span>
</a>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center gap-4">
<a
href="https://github.com/alam00000/bentopdf/"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 text-sm font-medium bg-gray-800 text-gray-200 border border-gray-600 pl-2.5 pr-3 py-1 rounded-full transition-colors duration-200 shadow-sm hover:shadow-md hover:bg-gray-700"
>
<svg
class="w-4 h-4 flex-shrink-0 text-gray-200"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
<span id="github-stars-mobile">-</span>
</a>
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only" data-i18n="nav.openMainMenu"
>Open main menu</span
>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home"
>Home</a
>
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about"
>About</a
>
<a
href="/contact.html"
class="mobile-nav-link"
data-i18n="nav.contact"
>Contact</a
>
<a
href="/licensing.html"
class="mobile-nav-link"
data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="mobile-nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
</div>
</nav>
<!-- Donation Ribbon --> <!-- Donation Ribbon -->
<div <div
@@ -369,6 +209,21 @@
</svg> </svg>
</span> </span>
</a> </a>
<!-- DigitalOcean -->
<a
href="https://www.digitalocean.com/?refcode=d93c189ef6d0&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"
target="_blank"
rel="noopener noreferrer"
class="mt-8 opacity-50 hover:opacity-100 transition-opacity duration-300 hide-section"
>
<img
src="/images/badge.svg"
alt="DigitalOcean Referral Badge"
width="150"
height="32"
class="h-8"
/>
</a>
</div> </div>
</section> </section>
@@ -861,6 +716,28 @@
></div> ></div>
</label> </label>
</div> </div>
<!-- Advanced Settings Link -->
<a
href="wasm-settings.html"
class="flex items-center justify-between p-4 bg-gray-900 rounded-lg border border-gray-700 hover:bg-gray-800 hover:border-indigo-500/50 transition-all group"
>
<div class="flex-1">
<span
class="text-sm font-medium text-gray-200 group-hover:text-white"
>
Advanced Settings
</span>
<p class="text-xs text-gray-400 mt-1">
Configure external processing modules (PyMuPDF, Ghostscript,
CoherentPDF)
</p>
</div>
<i
data-lucide="chevron-right"
class="w-5 h-5 text-gray-500 group-hover:text-indigo-400 transition-colors"
></i>
</a>
</div> </div>
</div> </div>
@@ -1434,171 +1311,7 @@
</button> </button>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="public/images/favicon.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
<span data-i18n="footer.version">Version</span>
<span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">
Company
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/about.html"
class="hover:text-indigo-400"
data-i18n="footer.aboutUs"
>About Us</a
>
</li>
<li>
<a
href="/faq.html"
class="hover:text-indigo-400"
data-i18n="footer.faqLink"
>FAQ</a
>
</li>
<li>
<a
href="/contact.html"
class="hover:text-indigo-400"
data-i18n="footer.contactUs"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">
Legal
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/licensing.html"
class="hover:text-indigo-400"
data-i18n="nav.licensing"
>Licensing</a
>
</li>
<li>
<a
href="/terms.html"
class="hover:text-indigo-400"
data-i18n="footer.termsAndConditions"
>Terms and Conditions</a
>
</li>
<li>
<a
href="/privacy.html"
class="hover:text-indigo-400"
data-i18n="footer.privacyPolicy"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">
Follow Us
</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

View File

@@ -78,125 +78,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link" data-i18n="nav.home">Home</a>
<a href="/about.html" class="nav-link" data-i18n="nav.about"
>About</a
>
<a href="/contact.html" class="nav-link" data-i18n="nav.contact"
>Contact</a
>
<a href="/licensing.html" class="nav-link" data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link" data-i18n="nav.home"
>Home</a
>
<a href="/about.html" class="mobile-nav-link" data-i18n="nav.about"
>About</a
>
<a
href="/contact.html"
class="mobile-nav-link"
data-i18n="nav.contact"
>Contact</a
>
<a
href="/licensing.html"
class="mobile-nav-link"
data-i18n="nav.licensing"
>Licensing</a
>
<a
href="index.html#tools-header"
class="mobile-nav-link"
data-i18n="nav.allTools"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="min-h-screen container mx-auto p-4 md:p-8"> <div id="app" class="min-h-screen container mx-auto p-4 md:p-8">
<section id="licensing-hero" class="text-center py-16 md:py-24"> <section id="licensing-hero" class="text-center py-16 md:py-24">
@@ -212,6 +94,131 @@
</p> </p>
</section> </section>
<!-- Prominent Pricing Section -->
<section id="pricing-hero" class="py-12 max-w-3xl mx-auto">
<div class="bg-gray-900 rounded-2xl p-8 md:p-10 border border-gray-700">
<!-- Badge -->
<span
class="inline-block bg-indigo-500 text-white text-xs font-bold px-4 py-1.5 rounded-full mb-6"
>
LIFETIME LICENSE
</span>
<!-- Title -->
<h2 class="text-3xl md:text-4xl font-bold text-white mb-6">
Commercial License
</h2>
<!-- Divider -->
<div class="border-t border-gray-700 mb-8"></div>
<!-- Features Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4 mb-8">
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Proprietary Use</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Unlimited Devices</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Enterprise Support</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Unlimited Users</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Early Access Features</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Lifetime Updates</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">No AGPL Obligations</span>
</div>
<div class="flex items-center gap-3">
<div
class="w-6 h-6 rounded-full bg-indigo-500/20 flex items-center justify-center flex-shrink-0"
>
<i data-lucide="check" class="w-4 h-4 text-indigo-400"></i>
</div>
<span class="text-gray-200">Flexible Terms</span>
</div>
</div>
<!-- Divider -->
<div class="border-t border-gray-700 mb-6"></div>
<!-- Price and CTA -->
<div
class="flex flex-col sm:flex-row items-center justify-between gap-4"
>
<div class="flex items-baseline gap-2">
<span class="text-gray-500 text-xl line-through">$99</span>
<span class="text-white text-4xl font-bold">$49</span>
<span class="text-gray-400 text-lg"> one-time</span>
</div>
<a
href="https://buy.polar.sh/polar_cl_ThDfffbl733x7oAodcIryCzhlO57ZtcWPq6HJ1qMChd"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 px-8 py-3 rounded-full bg-indigo-500 text-white font-semibold hover:bg-indigo-400 transition-all duration-200 group"
>
Get License Now
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
class="transition-transform group-hover:translate-x-1"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
</div>
</div>
</section>
<div class="section-divider"></div> <div class="section-divider"></div>
<section id="licensing-options" class="py-16 max-w-6xl mx-auto"> <section id="licensing-options" class="py-16 max-w-6xl mx-auto">
@@ -360,75 +367,34 @@
</div> </div>
</div> </div>
</div> </div>
<div <!-- CTA Button -->
class="mt-8 bg-gradient-to-r from-indigo-900/40 to-purple-900/40 rounded-lg p-6 border border-indigo-500/50"
>
<div class="text-center">
<div
class="inline-block bg-red-500 text-white text-xs font-bold px-3 py-1 rounded-full mb-3 animate-pulse"
>
LIMITED TIME OFFER
</div>
<p class="text-white text-2xl font-bold mb-2">
Lifetime License
</p>
<div class="flex items-center justify-center gap-3 mb-3">
<span class="text-gray-400 text-2xl line-through">$99</span>
<span class="text-indigo-300 text-4xl font-bold">$49</span>
<span class="text-lg font-normal text-gray-400"
>one-time</span
>
</div>
<p class="text-gray-300 text-sm mb-4">
Includes all feature updates forever
</p>
<!-- Unlimited Usage Info -->
<div
class="bg-indigo-900/30 rounded-lg p-4 mb-4 border border-indigo-500/30"
>
<div class="flex items-start gap-3 text-left">
<i
data-lucide="infinity"
class="w-6 h-6 text-indigo-400 flex-shrink-0 mt-0.5"
></i>
<div>
<h4 class="text-white font-semibold text-sm mb-2">
Unlimited Usage
</h4>
<p class="text-gray-300 text-xs leading-relaxed">
The BentoPDF commercial license permits use on an
<strong class="text-white"
>unrestricted, unlimited number of devices, servers,
and user machines</strong
>
within your organization. There are
<strong class="text-white"
>no per-user, per-machine, or per-seat
limitations</strong
>. Once you purchase the license, all your internal
users may use BentoPDF through your browser-based system
without requiring additional licenses.
</p>
</div>
</div>
</div>
<a <a
href="https://ko-fi.com/s/f32ca4cb75" href="https://buy.polar.sh/polar_cl_ThDfffbl733x7oAodcIryCzhlO57ZtcWPq6HJ1qMChd"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="inline-block px-8 py-3 rounded-lg bg-indigo-600 text-white font-semibold hover:bg-indigo-700 transition-colors shadow-lg hover:shadow-indigo-500/50 transform hover:scale-105 transition-all duration-200" class="mt-8 inline-flex items-center gap-2 px-8 py-3 rounded-full bg-indigo-500 text-white font-semibold hover:bg-indigo-400 transition-all duration-200 group"
> >
Purchase Lifetime License Get Commercial License
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
class="transition-transform group-hover:translate-x-1"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a> </a>
</div> <p class="mt-4 text-sm text-gray-400 italic">
</div>
<p class="mt-4 text-sm text-gray-400 italic text-center">
💡 Custom requests and development are available for separate 💡 Custom requests and development are available for separate
charges. charges.
<a href="/contact.html" class="text-indigo-400 hover:underline" <a href="contact.html" class="text-indigo-400 hover:underline"
>Contact us</a >Contact us</a
> >
for details. for details.
@@ -586,10 +552,33 @@
</div> </div>
<div class="mt-8 text-center"> <div class="mt-8 text-center">
<a
href="https://buy.polar.sh/polar_cl_ThDfffbl733x7oAodcIryCzhlO57ZtcWPq6HJ1qMChd"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 px-8 py-3 rounded-full bg-indigo-500 text-white font-semibold hover:bg-indigo-400 transition-all duration-200 group mb-4"
>
Get Commercial License - $49
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
class="transition-transform group-hover:translate-x-1"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
<p class="text-gray-400 text-sm"> <p class="text-gray-400 text-sm">
Still not sure? Still not sure?
<a <a
href="/contact.html" href="contact.html"
class="text-indigo-400 hover:underline font-semibold" class="text-indigo-400 hover:underline font-semibold"
>Contact us</a >Contact us</a
> >
@@ -909,13 +898,15 @@
BentoPDF is available under a lifetime, one-time purchase BentoPDF is available under a lifetime, one-time purchase
commercial license. You can purchase it directly here: commercial license. You can purchase it directly here:
<a <a
href="https://ko-fi.com/s/f32ca4cb75" href="https://buy.polar.sh/polar_cl_ThDfffbl733x7oAodcIryCzhlO57ZtcWPq6HJ1qMChd"
class="text-indigo-400 hover:underline" target="_blank"
rel="noopener noreferrer"
class="text-indigo-400 hover:underline font-semibold"
>Buy Commercial License</a >Buy Commercial License</a
>. <br /><br /> >. <br /><br />
If you have specific requirements or want a custom licensing If you have specific requirements or want a custom licensing
arrangement, feel free to arrangement, feel free to
<a href="/contact.html" class="text-indigo-400 hover:underline" <a href="contact.html" class="text-indigo-400 hover:underline"
>contact us</a >contact us</a
> >
with details about your use case, company size, and deployment with details about your use case, company size, and deployment
@@ -1002,53 +993,22 @@
<h3 <h3
class="text-xl font-semibold text-white mb-4 flex items-center gap-3" class="text-xl font-semibold text-white mb-4 flex items-center gap-3"
> >
<i <i data-lucide="file-text" class="w-6 h-6 text-indigo-400"></i>
data-lucide="alert-triangle" Invoicing
class="w-6 h-6 text-yellow-400"
></i>
Important Notice on Third-Party Components
</h3> </h3>
<p class="text-gray-300 mb-4">
This software includes components licensed under the
<strong class="text-white">GNU AGPL v3</strong>, including:
</p>
<ul class="flex flex-wrap gap-2 mb-4">
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
CPDF
</li>
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
PyMuPDF
</li>
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
Ghostscript
</li>
</ul>
<ul class="space-y-3 text-gray-400"> <ul class="space-y-3 text-gray-400">
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<i <i
data-lucide="alert-circle" data-lucide="check-circle"
class="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"
></i> ></i>
<span <span
>This commercial license >We use <strong class="text-white">Polar</strong> for
<strong class="text-white">does not</strong> grant rights to payments, which
use AGPL components in a closed-source manner.</span <strong class="text-white"
>automatically sends invoices</strong
> >
</li> via email after purchase.</span
<li class="flex items-start gap-3">
<i
data-lucide="alert-circle"
class="w-5 h-5 text-yellow-400 flex-shrink-0 mt-0.5"
></i>
<span
>Users must comply with the AGPL v3 terms for these
components.</span
> >
</li> </li>
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
@@ -1057,8 +1017,23 @@
class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"
></i> ></i>
<span <span
>Source code for all AGPL components is included in the >Polar handles
distribution.</span <strong class="text-white">VAT invoices</strong> for
businesses in applicable regions.</span
>
</li>
<li class="flex items-start gap-3">
<i
data-lucide="info"
class="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"
></i>
<span
>For VAT invoices, select
<strong class="text-white"
>"I'm purchasing as a business"</strong
>
during checkout and enter your billing address and Tax/VAT
number.</span
> >
</li> </li>
</ul> </ul>
@@ -1068,36 +1043,76 @@
<h3 <h3
class="text-xl font-semibold text-white mb-4 flex items-center gap-3" class="text-xl font-semibold text-white mb-4 flex items-center gap-3"
> >
<i data-lucide="file-text" class="w-6 h-6 text-indigo-400"></i>
Invoicing
</h3>
<ul class="space-y-3 text-gray-400">
<li class="flex items-start gap-3">
<i <i
data-lucide="info" data-lucide="alert-triangle"
class="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" class="w-6 h-6 text-indigo-400"
></i> ></i>
<span>Ko-fi does not automatically issue invoices.</span> AGPL Components - Not Bundled
</h3>
<p class="text-gray-300 mb-4">
BentoPDF
<strong class="text-white">does not bundle</strong> AGPL-licensed
processing libraries. The following components must be configured
separately via
<strong class="text-white">Advanced Settings</strong> if you wish
to use their features:
</p>
<ul class="flex flex-wrap gap-2 mb-4">
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
PyMuPDF (AGPL-3.0)
</li>
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
Ghostscript (AGPL-3.0)
</li>
<li
class="px-3 py-1 bg-gray-700 rounded-full text-sm text-gray-300"
>
CoherentPDF / CPDF (AGPL-3.0)
</li>
</ul>
<p class="text-gray-300 mb-4">
<strong class="text-white"
>To enable features powered by these libraries:</strong
>
</p>
<ul class="space-y-2 text-gray-400 mb-4">
<li class="flex items-start gap-3">
<span class="text-indigo-400 font-bold">1.</span>
<span
>Navigate to
<strong class="text-white">Advanced Settings</strong> in
BentoPDF</span
>
</li> </li>
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<i <span class="text-indigo-400 font-bold">2.</span>
data-lucide="check-circle" <span>Configure the URL for each WASM module you need</span>
class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" </li>
></i> <li class="flex items-start gap-3">
<span class="text-indigo-400 font-bold">3.</span>
<span <span
>An official invoice will be provided immediately upon >You can host your own files, use a
request.</span <a
href="https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md"
class="text-indigo-400 hover:underline"
>WASM proxy</a
>, or use any compatible CDN</span
> >
</li> </li>
</ul> </ul>
<p class="mt-4 text-gray-300"> <p class="text-gray-400 text-sm mt-4">
<strong class="text-white">Contact us:</strong> <i
<a data-lucide="alert-circle"
href="mailto:contact@bentopdf.com" class="w-4 h-4 inline-block mr-1 text-indigo-400"
class="text-indigo-400 hover:underline" ></i>
>contact@bentopdf.com</a The commercial license covers
> <strong class="text-white">BentoPDF's own code only</strong>. It
with your purchase details. does not bypass the AGPL licensing of these components. Users must
comply with the AGPL v3 terms for these components.
</p> </p>
</div> </div>
</div> </div>
@@ -1113,7 +1128,7 @@
We're here to help. Reach out to discuss your licensing needs. We're here to help. Reach out to discuss your licensing needs.
</p> </p>
<a <a
href="/contact.html" href="contact.html"
class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1" class="inline-block px-8 py-3 rounded-full bg-gradient-to-b from-indigo-500 to-indigo-600 text-white font-semibold focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-400 hover:shadow-xl hover:shadow-indigo-500/30 transition-all duration-200 transform hover:-translate-y-1"
> >
Contact Us Contact Us
@@ -1121,171 +1136,7 @@
</section> </section>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm" data-i18n="footer.copyright">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
<span data-i18n="footer.version">Version</span>
<span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.company">
Company
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/about.html"
class="hover:text-indigo-400"
data-i18n="footer.aboutUs"
>About Us</a
>
</li>
<li>
<a
href="/faq.html"
class="hover:text-indigo-400"
data-i18n="footer.faqLink"
>FAQ</a
>
</li>
<li>
<a
href="/contact.html"
class="hover:text-indigo-400"
data-i18n="footer.contactUs"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.legal">
Legal
</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a
href="/licensing.html"
class="hover:text-indigo-400"
data-i18n="nav.licensing"
>Licensing</a
>
</li>
<li>
<a
href="/terms.html"
class="hover:text-indigo-400"
data-i18n="footer.termsAndConditions"
>Terms and Conditions</a
>
</li>
<li>
<a
href="/privacy.html"
class="hover:text-indigo-400"
data-i18n="footer.privacyPolicy"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4" data-i18n="footer.followUs">
Follow Us
</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>

View File

@@ -26,7 +26,6 @@ http {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
rewrite ^/(en|de|zh|vi|it|tr|id)/(.*)$ /$2 last;
location ~* \.html$ { location ~* \.html$ {
expires 1h; expires 1h;
@@ -84,6 +83,22 @@ http {
add_header Cache-Control "public, immutable"; add_header Cache-Control "public, immutable";
} }
location ~ ^/(en|de|es|zh|zh-TW|vi|it|id|tr|fr|pt|be)(/.*)?$ {
try_files $uri $uri/ $uri.html /$1/index.html /index.html;
expires 5m;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~ ^/(.+?)/(en|de|es|zh|zh-TW|vi|it|id|tr|fr|pt|be)(/.*)?$ {
try_files $uri $uri/ $uri.html /$1/$2/index.html /$1/index.html /index.html;
expires 5m;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location / { location / {
try_files $uri $uri/ $uri.html /index.html; try_files $uri $uri/ $uri.html /index.html;
expires 5m; expires 5m;

629
package-lock.json generated
View File

@@ -1,16 +1,14 @@
{ {
"name": "bento-pdf", "name": "bento-pdf",
"version": "1.15.4", "version": "1.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bento-pdf", "name": "bento-pdf",
"version": "1.15.4", "version": "1.16.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@bentopdf/gs-wasm": "^0.1.0",
"@bentopdf/pymupdf-wasm": "^0.1.11",
"@fontsource/cedarville-cursive": "^5.2.7", "@fontsource/cedarville-cursive": "^5.2.7",
"@fontsource/dancing-script": "^5.2.8", "@fontsource/dancing-script": "^5.2.8",
"@fontsource/dm-sans": "^5.2.8", "@fontsource/dm-sans": "^5.2.8",
@@ -37,7 +35,7 @@
"i18next": "^25.7.2", "i18next": "^25.7.2",
"i18next-browser-languagedetector": "^8.2.0", "i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"jspdf": "^3.0.3", "jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.2", "jspdf-autotable": "^5.0.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lucide": "^0.546.0", "lucide": "^0.546.0",
@@ -78,6 +76,7 @@
"@types/pdfkit": "^0.17.3", "@types/pdfkit": "^0.17.3",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/utif": "^3.0.6", "@types/utif": "^3.0.6",
"@vitejs/plugin-basic-ssl": "^2.1.4",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.39.2", "eslint": "^9.39.2",
@@ -91,6 +90,7 @@
"typescript-eslint": "^8.51.0", "typescript-eslint": "^8.51.0",
"vite": "^7.1.11", "vite": "^7.1.11",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-handlebars": "^2.0.0",
"vite-plugin-node-polyfills": "^0.24.0", "vite-plugin-node-polyfills": "^0.24.0",
"vitepress": "^1.6.4", "vitepress": "^1.6.4",
"vitest": "^3.2.4", "vitest": "^3.2.4",
@@ -504,24 +504,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@bentopdf/gs-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@bentopdf/gs-wasm/-/gs-wasm-0.1.0.tgz",
"integrity": "sha512-C71zxZW4R7Oa6fdya5leTh2VOZOxqH8IQlveh13OeuwZ2ulrovSi9629xTzAiIeeVKvDZma1Klxy4MuK65xe9w==",
"license": "AGPL-3.0",
"dependencies": {
"@types/emscripten": "^1.39.10"
}
},
"node_modules/@bentopdf/pymupdf-wasm": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/@bentopdf/pymupdf-wasm/-/pymupdf-wasm-0.1.11.tgz",
"integrity": "sha512-sbDFmvm2KzT3oCmqNqMx7w6TMsKpLXeooVK8EVRjyQIV4hU5Ioq0JxWMr8SX7MESu8Caz1feeELd6zt5K966SA==",
"license": "AGPL-3.0",
"peerDependencies": {
"@bentopdf/gs-wasm": "*"
}
},
"node_modules/@braintree/sanitize-url": { "node_modules/@braintree/sanitize-url": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz",
@@ -3619,12 +3601,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -4048,6 +4024,19 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/@vitejs/plugin-basic-ssl": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.4.tgz",
"integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"peerDependencies": {
"vite": "^6.0.0 || ^7.0.0"
}
},
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
@@ -7351,6 +7340,37 @@
"integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"bin": {
"handlebars": "bin/handlebars"
},
"engines": {
"node": ">=0.4.7"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -8184,9 +8204,9 @@
} }
}, },
"node_modules/jspdf": { "node_modules/jspdf": {
"version": "3.0.4", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz", "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.0.0.tgz",
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==", "integrity": "sha512-w12U97Z6edKd2tXDn3LzTLg7C7QLJlx0BPfM3ecjK2BckUl9/81vZ+r5gK4/3KQdhAcEZhENUxRhtgYBj75MqQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
@@ -8790,9 +8810,9 @@
} }
}, },
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash-es": { "node_modules/lodash-es": {
@@ -9327,6 +9347,16 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": { "node_modules/minipass": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -9417,6 +9447,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -11830,6 +11867,20 @@
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -12169,6 +12220,507 @@
"vite": ">=2.0.0" "vite": ">=2.0.0"
} }
}, },
"node_modules/vite-plugin-handlebars": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vite-plugin-handlebars/-/vite-plugin-handlebars-2.0.0.tgz",
"integrity": "sha512-+J3It0nyhPzx4nT1I+fnWH+jShTEXzm6X0Tgsggdm9IYFD7/eJ6a3ROI13HTe0CVoyaxm/fPxH5HDAKyfz7T0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"handlebars": "^4.7.6",
"vite": "^5.0.0"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite-plugin-handlebars/node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/vite-plugin-handlebars/node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vite-plugin-node-polyfills": { "node_modules/vite-plugin-node-polyfills": {
"version": "0.24.0", "version": "0.24.0",
"resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz", "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz",
@@ -13066,6 +13618,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",

View File

@@ -1,12 +1,12 @@
{ {
"name": "bento-pdf", "name": "bento-pdf",
"private": true, "private": true,
"version": "1.15.4", "version": "1.16.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build && NODE_OPTIONS='--max-old-space-size=3072' node scripts/generate-i18n-pages.mjs && node scripts/generate-sitemap.mjs",
"build:with-docs": "npm run build && npm run docs:build && node scripts/include-docs-in-dist.js", "build:with-docs": "npm run build && npm run docs:build && node scripts/include-docs-in-dist.js",
"build:gzip": "COMPRESSION_MODE=g npm run build", "build:gzip": "COMPRESSION_MODE=g npm run build",
"build:brotli": "COMPRESSION_MODE=b npm run build", "build:brotli": "COMPRESSION_MODE=b npm run build",
@@ -44,6 +44,7 @@
"@types/pdfkit": "^0.17.3", "@types/pdfkit": "^0.17.3",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/utif": "^3.0.6", "@types/utif": "^3.0.6",
"@vitejs/plugin-basic-ssl": "^2.1.4",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.39.2", "eslint": "^9.39.2",
@@ -57,14 +58,13 @@
"typescript-eslint": "^8.51.0", "typescript-eslint": "^8.51.0",
"vite": "^7.1.11", "vite": "^7.1.11",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-handlebars": "^2.0.0",
"vite-plugin-node-polyfills": "^0.24.0", "vite-plugin-node-polyfills": "^0.24.0",
"vitepress": "^1.6.4", "vitepress": "^1.6.4",
"vitest": "^3.2.4", "vitest": "^3.2.4",
"vue": "^3.5.26" "vue": "^3.5.26"
}, },
"dependencies": { "dependencies": {
"@bentopdf/gs-wasm": "^0.1.0",
"@bentopdf/pymupdf-wasm": "^0.1.11",
"@fontsource/cedarville-cursive": "^5.2.7", "@fontsource/cedarville-cursive": "^5.2.7",
"@fontsource/dancing-script": "^5.2.8", "@fontsource/dancing-script": "^5.2.8",
"@fontsource/dm-sans": "^5.2.8", "@fontsource/dm-sans": "^5.2.8",
@@ -91,7 +91,7 @@
"i18next": "^25.7.2", "i18next": "^25.7.2",
"i18next-browser-languagedetector": "^8.2.0", "i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"jspdf": "^3.0.3", "jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.2", "jspdf-autotable": "^5.0.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lucide": "^0.546.0", "lucide": "^0.546.0",

View File

@@ -84,64 +84,7 @@
<body class="antialiased bg-gray-900"> <body class="antialiased bg-gray-900">
<!-- Navigation --> <!-- Navigation -->
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="/">BentoPDF</a>
</span>
</div>
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="/" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/" class="nav-link">All Tools</a>
</div>
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700"
>
<svg
id="menu-icon"
class="block h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</div>
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/" class="mobile-nav-link">All Tools</a>
</div>
</div>
</nav>
<!-- Hero Section --> <!-- Hero Section -->
<section class="container mx-auto px-4 py-12 md:py-16"> <section class="container mx-auto px-4 py-12 md:py-16">
@@ -539,116 +482,7 @@
</section> </section>
<!-- Footer --> <!-- Footer -->
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
© 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li><a href="/faq.html" class="hover:text-indigo-400">FAQ</a></li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="instagram"></i
></a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="linkedin"></i
></a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="/src/js/utils/lucide-init.ts"></script> <script type="module" src="/src/js/utils/lucide-init.ts"></script>
<script type="module" src="/src/version.ts"></script> <script type="module" src="/src/version.ts"></script>

View File

@@ -84,64 +84,7 @@
<body class="antialiased bg-gray-900"> <body class="antialiased bg-gray-900">
<!-- Navigation --> <!-- Navigation -->
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="/">BentoPDF</a>
</span>
</div>
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="/" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/" class="nav-link">All Tools</a>
</div>
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700"
>
<svg
id="menu-icon"
class="block h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</div>
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/" class="mobile-nav-link">All Tools</a>
</div>
</div>
</nav>
<!-- Hero Section --> <!-- Hero Section -->
<section class="container mx-auto px-4 py-12 md:py-16"> <section class="container mx-auto px-4 py-12 md:py-16">
@@ -408,116 +351,7 @@
</section> </section>
<!-- Footer --> <!-- Footer -->
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
© 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li><a href="/faq.html" class="hover:text-indigo-400">FAQ</a></li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="instagram"></i
></a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="linkedin"></i
></a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="/src/js/utils/lucide-init.ts"></script> <script type="module" src="/src/js/utils/lucide-init.ts"></script>
<script type="module" src="/src/version.ts"></script> <script type="module" src="/src/version.ts"></script>

View File

@@ -95,64 +95,7 @@
<body class="antialiased bg-gray-900"> <body class="antialiased bg-gray-900">
<!-- Navigation --> <!-- Navigation -->
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="/">BentoPDF</a>
</span>
</div>
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="/" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/" class="nav-link">All Tools</a>
</div>
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700"
>
<svg
id="menu-icon"
class="block h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</div>
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/" class="mobile-nav-link">All Tools</a>
</div>
</div>
</nav>
<!-- Hero Section --> <!-- Hero Section -->
<section class="container mx-auto px-4 py-12 md:py-16"> <section class="container mx-auto px-4 py-12 md:py-16">
@@ -366,116 +309,7 @@
</section> </section>
<!-- Footer --> <!-- Footer -->
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
© 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li><a href="/faq.html" class="hover:text-indigo-400">FAQ</a></li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="instagram"></i
></a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="linkedin"></i
></a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="/src/js/utils/lucide-init.ts"></script> <script type="module" src="/src/js/utils/lucide-init.ts"></script>
<script type="module" src="/src/version.ts"></script> <script type="module" src="/src/version.ts"></script>

View File

@@ -84,64 +84,7 @@
<body class="antialiased bg-gray-900"> <body class="antialiased bg-gray-900">
<!-- Navigation --> <!-- Navigation -->
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="/">BentoPDF</a>
</span>
</div>
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="/" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/" class="nav-link">All Tools</a>
</div>
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700"
>
<svg
id="menu-icon"
class="block h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</div>
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/" class="mobile-nav-link">All Tools</a>
</div>
</div>
</nav>
<!-- Hero Section --> <!-- Hero Section -->
<section class="container mx-auto px-4 py-12 md:py-16"> <section class="container mx-auto px-4 py-12 md:py-16">
@@ -343,116 +286,7 @@
</section> </section>
<!-- Footer --> <!-- Footer -->
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
© 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li><a href="/faq.html" class="hover:text-indigo-400">FAQ</a></li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="instagram"></i
></a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
><i data-lucide="linkedin"></i
></a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="/src/js/utils/lucide-init.ts"></script> <script type="module" src="/src/js/utils/lucide-init.ts"></script>
<script type="module" src="/src/version.ts"></script> <script type="module" src="/src/version.ts"></script>

View File

@@ -77,97 +77,7 @@
</head> </head>
<body class="antialiased bg-gray-900 text-gray-300"> <body class="antialiased bg-gray-900 text-gray-300">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> {{> navbar }}
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="nav-link">Home</a>
<a href="/about.html" class="nav-link">About</a>
<a href="/contact.html" class="nav-link">Contact</a>
<a href="/licensing.html" class="nav-link">Licensing</a>
<a href="index.html#tools-header" class="nav-link">All Tools</a>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link">Home</a>
<a href="/about.html" class="mobile-nav-link">About</a>
<a href="/contact.html" class="mobile-nav-link">Contact</a>
<a href="/licensing.html" class="mobile-nav-link">Licensing</a>
<a href="index.html#tools-header" class="mobile-nav-link"
>All Tools</a
>
</div>
</div>
</nav>
<div id="app" class="container mx-auto p-4 md:p-8"> <div id="app" class="container mx-auto p-4 md:p-8">
<section class="max-w-4xl mx-auto py-12"> <section class="max-w-4xl mx-auto py-12">
@@ -303,142 +213,7 @@
</section> </section>
</div> </div>
<footer class="mt-16 border-t-2 border-gray-700 py-8"> {{> footer }}
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="/images/favicon-no-bg.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
&copy; 2026 BentoPDF. All rights reserved.
</p>
<p class="text-gray-500 text-xs mt-2">
Version <span id="app-version"></span>
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li>
<a href="/faq.html" class="hover:text-indigo-400">FAQ</a>
</li>
<li>
<a href="/contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="/licensing.html" class="hover:text-indigo-400"
>Licensing</a
>
</li>
<li>
<a href="/terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="/privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/Bgq3Ay3f2w"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script> <script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/version.ts"></script> <script type="module" src="src/version.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script> <script type="module" src="src/js/mobileMenu.ts"></script>

View File

@@ -1,113 +0,0 @@
async function Module(moduleArg={}){var moduleRtn;var f=moduleArg,aa="object"==typeof window,ba="undefined"!=typeof WorkerGlobalScope,k="object"==typeof process&&process.versions?.node&&"renderer"!=process.type,ca=!aa&&!k&&!ba;if(k){const {createRequire:a}=await import("module");var require=a(import.meta.url)}var da="./this.program",ea=(a,b)=>{throw b;},ia=import.meta.url,ja="",ka,la;
if(k){if("object"!=typeof process||!process.versions?.node||"renderer"==process.type)throw Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");var ma=process.versions.node,na=ma.split(".").slice(0,3);na=1E4*na[0]+100*na[1]+1*na[2].split("-")[0];if(16E4>na)throw Error("This emscripten-generated code requires node v16.0.0 (detected v"+ma+")");var fs=require("fs");
ia.startsWith("file:")&&(ja=require("path").dirname(require("url").fileURLToPath(ia))+"/");la=a=>{a=oa(a)?new URL(a):a;a=fs.readFileSync(a);m(Buffer.isBuffer(a));return a};ka=async a=>{a=oa(a)?new URL(a):a;a=fs.readFileSync(a,void 0);m(Buffer.isBuffer(a));return a};1<process.argv.length&&(da=process.argv[1].replace(/\\/g,"/"));process.argv.slice(2);ea=(a,b)=>{process.exitCode=a;throw b;}}else if(ca){if("object"==typeof process&&process.versions?.node&&"renderer"!=process.type||"object"==typeof window||
"undefined"!=typeof WorkerGlobalScope)throw Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");}else if(aa||ba){try{ja=(new URL(".",ia)).href}catch{}if("object"!=typeof window&&"undefined"==typeof WorkerGlobalScope)throw Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");
ba&&(la=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)});ka=async a=>{if(oa(a))return new Promise((c,d)=>{var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";e.onload=()=>{200==e.status||0==e.status&&e.response?c(e.response):d(e.status)};e.onerror=d;e.send(null)});var b=await fetch(a,{credentials:"same-origin"});if(b.ok)return b.arrayBuffer();throw Error(b.status+" : "+b.url);}}else throw Error("environment detection error");
var p=console.log.bind(console),t=console.error.bind(console);m(!ca,"shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.");var pa;"object"!=typeof WebAssembly&&t("no native wasm support detected");var qa=!1,ra;function m(a,b){a||v("Assertion failed"+(b?": "+b:""))}var oa=a=>a.startsWith("file://");function sa(){var a=ta();m(0==(a&3));0==a&&(a+=4);x[a>>2]=34821223;x[a+4>>2]=2310721022;x[0]=1668509029}
function ua(){if(!qa){var a=ta();0==a&&(a+=4);var b=x[a>>2],c=x[a+4>>2];34821223==b&&2310721022==c||v(`Stack overflow! Stack cookie has been overwritten at ${va(a)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${va(c)} ${va(b)}`);1668509029!=x[0]&&v("Runtime error: The application has corrupted its heap memory area (address zero)!")}}var wa=new Int16Array(1),xa=new Int8Array(wa.buffer);wa[0]=25459;
if(115!==xa[0]||99!==xa[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)";function ya(a){Object.getOwnPropertyDescriptor(f,a)||Object.defineProperty(f,a,{configurable:!0,set(){v(`Attempt to set \`Module.${a}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`)}})}
function y(a){return()=>m(!1,`call to '${a}' via reference taken before Wasm module initialization`)}function Aa(a){return"FS_createPath"===a||"FS_createDataFile"===a||"FS_createPreloadedFile"===a||"FS_preloadFile"===a||"FS_unlink"===a||"addRunDependency"===a||"FS_createLazyFile"===a||"FS_createDevice"===a||"removeRunDependency"===a}function Ba(a,b){"undefined"==typeof globalThis||Object.getOwnPropertyDescriptor(globalThis,a)||Object.defineProperty(globalThis,a,{configurable:!0,get(){b()}})}
function Ca(a,b){Ba(a,()=>{z(`\`${a}\` is not longer defined by emscripten. ${b}`)})}Ca("buffer","Please use HEAP8.buffer or wasmMemory.buffer");Ca("asm","Please use wasmExports instead");function Da(a){Object.getOwnPropertyDescriptor(f,a)||Object.defineProperty(f,a,{configurable:!0,get(){var b=`'${a}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`;Aa(a)&&(b+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you");v(b)}})}
var Ea,Fa,Ga,A,Ha,Ia,B,x,C,Ja=!1;function Ka(){var a=Ga.buffer;A=new Int8Array(a);Ia=new Int16Array(a);Ha=new Uint8Array(a);new Uint16Array(a);B=new Int32Array(a);x=new Uint32Array(a);new Float32Array(a);new Float64Array(a);C=new BigInt64Array(a);new BigUint64Array(a)}m("undefined"!=typeof Int32Array&&"undefined"!==typeof Float64Array&&void 0!=Int32Array.prototype.subarray&&void 0!=Int32Array.prototype.set,"JS engine does not provide full typed array support");var E=0,La=null,Ma={},I=null;
function Na(a){E++;f.monitorRunDependencies?.(E);m(a,"addRunDependency requires an ID");m(!Ma[a]);Ma[a]=1;null===I&&"undefined"!=typeof setInterval&&(I=setInterval(()=>{if(qa)clearInterval(I),I=null;else{var b=!1,c;for(c in Ma)b||(b=!0,t("still waiting on run dependencies:")),t(`dependency: ${c}`);b&&t("(end of list)")}},1E4),I.unref?.())}
function Oa(a){E--;f.monitorRunDependencies?.(E);m(a,"removeRunDependency requires an ID");m(Ma[a]);delete Ma[a];0==E&&(null!==I&&(clearInterval(I),I=null),La&&(a=La,La=null,a()))}function v(a){f.onAbort?.(a);a="Aborted("+a+")";t(a);qa=!0;a=new WebAssembly.RuntimeError(a);Fa?.(a);throw a;}
function K(a,b){return(...c)=>{m(Ja,`native function \`${a}\` called before runtime initialization`);var d=L[a];m(d,`exported native function \`${a}\` not found`);m(c.length<=b,`native function \`${a}\` called with ${c.length} args but expects ${b}`);return d(...c)}}var Pa;async function Qa(a){if(!pa)try{var b=await ka(a);return new Uint8Array(b)}catch{}if(a==Pa&&pa)a=new Uint8Array(pa);else if(la)a=la(a);else throw"both async and sync fetching of the wasm failed";return a}
async function Ra(a,b){try{var c=await Qa(a);return await WebAssembly.instantiate(c,b)}catch(d){t(`failed to asynchronously prepare wasm: ${d}`),oa(Pa)&&t(`warning: Loading from a file URI (${Pa}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`),v(d)}}
async function Sa(a){var b=Pa;if(!pa&&!oa(b)&&!k)try{var c=fetch(b,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(c,a)}catch(d){t(`wasm streaming compile failed: ${d}`),t("falling back to ArrayBuffer instantiation")}return Ra(b,a)}class Ta{name="ExitStatus";constructor(a){this.message=`Program terminated with exit(${a})`;this.status=a}}
var Ua=a=>{for(;0<a.length;)a.shift()(f)},Va=[],Wa=[],Xa=()=>{var a=f.preRun.shift();Wa.push(a)},Ya=!0,va=a=>{m("number"===typeof a);return"0x"+(a>>>0).toString(16).padStart(8,"0")},z=a=>{z.$||(z.$={});z.$[a]||(z.$[a]=1,k&&(a="warning: "+a),t(a))},Za="undefined"!=typeof TextDecoder?new TextDecoder:void 0,$a=(a,b=0)=>{var c=b;for(var d=c+void 0;a[c]&&!(c>=d);)++c;if(16<c-b&&a.buffer&&Za)return Za.decode(a.subarray(b,c));for(d="";b<c;){var e=a[b++];if(e&128){var g=a[b++]&63;if(192==(e&224))d+=String.fromCharCode((e&
31)<<6|g);else{var h=a[b++]&63;224==(e&240)?e=(e&15)<<12|g<<6|h:(240!=(e&248)&&z("Invalid UTF-8 leading byte "+va(e)+" encountered when deserializing a UTF-8 string in wasm memory to a JS string!"),e=(e&7)<<18|g<<12|h<<6|a[b++]&63);65536>e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d},M=a=>{m("number"==typeof a,`UTF8ToString expects a number (got ${typeof a})`);return a?$a(Ha,a):""},ab=(a,b)=>{for(var c=0,d=a.length-
1;0<=d;d--){var e=a[d];"."===e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a},bb=a=>{var b="/"===a.charAt(0),c="/"===a.slice(-1);(a=ab(a.split("/").filter(d=>!!d),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a},cb=a=>{var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&=b.slice(0,-1);return a+b},N=a=>a&&a.match(/([^\/]+|\/)\/*$/)[1],db=(a,b)=>bb(a+"/"+
b),eb=()=>{if(k){var a=require("crypto");return b=>a.randomFillSync(b)}return b=>crypto.getRandomValues(b)},fb=a=>{(fb=eb())(a)},gb=(...a)=>{for(var b="",c=!1,d=a.length-1;-1<=d&&!c;d--){c=0<=d?a[d]:O.cwd();if("string"!=typeof c)throw new TypeError("Arguments to path.resolve must be strings");if(!c)return"";b=c+"/"+b;c="/"===c.charAt(0)}b=ab(b.split("/").filter(e=>!!e),!c).join("/");return(c?"/":"")+b||"."},hb=(a,b)=>{function c(h){for(var l=0;l<h.length&&""===h[l];l++);for(var r=h.length-1;0<=r&&
""===h[r];r--);return l>r?[]:h.slice(l,r-l+1)}a=gb(a).slice(1);b=gb(b).slice(1);a=c(a.split("/"));b=c(b.split("/"));for(var d=Math.min(a.length,b.length),e=d,g=0;g<d;g++)if(a[g]!==b[g]){e=g;break}d=[];for(g=e;g<a.length;g++)d.push("..");d=d.concat(b.slice(e));return d.join("/")},ib=[],jb=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=d?(b+=4,++c):b+=3}return b},kb=(a,b,c,d)=>{m("string"===typeof a,`stringToUTF8Array expects a string (got ${typeof a})`);
if(!(0<d))return 0;var e=c;d=c+d-1;for(var g=0;g<a.length;++g){var h=a.codePointAt(g);if(127>=h){if(c>=d)break;b[c++]=h}else if(2047>=h){if(c+1>=d)break;b[c++]=192|h>>6;b[c++]=128|h&63}else if(65535>=h){if(c+2>=d)break;b[c++]=224|h>>12;b[c++]=128|h>>6&63;b[c++]=128|h&63}else{if(c+3>=d)break;1114111<h&&z("Invalid Unicode code point "+va(h)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).");b[c++]=240|h>>18;b[c++]=
128|h>>12&63;b[c++]=128|h>>6&63;b[c++]=128|h&63;g++}}b[c]=0;return c-e},lb=a=>{var b=Array(jb(a)+1);a=kb(a,b,0,b.length);b.length=a;return b},mb=[];function nb(a,b){mb[a]={input:[],output:[],H:b};ob(a,pb)}
var pb={open(a){var b=mb[a.node.rdev];if(!b)throw new O.g(43);a.tty=b;a.seekable=!1},close(a){a.tty.H.fsync(a.tty)},fsync(a){a.tty.H.fsync(a.tty)},read(a,b,c,d){if(!a.tty||!a.tty.H.ha)throw new O.g(60);for(var e=0,g=0;g<d;g++){try{var h=a.tty.H.ha(a.tty)}catch(l){throw new O.g(29);}if(void 0===h&&0===e)throw new O.g(6);if(null===h||void 0===h)break;e++;b[c+g]=h}e&&(a.node.atime=Date.now());return e},write(a,b,c,d){if(!a.tty||!a.tty.H.Z)throw new O.g(60);try{for(var e=0;e<d;e++)a.tty.H.Z(a.tty,b[c+
e])}catch(g){throw new O.g(29);}d&&(a.node.mtime=a.node.ctime=Date.now());return e}},qb={ha(){a:{if(!ib.length){var a=null;if(k){var b=Buffer.alloc(256),c=0,d=process.stdin.fd;try{c=fs.readSync(d,b,0,256)}catch(e){if(e.toString().includes("EOF"))c=0;else throw e;}0<c&&(a=b.slice(0,c).toString("utf-8"))}else"undefined"!=typeof window&&"function"==typeof window.prompt&&(a=window.prompt("Input: "),null!==a&&(a+="\n"));if(!a){a=null;break a}ib=lb(a)}a=ib.shift()}return a},Z(a,b){null===b||10===b?(p($a(a.output)),
a.output=[]):0!=b&&a.output.push(b)},fsync(a){0<a.output?.length&&(p($a(a.output)),a.output=[])},xa(){return{Qa:25856,Sa:5,Pa:191,Ra:35387,Oa:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ya(){return 0},za(){return[24,80]}},rb={Z(a,b){null===b||10===b?(t($a(a.output)),a.output=[]):0!=b&&a.output.push(b)},fsync(a){0<a.output?.length&&(t($a(a.output)),a.output=[])}},sb=()=>{v("internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported")},
P={D:null,m(){return P.createNode(null,"/",16895,0)},createNode(a,b,c,d){if(24576===(c&61440)||O.isFIFO(c))throw new O.g(63);P.D||(P.D={dir:{node:{v:P.h.v,B:P.h.B,lookup:P.h.lookup,G:P.h.G,rename:P.h.rename,unlink:P.h.unlink,rmdir:P.h.rmdir,readdir:P.h.readdir,symlink:P.h.symlink},stream:{s:P.i.s}},file:{node:{v:P.h.v,B:P.h.B},stream:{s:P.i.s,read:P.i.read,write:P.i.write,M:P.i.M,P:P.i.P}},link:{node:{v:P.h.v,B:P.h.B,readlink:P.h.readlink},stream:{}},ba:{node:{v:P.h.v,B:P.h.B},stream:O.ta}});c=O.createNode(a,
b,c,d);Q(c.mode)?(c.h=P.D.dir.node,c.i=P.D.dir.stream,c.j={}):O.isFile(c.mode)?(c.h=P.D.file.node,c.i=P.D.file.stream,c.o=0,c.j=null):40960===(c.mode&61440)?(c.h=P.D.link.node,c.i=P.D.link.stream):8192===(c.mode&61440)&&(c.h=P.D.ba.node,c.i=P.D.ba.stream);c.atime=c.mtime=c.ctime=Date.now();a&&(a.j[b]=c,a.atime=a.mtime=a.ctime=c.atime);return c},fb(a){return a.j?a.j.subarray?a.j.subarray(0,a.o):new Uint8Array(a.j):new Uint8Array(0)},h:{v(a){var b={};b.dev=8192===(a.mode&61440)?a.id:1;b.ino=a.id;b.mode=
a.mode;b.nlink=1;b.uid=0;b.gid=0;b.rdev=a.rdev;Q(a.mode)?b.size=4096:O.isFile(a.mode)?b.size=a.o:40960===(a.mode&61440)?b.size=a.link.length:b.size=0;b.atime=new Date(a.atime);b.mtime=new Date(a.mtime);b.ctime=new Date(a.ctime);b.blksize=4096;b.blocks=Math.ceil(b.size/b.blksize);return b},B(a,b){for(var c of["mode","atime","mtime","ctime"])null!=b[c]&&(a[c]=b[c]);void 0!==b.size&&(b=b.size,a.o!=b&&(0==b?(a.j=null,a.o=0):(c=a.j,a.j=new Uint8Array(b),c&&a.j.set(c.subarray(0,Math.min(b,a.o))),a.o=b)))},
lookup(){throw new O.g(44);},G(a,b,c,d){return P.createNode(a,b,c,d)},rename(a,b,c){try{var d=R(b,c)}catch(g){}if(d){if(Q(a.mode))for(var e in d.j)throw new O.g(55);tb(d)}delete a.parent.j[a.name];b.j[c]=a;a.name=c;b.ctime=b.mtime=a.parent.ctime=a.parent.mtime=Date.now()},unlink(a,b){delete a.j[b];a.ctime=a.mtime=Date.now()},rmdir(a,b){var c=R(a,b),d;for(d in c.j)throw new O.g(55);delete a.j[b];a.ctime=a.mtime=Date.now()},readdir(a){return[".","..",...Object.keys(a.j)]},symlink(a,b,c){a=P.createNode(a,
b,41471,0);a.link=c;return a},readlink(a){if(40960!==(a.mode&61440))throw new O.g(28);return a.link}},i:{read(a,b,c,d,e){var g=a.node.j;if(e>=a.node.o)return 0;a=Math.min(a.node.o-e,d);m(0<=a);if(8<a&&g.subarray)b.set(g.subarray(e,e+a),c);else for(d=0;d<a;d++)b[c+d]=g[e+d];return a},write(a,b,c,d,e,g){m(!(b instanceof ArrayBuffer));b.buffer===A.buffer&&(g=!1);if(!d)return 0;a=a.node;a.mtime=a.ctime=Date.now();if(b.subarray&&(!a.j||a.j.subarray)){if(g)return m(0===e,"canOwn must imply no weird position inside the file"),
a.j=b.subarray(c,c+d),a.o=d;if(0===a.o&&0===e)return a.j=b.slice(c,c+d),a.o=d;if(e+d<=a.o)return a.j.set(b.subarray(c,c+d),e),d}g=e+d;var h=a.j?a.j.length:0;h>=g||(g=Math.max(g,h*(1048576>h?2:1.125)>>>0),0!=h&&(g=Math.max(g,256)),h=a.j,a.j=new Uint8Array(g),0<a.o&&a.j.set(h.subarray(0,a.o),0));if(a.j.subarray&&b.subarray)a.j.set(b.subarray(c,c+d),e);else for(g=0;g<d;g++)a.j[e+g]=b[c+g];a.o=Math.max(a.o,e+d);return d},s(a,b,c){1===c?b+=a.position:2===c&&O.isFile(a.node.mode)&&(b+=a.node.o);if(0>b)throw new O.g(28);
return b},M(a,b,c,d,e){if(!O.isFile(a.node.mode))throw new O.g(43);a=a.node.j;if(e&2||!a||a.buffer!==A.buffer){d=!0;e=sb();if(!e)throw new O.g(48);if(a){if(0<c||c+b<a.length)a.subarray?a=a.subarray(c,c+b):a=Array.prototype.slice.call(a,c,c+b);A.set(a,e)}}else d=!1,e=a.byteOffset;return{Ia:e,sa:d}},P(a,b,c,d){P.i.write(a,b,0,d,c,!1);return 0}}},ub=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},vb={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,
EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,
ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,
EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135},wb=async a=>{var b=await ka(a);m(b,`Loading data file "${a}" failed (no arrayBuffer).`);return new Uint8Array(b)},xb=[],zb=async(a,b)=>{"undefined"!=typeof Browser&&yb();for(var c of xb)if(c.canHandle(b))return m("AsyncFunction"===
c.handle.constructor.name,"Filesystem plugin handlers must be async functions (See #24914)"),c.handle(a,b);return a},Bb=async(a,b,c,d,e,g,h,l)=>{var r=b?gb(bb(a+"/"+b)):a,q;a:for(var u=q=`cp ${r}`;;){if(!Ma[q])break a;q=u+Math.random()}Na(q);try{if(u=c,"string"==typeof c&&(u=await wb(c)),u=await zb(u,r),l?.(),!g){c=u;g=b;a&&(a="string"==typeof a?a:Ab(a),g=b?bb(a+"/"+b):a);var n=ub(d,e),w=O.create(g,n);if(c){if("string"==typeof c){var F=Array(c.length);b=0;for(var G=c.length;b<G;++b)F[b]=c.charCodeAt(b);
c=F}O.chmod(w,n|146);var D=O.open(w,577);O.write(D,c,0,c.length,0,h);O.close(D);O.chmod(w,n)}}}finally{Oa(q)}};
function yb(){m(!O.T,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)");O.T=!0;b??=f.stdin;c??=f.stdout;a??=f.stderr;b?O.I("/dev","stdin",b):O.symlink("/dev/tty","/dev/stdin");c?O.I("/dev","stdout",null,c):O.symlink("/dev/tty","/dev/stdout");a?O.I("/dev","stderr",null,a):O.symlink("/dev/tty1","/dev/stderr");var a=O.open("/dev/stdin",0);var b=O.open("/dev/stdout",1);var c=O.open("/dev/stderr",
1);m(0===a.fd,`invalid handle for stdin (${a.fd})`);m(1===b.fd,`invalid handle for stdout (${b.fd})`);m(2===c.fd,`invalid handle for stderr (${c.fd})`)}function ob(a,b){O.ea[a]={i:b}}function Q(a){return 16384===(a&61440)}function R(a,b){var c=Q(a.mode)?(c=S(a,"x"))?c:a.h.lookup?0:2:54;if(c)throw new O.g(c);for(c=O.C[Cb(a.id,b)];c;c=c.L){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return O.lookup(a,b)}
function tb(a){var b=Cb(a.parent.id,a.name);if(O.C[b]===a)O.C[b]=a.L;else for(b=O.C[b];b;){if(b.L===a){b.L=a.L;break}b=b.L}}
function T(a,b={}){if(!a)throw new O.g(44);b.S??(b.S=!0);"/"===a.charAt(0)||(a=O.cwd()+"/"+a);var c=0;a:for(;40>c;c++){a=a.split("/").filter(l=>!!l);for(var d=O.root,e="/",g=0;g<a.length;g++){var h=g===a.length-1;if(h&&b.parent)break;if("."!==a[g])if(".."===a[g])if(e=cb(e),O.N(d)){a=e+"/"+a.slice(g+1).join("/");c--;continue a}else d=d.parent;else{e=bb(e+"/"+a[g]);try{d=R(d,a[g])}catch(l){if(44===l?.l&&h&&b.Ca)return{path:e};throw l;}!d.A||h&&!b.S||(d=d.A.root);if(40960===(d.mode&61440)&&(!h||b.u)){if(!d.h.readlink)throw new O.g(52);
d=d.h.readlink(d);"/"===d.charAt(0)||(d=cb(e)+"/"+d);a=d+"/"+a.slice(g+1).join("/");continue a}}}return{path:e,node:d}}throw new O.g(32);}function Ab(a){for(var b;;){if(O.N(a))return a=a.m.ma,b?"/"!==a[a.length-1]?`${a}/${b}`:a+b:a;b=b?`${a.name}/${b}`:a.name;a=a.parent}}function Cb(a,b){for(var c=0,d=0;d<b.length;d++)c=(c<<5)-c+b.charCodeAt(d)|0;return(a+c>>>0)%O.C.length}function Db(a){var b=Cb(a.parent.id,a.name);a.L=O.C[b];O.C[b]=a}
function Eb(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b}function S(a,b){if(O.ia)return 0;if(!b.includes("r")||a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0}function Fb(a,b){if(!Q(a.mode))return 54;try{return R(a,b),20}catch(c){}return S(a,"wx")}
function Gb(a,b,c){try{var d=R(a,b)}catch(e){return e.l}if(a=S(a,"wx"))return a;if(c){if(!Q(d.mode))return 54;if(O.N(d)||Ab(d)===O.cwd())return 10}else if(Q(d.mode))return 31;return 0}function Hb(a,b){if(!a)throw new O.g(b);return a}function U(a){a=O.ga(a);if(!a)throw new O.g(8);return a}function Ib(a,b=-1){m(-1<=b);a=Object.assign(new O.ra,a);if(-1==b)a:{for(b=0;b<=O.aa;b++)if(!O.streams[b])break a;throw new O.g(33);}a.fd=b;return O.streams[b]=a}
function Jb(a,b=-1){a=Ib(a,b);a.i?.Za?.(a);return a}function Kb(a,b,c){var d=a?.i.B;a=d?a:b;d??=b.h.B;Hb(d,63);d(a,c)}function Lb(a){var b=[];for(a=[a];a.length;){var c=a.pop();b.push(c);a.push(...c.O)}return b}function Mb(a){var b={Na:4096,bb:4096,blocks:1E6,Ma:5E5,La:5E5,files:O.Y,$a:O.Y-1,cb:42,flags:2,kb:255};a.h.oa&&Object.assign(b,a.h.oa(a.m.Ea.root));return b}function Nb(a,b,c){"undefined"==typeof c&&(c=b,b=438);return O.G(a,b|8192,c)}
function Ob(a,b,c,d){Kb(a,b,{mode:c&4095|b.mode&-4096,ctime:Date.now(),fa:d})}function Pb(a,b,c){if(Q(b.mode))throw new O.g(31);if(!O.isFile(b.mode))throw new O.g(28);var d=S(b,"w");if(d)throw new O.g(d);Kb(a,b,{size:c,timestamp:Date.now()})}
function Qb(a,b){try{var c=T(a,{u:!b});a=c.path}catch(e){}var d={N:!1,exists:!1,error:0,name:null,path:null,object:null,Fa:!1,Ha:null,Ga:null};try{c=T(a,{parent:!0}),d.Fa=!0,d.Ha=c.path,d.Ga=c.node,d.name=N(a),c=T(a,{u:!b}),d.exists=!0,d.path=c.path,d.object=c.node,d.name=c.node.name,d.N="/"===c.path}catch(e){d.error=e.l}return d}function Rb(a,b,c,d){a="string"==typeof a?a:Ab(a);b=bb(a+"/"+b);return O.create(b,ub(c,d))}
function Sb(a){if(!(a.Aa||a.Ba||a.link||a.j)){if("undefined"!=typeof XMLHttpRequest)throw Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");try{a.j=la(a.url),a.o=a.j.length}catch(b){throw new O.g(29);}}}
var O={root:null,O:[],ea:{},streams:[],Y:1,C:null,da:"/",T:!1,ia:!0,va:null,R:0,na:{},g:class extends Error{name="ErrnoError";constructor(a){super(Ja?M(Tb(a)):"");this.l=a;for(var b in vb)if(vb[b]===a){this.code=b;break}}},ra:class{F={};node=null;get object(){return this.node}set object(a){this.node=a}get flags(){return this.F.flags}set flags(a){this.F.flags=a}get position(){return this.F.position}set position(a){this.F.position=a}},qa:class{h={};i={};A=null;constructor(a,b,c,d){a||=this;this.parent=
a;this.m=a.m;this.id=O.Y++;this.name=b;this.mode=c;this.rdev=d;this.atime=this.mtime=this.ctime=Date.now()}get read(){return 365===(this.mode&365)}set read(a){a?this.mode|=365:this.mode&=-366}get write(){return 146===(this.mode&146)}set write(a){a?this.mode|=146:this.mode&=-147}get Ba(){return Q(this.mode)}get Aa(){return 8192===(this.mode&61440)}},createNode(a,b,c,d){m("object"==typeof a);a=new O.qa(a,b,c,d);Db(a);return a},N(a){return a===a.parent},isFile(a){return 32768===(a&61440)},isFIFO(a){return 4096===
(a&61440)},isSocket(a){return 49152===(a&49152)},aa:4096,ga:a=>O.streams[a],ta:{open(a){a.i=O.wa(a.node.rdev).i;a.i.open?.(a)},s(){throw new O.g(70);}},X:a=>a>>8,hb:a=>a&255,K:(a,b)=>a<<8|b,wa:a=>O.ea[a],pa(a,b){function c(h){m(0<O.R);O.R--;return b(h)}function d(h){if(h){if(!d.ua)return d.ua=!0,c(h)}else++g>=e.length&&c(null)}"function"==typeof a&&(b=a,a=!1);O.R++;1<O.R&&t(`warning: ${O.R} FS.syncfs operations in flight at once, probably just doing extra work`);var e=Lb(O.root.m),g=0;e.forEach(h=>
{if(!h.type.pa)return d(null);h.type.pa(h,a,d)})},m(a,b,c){if("string"==typeof a)throw a;var d="/"===c,e=!c;if(d&&O.root)throw new O.g(10);if(!d&&!e){var g=T(c,{S:!1});c=g.path;g=g.node;if(g.A)throw new O.g(10);if(!Q(g.mode))throw new O.g(54);}b={type:a,Ea:b,ma:c,O:[]};a=a.m(b);a.m=b;b.root=a;d?O.root=a:g&&(g.A=b,g.m&&g.m.O.push(b));return a},qb(a){a=T(a,{S:!1});if(!a.node.A)throw new O.g(28);a=a.node;var b=a.A,c=Lb(b);Object.keys(O.C).forEach(d=>{for(d=O.C[d];d;){var e=d.L;c.includes(d.m)&&tb(d);
d=e}});a.A=null;b=a.m.O.indexOf(b);m(-1!==b);a.m.O.splice(b,1)},lookup(a,b){return a.h.lookup(a,b)},G(a,b,c){var d=T(a,{parent:!0}).node;a=N(a);if(!a)throw new O.g(28);if("."===a||".."===a)throw new O.g(20);var e=Fb(d,a);if(e)throw new O.g(e);if(!d.h.G)throw new O.g(63);return d.h.G(d,a,b,c)},oa(a){return Mb(T(a,{u:!0}).node)},ob(a){return Mb(a.node)},create(a,b=438){return O.G(a,b&4095|32768,0)},mkdir(a,b=511){return O.G(a,b&1023|16384,0)},ib(a,b){var c=a.split("/"),d="",e;for(e of c)if(e){if(d||
"/"===a.charAt(0))d+="/";d+=e;try{O.mkdir(d,b)}catch(g){if(20!=g.l)throw g;}}},symlink(a,b){if(!gb(a))throw new O.g(44);var c=T(b,{parent:!0}).node;if(!c)throw new O.g(44);b=N(b);var d=Fb(c,b);if(d)throw new O.g(d);if(!c.h.symlink)throw new O.g(63);return c.h.symlink(c,b,a)},rename(a,b){var c=cb(a),d=cb(b),e=N(a),g=N(b);var h=T(a,{parent:!0});var l=h.node;h=T(b,{parent:!0});h=h.node;if(!l||!h)throw new O.g(44);if(l.m!==h.m)throw new O.g(75);var r=R(l,e);a=hb(a,d);if("."!==a.charAt(0))throw new O.g(28);
a=hb(b,c);if("."!==a.charAt(0))throw new O.g(55);try{var q=R(h,g)}catch(u){}if(r!==q){b=Q(r.mode);if(e=Gb(l,e,b))throw new O.g(e);if(e=q?Gb(h,g,b):Fb(h,g))throw new O.g(e);if(!l.h.rename)throw new O.g(63);if(r.A||q&&q.A)throw new O.g(10);if(h!==l&&(e=S(l,"w")))throw new O.g(e);tb(r);try{l.h.rename(r,h,g),r.parent=h}catch(u){throw u;}finally{Db(r)}}},rmdir(a){var b=T(a,{parent:!0}).node;a=N(a);var c=R(b,a),d=Gb(b,a,!0);if(d)throw new O.g(d);if(!b.h.rmdir)throw new O.g(63);if(c.A)throw new O.g(10);
b.h.rmdir(b,a);tb(c)},readdir(a){a=T(a,{u:!0}).node;return Hb(a.h.readdir,54)(a)},unlink(a){var b=T(a,{parent:!0}).node;if(!b)throw new O.g(44);a=N(a);var c=R(b,a),d=Gb(b,a,!1);if(d)throw new O.g(d);if(!b.h.unlink)throw new O.g(63);if(c.A)throw new O.g(10);b.h.unlink(b,a);tb(c)},readlink(a){a=T(a).node;if(!a)throw new O.g(44);if(!a.h.readlink)throw new O.g(28);return a.h.readlink(a)},stat(a,b){a=T(a,{u:!b}).node;return Hb(a.h.v,63)(a)},fstat(a){var b=U(a);a=b.node;var c=b.i.v;b=c?b:a;c??=a.h.v;Hb(c,
63);return c(b)},lstat(a){return O.stat(a,!0)},chmod(a,b,c){a="string"==typeof a?T(a,{u:!c}).node:a;Ob(null,a,b,c)},lchmod(a,b){O.chmod(a,b,!0)},fchmod(a,b){a=U(a);Ob(a,a.node,b,!1)},chown(a,b,c,d){a="string"==typeof a?T(a,{u:!d}).node:a;Kb(null,a,{timestamp:Date.now(),fa:d})},lchown(a,b,c){O.chown(a,b,c,!0)},fchown(a){a=U(a);Kb(a,a.node,{timestamp:Date.now(),fa:!1})},truncate(a,b){if(0>b)throw new O.g(28);a="string"==typeof a?T(a,{u:!0}).node:a;Pb(null,a,b)},eb(a,b){a=U(a);if(0>b||0===(a.flags&2097155))throw new O.g(28);
Pb(a,a.node,b)},rb(a,b,c){a=T(a,{u:!0}).node;Hb(a.h.B,63)(a,{atime:b,mtime:c})},open(a,b,c=438){if(""===a)throw new O.g(44);if("string"==typeof b){var d={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090}[b];if("undefined"==typeof d)throw Error(`Unknown file open mode: ${b}`);b=d}c=b&64?c&4095|32768:0;if("object"==typeof a)d=a;else{var e=a.endsWith("/");a=T(a,{u:!(b&131072),Ca:!0});d=a.node;a=a.path}var g=!1;if(b&64)if(d){if(b&128)throw new O.g(20);}else{if(e)throw new O.g(31);d=O.G(a,c|511,0);g=!0}if(!d)throw new O.g(44);
8192===(d.mode&61440)&&(b&=-513);if(b&65536&&!Q(d.mode))throw new O.g(54);if(!g&&(e=d?40960===(d.mode&61440)?32:Q(d.mode)&&("r"!==Eb(b)||b&576)?31:S(d,Eb(b)):44))throw new O.g(e);b&512&&!g&&O.truncate(d,0);b&=-131713;e=Ib({node:d,path:Ab(d),flags:b,seekable:!0,position:0,i:d.i,Ja:[],error:!1});e.i.open&&e.i.open(e);g&&O.chmod(d,c&511);!f.logReadFiles||b&1||a in O.na||(O.na[a]=1);return e},close(a){if(null===a.fd)throw new O.g(8);a.J&&(a.J=null);try{a.i.close&&a.i.close(a)}catch(b){throw b;}finally{O.streams[a.fd]=
null}a.fd=null},s(a,b,c){if(null===a.fd)throw new O.g(8);if(!a.seekable||!a.i.s)throw new O.g(70);if(0!=c&&1!=c&&2!=c)throw new O.g(28);a.position=a.i.s(a,b,c);a.Ja=[];return a.position},read(a,b,c,d,e){m(0<=c);if(0>d||0>e)throw new O.g(28);if(null===a.fd)throw new O.g(8);if(1===(a.flags&2097155))throw new O.g(8);if(Q(a.node.mode))throw new O.g(31);if(!a.i.read)throw new O.g(28);var g="undefined"!=typeof e;if(!g)e=a.position;else if(!a.seekable)throw new O.g(70);b=a.i.read(a,b,c,d,e);g||(a.position+=
b);return b},write(a,b,c,d,e,g){m(0<=c);if(0>d||0>e)throw new O.g(28);if(null===a.fd)throw new O.g(8);if(0===(a.flags&2097155))throw new O.g(8);if(Q(a.node.mode))throw new O.g(31);if(!a.i.write)throw new O.g(28);a.seekable&&a.flags&1024&&O.s(a,0,2);var h="undefined"!=typeof e;if(!h)e=a.position;else if(!a.seekable)throw new O.g(70);b=a.i.write(a,b,c,d,e,g);h||(a.position+=b);return b},M(a,b,c,d,e){if(0!==(d&2)&&0===(e&2)&&2!==(a.flags&2097155))throw new O.g(2);if(1===(a.flags&2097155))throw new O.g(2);
if(!a.i.M)throw new O.g(43);if(!b)throw new O.g(28);return a.i.M(a,b,c,d,e)},P(a,b,c,d,e){m(0<=c);return a.i.P?a.i.P(a,b,c,d,e):0},W(a,b,c){if(!a.i.W)throw new O.g(59);return a.i.W(a,b,c)},readFile(a,b={}){b.flags=b.flags||0;b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&"binary"!==b.encoding)throw Error(`Invalid encoding type "${b.encoding}"`);var c=O.open(a,b.flags);a=O.stat(a).size;var d=new Uint8Array(a);O.read(c,d,0,a,0);"utf8"===b.encoding&&(d=$a(d));O.close(c);return d},writeFile(a,
b,c={}){c.flags=c.flags||577;a=O.open(a,c.flags,c.mode);"string"==typeof b&&(b=new Uint8Array(lb(b)));if(ArrayBuffer.isView(b))O.write(a,b,0,b.byteLength,void 0,c.Ta);else throw Error("Unsupported data type");O.close(a)},cwd:()=>O.da,chdir(a){a=T(a,{u:!0});if(null===a.node)throw new O.g(44);if(!Q(a.node.mode))throw new O.g(54);var b=S(a.node,"x");if(b)throw new O.g(b);O.da=a.path},mb(){O.T=!1;Ub(0);for(var a of O.streams)a&&O.close(a)},ab(a,b){a=Qb(a,b);return a.exists?a.object:null},Xa(a,b){a="string"==
typeof a?a:Ab(a);for(b=b.split("/").reverse();b.length;){var c=b.pop();if(c){var d=bb(a+"/"+c);try{O.mkdir(d)}catch(e){if(20!=e.l)throw e;}a=d}}return d},I(a,b,c,d){a=db("string"==typeof a?a:Ab(a),b);b=ub(!!c,!!d);var e;(e=O.I).X??(e.X=64);e=O.K(O.I.X++,0);ob(e,{open(g){g.seekable=!1},close(){d?.buffer?.length&&d(10)},read(g,h,l,r){for(var q=0,u=0;u<r;u++){try{var n=c()}catch(w){throw new O.g(29);}if(void 0===n&&0===q)throw new O.g(6);if(null===n||void 0===n)break;q++;h[l+u]=n}q&&(g.node.atime=Date.now());
return q},write(g,h,l,r){for(var q=0;q<r;q++)try{d(h[l+q])}catch(u){throw new O.g(29);}r&&(g.node.mtime=g.node.ctime=Date.now());return q}});return Nb(a,b,e)},Va(a,b,c,d,e){function g(n,w,F,G,D){n=n.node.j;if(D>=n.length)return 0;G=Math.min(n.length-D,G);m(0<=G);if(n.slice)for(var H=0;H<G;H++)w[F+H]=n[D+H];else for(H=0;H<G;H++)w[F+H]=n.get(D+H);return G}class h{V=!1;F=[];U=void 0;ka=0;ja=0;get(n){if(!(n>this.length-1||0>n)){var w=n%this.chunkSize;return this.U(n/this.chunkSize|0)[w]}}Da(n){this.U=
n}la(){var n=new XMLHttpRequest;n.open("HEAD",c,!1);n.send(null);if(!(200<=n.status&&300>n.status||304===n.status))throw Error("Couldn't load "+c+". Status: "+n.status);var w=Number(n.getResponseHeader("Content-length")),F,G=(F=n.getResponseHeader("Accept-Ranges"))&&"bytes"===F;n=(F=n.getResponseHeader("Content-Encoding"))&&"gzip"===F;var D=1048576;G||(D=w);var H=this;H.Da(fa=>{var za=fa*D,ha=(fa+1)*D-1;ha=Math.min(ha,w-1);if("undefined"==typeof H.F[fa]){var Bc=H.F;if(za>ha)throw Error("invalid range ("+
za+", "+ha+") or no bytes requested!");if(ha>w-1)throw Error("only "+w+" bytes available! programmer error!");var J=new XMLHttpRequest;J.open("GET",c,!1);w!==D&&J.setRequestHeader("Range","bytes="+za+"-"+ha);J.responseType="arraybuffer";J.overrideMimeType&&J.overrideMimeType("text/plain; charset=x-user-defined");J.send(null);if(!(200<=J.status&&300>J.status||304===J.status))throw Error("Couldn't load "+c+". Status: "+J.status);za=void 0!==J.response?new Uint8Array(J.response||[]):lb(J.responseText||
"");Bc[fa]=za}if("undefined"==typeof H.F[fa])throw Error("doXHR failed!");return H.F[fa]});if(n||!w)D=w=1,D=w=this.U(0).length,p("LazyFiles on gzip forces download of the whole file when length is accessed");this.ka=w;this.ja=D;this.V=!0}get length(){this.V||this.la();return this.ka}get chunkSize(){this.V||this.la();return this.ja}}if("undefined"!=typeof XMLHttpRequest){if(!ba)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";
var l=new h;var r=void 0}else r=c,l=void 0;var q=Rb(a,b,d,e);l?q.j=l:r&&(q.j=null,q.url=r);Object.defineProperties(q,{o:{get:function(){return this.j.length}}});var u={};Object.keys(q.i).forEach(n=>{var w=q.i[n];u[n]=(...F)=>{Sb(q);return w(...F)}});u.read=(n,w,F,G,D)=>{Sb(q);return g(n,w,F,G,D)};u.M=(n,w,F)=>{Sb(q);var G=sb();if(!G)throw new O.g(48);g(n,A,G,w,F);return{Ia:G,sa:!0}};q.i=u;return q},Ka(){v("FS.absolutePath has been removed; use PATH_FS.resolve instead")},Ua(){v("FS.createFolder has been removed; use FS.mkdir instead")},
Wa(){v("FS.createLink has been removed; use FS.symlink instead")},gb(){v("FS.joinPath has been removed; use PATH.join instead")},jb(){v("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},nb(){v("FS.standardizePath has been removed; use PATH.normalize instead")}};function Vb(a,b,c){if("/"===b.charAt(0))return b;a=-100===a?O.cwd():U(a).path;if(0==b.length){if(!c)throw new O.g(44);return a}return a+"/"+b}
function Wb(a,b){x[a>>2]=b.dev;x[a+4>>2]=b.mode;x[a+8>>2]=b.nlink;x[a+12>>2]=b.uid;x[a+16>>2]=b.gid;x[a+20>>2]=b.rdev;C[a+24>>3]=BigInt(b.size);B[a+32>>2]=4096;B[a+36>>2]=b.blocks;var c=b.atime.getTime(),d=b.mtime.getTime(),e=b.ctime.getTime();C[a+40>>3]=BigInt(Math.floor(c/1E3));x[a+48>>2]=c%1E3*1E6;C[a+56>>3]=BigInt(Math.floor(d/1E3));x[a+64>>2]=d%1E3*1E6;C[a+72>>3]=BigInt(Math.floor(e/1E3));x[a+80>>2]=e%1E3*1E6;C[a+88>>3]=BigInt(b.ino);return 0}
var Xb=void 0,V=()=>{m(void 0!=Xb);var a=B[+Xb>>2];Xb+=4;return a},W=(a,b,c)=>{m("number"==typeof c,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!");return kb(a,Ha,b,c)},Yb=0,Zb=a=>0===a%4&&(0!==a%100||0===a%400),$b=[0,31,60,91,121,152,182,213,244,274,305,335],ac=[0,31,59,90,120,151,181,212,243,273,304,334],bc={},cc=a=>{if(a instanceof Ta||"unwind"==a)return ra;ua();a instanceof WebAssembly.RuntimeError&&0>=X()&&t("Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)");
ea(1,a)},dc=a=>{ra=a;Ya||0<Yb||(f.onExit?.(a),qa=!0);ea(a,new Ta(a))},fc=(a,b)=>{ra=a;ec();(Ya||0<Yb)&&!b&&(b=`program exited (with status: ${a}), but keepRuntimeAlive() is set (counter=${Yb}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`,Fa?.(b),t(b));dc(a)},gc=a=>{if(qa)t("user callback triggered after runtime exited or application aborted. Ignoring.");
else try{if(a(),!(Ya||0<Yb))try{fc(ra)}catch(b){cc(b)}}catch(b){cc(b)}},hc={},jc=()=>{if(!ic){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.language||"C").replace("-","_")+".UTF-8",_:da||"./this.program"},b;for(b in hc)void 0===hc[b]?delete a[b]:a[b]=hc[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);ic=c}return ic},ic,kc=(a,b,c,d)=>{for(var e=0,g=0;g<c;g++){var h=x[b>>2],l=x[b+4>>2];b+=8;h=O.read(a,A,h,l,d);if(0>h)return-1;
e+=h;if(h<l)break;"undefined"!=typeof d&&(d+=h)}return e},lc=(a,b,c,d)=>{for(var e=0,g=0;g<c;g++){var h=x[b>>2],l=x[b+4>>2];b+=8;h=O.write(a,A,h,l,d);if(0>h)return-1;e+=h;if(h<l)break;"undefined"!=typeof d&&(d+=h)}return e},mc;O.Ya=(a,b,c,d,e,g,h,l,r,q)=>{Bb(a,b,c,d,e,l,r,q).then(g).catch(h)};O.lb=Bb;O.C=Array(4096);O.m(P,{},"/");O.mkdir("/tmp");O.mkdir("/home");O.mkdir("/home/web_user");
(function(){O.mkdir("/dev");ob(O.K(1,3),{read:()=>0,write:(d,e,g,h)=>h,s:()=>0});Nb("/dev/null",O.K(1,3));nb(O.K(5,0),qb);nb(O.K(6,0),rb);Nb("/dev/tty",O.K(5,0));Nb("/dev/tty1",O.K(6,0));var a=new Uint8Array(1024),b=0,c=()=>{0===b&&(fb(a),b=a.byteLength);return a[--b]};O.I("/dev","random",c);O.I("/dev","urandom",c);O.mkdir("/dev/shm");O.mkdir("/dev/shm/tmp")})();
(function(){O.mkdir("/proc");var a=O.mkdir("/proc/self");O.mkdir("/proc/self/fd");O.m({m(){var b=O.createNode(a,"fd",16895,73);b.i={s:P.i.s};b.h={lookup(c,d){c=+d;var e=U(c);c={parent:null,m:{ma:"fake"},h:{readlink:()=>e.path},id:c+1};return c.parent=c},readdir(){return Array.from(O.streams.entries()).filter(([,c])=>c).map(([c])=>c.toString())}};return b}},{},"/proc/self/fd")})();O.va={MEMFS:P};f.noExitRuntime&&(Ya=f.noExitRuntime);f.preloadPlugins&&(xb=f.preloadPlugins);f.print&&(p=f.print);
f.printErr&&(t=f.printErr);f.wasmBinary&&(pa=f.wasmBinary);Object.getOwnPropertyDescriptor(f,"fetchSettings")&&v("`Module.fetchSettings` was supplied but `fetchSettings` not included in INCOMING_MODULE_JS_API");f.thisProgram&&(da=f.thisProgram);m("undefined"==typeof f.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead");
m("undefined"==typeof f.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead");m("undefined"==typeof f.read,"Module.read option was removed");m("undefined"==typeof f.readAsync,"Module.readAsync option was removed (modify readAsync in JS)");m("undefined"==typeof f.readBinary,"Module.readBinary option was removed (modify readBinary in JS)");
m("undefined"==typeof f.setWindowTitle,"Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)");m("undefined"==typeof f.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY");m("undefined"==typeof f.ENVIRONMENT,"Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");m("undefined"==typeof f.STACK_SIZE,"STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time");
m("undefined"==typeof f.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally");m("undefined"==typeof f.INITIAL_MEMORY,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically");f.callMain=nc;f.FS=O;
"writeI53ToI64 writeI53ToI64Clamped writeI53ToI64Signaling writeI53ToU64Clamped writeI53ToU64Signaling readI53FromI64 readI53FromU64 convertI32PairToI53 convertI32PairToI53Checked convertU32PairToI53 getTempRet0 setTempRet0 zeroMemory withStackSave inetPton4 inetNtop4 inetPton6 inetNtop6 readSockaddr writeSockaddr readEmAsmArgs jstoi_q autoResumeAudioContext dynCallLegacy getDynCaller dynCall runtimeKeepalivePush runtimeKeepalivePop asmjsMangle HandleAllocator getNativeTypeSize addOnInit addOnPostCtor addOnPreMain addOnExit STACK_SIZE STACK_ALIGN POINTER_SIZE ASSERTIONS ccall cwrap convertJsFunctionToWasm getEmptyTableSlot updateTableMap getFunctionAddress addFunction removeFunction intArrayToString AsciiToString stringToAscii UTF16ToString stringToUTF16 lengthBytesUTF16 UTF32ToString stringToUTF32 lengthBytesUTF32 stringToNewUTF8 writeArrayToMemory registerKeyEventCallback maybeCStringToJsString findEventTarget getBoundingClientRect fillMouseEventData registerMouseEventCallback registerWheelEventCallback registerUiEventCallback registerFocusEventCallback fillDeviceOrientationEventData registerDeviceOrientationEventCallback fillDeviceMotionEventData registerDeviceMotionEventCallback screenOrientation fillOrientationChangeEventData registerOrientationChangeEventCallback fillFullscreenChangeEventData registerFullscreenChangeEventCallback JSEvents_requestFullscreen JSEvents_resizeCanvasForFullscreen registerRestoreOldStyle hideEverythingExceptGivenElement restoreHiddenElements setLetterbox softFullscreenResizeWebGLRenderTarget doRequestFullscreen fillPointerlockChangeEventData registerPointerlockChangeEventCallback registerPointerlockErrorEventCallback requestPointerLock fillVisibilityChangeEventData registerVisibilityChangeEventCallback registerTouchEventCallback fillGamepadEventData registerGamepadEventCallback registerBeforeUnloadEventCallback fillBatteryEventData registerBatteryEventCallback setCanvasElementSize getCanvasElementSize jsStackTrace getCallstack convertPCtoSourceLocation wasiRightsToMuslOFlags wasiOFlagsToMuslOFlags safeSetTimeout setImmediateWrapped safeRequestAnimationFrame clearImmediateWrapped registerPostMainLoop registerPreMainLoop getPromise makePromise idsToPromises makePromiseCallback ExceptionInfo findMatchingCatch Browser_asyncPrepareDataCounter arraySum addDays getSocketFromFD getSocketAddress FS_mkdirTree _setNetworkCallback heapObjectForWebGLType toTypedArrayIndex webgl_enable_ANGLE_instanced_arrays webgl_enable_OES_vertex_array_object webgl_enable_WEBGL_draw_buffers webgl_enable_WEBGL_multi_draw webgl_enable_EXT_polygon_offset_clamp webgl_enable_EXT_clip_control webgl_enable_WEBGL_polygon_mode emscriptenWebGLGet computeUnpackAlignedImageSize colorChannelsInGlTextureFormat emscriptenWebGLGetTexPixelData emscriptenWebGLGetUniform webglGetUniformLocation webglPrepareUniformLocationsBeforeFirstUse webglGetLeftBracePos emscriptenWebGLGetVertexAttrib __glGetActiveAttribOrUniform writeGLArray registerWebGlEventCallback runAndAbortIfError ALLOC_NORMAL ALLOC_STACK allocate writeStringToMemory writeAsciiToMemory demangle stackTrace".split(" ").forEach(function(a){Ba(a,()=>
{var b=`\`${a}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`,c=a;c.startsWith("_")||(c="$"+a);b+=` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${c}')`;Aa(a)&&(b+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you");z(b)});Da(a)});"run addRunDependency removeRunDependency out err abort wasmMemory wasmExports HEAPF32 HEAPF64 HEAP8 HEAPU8 HEAP16 HEAPU16 HEAP32 HEAPU32 HEAP64 HEAPU64 writeStackCookie checkStackCookie INT53_MAX INT53_MIN bigintToI53Checked stackSave stackRestore stackAlloc ptrToString exitJS getHeapMax growMemory ENV ERRNO_CODES strError DNS Protocols Sockets timers warnOnce readEmAsmArgsArray getExecutableName handleException keepRuntimeAlive callUserCallback maybeExit asyncLoad alignMemory mmapAlloc wasmTable getUniqueRunDependency noExitRuntime addOnPreRun addOnPostRun freeTableIndexes functionsInTableMap setValue getValue PATH PATH_FS UTF8Decoder UTF8ArrayToString UTF8ToString stringToUTF8Array stringToUTF8 lengthBytesUTF8 intArrayFromString UTF16Decoder stringToUTF8OnStack JSEvents specialHTMLTargets findCanvasEventTarget currentFullscreenStrategy restoreOldWindowedStyle UNWIND_CACHE ExitStatus getEnvStrings checkWasiClock doReadv doWritev initRandomFill randomFill emSetImmediate emClearImmediate_deps emClearImmediate promiseMap uncaughtExceptionCount exceptionLast exceptionCaught Browser requestFullscreen requestFullScreen setCanvasSize getUserMedia createContext getPreloadedImageData__data wget MONTH_DAYS_REGULAR MONTH_DAYS_LEAP MONTH_DAYS_REGULAR_CUMULATIVE MONTH_DAYS_LEAP_CUMULATIVE isLeapYear ydayFromDate SYSCALLS preloadPlugins FS_createPreloadedFile FS_preloadFile FS_modeStringToFlags FS_getMode FS_stdin_getChar_buffer FS_stdin_getChar FS_unlink FS_createPath FS_createDevice FS_readFile FS_root FS_mounts FS_devices FS_streams FS_nextInode FS_nameTable FS_currentPath FS_initialized FS_ignorePermissions FS_filesystems FS_syncFSRequests FS_readFiles FS_lookupPath FS_getPath FS_hashName FS_hashAddNode FS_hashRemoveNode FS_lookupNode FS_createNode FS_destroyNode FS_isRoot FS_isMountpoint FS_isFile FS_isDir FS_isLink FS_isChrdev FS_isBlkdev FS_isFIFO FS_isSocket FS_flagsToPermissionString FS_nodePermissions FS_mayLookup FS_mayCreate FS_mayDelete FS_mayOpen FS_checkOpExists FS_nextfd FS_getStreamChecked FS_getStream FS_createStream FS_closeStream FS_dupStream FS_doSetAttr FS_chrdev_stream_ops FS_major FS_minor FS_makedev FS_registerDevice FS_getDevice FS_getMounts FS_syncfs FS_mount FS_unmount FS_lookup FS_mknod FS_statfs FS_statfsStream FS_statfsNode FS_create FS_mkdir FS_mkdev FS_symlink FS_rename FS_rmdir FS_readdir FS_readlink FS_stat FS_fstat FS_lstat FS_doChmod FS_chmod FS_lchmod FS_fchmod FS_doChown FS_chown FS_lchown FS_fchown FS_doTruncate FS_truncate FS_ftruncate FS_utime FS_open FS_close FS_isClosed FS_llseek FS_read FS_write FS_mmap FS_msync FS_ioctl FS_writeFile FS_cwd FS_chdir FS_createDefaultDirectories FS_createDefaultDevices FS_createSpecialDirectories FS_createStandardStreams FS_staticInit FS_init FS_quit FS_findObject FS_analyzePath FS_createFile FS_createDataFile FS_forceLoadFile FS_createLazyFile FS_absolutePath FS_createFolder FS_createLink FS_joinPath FS_mmapAlloc FS_standardizePath MEMFS TTY PIPEFS SOCKFS tempFixedLengthArray miniTempWebGLFloatBuffers miniTempWebGLIntBuffers GL AL GLUT EGL GLEW IDBStore SDL SDL_gfx allocateUTF8 allocateUTF8OnStack print printErr jstoi_s".split(" ").forEach(Da);
var oc=f._main=y("_main"),Tb=y("_strerror"),Ub=y("_fflush"),ta=y("_emscripten_stack_get_end"),pc=y("__emscripten_timeout"),Y=y("_setThrew"),qc=y("_emscripten_stack_init"),Z=y("__emscripten_stack_restore"),rc=y("__emscripten_stack_alloc"),X=y("_emscripten_stack_get_current"),dynCall_v=y("dynCall_v"),dynCall_iii=y("dynCall_iii"),sc=y("dynCall_viii"),tc=y("dynCall_iiii"),uc=y("dynCall_ii"),dynCall_vi=y("dynCall_vi"),vc=y("dynCall_iiiii"),wc=y("dynCall_iiiiiiiii"),dynCall_vii=y("dynCall_vii"),xc=y("dynCall_iiji"),
yc=y("dynCall_viiii"),zc=y("dynCall_viiiii"),Ac=y("dynCall_viiiiii"),Oc={__assert_fail:(a,b,c,d)=>v(`Assertion failed: ${M(a)}, at: `+[b?M(b):"unknown filename",c,d?M(d):"unknown function"]),__syscall_dup:function(a){try{var b=U(a);return Jb(b).fd}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return-c.l}},__syscall_dup3:function(a,b,c){try{var d=U(a);m(!c);if(d.fd===b)return-28;if(0>b||b>=O.aa)return-8;var e=O.ga(b);e&&O.close(e);return Jb(d,b).fd}catch(g){if("undefined"==typeof O||
"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_fcntl64:function(a,b,c){Xb=c;try{var d=U(a);switch(b){case 0:var e=V();if(0>e)break;for(;O.streams[e];)e++;return Jb(d,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=V(),d.flags|=e,0;case 12:return e=V(),Ia[e+0>>1]=2,0;case 13:case 14:return 0}return-28}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_fstat64:function(a,b){try{return Wb(b,O.fstat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==
c.name)throw c;return-c.l}},__syscall_getdents64:function(a,b,c){try{var d=U(a);d.J||(d.J=O.readdir(d.path));a=0;var e=O.s(d,0,1),g=Math.floor(e/280),h=Math.min(d.J.length,g+Math.floor(c/280));for(c=g;c<h;c++){var l=d.J[c];if("."===l){var r=d.node.id;var q=4}else if(".."===l)r=T(d.path,{parent:!0}).node.id,q=4;else{try{var u=R(d.node,l)}catch(n){if(28===n?.l)continue;throw n;}r=u.id;q=8192===(u.mode&61440)?2:Q(u.mode)?4:40960===(u.mode&61440)?10:8}m(r);C[b+a>>3]=BigInt(r);C[b+a+8>>3]=BigInt(280*(c+
1));Ia[b+a+16>>1]=280;A[b+a+18]=q;W(l,b+a+19,256);a+=280}O.s(d,280*c,0);return a}catch(n){if("undefined"==typeof O||"ErrnoError"!==n.name)throw n;return-n.l}},__syscall_ioctl:function(a,b,c){Xb=c;try{var d=U(a);switch(b){case 21509:return d.tty?0:-59;case 21505:if(!d.tty)return-59;if(d.tty.H.xa){a=[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];var e=V();B[e>>2]=25856;B[e+4>>2]=5;B[e+8>>2]=191;B[e+12>>2]=35387;for(var g=0;32>g;g++)A[e+g+17]=a[g]||0}return 0;case 21510:case 21511:case 21512:return d.tty?
0:-59;case 21506:case 21507:case 21508:if(!d.tty)return-59;if(d.tty.H.ya)for(e=V(),a=[],g=0;32>g;g++)a.push(A[e+g+17]);return 0;case 21519:if(!d.tty)return-59;e=V();return B[e>>2]=0;case 21520:return d.tty?-28:-59;case 21537:case 21531:return e=V(),O.W(d,b,e);case 21523:if(!d.tty)return-59;d.tty.H.za&&(g=[24,80],e=V(),Ia[e>>1]=g[0],Ia[e+2>>1]=g[1]);return 0;case 21524:return d.tty?0:-59;case 21515:return d.tty?0:-59;default:return-28}}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;
return-h.l}},__syscall_lstat64:function(a,b){try{return a=M(a),Wb(b,O.lstat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return-c.l}},__syscall_newfstatat:function(a,b,c,d){try{b=M(b);var e=d&256,g=d&4096;d&=-6401;m(!d,`unknown flags in __syscall_newfstatat: ${d}`);b=Vb(a,b,g);return Wb(c,e?O.lstat(b):O.stat(b))}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return-h.l}},__syscall_openat:function(a,b,c,d){Xb=d;try{b=M(b);b=Vb(a,b);var e=d?V():0;return O.open(b,
c,e).fd}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return-g.l}},__syscall_renameat:function(a,b,c,d){try{return b=M(b),d=M(d),b=Vb(a,b),d=Vb(c,d),O.rename(b,d),0}catch(e){if("undefined"==typeof O||"ErrnoError"!==e.name)throw e;return-e.l}},__syscall_rmdir:function(a){try{return a=M(a),O.rmdir(a),0}catch(b){if("undefined"==typeof O||"ErrnoError"!==b.name)throw b;return-b.l}},__syscall_stat64:function(a,b){try{return a=M(a),Wb(b,O.stat(a))}catch(c){if("undefined"==typeof O||"ErrnoError"!==
c.name)throw c;return-c.l}},__syscall_unlinkat:function(a,b,c){try{b=M(b);b=Vb(a,b);if(c)if(512===c)O.rmdir(b);else return-28;else O.unlink(b);return 0}catch(d){if("undefined"==typeof O||"ErrnoError"!==d.name)throw d;return-d.l}},_abort_js:()=>v("native code called abort()"),_emscripten_runtime_keepalive_clear:()=>{Ya=!1;Yb=0},_emscripten_throw_longjmp:()=>{throw Infinity;},_gmtime_js:function(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);a=new Date(1E3*a);B[b>>2]=a.getUTCSeconds();
B[b+4>>2]=a.getUTCMinutes();B[b+8>>2]=a.getUTCHours();B[b+12>>2]=a.getUTCDate();B[b+16>>2]=a.getUTCMonth();B[b+20>>2]=a.getUTCFullYear()-1900;B[b+24>>2]=a.getUTCDay();B[b+28>>2]=(a.getTime()-Date.UTC(a.getUTCFullYear(),0,1,0,0,0,0))/864E5|0},_localtime_js:function(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);a=new Date(1E3*a);B[b>>2]=a.getSeconds();B[b+4>>2]=a.getMinutes();B[b+8>>2]=a.getHours();B[b+12>>2]=a.getDate();B[b+16>>2]=a.getMonth();B[b+20>>2]=a.getFullYear()-1900;B[b+24>>
2]=a.getDay();B[b+28>>2]=(Zb(a.getFullYear())?$b:ac)[a.getMonth()]+a.getDate()-1|0;B[b+36>>2]=-(60*a.getTimezoneOffset());var c=(new Date(a.getFullYear(),6,1)).getTimezoneOffset(),d=(new Date(a.getFullYear(),0,1)).getTimezoneOffset();B[b+32>>2]=(c!=d&&a.getTimezoneOffset()==Math.min(d,c))|0},_mktime_js:function(a){var b=new Date(B[a+20>>2]+1900,B[a+16>>2],B[a+12>>2],B[a+8>>2],B[a+4>>2],B[a>>2],0),c=B[a+32>>2],d=b.getTimezoneOffset(),e=(new Date(b.getFullYear(),6,1)).getTimezoneOffset(),g=(new Date(b.getFullYear(),
0,1)).getTimezoneOffset(),h=Math.min(g,e);0>c?B[a+32>>2]=Number(e!=g&&h==d):0<c!=(h==d)&&(e=Math.max(g,e),b.setTime(b.getTime()+6E4*((0<c?h:e)-d)));B[a+24>>2]=b.getDay();B[a+28>>2]=(Zb(b.getFullYear())?$b:ac)[b.getMonth()]+b.getDate()-1|0;B[a>>2]=b.getSeconds();B[a+4>>2]=b.getMinutes();B[a+8>>2]=b.getHours();B[a+12>>2]=b.getDate();B[a+16>>2]=b.getMonth();B[a+20>>2]=b.getYear();a=b.getTime();return BigInt(isNaN(a)?-1:a/1E3)},_setitimer_js:(a,b)=>{bc[a]&&(clearTimeout(bc[a].id),delete bc[a]);if(!b)return 0;
var c=setTimeout(()=>{m(a in bc);delete bc[a];gc(()=>pc(a,performance.now()))},b);bc[a]={id:c,pb:b};return 0},_tzset_js:(a,b,c,d)=>{var e=(new Date).getFullYear(),g=(new Date(e,0,1)).getTimezoneOffset();e=(new Date(e,6,1)).getTimezoneOffset();x[a>>2]=60*Math.max(g,e);B[b>>2]=Number(g!=e);b=h=>{var l=Math.abs(h);return`UTC${0<=h?"-":"+"}${String(Math.floor(l/60)).padStart(2,"0")}${String(l%60).padStart(2,"0")}`};a=b(g);b=b(e);m(a);m(b);m(16>=jb(a),`timezone name truncated to fit in TZNAME_MAX (${a})`);
m(16>=jb(b),`timezone name truncated to fit in TZNAME_MAX (${b})`);e<g?(W(a,c,17),W(b,d,17)):(W(a,d,17),W(b,c,17))},clock_time_get:function(a,b,c){if(!(0<=a&&3>=a))return 28;C[c>>3]=BigInt(Math.round(1E6*(0===a?Date.now():performance.now())));return 0},emscripten_date_now:()=>Date.now(),emscripten_resize_heap:a=>{var b=Ha.length;a>>>=0;m(a>b);if(2147483648<a)return t(`Cannot enlarge memory, requested ${a} bytes, but the limit is ${2147483648} bytes!`),!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=
Math.min(d,a+100663296);var e=Math,g=e.min;d=Math.max(a,d);m(65536,"alignment argument is required");e=g.call(e,2147483648,65536*Math.ceil(d/65536));a:{g=e;d=Ga.buffer.byteLength;try{Ga.grow((g-d+65535)/65536|0);Ka();var h=1;break a}catch(l){t(`growMemory: Attempted to grow heap from ${d} bytes to ${g} bytes, but got error: ${l}`)}h=void 0}if(h)return!0}t(`Failed to grow the heap from ${b} bytes to ${e} bytes, not enough memory!`);return!1},environ_get:(a,b)=>{var c=0,d=0,e;for(e of jc()){var g=b+
c;x[a+d>>2]=g;c+=W(e,g,Infinity)+1;d+=4}return 0},environ_sizes_get:(a,b)=>{var c=jc();x[a>>2]=c.length;a=0;for(var d of c)a+=jb(d)+1;x[b>>2]=a;return 0},exit:fc,fd_close:function(a){try{var b=U(a);O.close(b);return 0}catch(c){if("undefined"==typeof O||"ErrnoError"!==c.name)throw c;return c.l}},fd_pread:function(a,b,c,d,e){d=-9007199254740992>d||9007199254740992<d?NaN:Number(d);try{if(isNaN(d))return 61;var g=U(a),h=kc(g,b,c,d);x[e>>2]=h;return 0}catch(l){if("undefined"==typeof O||"ErrnoError"!==
l.name)throw l;return l.l}},fd_pwrite:function(a,b,c,d,e){d=-9007199254740992>d||9007199254740992<d?NaN:Number(d);try{if(isNaN(d))return 61;var g=U(a),h=lc(g,b,c,d);x[e>>2]=h;return 0}catch(l){if("undefined"==typeof O||"ErrnoError"!==l.name)throw l;return l.l}},fd_read:function(a,b,c,d){try{var e=U(a),g=kc(e,b,c);x[d>>2]=g;return 0}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return h.l}},fd_seek:function(a,b,c,d){b=-9007199254740992>b||9007199254740992<b?NaN:Number(b);try{if(isNaN(b))return 61;
var e=U(a);O.s(e,b,c);C[d>>3]=BigInt(e.position);e.J&&0===b&&0===c&&(e.J=null);return 0}catch(g){if("undefined"==typeof O||"ErrnoError"!==g.name)throw g;return g.l}},fd_write:function(a,b,c,d){try{var e=U(a),g=lc(e,b,c);x[d>>2]=g;return 0}catch(h){if("undefined"==typeof O||"ErrnoError"!==h.name)throw h;return h.l}},invoke_ii:Cc,invoke_iii:Dc,invoke_iiii:Ec,invoke_iiiii:Fc,invoke_iiiiiiiii:Gc,invoke_iiji:Hc,invoke_vi:Ic,invoke_vii:Jc,invoke_viii:Kc,invoke_viiii:Lc,invoke_viiiii:Mc,invoke_viiiiii:Nc,
proc_exit:dc},L=await (async function(){function a(d){L=d.exports;Ga=L.memory;m(Ga,"memory not found in wasm exports");Ka();mc=L.__indirect_function_table;m(mc,"table not found in wasm exports");d=L;f._main=oc=K("__main_argc_argv",2);Tb=K("strerror",1);Ub=K("fflush",1);ta=d.emscripten_stack_get_end;pc=K("_emscripten_timeout",2);Y=K("setThrew",2);qc=d.emscripten_stack_init;Z=d._emscripten_stack_restore;rc=d._emscripten_stack_alloc;X=d.emscripten_stack_get_current;dynCall_v=K("dynCall_v",
1);dynCall_iii=K("dynCall_iii",3);sc=K("dynCall_viii",4);tc=K("dynCall_iiii",4);uc=K("dynCall_ii",2);dynCall_vi=K("dynCall_vi",2);vc=K("dynCall_iiiii",5);wc=K("dynCall_iiiiiiiii",9);dynCall_vii=K("dynCall_vii",3);xc=K("dynCall_iiji",4);yc=K("dynCall_viiii",5);zc=K("dynCall_viiiii",6);Ac=K("dynCall_viiiiii",7);Oa("wasm-instantiate");return L}Na("wasm-instantiate");var b=f,c={env:Oc,wasi_snapshot_preview1:Oc};if(f.instantiateWasm)return new Promise((d,e)=>{try{f.instantiateWasm(c,(g,h)=>{d(a(g,h))})}catch(g){t(`Module.instantiateWasm callback failed with error: ${g}`),
e(g)}});Pa??=f.locateFile?f.locateFile?f.locateFile("gs.wasm",ja):ja+"gs.wasm":(new URL("gs.wasm",import.meta.url)).href;return function(d){m(f===b,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?");b=null;return a(d.instance)}(await Sa(c))}());function Ic(a,b){var c=X();try{dynCall_vi(a,b)}catch(d){Z(c);if(d!==d+0)throw d;Y(1,0)}}
function Jc(a,b,c){var d=X();try{dynCall_vii(a,b,c)}catch(e){Z(d);if(e!==e+0)throw e;Y(1,0)}}function Cc(a,b){var c=X();try{return uc(a,b)}catch(d){Z(c);if(d!==d+0)throw d;Y(1,0)}}function Ec(a,b,c,d){var e=X();try{return tc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}function Dc(a,b,c){var d=X();try{return dynCall_iii(a,b,c)}catch(e){Z(d);if(e!==e+0)throw e;Y(1,0)}}function Kc(a,b,c,d){var e=X();try{sc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}
function Lc(a,b,c,d,e){var g=X();try{yc(a,b,c,d,e)}catch(h){Z(g);if(h!==h+0)throw h;Y(1,0)}}function Hc(a,b,c,d){var e=X();try{return xc(a,b,c,d)}catch(g){Z(e);if(g!==g+0)throw g;Y(1,0)}}function Fc(a,b,c,d,e){var g=X();try{return vc(a,b,c,d,e)}catch(h){Z(g);if(h!==h+0)throw h;Y(1,0)}}function Mc(a,b,c,d,e,g){var h=X();try{zc(a,b,c,d,e,g)}catch(l){Z(h);if(l!==l+0)throw l;Y(1,0)}}function Nc(a,b,c,d,e,g,h){var l=X();try{Ac(a,b,c,d,e,g,h)}catch(r){Z(l);if(r!==r+0)throw r;Y(1,0)}}
function Gc(a,b,c,d,e,g,h,l,r){var q=X();try{return wc(a,b,c,d,e,g,h,l,r)}catch(u){Z(q);if(u!==u+0)throw u;Y(1,0)}}var Pc;
function nc(a=[]){m(0==E,'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])');m("undefined"===typeof Wa||0==Wa.length,"cannot call main when preRun functions remain to be called");var b=oc;a.unshift(da);var c=a.length,d=rc(4*(c+1)),e=d;a.forEach(h=>{var l=x,r=e>>2,q=jb(h)+1,u=rc(q);W(h,u,q);l[r]=u;e+=4});x[e>>2]=0;try{var g=b(c,d);fc(g,!0);return g}catch(h){return cc(h)}}
function Qc(){function a(){m(!Pc);Pc=!0;f.calledRun=!0;if(!qa){m(!Ja);Ja=!0;ua();f.noFSInit||O.T||yb();L.__wasm_call_ctors();O.ia=!1;ua();Ea?.(f);f.onRuntimeInitialized?.();ya("onRuntimeInitialized");ua();if(f.postRun)for("function"==typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;){var b=f.postRun.shift();Va.push(b)}ya("postRun");Ua(Va)}}if(0<E)La=Qc;else{qc();sa();if(f.preRun)for("function"==typeof f.preRun&&(f.preRun=[f.preRun]);f.preRun.length;)Xa();ya("preRun");Ua(Wa);0<E?La=Qc:(f.setStatus?
(f.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>f.setStatus(""),1);a()},1)):a(),ua())}}function ec(){var a=p,b=t,c=!1;p=t=()=>{c=!0};try{Ub(0),["stdout","stderr"].forEach(d=>{(d=Qb("/dev/"+d))&&mb[d.object.rdev]?.output?.length&&(c=!0)})}catch(d){}p=a;t=b;c&&z("stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.")}
if(f.preInit)for("function"==typeof f.preInit&&(f.preInit=[f.preInit]);0<f.preInit.length;)f.preInit.shift()();ya("preInit");Qc();Ja?moduleRtn=f:moduleRtn=new Promise((a,b)=>{Ea=a;Fa=b});for(const a of Object.keys(f))a in moduleArg||Object.defineProperty(moduleArg,a,{configurable:!0,get(){v(`Access to module property ('${a}') is no longer possible via the module constructor argument; Instead, use the result of the module constructor.`)}});
;return moduleRtn}export default Module;

Binary file not shown.

101
public/images/badge.svg Normal file
View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 604 129" style="enable-background:new 0 0 604 129;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
</style>
<g>
<g>
<g>
<path class="st0" d="M174.3,3c4.9,0,8.7,2.9,8.7,8.6c0,5.6-3.8,8.5-8.7,8.5h-7.6v11.1h-3.5V3H174.3z M166.7,17.1h7.2
c3,0,5.6-1.8,5.6-5.5c0-3.8-2.5-5.5-5.6-5.5h-7.2V17.1z"/>
<path class="st0" d="M208.8,21.7c0,6.1-4.3,10-9.9,10c-5.6,0-9.9-3.9-9.9-10c0-6.1,4.3-10,9.9-10
C204.5,11.7,208.8,15.6,208.8,21.7z M192.3,21.7c0,4.5,2.9,7.2,6.6,7.2c3.7,0,6.6-2.7,6.6-7.2c0-4.5-2.9-7.1-6.6-7.1
C195.2,14.5,192.3,17.2,192.3,21.7z"/>
<path class="st0" d="M234.4,31.3l-5.2-13.8L224,31.3h-2.6L214.1,12h3.6l5.2,14l5.2-14h2.3l5.3,14l5.2-14h3.5L237,31.3H234.4z"/>
<path class="st0" d="M253,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H253z M253,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C255.6,14.5,253.2,16.5,253,20.3z"/>
<path class="st0" d="M285.4,14.9c-3.4,0-5.6,2.3-5.6,5.3v11.1h-3.2V12h3.2v2.9c0.7-1.6,2.5-3.1,5.7-3.1V14.9z"/>
<path class="st0" d="M294.7,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H294.7z M294.7,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C297.4,14.5,294.9,16.5,294.7,20.3z"/>
<path class="st0" d="M333.1,31.3v-3.1c-1.1,2-3.6,3.5-6.8,3.5c-5.3,0-9.3-3.8-9.3-10c0-6.2,4-10,9.3-10c3.2,0,5.6,1.4,6.6,3.2V2
h3.2v29.4H333.1z M320.3,21.7c0,4.6,2.8,7.2,6.5,7.2c3.6,0,6.2-2.2,6.2-6.6v-1.1c0-4.3-2.6-6.6-6.2-6.6
C323.1,14.5,320.3,17.1,320.3,21.7z"/>
<path class="st0" d="M361.8,14.9c1.1-1.9,3.4-3.2,6.7-3.2c5.3,0,9.3,3.8,9.3,10c0,6.2-4,10-9.3,10c-3.3,0-5.7-1.5-6.8-3.5v3.1
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
C364.5,14.5,361.9,16.8,361.9,21.1z"/>
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z"/>
</g>
</g>
<g id="XMLID_2369_">
<g>
<g id="XMLID_281_">
<g id="XMLID_282_">
<g>
<g id="XMLID_283_">
<g id="XMLID_287_">
<path id="XMLID_288_" class="st0" d="M64.4,127l0-24.2c25.6,0,45.5-25.4,35.7-52.3c-3.6-10-11.6-17.9-21.6-21.6
c-27-9.8-52.3,10-52.3,35.7c0,0,0,0,0,0L2,64.7C2,23.8,41.5-8,84.3,5.4c18.7,5.8,33.6,20.7,39.4,39.4
C137,87.6,105.2,127,64.4,127z"/>
</g>
<polygon id="XMLID_286_" class="st1" points="64.4,102.9 40.4,102.9 40.4,78.9 40.4,78.9 64.4,78.9 64.4,78.9 "/>
<polygon id="XMLID_285_" class="st1" points="40.3,121.5 21.8,121.5 21.8,121.5 21.8,102.9 40.4,102.9 40.4,121.5 "/>
<path id="XMLID_284_" class="st1" d="M21.9,102.9H6.3c0,0,0,0,0,0V87.4c0,0,0,0,0,0h15.5c0,0,0,0,0,0V102.9z"/>
</g>
</g>
</g>
</g>
<g id="XMLID_254_">
<path id="XMLID_278_" class="st0" d="M200.9,52.4c-5.5-3.8-12.4-5.8-20.5-5.8h-17.5v55.5h17.5c8,0,14.9-2.1,20.5-6.1
c3-2.1,5.4-5.1,7.1-8.9c1.7-3.7,2.5-8.2,2.5-13.1c0-4.9-0.8-9.3-2.5-13C206.3,57.4,203.9,54.4,200.9,52.4z M173.1,56h5.5
c6.1,0,11.1,1.2,15,3.6c4.2,2.6,6.4,7.4,6.4,14.4c0,7.2-2.2,12.3-6.4,15.1h0c-3.7,2.4-8.7,3.6-14.9,3.6h-5.6V56z"/>
<path id="XMLID_277_" class="st0" d="M222.6,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
C225.7,46.5,224.3,45.9,222.6,45.9z"/>
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1"/>
<path id="XMLID_273_" class="st0" d="M263.2,66.3c-3-2.6-6.3-4.2-9.9-4.2c-5.4,0-9.9,1.9-13.4,5.6c-3.5,3.7-5.3,8.4-5.3,14.1
c0,5.5,1.8,10.2,5.2,14c3.5,3.7,8,5.5,13.5,5.5c3.8,0,7.1-1.1,9.7-3.1V99c0,3.2-0.9,5.8-2.6,7.5c-1.7,1.7-4.1,2.6-7.1,2.6
c-4.5,0-7.4-1.8-10.9-6.5l-6.7,6.4l0.2,0.3c1.4,2,3.7,4,6.6,5.9c2.9,1.9,6.6,2.8,10.9,2.8c5.8,0,10.6-1.8,14.1-5.4
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"/>
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1"/>
<path id="XMLID_271_" class="st0" d="M286.3,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2C289.4,46.5,288,45.9,286.3,45.9
z"/>
<path id="XMLID_270_" class="st0" d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"/>
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5"/>
<path id="XMLID_268_" class="st0" d="M477.3,88.2c-1.8,2-3.6,3.7-4.9,4.6v0c-1.4,0.9-3.1,1.3-5.1,1.3c-2.9,0-5.2-1.1-7.1-3.2
c-1.9-2.2-2.8-4.9-2.8-8.3s0.9-6.1,2.8-8.2c1.9-2.2,4.2-3.2,7.1-3.2c3.2,0,6.5,2,9.4,5.4l6.5-6.2l0,0c-4.2-5.5-9.7-8.1-16.1-8.1
c-5.4,0-10.1,2-13.9,5.8c-3.8,3.9-5.7,8.8-5.7,14.6s1.9,10.7,5.7,14.6c3.8,3.9,8.5,5.9,13.9,5.9c7.1,0,12.9-3.1,16.8-8.7
L477.3,88.2z"/>
<path id="XMLID_265_" class="st0" d="M517.7,68.5c-1.4-1.9-3.3-3.5-5.7-4.7c-2.3-1.1-5.1-1.7-8.1-1.7c-5.5,0-10,2-13.4,6
c-3.3,4-4.9,8.9-4.9,14.7c0,5.9,1.8,10.8,5.4,14.6c3.6,3.7,8.4,5.6,14.2,5.6c6.6,0,12.1-2.7,16.2-8l0.2-0.3l-6.4-6.2l0,0
c-0.6,0.7-1.4,1.5-2.2,2.3c-1,0.9-1.9,1.6-2.9,2.1c-1.5,0.7-3.1,1.1-5,1.1c-2.7,0-5-0.8-6.7-2.4c-1.6-1.5-2.6-3.5-2.8-5.9h26.1
l0.1-3.6c0-2.5-0.3-5-1-7.3C520.1,72.6,519.1,70.4,517.7,68.5z M496.2,77.7c0.5-1.9,1.3-3.4,2.6-4.6c1.3-1.3,3.1-2,5.2-2
c2.4,0,4.2,0.7,5.5,2c1.2,1.2,1.8,2.8,2,4.6H496.2z"/>
<path id="XMLID_262_" class="st0" d="M555.5,66L555.5,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C560,72.2,558.5,68.5,555.5,66z M538,87.2c1.1-0.8,2.7-1.2,4.7-1.2c2.4,0,4.9,0.5,7.5,1.4
v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C536.4,89,536.9,88,538,87.2z"/>
<path id="XMLID_261_" class="st0" d="M597.9,66.7c-2.7-3.1-6.6-4.6-11.5-4.6c-3.9,0-7.1,1.1-9.4,3.3V63h-9.7v39.1h9.8V80.6
c0-3,0.7-5.3,2.1-7c1.4-1.7,3.3-2.5,5.8-2.5c2.2,0,3.9,0.7,5.2,2.2c1.3,1.5,1.9,3.6,1.9,6.2v22.7h9.8V79.5
C602,74.1,600.6,69.8,597.9,66.7z"/>
<path id="XMLID_258_" class="st0" d="M355.6,66L355.6,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C360.2,72.2,358.7,68.5,355.6,66z M338.2,87.2c1.1-0.8,2.7-1.2,4.7-1.2
c2.4,0,4.9,0.5,7.5,1.4v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C336.6,89,337.1,88,338.2,87.2z"/>
<path id="XMLID_255_" class="st0" d="M413.6,103c-15.8,0-28.6-12.8-28.6-28.6s12.8-28.6,28.6-28.6s28.6,12.8,28.6,28.6
S429.4,103,413.6,103z M413.6,55.8c-10.2,0-18.5,8.3-18.5,18.5s8.3,18.5,18.5,18.5s18.5-8.3,18.5-18.5S423.8,55.8,413.6,55.8z"
/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,323 @@
{
"nav": {
"home": "Галоўная",
"about": "Пра нас",
"contact": "Кантакты",
"licensing": "Ліцэнзія",
"allTools": "Усе інструменты",
"openMainMenu": "Адкрыць галоўнае меню",
"language": "Мова"
},
"donation": {
"message": "Падабаецца BentoPDF? Падтрымайце, каб ён заставаўся бясплатным і з адкрытым зыходным кодам!",
"button": "Ахвяраваць"
},
"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": "Назад да інструментаў",
"firstLoadNotice": "Першае адкрыццё займае крыху часу, пакуль загружаецца рухавік канвертацыі. Потым усё будзе адкрывацца імгненна."
},
"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": "Ці працуе BentoPDF на 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": "Вы выкарыстоўваеце cookies або аналітыку, каб сачыць за мной?",
"answer": "Мы дбаем пра вашу прыватнасць. BentoPDF не адсочвае персанальную інфармацыю. Мы выкарыстоўваем Simple Analytics, толькі каб бачыць ананімную статыстыку наведванняў. Гэта значыць, мы ведаем, колькі карыстальнікаў наведвае наш сайт, але ніколі не ведаем, хто вы. Simple Analytics цалкам адпавядае GDPR і шануе вашу прыватнасць."
}
},
"testimonials": {
"title": "Што кажуць",
"users": "нашы карыстальнікі",
"say": " "
},
"support": {
"title": "Спадабалася мая праца?",
"description": "BentoPDF — гэта праект, створаны на энтузіязме з мэтай, каб кожны меў бясплатны, прыватны і магутны набор інструментаў PDF. Калі ён прыносіць вам карысць, падтрымайце распрацоўку. Дапамагае кожная кава!",
"buyMeCoffee": "Пачастуйце мяне кавай"
},
"footer": {
"copyright": "© 2026 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": "Вы можаце напісаць нам на 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": "Не ўдалося загрузіць"
}
}

View File

@@ -0,0 +1,533 @@
{
"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.",
"algorithmLabel": "Алгарытм сціскання",
"condense": "Condense (Рэкамендуецца)",
"photon": "Photon (Для PDF з вялікай колькасцю фота)",
"condenseInfo": "Condense выкарыстоўвае прасунутае сцісканне: выдаляе лішняе, аптымізуе відарысы, паднаборы шрыфтоў. Найлепш пасуе для большасці PDF.",
"photonInfo": "Photon ператварае старонкі ў відарысы. Для PDF з вялікай колькасцю фота або сканіраваных PDF.",
"photonWarning": "Папярэджанне: стане немагчыма вылучыць тэкст, і перастануць працаваць спасылкі.",
"levelLabel": "Узровень сціскання",
"light": "Лёгкі (Захаванне якасці)",
"balanced": "Збалансаваны (Рэкамендуецца)",
"aggressive": "Агрэсіўны (Меншыя файлы)",
"extreme": "Экстрэмальны (Максімальнае сцісканне)",
"grayscale": "Канвертаваць у градацыі шэрага",
"grayscaleHint": "Памяншае памер файла, выдаляючы інфармацыю пра колер",
"customSettings": "Карыстальніцкія налады",
"customSettingsHint": "Дакладная настройка параметраў сціскання:",
"outputQuality": "Якасць вываду",
"resizeImagesTo": "Змяніць памер відарысаў да",
"onlyProcessAbove": "Апрацоўваць толькі большыя за",
"removeMetadata": "Выдаліць метаданыя",
"subsetFonts": "Паднабор шрыфтоў (выдаліць сімвалы, якія не выкарыстоўваюцца)",
"removeThumbnails": "Выдаліць убудаваныя мініяцюры",
"compressButton": "Сціснуць PDF"
},
"pdfEditor": {
"name": "Рэдактар PDF",
"subtitle": "Анатаваць, вылучыць, зацямніць, дадаць фігуры/відарысы, каментарыі, пошук і прагляд PDF."
},
"jpgToPdf": {
"name": "JPG у PDF",
"subtitle": "Стварыць PDF з відарысаў JPG, JPEG і JPEG2000 (JP2/JPX)."
},
"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 штампы-відарысы праз панэль анатацый.",
"usernameLabel": "Імя для штампа",
"usernamePlaceholder": "Увядзіце сваё імя (для штампаў)",
"usernameHint": "Гэта імя з'явіцца на створаных вамі штампах."
},
"removeAnnotations": {
"name": "Выдаліць анатацыі",
"subtitle": "Выдаліць каментарыі, вылучэнні і спасылкі."
},
"pdfFormFiller": {
"name": "Запоўніць форму PDF",
"subtitle": "Запоўніць форму непасрэдна ў браўзеры. Таксама падтрымліваюцца формы XFA."
},
"createPdfForm": {
"name": "Стварыць форму PDF",
"subtitle": "Стварыць запаўняльныя формы PDF з тэкставымі палямі, якія можна перацягваць."
},
"removeBlankPages": {
"name": "Выдаліць пустыя старонкі",
"subtitle": "Аўтаматычна выявіць і выдаліць пустыя старонкі."
},
"imageToPdf": {
"name": "Відарысы ў PDF",
"subtitle": "Канвертаваць JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP у PDF."
},
"pngToPdf": {
"name": "PNG у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў PNG."
},
"webpToPdf": {
"name": "WebP у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў WebP."
},
"svgToPdf": {
"name": "SVG у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў SVG."
},
"bmpToPdf": {
"name": "BMP у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў BMP."
},
"heicToPdf": {
"name": "HEIC у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў HEIC."
},
"tiffToPdf": {
"name": "TIFF у PDF",
"subtitle": "Стварыць PDF з аднаго або некалькіх відарысаў TIFF."
},
"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 градусаў."
},
"rotateCustom": {
"name": "Павярнуць на зададзены вугал",
"subtitle": "Павярнуць старонкі на любы вугал."
},
"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": "Пераўтварыць у постэр",
"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."
},
"odtToPdf": {
"name": "ODT у PDF",
"subtitle": "Канвертаваць файлы OpenDocument Text у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы ODT",
"convertButton": "Канвертаваць у PDF"
},
"csvToPdf": {
"name": "CSV у PDF",
"subtitle": "Канвертаваць табліцы CSV у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы CSV",
"convertButton": "Канвертаваць у PDF"
},
"rtfToPdf": {
"name": "RTF у PDF",
"subtitle": "Канвертаваць дакументы Rich Text Format у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы RTF",
"convertButton": "Канвертаваць у PDF"
},
"wordToPdf": {
"name": "Word у PDF",
"subtitle": "Канвертаваць дакументы Word (DOCX, DOC, ODT, RTF) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы DOCX, DOC, ODT, RTF",
"convertButton": "Канвертаваць у PDF"
},
"excelToPdf": {
"name": "Excel у PDF",
"subtitle": "Канвертаваць табліцы Excel (XLSX, XLS, ODS, CSV) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы XLSX, XLS, ODS, CSV",
"convertButton": "Канвертаваць у PDF"
},
"powerpointToPdf": {
"name": "PowerPoint у PDF",
"subtitle": "Канвертаваць прэзентацыі PowerPoint (PPTX, PPT, ODP) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы PPTX, PPT, ODP",
"convertButton": "Канвертаваць у PDF"
},
"markdownToPdf": {
"name": "Markdown у PDF",
"subtitle": "Напісаць або ўставіць Markdown і экспартаваць яго ў прыгожа аформлены PDF.",
"paneMarkdown": "Markdown",
"panePreview": "Папярэдні прагляд",
"btnUpload": "Запампаваць",
"btnSyncScroll": "Сінхранізацыя пракруткі",
"btnSettings": "Налады",
"btnExportPdf": "Экспартаваць PDF",
"settingsTitle": "Налады Markdown",
"settingsPreset": "Набор налад",
"presetDefault": "Прадвызначаны (як GFM)",
"presetCommonmark": "CommonMark (строгі)",
"presetZero": "Мінімальны (без функцый)",
"settingsOptions": "Параметры Markdown",
"optAllowHtml": "Дазволіць HTML-тэгі",
"optBreaks": "Пераўтвараць пераносы радкоў у <br>",
"optLinkify": "Аўтаматычна пераўтвараць URL-адрасы ў спасылкі",
"optTypographer": "Тыпограф (разумныя двукоссі і інш.)"
},
"pdfBooklet": {
"name": "Буклет PDF",
"subtitle": "Змяніць парадак старонак для друку двухбаковага буклета. Складзіце і счапіце аркушы, каб атрымаўся буклет.",
"howItWorks": "Як гэта працуе:",
"step1": "Запампуйце файл PDF.",
"step2": "Парадак старонак будзе зменены для буклета.",
"step3": "Надрукуйце з двух бакоў, перавярніце па кароткім краі, складзіце і сшыйце.",
"paperSize": "Памер паперы",
"orientation": "Арыентацыя",
"portrait": "Кніжная",
"landscape": "Альбомная",
"pagesPerSheet": "Старонак на аркуш",
"createBooklet": "Стварыць буклет",
"processing": "Апрацоўка...",
"pageCount": "Колькасць старонак будзе дапоўнена да кратнай 4 пры неабходнасці."
},
"xpsToPdf": {
"name": "XPS у PDF",
"subtitle": "Канвертаваць дакументы XPS/OXPS у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы XPS, OXPS",
"convertButton": "Канвертаваць у PDF"
},
"mobiToPdf": {
"name": "MOBI у PDF",
"subtitle": "Канвертаваць электронныя кнігі MOBI у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы MOBI",
"convertButton": "Канвертаваць у PDF"
},
"epubToPdf": {
"name": "EPUB у PDF",
"subtitle": "Канвертаваць электронныя кнігі EPUB у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы EPUB",
"convertButton": "Канвертаваць у PDF"
},
"fb2ToPdf": {
"name": "FB2 у PDF",
"subtitle": "Канвертаваць электронныя кнігі FictionBook (FB2) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы FB2",
"convertButton": "Канвертаваць у PDF"
},
"cbzToPdf": {
"name": "CBZ у PDF",
"subtitle": "Канвертаваць архівы коміксаў (CBZ/CBR) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы CBZ, CBR",
"convertButton": "Канвертаваць у PDF"
},
"wpdToPdf": {
"name": "WPD у PDF",
"subtitle": "Канвертаваць дакументы WordPerfect (WPD) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы WPD",
"convertButton": "Канвертаваць у PDF"
},
"wpsToPdf": {
"name": "WPS у PDF",
"subtitle": "Канвертаваць дакументы WPS Office у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы WPS",
"convertButton": "Канвертаваць у PDF"
},
"xmlToPdf": {
"name": "XML у PDF",
"subtitle": "Канвертаваць дакументы XML у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы XML",
"convertButton": "Канвертаваць у PDF"
},
"pagesToPdf": {
"name": "Pages у PDF",
"subtitle": "Канвертаваць дакументы Apple Pages у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы Pages",
"convertButton": "Канвертаваць у PDF"
},
"odgToPdf": {
"name": "ODG у PDF",
"subtitle": "Канвертаваць файлы OpenDocument Graphics (ODG) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы ODG",
"convertButton": "Канвертаваць у PDF"
},
"odsToPdf": {
"name": "ODS у PDF",
"subtitle": "Канвертаваць файлы OpenDocument Spreadsheet (ODS) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы ODS",
"convertButton": "Канвертаваць у PDF"
},
"odpToPdf": {
"name": "ODP у PDF",
"subtitle": "Канвертаваць файлы OpenDocument Presentation (ODP) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы ODP",
"convertButton": "Канвертаваць у PDF"
},
"pubToPdf": {
"name": "PUB у PDF",
"subtitle": "Канвертаваць файлы Microsoft Publisher (PUB) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы PUB",
"convertButton": "Канвертаваць у PDF"
},
"vsdToPdf": {
"name": "VSD у PDF",
"subtitle": "Канвертаваць файлы Microsoft Visio (VSD, VSDX) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы VSD, VSDX",
"convertButton": "Канвертаваць у PDF"
},
"psdToPdf": {
"name": "PSD у PDF",
"subtitle": "Канвертаваць файлы Adobe Photoshop (PSD) у PDF. Падтрымлівае некалькі файлаў.",
"acceptedFormats": "файлы PSD",
"convertButton": "Канвертаваць у PDF"
},
"pdfToSvg": {
"name": "PDF у SVG",
"subtitle": "Канвертаваць кожную старонку PDF у маштабаваную вектарную графіку (SVG) для ідэальнай якасці на любым памеры."
},
"extractTables": {
"name": "Выняць табліцы з PDF",
"subtitle": "Выняць табліцы з PDF і экспартаваць у CSV, JSON або Markdown."
},
"pdfToCsv": {
"name": "PDF у CSV",
"subtitle": "Выняць табліцы з PDF і канвертаваць у CSV."
},
"pdfToExcel": {
"name": "PDF у Excel",
"subtitle": "Выняць табліцы з PDF і канвертаваць у Excel (XLSX)."
},
"pdfToText": {
"name": "PDF у тэкст",
"subtitle": "Выняць тэкст з файлаў PDF і захаваць як тэкставы файл (.txt). Падтрымлівае некалькі файлаў.",
"note": "Гэты інструмент працуе ТОЛЬКІ з PDF, створанымі лічбавым спосабам. Для сканіраваных дакументаў або PDF на аснове відарысаў выкарыстоўвайце інструмент OCR PDF.",
"convertButton": "Выняць тэкст"
},
"digitalSignPdf": {
"name": "Лічбавы подпіс PDF",
"pageTitle": "Лічбавы подпіс PDF - Дадаць крыптаграфічны подпіс | BentoPDF",
"subtitle": "Дадаць крыптаграфічны лічбавы подпіс да PDF з выкарыстаннем сертыфікатаў X.509. Падтрымлівае фарматы PKCS#12 (.pfx, .p12) і PEM. Ваш прыватны ключ ніколі не пакідае ваш браўзер.",
"certificateSection": "Сертыфікат",
"uploadCert": "Запампаваць сертыфікат (.pfx, .p12)",
"certPassword": "Пароль сертыфіката",
"certPasswordPlaceholder": "Увядзіце пароль сертыфіката",
"certInfo": "Інфармацыя пра сертыфікат",
"certSubject": "Суб'ект",
"certIssuer": "Выдавец",
"certValidity": "Дзейсны",
"signatureDetails": "Дэталі подпісу (неабавязкова)",
"reason": "Прычына",
"reasonPlaceholder": "напрыклад, я ўхваляю гэты дакумент",
"location": "Месцазнаходжанне",
"locationPlaceholder": "напрыклад, Мінск, Беларусь",
"contactInfo": "Кантактная інфармацыя",
"contactPlaceholder": "напрыклад, email@example.com",
"applySignature": "Ужыць лічбавы подпіс",
"successMessage": "PDF паспяхова падпісаны! Подпіс можна праверыць у любым PDF-чытальніку."
},
"validateSignaturePdf": {
"name": "Праверыць подпіс PDF",
"pageTitle": "Праверыць подпіс PDF - Верыфікацыя лічбавых подпісаў | BentoPDF",
"subtitle": "Праверыць лічбавыя подпісы ў PDF. Праверыць сапраўднасць сертыфіката, паглядзець дэталі падпісанта і пацвердзіць надзейнасць дакумента. Уся апрацоўка адбываецца ў вашым браўзеры."
},
"emailToPdf": {
"name": "Email у PDF",
"subtitle": "Канвертаваць файлы email (EML, MSG) у PDF. Падтрымлівае экспарты Outlook і стандартныя email-фарматы.",
"acceptedFormats": "файлы EML, MSG",
"convertButton": "Канвертаваць у PDF"
},
"fontToOutline": {
"name": "Шрыфт у контуры",
"subtitle": "Ператварыць усе шрыфты ў вектарныя контуры для стабільнага адлюстравання на ўсіх прыладах."
},
"deskewPdf": {
"name": "Выпрастаць PDF",
"subtitle": "Аўтаматычна выраўняць нахіленыя адсканіраваныя старонкі з дапамогай OpenCV."
}
}

View File

@@ -8,6 +8,10 @@
"openMainMenu": "Hauptmenü öffnen", "openMainMenu": "Hauptmenü öffnen",
"language": "Sprache" "language": "Sprache"
}, },
"donation": {
"message": "Lieben Sie BentoPDF? Helfen Sie uns, es kostenlos und Open Source zu halten!",
"button": "Spenden"
},
"hero": { "hero": {
"title": "Das", "title": "Das",
"pdfToolkit": "PDF-Toolkit", "pdfToolkit": "PDF-Toolkit",
@@ -62,7 +66,8 @@
"pdfOrImages": "PDFs oder Bilder", "pdfOrImages": "PDFs oder Bilder",
"filesNeverLeave": "Ihre Dateien verlassen nie Ihr Gerät.", "filesNeverLeave": "Ihre Dateien verlassen nie Ihr Gerät.",
"addMore": "Weitere Dateien hinzufügen", "addMore": "Weitere Dateien hinzufügen",
"clearAll": "Alle löschen" "clearAll": "Alle löschen",
"clearFiles": "Dateien löschen"
}, },
"loader": { "loader": {
"processing": "Verarbeitung..." "processing": "Verarbeitung..."
@@ -179,7 +184,7 @@
"buyMeCoffee": "Kauf mir einen Kaffee" "buyMeCoffee": "Kauf mir einen Kaffee"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. Alle Rechte vorbehalten.", "copyright": "© 2026 BentoPDF. Alle Rechte vorbehalten.",
"version": "Version", "version": "Version",
"company": "Unternehmen", "company": "Unternehmen",
"aboutUs": "Über uns", "aboutUs": "Über uns",
@@ -222,7 +227,8 @@
"error": "Fehler", "error": "Fehler",
"success": "Erfolg", "success": "Erfolg",
"file": "Datei", "file": "Datei",
"files": "Dateien" "files": "Dateien",
"close": "Schließen"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -521,5 +521,13 @@
"subtitle": "E-Mail-Dateien (EML, MSG) in PDF-Format konvertieren. Unterstützt Outlook-Exporte und Standard-E-Mail-Formate.", "subtitle": "E-Mail-Dateien (EML, MSG) in PDF-Format konvertieren. Unterstützt Outlook-Exporte und Standard-E-Mail-Formate.",
"acceptedFormats": "EML, MSG-Dateien", "acceptedFormats": "EML, MSG-Dateien",
"convertButton": "In PDF konvertieren" "convertButton": "In PDF konvertieren"
},
"fontToOutline": {
"name": "Schriftart zu Umriss",
"subtitle": "Alle Schriftarten in Vektorumrisse für konsistente Darstellung auf allen Geräten konvertieren."
},
"deskewPdf": {
"name": "PDF entzerren",
"subtitle": "Automatisch schiefe gescannte Seiten mit OpenCV begradigen."
} }
} }

View File

@@ -66,7 +66,8 @@
"pdfOrImages": "PDFs or Images", "pdfOrImages": "PDFs or Images",
"filesNeverLeave": "Your files never leave your device.", "filesNeverLeave": "Your files never leave your device.",
"addMore": "Add More Files", "addMore": "Add More Files",
"clearAll": "Clear All" "clearAll": "Clear All",
"clearFiles": "Clear Files"
}, },
"loader": { "loader": {
"processing": "Processing..." "processing": "Processing..."
@@ -183,7 +184,7 @@
"buyMeCoffee": "Buy Me a Coffee" "buyMeCoffee": "Buy Me a Coffee"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. All rights reserved.", "copyright": "© 2026 BentoPDF. All rights reserved.",
"version": "Version", "version": "Version",
"company": "Company", "company": "Company",
"aboutUs": "About Us", "aboutUs": "About Us",
@@ -226,7 +227,8 @@
"error": "Error", "error": "Error",
"success": "Success", "success": "Success",
"file": "File", "file": "File",
"files": "Files" "files": "Files",
"close": "Close"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -521,5 +521,13 @@
"subtitle": "Convert email files (EML, MSG) to PDF format. Supports Outlook exports and standard email formats.", "subtitle": "Convert email files (EML, MSG) to PDF format. Supports Outlook exports and standard email formats.",
"acceptedFormats": "EML, MSG files", "acceptedFormats": "EML, MSG files",
"convertButton": "Convert to PDF" "convertButton": "Convert to PDF"
},
"fontToOutline": {
"name": "Font to Outline",
"subtitle": "Convert all fonts to vector outlines for consistent rendering across all devices."
},
"deskewPdf": {
"name": "Deskew PDF",
"subtitle": "Automatically straighten tilted scanned pages using OpenCV."
} }
} }

View File

@@ -0,0 +1,325 @@
{
"nav": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto",
"licensing": "Licencias",
"allTools": "Todas las Herramientas",
"openMainMenu": "Abrir menú principal",
"language": "Idioma"
},
"donation": {
"message": "¿Te encanta BentoPDF? ¡Ayúdanos a mantenerlo gratis y de código abierto!",
"button": "Donar"
},
"hero": {
"title": "El",
"pdfToolkit": "Kit de Herramientas PDF",
"builtForPrivacy": "diseñado para la privacidad",
"noSignups": "Sin Registro",
"unlimitedUse": "Uso Ilimitado",
"worksOffline": "Funciona Sin Conexión",
"startUsing": "Comenzar a Usar Ahora"
},
"usedBy": {
"title": "Usado por empresas y personas que trabajan en"
},
"features": {
"title": "¿Por qué elegir",
"bentoPdf": "BentoPDF?",
"noSignup": {
"title": "Sin Registro",
"description": "Comienza al instante, sin cuentas ni correos electrónicos."
},
"noUploads": {
"title": "Sin Cargas",
"description": "100% del lado del cliente, tus archivos nunca salen de tu dispositivo."
},
"foreverFree": {
"title": "Gratis para Siempre",
"description": "Todas las herramientas, sin pruebas, sin restricciones de pago."
},
"noLimits": {
"title": "Sin Límites",
"description": "Usa tanto como quieras, sin límites ocultos."
},
"batchProcessing": {
"title": "Procesamiento por Lotes",
"description": "Maneja PDFs ilimitados de una sola vez."
},
"lightningFast": {
"title": "Ultrarrápido",
"description": "Procesa PDFs al instante, sin esperas ni retrasos."
}
},
"tools": {
"title": "Comienza con",
"toolsLabel": "Herramientas",
"subtitle": "Haz clic en una herramienta para abrir el cargador de archivos",
"searchPlaceholder": "Buscar una herramienta (ej., 'dividir', 'organizar'...)",
"backToTools": "Volver a Herramientas",
"firstLoadNotice": "La primera carga toma un momento mientras descargamos nuestro motor de conversión. Después de eso, todas las cargas serán instantáneas."
},
"upload": {
"clickToSelect": "Haz clic para seleccionar un archivo",
"orDragAndDrop": "o arrastra y suelta",
"pdfOrImages": "PDFs o Imágenes",
"filesNeverLeave": "Tus archivos nunca salen de tu dispositivo.",
"addMore": "Agregar Más Archivos",
"clearAll": "Limpiar Todo",
"clearFiles": "Borrar archivos"
},
"loader": {
"processing": "Procesando..."
},
"alert": {
"title": "Alerta",
"ok": "OK"
},
"preview": {
"title": "Vista Previa del Documento",
"downloadAsPdf": "Descargar como PDF",
"close": "Cerrar"
},
"settings": {
"title": "Configuración",
"shortcuts": "Atajos",
"preferences": "Preferencias",
"displayPreferences": "Preferencias de Visualización",
"searchShortcuts": "Buscar atajos...",
"shortcutsInfo": "Mantén presionadas las teclas para establecer un atajo. Los cambios se guardan automáticamente.",
"shortcutsWarning": "⚠️ Evita los atajos comunes del navegador (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, etc.) ya que pueden no funcionar de manera confiable.",
"import": "Importar",
"export": "Exportar",
"resetToDefaults": "Restaurar Valores Predeterminados",
"fullWidthMode": "Modo de Ancho Completo",
"fullWidthDescription": "Usa el ancho completo de la pantalla para todas las herramientas en lugar de un contenedor centrado",
"settingsAutoSaved": "La configuración se guarda automáticamente",
"clickToSet": "Haz clic para establecer",
"pressKeys": "Presiona teclas...",
"warnings": {
"alreadyInUse": "Atajo Ya en Uso",
"assignedTo": "ya está asignado a:",
"chooseDifferent": "Por favor elige un atajo diferente.",
"reserved": "Advertencia de Atajo Reservado",
"commonlyUsed": "se usa comúnmente para:",
"unreliable": "Este atajo puede no funcionar de manera confiable o puede entrar en conflicto con el comportamiento del navegador/sistema.",
"useAnyway": "¿Quieres usarlo de todos modos?",
"resetTitle": "Restablecer Atajos",
"resetMessage": "¿Estás seguro de que quieres restablecer todos los atajos a los valores predeterminados?<br><br>Esta acción no se puede deshacer.",
"importSuccessTitle": "Importación Exitosa",
"importSuccessMessage": "¡Atajos importados exitosamente!",
"importFailTitle": "Importación Fallida",
"importFailMessage": "Error al importar atajos. Formato de archivo inválido."
}
},
"warning": {
"title": "Advertencia",
"cancel": "Cancelar",
"proceed": "Continuar"
},
"compliance": {
"title": "Tus datos nunca salen de tu dispositivo",
"weKeep": "Mantenemos",
"yourInfoSafe": "tu información segura",
"byFollowingStandards": "siguiendo estándares de seguridad globales.",
"processingLocal": "Todo el procesamiento ocurre localmente en tu dispositivo.",
"gdpr": {
"title": "Cumplimiento GDPR",
"description": "Protege los datos personales y la privacidad de las personas dentro de la Unión Europea."
},
"ccpa": {
"title": "Cumplimiento CCPA",
"description": "Otorga a los residentes de California derechos sobre cómo se recopila, usa y comparte su información personal."
},
"hipaa": {
"title": "Cumplimiento HIPAA",
"description": "Establece salvaguardas para el manejo de información de salud sensible en el sistema de atención médica de Estados Unidos."
}
},
"faq": {
"title": "Preguntas",
"questions": "Frecuentes",
"isFree": {
"question": "¿BentoPDF es realmente gratis?",
"answer": "Sí, absolutamente. Todas las herramientas en BentoPDF son 100% gratuitas, sin límites de archivos, sin registro y sin marcas de agua. Creemos que todos merecen acceso a herramientas PDF simples y potentes sin un muro de pago."
},
"areFilesSecure": {
"question": "¿Mis archivos están seguros? ¿Dónde se procesan?",
"answer": "Tus archivos están lo más seguros posible porque nunca salen de tu computadora. Todo el procesamiento ocurre directamente en tu navegador web (del lado del cliente). Nunca cargamos tus archivos a un servidor, por lo que mantienes total privacidad y control sobre tus documentos."
},
"platforms": {
"question": "¿Funciona en Mac, Windows y Móvil?",
"answer": "¡Sí! Dado que BentoPDF se ejecuta completamente en tu navegador, funciona en cualquier sistema operativo con un navegador web moderno, incluyendo Windows, macOS, Linux, iOS y Android."
},
"gdprCompliant": {
"question": "¿BentoPDF cumple con GDPR?",
"answer": "Sí. BentoPDF cumple completamente con GDPR. Dado que todo el procesamiento de archivos ocurre localmente en tu navegador y nunca recopilamos ni transmitimos tus archivos a ningún servidor, no tenemos acceso a tus datos. Esto garantiza que siempre tengas el control de tus documentos."
},
"dataStorage": {
"question": "¿Almacenan o rastrean alguno de mis archivos?",
"answer": "No. Nunca almacenamos, rastreamos ni registramos tus archivos. Todo lo que haces en BentoPDF ocurre en la memoria de tu navegador y desaparece una vez que cierras la página. No hay cargas, no hay registros de historial y no hay servidores involucrados."
},
"different": {
"question": "¿Qué hace que BentoPDF sea diferente de otras herramientas PDF?",
"answer": "La mayoría de las herramientas PDF cargan tus archivos a un servidor para procesarlos. BentoPDF nunca hace eso. Utilizamos tecnología web moderna y segura para procesar tus archivos directamente en tu navegador. Esto significa un rendimiento más rápido, mayor privacidad y total tranquilidad."
},
"browserBased": {
"question": "¿Cómo me mantiene seguro el procesamiento basado en navegador?",
"answer": "Al ejecutarse completamente dentro de tu navegador, BentoPDF garantiza que tus archivos nunca salgan de tu dispositivo. Esto elimina los riesgos de hackeos de servidores, violaciones de datos o accesos no autorizados. Tus archivos siguen siendo tuyos, siempre."
},
"analytics": {
"question": "¿Usan cookies o análisis para rastrearme?",
"answer": "Nos preocupamos por tu privacidad. BentoPDF no rastrea información personal. Usamos Simple Analytics únicamente para ver recuentos de visitas anónimas. Esto significa que podemos saber cuántos usuarios visitan nuestro sitio, pero nunca sabemos quién eres. Simple Analytics cumple completamente con GDPR y respeta tu privacidad."
}
},
"testimonials": {
"title": "Lo que Nuestros",
"users": "Usuarios",
"say": "Dicen"
},
"support": {
"title": "¿Te Gusta Mi Trabajo?",
"description": "BentoPDF es un proyecto de pasión, creado para proporcionar un kit de herramientas PDF gratuito, privado y potente para todos. Si te resulta útil, considera apoyar su desarrollo. ¡Cada café ayuda!",
"buyMeCoffee": "Cómprame un Café"
},
"footer": {
"copyright": "© 2025 BentoPDF. Todos los derechos reservados.",
"version": "Versión",
"company": "Empresa",
"aboutUs": "Acerca de Nosotros",
"faqLink": "Preguntas Frecuentes",
"contactUs": "Contáctanos",
"legal": "Legal",
"termsAndConditions": "Términos y Condiciones",
"privacyPolicy": "Política de Privacidad",
"followUs": "Síguenos"
},
"merge": {
"title": "Fusionar PDFs",
"description": "Combina archivos completos o selecciona páginas específicas para fusionar en un nuevo documento.",
"fileMode": "Modo Archivo",
"pageMode": "Modo Página",
"howItWorks": "Cómo funciona:",
"fileModeInstructions": [
"Haz clic y arrastra el ícono para cambiar el orden de los archivos.",
"En el cuadro \"Páginas\" para cada archivo, puedes especificar rangos (ej., \"1-3, 5\") para fusionar solo esas páginas.",
"Deja el cuadro \"Páginas\" en blanco para incluir todas las páginas de ese archivo."
],
"pageModeInstructions": [
"Todas las páginas de tus PDFs cargados se muestran a continuación.",
"Simplemente arrastra y suelta las miniaturas de páginas individuales para crear el orden exacto que deseas para tu nuevo archivo."
],
"mergePdfs": "Fusionar PDFs"
},
"common": {
"page": "Página",
"pages": "Páginas",
"of": "de",
"download": "Descargar",
"cancel": "Cancelar",
"save": "Guardar",
"delete": "Eliminar",
"edit": "Editar",
"add": "Agregar",
"remove": "Remover",
"loading": "Cargando...",
"error": "Error",
"success": "Éxito",
"file": "Archivo",
"files": "Archivos",
"close": "Cerrar"
},
"about": {
"hero": {
"title": "Creemos que las herramientas PDF deben ser",
"subtitle": "rápidas, privadas y gratuitas.",
"noCompromises": "Sin compromisos."
},
"mission": {
"title": "Nuestra Misión",
"description": "Proporcionar la caja de herramientas PDF más completa que respete tu privacidad y nunca pida pago. Creemos que las herramientas de documentos esenciales deben ser accesibles para todos, en todas partes, sin barreras."
},
"philosophy": {
"label": "Nuestra Filosofía Central",
"title": "Privacidad Primero. Siempre.",
"description": "En una era donde los datos son una mercancía, adoptamos un enfoque diferente. Todo el procesamiento de las herramientas de Bentopdf ocurre localmente en tu navegador. Esto significa que tus archivos nunca tocan nuestros servidores, nunca vemos tus documentos y no rastreamos lo que haces. Tus documentos permanecen completa e inequívocamente privados. No es solo una característica; es nuestra base."
},
"whyBentopdf": {
"title": "Por qué",
"speed": {
"title": "Diseñado para la Velocidad",
"description": "Sin esperar cargas o descargas a un servidor. Al procesar archivos directamente en tu navegador usando tecnologías web modernas como WebAssembly, ofrecemos una velocidad incomparable para todas nuestras herramientas."
},
"free": {
"title": "Completamente Gratis",
"description": "Sin pruebas, sin suscripciones, sin tarifas ocultas y sin funciones \"premium\" retenidas como rehenes. Creemos que las herramientas PDF potentes deben ser una utilidad pública, no un centro de ganancias."
},
"noAccount": {
"title": "No Requiere Cuenta",
"description": "Comienza a usar cualquier herramienta de inmediato. No necesitamos tu correo electrónico, una contraseña o cualquier información personal. Tu flujo de trabajo debe ser sin fricciones y anónimo."
},
"openSource": {
"title": "Espíritu de Código Abierto",
"description": "Construido con transparencia en mente. Aprovechamos increíbles bibliotecas de código abierto como PDF-lib y PDF.js, y creemos en el esfuerzo impulsado por la comunidad para hacer que las herramientas potentes sean accesibles para todos."
}
},
"cta": {
"title": "¿Listo para comenzar?",
"description": "Únete a miles de usuarios que confían en Bentopdf para sus necesidades diarias de documentos. Experimenta la diferencia que la privacidad y el rendimiento pueden hacer.",
"button": "Explorar Todas las Herramientas"
}
},
"contact": {
"title": "Ponte en Contacto",
"subtitle": "Nos encantaría saber de ti. Ya sea que tengas una pregunta, comentario o solicitud de función, no dudes en comunicarte.",
"email": "Puedes contactarnos directamente por correo electrónico en:"
},
"licensing": {
"title": "Licencias para",
"subtitle": "Elige la licencia que se ajuste a tus necesidades."
},
"multiTool": {
"uploadPdfs": "Cargar PDFs",
"upload": "Cargar",
"addBlankPage": "Agregar Página en Blanco",
"edit": "Editar:",
"undo": "Deshacer",
"redo": "Rehacer",
"reset": "Restablecer",
"selection": "Selección:",
"selectAll": "Seleccionar Todo",
"deselectAll": "Deseleccionar Todo",
"rotate": "Rotar:",
"rotateLeft": "Izquierda",
"rotateRight": "Derecha",
"transform": "Transformar:",
"duplicate": "Duplicar",
"split": "Dividir",
"clear": "Limpiar:",
"delete": "Eliminar",
"download": "Descargar:",
"downloadSelected": "Descargar Seleccionados",
"exportPdf": "Exportar PDF",
"uploadPdfFiles": "Seleccionar Archivos PDF",
"dragAndDrop": "Arrastra y suelta archivos PDF aquí, o haz clic para seleccionar",
"selectFiles": "Seleccionar Archivos",
"renderingPages": "Renderizando páginas...",
"actions": {
"duplicatePage": "Duplicar esta página",
"deletePage": "Eliminar esta página",
"insertPdf": "Insertar PDF después de esta página",
"toggleSplit": "Alternar división después de esta página"
},
"pleaseWait": "Por Favor Espera",
"pagesRendering": "Las páginas aún se están renderizando. Por favor espera...",
"noPagesSelected": "No Se Seleccionaron Páginas",
"selectOnePage": "Por favor selecciona al menos una página para descargar.",
"noPages": "Sin Páginas",
"noPagesToExport": "No hay páginas para exportar.",
"renderingTitle": "Renderizando vistas previas de páginas",
"errorRendering": "Error al renderizar miniaturas de páginas",
"error": "Error",
"failedToLoad": "Error al cargar"
}
}

View File

@@ -0,0 +1,533 @@
{
"categories": {
"popularTools": "Herramientas Populares",
"editAnnotate": "Editar y Anotar",
"convertToPdf": "Convertir a PDF",
"convertFromPdf": "Convertir desde PDF",
"organizeManage": "Organizar y Gestionar",
"optimizeRepair": "Optimizar y Reparar",
"securePdf": "Asegurar PDF"
},
"pdfMultiTool": {
"name": "Multiherramienta PDF",
"subtitle": "Fusionar, Dividir, Organizar, Eliminar, Rotar, Agregar Páginas en Blanco, Extraer y Duplicar en una interfaz unificada."
},
"mergePdf": {
"name": "Fusionar PDF",
"subtitle": "Combina múltiples PDFs en un solo archivo. Preserva Marcadores."
},
"splitPdf": {
"name": "Dividir PDF",
"subtitle": "Extrae un rango de páginas en un nuevo PDF."
},
"compressPdf": {
"name": "Comprimir PDF",
"subtitle": "Reduce el tamaño de archivo de tu PDF.",
"algorithmLabel": "Algoritmo de Compresión",
"condense": "Condensar (Recomendado)",
"photon": "Photon (Para PDFs con Muchas Fotos)",
"condenseInfo": "Condensar usa compresión avanzada: elimina peso muerto, optimiza imágenes, reduce fuentes. Mejor para la mayoría de PDFs.",
"photonInfo": "Photon convierte páginas en imágenes. Úsalo para PDFs con muchas fotos/escaneados.",
"photonWarning": "Advertencia: El texto dejará de ser seleccionable y los enlaces dejarán de funcionar.",
"levelLabel": "Nivel de Compresión",
"light": "Ligero (Preservar Calidad)",
"balanced": "Equilibrado (Recomendado)",
"aggressive": "Agresivo (Archivos Más Pequeños)",
"extreme": "Extremo (Compresión Máxima)",
"grayscale": "Convertir a Escala de Grises",
"grayscaleHint": "Reduce el tamaño del archivo eliminando información de color",
"customSettings": "Configuración Personalizada",
"customSettingsHint": "Ajusta los parámetros de compresión:",
"outputQuality": "Calidad de Salida",
"resizeImagesTo": "Redimensionar Imágenes a",
"onlyProcessAbove": "Solo Procesar Arriba de",
"removeMetadata": "Eliminar metadatos",
"subsetFonts": "Reducir fuentes (eliminar glifos no usados)",
"removeThumbnails": "Eliminar miniaturas incrustadas",
"compressButton": "Comprimir PDF"
},
"pdfEditor": {
"name": "Editor PDF",
"subtitle": "Anotar, resaltar, redactar, comentar, agregar formas/imágenes, buscar y ver PDFs."
},
"jpgToPdf": {
"name": "JPG a PDF",
"subtitle": "Crea un PDF desde imágenes JPG, JPEG y JPEG2000 (JP2/JPX)."
},
"signPdf": {
"name": "Firmar PDF",
"subtitle": "Dibuja, escribe o carga tu firma."
},
"cropPdf": {
"name": "Recortar PDF",
"subtitle": "Recorta los márgenes de cada página en tu PDF."
},
"extractPages": {
"name": "Extraer Páginas",
"subtitle": "Guarda una selección de páginas como nuevos archivos."
},
"duplicateOrganize": {
"name": "Duplicar y Organizar",
"subtitle": "Duplica, reordena y elimina páginas."
},
"deletePages": {
"name": "Eliminar Páginas",
"subtitle": "Elimina páginas específicas de tu documento."
},
"editBookmarks": {
"name": "Editar Marcadores",
"subtitle": "Agrega, edita, importa, elimina y extrae marcadores PDF."
},
"tableOfContents": {
"name": "Tabla de Contenidos",
"subtitle": "Genera una página de tabla de contenidos desde los marcadores PDF."
},
"pageNumbers": {
"name": "Números de Página",
"subtitle": "Inserta números de página en tu documento."
},
"addWatermark": {
"name": "Agregar Marca de Agua",
"subtitle": "Estampa texto o una imagen sobre tus páginas PDF."
},
"headerFooter": {
"name": "Encabezado y Pie de Página",
"subtitle": "Agrega texto en la parte superior e inferior de las páginas."
},
"invertColors": {
"name": "Invertir Colores",
"subtitle": "Crea una versión en \"modo oscuro\" de tu PDF."
},
"backgroundColor": {
"name": "Color de Fondo",
"subtitle": "Cambia el color de fondo de tu PDF."
},
"changeTextColor": {
"name": "Cambiar Color de Texto",
"subtitle": "Cambia el color del texto en tu PDF."
},
"addStamps": {
"name": "Agregar Sellos",
"subtitle": "Agrega sellos de imagen a tu PDF usando la barra de herramientas de anotación.",
"usernameLabel": "Nombre de Usuario del Sello",
"usernamePlaceholder": "Ingresa tu nombre (para sellos)",
"usernameHint": "Este nombre aparecerá en los sellos que crees."
},
"removeAnnotations": {
"name": "Eliminar Anotaciones",
"subtitle": "Elimina comentarios, resaltados y enlaces."
},
"pdfFormFiller": {
"name": "Rellenar Formularios PDF",
"subtitle": "Rellena formularios directamente en el navegador. También soporta formularios XFA."
},
"createPdfForm": {
"name": "Crear Formulario PDF",
"subtitle": "Crea formularios PDF rellenables con campos de texto arrastrables."
},
"removeBlankPages": {
"name": "Eliminar Páginas en Blanco",
"subtitle": "Detecta y elimina automáticamente páginas en blanco."
},
"imageToPdf": {
"name": "Imágenes a PDF",
"subtitle": "Convierte JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP a PDF."
},
"pngToPdf": {
"name": "PNG a PDF",
"subtitle": "Crea un PDF desde una o más imágenes PNG."
},
"webpToPdf": {
"name": "WebP a PDF",
"subtitle": "Crea un PDF desde una o más imágenes WebP."
},
"svgToPdf": {
"name": "SVG a PDF",
"subtitle": "Crea un PDF desde una o más imágenes SVG."
},
"bmpToPdf": {
"name": "BMP a PDF",
"subtitle": "Crea un PDF desde una o más imágenes BMP."
},
"heicToPdf": {
"name": "HEIC a PDF",
"subtitle": "Crea un PDF desde una o más imágenes HEIC."
},
"tiffToPdf": {
"name": "TIFF a PDF",
"subtitle": "Crea un PDF desde una o más imágenes TIFF."
},
"textToPdf": {
"name": "Texto a PDF",
"subtitle": "Convierte un archivo de texto plano en un PDF."
},
"jsonToPdf": {
"name": "JSON a PDF",
"subtitle": "Convierte archivos JSON a formato PDF."
},
"pdfToJpg": {
"name": "PDF a JPG",
"subtitle": "Convierte cada página PDF en una imagen JPG."
},
"pdfToPng": {
"name": "PDF a PNG",
"subtitle": "Convierte cada página PDF en una imagen PNG."
},
"pdfToWebp": {
"name": "PDF a WebP",
"subtitle": "Convierte cada página PDF en una imagen WebP."
},
"pdfToBmp": {
"name": "PDF a BMP",
"subtitle": "Convierte cada página PDF en una imagen BMP."
},
"pdfToTiff": {
"name": "PDF a TIFF",
"subtitle": "Convierte cada página PDF en una imagen TIFF."
},
"pdfToGreyscale": {
"name": "PDF a Escala de Grises",
"subtitle": "Convierte todos los colores a blanco y negro."
},
"pdfToJson": {
"name": "PDF a JSON",
"subtitle": "Convierte archivos PDF a formato JSON."
},
"ocrPdf": {
"name": "OCR PDF",
"subtitle": "Hace que un PDF sea buscable y copiable."
},
"alternateMix": {
"name": "Alternar y Mezclar Páginas",
"subtitle": "Fusiona PDFs alternando páginas de cada PDF. Preserva Marcadores."
},
"addAttachments": {
"name": "Agregar Adjuntos",
"subtitle": "Incrusta uno o más archivos en tu PDF."
},
"extractAttachments": {
"name": "Extraer Adjuntos",
"subtitle": "Extrae todos los archivos incrustados de PDF(s) como un ZIP."
},
"editAttachments": {
"name": "Editar Adjuntos",
"subtitle": "Ve o elimina adjuntos en tu PDF."
},
"dividePages": {
"name": "Dividir Páginas",
"subtitle": "Divide páginas horizontal o verticalmente."
},
"addBlankPage": {
"name": "Agregar Página en Blanco",
"subtitle": "Inserta una página vacía en cualquier lugar de tu PDF."
},
"reversePages": {
"name": "Invertir Páginas",
"subtitle": "Invierte el orden de todas las páginas en tu documento."
},
"rotatePdf": {
"name": "Rotar PDF",
"subtitle": "Gira páginas en incrementos de 90 grados."
},
"rotateCustom": {
"name": "Rotar por Grados Personalizados",
"subtitle": "Rota páginas por cualquier ángulo personalizado."
},
"nUpPdf": {
"name": "N-Up PDF",
"subtitle": "Organiza múltiples páginas en una sola hoja."
},
"combineToSinglePage": {
"name": "Combinar en Una Sola Página",
"subtitle": "Une todas las páginas en un desplazamiento continuo."
},
"viewMetadata": {
"name": "Ver Metadatos",
"subtitle": "Inspecciona las propiedades ocultas de tu PDF."
},
"editMetadata": {
"name": "Editar Metadatos",
"subtitle": "Cambia el autor, título y otras propiedades."
},
"pdfsToZip": {
"name": "PDFs a ZIP",
"subtitle": "Empaqueta múltiples archivos PDF en un archivo ZIP."
},
"comparePdfs": {
"name": "Comparar PDFs",
"subtitle": "Compara dos PDFs lado a lado."
},
"posterizePdf": {
"name": "Posterizar PDF",
"subtitle": "Divide una página grande en múltiples páginas más pequeñas."
},
"fixPageSize": {
"name": "Fijar Tamaño de Página",
"subtitle": "Estandariza todas las páginas a un tamaño uniforme."
},
"linearizePdf": {
"name": "Linealizar PDF",
"subtitle": "Optimiza el PDF para visualización web rápida."
},
"pageDimensions": {
"name": "Dimensiones de Página",
"subtitle": "Analiza el tamaño, orientación y unidades de página."
},
"removeRestrictions": {
"name": "Eliminar Restricciones",
"subtitle": "Elimina la protección por contraseña y las restricciones de seguridad asociadas con archivos PDF firmados digitalmente."
},
"repairPdf": {
"name": "Reparar PDF",
"subtitle": "Recupera datos de archivos PDF corruptos o dañados."
},
"encryptPdf": {
"name": "Cifrar PDF",
"subtitle": "Bloquea tu PDF agregando una contraseña."
},
"sanitizePdf": {
"name": "Sanear PDF",
"subtitle": "Elimina metadatos, anotaciones, scripts y más."
},
"decryptPdf": {
"name": "Descifrar PDF",
"subtitle": "Desbloquea PDF eliminando la protección por contraseña."
},
"flattenPdf": {
"name": "Aplanar PDF",
"subtitle": "Hace que los campos de formulario y las anotaciones no sean editables."
},
"removeMetadata": {
"name": "Eliminar Metadatos",
"subtitle": "Elimina datos ocultos de tu PDF."
},
"changePermissions": {
"name": "Cambiar Permisos",
"subtitle": "Establece o cambia los permisos de usuario en un PDF."
},
"odtToPdf": {
"name": "ODT a PDF",
"subtitle": "Convierte archivos OpenDocument Text a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos ODT",
"convertButton": "Convertir a PDF"
},
"csvToPdf": {
"name": "CSV a PDF",
"subtitle": "Convierte archivos de hoja de cálculo CSV a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos CSV",
"convertButton": "Convertir a PDF"
},
"rtfToPdf": {
"name": "RTF a PDF",
"subtitle": "Convierte documentos Rich Text Format a PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos RTF",
"convertButton": "Convertir a PDF"
},
"wordToPdf": {
"name": "Word a PDF",
"subtitle": "Convierte documentos Word (DOCX, DOC, ODT, RTF) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos DOCX, DOC, ODT, RTF",
"convertButton": "Convertir a PDF"
},
"excelToPdf": {
"name": "Excel a PDF",
"subtitle": "Convierte hojas de cálculo Excel (XLSX, XLS, ODS, CSV) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos XLSX, XLS, ODS, CSV",
"convertButton": "Convertir a PDF"
},
"powerpointToPdf": {
"name": "PowerPoint a PDF",
"subtitle": "Convierte presentaciones PowerPoint (PPTX, PPT, ODP) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos PPTX, PPT, ODP",
"convertButton": "Convertir a PDF"
},
"markdownToPdf": {
"name": "Markdown a PDF",
"subtitle": "Escribe o pega Markdown y expórtalo como un PDF bellamente formateado.",
"paneMarkdown": "Markdown",
"panePreview": "Vista Previa",
"btnUpload": "Cargar",
"btnSyncScroll": "Sincronizar Desplazamiento",
"btnSettings": "Configuración",
"btnExportPdf": "Exportar PDF",
"settingsTitle": "Configuración de Markdown",
"settingsPreset": "Predefinido",
"presetDefault": "Predeterminado (similar a GFM)",
"presetCommonmark": "CommonMark (estricto)",
"presetZero": "Mínimo (sin funciones)",
"settingsOptions": "Opciones de Markdown",
"optAllowHtml": "Permitir etiquetas HTML",
"optBreaks": "Convertir saltos de línea a <br>",
"optLinkify": "Auto-convertir URLs a enlaces",
"optTypographer": "Tipógrafo (comillas inteligentes, etc.)"
},
"pdfBooklet": {
"name": "Folleto PDF",
"subtitle": "Reorganiza páginas para impresión de folleto a doble cara. Dobla y engrapa para crear un folleto.",
"howItWorks": "Cómo funciona:",
"step1": "Carga un archivo PDF.",
"step2": "Las páginas se reorganizarán en orden de folleto.",
"step3": "Imprime a doble cara, voltea por el borde corto, dobla y engrapa.",
"paperSize": "Tamaño de Papel",
"orientation": "Orientación",
"portrait": "Vertical",
"landscape": "Horizontal",
"pagesPerSheet": "Páginas por Hoja",
"createBooklet": "Crear Folleto",
"processing": "Procesando...",
"pageCount": "El recuento de páginas se rellenará a múltiplo de 4 si es necesario."
},
"xpsToPdf": {
"name": "XPS a PDF",
"subtitle": "Convierte documentos XPS/OXPS a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos XPS, OXPS",
"convertButton": "Convertir a PDF"
},
"mobiToPdf": {
"name": "MOBI a PDF",
"subtitle": "Convierte e-books MOBI a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos MOBI",
"convertButton": "Convertir a PDF"
},
"epubToPdf": {
"name": "EPUB a PDF",
"subtitle": "Convierte e-books EPUB a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos EPUB",
"convertButton": "Convertir a PDF"
},
"fb2ToPdf": {
"name": "FB2 a PDF",
"subtitle": "Convierte e-books FictionBook (FB2) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos FB2",
"convertButton": "Convertir a PDF"
},
"cbzToPdf": {
"name": "CBZ a PDF",
"subtitle": "Convierte archivos de cómics (CBZ/CBR) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos CBZ, CBR",
"convertButton": "Convertir a PDF"
},
"wpdToPdf": {
"name": "WPD a PDF",
"subtitle": "Convierte documentos WordPerfect (WPD) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos WPD",
"convertButton": "Convertir a PDF"
},
"wpsToPdf": {
"name": "WPS a PDF",
"subtitle": "Convierte documentos WPS Office a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos WPS",
"convertButton": "Convertir a PDF"
},
"xmlToPdf": {
"name": "XML a PDF",
"subtitle": "Convierte documentos XML a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos XML",
"convertButton": "Convertir a PDF"
},
"pagesToPdf": {
"name": "Pages a PDF",
"subtitle": "Convierte documentos Apple Pages a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos Pages",
"convertButton": "Convertir a PDF"
},
"odgToPdf": {
"name": "ODG a PDF",
"subtitle": "Convierte archivos OpenDocument Graphics (ODG) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos ODG",
"convertButton": "Convertir a PDF"
},
"odsToPdf": {
"name": "ODS a PDF",
"subtitle": "Convierte archivos OpenDocument Spreadsheet (ODS) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos ODS",
"convertButton": "Convertir a PDF"
},
"odpToPdf": {
"name": "ODP a PDF",
"subtitle": "Convierte archivos OpenDocument Presentation (ODP) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos ODP",
"convertButton": "Convertir a PDF"
},
"pubToPdf": {
"name": "PUB a PDF",
"subtitle": "Convierte archivos Microsoft Publisher (PUB) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos PUB",
"convertButton": "Convertir a PDF"
},
"vsdToPdf": {
"name": "VSD a PDF",
"subtitle": "Convierte archivos Microsoft Visio (VSD, VSDX) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos VSD, VSDX",
"convertButton": "Convertir a PDF"
},
"psdToPdf": {
"name": "PSD a PDF",
"subtitle": "Convierte archivos Adobe Photoshop (PSD) a formato PDF. Soporta múltiples archivos.",
"acceptedFormats": "Archivos PSD",
"convertButton": "Convertir a PDF"
},
"pdfToSvg": {
"name": "PDF a SVG",
"subtitle": "Convierte cada página de un archivo PDF en un gráfico vectorial escalable (SVG) para calidad perfecta a cualquier tamaño."
},
"extractTables": {
"name": "Extraer Tablas de PDF",
"subtitle": "Extrae tablas de archivos PDF y exporta como CSV, JSON o Markdown."
},
"pdfToCsv": {
"name": "PDF a CSV",
"subtitle": "Extrae tablas de PDF y convierte a formato CSV."
},
"pdfToExcel": {
"name": "PDF a Excel",
"subtitle": "Extrae tablas de PDF y convierte a formato Excel (XLSX)."
},
"pdfToText": {
"name": "PDF a Texto",
"subtitle": "Extrae texto de archivos PDF y guarda como texto plano (.txt). Soporta múltiples archivos.",
"note": "Esta herramienta funciona SOLO con PDFs creados digitalmente. Para documentos escaneados o PDFs basados en imágenes, usa nuestra herramienta OCR PDF en su lugar.",
"convertButton": "Extraer Texto"
},
"digitalSignPdf": {
"name": "Firma Digital PDF",
"pageTitle": "Firma Digital PDF - Agregar Firma Criptográfica | BentoPDF",
"subtitle": "Agrega una firma digital criptográfica a tu PDF usando certificados X.509. Soporta formatos PKCS#12 (.pfx, .p12) y PEM. Tu clave privada nunca sale de tu navegador.",
"certificateSection": "Certificado",
"uploadCert": "Cargar certificado (.pfx, .p12)",
"certPassword": "Contraseña del Certificado",
"certPasswordPlaceholder": "Ingresa la contraseña del certificado",
"certInfo": "Información del Certificado",
"certSubject": "Sujeto",
"certIssuer": "Emisor",
"certValidity": "Válido",
"signatureDetails": "Detalles de la Firma (Opcional)",
"reason": "Razón",
"reasonPlaceholder": "ej., Apruebo este documento",
"location": "Ubicación",
"locationPlaceholder": "ej., Madrid, España",
"contactInfo": "Información de Contacto",
"contactPlaceholder": "ej., email@ejemplo.com",
"applySignature": "Aplicar Firma Digital",
"successMessage": "¡PDF firmado exitosamente! La firma se puede verificar en cualquier lector de PDF."
},
"validateSignaturePdf": {
"name": "Validar Firma PDF",
"pageTitle": "Validar Firma PDF - Verificar Firmas Digitales | BentoPDF",
"subtitle": "Verifica firmas digitales en tus archivos PDF. Comprueba la validez del certificado, ve los detalles del firmante y confirma la integridad del documento."
},
"emailToPdf": {
"name": "Email a PDF",
"subtitle": "Convierte archivos de correo (EML, MSG) a formato PDF. Soporta exportaciones de Outlook y formatos de correo estándar.",
"acceptedFormats": "Archivos EML, MSG",
"convertButton": "Convertir a PDF"
},
"fontToOutline": {
"name": "Fuente a Contorno",
"subtitle": "Convierte todas las fuentes a contornos vectoriales para una renderización consistente en todos los dispositivos."
},
"deskewPdf": {
"name": "Enderezar PDF",
"subtitle": "Endereza automáticamente páginas escaneadas inclinadas usando OpenCV."
}
}

View File

@@ -0,0 +1,325 @@
{
"nav": {
"home": "Accueil",
"about": "À propos",
"contact": "Contact",
"licensing": "Licence",
"allTools": "Tous les outils",
"openMainMenu": "Ouvrir le menu principal",
"language": "Langue"
},
"donation": {
"message": "Vous aimez BentoPDF ? Aidez-nous à le garder open source !",
"button": "Soutenir"
},
"hero": {
"title": "La",
"pdfToolkit": "palette doutils PDF",
"builtForPrivacy": "conçue pour la confidentialité",
"noSignups": "Sans inscription",
"unlimitedUse": "Utilisation illimitée",
"worksOffline": "Fonctionne hors ligne",
"startUsing": "Commencer maintenant"
},
"usedBy": {
"title": "Utilisé par des entreprises et des professionnels de"
},
"features": {
"title": "Pourquoi choisir",
"bentoPdf": "BentoPDF ?",
"noSignup": {
"title": "Sans inscription",
"description": "Utilisation immédiate, sans compte ni email."
},
"noUploads": {
"title": "Aucun envoi de fichiers",
"description": "100 % côté navigateur, vos fichiers ne quittent jamais votre appareil."
},
"foreverFree": {
"title": "Gratuit pour toujours",
"description": "Tous les outils, sans essai, sans paiement, sans restrictions."
},
"noLimits": {
"title": "Sans limites",
"description": "Utilisez autant que vous voulez, sans plafonds cachés."
},
"batchProcessing": {
"title": "Traitement par lots",
"description": "Gérez un nombre illimité de PDF en une seule fois."
},
"lightningFast": {
"title": "Ultra rapide",
"description": "Traitez vos PDF instantanément, sans attente."
}
},
"tools": {
"title": "Commencer avec",
"toolsLabel": "Les outils",
"subtitle": "Cliquez sur un outil pour importer vos fichiers",
"searchPlaceholder": "Rechercher un outil (ex. « scinder », « organiser »...)",
"backToTools": "Retour aux outils",
"firstLoadNotice": "Le premier chargement peut prendre quelques instants, le temps de charger notre moteur de conversion. Les prochaines fois, tout se chargera instantanément."
},
"upload": {
"clickToSelect": "Cliquez pour sélectionner un fichier",
"orDragAndDrop": "ou glissez-déposez",
"pdfOrImages": "PDF ou images",
"filesNeverLeave": "Vos fichiers restent sur votre appareil.",
"addMore": "Ajouter dautres fichiers",
"clearAll": "Tout effacer",
"clearFiles": "Effacer les fichiers"
},
"loader": {
"processing": "Traitement en cours..."
},
"alert": {
"title": "Alerte",
"ok": "OK"
},
"preview": {
"title": "Aperçu du document",
"downloadAsPdf": "Télécharger en PDF",
"close": "Fermer"
},
"settings": {
"title": "Paramètres",
"shortcuts": "Raccourcis",
"preferences": "Préférences",
"displayPreferences": "Préférences daffichage",
"searchShortcuts": "Rechercher un raccourci...",
"shortcutsInfo": "Maintenez les touches pour définir un raccourci. Les changements sont enregistrés automatiquement.",
"shortcutsWarning": "⚠️ Évitez les raccourcis courants du navigateur (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N, etc.), ils peuvent ne pas fonctionner correctement.",
"import": "Importer",
"export": "Exporter",
"resetToDefaults": "Rétablir les paramètres par défaut",
"fullWidthMode": "Mode pleine largeur",
"fullWidthDescription": "Utiliser toute la largeur de lécran au lieu dun affichage centré",
"settingsAutoSaved": "Les paramètres sont enregistrés automatiquement",
"clickToSet": "Cliquez pour définir",
"pressKeys": "Appuyez sur les touches...",
"warnings": {
"alreadyInUse": "Raccourci déjà utilisé",
"assignedTo": "est déjà attribué à :",
"chooseDifferent": "Veuillez choisir un autre raccourci.",
"reserved": "Avertissement de raccourci réservé",
"commonlyUsed": "est couramment utilisé pour :",
"unreliable": "Ce raccourci peut ne pas fonctionner correctement ou entrer en conflit avec le navigateur ou le système.",
"useAnyway": "Souhaitez-vous lutiliser quand même ?",
"resetTitle": "Réinitialiser les raccourcis",
"resetMessage": "Êtes-vous sûr de vouloir réinitialiser tous les raccourcis par défaut ?<br><br>Cette action est irréversible.",
"importSuccessTitle": "Importation réussie",
"importSuccessMessage": "Les raccourcis ont été importés avec succès !",
"importFailTitle": "Échec de limportation",
"importFailMessage": "Impossible dimporter les raccourcis. Format de fichier invalide."
}
},
"warning": {
"title": "Attention",
"cancel": "Annuler",
"proceed": "Continuer"
},
"compliance": {
"title": "Vos données ne quittent jamais votre appareil",
"weKeep": "Nous protégeons",
"yourInfoSafe": "vos informations",
"byFollowingStandards": "en respectant les normes de sécurité internationales.",
"processingLocal": "Tous les traitements sont effectués localement sur votre appareil.",
"gdpr": {
"title": "Conformité RGPD",
"description": "Protège les données personnelles et la vie privée des citoyens de lUnion européenne."
},
"ccpa": {
"title": "Conformité CCPA",
"description": "Accorde aux résidents de Californie des droits sur lutilisation de leurs données personnelles."
},
"hipaa": {
"title": "Conformité HIPAA",
"description": "Définit des règles strictes pour la gestion des données de santé aux États-Unis."
}
},
"faq": {
"title": "Questions",
"questions": "fréquentes",
"isFree": {
"question": "BentoPDF est-il vraiment gratuit ?",
"answer": "Oui, totalement. Tous les outils BentoPDF sont 100 % gratuits, sans limite de fichiers, sans inscription et sans filigrane. Nous pensons que chacun doit avoir accès à des outils PDF simples et puissants, sans barrière payante."
},
"areFilesSecure": {
"question": "Mes fichiers sont-ils en sécurité ? Où sont-ils traités ?",
"answer": "Vos fichiers sont parfaitement sécurisés car ils ne quittent jamais votre ordinateur. Tous les traitements se font directement dans votre navigateur. Aucun fichier nest envoyé sur un serveur."
},
"platforms": {
"question": "Est-ce compatible avec Mac, Windows et mobile ?",
"answer": "Oui ! BentoPDF fonctionne entièrement dans le navigateur et est compatible avec Windows, macOS, Linux, iOS et Android."
},
"gdprCompliant": {
"question": "BentoPDF est-il conforme au RGPD ?",
"answer": "Oui. Comme tous les traitements sont locaux et quaucune donnée nest collectée ou transmise, vous restez entièrement maître de vos documents."
},
"dataStorage": {
"question": "Stockez-vous ou suivez-vous mes fichiers ?",
"answer": "Non. Aucun stockage, aucun suivi, aucun historique. Tout disparaît dès que vous fermez la page."
},
"different": {
"question": "Quest-ce qui différencie BentoPDF des autres outils PDF ?",
"answer": "La plupart des outils envoient vos fichiers sur un serveur. BentoPDF traite tout localement dans votre navigateur, pour plus de rapidité, de confidentialité et de tranquillité desprit."
},
"browserBased": {
"question": "Pourquoi le traitement dans le navigateur est-il plus sûr ?",
"answer": "Parce que vos fichiers restent sur votre appareil. Aucun risque de fuite, de piratage ou daccès non autorisé."
},
"analytics": {
"question": "Utilisez-vous des cookies ou des outils de suivi ?",
"answer": "Nous respectons votre vie privée. BentoPDF utilise uniquement des statistiques anonymes pour connaître le nombre de visites, sans jamais identifier les utilisateurs."
}
},
"testimonials": {
"title": "Ce que disent",
"users": "nos utilisateurs",
"say": ""
},
"support": {
"title": "Vous aimez ce projet ?",
"description": "BentoPDF est un projet passion, créé pour offrir une palette doutils PDF gratuite, privée et puissante. Si cela vous aide, vous pouvez soutenir son développement. Chaque café compte !",
"buyMeCoffee": "Moffrir un café"
},
"footer": {
"copyright": "© 2026 BentoPDF. Tous droits réservés.",
"version": "Version",
"company": "Entreprise",
"aboutUs": "À propos",
"faqLink": "FAQ",
"contactUs": "Nous contacter",
"legal": "Mentions légales",
"termsAndConditions": "Conditions générales",
"privacyPolicy": "Politique de confidentialité",
"followUs": "Nous suivre"
},
"merge": {
"title": "Fusionner des PDF",
"description": "Combinez des fichiers entiers ou sélectionnez des pages spécifiques pour créer un nouveau document.",
"fileMode": "Mode fichiers",
"pageMode": "Mode pages",
"howItWorks": "Fonctionnement :",
"fileModeInstructions": [
"Cliquez-glissez licône pour modifier lordre des fichiers.",
"Dans le champ « Pages » de chaque fichier, vous pouvez définir des plages (ex. « 1-3, 5 ») pour ne fusionner que certaines pages.",
"Laissez le champ « Pages » vide pour inclure toutes les pages du fichier."
],
"pageModeInstructions": [
"Toutes les pages de vos PDF importés saffichent ci-dessous.",
"Glissez-déposez simplement les miniatures pour définir lordre exact de votre nouveau document."
],
"mergePdfs": "Fusionner les PDF"
},
"common": {
"page": "Page",
"pages": "Pages",
"of": "sur",
"download": "Télécharger",
"cancel": "Annuler",
"save": "Enregistrer",
"delete": "Supprimer",
"edit": "Modifier",
"add": "Ajouter",
"remove": "Retirer",
"loading": "Chargement...",
"error": "Erreur",
"success": "Succès",
"file": "Fichier",
"files": "Fichiers",
"close": "Fermer"
},
"about": {
"hero": {
"title": "Nous pensons que les outils PDF doivent être",
"subtitle": "rapides, privés et gratuits.",
"noCompromises": "Sans compromis."
},
"mission": {
"title": "Notre mission",
"description": "Proposer la palette doutils PDF la plus complète, tout en respectant votre vie privée et sans jamais demander de paiement. Les outils essentiels doivent être accessibles à tous, partout, sans barrières."
},
"philosophy": {
"label": "Notre philosophie",
"title": "La confidentialité avant tout. Toujours.",
"description": "À une époque où les données sont devenues une monnaie, nous faisons un choix différent. Tous les traitements des outils BentoPDF sont effectués localement dans votre navigateur. Vos fichiers ne passent jamais par nos serveurs, nous ne voyons jamais vos documents et nous ne suivons pas votre activité. Ce nest pas une option, cest notre fondation."
},
"whyBentopdf": {
"title": "Pourquoi",
"speed": {
"title": "Pensé pour la vitesse",
"description": "Aucune attente liée aux envois ou téléchargements serveur. Grâce au traitement local et aux technologies web modernes comme WebAssembly, nos outils sont extrêmement rapides."
},
"free": {
"title": "Entièrement gratuit",
"description": "Aucun essai, aucun abonnement, aucun coût caché, aucune fonctionnalité « premium » bloquée. Les outils PDF doivent être un service public, pas un produit de luxe."
},
"noAccount": {
"title": "Aucun compte requis",
"description": "Utilisez nimporte quel outil immédiatement. Pas demail, pas de mot de passe, aucune donnée personnelle. Votre flux de travail reste fluide et anonyme."
},
"openSource": {
"title": "Esprit open source",
"description": "Conçu dans un esprit de transparence. Nous utilisons des bibliothèques open source reconnues comme PDF-lib et PDF.js, et croyons en la force de la communauté."
}
},
"cta": {
"title": "Prêt à commencer ?",
"description": "Rejoignez des milliers dutilisateurs qui font confiance à BentoPDF au quotidien. Découvrez la différence quapportent la confidentialité et la performance.",
"button": "Explorer tous les outils"
}
},
"contact": {
"title": "Nous contacter",
"subtitle": "Nous serions ravis déchanger avec vous. Question, retour ou suggestion de fonctionnalité, nhésitez pas à nous écrire.",
"email": "Vous pouvez nous contacter directement par email à :"
},
"licensing": {
"title": "Licences pour",
"subtitle": "Choisissez la licence adaptée à vos besoins."
},
"multiTool": {
"uploadPdfs": "Importer des PDF",
"upload": "Importer",
"addBlankPage": "Ajouter une page vierge",
"edit": "Modifier :",
"undo": "Annuler",
"redo": "Rétablir",
"reset": "Réinitialiser",
"selection": "Sélection :",
"selectAll": "Tout sélectionner",
"deselectAll": "Tout désélectionner",
"rotate": "Rotation :",
"rotateLeft": "Gauche",
"rotateRight": "Droite",
"transform": "Transformer :",
"duplicate": "Dupliquer",
"split": "Scinder",
"clear": "Effacer :",
"delete": "Supprimer",
"download": "Téléchargement :",
"downloadSelected": "Télécharger la sélection",
"exportPdf": "Exporter en PDF",
"uploadPdfFiles": "Sélectionner des fichiers PDF",
"dragAndDrop": "Glissez-déposez vos fichiers PDF ici ou cliquez pour sélectionner",
"selectFiles": "Sélectionner des fichiers",
"renderingPages": "Rendu des pages...",
"actions": {
"duplicatePage": "Dupliquer cette page",
"deletePage": "Supprimer cette page",
"insertPdf": "Insérer un PDF après cette page",
"toggleSplit": "Activer/désactiver la séparation après cette page"
},
"pleaseWait": "Veuillez patienter",
"pagesRendering": "Les pages sont en cours de rendu. Veuillez patienter...",
"noPagesSelected": "Aucune page sélectionnée",
"selectOnePage": "Veuillez sélectionner au moins une page à télécharger.",
"noPages": "Aucune page",
"noPagesToExport": "Aucune page à exporter.",
"renderingTitle": "Génération des aperçus",
"errorRendering": "Échec du rendu des miniatures",
"error": "Erreur",
"failedToLoad": "Échec du chargement"
}
}

View File

@@ -0,0 +1,533 @@
{
"categories": {
"popularTools": "Outils populaires",
"editAnnotate": "Éditer et annoter",
"convertToPdf": "Convertir en PDF",
"convertFromPdf": "Convertir depuis le PDF",
"organizeManage": "Organiser et gérer",
"optimizeRepair": "Optimiser et réparer",
"securePdf": "Sécuriser les PDF"
},
"pdfMultiTool": {
"name": "Outil PDF tout-en-un",
"subtitle": "Fusionner, scinder, organiser, supprimer, faire pivoter, ajouter des pages vierges, extraire et dupliquer dans une interface unifiée."
},
"mergePdf": {
"name": "Fusionner des PDF",
"subtitle": "Assembler plusieurs PDF en un seul fichier, tout en conservant les signets."
},
"splitPdf": {
"name": "Scinder un PDF",
"subtitle": "Extraire une plage de pages dans un nouveau PDF."
},
"compressPdf": {
"name": "Compresser un PDF",
"subtitle": "Réduire la taille du fichier PDF.",
"algorithmLabel": "Algorithme de compression",
"condense": "Condensé (recommandé)",
"photon": "Photon (pour les PDF riches en photos)",
"condenseInfo": "Condensé utilise une compression avancée : suppression du superflu, optimisation des images, sous-ensemble des polices. Idéal pour la plupart des PDF.",
"photonInfo": "Photon convertit les pages en images. À utiliser pour les PDF contenant beaucoup de photos ou scannés.",
"photonWarning": "Attention : le texte ne sera plus sélectionnable et les liens ne fonctionneront plus.",
"levelLabel": "Niveau de compression",
"light": "Léger (préserver la qualité)",
"balanced": "Équilibré (recommandé)",
"aggressive": "Agressif (fichiers plus petits)",
"extreme": "Extrême (compression maximale)",
"grayscale": "Convertir en niveaux de gris",
"grayscaleHint": "Réduit la taille du fichier en supprimant les informations de couleur",
"customSettings": "Paramètres personnalisés",
"customSettingsHint": "Affiner les paramètres de compression :",
"outputQuality": "Qualité de sortie",
"resizeImagesTo": "Redimensionner les images à",
"onlyProcessAbove": "Traiter uniquement au-dessus de",
"removeMetadata": "Supprimer les métadonnées",
"subsetFonts": "Sous-ensemble des polices (supprimer les glyphes inutilisés)",
"removeThumbnails": "Supprimer les vignettes intégrées",
"compressButton": "Compresser le PDF"
},
"pdfEditor": {
"name": "Éditeur PDF",
"subtitle": "Annoter, surligner, masquer, commenter, ajouter des formes ou images, rechercher et afficher des PDF."
},
"jpgToPdf": {
"name": "JPG vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images JPG."
},
"signPdf": {
"name": "Signer un PDF",
"subtitle": "Dessiner, saisir ou importer votre signature."
},
"cropPdf": {
"name": "Rogner un PDF",
"subtitle": "Ajuster les marges de chaque page du PDF."
},
"extractPages": {
"name": "Extraire des pages",
"subtitle": "Enregistrer une sélection de pages dans de nouveaux fichiers."
},
"duplicateOrganize": {
"name": "Dupliquer et organiser",
"subtitle": "Dupliquer, réorganiser et supprimer des pages."
},
"deletePages": {
"name": "Supprimer des pages",
"subtitle": "Retirer des pages spécifiques du document."
},
"editBookmarks": {
"name": "Modifier les signets",
"subtitle": "Ajouter, modifier, importer, supprimer et extraire des signets PDF."
},
"tableOfContents": {
"name": "Table des matières",
"subtitle": "Générer une table des matières à partir des signets du PDF."
},
"pageNumbers": {
"name": "Numéros de page",
"subtitle": "Insérer une numérotation dans le document."
},
"addWatermark": {
"name": "Ajouter un filigrane",
"subtitle": "Apposer un texte ou une image sur les pages du PDF."
},
"headerFooter": {
"name": "En-tête et pied de page",
"subtitle": "Ajouter du texte en haut et en bas des pages."
},
"invertColors": {
"name": "Inverser les couleurs",
"subtitle": "Créer une version « mode sombre » du PDF."
},
"backgroundColor": {
"name": "Couleur de fond",
"subtitle": "Modifier la couleur de fond du PDF."
},
"changeTextColor": {
"name": "Changer la couleur du texte",
"subtitle": "Modifier la couleur du texte dans le PDF."
},
"addStamps": {
"name": "Ajouter des tampons",
"subtitle": "Ajouter des tampons image via la barre dannotations.",
"usernameLabel": "Nom du tampon",
"usernamePlaceholder": "Entrez votre nom (pour les tampons)",
"usernameHint": "Ce nom apparaîtra sur les tampons que vous créez."
},
"removeAnnotations": {
"name": "Supprimer les annotations",
"subtitle": "Retirer les commentaires, surlignages et liens."
},
"pdfFormFiller": {
"name": "Remplir un formulaire PDF",
"subtitle": "Remplir des formulaires directement dans le navigateur, y compris les formulaires XFA."
},
"createPdfForm": {
"name": "Créer un formulaire PDF",
"subtitle": "Créer des formulaires PDF interactifs avec des champs glisser-déposer."
},
"removeBlankPages": {
"name": "Supprimer les pages blanches",
"subtitle": "Détecter et supprimer automatiquement les pages vides."
},
"imageToPdf": {
"name": "Images vers PDF",
"subtitle": "Convertir un JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP en PDF."
},
"pngToPdf": {
"name": "PNG vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images PNG."
},
"webpToPdf": {
"name": "WebP vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images WebP."
},
"svgToPdf": {
"name": "SVG vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images SVG."
},
"bmpToPdf": {
"name": "BMP vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images BMP."
},
"heicToPdf": {
"name": "HEIC vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images HEIC."
},
"tiffToPdf": {
"name": "TIFF vers PDF",
"subtitle": "Créer un PDF à partir dune ou plusieurs images TIFF."
},
"textToPdf": {
"name": "Texte vers PDF",
"subtitle": "Convertir un fichier texte en PDF."
},
"jsonToPdf": {
"name": "JSON vers PDF",
"subtitle": "Convertir des fichiers JSON en PDF."
},
"pdfToJpg": {
"name": "PDF vers JPG",
"subtitle": "Convertir chaque page du PDF en image JPG."
},
"pdfToPng": {
"name": "PDF vers PNG",
"subtitle": "Convertir chaque page du PDF en image PNG."
},
"pdfToWebp": {
"name": "PDF vers WebP",
"subtitle": "Convertir chaque page du PDF en image WebP."
},
"pdfToBmp": {
"name": "PDF vers BMP",
"subtitle": "Convertir chaque page du PDF en image BMP."
},
"pdfToTiff": {
"name": "PDF vers TIFF",
"subtitle": "Convertir chaque page du PDF en image TIFF."
},
"pdfToGreyscale": {
"name": "PDF en niveaux de gris",
"subtitle": "Convertir toutes les couleurs en noir et blanc."
},
"pdfToJson": {
"name": "PDF vers JSON",
"subtitle": "Convertir des fichiers PDF en JSON."
},
"ocrPdf": {
"name": "OCR PDF",
"subtitle": "Rendre un PDF consultable et copiable."
},
"alternateMix": {
"name": "Alterner et mélanger les pages",
"subtitle": "Fusionner des PDF en alternant les pages de chaque fichier, tout en conservant les signets."
},
"addAttachments": {
"name": "Ajouter des pièces jointes",
"subtitle": "Intégrer un ou plusieurs fichiers dans le PDF."
},
"extractAttachments": {
"name": "Extraire les pièces jointes",
"subtitle": "Extraire tous les fichiers intégrés des PDF dans une archive ZIP."
},
"editAttachments": {
"name": "Gérer les pièces jointes",
"subtitle": "Afficher ou supprimer les pièces jointes du PDF."
},
"dividePages": {
"name": "Diviser les pages",
"subtitle": "Diviser les pages horizontalement ou verticalement."
},
"addBlankPage": {
"name": "Ajouter une page vierge",
"subtitle": "Insérer une page vide à nimporte quel endroit du PDF."
},
"reversePages": {
"name": "Inverser lordre des pages",
"subtitle": "Renverser lordre de toutes les pages du document."
},
"rotatePdf": {
"name": "Faire pivoter un PDF",
"subtitle": "Tourner les pages par incréments de 90°."
},
"rotateCustom": {
"name": "Rotation par angle personnalisé",
"subtitle": "Faire pivoter les pages selon un angle personnalisé."
},
"nUpPdf": {
"name": "PDF N-up",
"subtitle": "Afficher plusieurs pages sur une seule feuille."
},
"combineToSinglePage": {
"name": "Combiner en une seule page",
"subtitle": "Assembler toutes les pages en un défilement continu."
},
"viewMetadata": {
"name": "Afficher les métadonnées",
"subtitle": "Consulter les propriétés internes du PDF."
},
"editMetadata": {
"name": "Modifier les métadonnées",
"subtitle": "Changer lauteur, le titre et autres propriétés."
},
"pdfsToZip": {
"name": "PDF vers ZIP",
"subtitle": "Regrouper plusieurs fichiers PDF dans une archive ZIP."
},
"comparePdfs": {
"name": "Comparer des PDF",
"subtitle": "Comparer deux PDF côte à côte."
},
"posterizePdf": {
"name": "Posteriser un PDF",
"subtitle": "Découper une grande page en plusieurs pages plus petites."
},
"fixPageSize": {
"name": "Uniformiser la taille des pages",
"subtitle": "Standardiser toutes les pages à un format identique."
},
"linearizePdf": {
"name": "Optimiser pour le web",
"subtitle": "Optimiser le PDF pour un affichage rapide en ligne."
},
"pageDimensions": {
"name": "Dimensions des pages",
"subtitle": "Analyser la taille, lorientation et les unités des pages."
},
"removeRestrictions": {
"name": "Supprimer les restrictions",
"subtitle": "Supprimer les protections par mot de passe et restrictions de sécurité des PDF signés."
},
"repairPdf": {
"name": "Réparer un PDF",
"subtitle": "Récupérer les données de fichiers PDF corrompus ou endommagés."
},
"encryptPdf": {
"name": "Chiffrer un PDF",
"subtitle": "Protéger le PDF en ajoutant un mot de passe."
},
"sanitizePdf": {
"name": "Nettoyer un PDF",
"subtitle": "Supprimer les métadonnées, annotations, scripts et autres éléments sensibles."
},
"decryptPdf": {
"name": "Déverrouiller un PDF",
"subtitle": "Supprimer la protection par mot de passe."
},
"flattenPdf": {
"name": "Aplatir le PDF",
"subtitle": "Rendre les champs de formulaire et annotations non modifiables."
},
"removeMetadata": {
"name": "Supprimer les métadonnées",
"subtitle": "Effacer les données cachées du PDF."
},
"changePermissions": {
"name": "Modifier les autorisations",
"subtitle": "Définir ou modifier les permissions utilisateur du PDF."
},
"odtToPdf": {
"name": "ODT vers PDF",
"subtitle": "Convertir des fichiers OpenDocument Text au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers ODT",
"convertButton": "Convertir en PDF"
},
"csvToPdf": {
"name": "CSV vers PDF",
"subtitle": "Convertir des fichiers tableur CSV au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers CSV",
"convertButton": "Convertir en PDF"
},
"rtfToPdf": {
"name": "RTF vers PDF",
"subtitle": "Convertir des documents Rich Text Format en PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers RTF",
"convertButton": "Convertir en PDF"
},
"wordToPdf": {
"name": "Word vers PDF",
"subtitle": "Convertir des documents Word (DOCX, DOC, ODT, RTF) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers DOCX, DOC, ODT, RTF",
"convertButton": "Convertir en PDF"
},
"excelToPdf": {
"name": "Excel vers PDF",
"subtitle": "Convertir des feuilles de calcul Excel (XLSX, XLS, ODS, CSV) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers XLSX, XLS, ODS, CSV",
"convertButton": "Convertir en PDF"
},
"powerpointToPdf": {
"name": "PowerPoint vers PDF",
"subtitle": "Convertir des présentations PowerPoint (PPTX, PPT, ODP) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers PPTX, PPT, ODP",
"convertButton": "Convertir en PDF"
},
"markdownToPdf": {
"name": "Markdown vers PDF",
"subtitle": "Écrire ou coller du Markdown et lexporter en PDF avec une mise en forme soignée.",
"paneMarkdown": "Markdown",
"panePreview": "Aperçu",
"btnUpload": "Téléverser",
"btnSyncScroll": "Synchroniser le défilement",
"btnSettings": "Paramètres",
"btnExportPdf": "Exporter en PDF",
"settingsTitle": "Paramètres Markdown",
"settingsPreset": "Préréglage",
"presetDefault": "Par défaut (type GFM)",
"presetCommonmark": "CommonMark (strict)",
"presetZero": "Minimal (aucune fonctionnalité)",
"settingsOptions": "Options Markdown",
"optAllowHtml": "Autoriser les balises HTML",
"optBreaks": "Convertir les retours à la ligne en <br>",
"optLinkify": "Convertir automatiquement les URL en liens",
"optTypographer": "Typographie (guillemets intelligents, etc.)"
},
"pdfBooklet": {
"name": "Livret PDF",
"subtitle": "Réorganiser les pages pour limpression recto verso en livret. Pliez et agrafez pour créer un livret.",
"howItWorks": "Fonctionnement :",
"step1": "Téléversez un fichier PDF.",
"step2": "Les pages seront réorganisées dans lordre du livret.",
"step3": "Imprimez en recto verso, retournement sur le bord court, pliez et agrafez.",
"paperSize": "Format du papier",
"orientation": "Orientation",
"portrait": "Portrait",
"landscape": "Paysage",
"pagesPerSheet": "Pages par feuille",
"createBooklet": "Créer le livret",
"processing": "Traitement...",
"pageCount": "Le nombre de pages sera complété au multiple de 4 si nécessaire."
},
"xpsToPdf": {
"name": "XPS vers PDF",
"subtitle": "Convertir des documents XPS/OXPS au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers XPS, OXPS",
"convertButton": "Convertir en PDF"
},
"mobiToPdf": {
"name": "MOBI vers PDF",
"subtitle": "Convertir des livres numériques MOBI au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers MOBI",
"convertButton": "Convertir en PDF"
},
"epubToPdf": {
"name": "EPUB vers PDF",
"subtitle": "Convertir des livres numériques EPUB au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers EPUB",
"convertButton": "Convertir en PDF"
},
"fb2ToPdf": {
"name": "FB2 vers PDF",
"subtitle": "Convertir des livres numériques FictionBook (FB2) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers FB2",
"convertButton": "Convertir en PDF"
},
"cbzToPdf": {
"name": "CBZ vers PDF",
"subtitle": "Convertir des archives de bandes dessinées (CBZ/CBR) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers CBZ, CBR",
"convertButton": "Convertir en PDF"
},
"wpdToPdf": {
"name": "WPD vers PDF",
"subtitle": "Convertir des documents WordPerfect (WPD) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers WPD",
"convertButton": "Convertir en PDF"
},
"wpsToPdf": {
"name": "WPS vers PDF",
"subtitle": "Convertir des documents WPS Office au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers WPS",
"convertButton": "Convertir en PDF"
},
"xmlToPdf": {
"name": "XML vers PDF",
"subtitle": "Convertir des documents XML au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers XML",
"convertButton": "Convertir en PDF"
},
"pagesToPdf": {
"name": "Pages vers PDF",
"subtitle": "Convertir des documents Apple Pages au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers Pages",
"convertButton": "Convertir en PDF"
},
"odgToPdf": {
"name": "ODG vers PDF",
"subtitle": "Convertir des fichiers OpenDocument Graphics (ODG) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers ODG",
"convertButton": "Convertir en PDF"
},
"odsToPdf": {
"name": "ODS vers PDF",
"subtitle": "Convertir des fichiers OpenDocument Spreadsheet (ODS) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers ODS",
"convertButton": "Convertir en PDF"
},
"odpToPdf": {
"name": "ODP vers PDF",
"subtitle": "Convertir des fichiers OpenDocument Presentation (ODP) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers ODP",
"convertButton": "Convertir en PDF"
},
"pubToPdf": {
"name": "PUB vers PDF",
"subtitle": "Convertir des fichiers Microsoft Publisher (PUB) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers PUB",
"convertButton": "Convertir en PDF"
},
"vsdToPdf": {
"name": "VSD vers PDF",
"subtitle": "Convertir des fichiers Microsoft Visio (VSD, VSDX) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers VSD, VSDX",
"convertButton": "Convertir en PDF"
},
"psdToPdf": {
"name": "PSD vers PDF",
"subtitle": "Convertir des fichiers Adobe Photoshop (PSD) au format PDF. Prend en charge plusieurs fichiers.",
"acceptedFormats": "Fichiers PSD",
"convertButton": "Convertir en PDF"
},
"pdfToSvg": {
"name": "PDF vers SVG",
"subtitle": "Convertir chaque page dun fichier PDF en graphique vectoriel évolutif (SVG) pour une qualité parfaite à toutes les tailles."
},
"extractTables": {
"name": "Extraire les tableaux PDF",
"subtitle": "Extraire les tableaux des fichiers PDF et les exporter en CSV, JSON ou Markdown."
},
"pdfToCsv": {
"name": "PDF vers CSV",
"subtitle": "Extraire les tableaux dun PDF et les convertir au format CSV."
},
"pdfToExcel": {
"name": "PDF vers Excel",
"subtitle": "Extraire les tableaux dun PDF et les convertir au format Excel (XLSX)."
},
"pdfToText": {
"name": "PDF vers texte",
"subtitle": "Extraire le texte des fichiers PDF et lenregistrer en texte brut (.txt). Prend en charge plusieurs fichiers.",
"note": "Cet outil fonctionne UNIQUEMENT avec des PDF créés numériquement. Pour les documents scannés ou les PDF basés sur des images, utilisez plutôt notre outil OCR PDF.",
"convertButton": "Extraire le texte"
},
"digitalSignPdf": {
"name": "Signature numérique PDF",
"pageTitle": "Signature numérique PDF - Ajouter une signature cryptographique | BentoPDF",
"subtitle": "Ajouter une signature numérique cryptographique à votre PDF à laide de certificats X.509. Prend en charge les formats PKCS#12 (.pfx, .p12) et PEM. Votre clé privée ne quitte jamais votre navigateur.",
"certificateSection": "Certificat",
"uploadCert": "Téléverser un certificat (.pfx, .p12)",
"certPassword": "Mot de passe du certificat",
"certPasswordPlaceholder": "Saisissez le mot de passe du certificat",
"certInfo": "Informations du certificat",
"certSubject": "Sujet",
"certIssuer": "Émetteur",
"certValidity": "Validité",
"signatureDetails": "Détails de la signature (facultatif)",
"reason": "Motif",
"reasonPlaceholder": "ex. : Japprouve ce document",
"location": "Lieu",
"locationPlaceholder": "ex. : Paris, France",
"contactInfo": "Coordonnées",
"contactPlaceholder": "ex. : email@exemple.com",
"applySignature": "Appliquer la signature numérique",
"successMessage": "PDF signé avec succès ! La signature peut être vérifiée dans nimporte quel lecteur PDF."
},
"validateSignaturePdf": {
"name": "Valider la signature PDF",
"pageTitle": "Valider la signature PDF - Vérifier les signatures numériques | BentoPDF",
"subtitle": "Vérifier les signatures numériques de vos fichiers PDF. Contrôlez la validité du certificat, consultez les informations du signataire et confirmez lintégrité du document. Tout le traitement seffectue dans votre navigateur."
},
"emailToPdf": {
"name": "Email vers PDF",
"subtitle": "Convertir des fichiers email (EML, MSG) au format PDF. Prend en charge les exports Outlook et les formats email standards.",
"acceptedFormats": "Fichiers EML, MSG",
"convertButton": "Convertir en PDF"
},
"fontToOutline": {
"name": "Polices en contours",
"subtitle": "Convertir toutes les polices en contours vectoriels pour un rendu cohérent sur tous les appareils."
},
"deskewPdf": {
"name": "Redresser un PDF",
"subtitle": "Redresser automatiquement les pages scannées inclinées à laide dOpenCV."
}
}

View File

@@ -66,7 +66,8 @@
"pdfOrImages": "PDF atau Gambar", "pdfOrImages": "PDF atau Gambar",
"filesNeverLeave": "File Anda tidak pernah meninggalkan perangkat Anda.", "filesNeverLeave": "File Anda tidak pernah meninggalkan perangkat Anda.",
"addMore": "Tambah Lebih Banyak File", "addMore": "Tambah Lebih Banyak File",
"clearAll": "Hapus Semua" "clearAll": "Hapus Semua",
"clearFiles": "Hapus file"
}, },
"loader": { "loader": {
"processing": "Memproses..." "processing": "Memproses..."
@@ -183,7 +184,7 @@
"buyMeCoffee": "Beli Kopi untuk Saya" "buyMeCoffee": "Beli Kopi untuk Saya"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. Hak cipta dilindungi.", "copyright": "© 2026 BentoPDF. Hak cipta dilindungi.",
"version": "Versi", "version": "Versi",
"company": "Perusahaan", "company": "Perusahaan",
"aboutUs": "Tentang Kami", "aboutUs": "Tentang Kami",
@@ -226,7 +227,8 @@
"error": "Kesalahan", "error": "Kesalahan",
"success": "Berhasil", "success": "Berhasil",
"file": "File", "file": "File",
"files": "File" "files": "File",
"close": "Tutup"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -521,5 +521,13 @@
"subtitle": "Konversi file email (EML, MSG) ke format PDF. Mendukung ekspor Outlook dan format email standar.", "subtitle": "Konversi file email (EML, MSG) ke format PDF. Mendukung ekspor Outlook dan format email standar.",
"acceptedFormats": "File EML, MSG", "acceptedFormats": "File EML, MSG",
"convertButton": "Konversi ke PDF" "convertButton": "Konversi ke PDF"
},
"fontToOutline": {
"name": "Font ke Garis Tepi",
"subtitle": "Konversi semua font ke garis tepi vektor untuk tampilan konsisten di semua perangkat."
},
"deskewPdf": {
"name": "Luruskan PDF",
"subtitle": "Otomatis meluruskan halaman hasil pindai yang miring menggunakan OpenCV."
} }
} }

View File

@@ -1,50 +1,50 @@
{ {
"nav": { "nav": {
"home": "Home", "home": "Home",
"about": "Chi Siamo", "about": "Chi siamo",
"contact": "Contatti", "contact": "Contatti",
"licensing": "Licenze", "licensing": "Licenze",
"allTools": "Tutti gli Strumenti", "allTools": "Tutti gli strumenti",
"openMainMenu": "Apri il Menu Principale", "openMainMenu": "Apri il menu principale",
"language": "Lingua" "language": "Lingua"
}, },
"donation": { "donation": {
"message": "Love BentoPDF? Help us keep it free and open source!", "message": "Ti piace BentoPDF? Aiutaci a mantenerlo gratuito e open source!",
"button": "Donate" "button": "Dona"
}, },
"hero": { "hero": {
"title": "I", "title": "Il",
"pdfToolkit": "tuoi attrezzi per i PDF", "pdfToolkit": "kit di strumenti PDF",
"builtForPrivacy": "creati per la privacy", "builtForPrivacy": "pensato per la privacy",
"noSignups": "Nessun iscrizione", "noSignups": "Nessuna registrazione",
"unlimitedUse": "Uso illimitato", "unlimitedUse": "Uso illimitato",
"worksOffline": "Funziona offline", "worksOffline": "Funziona offline",
"startUsing": "Inizia ad usarlo ora" "startUsing": "Inizia a usarlo ora"
}, },
"usedBy": { "usedBy": {
"title": "Usato da aziende e persone che lavorano in" "title": "Usato da aziende e persone che lavorano in"
}, },
"features": { "features": {
"title": "Perchè scegliere", "title": "Perché scegliere",
"bentoPdf": "BentoPDF?", "bentoPdf": "BentoPDF?",
"noSignup": { "noSignup": {
"title": "Nessuna Registrazione", "title": "Nessuna registrazione",
"description": "Usa subito, senza account o email." "description": "Inizia subito, senza account email."
}, },
"noUploads": { "noUploads": {
"title": "Nessun caricamento", "title": "Nessun caricamento",
"description": "100% client-side, i tuoi file non lasciano mai il dispositivo." "description": "100% client-side, i tuoi file non lasciano mai il dispositivo."
}, },
"foreverFree": { "foreverFree": {
"title": "Sempre Gratis", "title": "Sempre gratis",
"description": "Tutti gli strumenti, nessuna prova, nessun paywall." "description": "Tutti gli strumenti, nessuna prova, nessun paywall."
}, },
"noLimits": { "noLimits": {
"title": "Senza Limiti", "title": "Senza limiti",
"description": "Usalo quanto vuoi, senza limiti nascosti." "description": "Usalo quanto vuoi, senza limiti nascosti."
}, },
"batchProcessing": { "batchProcessing": {
"title": "Elaborazione in Batch", "title": "Elaborazione in batch",
"description": "Gestisci un numero illimitato di PDF in un'unica operazione." "description": "Gestisci un numero illimitato di PDF in un'unica operazione."
}, },
"lightningFast": { "lightningFast": {
@@ -55,18 +55,19 @@
"tools": { "tools": {
"title": "Inizia con", "title": "Inizia con",
"toolsLabel": "Strumenti", "toolsLabel": "Strumenti",
"subtitle": "Clicca uno strumento per aprire il caricatore di file", "subtitle": "Clicca su uno strumento per aprire il caricatore di file",
"searchPlaceholder": "Cerca uno strumento (es. 'split', 'organize'...)", "searchPlaceholder": "Cerca uno strumento (es. \"split\", \"organizza\"...)",
"backToTools": "Torna agli Strumenti", "backToTools": "Torna agli strumenti",
"firstLoadNotice": "Il primo caricamento richiede un momento mentre scarichiamo il nostro motore di conversione. Dopo di ciò, tutti i caricamenti saranno immediati." "firstLoadNotice": "Il primo caricamento richiede qualche istante mentre scarichiamo il motore di conversione. Dopo di ciò, tutti i caricamenti saranno immediati."
}, },
"upload": { "upload": {
"clickToSelect": "Clicca per selezionare un file", "clickToSelect": "Clicca per selezionare un file",
"orDragAndDrop": "o trascina e rilascia", "orDragAndDrop": "oppure trascina e rilascia",
"pdfOrImages": "PDF o Immagini", "pdfOrImages": "PDF o immagini",
"filesNeverLeave": "I tuoi file non lasciano mai il tuo dispositivo.", "filesNeverLeave": "I tuoi file non lasciano mai il tuo dispositivo.",
"addMore": "Aggiungi altri file", "addMore": "Aggiungi altri file",
"clearAll": "Svuota tutto" "clearAll": "Svuota tutto",
"clearFiles": "Cancella file"
}, },
"loader": { "loader": {
"processing": "Elaborazione..." "processing": "Elaborazione..."
@@ -76,7 +77,7 @@
"ok": "OK" "ok": "OK"
}, },
"preview": { "preview": {
"title": "Anteprima Documento", "title": "Anteprima documento",
"downloadAsPdf": "Scarica come PDF", "downloadAsPdf": "Scarica come PDF",
"close": "Chiudi" "close": "Chiudi"
}, },
@@ -87,7 +88,7 @@
"displayPreferences": "Preferenze di visualizzazione", "displayPreferences": "Preferenze di visualizzazione",
"searchShortcuts": "Cerca scorciatoie...", "searchShortcuts": "Cerca scorciatoie...",
"shortcutsInfo": "Premi e tieni premuti i tasti per impostare una scorciatoia. Le modifiche vengono salvate automaticamente.", "shortcutsInfo": "Premi e tieni premuti i tasti per impostare una scorciatoia. Le modifiche vengono salvate automaticamente.",
"shortcutsWarning": "⚠️ Evita scorciatoie comuni del browser (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N ecc.) poiché potrebbero non funzionare in modo affidabile.", "shortcutsWarning": "⚠️ Evita le scorciatoie comuni del browser (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N ecc.) perché potrebbero non funzionare in modo affidabile.",
"import": "Importa", "import": "Importa",
"export": "Esporta", "export": "Esporta",
"resetToDefaults": "Reimposta ai valori predefiniti", "resetToDefaults": "Reimposta ai valori predefiniti",
@@ -95,7 +96,7 @@
"fullWidthDescription": "Usa l'intera larghezza dello schermo per tutti gli strumenti invece di un contenitore centrato", "fullWidthDescription": "Usa l'intera larghezza dello schermo per tutti gli strumenti invece di un contenitore centrato",
"settingsAutoSaved": "Le impostazioni vengono salvate automaticamente", "settingsAutoSaved": "Le impostazioni vengono salvate automaticamente",
"clickToSet": "Clicca per impostare", "clickToSet": "Clicca per impostare",
"pressKeys": "Premi tasti...", "pressKeys": "Premi i tasti...",
"warnings": { "warnings": {
"alreadyInUse": "Scorciatoia già in uso", "alreadyInUse": "Scorciatoia già in uso",
"assignedTo": "è già assegnata a:", "assignedTo": "è già assegnata a:",
@@ -104,11 +105,11 @@
"commonlyUsed": "è comunemente usata per:", "commonlyUsed": "è comunemente usata per:",
"unreliable": "Questa scorciatoia potrebbe non funzionare in modo affidabile o potrebbe avere conflitti con il comportamento del browser/sistema.", "unreliable": "Questa scorciatoia potrebbe non funzionare in modo affidabile o potrebbe avere conflitti con il comportamento del browser/sistema.",
"useAnyway": "Vuoi usarla comunque?", "useAnyway": "Vuoi usarla comunque?",
"resetTitle": "Reimposta Scorciatoie", "resetTitle": "Reimposta scorciatoie",
"resetMessage": "Sei sicuro di voler reimpostare tutte le scorciatoie ai valori predefiniti?<br><br>Questa azione non può essere annullata.", "resetMessage": "Sei sicuro di voler reimpostare tutte le scorciatoie ai valori predefiniti?<br><br>Questa azione non può essere annullata.",
"importSuccessTitle": "Importazione Riuscita", "importSuccessTitle": "Importazione riuscita",
"importSuccessMessage": "Scorciatoie importate con successo!", "importSuccessMessage": "Scorciatoie importate con successo!",
"importFailTitle": "Importazione Fallita", "importFailTitle": "Importazione fallita",
"importFailMessage": "Impossibile importare le scorciatoie. Formato file non valido." "importFailMessage": "Impossibile importare le scorciatoie. Formato file non valido."
} }
}, },
@@ -137,8 +138,8 @@
} }
}, },
"faq": { "faq": {
"title": "Domande Frequenti", "title": "Domande",
"questions": "Domande", "questions": "Frequenti",
"isFree": { "isFree": {
"question": "BentoPDF è davvero gratuito?", "question": "BentoPDF è davvero gratuito?",
"answer": "Sì, assolutamente. Tutti gli strumenti su BentoPDF sono gratuiti al 100%, senza limiti di file, senza registrazioni e senza filigrane. Crediamo che tutti debbano poter accedere a strumenti PDF semplici e potenti senza barriere a pagamento." "answer": "Sì, assolutamente. Tutti gli strumenti su BentoPDF sono gratuiti al 100%, senza limiti di file, senza registrazioni e senza filigrane. Crediamo che tutti debbano poter accedere a strumenti PDF semplici e potenti senza barriere a pagamento."
@@ -174,8 +175,8 @@
}, },
"testimonials": { "testimonials": {
"title": "Cosa", "title": "Cosa",
"users": "i Nostri Utenti", "users": "dicono i nostri",
"say": "Dicono" "say": "utenti"
}, },
"support": { "support": {
"title": "Ti piace il mio lavoro?", "title": "Ti piace il mio lavoro?",
@@ -183,22 +184,22 @@
"buyMeCoffee": "Offrimi un caffè" "buyMeCoffee": "Offrimi un caffè"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. Tutti i diritti riservati.", "copyright": "© 2026 BentoPDF. Tutti i diritti riservati.",
"version": "Versione", "version": "Versione",
"company": "Azienda", "company": "Azienda",
"aboutUs": "Chi Siamo", "aboutUs": "Chi siamo",
"faqLink": "FAQ", "faqLink": "FAQ",
"contactUs": "Contattaci", "contactUs": "Contattaci",
"legal": "Legale", "legal": "Legale",
"termsAndConditions": "Termini e Condizioni", "termsAndConditions": "Termini e condizioni",
"privacyPolicy": "Informativa sulla Privacy", "privacyPolicy": "Informativa sulla privacy",
"followUs": "Seguici" "followUs": "Seguici"
}, },
"merge": { "merge": {
"title": "Unisci PDF", "title": "Unisci PDF",
"description": "Combina file interi, oppure seleziona pagine specifiche da unire in un nuovo documento.", "description": "Combina file interi, oppure seleziona pagine specifiche da unire in un nuovo documento.",
"fileMode": "Modalità File", "fileMode": "Modalità file",
"pageMode": "Modalità Pagina", "pageMode": "Modalità pagina",
"howItWorks": "Come funziona:", "howItWorks": "Come funziona:",
"fileModeInstructions": [ "fileModeInstructions": [
"Clicca e trascina l'icona per cambiare l'ordine dei file.", "Clicca e trascina l'icona per cambiare l'ordine dei file.",
@@ -226,7 +227,8 @@
"error": "Errore", "error": "Errore",
"success": "Successo", "success": "Successo",
"file": "File", "file": "File",
"files": "File" "files": "File",
"close": "Chiudi"
}, },
"about": { "about": {
"hero": { "hero": {
@@ -247,7 +249,7 @@
"title": "Perché BentoPDF", "title": "Perché BentoPDF",
"speed": { "speed": {
"title": "Progettato per la velocità", "title": "Progettato per la velocità",
"description": "Nessuna attesa per upload o download verso un server. Elaborando i file direttamente nel browser con tecnologie web moderne come WebAssembly, offriamo velocità impareggiabile per tutti i nostri strumenti." "description": "Nessuna attesa per upload o download verso un server. Elaborando i file direttamente nel browser con tecnologie web moderne come WebAssembly, offriamo una velocità impareggiabile per tutti i nostri strumenti."
}, },
"free": { "free": {
"title": "Completamente gratuito", "title": "Completamente gratuito",
@@ -266,6 +268,7 @@
"title": "Pronto per iniziare?", "title": "Pronto per iniziare?",
"description": "Unisciti a migliaia di utenti che si affidano a BentoPDF per le loro esigenze quotidiane sui documenti. Sperimenta la differenza che privacy e prestazioni possono offrire.", "description": "Unisciti a migliaia di utenti che si affidano a BentoPDF per le loro esigenze quotidiane sui documenti. Sperimenta la differenza che privacy e prestazioni possono offrire.",
"button": "Esplora tutti gli strumenti" "button": "Esplora tutti gli strumenti"
}
}, },
"contact": { "contact": {
"title": "Contattaci", "title": "Contattaci",
@@ -320,4 +323,3 @@
"failedToLoad": "Caricamento fallito" "failedToLoad": "Caricamento fallito"
} }
} }
}

View File

@@ -3,18 +3,18 @@
"popularTools": "Strumenti popolari", "popularTools": "Strumenti popolari",
"editAnnotate": "Modifica e Annota", "editAnnotate": "Modifica e Annota",
"convertToPdf": "Converti in PDF", "convertToPdf": "Converti in PDF",
"convertFromPdf": "Convert da PDF", "convertFromPdf": "Converti da PDF",
"organizeManage": "Organizza e Gestisci", "organizeManage": "Organizza e Gestisci",
"optimizeRepair": "Ottimizza e Ripara", "optimizeRepair": "Ottimizza e Ripara",
"securePdf": "Proteggi PDF" "securePdf": "Proteggi PDF"
}, },
"pdfMultiTool": { "pdfMultiTool": {
"name": "PDF Multi Tool", "name": "Strumento PDF multifunzione",
"subtitle": "Unisci, Dividi, Organizza, Elimina, Ruota, Aggiungi Pagine Vuote, Estrai e Duplica in un'interfaccia unificata." "subtitle": "Unisci, dividi, organizza, elimina, ruota, aggiungi pagine vuote, estrai e duplica in un'interfaccia unificata."
}, },
"mergePdf": { "mergePdf": {
"name": "Unisci PDF", "name": "Unisci PDF",
"subtitle": "Unisci più PDF in un unico file. Conserva i Segnalibri." "subtitle": "Unisci più PDF in un unico file. Conserva i segnalibri."
}, },
"splitPdf": { "splitPdf": {
"name": "Dividi PDF", "name": "Dividi PDF",
@@ -68,7 +68,7 @@
}, },
"duplicateOrganize": { "duplicateOrganize": {
"name": "Duplica e Organizza", "name": "Duplica e Organizza",
"subtitle": "Duplica, riordina e elimina pagine." "subtitle": "Duplica, riordina ed elimina pagine."
}, },
"deletePages": { "deletePages": {
"name": "Elimina Pagine", "name": "Elimina Pagine",
@@ -199,7 +199,7 @@
}, },
"alternateMix": { "alternateMix": {
"name": "Alterna e Riordina Pagine", "name": "Alterna e Riordina Pagine",
"subtitle": "Unisci PDF sostituendo le pagine di ogni file. Conserva i segnalibri." "subtitle": "Unisci i PDF alternando le pagine di ogni file. Conserva i segnalibri."
}, },
"addAttachments": { "addAttachments": {
"name": "Aggiungi Allegati", "name": "Aggiungi Allegati",
@@ -489,10 +489,45 @@
"note": "Questo strumento funziona SOLO con PDF creati digitalmente. Per documenti scansionati o basati su immagini, usa invece il nostro strumento OCR PDF.", "note": "Questo strumento funziona SOLO con PDF creati digitalmente. Per documenti scansionati o basati su immagini, usa invece il nostro strumento OCR PDF.",
"convertButton": "Estrai testo" "convertButton": "Estrai testo"
}, },
"digitalSignPdf": {
"name": "Firma digitale PDF",
"pageTitle": "Firma digitale PDF - Aggiungi firma crittografica | BentoPDF",
"subtitle": "Aggiungi una firma digitale crittografica al tuo PDF usando certificati X.509. Supporta i formati PKCS#12 (.pfx, .p12) e PEM. La tua chiave privata non lascia mai il browser.",
"certificateSection": "Certificato",
"uploadCert": "Carica certificato (.pfx, .p12)",
"certPassword": "Password del certificato",
"certPasswordPlaceholder": "Inserisci la password del certificato",
"certInfo": "Informazioni sul certificato",
"certSubject": "Soggetto",
"certIssuer": "Emittente",
"certValidity": "Valido",
"signatureDetails": "Dettagli della firma (opzionale)",
"reason": "Motivo",
"reasonPlaceholder": "es. Approvo questo documento",
"location": "Luogo",
"locationPlaceholder": "es. Roma, Italia",
"contactInfo": "Contatto",
"contactPlaceholder": "es. email@example.com",
"applySignature": "Applica firma digitale",
"successMessage": "PDF firmato con successo! La firma può essere verificata in qualsiasi lettore PDF."
},
"validateSignaturePdf": {
"name": "Verifica firma PDF",
"pageTitle": "Verifica firma PDF - Controlla firme digitali | BentoPDF",
"subtitle": "Verifica le firme digitali nei tuoi PDF. Controlla la validità del certificato, visualizza i dati del firmatario e conferma l'integrità del documento. Tutto avviene nel tuo browser."
},
"emailToPdf": { "emailToPdf": {
"name": "Email in PDF", "name": "Email in PDF",
"subtitle": "Converti file email (EML, MSG) in formato PDF. Supporta esportazioni Outlook e formati email standard.", "subtitle": "Converti file email (EML, MSG) in formato PDF. Supporta esportazioni Outlook e formati email standard.",
"acceptedFormats": "File EML, MSG", "acceptedFormats": "File EML, MSG",
"convertButton": "Converti in PDF" "convertButton": "Converti in PDF"
},
"fontToOutline": {
"name": "Font in Contorni",
"subtitle": "Converti tutti i font in contorni vettoriali per una visualizzazione coerente su tutti i dispositivi."
},
"deskewPdf": {
"name": "Raddrizza PDF",
"subtitle": "Raddrizza automaticamente le pagine scansionate inclinate usando OpenCV."
} }
} }

View File

@@ -0,0 +1,325 @@
{
"nav": {
"home": "Thuis",
"about": "Over",
"contact": "Contact",
"licensing": "Licentie",
"allTools": "Alle Tools",
"openMainMenu": "Hoofdmenu openen",
"language": "Taal"
},
"donation": {
"message": "Vind je BentoPDF geweldig? Help ons het gratis en open source te houden!",
"button": "Doneren"
},
"hero": {
"title": "De",
"pdfToolkit": "PDF Toolkit",
"builtForPrivacy": "gemaakt voor privacy",
"noSignups": "Geen aanmelding",
"unlimitedUse": "Onbegrenst gebruik",
"worksOffline": "Werkt Offline",
"startUsing": "Direct aan de slag"
},
"usedBy": {
"title": "Gebruikt door bedrijven en personen werkzaam bij"
},
"features": {
"title": "Waarom",
"bentoPdf": "BentoPDF?",
"noSignup": {
"title": "Geen aanmelding",
"description": "Direct aan de slag, zonder account of e-mails."
},
"noUploads": {
"title": "Geen uploads",
"description": "100% client-side, je bestanden blijven op jouw apparaat."
},
"foreverFree": {
"title": "Voor altijd gratis",
"description": "Alle tools, geen proef, geen betaalmuur."
},
"noLimits": {
"title": "Geen beperkingen",
"description": "Gebruik het zoveel je wilt, geen verborgen limiet."
},
"batchProcessing": {
"title": "Reeksen verwerken",
"description": "Verwerk in een keer een onbeperkt aantal PDF's."
},
"lightningFast": {
"title": "Bliksemsnel",
"description": "Verwerk PDF's direct, zonder wachten of vertraging."
}
},
"tools": {
"title": "Aan de slag met",
"toolsLabel": "Tools",
"subtitle": "Klik een tool om de bestandslader te openen",
"searchPlaceholder": "Zoek een tool (bijv., 'splitsen', 'organiseren'...)",
"backToTools": "Terug naar Tools",
"firstLoadNotice": "De eerste keer duurt het even om onze conversiemachine te laden. Daarna gaat alles meteen."
},
"upload": {
"clickToSelect": "Klik om een bestand te selecteren",
"orDragAndDrop": "of sleep er een hierheen",
"pdfOrImages": "PDF's of Afbeeldingen",
"filesNeverLeave": "Je bestanden blijven op jouw apparaat.",
"addMore": "Meer bestanden toevoegen",
"clearAll": "Alles wissen",
"clearFiles": "Bestanden wissen"
},
"loader": {
"processing": "Verwerken..."
},
"alert": {
"title": "Let op",
"ok": "OK"
},
"preview": {
"title": "Document Weergeven",
"downloadAsPdf": "Downloaden als PDF",
"close": "Sluiten"
},
"settings": {
"title": "Instellingen",
"shortcuts": "Sneltoetsen",
"preferences": "Voorkeuren",
"displayPreferences": "Voorkeuren weergeven",
"searchShortcuts": "Sneltoetsen zoeken...",
"shortcutsInfo": "Houd toetsen ingedrukt om deze in te stellen als sneltoets. Aanpassingen worden automatisch opgeslagen.",
"shortcutsWarning": "⚠️ Voorkom algemene sneltoetsen voor browsers (Cmd/Ctrl+W, Cmd/Ctrl+T, Cmd/Ctrl+N etc.) aangezien deze niet betrouwbaar kunnen functioneren.",
"import": "Importeren",
"export": "Exporteren",
"resetToDefaults": "Terugzetten naar standaard",
"fullWidthMode": "Volledige breedte",
"fullWidthDescription": "Gebruik de volledige breedte van het scherm in plaats van een gecentreerde kolom",
"settingsAutoSaved": "Instellingen worden automatisch opgeslagen",
"clickToSet": "Klik om in te stellen",
"pressKeys": "Druk toetsen...",
"warnings": {
"alreadyInUse": "Sneltoets al in gebruik",
"assignedTo": "is al toegewezen aan:",
"chooseDifferent": "Kies een andere sneltoets.",
"reserved": "Waarschuwing voor gereserveerde sneltoets",
"commonlyUsed": "wordt algemeen gebruikt voor:",
"unreliable": "Deze sneltoets werkt mogelijk niet goed of is in conflict met gedrag van browser of systeem.",
"useAnyway": "Wil je het toch gebruiken?",
"resetTitle": "Sneltoetsen terugzetten",
"resetMessage": "Weet je zeker dat je alle sneltoetsen wilt terugzetten naar standaard?<br><br>Deze actie kan niet ongedaan worden gemaakt.",
"importSuccessTitle": "Import succesvoll",
"importSuccessMessage": "Sneltoetsen zijn met succes geïmporteerd!",
"importFailTitle": "Import mislukt",
"importFailMessage": "Het importeren van sneltoetsen is mislukt. Ongeldig bestandsformaat."
}
},
"warning": {
"title": "Waarschuwing",
"cancel": "Annuleren",
"proceed": "Verder"
},
"compliance": {
"title": "Jouw gegevens blijven op jouw appraat",
"weKeep": "Wij houden",
"yourInfoSafe": "jouw informatie veilig",
"byFollowingStandards": "volgens de volgende algemene veiligheidsstandaarden.",
"processingLocal": "Alle verwerking vindt lokaal plaats op jouw apparaat.",
"gdpr": {
"title": "AVG-naleving",
"description": "Beschermt persoonlijke gegevens en privacy van individuën binnen de Europese Unie."
},
"ccpa": {
"title": "CCPA-naleving",
"description": "Geeft inwoners van Californië rechten over hoe hun persoonlijke gegevens worden verzameld, gebruikt en gedeeld."
},
"hipaa": {
"title": "HIPAA-naleving",
"description": "Stelt waarborgen in voor het omgaan met gevoelige gezondheidsinformatie in het gezondheidszorgsysteem van de Verenigde Staten."
}
},
"faq": {
"title": "Vaak gestelde vragen",
"questions": "Vragen",
"isFree": {
"question": "Is BentoPDF echt gratis?",
"answer": "Ja, absoluut. Alle tools op BentoPDF zijn 100% gratis te gebruiken, zonder bestandslimieten, zonder aanmeldingen en zonder watermerken. Wij vinden dat iedereen toegang verdient tot eenvoudige, krachtige PDF-tools zonder betaalmuur."
},
"areFilesSecure": {
"question": "Zijn mijn bestanden veilig? Waar worden ze verwerkt?",
"answer": "Je bestanden zijn zo veilig mogelijk omdat ze je computer nooit verlaten. Alle verwerking gebeurt direct in je webbrowser (client-side). Je bestanden worden nooit naar een server ge-upload, zodat je volledige privacy en controle over je documenten behoudt."
},
"platforms": {
"question": "Werkt het op Mac, Windows en mobiel?",
"answer": "Ja! Omdat BentoPDF volledig in je browser werkt, werkt het op elk besturingssysteem met een moderne webbrowser, inclusief Windows, macOS, Linux, iOS en Android."
},
"gdprCompliant": {
"question": "Is BentoPDF AVG-conform?",
"answer": "Ja. BentoPDF voldoet volledig aan de AVG. Omdat alle bestandsverwerking lokaal in je browser gebeurt en we nooit je bestanden naar een server sturen of verzamelen, hebben wij geen toegang tot je gegevens. Zo houd jij altijd de controle over je documenten."
},
"dataStorage": {
"question": "Slaan jullie mijn bestanden op of volgen jullie die?",
"answer": "Nee. We slaan je bestanden nooit op, volgen ze niet en houden er geen logboek van bij. Alles wat je op BentoPDF doet, gebeurt in het geheugen van je browser en verdwijnt zodra je de pagina sluit. Er is geen upload, geen geschiedenislogboek en geen servers bij betrokken."
},
"different": {
"question": "Wat maakt BentoPDF anders dan andere PDF-tools?",
"answer": "De meeste PDF-tools verlangen dat je je bestanden voor verwerking naar een server uploadt. BentoPDF doet dat nooit. Wij gebruiken veilige, moderne webtechnologie om je bestanden direct in je browser te verwerken. Dit betekent snellere prestaties, betere privacy en totale gemoedsrust."
},
"browserBased": {
"question": "Hoe zorgt browsergebaseerde verwerking ervoor dat ik veilig blijf?",
"answer": "Omdat BentoPDF helemaal in je browser draait, blijven je bestanden altijd op je apparaat. Daardoor hoef je je geen zorgen te maken over hacks, datalekken of ongeoorloofde toegang. Je bestanden zijn altijd van jou."
},
"analytics": {
"question": "Gebruikt BentoPDF cookies of analytics om mij te volgen?",
"answer": "We geven om je privacy. BentoPDF houdt geen persoonlijke gegevens bij. We gebruiken Simple Analytics alleen om anonieme bezoekersaantallen te zien. Dit betekent dat we kunnen weten hoeveel mensen onze site bezoeken, maar we weten nooit wie jij bent. Simple Analytics is volledig AVG-conform en respecteert je privacy."
}
},
"testimonials": {
"title": "Wat Onze",
"users": "Gebruikers",
"say": "Zeggen"
},
"support": {
"title": "Vind je mijn werk leuk?",
"description": "BentoPDF is een project van passie, gemaakt om iedereen een gratis, privé en krachtig PDF-gereedschap te bieden. Als je het handig vindt, overweeg dan om de ontwikkeling te steunen. Elke koffie helpt!",
"buyMeCoffee": "Koop een kopje koffie voor me"
},
"footer": {
"copyright": "© 2025 BentoPDF. Alle rechten voorbehouden.",
"version": "Versie",
"company": "Bedrijf",
"aboutUs": "Over ons",
"faqLink": "FAQ",
"contactUs": "Contact",
"legal": "Juridisch",
"termsAndConditions": "Algemene voorwaarden",
"privacyPolicy": "Privacybeleid",
"followUs": "Volgen"
},
"merge": {
"title": "PDF's Samenvoegen",
"description": "Combineer hele bestanden, of selecteer specifieke pagina's om te samen te voegen tot een nieuw document.",
"fileMode": "Bestandsmodus",
"pageMode": "Paginamodus",
"howItWorks": "Hoe het werkt:",
"fileModeInstructions": [
"Klik en sleep het pictogram om de volgorde van de bestanden te wijzigen.",
"In het vak \"Pagina's\" voor elk bestand kan je reeksen opgeven (bijv. \"1-3, 5\") om alleen die pagina's samen te voegen.",
"Laat het vak \"Pagina's\" leeg om alle pagina's in het bestand op te nemen."
],
"pageModeInstructions": [
"Alle pagina's van je PDF's worden hieronder weergegeven.",
"Sleep afzonderlijke pagina-miniaturen in de gewenste volgorde voor je nieuwe bestand."
],
"mergePdfs": "PDF's Samenvoegen"
},
"common": {
"page": "Pagina",
"pages": "Pagina's",
"of": "van",
"download": "Download",
"cancel": "Annuleren",
"save": "Opslaan",
"delete": "Verwijderen",
"edit": "Bewerken",
"add": "Toevoegen",
"remove": "Verwijderen",
"loading": "Laden...",
"error": "Fout",
"success": "Success",
"file": "Bestand",
"files": "Bestanden",
"close": "Sluiten"
},
"about": {
"hero": {
"title": "Wij vinden dat PDF-tools",
"subtitle": "snel, privé, en gratis moeten zijn.",
"noCompromises": "Geen compromissen."
},
"mission": {
"title": "Onze Missie",
"description": "Ons doel is om de meest complete PDF-toolbox te bieden die je privacy respecteert en nooit om betaling vraagt. Wij geloven dat essentiële documententools voor iedereen, overal en zonder obstakels toegankelijk moeten zijn."
},
"philosophy": {
"label": "Onze kernfilosofie",
"title": "Privacy First. Always.",
"description": "In een tijdperk waarin data een handelswaar is, doen wij het net even anders. Alle verwerking voor BentoPDF-tools gebeurt lokaal in je browser. Dat betekent dat je bestanden nooit oop onze servers terechtkomen, dat wij je documenten nooit zien en niets volgen van wat je doet. Je documenten blijven volledig privé, punt. Het is niet zomaar een functie; het is onze basis."
},
"whyBentopdf": {
"title": "Waarom",
"speed": {
"title": "Gemaakt voor snelheid",
"description": "Geen wachttijd voor uploads of downloads naar een server. Door bestanden direct in je browser te verwerken met moderne webtechnologieën zoals WebAssembly, bieden we ongeëvenaarde snelheid voor al onze tools."
},
"free": {
"title": "Volledig gratis",
"description": "Geen proefversies, geen abonnementen, geen verborgen kosten en geen \"premium\" functies die vworden achtergehouden. Wij vinden dat krachtige PDF-tools een openbare dienst moeten zijn, geen winstmachine."
},
"noAccount": {
"title": "Geen account vereist",
"description": "Begin meteen met het gebruiken van een tool. Je e-mail, wachtwoord of persoonlijke gegevens hebben we niet nodig. Je workflow kan soepel en anoniem zijn."
},
"openSource": {
"title": "De geest van Open Source",
"description": "Gebouwd met transparantie in gedachten. We maken gebruik van geweldige open-source bibliotheken zoals PDF-lib en PDF.js en geloven in de communitygedreven inspanning om krachtige tools voor iedereen toegankelijk te maken."
}
},
"cta": {
"title": "Klaar om te beginnen?",
"description": "Sluit je aan bij duizenden gebruikers die BentoPDF vertrouwen voor hun dagelijkse documentbehoeften. Ervaar zelf het verschil dat privacy en prestaties kunnen maken.",
"button": "Ontdek alle tools"
}
},
"contact": {
"title": "Neem contact op",
"subtitle": "We horen graag van je. Of je nu een vraag, feedback of een verzoek voor een functie hebt, aarzel dan niet om contact met ons op te nemen.",
"email": "Je kunt ons rechtstreeks bereiken via e-mail op:"
},
"licensing": {
"title": "Licentie voor",
"subtitle": "Kies de licentie die bij je past."
},
"multiTool": {
"uploadPdfs": "PDF's laden",
"upload": "Laden",
"addBlankPage": "Blanco pagina toevoegen",
"edit": "Bewerken:",
"undo": "Ongedaan maken",
"redo": "Opnieuw",
"reset": "Terugzetten",
"selection": "Selectie:",
"selectAll": "Alles selecteren",
"deselectAll": "Selectie opheffen",
"rotate": "Roteren:",
"rotateLeft": "Linksom",
"rotateRight": "Rechtsom",
"transform": "Transformeren:",
"duplicate": "Dupliceren",
"split": "Splitsen",
"clear": "Wissen:",
"delete": "Verwijderen",
"download": "Laden:",
"downloadSelected": "Selectie laden",
"exportPdf": "PDF Exporteren",
"uploadPdfFiles": "PDF-bestanden selecteren",
"dragAndDrop": "Klik om een bestand te selecteren, of sleep er een hierheen",
"selectFiles": "Bestanden selecteren",
"renderingPages": "Pagina's vewerken...",
"actions": {
"duplicatePage": "Deze pagina dupliceren",
"deletePage": "Deze pagina verwijderen",
"insertPdf": "PDF achter deze pagina invoegen",
"toggleSplit": "Splitsing maken na deze pagina"
},
"pleaseWait": "Even geduld",
"pagesRendering": "Pagina's worden nog verwerkt. Even geduld...",
"noPagesSelected": "Geen pagina's geselecteerd",
"selectOnePage": "Kies tenminste een pagina om te laden.",
"noPages": "Geen pagina's",
"noPagesToExport": "Er zijn geen pagina's om te exporteren.",
"renderingTitle": "Paginavoorbeeld verwerken",
"errorRendering": "Generatie van pagina-miniaturen is mislukt",
"error": "Fout",
"failedToLoad": "Laden is mislukt"
}
}

View File

@@ -0,0 +1,533 @@
{
"categories": {
"popularTools": "Populaire Tools",
"editAnnotate": "Bewerken & Annoteren",
"convertToPdf": "Converteren naar PDF",
"convertFromPdf": "Converteren van PDF",
"organizeManage": "Organiseren & Beheren",
"optimizeRepair": "Optimaliseren & Repareren",
"securePdf": "PDF beveiligen"
},
"pdfMultiTool": {
"name": "PDF Multi-tool",
"subtitle": "Samenvoegen, Splitsen, Organiseren, Verwijderen, Roteren, Blanco pagina's toevoegen, Extraheren and Dupliceren in een enkele werkomgeving."
},
"mergePdf": {
"name": "PDF Samenvoegen",
"subtitle": "Meerdere PDFs combineren tot een enkel bestand. Bladwijzers behouden."
},
"splitPdf": {
"name": "PDF Splitsen",
"subtitle": "Een reeks pagina's opslaan in een nieuwe PDF."
},
"compressPdf": {
"name": "PDF Comprimeren",
"subtitle": "De bestandsgrootte van je PDF verkleinen.",
"algorithmLabel": "Compressie-algoritme",
"condense": "Condense (Aanbevolen)",
"photon": "Photon (Voor PDF's met veel foto's)",
"condenseInfo": "Condense gebruikt geavanceerde compressie: verwijdert overbodige onderdelen, optimaliseert afbeeldingen en kiest alleen de benodigde letters uit fonts. Ideaal voor de meeste PDF's.",
"photonInfo": "Photon zet pagina's om in afbeeldingen. Handig voor PDF's met veel foto's of gescande documenten.",
"photonWarning": "Let op: De tekst kan dan niet meer geselecteerd worden en links werken niet meer.",
"levelLabel": "Compressieniveau",
"light": "Licht (Kwaliteit behouden)",
"balanced": "Gebalanceerd (Aanbevolen)",
"aggressive": "Agressief (Kleinere bestanden)",
"extreme": "Extreem (Maximale compressie)",
"grayscale": "Converteren naar grijswaarden",
"grayscaleHint": "Vermindert de bestandsgrootte door kleurinformatie te verwijderen",
"customSettings": "Aangepaste instellingen",
"customSettingsHint": "Compressie-instellingen verfijnen:",
"outputQuality": "Uitvoerkwaliteit",
"resizeImagesTo": "Formaat van afbeeldingen aanpassen naar",
"onlyProcessAbove": "Alleen verwerken boven",
"removeMetadata": "Metagegevens wissen",
"subsetFonts": "Subset-lettertypen (ongebruikte tekens verwijderen)",
"removeThumbnails": "Ingesloten miniaturen verwijderen",
"compressButton": "PDF Comprimeren"
},
"pdfEditor": {
"name": "PDF Editor",
"subtitle": "PDFs annoteren, markeren, redigeren, commentaar toevoegen, vormen/afbeelding toevoegen, doorzoeken and weergeven."
},
"jpgToPdf": {
"name": "JPG naar PDF",
"subtitle": "Een of meer JPG-afbeeldingen opslaan als PDF."
},
"signPdf": {
"name": "PDF Ondertekenen",
"subtitle": "Je handtekening tekenen, typen, of invoegen."
},
"cropPdf": {
"name": "PDF Bijsnijden",
"subtitle": "De marges aanpassen van alle pagina's in je PDF."
},
"extractPages": {
"name": "Pagina's Extraheren",
"subtitle": "Een selectie van pagina's opslaan als nieuw bestand."
},
"duplicateOrganize": {
"name": "Dupliceren & Organiseren",
"subtitle": "Pagina's dupliceren, ordenen en verwijderen."
},
"deletePages": {
"name": "Pagina's Verwijderen",
"subtitle": "Specifieke pagina's uit een dodocument verwijderen."
},
"editBookmarks": {
"name": "Bladwijzers Bewerken",
"subtitle": "PDF-bladwijzers toevoegen, bewerken, importeren, verwijderen and extraheren."
},
"tableOfContents": {
"name": "Inhoudsopgave",
"subtitle": "Een inhoudsopgave genereren van PDF-bladwijzers."
},
"pageNumbers": {
"name": "Paginanummers",
"subtitle": "Paginanummers aan je document toevoegen."
},
"addWatermark": {
"name": "Watermerk toevoegen",
"subtitle": "Tekst of een afbeelding over de pagina's van je PDF stempelen."
},
"headerFooter": {
"name": "Koptekst & Voettekst",
"subtitle": "Tekst boven- of onderaan de pagina's toevoegen."
},
"invertColors": {
"name": "Kleuren Omkeren",
"subtitle": "Maak een \"donkere modus\"-versie van je PDF."
},
"backgroundColor": {
"name": "Achtergrondkleur",
"subtitle": "Wijzig de achtergrondkleur van je PDF."
},
"changeTextColor": {
"name": "Tekstkleur",
"subtitle": "Wijzig de tekstkleur van je PDF."
},
"addStamps": {
"name": "Stempels Toevoegen",
"subtitle": "Voeg afbeeldingsstempels toe aan je PDF met de werkbalk Annotatie.",
"usernameLabel": "Gebruikersnaam stempelen",
"usernamePlaceholder": "Voer je naam in (voor stempels)",
"usernameHint": "Deze naam verschijnt op stempels die jij aanmaakt."
},
"removeAnnotations": {
"name": "Annotaties Verwijderen",
"subtitle": "Commentaar, markeringen en links verwijderen."
},
"pdfFormFiller": {
"name": "PDF-formulier Vullen",
"subtitle": "Vul formulieren in vanuit de browser, incl. ondersteuning voor XFA-formulieren."
},
"createPdfForm": {
"name": "PDF-formulier Aanmaken",
"subtitle": "Maak invulbare PDF-formulieren aan met drag-and-drop tekstvelden."
},
"removeBlankPages": {
"name": "Blanco pagina's Verwijderen",
"subtitle": "Automatisch blanco pagina's detecteren en verwijderen."
},
"imageToPdf": {
"name": "Afbeelding naar PDF",
"subtitle": "Converteer JPG, PNG, BMP, GIF, TIFF, PNM, PGM, PBM, PPM, PAM, JXR, JPX, JP2, PSD, SVG, HEIC, WebP naar PDF."
},
"pngToPdf": {
"name": "PNG naar PDF",
"subtitle": "Maak een PDF van een of meer PNG-afbeeldingen."
},
"webpToPdf": {
"name": "WebP naar PDF",
"subtitle": "Maak een PDF van een of meer WebP-afbeeldingen."
},
"svgToPdf": {
"name": "SVG naar PDF",
"subtitle": "Maak een PDF van een of meer SVG-afbeeldingen."
},
"bmpToPdf": {
"name": "BMP naar PDF",
"subtitle": "Maak een PDF van een of meer BMP-afbeeldingen."
},
"heicToPdf": {
"name": "HEIC naar PDF",
"subtitle": "Maak een PDF van een of meer HEIC-afbeeldingen."
},
"tiffToPdf": {
"name": "TIFF naar PDF",
"subtitle": "Maak een PDF van een of meer TIFF-afbeeldingen."
},
"textToPdf": {
"name": "Tekst naar PDF",
"subtitle": "Converteer een bestand met platte tekst naar een PDF."
},
"jsonToPdf": {
"name": "JSON naar PDF",
"subtitle": "Converteer JSON-bestanden naar PDF-formaat."
},
"pdfToJpg": {
"name": "PDF naar JPG",
"subtitle": "Converteer elke PDF-pagina naar een JPG-afbeelding."
},
"pdfToPng": {
"name": "PDF naar PNG",
"subtitle": "Converteer elke PDF-pagina naar een PNG-afbeelding."
},
"pdfToWebp": {
"name": "PDF naar WebP",
"subtitle": "Converteer elke PDF-pagina naar een WebP-afbeelding."
},
"pdfToBmp": {
"name": "PDF naar BMP",
"subtitle": "Converteer elke PDF-pagina naar een BMP-afbeelding."
},
"pdfToTiff": {
"name": "PDF naar TIFF",
"subtitle": "Converteer elke PDF-pagina naar een TIFF-afbeelding."
},
"pdfToGreyscale": {
"name": "PDF naar Grijswaarden",
"subtitle": "Converteer alle kleuren naar zwart-wit."
},
"pdfToJson": {
"name": "PDF naar JSON",
"subtitle": "Converteer PDF-bestanden naar JSON-formaat."
},
"ocrPdf": {
"name": "OCR PDF",
"subtitle": "Maak een PDF doorzoekbaar en kopieerbaar."
},
"alternateMix": {
"name": "Pagina's Afwisselen & Mixen",
"subtitle": "Voeg PDF's samen met afwisselende pagina's vanuit elke PDF. Bladwijzers behouden."
},
"addAttachments": {
"name": "Bijlagen Toevoegen",
"subtitle": "Voeg een of meerbestanden in je PDF."
},
"extractAttachments": {
"name": "Bijlagen Extraheren",
"subtitle": "Extraheer alle ingevoegde bestanden als ZIP uit PDF(-en)."
},
"editAttachments": {
"name": "Bijlagen Bewerken",
"subtitle": "Bijlagen in je PDF weergeven of verwijderen."
},
"dividePages": {
"name": "Paginas Opdelen",
"subtitle": "Pagina's horizontaal of verticaal opdelen."
},
"addBlankPage": {
"name": "Blanco pagina Toevoegen",
"subtitle": "Een blanco pagina ergens in je PDF invoegen."
},
"reversePages": {
"name": "Paginavolgorde Omkeren",
"subtitle": "Keer de volgorde om van alle pagina's in je document."
},
"rotatePdf": {
"name": "PDF Roteren",
"subtitle": "Roteer pagina's in stappen van 90 gradden."
},
"rotateCustom": {
"name": "Roteren met aangepaste hoek",
"subtitle": "Roteer pagina's met elke gewenste hoek."
},
"nUpPdf": {
"name": "N+ PDF",
"subtitle": "Arrangeer meerdere pagina's op een enkel blad."
},
"combineToSinglePage": {
"name": "Combineren tot Enkele pagina",
"subtitle": "Plak alle pagina's aan elkaar tot een doorlopende rol."
},
"viewMetadata": {
"name": "Metadata Weergeven",
"subtitle": "Bekijk de verborgen eigenschappen van je PDF."
},
"editMetadata": {
"name": "Metadata Bewerken",
"subtitle": "Auteur, titel en andere eigenschappen aanpassen."
},
"pdfsToZip": {
"name": "PDF naar ZIP",
"subtitle": "Archiveer meerdere PDF's in een ZIP-bestand."
},
"comparePdfs": {
"name": "PDF's Vergelijken",
"subtitle": "Twee PDF's zij-aan-zij vergelijken."
},
"posterizePdf": {
"name": "PDF-Poster",
"subtitle": "Deel een grote pagina op in meerdere kleinere pagina's."
},
"fixPageSize": {
"name": "Paginagrootte Fiksen",
"subtitle": "Alle pagina's aanpassen tot een uniform formaat."
},
"linearizePdf": {
"name": "PDF Lineariseren",
"subtitle": "Optimaliseer een PDF voor snelle webweergave."
},
"pageDimensions": {
"name": "Pagina Afmetingen",
"subtitle": "Maak een analyse van paginagrootte, oriëntatie en eenheden."
},
"removeRestrictions": {
"name": "Beperkingen Verwijderen",
"subtitle": "Beveiligingswachtwoord en -beperkingen verwijderen van digitaal ondertekende PDF-bestanden."
},
"repairPdf": {
"name": "PDF Repareren",
"subtitle": "Gegevens herstellen van beschadigde PDF-bestanden."
},
"encryptPdf": {
"name": "PDF Versleutelen",
"subtitle": "Vegrendel je PDF door toevoeging van een toegangswachtwoord."
},
"sanitizePdf": {
"name": "PDF Opschonen",
"subtitle": "Metadata, annotaties, scripts en meer verwijderen."
},
"decryptPdf": {
"name": "PDF Ontgrendelen",
"subtitle": "PDF ontgrendelen door het verwijderen van de wachtwoordbeveiliging."
},
"flattenPdf": {
"name": "PDF Platmaken",
"subtitle": "Maak formuliervelden en annotaties onbewerkbaar."
},
"removeMetadata": {
"name": "Metadata Verwijderen",
"subtitle": "Verwijder verborgen gegevens uit je PDF."
},
"changePermissions": {
"name": "Rechten Aanpassen",
"subtitle": "Gebruikersrechten van een PDF instellen of aanpasssen."
},
"odtToPdf": {
"name": "ODT naar PDF",
"subtitle": "Converteer OpenDocument-tekstbestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "ODT-bestanden",
"convertButton": "Omzetten naar PDF"
},
"csvToPdf": {
"name": "CSV naar PDF",
"subtitle": "Converteer CSV-spreadsheetbestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "CSV-bestanden",
"convertButton": "Omzetten naar PDF"
},
"rtfToPdf": {
"name": "RTF naar PDF",
"subtitle": "Converteer Rich Text Format-documenten naar PDF. Ondersteunt meerdere bestanden.",
"acceptedFormats": "RTF-bestanden",
"convertButton": "Omzetten naar PDF"
},
"wordToPdf": {
"name": "Word naar PDF",
"subtitle": "Converteer Word-documenten (DOCX, DOC, ODT, RTF) naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "DOCX-, DOC-, ODT-, RTF-bestanden",
"convertButton": "Omzetten naar PDF"
},
"excelToPdf": {
"name": "Excel naar PDF",
"subtitle": "Converteer Excel-spreadsheets (XLSX, XLS, ODS, CSV) naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "XLSX-, XLS-, ODS-, CSV-bestanden",
"convertButton": "Omzetten naar PDF"
},
"powerpointToPdf": {
"name": "PowerPoint naar PDF",
"subtitle": "Converteer PowerPoint-presentaties (PPTX, PPT, ODP) naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "PPTX-, PPT-, ODP-bestanden",
"convertButton": "Omzetten naar PDF"
},
"markdownToPdf": {
"name": "Markdown naar PDF",
"subtitle": "Schrijf of plak Markdown en zet het om in een opgemaakte PDF.",
"paneMarkdown": "Markdown",
"panePreview": "Voorbeeld",
"btnUpload": "Laden",
"btnSyncScroll": "Synchroon bladeren",
"btnSettings": "Instellingen",
"btnExportPdf": "PDF exporteren",
"settingsTitle": "Markdown-instellingen",
"settingsPreset": "Voorinstelling",
"presetDefault": "Standaard (als GFM)",
"presetCommonmark": "CommonMark (strict)",
"presetZero": "Minimaal (geen functies)",
"settingsOptions": "Markdown-opties",
"optAllowHtml": "HTML-tags toestaan",
"optBreaks": "Newlines omzetten naar <br>",
"optLinkify": "URL's autom. omzetten naar links",
"optTypographer": "Typograaf (slimme aanhalingstekens, etc.)"
},
"pdfBooklet": {
"name": "PDF Boekje",
"subtitle": "Herschik de pagina's voor dubbelzijdig boekjeprinten. Vouw en niet ze om een boekje te maken.",
"howItWorks": "Het werkt zo:",
"step1": "Laad een PDF-bestand.",
"step2": "Pagina's worden in brochurevolgorde gerangschikt.",
"step3": "Dubbelzijdig afdrukken, omdraaien langs de korte kant, vouwen en nieten.",
"paperSize": "Paperformaat",
"orientation": "Ori<72>ntatie",
"portrait": "Staand",
"landscape": "Liggend",
"pagesPerSheet": "Pagina's per vel",
"createBooklet": "Boekje aanmaken",
"processing": "Verwerken...",
"pageCount": "Het aantal pagina's wordt indien nodig op een meervoud van 4 afgerond."
},
"xpsToPdf": {
"name": "XPS naar PDF",
"subtitle": "Converteer XPS/OXPS-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "XPS-, OXPS-bestanden",
"convertButton": "Omzetten naar PDF"
},
"mobiToPdf": {
"name": "MOBI naar PDF",
"subtitle": "Converteer MOBI e-books naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "MOBI-bestanden",
"convertButton": "Omzetten naar PDF"
},
"epubToPdf": {
"name": "EPUB naar PDF",
"subtitle": "Zet EPUB-e-books om naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "EPUB-bestanden",
"convertButton": "Omzetten naar PDF"
},
"fb2ToPdf": {
"name": "FB2 naar PDF",
"subtitle": "Converteer FictionBook (FB2) e-boeken naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "FB2-bestanden",
"convertButton": "Omzetten naar PDF"
},
"cbzToPdf": {
"name": "CBZ naar PDF",
"subtitle": "Converteer stripboekenarchieven (CBZ/CBR) naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "CBZ-, CBR-bestanden",
"convertButton": "Omzetten naar PDF"
},
"wpdToPdf": {
"name": "WPD naar PDF",
"subtitle": "Converteer WordPerfect-documenten (WPD) naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "WPD-bestanden",
"convertButton": "Omzetten naar PDF"
},
"wpsToPdf": {
"name": "WPS naar PDF",
"subtitle": "Converteer WPS Office-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "WPS-bestanden",
"convertButton": "Omzetten naar PDF"
},
"xmlToPdf": {
"name": "XML naar PDF",
"subtitle": "Converteer XML-documenten naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "XML-bestanden",
"convertButton": "Omzetten naar PDF"
},
"pagesToPdf": {
"name": "Pages naar PDF",
"subtitle": "Zet Apple Pages-documenten om naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "Pages-bestanden",
"convertButton": "Omzetten naar PDF"
},
"odgToPdf": {
"name": "ODG naar PDF",
"subtitle": "Converteer OpenDocument Graphics (ODG)-bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "ODG-bestanden",
"convertButton": "Omzetten naar PDF"
},
"odsToPdf": {
"name": "ODS naar PDF",
"subtitle": "Converteer OpenDocument Spreadsheet (ODS)-bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "ODS-bestanden",
"convertButton": "Omzetten naar PDF"
},
"odpToPdf": {
"name": "ODP naar PDF",
"subtitle": "Converteer OpenDocument-presentatie (ODP) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "ODP-bestanden",
"convertButton": "Omzetten naar PDF"
},
"pubToPdf": {
"name": "PUB naar PDF",
"subtitle": "Converteer Microsoft Publisher (PUB) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "PUB-bestanden",
"convertButton": "Omzetten naar PDF"
},
"vsdToPdf": {
"name": "VSD naar PDF",
"subtitle": "Converteer Microsoft Visio (VSD, VSDX) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "VSD-, VSDX-bestanden",
"convertButton": "Omzetten naar PDF"
},
"psdToPdf": {
"name": "PSD naar PDF",
"subtitle": "Converteer Adobe Photoshop (PSD) bestanden naar PDF-formaat. Ondersteunt meerdere bestanden.",
"acceptedFormats": "PSD-bestanden",
"convertButton": "Omzetten naar PDF"
},
"pdfToSvg": {
"name": "PDF naar SVG",
"subtitle": "Converteer elke pagina van een PDF-bestand naar een schaalbare vectorafbeelding (SVG) voor perfecte kwaliteit op elke grootte."
},
"extractTables": {
"name": "PDF-tabellen extraheren",
"subtitle": "Haal tabellen uit PDF-bestanden en exporteer ze als CSV, JSON of Markdown."
},
"pdfToCsv": {
"name": "PDF naar CSV",
"subtitle": "Haal tabellen uit een PDF en zet ze om naar CSV-formaat."
},
"pdfToExcel": {
"name": "PDF naar Excel",
"subtitle": "Haal tabellen uit PDF en converteer ze naar Excel (XLSX) formaat."
},
"pdfToText": {
"name": "PDF naar Text",
"subtitle": "Haal tekst uit PDF-bestanden en sla op als gewone tekst (.txt). Ondersteunt meerdere bestanden.",
"note": "Dit hulpmiddel werkt ALLEEN met digitaal gemaakte PDF's. Gebruik voor gescande documenten of op afbeeldingen gebaseerde PDF's in plaats hiervan de OCR PDF-tool.",
"convertButton": "Tekst extraheren"
},
"digitalSignPdf": {
"name": "Digitale handtekening PDF",
"pageTitle": "Digitale handtekening PDF - Cryptografische handtekening toevoegen | BentoPDF",
"subtitle": "Voeg een cryptografische digitale handtekening toe aan je PDF met behulp van X.509-certificaten. Ondersteunt PKCS#12 (.pfx, .p12) en PEM-formaten. Je priv<69>sleutel verlaat jouw browser nooit.",
"certificateSection": "Certificaat",
"uploadCert": "Certificaat laden (.pfx, .p12)",
"certPassword": "Certificaatwachtwoord",
"certPasswordPlaceholder": "Voer het wachtwoord van het certificaat in",
"certInfo": "Certificaatinformatie",
"certSubject": "Onderwerp",
"certIssuer": "Uitgever",
"certValidity": "Geldigheid",
"signatureDetails": "Signature Details (Optioneel)",
"reason": "Reden",
"reasonPlaceholder": "bijv., Ik keur dit document goed",
"location": "Plaats",
"locationPlaceholder": "bijv., New York, USA",
"contactInfo": "Contactinformatie",
"contactPlaceholder": "bijv., email@example.com",
"applySignature": "Digitale handtekening toepassen",
"successMessage": "PDF succesvol ondertekend! De handtekening kan in elke PDF-lezer worden geverifieerd."
},
"validateSignaturePdf": {
"name": "PDF-handtekening valideren",
"pageTitle": "PDF-handtekening valideren - Digitale handtekeningen verifi<66>ren | BentoPDF",
"subtitle": "Digitale handtekeningen in je PDF-bestanden verifi<66>ren. Controleer de geldigheid van het certificaat, bekijk de gegevens van de ondertekenaar en bevestig de integriteit van het document. Alle verwerking gebeurt binnen jouw browser."
},
"emailToPdf": {
"name": "E-mail naar PDF",
"subtitle": "Converteer e-mailbestanden (EML, MSG) naar PDF-formaat. Ondersteunt Outlook-exports en standaard e-mailformaten.",
"acceptedFormats": "EML-, MSG-bestanden",
"convertButton": "Omzetten naar PDF"
},
"fontToOutline": {
"name": "Lettertype naar Contour",
"subtitle": "Converteer alle lettertypen naar vector-contouren voor consistente weergave over alle apparaten."
},
"deskewPdf": {
"name": "PDF rechttrekken",
"subtitle": "Automatisch rechttrekken van scheef gescande pagina's met OpenCV."
}
}

View File

@@ -0,0 +1,325 @@
{
"nav": {
"home": "Início",
"about": "Sobre",
"contact": "Contato",
"licensing": "Licenciamento",
"allTools": "Todas as Ferramentas",
"openMainMenu": "Abrir menu principal",
"language": "Idioma"
},
"donation": {
"message": "Adora o BentoPDF? Ajude-nos a mantê-lo gratuito e de código aberto!",
"button": "Doar"
},
"hero": {
"title": "O",
"pdfToolkit": "Kit de Ferramentas PDF",
"builtForPrivacy": "feito para sua privacidade",
"noSignups": "Sem Cadastros",
"unlimitedUse": "Uso Ilimitado",
"worksOffline": "Funciona Offline",
"startUsing": "Comece a Usar Agora"
},
"usedBy": {
"title": "Usado por empresas e pessoas que trabalham em"
},
"features": {
"title": "Por que escolher o",
"bentoPdf": "BentoPDF?",
"noSignup": {
"title": "Sem Cadastro",
"description": "Comece instantaneamente, sem contas ou e-mails."
},
"noUploads": {
"title": "Sem Uploads",
"description": "100% no navegador, seus arquivos nunca saem do seu dispositivo."
},
"foreverFree": {
"title": "Sempre Grátis",
"description": "Todas as ferramentas, sem testes ou assinaturas."
},
"noLimits": {
"title": "Sem Limites",
"description": "Use o quanto quiser, sem taxas escondidas."
},
"batchProcessing": {
"title": "Processamento em Lote",
"description": "Gerencie vários PDFs de uma só vez."
},
"lightningFast": {
"title": "Super Rápido",
"description": "Processe PDFs instantaneamente, sem esperas ou atrasos."
}
},
"tools": {
"title": "Comece com as",
"toolsLabel": "Ferramentas",
"subtitle": "Clique em uma ferramenta para abrir o seletor de arquivos",
"searchPlaceholder": "Buscar ferramenta (ex: 'dividir', 'organizar'...)",
"backToTools": "Voltar para Ferramentas",
"firstLoadNotice": "O primeiro carregamento demora um momento enquanto baixamos nosso mecanismo de conversão. Depois disso, todos os carregamentos serão instantâneos."
},
"upload": {
"clickToSelect": "Clique para selecionar um arquivo",
"orDragAndDrop": "ou arraste e solte",
"pdfOrImages": "PDFs ou Imagens",
"filesNeverLeave": "Seus arquivos nunca saem do seu dispositivo.",
"addMore": "Adicionar Mais Arquivos",
"clearAll": "Limpar Tudo",
"clearFiles": "Limpar arquivos"
},
"loader": {
"processing": "Processando..."
},
"alert": {
"title": "Alerta",
"ok": "OK"
},
"preview": {
"title": "Visualização do Documento",
"downloadAsPdf": "Baixar como PDF",
"close": "Fechar"
},
"settings": {
"title": "Configurações",
"shortcuts": "Atalhos",
"preferences": "Preferências",
"displayPreferences": "Preferências de Exibição",
"searchShortcuts": "Buscar atalhos...",
"shortcutsInfo": "Mantenha as teclas pressionadas para definir um atalho. As alterações são salvas automaticamente.",
"shortcutsWarning": "⚠️ Evite atalhos comuns do navegador (Cmd/Ctrl+W, T, N etc.), pois podem não funcionar corretamente.",
"import": "Importar",
"export": "Exportar",
"resetToDefaults": "Restaurar Padrões",
"fullWidthMode": "Modo Largura Total",
"fullWidthDescription": "Usa toda a largura da tela para as ferramentas em vez de um container centralizado",
"settingsAutoSaved": "As configurações são salvas automaticamente",
"clickToSet": "Clique para definir",
"pressKeys": "Pressione as teclas...",
"warnings": {
"alreadyInUse": "Atalho Já em Uso",
"assignedTo": "já está atribuído a:",
"chooseDifferent": "Por favor, escolha um atalho diferente.",
"reserved": "Aviso de Atalho Reservado",
"commonlyUsed": "é comumente usado para:",
"unreliable": "Este atalho pode não funcionar bem ou conflitar com o navegador/sistema.",
"useAnyway": "Deseja usar mesmo assim?",
"resetTitle": "Redefinir Atalhos",
"resetMessage": "Tem certeza que deseja redefinir todos os atalhos?<br><br>Esta ação não pode ser desfeita.",
"importSuccessTitle": "Importação Concluída",
"importSuccessMessage": "Atalhos importados com sucesso!",
"importFailTitle": "Falha na Importação",
"importFailMessage": "Falha ao importar atalhos. Formato de arquivo inválido."
}
},
"warning": {
"title": "Aviso",
"cancel": "Cancelar",
"proceed": "Prosseguir"
},
"compliance": {
"title": "Seus dados nunca saem do seu dispositivo",
"weKeep": "Mantemos",
"yourInfoSafe": "suas informações seguras",
"byFollowingStandards": "seguindo padrões globais de segurança.",
"processingLocal": "Todo o processamento acontece localmente no seu dispositivo.",
"gdpr": {
"title": "Conformidade GDPR",
"description": "Protege os dados pessoais e a privacidade de indivíduos na União Europeia."
},
"ccpa": {
"title": "Conformidade CCPA",
"description": "Dá aos residentes da Califórnia direitos sobre como suas informações pessoais são coletadas e usadas."
},
"hipaa": {
"title": "Conformidade HIPAA",
"description": "Estabelece salvaguardas para o tratamento de informações de saúde sensíveis nos Estados Unidos."
}
},
"faq": {
"title": "Perguntas",
"questions": "Frequentes",
"isFree": {
"question": "O BentoPDF é realmente grátis?",
"answer": "Sim, com certeza. Todas as ferramentas do BentoPDF são 100% gratuitas, sem limites de arquivos, sem cadastros e sem marcas d'água. Acreditamos que todos merecem acesso a ferramentas PDF poderosas sem barreiras financeiras."
},
"areFilesSecure": {
"question": "Meus arquivos estão seguros? Onde são processados?",
"answer": "Seus arquivos estão o mais seguros possível porque nunca saem do seu computador. Todo o processamento ocorre diretamente no seu navegador (client-side). Nunca fazemos upload para um servidor, garantindo privacidade total."
},
"platforms": {
"question": "Funciona no Mac, Windows e Celular?",
"answer": "Sim! Como o BentoPDF roda inteiramente no navegador, funciona em qualquer sistema operacional moderno, incluindo Windows, macOS, Linux, iOS e Android."
},
"gdprCompliant": {
"question": "O BentoPDF está em conformidade com a GDPR?",
"answer": "Sim. Como o processamento é local e não coletamos seus arquivos, não temos acesso aos seus dados. Isso garante total conformidade e controle por parte do usuário."
},
"dataStorage": {
"question": "Vocês armazenam ou rastreiam meus arquivos?",
"answer": "Não. Nunca armazenamos, rastreamos ou registramos seus arquivos. Tudo acontece na memória do navegador e desaparece ao fechar a página. Não há logs nem servidores envolvidos."
},
"different": {
"question": "O que torna o BentoPDF diferente de outras ferramentas?",
"answer": "A maioria das ferramentas faz upload dos arquivos para um servidor. O BentoPDF usa tecnologia web moderna para processar tudo localmente no seu navegador, garantindo mais velocidade e privacidade."
},
"browserBased": {
"question": "Como o processamento no navegador me mantém seguro?",
"answer": "Ao rodar no seu dispositivo, eliminamos riscos de ataques a servidores ou vazamentos de dados de terceiros. Seus arquivos permanecem seus — sempre."
},
"analytics": {
"question": "Vocês usam cookies ou rastreamento?",
"answer": "Usamos apenas o Simple Analytics para contar visitas de forma anônima. Sabemos quantos usuários nos visitam, mas nunca quem você é. O sistema respeita totalmente a GDPR."
}
},
"testimonials": {
"title": "O que nossos",
"users": "Usuários",
"say": "Dizem"
},
"support": {
"title": "Gostou do Trabalho?",
"description": "O BentoPDF é um projeto pessoal feito para fornecer ferramentas poderosas e privadas para todos. Se for útil para você, considere apoiar o desenvolvimento!",
"buyMeCoffee": "Pague um Café"
},
"footer": {
"copyright": "© 2026 BentoPDF. Todos os direitos reservados.",
"version": "Versão",
"company": "Empresa",
"aboutUs": "Sobre Nós",
"faqLink": "FAQ",
"contactUs": "Contato",
"legal": "Jurídico",
"termsAndConditions": "Termos e Condições",
"privacyPolicy": "Política de Privacidade",
"followUs": "Siga-nos"
},
"merge": {
"title": "Mesclar PDFs",
"description": "Combine arquivos inteiros ou selecione páginas específicas para criar um novo documento.",
"fileMode": "Modo Arquivo",
"pageMode": "Modo Página",
"howItWorks": "Como funciona:",
"fileModeInstructions": [
"Clique e arraste o ícone para alterar a ordem dos arquivos.",
"No campo \"Páginas\", você pode definir intervalos (ex: \"1-3, 5\") para mesclar apenas essas páginas.",
"Deixe o campo em branco para incluir todas as páginas do arquivo."
],
"pageModeInstructions": [
"Todas as páginas dos PDFs enviados aparecem abaixo.",
"Arraste as miniaturas para criar a ordem exata que deseja no novo arquivo."
],
"mergePdfs": "Mesclar PDFs"
},
"common": {
"page": "Página",
"pages": "Páginas",
"of": "de",
"download": "Baixar",
"cancel": "Cancelar",
"save": "Salvar",
"delete": "Excluir",
"edit": "Editar",
"add": "Adicionar",
"remove": "Remover",
"loading": "Carregando...",
"error": "Erro",
"success": "Sucesso",
"file": "Arquivo",
"files": "Arquivos",
"close": "Fechar"
},
"about": {
"hero": {
"title": "Acreditamos que ferramentas PDF devem ser",
"subtitle": "rápidas, privadas e gratuitas.",
"noCompromises": "Sem concessões."
},
"mission": {
"title": "Nossa Missão",
"description": "Fornecer o kit de ferramentas PDF mais completo, respeitando sua privacidade e sem cobrar por isso. Ferramentas essenciais devem ser acessíveis a todos, sem barreiras."
},
"philosophy": {
"label": "Nossa Filosofia",
"title": "Privacidade Primeiro. Sempre.",
"description": "Em uma era onde dados são mercadoria, seguimos outro caminho. Todo o processamento ocorre no seu navegador. Arquivos não tocam nossos servidores e não rastreamos você. Privacidade não é apenas um recurso; é nossa base."
},
"whyBentopdf": {
"title": "Por que o BentoPDF?",
"speed": {
"title": "Feito para Velocidade",
"description": "Sem esperas de upload. Usando tecnologias como WebAssembly, processamos tudo diretamente no navegador com velocidade inigualável."
},
"free": {
"title": "Totalmente Grátis",
"description": "Sem períodos de teste, assinaturas ou funções \"premium\" bloqueadas. Acreditamos em ferramentas como um serviço público."
},
"noAccount": {
"title": "Sem Necessidade de Conta",
"description": "Use qualquer ferramenta imediatamente. Não pedimos e-mail, senha ou qualquer dado pessoal. Seu fluxo de trabalho deve ser anônimo."
},
"openSource": {
"title": "Espírito Open Source",
"description": "Construído com transparência. Utilizamos bibliotecas incríveis como PDF-lib e PDF.js para democratizar o acesso a ferramentas poderosas."
}
},
"cta": {
"title": "Pronto para começar?",
"description": "Junte-se a milhares de usuários que confiam no BentoPDF. Sinta a diferença da privacidade e do desempenho.",
"button": "Explorar Ferramentas"
}
},
"contact": {
"title": "Entre em Contato",
"subtitle": "Adoraríamos ouvir você. Se tiver dúvidas, feedback ou sugestões de recursos, não hesite em nos contatar.",
"email": "Você pode nos contatar diretamente por e-mail em:"
},
"licensing": {
"title": "Licenciamento de",
"subtitle": "Escolha a licença que melhor atende às suas necessidades."
},
"multiTool": {
"uploadPdfs": "Enviar PDFs",
"upload": "Enviar",
"addBlankPage": "Adicionar Página em Branco",
"edit": "Editar:",
"undo": "Desfazer",
"redo": "Refazer",
"reset": "Redefinir",
"selection": "Seleção:",
"selectAll": "Selecionar Tudo",
"deselectAll": "Desmarcar Tudo",
"rotate": "Girar:",
"rotateLeft": "Esquerda",
"rotateRight": "Direita",
"transform": "Transformar:",
"duplicate": "Duplicar",
"split": "Dividir",
"clear": "Limpar:",
"delete": "Excluir",
"download": "Baixar:",
"downloadSelected": "Baixar Selecionadas",
"exportPdf": "Exportar PDF",
"uploadPdfFiles": "Selecionar Arquivos PDF",
"dragAndDrop": "Arraste arquivos PDF aqui ou clique para selecionar",
"selectFiles": "Selecionar Arquivos",
"renderingPages": "Renderizando páginas...",
"actions": {
"duplicatePage": "Duplicar esta página",
"deletePage": "Excluir esta página",
"insertPdf": "Inserir PDF após esta página",
"toggleSplit": "Alternar divisão após esta página"
},
"pleaseWait": "Aguarde",
"pagesRendering": "As páginas ainda estão sendo renderizadas. Por favor, aguarde...",
"noPagesSelected": "Nenhuma Página Selecionada",
"selectOnePage": "Selecione pelo menos uma página para baixar.",
"noPages": "Sem Páginas",
"noPagesToExport": "Não há páginas para exportar.",
"renderingTitle": "Renderizando visualizações das páginas",
"errorRendering": "Falha ao renderizar miniaturas das páginas",
"error": "Erro",
"failedToLoad": "Falha ao carregar"
}
}

View File

@@ -0,0 +1,511 @@
{
"categories": {
"popularTools": "Ferramentas Populares",
"editAnnotate": "Editar e Anotar",
"convertToPdf": "Converter para PDF",
"convertFromPdf": "Converter de PDF",
"organizeManage": "Organizar e Gerenciar",
"optimizeRepair": "Otimizar e Reparar",
"securePdf": "Segurança de PDF"
},
"pdfMultiTool": {
"name": "Multiferramenta PDF",
"subtitle": "Mesclar, dividir, organizar, excluir, girar, adicionar páginas em branco, extrair e duplicar em uma única interface."
},
"mergePdf": {
"name": "Mesclar PDF",
"subtitle": "Combine vários PDFs em um único arquivo. Preserva os favoritos (bookmarks)."
},
"splitPdf": {
"name": "Dividir PDF",
"subtitle": "Extraia um intervalo de páginas para um novo PDF."
},
"compressPdf": {
"name": "Comprimir PDF",
"subtitle": "Reduza o tamanho do arquivo do seu PDF."
},
"pdfEditor": {
"name": "Editor de PDF",
"subtitle": "Anotar, destacar, redigir, comentar, adicionar formas/imagens, pesquisar e visualizar PDFs."
},
"jpgToPdf": {
"name": "JPG para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens JPG."
},
"signPdf": {
"name": "Assinar PDF",
"subtitle": "Desenhe, digite ou faça upload da sua assinatura."
},
"cropPdf": {
"name": "Cortar PDF",
"subtitle": "Corte as margens de cada página do seu PDF."
},
"extractPages": {
"name": "Extrair Páginas",
"subtitle": "Salve uma seleção de páginas como novos arquivos."
},
"duplicateOrganize": {
"name": "Duplicar e Organizar",
"subtitle": "Duplique, reordene e exclua páginas."
},
"deletePages": {
"name": "Excluir Páginas",
"subtitle": "Remova páginas específicas do seu documento."
},
"editBookmarks": {
"name": "Editar Favoritos",
"subtitle": "Adicione, edite, importe, exclua e extraia favoritos de PDF."
},
"tableOfContents": {
"name": "Sumário",
"subtitle": "Gere uma página de sumário a partir dos favoritos do PDF."
},
"pageNumbers": {
"name": "Números de Página",
"subtitle": "Insira números de página no seu documento."
},
"addWatermark": {
"name": "Adicionar Marca d'Água",
"subtitle": "Carimbe texto ou uma imagem sobre as páginas do seu PDF."
},
"headerFooter": {
"name": "Cabeçalho e Rodapé",
"subtitle": "Adicione texto no topo e no final das páginas."
},
"invertColors": {
"name": "Inverter Cores",
"subtitle": "Crie uma versão em \"modo escuro\" do seu PDF."
},
"backgroundColor": {
"name": "Cor de Fundo",
"subtitle": "Altere a cor de fundo do seu PDF."
},
"changeTextColor": {
"name": "Alterar Cor do Texto",
"subtitle": "Altere a cor do texto no seu PDF."
},
"addStamps": {
"name": "Adicionar Carimbos",
"subtitle": "Adicione carimbos de imagem ao seu PDF usando a barra de ferramentas de anotação.",
"usernameLabel": "Nome do Usuário no Carimbo",
"usernamePlaceholder": "Digite seu nome (para os carimbos)",
"usernameHint": "Este nome aparecerá nos carimbos que você criar."
},
"removeAnnotations": {
"name": "Remover Anotações",
"subtitle": "Remova comentários, destaques e links."
},
"pdfFormFiller": {
"name": "Preenchimento de Formulário",
"subtitle": "Preencha formulários diretamente no navegador. Também suporta formulários XFA."
},
"createPdfForm": {
"name": "Criar Formulário PDF",
"subtitle": "Crie formulários PDF preenchíveis com campos de texto de arrastar e soltar."
},
"removeBlankPages": {
"name": "Remover Páginas em Branco",
"subtitle": "Detecte e exclua automaticamente páginas em branco."
},
"imageToPdf": {
"name": "Imagem para PDF",
"subtitle": "Converta JPG, PNG, WebP, BMP, TIFF, SVG, HEIC para PDF."
},
"pngToPdf": {
"name": "PNG para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens PNG."
},
"webpToPdf": {
"name": "WebP para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens WebP."
},
"svgToPdf": {
"name": "SVG para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens SVG."
},
"bmpToPdf": {
"name": "BMP para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens BMP."
},
"heicToPdf": {
"name": "HEIC para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens HEIC."
},
"tiffToPdf": {
"name": "TIFF para PDF",
"subtitle": "Crie um PDF a partir de uma ou mais imagens TIFF."
},
"textToPdf": {
"name": "Texto para PDF",
"subtitle": "Converta um arquivo de texto simples (.txt) em PDF."
},
"jsonToPdf": {
"name": "JSON para PDF",
"subtitle": "Converta arquivos JSON para o formato PDF."
},
"pdfToJpg": {
"name": "PDF para JPG",
"subtitle": "Converta cada página do PDF em uma imagem JPG."
},
"pdfToPng": {
"name": "PDF para PNG",
"subtitle": "Converta cada página do PDF em uma imagem PNG."
},
"pdfToWebp": {
"name": "PDF para WebP",
"subtitle": "Converta cada página do PDF em uma imagem WebP."
},
"pdfToBmp": {
"name": "PDF para BMP",
"subtitle": "Converta cada página do PDF em uma imagem BMP."
},
"pdfToTiff": {
"name": "PDF para TIFF",
"subtitle": "Converta cada página do PDF em uma imagem TIFF."
},
"pdfToGreyscale": {
"name": "PDF para Tons de Cinza",
"subtitle": "Converta todas as cores para preto e branco."
},
"pdfToJson": {
"name": "PDF para JSON",
"subtitle": "Converta arquivos PDF para o formato JSON."
},
"ocrPdf": {
"name": "OCR PDF",
"subtitle": "Torne um PDF pesquisável e copiável (reconhecimento de texto)."
},
"alternateMix": {
"name": "Alternar e Misturar Páginas",
"subtitle": "Mescle PDFs alternando as páginas de cada arquivo. Preserva os favoritos."
},
"addAttachments": {
"name": "Adicionar Anexos",
"subtitle": "Incorpore um ou mais arquivos dentro do seu PDF."
},
"extractAttachments": {
"name": "Extrair Anexos",
"subtitle": "Extraia todos os arquivos incorporados de PDF(s) como um ZIP."
},
"editAttachments": {
"name": "Editar Anexos",
"subtitle": "Visualize ou remova anexos do seu PDF."
},
"dividePages": {
"name": "Dividir Páginas",
"subtitle": "Divida as páginas horizontalmente ou verticalmente."
},
"addBlankPage": {
"name": "Adicionar Página em Branco",
"subtitle": "Insira uma página vazia em qualquer lugar do seu PDF."
},
"reversePages": {
"name": "Inverter Páginas",
"subtitle": "Inverta a ordem de todas as páginas do seu documento."
},
"rotatePdf": {
"name": "Girar PDF",
"subtitle": "Gire as páginas em incrementos de 90 graus."
},
"nUpPdf": {
"name": "PDF N-Up",
"subtitle": "Organize várias páginas em uma única folha de impressão."
},
"combineToSinglePage": {
"name": "Combinar em Página Única",
"subtitle": "Costure todas as páginas em um único fluxo contínuo."
},
"viewMetadata": {
"name": "Ver Metadados",
"subtitle": "Inspecione as propriedades ocultas do seu PDF."
},
"editMetadata": {
"name": "Editar Metadados",
"subtitle": "Altere o autor, título e outras propriedades."
},
"pdfsToZip": {
"name": "PDFs para ZIP",
"subtitle": "Empacote vários arquivos PDF em um arquivo compactado ZIP."
},
"comparePdfs": {
"name": "Comparar PDFs",
"subtitle": "Compare dois PDFs lado a lado."
},
"posterizePdf": {
"name": "Posterizar PDF",
"subtitle": "Divida uma página grande em várias páginas menores."
},
"fixPageSize": {
"name": "Ajustar Tamanho da Página",
"subtitle": "Padronize todas as páginas para um tamanho uniforme."
},
"linearizePdf": {
"name": "Linearizar PDF",
"subtitle": "Otimize o PDF para visualização rápida na web."
},
"pageDimensions": {
"name": "Dimensões da Página",
"subtitle": "Analise o tamanho, orientação e unidades das páginas."
},
"removeRestrictions": {
"name": "Remover Restrições",
"subtitle": "Remova proteção por senha e restrições de segurança de arquivos assinados digitalmente."
},
"repairPdf": {
"name": "Reparar PDF",
"subtitle": "Recupere dados de arquivos PDF corrompidos ou danificados."
},
"encryptPdf": {
"name": "Criptografar PDF",
"subtitle": "Bloqueie seu PDF adicionando uma senha."
},
"sanitizePdf": {
"name": "Sanitizar PDF",
"subtitle": "Remova metadados, anotações, scripts e outros dados ocultos."
},
"decryptPdf": {
"name": "Descriptografar PDF",
"subtitle": "Desbloqueie o PDF removendo a proteção por senha."
},
"flattenPdf": {
"name": "Achatar PDF (Flatten)",
"subtitle": "Torne os campos de formulário e anotações não editáveis."
},
"removeMetadata": {
"name": "Remover Metadados",
"subtitle": "Limpe dados ocultos do seu PDF."
},
"changePermissions": {
"name": "Alterar Permissões",
"subtitle": "Defina ou altere as permissões de usuário em um PDF."
},
"emailToPdf": {
"name": "Email para PDF",
"subtitle": "Converta arquivos de email (EML, MSG) para PDF. Suporta exportações do Outlook e formatos de email padrão.",
"acceptedFormats": "Arquivos EML, MSG",
"convertButton": "Converter para PDF"
},
"fontToOutline": {
"name": "Fonte para Contorno",
"subtitle": "Converta todas as fontes em contornos vetoriais para renderização consistente em todos os dispositivos."
},
"deskewPdf": {
"name": "Desinclinar PDF",
"subtitle": "Endireite automaticamente páginas digitalizadas inclinadas usando OpenCV."
},
"rotateCustom": {
"name": "Rotate by Custom Degrees",
"subtitle": "Rotate pages by any custom angle."
},
"odtToPdf": {
"name": "ODT to PDF",
"subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.",
"acceptedFormats": "ODT files",
"convertButton": "Convert to PDF"
},
"csvToPdf": {
"name": "CSV to PDF",
"subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.",
"acceptedFormats": "CSV files",
"convertButton": "Convert to PDF"
},
"rtfToPdf": {
"name": "RTF to PDF",
"subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.",
"acceptedFormats": "RTF files",
"convertButton": "Convert to PDF"
},
"wordToPdf": {
"name": "Word to PDF",
"subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.",
"acceptedFormats": "DOCX, DOC, ODT, RTF files",
"convertButton": "Convert to PDF"
},
"excelToPdf": {
"name": "Excel to PDF",
"subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.",
"acceptedFormats": "XLSX, XLS, ODS, CSV files",
"convertButton": "Convert to PDF"
},
"powerpointToPdf": {
"name": "PowerPoint to PDF",
"subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.",
"acceptedFormats": "PPTX, PPT, ODP files",
"convertButton": "Convert to PDF"
},
"markdownToPdf": {
"name": "Markdown to PDF",
"subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.",
"paneMarkdown": "Markdown",
"panePreview": "Preview",
"btnUpload": "Upload",
"btnSyncScroll": "Sync Scroll",
"btnSettings": "Settings",
"btnExportPdf": "Export PDF",
"settingsTitle": "Markdown Settings",
"settingsPreset": "Preset",
"presetDefault": "Default (GFM-like)",
"presetCommonmark": "CommonMark (strict)",
"presetZero": "Minimal (no features)",
"settingsOptions": "Markdown Options",
"optAllowHtml": "Allow HTML tags",
"optBreaks": "Convert newlines to <br>",
"optLinkify": "Auto-convert URLs to links",
"optTypographer": "Typographer (smart quotes, etc.)"
},
"pdfBooklet": {
"name": "PDF Booklet",
"subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.",
"howItWorks": "How it works:",
"step1": "Upload a PDF file.",
"step2": "Pages will be rearranged in booklet order.",
"step3": "Print double-sided, flip on short edge, fold and staple.",
"paperSize": "Paper Size",
"orientation": "Orientation",
"portrait": "Portrait",
"landscape": "Landscape",
"pagesPerSheet": "Pages per Sheet",
"createBooklet": "Create Booklet",
"processing": "Processing...",
"pageCount": "Page count will be padded to multiple of 4 if needed."
},
"xpsToPdf": {
"name": "XPS to PDF",
"subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.",
"acceptedFormats": "XPS, OXPS files",
"convertButton": "Convert to PDF"
},
"mobiToPdf": {
"name": "MOBI to PDF",
"subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.",
"acceptedFormats": "MOBI files",
"convertButton": "Convert to PDF"
},
"epubToPdf": {
"name": "EPUB to PDF",
"subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.",
"acceptedFormats": "EPUB files",
"convertButton": "Convert to PDF"
},
"fb2ToPdf": {
"name": "FB2 to PDF",
"subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.",
"acceptedFormats": "FB2 files",
"convertButton": "Convert to PDF"
},
"cbzToPdf": {
"name": "CBZ to PDF",
"subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.",
"acceptedFormats": "CBZ, CBR files",
"convertButton": "Convert to PDF"
},
"wpdToPdf": {
"name": "WPD to PDF",
"subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.",
"acceptedFormats": "WPD files",
"convertButton": "Convert to PDF"
},
"wpsToPdf": {
"name": "WPS to PDF",
"subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.",
"acceptedFormats": "WPS files",
"convertButton": "Convert to PDF"
},
"xmlToPdf": {
"name": "XML to PDF",
"subtitle": "Convert XML documents to PDF format. Supports multiple files.",
"acceptedFormats": "XML files",
"convertButton": "Convert to PDF"
},
"pagesToPdf": {
"name": "Pages to PDF",
"subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.",
"acceptedFormats": "Pages files",
"convertButton": "Convert to PDF"
},
"odgToPdf": {
"name": "ODG to PDF",
"subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODG files",
"convertButton": "Convert to PDF"
},
"odsToPdf": {
"name": "ODS to PDF",
"subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODS files",
"convertButton": "Convert to PDF"
},
"odpToPdf": {
"name": "ODP to PDF",
"subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODP files",
"convertButton": "Convert to PDF"
},
"pubToPdf": {
"name": "PUB to PDF",
"subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.",
"acceptedFormats": "PUB files",
"convertButton": "Convert to PDF"
},
"vsdToPdf": {
"name": "VSD to PDF",
"subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.",
"acceptedFormats": "VSD, VSDX files",
"convertButton": "Convert to PDF"
},
"psdToPdf": {
"name": "PSD to PDF",
"subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.",
"acceptedFormats": "PSD files",
"convertButton": "Convert to PDF"
},
"pdfToSvg": {
"name": "PDF to SVG",
"subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size."
},
"extractTables": {
"name": "Extract PDF Tables",
"subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown."
},
"pdfToCsv": {
"name": "PDF to CSV",
"subtitle": "Extract tables from PDF and convert to CSV format."
},
"pdfToExcel": {
"name": "PDF to Excel",
"subtitle": "Extract tables from PDF and convert to Excel (XLSX) format."
},
"pdfToText": {
"name": "PDF to Text",
"subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.",
"note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.",
"convertButton": "Extract Text"
},
"digitalSignPdf": {
"name": "Assinatura Digital PDF",
"pageTitle": "Assinatura Digital PDF - Adicionar Assinatura Criptográfica | BentoPDF",
"subtitle": "Adicione uma assinatura digital criptográfica ao seu PDF usando certificados X.509. Suporta formatos PKCS#12 (.pfx, .p12) e PEM. Sua chave privada nunca sai do seu navegador.",
"certificateSection": "Certificado",
"uploadCert": "Carregar certificado (.pfx, .p12)",
"certPassword": "Senha do Certificado",
"certPasswordPlaceholder": "Digite a senha",
"certInfo": "Informações do Certificado",
"certSubject": "Assunto",
"certIssuer": "Emissor",
"certValidity": "Válido",
"signatureDetails": "Detalhes da Assinatura (Opcional)",
"reason": "Razão",
"reasonPlaceholder": "ex: Eu aprovo este documento",
"location": "Localização",
"locationPlaceholder": "ex: Lisboa, Portugal",
"contactInfo": "Contato",
"contactPlaceholder": "ex: email@exemplo.com",
"applySignature": "Aplicar Assinatura",
"successMessage": "PDF assinado com sucesso! A assinatura pode ser verificada em qualquer leitor de PDF."
},
"validateSignaturePdf": {
"name": "Validar Assinatura PDF",
"pageTitle": "Validar Assinatura PDF - Verificar Assinaturas Digitais | BentoPDF",
"subtitle": "Verifique assinaturas digitais em seus arquivos PDF. Verifique a validade do certificado e a integridade do documento."
}
}

View File

@@ -8,6 +8,10 @@
"openMainMenu": "Ana menüyü aç", "openMainMenu": "Ana menüyü aç",
"language": "Dil" "language": "Dil"
}, },
"donation": {
"message": "BentoPDF'i seviyor musunuz? Ücretsiz ve açık kaynaklı kalmasına yardımcı olun!",
"button": "Bağış Yap"
},
"hero": { "hero": {
"title": " ", "title": " ",
"pdfToolkit": "PDF Toolkit", "pdfToolkit": "PDF Toolkit",
@@ -53,7 +57,8 @@
"toolsLabel": "Başlayın", "toolsLabel": "Başlayın",
"subtitle": "Dosya yükleyiciyi açmak için bir araç seçin", "subtitle": "Dosya yükleyiciyi açmak için bir araç seçin",
"searchPlaceholder": "Bir araç arayın (örn. 'böl', 'düzenle'...)", "searchPlaceholder": "Bir araç arayın (örn. 'böl', 'düzenle'...)",
"backToTools": "Araçlara Dön" "backToTools": "Araçlara Dön",
"firstLoadNotice": "Dönüştürme motorumuzu indirirken ilk yükleme biraz zaman alır. Sonrasında tüm yüklemeler anında gerçekleşir."
}, },
"upload": { "upload": {
"clickToSelect": "Dosya seçmek için tıklayın", "clickToSelect": "Dosya seçmek için tıklayın",
@@ -61,7 +66,8 @@
"pdfOrImages": "PDF veya Görseller", "pdfOrImages": "PDF veya Görseller",
"filesNeverLeave": "Dosyalarınız cihazınızı asla terk etmez.", "filesNeverLeave": "Dosyalarınız cihazınızı asla terk etmez.",
"addMore": "Daha Fazla Dosya Ekle", "addMore": "Daha Fazla Dosya Ekle",
"clearAll": "Tümünü Temizle" "clearAll": "Tümünü Temizle",
"clearFiles": "Dosyaları temizle"
}, },
"loader": { "loader": {
"processing": "İşleniyor..." "processing": "İşleniyor..."
@@ -178,7 +184,7 @@
"buyMeCoffee": "Bana Kahve Ismarla" "buyMeCoffee": "Bana Kahve Ismarla"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. Tüm hakları saklıdır.", "copyright": "© 2026 BentoPDF. Tüm hakları saklıdır.",
"version": "Sürüm", "version": "Sürüm",
"company": "Şirket", "company": "Şirket",
"aboutUs": "Hakkımızda", "aboutUs": "Hakkımızda",
@@ -221,7 +227,8 @@
"error": "Hata", "error": "Hata",
"success": "Başarılı", "success": "Başarılı",
"file": "Dosya", "file": "Dosya",
"files": "Dosya" "files": "Dosya",
"close": "Kapat"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -284,5 +284,228 @@
"subtitle": "E-posta dosyalarını (EML, MSG) PDF formatına dönüştürün. Outlook dışa aktarmalarını ve standart e-posta formatlarını destekler.", "subtitle": "E-posta dosyalarını (EML, MSG) PDF formatına dönüştürün. Outlook dışa aktarmalarını ve standart e-posta formatlarını destekler.",
"acceptedFormats": "EML, MSG Dosyaları", "acceptedFormats": "EML, MSG Dosyaları",
"convertButton": "PDF'ye Dönüştür" "convertButton": "PDF'ye Dönüştür"
},
"fontToOutline": {
"name": "Yazı Tipi Çerçeveye Dönüştür",
"subtitle": "Tüm yazı tiplerini vektör çerçevelere dönüştürün, tüm cihazlarda tutarlı görüntü için."
},
"deskewPdf": {
"name": "PDF Eğriliğini Düzelt",
"subtitle": "OpenCV kullanarak eğik taranmış sayfaları otomatik olarak düzeltin."
},
"rotateCustom": {
"name": "Rotate by Custom Degrees",
"subtitle": "Rotate pages by any custom angle."
},
"odtToPdf": {
"name": "ODT to PDF",
"subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.",
"acceptedFormats": "ODT files",
"convertButton": "Convert to PDF"
},
"csvToPdf": {
"name": "CSV to PDF",
"subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.",
"acceptedFormats": "CSV files",
"convertButton": "Convert to PDF"
},
"rtfToPdf": {
"name": "RTF to PDF",
"subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.",
"acceptedFormats": "RTF files",
"convertButton": "Convert to PDF"
},
"wordToPdf": {
"name": "Word to PDF",
"subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.",
"acceptedFormats": "DOCX, DOC, ODT, RTF files",
"convertButton": "Convert to PDF"
},
"excelToPdf": {
"name": "Excel to PDF",
"subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.",
"acceptedFormats": "XLSX, XLS, ODS, CSV files",
"convertButton": "Convert to PDF"
},
"powerpointToPdf": {
"name": "PowerPoint to PDF",
"subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.",
"acceptedFormats": "PPTX, PPT, ODP files",
"convertButton": "Convert to PDF"
},
"markdownToPdf": {
"name": "Markdown to PDF",
"subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.",
"paneMarkdown": "Markdown",
"panePreview": "Preview",
"btnUpload": "Upload",
"btnSyncScroll": "Sync Scroll",
"btnSettings": "Settings",
"btnExportPdf": "Export PDF",
"settingsTitle": "Markdown Settings",
"settingsPreset": "Preset",
"presetDefault": "Default (GFM-like)",
"presetCommonmark": "CommonMark (strict)",
"presetZero": "Minimal (no features)",
"settingsOptions": "Markdown Options",
"optAllowHtml": "Allow HTML tags",
"optBreaks": "Convert newlines to <br>",
"optLinkify": "Auto-convert URLs to links",
"optTypographer": "Typographer (smart quotes, etc.)"
},
"pdfBooklet": {
"name": "PDF Booklet",
"subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.",
"howItWorks": "How it works:",
"step1": "Upload a PDF file.",
"step2": "Pages will be rearranged in booklet order.",
"step3": "Print double-sided, flip on short edge, fold and staple.",
"paperSize": "Paper Size",
"orientation": "Orientation",
"portrait": "Portrait",
"landscape": "Landscape",
"pagesPerSheet": "Pages per Sheet",
"createBooklet": "Create Booklet",
"processing": "Processing...",
"pageCount": "Page count will be padded to multiple of 4 if needed."
},
"xpsToPdf": {
"name": "XPS to PDF",
"subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.",
"acceptedFormats": "XPS, OXPS files",
"convertButton": "Convert to PDF"
},
"mobiToPdf": {
"name": "MOBI to PDF",
"subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.",
"acceptedFormats": "MOBI files",
"convertButton": "Convert to PDF"
},
"epubToPdf": {
"name": "EPUB to PDF",
"subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.",
"acceptedFormats": "EPUB files",
"convertButton": "Convert to PDF"
},
"fb2ToPdf": {
"name": "FB2 to PDF",
"subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.",
"acceptedFormats": "FB2 files",
"convertButton": "Convert to PDF"
},
"cbzToPdf": {
"name": "CBZ to PDF",
"subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.",
"acceptedFormats": "CBZ, CBR files",
"convertButton": "Convert to PDF"
},
"wpdToPdf": {
"name": "WPD to PDF",
"subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.",
"acceptedFormats": "WPD files",
"convertButton": "Convert to PDF"
},
"wpsToPdf": {
"name": "WPS to PDF",
"subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.",
"acceptedFormats": "WPS files",
"convertButton": "Convert to PDF"
},
"xmlToPdf": {
"name": "XML to PDF",
"subtitle": "Convert XML documents to PDF format. Supports multiple files.",
"acceptedFormats": "XML files",
"convertButton": "Convert to PDF"
},
"pagesToPdf": {
"name": "Pages to PDF",
"subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.",
"acceptedFormats": "Pages files",
"convertButton": "Convert to PDF"
},
"odgToPdf": {
"name": "ODG to PDF",
"subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODG files",
"convertButton": "Convert to PDF"
},
"odsToPdf": {
"name": "ODS to PDF",
"subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODS files",
"convertButton": "Convert to PDF"
},
"odpToPdf": {
"name": "ODP to PDF",
"subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODP files",
"convertButton": "Convert to PDF"
},
"pubToPdf": {
"name": "PUB to PDF",
"subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.",
"acceptedFormats": "PUB files",
"convertButton": "Convert to PDF"
},
"vsdToPdf": {
"name": "VSD to PDF",
"subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.",
"acceptedFormats": "VSD, VSDX files",
"convertButton": "Convert to PDF"
},
"psdToPdf": {
"name": "PSD to PDF",
"subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.",
"acceptedFormats": "PSD files",
"convertButton": "Convert to PDF"
},
"pdfToSvg": {
"name": "PDF to SVG",
"subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size."
},
"extractTables": {
"name": "Extract PDF Tables",
"subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown."
},
"pdfToCsv": {
"name": "PDF to CSV",
"subtitle": "Extract tables from PDF and convert to CSV format."
},
"pdfToExcel": {
"name": "PDF to Excel",
"subtitle": "Extract tables from PDF and convert to Excel (XLSX) format."
},
"pdfToText": {
"name": "PDF to Text",
"subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.",
"note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.",
"convertButton": "Extract Text"
},
"digitalSignPdf": {
"name": "Digital Signature PDF",
"pageTitle": "Digital Signature PDF - Add Cryptographic Signature | BentoPDF",
"subtitle": "Add a cryptographic digital signature to your PDF using X.509 certificates. Supports PKCS#12 (.pfx, .p12) and PEM formats. Your private key never leaves your browser.",
"certificateSection": "Certificate",
"uploadCert": "Upload certificate (.pfx, .p12)",
"certPassword": "Certificate Password",
"certPasswordPlaceholder": "Enter certificate password",
"certInfo": "Certificate Information",
"certSubject": "Subject",
"certIssuer": "Issuer",
"certValidity": "Valid",
"signatureDetails": "Signature Details (Optional)",
"reason": "Reason",
"reasonPlaceholder": "e.g., I approve this document",
"location": "Location",
"locationPlaceholder": "e.g., New York, USA",
"contactInfo": "Contact Info",
"contactPlaceholder": "e.g., email@example.com",
"applySignature": "Apply Digital Signature",
"successMessage": "PDF signed successfully! The signature can be verified in any PDF reader."
},
"validateSignaturePdf": {
"name": "Validate PDF Signature",
"pageTitle": "Validate PDF Signature - Verify Digital Signatures | BentoPDF",
"subtitle": "Verify digital signatures in your PDF files. Check certificate validity, view signer details, and confirm document integrity. All processing happens in your browser."
} }
} }

View File

@@ -8,6 +8,10 @@
"openMainMenu": "Mở menu chính", "openMainMenu": "Mở menu chính",
"language": "Ngôn ngữ" "language": "Ngôn ngữ"
}, },
"donation": {
"message": "Bạn yêu thích BentoPDF? Hãy giúp chúng tôi giữ nó miễn phí và mã nguồn mở!",
"button": "Quyên góp"
},
"hero": { "hero": {
"title": "Bộ công cụ", "title": "Bộ công cụ",
"pdfToolkit": "PDF", "pdfToolkit": "PDF",
@@ -62,7 +66,8 @@
"pdfOrImages": "PDF hoặc Hình ảnh", "pdfOrImages": "PDF hoặc Hình ảnh",
"filesNeverLeave": "Tệp của bạn không bao giờ rời khỏi thiết bị.", "filesNeverLeave": "Tệp của bạn không bao giờ rời khỏi thiết bị.",
"addMore": "Thêm tệp", "addMore": "Thêm tệp",
"clearAll": "Xóa tất cả" "clearAll": "Xóa tất cả",
"clearFiles": "Xóa tệp"
}, },
"loader": { "loader": {
"processing": "Đang xử lý..." "processing": "Đang xử lý..."
@@ -179,7 +184,7 @@
"buyMeCoffee": "Mua cho tôi một ly cà phê" "buyMeCoffee": "Mua cho tôi một ly cà phê"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. Bảo lưu mọi quyền.", "copyright": "© 2026 BentoPDF. Bảo lưu mọi quyền.",
"version": "Phiên bản", "version": "Phiên bản",
"company": "Công ty", "company": "Công ty",
"aboutUs": "Về chúng tôi", "aboutUs": "Về chúng tôi",
@@ -222,7 +227,8 @@
"error": "Lỗi", "error": "Lỗi",
"success": "Thành công", "success": "Thành công",
"file": "Tệp", "file": "Tệp",
"files": "Tệp" "files": "Tệp",
"close": "Đóng"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -521,5 +521,13 @@
"subtitle": "Chuyển đổi tệp email (EML, MSG) sang định dạng PDF. Hỗ trợ xuất Outlook và định dạng email tiêu chuẩn.", "subtitle": "Chuyển đổi tệp email (EML, MSG) sang định dạng PDF. Hỗ trợ xuất Outlook và định dạng email tiêu chuẩn.",
"acceptedFormats": "Tệp EML, MSG", "acceptedFormats": "Tệp EML, MSG",
"convertButton": "Chuyển đổi sang PDF" "convertButton": "Chuyển đổi sang PDF"
},
"fontToOutline": {
"name": "Phông chữ thành đường viền",
"subtitle": "Chuyển đổi tất cả phông chữ thành đường viền vector để hiển thị nhất quán trên mọi thiết bị."
},
"deskewPdf": {
"name": "Chỉnh nghiêng PDF",
"subtitle": "Tự động làm thẳng các trang quét bị nghiêng bằng OpenCV."
} }
} }

View File

@@ -0,0 +1,325 @@
{
"nav": {
"home": "首頁",
"about": "關於我們",
"contact": "聯絡我們",
"licensing": "產品授權",
"allTools": "所有工具",
"openMainMenu": "開啟主選單",
"language": "語言"
},
"donation": {
"message": "喜歡 BentoPDF幫助我們保持免費和開源",
"button": "捐贈"
},
"hero": {
"title": "專為隱私打造的",
"pdfToolkit": "PDF 工具箱",
"builtForPrivacy": " ",
"noSignups": "不須註冊",
"unlimitedUse": "無限使用",
"worksOffline": "離線可用",
"startUsing": "立刻開始使用"
},
"usedBy": {
"title": "被下列公司及其員工採用"
},
"features": {
"title": "為何你該選擇",
"bentoPdf": "BentoPDF",
"noSignup": {
"title": "不須註冊",
"description": "立即可用,不須帳號或電子郵件。"
},
"noUploads": {
"title": "不須上傳",
"description": "所有文件都在用戶端處理,永遠不會離開你的裝置。"
},
"foreverFree": {
"title": "永遠免費",
"description": "所有工具免費使用,沒有試用期,也沒有付費牆。"
},
"noLimits": {
"title": "沒有限制",
"description": "隨心所欲的使用,沒有任何隱藏限制。"
},
"batchProcessing": {
"title": "批量處理",
"description": "一次處理無限量的 PDF 檔案。"
},
"lightningFast": {
"title": "快如閃電",
"description": "瞬間處理 PDF無須忍受任何等待或延遲。"
}
},
"tools": {
"title": "開始使用",
"toolsLabel": "工具",
"subtitle": "點擊任意工具以開始上傳檔案",
"searchPlaceholder": "搜尋工具(例如「合併」或「分割」...",
"backToTools": "返回工具列表",
"firstLoadNotice": "首次載入需要一點時間,因為我們正在下載轉換引擎。之後所有載入將即時完成。"
},
"upload": {
"clickToSelect": "點擊以選擇檔案",
"orDragAndDrop": "或將檔案拖放到此處",
"pdfOrImages": "PDF 或圖片",
"filesNeverLeave": "你的檔案永遠不會離開你的裝置。",
"addMore": "添加更多檔案",
"clearAll": "清除全部",
"clearFiles": "清除檔案"
},
"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": "你會使用 Cookies 或網站分析來追蹤我嗎?",
"answer": "我們在乎你的隱私。BentoPDF 並不追蹤個人資訊。我們僅使用 Simple Analytics 來查看匿名訪問次數。這代表我們能知道有多少使用者造訪過我們的網站但我們永遠都不會知道你是誰。Simple Analytics 完全符合 GDPR 規範且尊重你的隱私。"
}
},
"testimonials": {
"title": "看看我們的",
"users": "使用者",
"say": "怎麼說"
},
"support": {
"title": "喜歡我的作品嗎?",
"description": "BentoPDF 是一個出於熱情開發的專案,旨在為每個人提供一個免費、注重隱私且強大的 PDF 工具組。如果有幫上你的忙,請考慮支持它的開發。每杯咖啡都意義重大!",
"buyMeCoffee": "買杯咖啡給我"
},
"footer": {
"copyright": "© 2026 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": "檔案",
"close": "關閉"
},
"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": "載入失敗"
}
}

View File

@@ -0,0 +1,511 @@
{
"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 中添加圖片印章。",
"usernameLabel": "印章使用者名稱",
"usernamePlaceholder": "輸入你的名稱(印章用)",
"usernameHint": "該名稱會出現在你建立的印章上。"
},
"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": "Text 轉 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 的頁面交錯合併。保留書籤。"
},
"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 上的使用者權限。"
},
"emailToPdf": {
"name": "Email 轉 PDF",
"subtitle": "將電子郵件檔案 (EML, MSG) 轉換為 PDF 格式。支援 Outlook 匯出和標準電子郵件格式。",
"acceptedFormats": "EML, MSG 檔案",
"convertButton": "轉換為 PDF"
},
"fontToOutline": {
"name": "字型轉外框",
"subtitle": "將所有字型轉換為向量外框,確保在所有裝置上呈現一致。"
},
"deskewPdf": {
"name": "PDF 歪斜修正",
"subtitle": "使用 OpenCV 自動調正傾斜的掃描頁面。"
},
"rotateCustom": {
"name": "Rotate by Custom Degrees",
"subtitle": "Rotate pages by any custom angle."
},
"odtToPdf": {
"name": "ODT to PDF",
"subtitle": "Convert OpenDocument Text files to PDF format. Supports multiple files.",
"acceptedFormats": "ODT files",
"convertButton": "Convert to PDF"
},
"csvToPdf": {
"name": "CSV to PDF",
"subtitle": "Convert CSV spreadsheet files to PDF format. Supports multiple files.",
"acceptedFormats": "CSV files",
"convertButton": "Convert to PDF"
},
"rtfToPdf": {
"name": "RTF to PDF",
"subtitle": "Convert Rich Text Format documents to PDF. Supports multiple files.",
"acceptedFormats": "RTF files",
"convertButton": "Convert to PDF"
},
"wordToPdf": {
"name": "Word to PDF",
"subtitle": "Convert Word documents (DOCX, DOC, ODT, RTF) to PDF format. Supports multiple files.",
"acceptedFormats": "DOCX, DOC, ODT, RTF files",
"convertButton": "Convert to PDF"
},
"excelToPdf": {
"name": "Excel to PDF",
"subtitle": "Convert Excel spreadsheets (XLSX, XLS, ODS, CSV) to PDF format. Supports multiple files.",
"acceptedFormats": "XLSX, XLS, ODS, CSV files",
"convertButton": "Convert to PDF"
},
"powerpointToPdf": {
"name": "PowerPoint to PDF",
"subtitle": "Convert PowerPoint presentations (PPTX, PPT, ODP) to PDF format. Supports multiple files.",
"acceptedFormats": "PPTX, PPT, ODP files",
"convertButton": "Convert to PDF"
},
"markdownToPdf": {
"name": "Markdown to PDF",
"subtitle": "Write or paste Markdown and export it as a beautifully formatted PDF.",
"paneMarkdown": "Markdown",
"panePreview": "Preview",
"btnUpload": "Upload",
"btnSyncScroll": "Sync Scroll",
"btnSettings": "Settings",
"btnExportPdf": "Export PDF",
"settingsTitle": "Markdown Settings",
"settingsPreset": "Preset",
"presetDefault": "Default (GFM-like)",
"presetCommonmark": "CommonMark (strict)",
"presetZero": "Minimal (no features)",
"settingsOptions": "Markdown Options",
"optAllowHtml": "Allow HTML tags",
"optBreaks": "Convert newlines to <br>",
"optLinkify": "Auto-convert URLs to links",
"optTypographer": "Typographer (smart quotes, etc.)"
},
"pdfBooklet": {
"name": "PDF Booklet",
"subtitle": "Rearrange pages for double-sided booklet printing. Fold and staple to create a booklet.",
"howItWorks": "How it works:",
"step1": "Upload a PDF file.",
"step2": "Pages will be rearranged in booklet order.",
"step3": "Print double-sided, flip on short edge, fold and staple.",
"paperSize": "Paper Size",
"orientation": "Orientation",
"portrait": "Portrait",
"landscape": "Landscape",
"pagesPerSheet": "Pages per Sheet",
"createBooklet": "Create Booklet",
"processing": "Processing...",
"pageCount": "Page count will be padded to multiple of 4 if needed."
},
"xpsToPdf": {
"name": "XPS to PDF",
"subtitle": "Convert XPS/OXPS documents to PDF format. Supports multiple files.",
"acceptedFormats": "XPS, OXPS files",
"convertButton": "Convert to PDF"
},
"mobiToPdf": {
"name": "MOBI to PDF",
"subtitle": "Convert MOBI e-books to PDF format. Supports multiple files.",
"acceptedFormats": "MOBI files",
"convertButton": "Convert to PDF"
},
"epubToPdf": {
"name": "EPUB to PDF",
"subtitle": "Convert EPUB e-books to PDF format. Supports multiple files.",
"acceptedFormats": "EPUB files",
"convertButton": "Convert to PDF"
},
"fb2ToPdf": {
"name": "FB2 to PDF",
"subtitle": "Convert FictionBook (FB2) e-books to PDF format. Supports multiple files.",
"acceptedFormats": "FB2 files",
"convertButton": "Convert to PDF"
},
"cbzToPdf": {
"name": "CBZ to PDF",
"subtitle": "Convert comic book archives (CBZ/CBR) to PDF format. Supports multiple files.",
"acceptedFormats": "CBZ, CBR files",
"convertButton": "Convert to PDF"
},
"wpdToPdf": {
"name": "WPD to PDF",
"subtitle": "Convert WordPerfect documents (WPD) to PDF format. Supports multiple files.",
"acceptedFormats": "WPD files",
"convertButton": "Convert to PDF"
},
"wpsToPdf": {
"name": "WPS to PDF",
"subtitle": "Convert WPS Office documents to PDF format. Supports multiple files.",
"acceptedFormats": "WPS files",
"convertButton": "Convert to PDF"
},
"xmlToPdf": {
"name": "XML to PDF",
"subtitle": "Convert XML documents to PDF format. Supports multiple files.",
"acceptedFormats": "XML files",
"convertButton": "Convert to PDF"
},
"pagesToPdf": {
"name": "Pages to PDF",
"subtitle": "Convert Apple Pages documents to PDF format. Supports multiple files.",
"acceptedFormats": "Pages files",
"convertButton": "Convert to PDF"
},
"odgToPdf": {
"name": "ODG to PDF",
"subtitle": "Convert OpenDocument Graphics (ODG) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODG files",
"convertButton": "Convert to PDF"
},
"odsToPdf": {
"name": "ODS to PDF",
"subtitle": "Convert OpenDocument Spreadsheet (ODS) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODS files",
"convertButton": "Convert to PDF"
},
"odpToPdf": {
"name": "ODP to PDF",
"subtitle": "Convert OpenDocument Presentation (ODP) files to PDF format. Supports multiple files.",
"acceptedFormats": "ODP files",
"convertButton": "Convert to PDF"
},
"pubToPdf": {
"name": "PUB to PDF",
"subtitle": "Convert Microsoft Publisher (PUB) files to PDF format. Supports multiple files.",
"acceptedFormats": "PUB files",
"convertButton": "Convert to PDF"
},
"vsdToPdf": {
"name": "VSD to PDF",
"subtitle": "Convert Microsoft Visio (VSD, VSDX) files to PDF format. Supports multiple files.",
"acceptedFormats": "VSD, VSDX files",
"convertButton": "Convert to PDF"
},
"psdToPdf": {
"name": "PSD to PDF",
"subtitle": "Convert Adobe Photoshop (PSD) files to PDF format. Supports multiple files.",
"acceptedFormats": "PSD files",
"convertButton": "Convert to PDF"
},
"pdfToSvg": {
"name": "PDF to SVG",
"subtitle": "Convert each page of a PDF file into a scalable vector graphic (SVG) for perfect quality at any size."
},
"extractTables": {
"name": "Extract PDF Tables",
"subtitle": "Extract tables from PDF files and export as CSV, JSON, or Markdown."
},
"pdfToCsv": {
"name": "PDF to CSV",
"subtitle": "Extract tables from PDF and convert to CSV format."
},
"pdfToExcel": {
"name": "PDF to Excel",
"subtitle": "Extract tables from PDF and convert to Excel (XLSX) format."
},
"pdfToText": {
"name": "PDF to Text",
"subtitle": "Extract text from PDF files and save as plain text (.txt). Supports multiple files.",
"note": "This tool works ONLY with digitally created PDFs. For scanned documents or image-based PDFs, use our OCR PDF tool instead.",
"convertButton": "Extract Text"
},
"digitalSignPdf": {
"name": "Digital Signature PDF",
"pageTitle": "Digital Signature PDF - Add Cryptographic Signature | BentoPDF",
"subtitle": "Add a cryptographic digital signature to your PDF using X.509 certificates. Supports PKCS#12 (.pfx, .p12) and PEM formats. Your private key never leaves your browser.",
"certificateSection": "Certificate",
"uploadCert": "Upload certificate (.pfx, .p12)",
"certPassword": "Certificate Password",
"certPasswordPlaceholder": "Enter certificate password",
"certInfo": "Certificate Information",
"certSubject": "Subject",
"certIssuer": "Issuer",
"certValidity": "Valid",
"signatureDetails": "Signature Details (Optional)",
"reason": "Reason",
"reasonPlaceholder": "e.g., I approve this document",
"location": "Location",
"locationPlaceholder": "e.g., New York, USA",
"contactInfo": "Contact Info",
"contactPlaceholder": "e.g., email@example.com",
"applySignature": "Apply Digital Signature",
"successMessage": "PDF signed successfully! The signature can be verified in any PDF reader."
},
"validateSignaturePdf": {
"name": "Validate PDF Signature",
"pageTitle": "Validate PDF Signature - Verify Digital Signatures | BentoPDF",
"subtitle": "Verify digital signatures in your PDF files. Check certificate validity, view signer details, and confirm document integrity. All processing happens in your browser."
}
}

View File

@@ -8,6 +8,10 @@
"openMainMenu": "打开主菜单", "openMainMenu": "打开主菜单",
"language": "语言" "language": "语言"
}, },
"donation": {
"message": "喜欢 BentoPDF帮助我们保持免费和开源",
"button": "捐赠"
},
"hero": { "hero": {
"title": "专为隐私打造的", "title": "专为隐私打造的",
"pdfToolkit": "PDF 工具箱", "pdfToolkit": "PDF 工具箱",
@@ -62,7 +66,8 @@
"pdfOrImages": "PDF 或图片", "pdfOrImages": "PDF 或图片",
"filesNeverLeave": "您的文件从未离开您的设备。", "filesNeverLeave": "您的文件从未离开您的设备。",
"addMore": "添加更多文件", "addMore": "添加更多文件",
"clearAll": "清空所有" "clearAll": "清空所有",
"clearFiles": "清除文件"
}, },
"loader": { "loader": {
"processing": "处理中..." "processing": "处理中..."
@@ -179,7 +184,7 @@
"buyMeCoffee": "请我喝杯咖啡" "buyMeCoffee": "请我喝杯咖啡"
}, },
"footer": { "footer": {
"copyright": "© 2025 BentoPDF. 保留所有权利。", "copyright": "© 2026 BentoPDF. 保留所有权利。",
"version": "版本", "version": "版本",
"company": "公司", "company": "公司",
"aboutUs": "关于我们", "aboutUs": "关于我们",
@@ -222,7 +227,8 @@
"error": "错误", "error": "错误",
"success": "成功", "success": "成功",
"file": "文件", "file": "文件",
"files": "文件" "files": "文件",
"close": "关闭"
}, },
"about": { "about": {
"hero": { "hero": {

View File

@@ -518,5 +518,13 @@
"subtitle": "将电子邮件文件 (EML, MSG) 转换为 PDF 格式。支持 Outlook 导出和标准邮件格式。", "subtitle": "将电子邮件文件 (EML, MSG) 转换为 PDF 格式。支持 Outlook 导出和标准邮件格式。",
"acceptedFormats": "EML, MSG 文件", "acceptedFormats": "EML, MSG 文件",
"convertButton": "转换为 PDF" "convertButton": "转换为 PDF"
},
"fontToOutline": {
"name": "字体转轮廓",
"subtitle": "将所有字体转换为矢量轮廓,确保在所有设备上一致呈现。"
},
"deskewPdf": {
"name": "校正 PDF",
"subtitle": "使用 OpenCV 自动校正倾斜的扫描页面。"
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,46 +5,26 @@
* Version: 1.1.0 * Version: 1.1.0
*/ */
const CACHE_VERSION = 'bentopdf-v7'; const CACHE_VERSION = 'bentopdf-v10';
const CACHE_NAME = `${CACHE_VERSION}-static`; const CACHE_NAME = `${CACHE_VERSION}-static`;
const getBasePath = () => { const getBasePath = () => {
const scope = self.registration?.scope || self.location.href; const scope = self.registration?.scope || self.location.href;
const url = new URL(scope); const url = new URL(scope);
return url.pathname.replace(/\/$/, '') || ''; return url.pathname.replace(/\/$/, '') || '';
}; };
const buildCriticalAssets = (basePath) => [ const buildCriticalAssets = () => [];
`${basePath}/pymupdf-wasm/pyodide.js`,
`${basePath}/pymupdf-wasm/pyodide.asm.js`,
`${basePath}/pymupdf-wasm/pyodide.asm.wasm`,
`${basePath}/pymupdf-wasm/python_stdlib.zip`,
`${basePath}/pymupdf-wasm/pyodide-lock.json`,
`${basePath}/pymupdf-wasm/pymupdf-1.26.3-cp313-none-pyodide_2025_0_wasm32.whl`,
`${basePath}/pymupdf-wasm/numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl`,
`${basePath}/pymupdf-wasm/opencv_python-4.11.0.86-cp313-cp313-pyodide_2025_0_wasm32.whl`,
`${basePath}/pymupdf-wasm/lxml-5.4.0-cp313-cp313-pyodide_2025_0_wasm32.whl`,
`${basePath}/pymupdf-wasm/python_docx-1.2.0-py3-none-any.whl`,
`${basePath}/pymupdf-wasm/pdf2docx-0.5.8-py3-none-any.whl`,
`${basePath}/pymupdf-wasm/fonttools-4.56.0-py3-none-any.whl`,
`${basePath}/pymupdf-wasm/typing_extensions-4.12.2-py3-none-any.whl`,
`${basePath}/pymupdf-wasm/pymupdf4llm-0.0.27-py3-none-any.whl`,
`${basePath}/ghostscript-wasm/gs.js`,
`${basePath}/ghostscript-wasm/gs.wasm`,
];
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
const basePath = getBasePath(); const CRITICAL_ASSETS = buildCriticalAssets();
const CRITICAL_ASSETS = buildCriticalAssets(basePath);
// console.log('🚀 [ServiceWorker] Installing version:', CACHE_VERSION); // console.log('🚀 [ServiceWorker] Installing version:', CACHE_VERSION);
// console.log('📍 [ServiceWorker] Base path detected:', basePath || '/'); // console.log('📍 [ServiceWorker] Base path detected:', basePath || '/');
// console.log('📦 [ServiceWorker] Will cache', CRITICAL_ASSETS.length, 'critical assets'); // console.log('📦 [ServiceWorker] Will cache', CRITICAL_ASSETS.length, 'critical assets');
event.waitUntil( event.waitUntil(
caches.open(CACHE_NAME) caches
.open(CACHE_NAME)
.then((cache) => { .then((cache) => {
// console.log('[ServiceWorker] Caching critical assets...'); // console.log('[ServiceWorker] Caching critical assets...');
return cacheInBatches(cache, CRITICAL_ASSETS, 5); return cacheInBatches(cache, CRITICAL_ASSETS, 5);
@@ -64,7 +44,8 @@ self.addEventListener('activate', (event) => {
// console.log('🔄 [ServiceWorker] Activating version:', CACHE_VERSION); // console.log('🔄 [ServiceWorker] Activating version:', CACHE_VERSION);
event.waitUntil( event.waitUntil(
caches.keys() caches
.keys()
.then((cacheNames) => { .then((cacheNames) => {
return Promise.all( return Promise.all(
cacheNames.map((cacheName) => { cacheNames.map((cacheName) => {
@@ -92,18 +73,35 @@ self.addEventListener('fetch', (event) => {
if (!isLocal && !isCDN) { if (!isLocal && !isCDN) {
return; return;
} }
if (isLocal && (url.searchParams.has('t') || url.searchParams.has('import') || url.searchParams.has('direct'))) { if (
isLocal &&
(url.searchParams.has('t') ||
url.searchParams.has('import') ||
url.searchParams.has('direct'))
) {
// console.log('🔧 [Dev Mode] Skipping Vite HMR request:', url.pathname); // console.log('🔧 [Dev Mode] Skipping Vite HMR request:', url.pathname);
return; return;
} }
if (isLocal && (url.pathname.includes('/@vite') || url.pathname.includes('/@id') || url.pathname.includes('/@fs'))) { if (
isLocal &&
(url.pathname.includes('/@vite') ||
url.pathname.includes('/@id') ||
url.pathname.includes('/@fs'))
) {
return; return;
} }
if (shouldCache(url.pathname, isCDN)) { if (isLocal && url.pathname.includes('/locales/')) {
event.respondWith(networkFirstStrategy(event.request));
} else if (shouldCache(url.pathname, isCDN)) {
event.respondWith(cacheFirstStrategyWithDedup(event.request, isCDN)); event.respondWith(cacheFirstStrategyWithDedup(event.request, isCDN));
} else if (isLocal && (url.pathname.endsWith('.html') || url.pathname === '/')) { } else if (
isLocal &&
(url.pathname.endsWith('.html') ||
url.pathname === '/' ||
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl)(\/|$)/.test(url.pathname))
) {
event.respondWith(networkFirstStrategy(event.request)); event.respondWith(networkFirstStrategy(event.request));
} }
}); });
@@ -117,7 +115,7 @@ async function cacheFirstStrategyWithDedup(request, isCDN) {
const fileName = url.pathname.split('/').pop(); const fileName = url.pathname.split('/').pop();
try { try {
const cachedResponse = await findCachedFile(fileName); const cachedResponse = await findCachedFile(fileName, request.url);
if (cachedResponse) { if (cachedResponse) {
// console.log('⚡ [Cache HIT] Instant load:', fileName); // console.log('⚡ [Cache HIT] Instant load:', fileName);
return cachedResponse; return cachedResponse;
@@ -128,12 +126,20 @@ async function cacheFirstStrategyWithDedup(request, isCDN) {
const networkResponse = await fetch(request); const networkResponse = await fetch(request);
if (networkResponse && networkResponse.status === 200) { if (networkResponse && networkResponse.status === 200) {
const clone = networkResponse.clone();
const buffer = await clone.arrayBuffer();
if (buffer.byteLength > 0) {
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
await removeDuplicateCache(cache, fileName, isCDN); await removeDuplicateCache(cache, fileName, isCDN);
await cache.put(
await cache.put(request, networkResponse.clone()); request,
// console.log(`💾 [Cached from ${isCDN ? 'CDN' : 'local'}] Saved:`, fileName); new Response(buffer, {
status: networkResponse.status,
statusText: networkResponse.statusText,
headers: networkResponse.headers,
})
);
}
} }
return networkResponse; return networkResponse;
@@ -148,13 +154,26 @@ async function cacheFirstStrategyWithDedup(request, isCDN) {
try { try {
const fallbackResponse = await fetch(localUrl); const fallbackResponse = await fetch(localUrl);
if (fallbackResponse && fallbackResponse.status === 200) { if (fallbackResponse && fallbackResponse.status === 200) {
const fbClone = fallbackResponse.clone();
const fbBuffer = await fbClone.arrayBuffer();
if (fbBuffer.byteLength > 0) {
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
await cache.put(localUrl, fallbackResponse.clone()); await cache.put(
// console.log('✅ [Fallback Success] Cached local version:', fileName); localUrl,
new Response(fbBuffer, {
status: fallbackResponse.status,
statusText: fallbackResponse.statusText,
headers: fallbackResponse.headers,
})
);
}
return fallbackResponse; return fallbackResponse;
} }
} catch (fallbackError) { } catch (fallbackError) {
console.error('[ServiceWorker] Both CDN and local failed for:', fileName); console.error(
'[ServiceWorker] Both CDN and local failed for:',
fileName
);
} }
} }
} }
@@ -162,14 +181,32 @@ async function cacheFirstStrategyWithDedup(request, isCDN) {
} }
} }
async function findCachedFile(fileName) { async function findCachedFile(fileName, requestUrl) {
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
const requests = await cache.keys();
const exactMatch = await cache.match(requestUrl);
if (exactMatch) {
const clone = exactMatch.clone();
const buffer = await clone.arrayBuffer();
if (buffer.byteLength > 0) {
return exactMatch;
}
await cache.delete(requestUrl);
}
const requests = await cache.keys();
for (const req of requests) { for (const req of requests) {
const reqUrl = new URL(req.url); const reqUrl = new URL(req.url);
if (reqUrl.pathname.endsWith(fileName)) { if (reqUrl.pathname.endsWith(fileName)) {
return await cache.match(req); const response = await cache.match(req);
if (response) {
const clone = response.clone();
const buffer = await clone.arrayBuffer();
if (buffer.byteLength > 0) {
return response;
}
await cache.delete(req);
}
} }
} }
return null; return null;
@@ -200,8 +237,19 @@ async function networkFirstStrategy(request) {
const networkResponse = await fetch(request); const networkResponse = await fetch(request);
if (networkResponse && networkResponse.status === 200) { if (networkResponse && networkResponse.status === 200) {
const clone = networkResponse.clone();
const buffer = await clone.arrayBuffer();
if (buffer.byteLength > 0) {
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone()); cache.put(
request,
new Response(buffer, {
status: networkResponse.status,
statusText: networkResponse.statusText,
headers: networkResponse.headers,
})
);
}
} }
return networkResponse; return networkResponse;
@@ -220,12 +268,6 @@ async function networkFirstStrategy(request) {
* Returns the local directory path for a given CDN package * Returns the local directory path for a given CDN package
*/ */
function getLocalPathForCDNUrl(pathname) { function getLocalPathForCDNUrl(pathname) {
if (pathname.includes('/@bentopdf/pymupdf-wasm')) {
return '/pymupdf-wasm/';
}
if (pathname.includes('/@bentopdf/gs-wasm')) {
return '/ghostscript-wasm/';
}
if (pathname.includes('/@matbee/libreoffice-converter')) { if (pathname.includes('/@matbee/libreoffice-converter')) {
return '/libreoffice-wasm/'; return '/libreoffice-wasm/';
} }
@@ -248,11 +290,11 @@ function shouldCache(pathname, isCDN = false) {
return ( return (
pathname.includes('/libreoffice-wasm/') || pathname.includes('/libreoffice-wasm/') ||
pathname.includes('/pymupdf-wasm/') ||
pathname.includes('/ghostscript-wasm/') ||
pathname.includes('/embedpdf/') || pathname.includes('/embedpdf/') ||
pathname.includes('/assets/') || pathname.includes('/assets/') ||
pathname.match(/\.(js|mjs|css|wasm|whl|zip|json|png|jpg|jpeg|gif|svg|woff|woff2|ttf|gz|br)$/) pathname.match(
/\.(js|mjs|css|wasm|whl|zip|json|png|jpg|jpeg|gif|svg|woff|woff2|ttf|gz|br)$/
)
); );
} }
@@ -262,15 +304,18 @@ function shouldCache(pathname, isCDN = false) {
async function cacheInBatches(cache, urls, batchSize = 5) { async function cacheInBatches(cache, urls, batchSize = 5) {
for (let i = 0; i < urls.length; i += batchSize) { for (let i = 0; i < urls.length; i += batchSize) {
const batch = urls.slice(i, i + batchSize); const batch = urls.slice(i, i + batchSize);
// console.log(`[ServiceWorker] Caching batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(urls.length / batchSize)}`);
await Promise.all( await Promise.all(
batch.map(async (url) => { batch.map(async (url) => {
try { try {
await cache.add(url); const response = await fetch(url);
const fileName = url.split('/').pop(); if (response.ok && response.status === 200) {
const fileSize = fileName.includes('.wasm') || fileName.includes('.whl') ? '(large file)' : ''; const clone = response.clone();
// console.log(` ✓ Cached: ${fileName} ${fileSize}`); const buffer = await clone.arrayBuffer();
if (buffer.byteLength > 0) {
await cache.put(url, response);
}
}
} catch (error) { } catch (error) {
console.warn('[ServiceWorker] Failed to cache:', url, error.message); console.warn('[ServiceWorker] Failed to cache:', url, error.message);
} }

View File

@@ -5,6 +5,7 @@ interface AddAttachmentsMessage {
pdfBuffer: ArrayBuffer; pdfBuffer: ArrayBuffer;
attachmentBuffers: ArrayBuffer[]; attachmentBuffers: ArrayBuffer[];
attachmentNames: string[]; attachmentNames: string[];
cpdfUrl?: string;
} }
interface AddAttachmentsSuccessResponse { interface AddAttachmentsSuccessResponse {
@@ -17,4 +18,6 @@ interface AddAttachmentsErrorResponse {
message: string; message: string;
} }
type AddAttachmentsResponse = AddAttachmentsSuccessResponse | AddAttachmentsErrorResponse; type AddAttachmentsResponse =
| AddAttachmentsSuccessResponse
| AddAttachmentsErrorResponse;

View File

@@ -1,13 +1,32 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function parsePageRange(rangeString, totalPages) { function parsePageRange(rangeString, totalPages) {
const pages = new Set(); const pages = new Set();
const parts = rangeString.split(',').map(s => s.trim()); const parts = rangeString.split(',').map((s) => s.trim());
for (const part of parts) { for (const part of parts) {
if (part.includes('-')) { if (part.includes('-')) {
const [start, end] = part.split('-').map(s => parseInt(s.trim(), 10)); const [start, end] = part.split('-').map((s) => parseInt(s.trim(), 10));
if (isNaN(start) || isNaN(end)) continue; if (isNaN(start) || isNaN(end)) continue;
for (let i = Math.max(1, start); i <= Math.min(totalPages, end); i++) { for (let i = Math.max(1, start); i <= Math.min(totalPages, end); i++) {
pages.add(i); pages.add(i);
@@ -23,7 +42,13 @@ function parsePageRange(rangeString, totalPages) {
return Array.from(pages).sort((a, b) => a - b); return Array.from(pages).sort((a, b) => a - b);
} }
function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNames, attachmentLevel, pageRange) { function addAttachmentsToPDFInWorker(
pdfBuffer,
attachmentBuffers,
attachmentNames,
attachmentLevel,
pageRange
) {
try { try {
const uint8Array = new Uint8Array(pdfBuffer); const uint8Array = new Uint8Array(pdfBuffer);
@@ -33,18 +58,21 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
} catch (error) { } catch (error) {
const errorMsg = error.message || error.toString(); const errorMsg = error.message || error.toString();
if (errorMsg.includes('Failed to read PDF') || if (
errorMsg.includes('Failed to read PDF') ||
errorMsg.includes('Could not read object') || errorMsg.includes('Could not read object') ||
errorMsg.includes('No /Root entry') || errorMsg.includes('No /Root entry') ||
errorMsg.includes('PDFError')) { errorMsg.includes('PDFError')
) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: 'The PDF file has structural issues and cannot be processed. The file may be corrupted, incomplete, or created with non-standard tools. Please try:\n\n• Opening and re-saving the PDF in another PDF viewer\n• Using a different PDF file\n• Repairing the PDF with a PDF repair tool' message:
'The PDF file has structural issues and cannot be processed. The file may be corrupted, incomplete, or created with non-standard tools. Please try:\n\n• Opening and re-saving the PDF in another PDF viewer\n• Using a different PDF file\n• Repairing the PDF with a PDF repair tool',
}); });
} else { } else {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: `Failed to load PDF: ${errorMsg}` message: `Failed to load PDF: ${errorMsg}`,
}); });
} }
return; return;
@@ -57,7 +85,7 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
if (!pageRange) { if (!pageRange) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: 'Page range is required for page-level attachments.' message: 'Page range is required for page-level attachments.',
}); });
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
return; return;
@@ -66,7 +94,7 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
if (targetPages.length === 0) { if (targetPages.length === 0) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: 'Invalid page range specified.' message: 'Invalid page range specified.',
}); });
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
return; return;
@@ -82,21 +110,25 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
coherentpdf.attachFileFromMemory(attachmentData, attachmentName, pdf); coherentpdf.attachFileFromMemory(attachmentData, attachmentName, pdf);
} else { } else {
for (const pageNum of targetPages) { for (const pageNum of targetPages) {
coherentpdf.attachFileToPageFromMemory(attachmentData, attachmentName, pdf, pageNum); coherentpdf.attachFileToPageFromMemory(
attachmentData,
attachmentName,
pdf,
pageNum
);
} }
} }
} catch (error) { } catch (error) {
console.warn(`Failed to attach file ${attachmentNames[i]}:`, error); console.warn(`Failed to attach file ${attachmentNames[i]}:`, error);
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: `Failed to attach file ${attachmentNames[i]}: ${error.message || error}` message: `Failed to attach file ${attachmentNames[i]}: ${error.message || error}`,
}); });
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
return; return;
} }
} }
// Save the modified PDF
const modifiedBytes = coherentpdf.toMemory(pdf, false, false); const modifiedBytes = coherentpdf.toMemory(pdf, false, false);
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
@@ -105,22 +137,46 @@ function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNam
modifiedBytes.byteOffset + modifiedBytes.byteLength modifiedBytes.byteOffset + modifiedBytes.byteLength
); );
self.postMessage({ self.postMessage(
{
status: 'success', status: 'success',
modifiedPDF: buffer modifiedPDF: buffer,
}, [buffer]); },
[buffer]
);
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error instanceof Error message:
error instanceof Error
? error.message ? error.message
: 'Unknown error occurred while adding attachments.' : 'Unknown error occurred while adding attachments.',
}); });
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'add-attachments') { if (e.data.command === 'add-attachments') {
addAttachmentsToPDFInWorker( addAttachmentsToPDFInWorker(
e.data.pdfBuffer, e.data.pdfBuffer,

View File

@@ -8,6 +8,7 @@ interface InterleaveFile {
interface InterleaveMessage { interface InterleaveMessage {
command: 'interleave'; command: 'interleave';
files: InterleaveFile[]; files: InterleaveFile[];
cpdfUrl?: string;
} }
interface InterleaveSuccessResponse { interface InterleaveSuccessResponse {

View File

@@ -1,8 +1,46 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
self.onmessage = function (e) { function loadCpdf(cpdfUrl) {
const { command, files } = e.data; if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
self.onmessage = async function (e) {
const { command, files, cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (command === 'interleave') { if (command === 'interleave') {
interleavePDFs(files); interleavePDFs(files);
@@ -16,7 +54,7 @@ function interleavePDFs(files) {
for (const file of files) { for (const file of files) {
const uint8Array = new Uint8Array(file.data); const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, ""); const pdfDoc = coherentpdf.fromMemory(uint8Array, '');
loadedPdfs.push(pdfDoc); loadedPdfs.push(pdfDoc);
pageCounts.push(coherentpdf.pages(pdfDoc)); pageCounts.push(coherentpdf.pages(pdfDoc));
} }
@@ -43,22 +81,29 @@ function interleavePDFs(files) {
throw new Error('No valid pages to merge.'); throw new Error('No valid pages to merge.');
} }
const mergedPdf = coherentpdf.mergeSame(pdfsToMerge, true, true, rangesToMerge); const mergedPdf = coherentpdf.mergeSame(
pdfsToMerge,
true,
true,
rangesToMerge
);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true); const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer; const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf); coherentpdf.deletePdf(mergedPdf);
loadedPdfs.forEach(pdf => coherentpdf.deletePdf(pdf)); loadedPdfs.forEach((pdf) => coherentpdf.deletePdf(pdf));
self.postMessage({ self.postMessage(
{
status: 'success', status: 'success',
pdfBytes: buffer pdfBytes: buffer,
}, [buffer]); },
[buffer]
);
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error.message || 'Unknown error during interleave merge' message: error.message || 'Unknown error during interleave merge',
}); });
} }
} }

View File

@@ -4,6 +4,7 @@ interface GetAttachmentsMessage {
command: 'get-attachments'; command: 'get-attachments';
fileBuffer: ArrayBuffer; fileBuffer: ArrayBuffer;
fileName: string; fileName: string;
cpdfUrl?: string;
} }
interface EditAttachmentsMessage { interface EditAttachmentsMessage {
@@ -11,13 +12,21 @@ interface EditAttachmentsMessage {
fileBuffer: ArrayBuffer; fileBuffer: ArrayBuffer;
fileName: string; fileName: string;
attachmentsToRemove: number[]; attachmentsToRemove: number[];
cpdfUrl?: string;
} }
type EditAttachmentsWorkerMessage = GetAttachmentsMessage | EditAttachmentsMessage; type EditAttachmentsWorkerMessage =
| GetAttachmentsMessage
| EditAttachmentsMessage;
interface GetAttachmentsSuccessResponse { interface GetAttachmentsSuccessResponse {
status: 'success'; status: 'success';
attachments: Array<{ index: number; name: string; page: number; data: ArrayBuffer }>; attachments: Array<{
index: number;
name: string;
page: number;
data: ArrayBuffer;
}>;
fileName: string; fileName: string;
} }
@@ -37,6 +46,12 @@ interface EditAttachmentsErrorResponse {
message: string; message: string;
} }
type GetAttachmentsResponse = GetAttachmentsSuccessResponse | GetAttachmentsErrorResponse; type GetAttachmentsResponse =
type EditAttachmentsResponse = EditAttachmentsSuccessResponse | EditAttachmentsErrorResponse; | GetAttachmentsSuccessResponse
type EditAttachmentsWorkerResponse = GetAttachmentsResponse | EditAttachmentsResponse; | GetAttachmentsErrorResponse;
type EditAttachmentsResponse =
| EditAttachmentsSuccessResponse
| EditAttachmentsErrorResponse;
type EditAttachmentsWorkerResponse =
| GetAttachmentsResponse
| EditAttachmentsResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function getAttachmentsFromPDFInWorker(fileBuffer, fileName) { function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
try { try {
@@ -11,7 +30,7 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}` message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`,
}); });
return; return;
} }
@@ -23,7 +42,7 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
self.postMessage({ self.postMessage({
status: 'success', status: 'success',
attachments: [], attachments: [],
fileName: fileName fileName: fileName,
}); });
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
return; return;
@@ -37,13 +56,16 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
const attachmentData = coherentpdf.getAttachmentData(i); const attachmentData = coherentpdf.getAttachmentData(i);
const dataArray = new Uint8Array(attachmentData); const dataArray = new Uint8Array(attachmentData);
const buffer = dataArray.buffer.slice(dataArray.byteOffset, dataArray.byteOffset + dataArray.byteLength); const buffer = dataArray.buffer.slice(
dataArray.byteOffset,
dataArray.byteOffset + dataArray.byteLength
);
attachments.push({ attachments.push({
index: i, index: i,
name: String(name), name: String(name),
page: Number(page), page: Number(page),
data: buffer data: buffer,
}); });
} catch (error) { } catch (error) {
console.warn(`Failed to get attachment ${i} from ${fileName}:`, error); console.warn(`Failed to get attachment ${i} from ${fileName}:`, error);
@@ -56,22 +78,27 @@ function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
const response = { const response = {
status: 'success', status: 'success',
attachments: attachments, attachments: attachments,
fileName: fileName fileName: fileName,
}; };
const transferBuffers = attachments.map(att => att.data); const transferBuffers = attachments.map((att) => att.data);
self.postMessage(response, transferBuffers); self.postMessage(response, transferBuffers);
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error instanceof Error message:
error instanceof Error
? error.message ? error.message
: 'Unknown error occurred during attachment listing.' : 'Unknown error occurred during attachment listing.',
}); });
} }
} }
function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove) { function editAttachmentsInPDFInWorker(
fileBuffer,
fileName,
attachmentsToRemove
) {
try { try {
const uint8Array = new Uint8Array(fileBuffer); const uint8Array = new Uint8Array(fileBuffer);
@@ -81,7 +108,7 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}` message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`,
}); });
return; return;
} }
@@ -103,7 +130,7 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
attachmentsToKeep.push({ attachmentsToKeep.push({
name: String(name), name: String(name),
page: Number(page), page: Number(page),
data: dataCopy data: dataCopy,
}); });
} }
} }
@@ -114,9 +141,18 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
for (const attachment of attachmentsToKeep) { for (const attachment of attachmentsToKeep) {
if (attachment.page === 0) { if (attachment.page === 0) {
coherentpdf.attachFileFromMemory(attachment.data, attachment.name, pdf); coherentpdf.attachFileFromMemory(
attachment.data,
attachment.name,
pdf
);
} else { } else {
coherentpdf.attachFileToPageFromMemory(attachment.data, attachment.name, pdf, attachment.page); coherentpdf.attachFileToPageFromMemory(
attachment.data,
attachment.name,
pdf,
attachment.page
);
} }
} }
} }
@@ -124,29 +160,58 @@ function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove)
const modifiedBytes = coherentpdf.toMemory(pdf, false, true); const modifiedBytes = coherentpdf.toMemory(pdf, false, true);
coherentpdf.deletePdf(pdf); coherentpdf.deletePdf(pdf);
const buffer = modifiedBytes.buffer.slice(modifiedBytes.byteOffset, modifiedBytes.byteOffset + modifiedBytes.byteLength); const buffer = modifiedBytes.buffer.slice(
modifiedBytes.byteOffset,
modifiedBytes.byteOffset + modifiedBytes.byteLength
);
const response = { const response = {
status: 'success', status: 'success',
modifiedPDF: buffer, modifiedPDF: buffer,
fileName: fileName fileName: fileName,
}; };
self.postMessage(response, [response.modifiedPDF]); self.postMessage(response, [response.modifiedPDF]);
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error instanceof Error message:
error instanceof Error
? error.message ? error.message
: 'Unknown error occurred during attachment editing.' : 'Unknown error occurred during attachment editing.',
}); });
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'get-attachments') { if (e.data.command === 'get-attachments') {
getAttachmentsFromPDFInWorker(e.data.fileBuffer, e.data.fileName); getAttachmentsFromPDFInWorker(e.data.fileBuffer, e.data.fileName);
} else if (e.data.command === 'edit-attachments') { } else if (e.data.command === 'edit-attachments') {
editAttachmentsInPDFInWorker(e.data.fileBuffer, e.data.fileName, e.data.attachmentsToRemove); editAttachmentsInPDFInWorker(
e.data.fileBuffer,
e.data.fileName,
e.data.attachmentsToRemove
);
} }
}; };

View File

@@ -4,6 +4,7 @@ interface ExtractAttachmentsMessage {
command: 'extract-attachments'; command: 'extract-attachments';
fileBuffers: ArrayBuffer[]; fileBuffers: ArrayBuffer[];
fileNames: string[]; fileNames: string[];
cpdfUrl?: string;
} }
interface ExtractAttachmentSuccessResponse { interface ExtractAttachmentSuccessResponse {
@@ -16,4 +17,6 @@ interface ExtractAttachmentErrorResponse {
message: string; message: string;
} }
type ExtractAttachmentResponse = ExtractAttachmentSuccessResponse | ExtractAttachmentErrorResponse; type ExtractAttachmentResponse =
| ExtractAttachmentSuccessResponse
| ExtractAttachmentErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) { function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
try { try {
@@ -37,7 +56,7 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
let uniqueName = attachmentName; let uniqueName = attachmentName;
let counter = 1; let counter = 1;
while (allAttachments.some(att => att.name === uniqueName)) { while (allAttachments.some((att) => att.name === uniqueName)) {
const nameParts = attachmentName.split('.'); const nameParts = attachmentName.split('.');
if (nameParts.length > 1) { if (nameParts.length > 1) {
const extension = nameParts.pop(); const extension = nameParts.pop();
@@ -56,10 +75,13 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
allAttachments.push({ allAttachments.push({
name: uniqueName, name: uniqueName,
data: attachmentData.buffer.slice(0) data: attachmentData.buffer.slice(0),
}); });
} catch (error) { } catch (error) {
console.warn(`Failed to extract attachment ${j} from ${fileName}:`, error); console.warn(
`Failed to extract attachment ${j} from ${fileName}:`,
error
);
} }
} }
@@ -70,21 +92,21 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
if (allAttachments.length === 0) { if (allAttachments.length === 0) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: 'No attachments were found in the selected PDF(s).' message: 'No attachments were found in the selected PDF(s).',
}); });
return; return;
} }
const response = { const response = {
status: 'success', status: 'success',
attachments: [] attachments: [],
}; };
const transferBuffers = []; const transferBuffers = [];
for (const attachment of allAttachments) { for (const attachment of allAttachments) {
response.attachments.push({ response.attachments.push({
name: attachment.name, name: attachment.name,
data: attachment.data data: attachment.data,
}); });
transferBuffers.push(attachment.data); transferBuffers.push(attachment.data);
} }
@@ -93,14 +115,36 @@ function extractAttachmentsFromPDFsInWorker(fileBuffers, fileNames) {
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error instanceof Error message:
error instanceof Error
? error.message ? error.message
: 'Unknown error occurred during attachment extraction.' : 'Unknown error occurred during attachment extraction.',
}); });
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'extract-attachments') { if (e.data.command === 'extract-attachments') {
extractAttachmentsFromPDFsInWorker(e.data.fileBuffers, e.data.fileNames); extractAttachmentsFromPDFsInWorker(e.data.fileBuffers, e.data.fileNames);
} }

View File

@@ -4,6 +4,7 @@ interface ConvertJSONToPDFMessage {
command: 'convert'; command: 'convert';
fileBuffers: ArrayBuffer[]; fileBuffers: ArrayBuffer[];
fileNames: string[]; fileNames: string[];
cpdfUrl?: string;
} }
interface JSONToPDFSuccessResponse { interface JSONToPDFSuccessResponse {
@@ -17,4 +18,3 @@ interface JSONToPDFErrorResponse {
} }
type JSONToPDFResponse = JSONToPDFSuccessResponse | JSONToPDFErrorResponse; type JSONToPDFResponse = JSONToPDFSuccessResponse | JSONToPDFErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function convertJSONsToPDFInWorker(fileBuffers, fileNames) { function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
try { try {
@@ -15,9 +34,8 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
try { try {
pdf = coherentpdf.fromJSONMemory(uint8Array); pdf = coherentpdf.fromJSONMemory(uint8Array);
} catch (error) { } catch (error) {
const errorMsg = error && error.message const errorMsg =
? error.message error && error.message ? error.message : 'Unknown error';
: 'Unknown error';
throw new Error( throw new Error(
`Failed to convert "${fileName}" to PDF. ` + `Failed to convert "${fileName}" to PDF. ` +
`The JSON file must be in the format produced by cpdf's outputJSONMemory. ` + `The JSON file must be in the format produced by cpdf's outputJSONMemory. ` +
@@ -56,9 +74,29 @@ function convertJSONsToPDFInWorker(fileBuffers, fileNames) {
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'convert') { if (e.data.command === 'convert') {
convertJSONsToPDFInWorker(e.data.fileBuffers, e.data.fileNames); convertJSONsToPDFInWorker(e.data.fileBuffers, e.data.fileNames);
} }
}; };

View File

@@ -18,6 +18,7 @@ interface MergeMessage {
command: 'merge'; command: 'merge';
files: MergeFile[]; files: MergeFile[];
jobs: MergeJob[]; jobs: MergeJob[];
cpdfUrl?: string;
} }
interface MergeSuccessResponse { interface MergeSuccessResponse {

View File

@@ -1,8 +1,46 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
self.onmessage = function (e) { function loadCpdf(cpdfUrl) {
const { command, files, jobs } = e.data; if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
self.onmessage = async function (e) {
const { command, files, jobs, cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (command === 'merge') { if (command === 'merge') {
mergePDFs(files, jobs); mergePDFs(files, jobs);
@@ -17,7 +55,7 @@ function mergePDFs(files, jobs) {
for (const file of files) { for (const file of files) {
const uint8Array = new Uint8Array(file.data); const uint8Array = new Uint8Array(file.data);
const pdfDoc = coherentpdf.fromMemory(uint8Array, ""); const pdfDoc = coherentpdf.fromMemory(uint8Array, '');
loadedPdfs[file.name] = pdfDoc; loadedPdfs[file.name] = pdfDoc;
} }
@@ -49,23 +87,30 @@ function mergePDFs(files, jobs) {
throw new Error('No valid files or pages to merge.'); throw new Error('No valid files or pages to merge.');
} }
const mergedPdf = coherentpdf.mergeSame(pdfsToMerge, true, true, rangesToMerge); const mergedPdf = coherentpdf.mergeSame(
pdfsToMerge,
true,
true,
rangesToMerge
);
const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true); const mergedPdfBytes = coherentpdf.toMemory(mergedPdf, false, true);
const buffer = mergedPdfBytes.buffer; const buffer = mergedPdfBytes.buffer;
coherentpdf.deletePdf(mergedPdf); coherentpdf.deletePdf(mergedPdf);
Object.values(loadedPdfs).forEach(pdf => coherentpdf.deletePdf(pdf)); Object.values(loadedPdfs).forEach((pdf) => coherentpdf.deletePdf(pdf));
self.postMessage({ self.postMessage(
{
status: 'success', status: 'success',
pdfBytes: buffer pdfBytes: buffer,
}, [buffer]); },
[buffer]
);
} catch (error) { } catch (error) {
self.postMessage({ self.postMessage({
status: 'error', status: 'error',
message: error.message || 'Unknown error during merge' message: error.message || 'Unknown error during merge',
}); });
} }
} }

View File

@@ -4,6 +4,7 @@ interface ConvertPDFToJSONMessage {
command: 'convert'; command: 'convert';
fileBuffers: ArrayBuffer[]; fileBuffers: ArrayBuffer[];
fileNames: string[]; fileNames: string[];
cpdfUrl?: string;
} }
interface PDFToJSONSuccessResponse { interface PDFToJSONSuccessResponse {
@@ -17,4 +18,3 @@ interface PDFToJSONErrorResponse {
} }
type PDFToJSONResponse = PDFToJSONSuccessResponse | PDFToJSONErrorResponse; type PDFToJSONResponse = PDFToJSONSuccessResponse | PDFToJSONErrorResponse;

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function convertPDFsToJSONInWorker(fileBuffers, fileNames) { function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
try { try {
@@ -12,8 +31,6 @@ function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
const uint8Array = new Uint8Array(buffer); const uint8Array = new Uint8Array(buffer);
const pdf = coherentpdf.fromMemory(uint8Array, ''); const pdf = coherentpdf.fromMemory(uint8Array, '');
//TODO:@ALAM -> add options for users to select these settings
// parse_content: true, no_stream_data: false, decompress_streams: false
const jsonData = coherentpdf.outputJSONMemory(true, false, false, pdf); const jsonData = coherentpdf.outputJSONMemory(true, false, false, pdf);
const jsonBuffer = jsonData.buffer.slice(0); const jsonBuffer = jsonData.buffer.slice(0);
@@ -44,9 +61,29 @@ function convertPDFsToJSONInWorker(fileBuffers, fileNames) {
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'convert') { if (e.data.command === 'convert') {
convertPDFsToJSONInWorker(e.data.fileBuffers, e.data.fileNames); convertPDFsToJSONInWorker(e.data.fileBuffers, e.data.fileNames);
} }
}; };

View File

@@ -7,6 +7,7 @@ interface GenerateTOCMessage {
fontSize: number; fontSize: number;
fontFamily: number; fontFamily: number;
addBookmark: boolean; addBookmark: boolean;
cpdfUrl?: string;
} }
interface TOCSuccessResponse { interface TOCSuccessResponse {

View File

@@ -1,5 +1,24 @@
const baseUrl = self.location.href.substring(0, self.location.href.lastIndexOf('/workers/') + 1); let cpdfLoaded = false;
self.importScripts(baseUrl + 'coherentpdf.browser.min.js');
function loadCpdf(cpdfUrl) {
if (cpdfLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
if (typeof coherentpdf !== 'undefined') {
cpdfLoaded = true;
resolve();
return;
}
try {
self.importScripts(cpdfUrl);
cpdfLoaded = true;
resolve();
} catch (error) {
reject(new Error('Failed to load CoherentPDF: ' + error.message));
}
});
}
function generateTableOfContentsInWorker( function generateTableOfContentsInWorker(
pdfData, pdfData,
@@ -49,7 +68,28 @@ function generateTableOfContentsInWorker(
} }
} }
self.onmessage = (e) => { self.onmessage = async function (e) {
const { cpdfUrl } = e.data;
if (!cpdfUrl) {
self.postMessage({
status: 'error',
message:
'CoherentPDF URL not provided. Please configure it in WASM Settings.',
});
return;
}
try {
await loadCpdf(cpdfUrl);
} catch (error) {
self.postMessage({
status: 'error',
message: error.message,
});
return;
}
if (e.data.command === 'generate-toc') { if (e.data.command === 'generate-toc') {
generateTableOfContentsInWorker( generateTableOfContentsInWorker(
e.data.pdfData, e.data.pdfData,

Some files were not shown because too many files have changed in this diff Show More