// Initialize the app container. const app = document.getElementById('app'); app.innerHTML = `
Translation services starting...
`; // Add retry count let retryCount = 0; const MAX_RETRIES = 10; // Will try for 20 seconds (10 attempts * 2 second interval) function checkServicesStatus() { fetch('/healthcheck') .then(response => { if (response.ok) { window.servicesReady = true; const translationControls = document.getElementById('translationControls'); const statusIndicator = document.getElementById('statusIndicator'); if (translationControls) { translationControls.style.display = 'block'; } if (statusIndicator) { statusIndicator.style.display = 'none'; } return true; } throw new Error('Services not ready'); }) .catch(error => { retryCount++; const statusIndicator = document.getElementById('statusIndicator'); if (statusIndicator) { if (retryCount >= MAX_RETRIES) { statusIndicator.innerHTML = '❌ Failed to initialize translation services. Try restarting the space.'; statusIndicator.style.color = '#dc3545'; clearInterval(statusInterval); } else { statusIndicator.innerHTML = ` Translation services starting... (attempt ${retryCount}/${MAX_RETRIES})`; } } console.log('Waiting for services...', error); return false; }); } // Check status every 2 seconds until ready or max retries reached const statusInterval = setInterval(() => { if (window.servicesReady || retryCount >= MAX_RETRIES) { clearInterval(statusInterval); } else { checkServicesStatus(); } }, 2000); // Load Document Authoring SDK const script = document.createElement('script'); script.src = 'https://document-authoring-cdn.pspdfkit.com/releases/document-authoring-1.0.26-umd.js'; script.crossOrigin = true; document.head.appendChild(script); script.onload = async () => { const docAuthSystem = await DocAuth.createDocAuthSystem({}); const editor = await docAuthSystem.createEditor( document.getElementById("editor"), { document: await docAuthSystem.importDOCX(fetch('./Sample.docx')), } ); // Helper function to extract text runs with their paths function flattenTextRuns(obj, path = [], result = []) { for (let key in obj) { const newPath = [...path, key]; if (typeof obj[key] === "string" && key === "text" && obj[key].length > 0) { result.push({ text: obj[key], path: newPath }); } else if (typeof obj[key] === "object") { flattenTextRuns(obj[key], newPath, result); } } return result; } // Helper function to group adjacent text runs function groupAdjacentRuns(textRuns, maxChunkSize = 1000) { const groups = []; let currentGroup = []; let currentSize = 0; for (let run of textRuns) { if (currentSize + run.text.length > maxChunkSize && currentGroup.length > 0) { groups.push([...currentGroup]); currentGroup = []; currentSize = 0; } currentGroup.push(run); currentSize += run.text.length; } if (currentGroup.length > 0) { groups.push(currentGroup); } return groups; } async function translate(content, lang) { try { const response = await fetch('/client/api/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ messages: [ { role: "system", content: `Translate the following text to ${lang}. Each segment is marked with ||| delimiters. Translate each segment independently while preserving original capitalization and punctuation. Add spaces at the start or end of segments only if they exist in the original text.` }, { role: "user", content: content } ], model: "gemma-2b", stream: false }) }); if (!response.ok) { throw new Error('Translation request failed'); } const data = await response.json(); return data.choices[0].message.content; } catch (error) { console.error("Translation error:", error); throw new Error("Failed to generate translation"); } } // Function to update nested object value by path function setNestedValue(obj, path, value) { let current = obj; for (let i = 0; i < path.length - 1; i++) { current = current[path[i]]; } current[path[path.length - 1]] = value; } // Enhanced recursive translation function async function translateRecursive(docJson, lang) { const textRuns = flattenTextRuns(docJson); const groups = groupAdjacentRuns(textRuns); for (let group of groups) { // Prepare content for translation, preserving spaces const content = group.map(run => `|||${run.text}|||`).join('\n'); // Get translations const translatedContent = await translate(content, lang); // Parse translations const translations = translatedContent .split('|||') .filter(t => t.trim()) .map(t => t.trim()); // Update document group.forEach((run, index) => { if (translations[index]) { // Preserve original leading/trailing spaces const originalText = run.text; let translatedText = translations[index]; if (originalText.startsWith(' ')) { translatedText = ' ' + translatedText; } if (originalText.endsWith(' ')) { translatedText = translatedText + ' '; } setNestedValue(docJson, run.path, translatedText); } }); } } // Add translation button handler const translateButton = document.getElementById('translateButton'); const languageSelect = document.getElementById('languageSelect'); translateButton.addEventListener('click', async () => { try { const selectedLanguage = languageSelect.value; translateButton.disabled = true; translateButton.innerHTML = ' Translating...'; const jsonDoc = await editor.currentDocument().saveDocument(); await translateRecursive(jsonDoc.container.document, selectedLanguage); const translatedDoc = await docAuthSystem.loadDocument(jsonDoc); editor.setCurrentDocument(translatedDoc); } catch (error) { alert('Translation failed: ' + error.message); } finally { translateButton.disabled = false; translateButton.innerHTML = 'Translate Document'; } }); };