Travel-Friendly Language Phrasebook Generator

Travel-Friendly Language Phrasebook Generator

Create a personalized phrasebook for your next adventure.

1. Customize Your Phrasebook

2. Select Phrase Categories

Phrase Categories

Your Custom Phrasebook

Your personalized phrasebook will appear here after generation.

${translations[phrase] || 'Translation not found'}

`; }); html += `
`; }); outputContainer.innerHTML = html; pdfDownloadBtn.classList.remove('hidden'); }; /** * A fetch wrapper with exponential backoff for retrying failed requests. */ async function fetchWithExponentialBackoff(url, options, maxRetries = 3) { let attempt = 0; while (attempt < maxRetries) { try { const response = await fetch(url, options); if (response.status === 429 || response.status >= 500) { throw new Error(`Retryable error: ${response.status}`); } return response; } catch (error) { attempt++; if (attempt >= maxRetries) throw error; const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } } /** * Generates and downloads a well-formatted, multi-page PDF. */ const downloadPdf = () => { if (loadingOverlay) loadingOverlay.style.display = 'flex'; try { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); const pageHeight = doc.internal.pageSize.getHeight(); const margin = 40; let y = margin; const addText = (text, options) => { const fontSize = options.fontSize || 10; const lineHeight = fontSize * 1.2; doc.setFont(options.font || 'helvetica', options.fontStyle || 'normal'); doc.setFontSize(fontSize); if (options.color) doc.setTextColor(options.color[0], options.color[1], options.color[2]); const lines = doc.splitTextToSize(text, doc.internal.pageSize.getWidth() - margin * 2); if (y + (lines.length * lineHeight) > pageHeight - margin) { doc.addPage(); y = margin; } doc.text(lines, margin, y); y += (lines.length * lineHeight) + (options.marginBottom || 0); }; addText(`Phrasebook: ${sourceLangEl.value} to ${targetLangEl.value}`, { fontSize: 22, fontStyle: 'bold', marginBottom: 20 }); const categories = outputContainer.querySelectorAll('h3'); categories.forEach(catEl => { addText(catEl.innerText, { fontSize: 16, fontStyle: 'bold', color: [79, 70, 229], marginBottom: 10 }); const phrases = catEl.nextElementSibling.children; for(const phraseEl of phrases) { const sourceText = phraseEl.children[0].innerText; const translatedText = phraseEl.children[1].innerText; addText(sourceText, { fontSize: 10, color: [55, 65, 81] }); addText(translatedText, { fontSize: 10, color: [37, 99, 235], marginBottom: 8 }); } }); doc.save(`Phrasebook-${sourceLangEl.value}-to-${targetLangEl.value}.pdf`); } catch (error) { console.error("PDF Generation Error:", error); } finally { if (loadingOverlay) loadingOverlay.style.display = 'none'; } }; // IV.A.2: Attach primary event listeners generateBtn.addEventListener('click', generatePhrasebook); pdfDownloadBtn.addEventListener('click', downloadPdf); // Initial setup populateCategories(); });
Scroll to Top