Merge pull request #886 from adrienboulle/fix-825-untranslated-notifications

fix(merge-pdf): route result notifications through i18n
This commit is contained in:
Alam authored and GitHub committed 2026-10-21 12:33:35 +05:30
commit 33d15b9890
4 files changed
+123 -17

No files matched your search

+15 -15
View File
@@ -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,30 +411,29 @@ 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();
}
);
} else {
// The worker has no access to i18next: its message is technical and
// English-only, so it goes to the console, not to the user.
console.error('Worker merge error:', e.data.message);
showAlert('Error', e.data.message || 'Failed to merge PDFs.');
showAlert(t('common.error'), 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 +447,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 +472,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();
+77
View File
@@ -0,0 +1,77 @@
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('never surfaces the raw worker message to the user', () => {
const rawWorkerMessageInAlert = [
...source.matchAll(/\bshowAlert\([^;]*?\be\.data\.message/gs),
];
expect(rawWorkerMessageInAlert).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([]);
});
}
});