mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 01:54:00 +01:00
Add translation strings, remove local files
This commit is contained in:
1 parent
ad5811efb0
commit
7831571696
5 files changed
+182
-37
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
npx -y node@20.19.0 ./node_modules/lint-staged/bin/lint-staged.js
|
||||
npx lint-staged
|
||||
@@ -516,7 +516,37 @@
|
||||
},
|
||||
"duplexCollate": {
|
||||
"name": "Duplex Collate",
|
||||
"subtitle": "Reorder front/back scan blocks into collated page order in one PDF."
|
||||
"subtitle": "Reorder front/back scan blocks into collated page order in one PDF.",
|
||||
"totalPagesLabel": "Total pages:",
|
||||
"splitAfterPageLabel": "Split after page",
|
||||
"splitAfterPagePlaceholder": "e.g. 50",
|
||||
"backBlockOrderLabel": "Back block order",
|
||||
"backBlockReverseOption": "Reverse back block (common for flipped stacks)",
|
||||
"backBlockKeepOption": "Keep back block as-is",
|
||||
"autoSplitButton": "Auto-detect split (half)",
|
||||
"exportGroupedLabel": "Export grouped files by original document length",
|
||||
"pagesPerDocumentLabel": "Pages per original document",
|
||||
"groupedHelp": "If enabled, output is a ZIP containing one PDF per grouped document.",
|
||||
"collateButton": "Collate PDF",
|
||||
"invalidFileTitle": "Invalid File",
|
||||
"invalidFileMessage": "Please select a PDF file.",
|
||||
"loadingPdf": "Loading PDF...",
|
||||
"loadErrorMessage": "Failed to load PDF file.",
|
||||
"fileMeta": "{{size}} - {{count}} pages",
|
||||
"oddPageWarning": "Odd page count ({{count}}). A duplex scan should have an even number of pages; you may have a missing or extra page.",
|
||||
"previewSummary": "Front block: {{frontCount}} page(s), back block: {{backCount}} page(s). Output: {{outputCount}} page(s), in front/back sequence.",
|
||||
"previewMismatchWarning": "Front and back block lengths differ ({{frontCount}} front vs {{backCount}} back). The {{extraCount}} extra page(s) on the longer side will be appended unpaired at the end.",
|
||||
"missingFileTitle": "Missing File",
|
||||
"missingFileMessage": "Please upload a PDF with at least 2 pages before processing.",
|
||||
"invalidSplitTitle": "Invalid Split Point",
|
||||
"invalidSplitMessage": "Split point must be between page 1 and page {{maxSplit}}.",
|
||||
"unevenConfirmMessage": "The front block has {{frontCount}} page(s) and the back block has {{backCount}} page(s).\nThis usually means a page was scanned twice or is missing.\n\nThe {{extraCount}} unpaired page(s) will be appended at the end.\n\nContinue anyway?",
|
||||
"invalidGroupTitle": "Invalid Group Size",
|
||||
"invalidGroupMessage": "Please enter a valid number of pages per original document.",
|
||||
"collatingLoader": "Collating duplex scan...",
|
||||
"successMessage": "Collated PDF generated successfully.",
|
||||
"successGroupedMessage": "Collation complete. Generated {{fileCount}} grouped file(s).",
|
||||
"processErrorMessage": "Failed to collate PDF."
|
||||
},
|
||||
"addAttachments": {
|
||||
"name": "Add Attachments",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { formatBytes, downloadFile } from '../utils/helpers.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
@@ -18,6 +19,15 @@ const duplexState: DuplexState = {
|
||||
totalPages: 0,
|
||||
};
|
||||
|
||||
const translate = (
|
||||
key: string,
|
||||
fallback: string,
|
||||
options?: Record<string, unknown>
|
||||
) => {
|
||||
const translation = t(key, options);
|
||||
return translation && translation !== key ? translation : fallback;
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
} else {
|
||||
@@ -106,11 +116,17 @@ async function handleFile(file: File) {
|
||||
file.type !== 'application/pdf' &&
|
||||
!file.name.toLowerCase().endsWith('.pdf')
|
||||
) {
|
||||
showAlert('Invalid File', 'Please select a PDF file.');
|
||||
showAlert(
|
||||
translate('tools:duplexCollate.invalidFileTitle', 'Invalid File'),
|
||||
translate(
|
||||
'tools:duplexCollate.invalidFileMessage',
|
||||
'Please select a PDF file.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Loading PDF...');
|
||||
showLoader(translate('tools:duplexCollate.loadingPdf', 'Loading PDF...'));
|
||||
|
||||
try {
|
||||
const result = await loadPdfWithPasswordPrompt(file);
|
||||
@@ -133,7 +149,13 @@ async function handleFile(file: File) {
|
||||
} catch (error) {
|
||||
console.error('Error loading PDF:', error);
|
||||
hideLoader();
|
||||
showAlert('Error', 'Failed to load PDF file.');
|
||||
showAlert(
|
||||
translate('common.error', 'Error'),
|
||||
translate(
|
||||
'tools:duplexCollate.loadErrorMessage',
|
||||
'Failed to load PDF file.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +195,14 @@ function updateFileDisplay() {
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = `${formatBytes(duplexState.file.size)} • ${duplexState.totalPages} pages`;
|
||||
metaSpan.textContent = translate(
|
||||
'tools:duplexCollate.fileMeta',
|
||||
`${formatBytes(duplexState.file.size)} - ${duplexState.totalPages} pages`,
|
||||
{
|
||||
size: formatBytes(duplexState.file.size),
|
||||
count: duplexState.totalPages,
|
||||
}
|
||||
);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
@@ -208,7 +237,13 @@ function showOddPageWarning() {
|
||||
if (!banner) return;
|
||||
if (duplexState.totalPages % 2 !== 0) {
|
||||
banner.classList.remove('hidden');
|
||||
banner.textContent = `⚠ Odd page count (${duplexState.totalPages}). A duplex scan should have an even number of pages — you may have a missing or extra page.`;
|
||||
banner.textContent = translate(
|
||||
'tools:duplexCollate.oddPageWarning',
|
||||
`Odd page count (${duplexState.totalPages}). A duplex scan should have an even number of pages; you may have a missing or extra page.`,
|
||||
{
|
||||
count: duplexState.totalPages,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
banner.classList.add('hidden');
|
||||
banner.textContent = '';
|
||||
@@ -318,15 +353,27 @@ function updatePreviewSummary() {
|
||||
backOrder
|
||||
);
|
||||
|
||||
summary.textContent =
|
||||
`Front block: ${frontCount} page(s), back block: ${backCount} page(s). ` +
|
||||
`Output: ${order.length} page(s), in front/back sequence.`;
|
||||
summary.textContent = translate(
|
||||
'tools:duplexCollate.previewSummary',
|
||||
`Front block: ${frontCount} page(s), back block: ${backCount} page(s). Output: ${order.length} page(s), in front/back sequence.`,
|
||||
{
|
||||
frontCount,
|
||||
backCount,
|
||||
outputCount: order.length,
|
||||
}
|
||||
);
|
||||
|
||||
if (frontCount !== backCount) {
|
||||
warning.classList.remove('hidden');
|
||||
warning.textContent =
|
||||
`Front and back block lengths differ (${frontCount} front vs ${backCount} back). ` +
|
||||
`The ${Math.abs(frontCount - backCount)} extra page(s) on the longer side will be appended unpaired at the end.`;
|
||||
warning.textContent = translate(
|
||||
'tools:duplexCollate.previewMismatchWarning',
|
||||
`Front and back block lengths differ (${frontCount} front vs ${backCount} back). The ${Math.abs(frontCount - backCount)} extra page(s) on the longer side will be appended unpaired at the end.`,
|
||||
{
|
||||
frontCount,
|
||||
backCount,
|
||||
extraCount: Math.abs(frontCount - backCount),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
warning.classList.add('hidden');
|
||||
warning.textContent = '';
|
||||
@@ -365,8 +412,11 @@ function bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
||||
async function processDuplexCollate() {
|
||||
if (!duplexState.file || !duplexState.pdfDoc || duplexState.totalPages < 2) {
|
||||
showAlert(
|
||||
'Missing File',
|
||||
'Please upload a PDF with at least 2 pages before processing.'
|
||||
translate('tools:duplexCollate.missingFileTitle', 'Missing File'),
|
||||
translate(
|
||||
'tools:duplexCollate.missingFileMessage',
|
||||
'Please upload a PDF with at least 2 pages before processing.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -374,8 +424,14 @@ async function processDuplexCollate() {
|
||||
const splitPoint = getSplitPoint();
|
||||
if (splitPoint <= 0 || splitPoint >= duplexState.totalPages) {
|
||||
showAlert(
|
||||
'Invalid Split Point',
|
||||
`Split point must be between page 1 and page ${duplexState.totalPages - 1}.`
|
||||
translate('tools:duplexCollate.invalidSplitTitle', 'Invalid Split Point'),
|
||||
translate(
|
||||
'tools:duplexCollate.invalidSplitMessage',
|
||||
`Split point must be between page 1 and page ${duplexState.totalPages - 1}.`,
|
||||
{
|
||||
maxSplit: duplexState.totalPages - 1,
|
||||
}
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -390,10 +446,15 @@ async function processDuplexCollate() {
|
||||
);
|
||||
if (frontCount !== backCount) {
|
||||
const proceed = window.confirm(
|
||||
`The front block has ${frontCount} page(s) and the back block has ${backCount} page(s).\n` +
|
||||
`This usually means a page was scanned twice or is missing.\n\n` +
|
||||
`The ${Math.abs(frontCount - backCount)} unpaired page(s) will be appended at the end.\n\n` +
|
||||
`Continue anyway?`
|
||||
translate(
|
||||
'tools:duplexCollate.unevenConfirmMessage',
|
||||
`The front block has ${frontCount} page(s) and the back block has ${backCount} page(s).\nThis usually means a page was scanned twice or is missing.\n\nThe ${Math.abs(frontCount - backCount)} unpaired page(s) will be appended at the end.\n\nContinue anyway?`,
|
||||
{
|
||||
frontCount,
|
||||
backCount,
|
||||
extraCount: Math.abs(frontCount - backCount),
|
||||
}
|
||||
)
|
||||
);
|
||||
if (!proceed) return;
|
||||
}
|
||||
@@ -413,13 +474,18 @@ async function processDuplexCollate() {
|
||||
(!Number.isFinite(pagesPerDocument) || pagesPerDocument < 1)
|
||||
) {
|
||||
showAlert(
|
||||
'Invalid Group Size',
|
||||
'Please enter a valid number of pages per original document.'
|
||||
translate('tools:duplexCollate.invalidGroupTitle', 'Invalid Group Size'),
|
||||
translate(
|
||||
'tools:duplexCollate.invalidGroupMessage',
|
||||
'Please enter a valid number of pages per original document.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Collating duplex scan...');
|
||||
showLoader(
|
||||
translate('tools:duplexCollate.collatingLoader', 'Collating duplex scan...')
|
||||
);
|
||||
|
||||
try {
|
||||
const { order } = buildDuplexOrder(
|
||||
@@ -436,7 +502,14 @@ async function processDuplexCollate() {
|
||||
});
|
||||
downloadFile(blob, `${baseName}_collated.pdf`);
|
||||
hideLoader();
|
||||
showAlert('Success', 'Collated PDF generated successfully.', 'success');
|
||||
showAlert(
|
||||
translate('common.success', 'Success'),
|
||||
translate(
|
||||
'tools:duplexCollate.successMessage',
|
||||
'Collated PDF generated successfully.'
|
||||
),
|
||||
'success'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -464,14 +537,26 @@ async function processDuplexCollate() {
|
||||
|
||||
hideLoader();
|
||||
showAlert(
|
||||
'Success',
|
||||
`Collation complete. Generated ${fileCount} grouped file(s).`,
|
||||
translate('common.success', 'Success'),
|
||||
translate(
|
||||
'tools:duplexCollate.successGroupedMessage',
|
||||
`Collation complete. Generated ${fileCount} grouped file(s).`,
|
||||
{
|
||||
fileCount,
|
||||
}
|
||||
),
|
||||
'success'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Duplex collate error:', error);
|
||||
hideLoader();
|
||||
showAlert('Error', 'Failed to collate PDF.');
|
||||
showAlert(
|
||||
translate('common.error', 'Error'),
|
||||
translate(
|
||||
'tools:duplexCollate.processErrorMessage',
|
||||
'Failed to collate PDF.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,8 +97,13 @@
|
||||
>
|
||||
</button>
|
||||
|
||||
<h1 class="text-2xl font-bold text-white mb-2">Duplex Collate</h1>
|
||||
<p class="text-gray-400 mb-6">
|
||||
<h1
|
||||
class="text-2xl font-bold text-white mb-2"
|
||||
data-i18n="tools:duplexCollate.name"
|
||||
>
|
||||
Duplex Collate
|
||||
</h1>
|
||||
<p class="text-gray-400 mb-6" data-i18n="tools:duplexCollate.subtitle">
|
||||
Reorder a PDF scanned as two blocks (all fronts, then all backs) into
|
||||
proper front/back order.
|
||||
</p>
|
||||
@@ -143,7 +148,10 @@
|
||||
<div id="duplex-options" class="hidden mt-6 space-y-4">
|
||||
<div class="p-3 bg-gray-900 rounded-lg border border-gray-700">
|
||||
<p class="text-sm text-gray-300">
|
||||
Total pages: <span id="total-pages" class="text-white">0</span>
|
||||
<span data-i18n="tools:duplexCollate.totalPagesLabel"
|
||||
>Total pages:</span
|
||||
>
|
||||
<span id="total-pages" class="text-white">0</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -152,6 +160,7 @@
|
||||
<label
|
||||
for="split-page"
|
||||
class="block mb-2 text-sm font-medium text-gray-300"
|
||||
data-i18n="tools:duplexCollate.splitAfterPageLabel"
|
||||
>Split after page</label
|
||||
>
|
||||
<input
|
||||
@@ -160,6 +169,7 @@
|
||||
min="1"
|
||||
class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2.5"
|
||||
placeholder="e.g. 50"
|
||||
data-i18n-placeholder="tools:duplexCollate.splitAfterPagePlaceholder"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -167,16 +177,26 @@
|
||||
<label
|
||||
for="back-order"
|
||||
class="block mb-2 text-sm font-medium text-gray-300"
|
||||
data-i18n="tools:duplexCollate.backBlockOrderLabel"
|
||||
>Back block order</label
|
||||
>
|
||||
<select
|
||||
id="back-order"
|
||||
class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2.5"
|
||||
>
|
||||
<option value="reverse" selected>
|
||||
<option
|
||||
value="reverse"
|
||||
selected
|
||||
data-i18n="tools:duplexCollate.backBlockReverseOption"
|
||||
>
|
||||
Reverse back block (common for flipped stacks)
|
||||
</option>
|
||||
<option value="keep">Keep back block as-is</option>
|
||||
<option
|
||||
value="keep"
|
||||
data-i18n="tools:duplexCollate.backBlockKeepOption"
|
||||
>
|
||||
Keep back block as-is
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,6 +204,7 @@
|
||||
<button
|
||||
id="auto-split-btn"
|
||||
class="w-full bg-gray-700 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition-colors duration-200"
|
||||
data-i18n="tools:duplexCollate.autoSplitButton"
|
||||
>
|
||||
Auto-detect split (half)
|
||||
</button>
|
||||
@@ -204,13 +225,16 @@
|
||||
type="checkbox"
|
||||
class="w-4 h-4 text-indigo-600 bg-gray-700 border-gray-600 rounded"
|
||||
/>
|
||||
Export grouped files by original document length
|
||||
<span data-i18n="tools:duplexCollate.exportGroupedLabel"
|
||||
>Export grouped files by original document length</span
|
||||
>
|
||||
</label>
|
||||
|
||||
<div id="grouped-options" class="hidden">
|
||||
<label
|
||||
for="pages-per-document"
|
||||
class="block mb-2 text-sm font-medium text-gray-300"
|
||||
data-i18n="tools:duplexCollate.pagesPerDocumentLabel"
|
||||
>Pages per original document</label
|
||||
>
|
||||
<input
|
||||
@@ -220,13 +244,20 @@
|
||||
value="2"
|
||||
class="w-full bg-gray-700 border border-gray-600 text-white rounded-lg p-2.5"
|
||||
/>
|
||||
<p class="text-xs text-gray-400 mt-2">
|
||||
<p
|
||||
class="text-xs text-gray-400 mt-2"
|
||||
data-i18n="tools:duplexCollate.groupedHelp"
|
||||
>
|
||||
If enabled, output is a ZIP containing one PDF per grouped
|
||||
document.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button id="process-btn" class="btn-gradient w-full mt-2">
|
||||
<button
|
||||
id="process-btn"
|
||||
class="btn-gradient w-full mt-2"
|
||||
data-i18n="tools:duplexCollate.collateButton"
|
||||
>
|
||||
Collate PDF
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user