mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 11:52:48 +01:00
@@ -20,7 +20,21 @@
|
||||
"subtitle": "Combine multiple PDFs into one file. Preserves Bookmarks.",
|
||||
"retainPageLabels": "Keep original page labels from each file",
|
||||
"retainPageLabelsHint": "Off (default): the merged PDF uses natural page numbering 1–N. On: each file's original page labels are preserved (may produce duplicate labels).",
|
||||
"removeDuplicateFonts": "Remove duplicate fonts"
|
||||
"removeDuplicateFonts": "Remove duplicate fonts",
|
||||
"alert": {
|
||||
"thumbnailError": "Failed to render page thumbnails",
|
||||
"noSelection": "No files or pages selected to merge.",
|
||||
"mergeSuccess": "PDFs merged successfully!",
|
||||
"mergeFailed": "Failed to merge PDFs.",
|
||||
"mergeError": "Failed to merge PDFs. Please check that all files are valid and not password-protected.",
|
||||
"workerError": "An unexpected error occurred in the merge worker.",
|
||||
"loadError": "Failed to load one or more PDF files"
|
||||
},
|
||||
"loader": {
|
||||
"renderingPreviews": "Rendering page previews...",
|
||||
"merging": "Merging PDFs...",
|
||||
"loadingDocuments": "Loading PDF documents..."
|
||||
}
|
||||
},
|
||||
"splitPdf": {
|
||||
"name": "Split PDF",
|
||||
|
||||
@@ -19,7 +19,21 @@
|
||||
"name": "Fusionner des PDF",
|
||||
"subtitle": "Assembler plusieurs PDF en un seul fichier, tout en conservant les signets.",
|
||||
"retainPageLabels": "Conserver les étiquettes de page d'origine de chaque fichier",
|
||||
"retainPageLabelsHint": "Désactivé (par défaut) : le PDF fusionné utilise une numérotation naturelle 1–N. Activé : les étiquettes de page d'origine de chaque fichier sont conservées (peut produire des étiquettes en double)."
|
||||
"retainPageLabelsHint": "Désactivé (par défaut) : le PDF fusionné utilise une numérotation naturelle 1–N. Activé : les étiquettes de page d'origine de chaque fichier sont conservées (peut produire des étiquettes en double).",
|
||||
"alert": {
|
||||
"thumbnailError": "Échec du rendu des aperçus de pages",
|
||||
"noSelection": "Aucun fichier ni page sélectionné pour la fusion.",
|
||||
"mergeSuccess": "PDF fusionnés avec succès !",
|
||||
"mergeFailed": "Échec de la fusion des PDF.",
|
||||
"mergeError": "Échec de la fusion des PDF. Veuillez vérifier que tous les fichiers sont valides et non protégés par mot de passe.",
|
||||
"workerError": "Une erreur inattendue s'est produite dans le worker de fusion.",
|
||||
"loadError": "Impossible de charger un ou plusieurs fichiers PDF"
|
||||
},
|
||||
"loader": {
|
||||
"renderingPreviews": "Génération des aperçus de pages…",
|
||||
"merging": "Fusion des PDF…",
|
||||
"loadingDocuments": "Chargement des documents PDF…"
|
||||
}
|
||||
},
|
||||
"splitPdf": {
|
||||
"name": "Scinder un PDF",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
showWasmRequiredDialog,
|
||||
WasmProvider,
|
||||
} from '../utils/wasm-provider.js';
|
||||
import { t } from '../i18n/i18n';
|
||||
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
@@ -199,7 +200,7 @@ async function renderPageMergeThumbnails() {
|
||||
lazyLoadMargin: '300px',
|
||||
onProgress: () => {
|
||||
currentPageNumber++;
|
||||
showLoader(`Rendering page previews...`);
|
||||
showLoader(t('tools:mergePdf.loader.renderingPreviews'));
|
||||
},
|
||||
onBatchComplete: () => {
|
||||
createIcons({ icons });
|
||||
@@ -216,7 +217,7 @@ async function renderPageMergeThumbnails() {
|
||||
initializePageThumbnailsSortable();
|
||||
} catch (error) {
|
||||
console.error('Error rendering page thumbnails:', error);
|
||||
showAlert('Error', 'Failed to render page thumbnails');
|
||||
showAlert(t('common.error'), t('tools:mergePdf.alert.thumbnailError'));
|
||||
} finally {
|
||||
hideLoader();
|
||||
mergeState.isRendering = false;
|
||||
@@ -282,7 +283,7 @@ export async function merge() {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Merging PDFs...');
|
||||
showLoader(t('tools:mergePdf.loader.merging'));
|
||||
try {
|
||||
const jobs: MergeJob[] = [];
|
||||
const filesToMerge: MergeFile[] = [];
|
||||
@@ -369,7 +370,7 @@ export async function merge() {
|
||||
}
|
||||
|
||||
if (jobs.length === 0) {
|
||||
showAlert('Error', 'No files or pages selected to merge.');
|
||||
showAlert(t('common.error'), t('tools:mergePdf.alert.noSelection'));
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
@@ -410,8 +411,8 @@ export async function merge() {
|
||||
downloadFile(blob, 'merged.pdf');
|
||||
mergeState.mergeSuccess = true;
|
||||
showAlert(
|
||||
'Success',
|
||||
'PDFs merged successfully!',
|
||||
t('common.success'),
|
||||
t('tools:mergePdf.alert.mergeSuccess'),
|
||||
'success',
|
||||
async () => {
|
||||
await resetState();
|
||||
@@ -419,21 +420,21 @@ export async function merge() {
|
||||
);
|
||||
} else {
|
||||
console.error('Worker merge error:', e.data.message);
|
||||
showAlert('Error', e.data.message || 'Failed to merge PDFs.');
|
||||
showAlert(
|
||||
t('common.error'),
|
||||
e.data.message || t('tools:mergePdf.alert.mergeFailed')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
mergeWorker.onerror = (e) => {
|
||||
hideLoader();
|
||||
console.error('Worker error:', e);
|
||||
showAlert('Error', 'An unexpected error occurred in the merge worker.');
|
||||
showAlert(t('common.error'), t('tools:mergePdf.alert.workerError'));
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Merge error:', e);
|
||||
showAlert(
|
||||
'Error',
|
||||
'Failed to merge PDFs. Please check that all files are valid and not password-protected.'
|
||||
);
|
||||
showAlert(t('common.error'), t('tools:mergePdf.alert.mergeError'));
|
||||
hideLoader();
|
||||
}
|
||||
}
|
||||
@@ -447,14 +448,14 @@ export async function refreshMergeUI() {
|
||||
|
||||
const wasInPageMode = mergeState.activeMode === 'page';
|
||||
|
||||
showLoader('Loading PDF documents...');
|
||||
showLoader(t('tools:mergePdf.loader.loadingDocuments'));
|
||||
try {
|
||||
mergeState.pdfDocs = {};
|
||||
mergeState.pdfBytes = {};
|
||||
|
||||
hideLoader();
|
||||
state.files = await batchDecryptIfNeeded(state.files);
|
||||
showLoader('Loading PDF documents...');
|
||||
showLoader(t('tools:mergePdf.loader.loadingDocuments'));
|
||||
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
const file = state.files[i];
|
||||
@@ -472,7 +473,7 @@ export async function refreshMergeUI() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading PDFs:', error);
|
||||
showAlert('Error', 'Failed to load one or more PDF files');
|
||||
showAlert(t('common.error'), t('tools:mergePdf.alert.loadError'));
|
||||
return;
|
||||
} finally {
|
||||
hideLoader();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const LOGIC_FILE = join(ROOT, 'src/js/logic/merge-pdf-page.ts');
|
||||
const CHECKED_LANGUAGES = ['en', 'fr'] as const;
|
||||
|
||||
const source = readFileSync(LOGIC_FILE, 'utf8');
|
||||
|
||||
const loadLocale = (lang: string, namespace: 'common' | 'tools') =>
|
||||
JSON.parse(
|
||||
readFileSync(
|
||||
join(ROOT, 'public/locales', lang, `${namespace}.json`),
|
||||
'utf8'
|
||||
)
|
||||
) as Record<string, unknown>;
|
||||
|
||||
const resolveKey = (bundle: Record<string, unknown>, key: string): unknown =>
|
||||
key
|
||||
.split('.')
|
||||
.reduce<unknown>(
|
||||
(node, part) =>
|
||||
node && typeof node === 'object'
|
||||
? (node as Record<string, unknown>)[part]
|
||||
: undefined,
|
||||
bundle
|
||||
);
|
||||
|
||||
const usedKeys = [...source.matchAll(/\bt\('([^']+)'/g)].map(
|
||||
(match) => match[1]
|
||||
);
|
||||
|
||||
describe('merge PDF notifications are localized', () => {
|
||||
it('uses translation keys for every alert and loader message', () => {
|
||||
const literalCalls = [
|
||||
...source.matchAll(/\bshow(?:Alert|Loader)\(\s*['"`]/g),
|
||||
];
|
||||
expect(literalCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('references at least the alert and loader keys added for the merge tool', () => {
|
||||
expect(usedKeys).toContain('tools:mergePdf.alert.mergeSuccess');
|
||||
expect(usedKeys).toContain('tools:mergePdf.loader.merging');
|
||||
expect(usedKeys).toContain('common.success');
|
||||
expect(usedKeys).toContain('common.error');
|
||||
});
|
||||
|
||||
for (const lang of CHECKED_LANGUAGES) {
|
||||
it(`resolves every key used by the merge tool in "${lang}"`, () => {
|
||||
const bundles = {
|
||||
common: loadLocale(lang, 'common'),
|
||||
tools: loadLocale(lang, 'tools'),
|
||||
};
|
||||
|
||||
const missing = usedKeys.filter((key) => {
|
||||
const [namespace, plainKey] = key.startsWith('tools:')
|
||||
? ['tools', key.slice('tools:'.length)]
|
||||
: ['common', key];
|
||||
const value = resolveKey(
|
||||
bundles[namespace as 'common' | 'tools'],
|
||||
plainKey
|
||||
);
|
||||
return typeof value !== 'string' || value.length === 0;
|
||||
});
|
||||
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user