Add translation strings, remove local files

This commit is contained in:
r0ckarong committed 2026-08-24 08:57:32 +02:00
1 parent ad5811efb0
commit 7831571696
5 files changed
+182 -37

No files matched your search

+1 -1
View File
@@ -1 +1 @@
npx -y node@20.19.0 ./node_modules/lint-staged/bin/lint-staged.js
npx lint-staged
-1
View File
@@ -1 +0,0 @@
20.19.0
+31 -1
View File
@@ -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",
+111 -26
View File
@@ -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.'
)
);
}
}
+39 -8
View File
@@ -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>