amine_dubs commited on
Commit
abc6641
·
1 Parent(s): be103a2

added more functions

Browse files
Files changed (3) hide show
  1. backend/main.py +102 -0
  2. static/script.js +225 -63
  3. static/style.css +65 -0
backend/main.py CHANGED
@@ -695,6 +695,108 @@ async def translate_document_endpoint(
695
  content={"success": False, "error": f"Document translation failed: {str(e)}"}
696
  )
697
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
  # Initialize models during startup
699
  @app.on_event("startup")
700
  async def startup_event():
 
695
  content={"success": False, "error": f"Document translation failed: {str(e)}"}
696
  )
697
 
698
+ @app.post("/download/translated-document")
699
+ async def download_translated_document(request: Request):
700
+ """Creates and returns a downloadable version of the translated document."""
701
+ try:
702
+ # Parse request body
703
+ data = await request.json()
704
+ content = data.get("content")
705
+ filename = data.get("filename")
706
+ original_type = data.get("original_type")
707
+
708
+ if not content or not filename:
709
+ return JSONResponse(
710
+ status_code=400,
711
+ content={"success": False, "error": "Missing required parameters"}
712
+ )
713
+
714
+ # Handle different file types
715
+ if filename.endswith('.txt'):
716
+ # Simple text file
717
+ from fastapi.responses import Response
718
+ return Response(
719
+ content=content.encode('utf-8'),
720
+ media_type="text/plain",
721
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
722
+ )
723
+
724
+ elif filename.endswith('.pdf'):
725
+ # Create PDF file
726
+ try:
727
+ import fitz # PyMuPDF
728
+ from io import BytesIO
729
+
730
+ # Create a new PDF document
731
+ doc = fitz.open()
732
+ page = doc.new_page()
733
+
734
+ # Insert text into the PDF
735
+ text_rect = fitz.Rect(50, 50, page.rect.width - 50, page.rect.height - 50)
736
+ page.insert_text(text_rect.tl, content, fontsize=11)
737
+
738
+ # Save to bytes
739
+ pdf_bytes = BytesIO()
740
+ doc.save(pdf_bytes)
741
+ doc.close()
742
+
743
+ # Return as attachment
744
+ from fastapi.responses import Response
745
+ return Response(
746
+ content=pdf_bytes.getvalue(),
747
+ media_type="application/pdf",
748
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
749
+ )
750
+ except ImportError:
751
+ return JSONResponse(
752
+ status_code=501,
753
+ content={"success": False, "error": "PDF creation requires PyMuPDF library"}
754
+ )
755
+
756
+ elif filename.endswith('.docx'):
757
+ # Create DOCX file
758
+ try:
759
+ import docx
760
+ from io import BytesIO
761
+
762
+ # Create a new document with the translated content
763
+ doc = docx.Document()
764
+ doc.add_paragraph(content)
765
+
766
+ # Save to bytes
767
+ docx_bytes = BytesIO()
768
+ doc.save(docx_bytes)
769
+
770
+ # Return as attachment
771
+ from fastapi.responses import Response
772
+ return Response(
773
+ content=docx_bytes.getvalue(),
774
+ media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
775
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
776
+ )
777
+ except ImportError:
778
+ return JSONResponse(
779
+ status_code=501,
780
+ content={"success": False, "error": "DOCX creation requires python-docx library"}
781
+ )
782
+
783
+ else:
784
+ # Fallback to text file
785
+ from fastapi.responses import Response
786
+ return Response(
787
+ content=content.encode('utf-8'),
788
+ media_type="text/plain",
789
+ headers={"Content-Disposition": f"attachment; filename={filename}.txt"}
790
+ )
791
+
792
+ except Exception as e:
793
+ print(f"Error creating downloadable document: {str(e)}")
794
+ traceback.print_exc()
795
+ return JSONResponse(
796
+ status_code=500,
797
+ content={"success": False, "error": f"Failed to create document: {str(e)}"}
798
+ )
799
+
800
  # Initialize models during startup
801
  @app.on_event("startup")
802
  async def startup_event():
static/script.js CHANGED
@@ -34,14 +34,15 @@ window.onload = function() {
34
  const sourceLangDoc = document.getElementById('source-lang-doc');
35
  const targetLangDoc = document.getElementById('target-lang-doc');
36
 
37
- // Get quick phrases elements
38
- const quickPhrasesContainer = document.getElementById('quick-phrases');
39
- const quickPhraseButtons = document.querySelectorAll('.quick-phrase');
40
-
41
  // Control buttons
42
- const swapLangBtn = document.getElementById('swap-lang-btn');
43
- const copyTextBtn = document.getElementById('copy-text-btn');
44
- const clearTextBtn = document.getElementById('clear-text-btn');
 
 
 
 
 
45
 
46
  // RTL language handling - list of languages that use RTL
47
  const rtlLanguages = ['ar', 'he'];
@@ -50,16 +51,16 @@ window.onload = function() {
50
  if (textTabLink && docTabLink && textSection && docSection) {
51
  textTabLink.addEventListener('click', function(e) {
52
  e.preventDefault();
53
- docSection.style.display = 'none';
54
- textSection.style.display = 'block';
55
  textTabLink.parentElement.classList.add('active');
56
  docTabLink.parentElement.classList.remove('active');
57
  });
58
 
59
  docTabLink.addEventListener('click', function(e) {
60
  e.preventDefault();
61
- textSection.style.display = 'none';
62
- docSection.style.display = 'block';
63
  docTabLink.parentElement.classList.add('active');
64
  textTabLink.parentElement.classList.remove('active');
65
  });
@@ -69,7 +70,7 @@ window.onload = function() {
69
  if (textInput && charCountElement) {
70
  textInput.addEventListener('input', function() {
71
  const charCount = textInput.value.length;
72
- charCountElement.textContent = `${charCount}`;
73
 
74
  // Add warning class if approaching or exceeding character limit
75
  if (charCount > 3000) {
@@ -83,19 +84,24 @@ window.onload = function() {
83
  }
84
 
85
  // Quick phrases implementation
86
- if (quickPhraseButtons && quickPhraseButtons.length > 0) {
87
- quickPhraseButtons.forEach(button => {
88
  button.addEventListener('click', function(e) {
89
  e.preventDefault();
90
- const phrase = this.getAttribute('data-phrase');
91
 
92
  if (phrase && textInput) {
 
 
 
 
 
93
  // Insert the phrase at cursor position, or append to end
94
  if (typeof textInput.selectionStart === 'number') {
95
  const startPos = textInput.selectionStart;
96
  const endPos = textInput.selectionEnd;
97
  const currentValue = textInput.value;
98
- const spaceChar = currentValue && currentValue[startPos - 1] !== ' ' ? ' ' : '';
99
 
100
  // Insert phrase at cursor position with space if needed
101
  textInput.value = currentValue.substring(0, startPos) +
@@ -108,7 +114,7 @@ window.onload = function() {
108
  } else {
109
  // Fallback for browsers that don't support selection
110
  const currentValue = textInput.value;
111
- const spaceChar = currentValue && currentValue[currentValue.length - 1] !== ' ' ? ' ' : '';
112
  textInput.value += spaceChar + phrase;
113
  }
114
 
@@ -118,14 +124,20 @@ window.onload = function() {
118
 
119
  // Focus back on the input
120
  textInput.focus();
 
 
 
 
 
 
121
  }
122
  });
123
  });
124
  }
125
 
126
  // Language swap functionality
127
- if (swapLangBtn && sourceLangText && targetLangText) {
128
- swapLangBtn.addEventListener('click', function(e) {
129
  e.preventDefault();
130
 
131
  // Don't swap if source is "auto" (language detection)
@@ -152,8 +164,9 @@ window.onload = function() {
152
  textInput.dispatchEvent(inputEvent);
153
 
154
  // Trigger translation
155
- const clickEvent = new Event('click');
156
- document.querySelector('#translate-text-btn').dispatchEvent(clickEvent);
 
157
  }
158
 
159
  // Apply RTL styling as needed
@@ -162,6 +175,27 @@ window.onload = function() {
162
  });
163
  }
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  // Apply RTL styling based on language
166
  function applyRtlStyling(langCode, element) {
167
  if (element) {
@@ -203,8 +237,8 @@ window.onload = function() {
203
  if (targetLangDoc) targetLangDoc.addEventListener('change', handleLanguageChange);
204
 
205
  // Copy translation to clipboard functionality
206
- if (copyTextBtn) {
207
- copyTextBtn.addEventListener('click', function() {
208
  if (textOutput && textOutput.textContent.trim() !== '') {
209
  navigator.clipboard.writeText(textOutput.textContent)
210
  .then(() => {
@@ -218,9 +252,25 @@ window.onload = function() {
218
  });
219
  }
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  // Clear text functionality
222
- if (clearTextBtn) {
223
- clearTextBtn.addEventListener('click', function() {
224
  if (textInput) {
225
  textInput.value = '';
226
  textOutput.textContent = '';
@@ -235,6 +285,31 @@ window.onload = function() {
235
  });
236
  }
237
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  // Text translation form submission
239
  if (textTranslationForm) {
240
  textTranslationForm.addEventListener('submit', function(e) {
@@ -260,7 +335,7 @@ window.onload = function() {
260
 
261
  const fileInput = document.getElementById('doc-input');
262
  if (!fileInput.files || fileInput.files.length === 0) {
263
- showNotification('Please select a document to translate.');
264
  return;
265
  }
266
 
@@ -273,12 +348,11 @@ window.onload = function() {
273
  }
274
 
275
  // File drag and drop
276
- const dropZone = document.getElementById('drop-zone');
277
- const fileInput = document.getElementById('doc-input');
278
 
279
- if (dropZone && fileInput) {
280
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
281
- dropZone.addEventListener(eventName, preventDefaults, false);
282
  });
283
 
284
  function preventDefaults(e) {
@@ -287,22 +361,22 @@ window.onload = function() {
287
  }
288
 
289
  ['dragenter', 'dragover'].forEach(eventName => {
290
- dropZone.addEventListener(eventName, highlight, false);
291
  });
292
 
293
  ['dragleave', 'drop'].forEach(eventName => {
294
- dropZone.addEventListener(eventName, unhighlight, false);
295
  });
296
 
297
  function highlight() {
298
- dropZone.classList.add('highlight');
299
  }
300
 
301
  function unhighlight() {
302
- dropZone.classList.remove('highlight');
303
  }
304
 
305
- dropZone.addEventListener('drop', handleDrop, false);
306
 
307
  function handleDrop(e) {
308
  const dt = e.dataTransfer;
@@ -311,25 +385,17 @@ window.onload = function() {
311
  if (files && files.length > 0) {
312
  fileInput.files = files;
313
  const fileName = files[0].name;
314
- fileNameDisplay.textContent = fileName;
315
  fileNameDisplay.style.display = 'block';
316
- docFilename.value = fileName;
 
 
 
 
 
 
317
  }
318
  }
319
-
320
- // Handle file selection through the input
321
- fileInput.addEventListener('change', function() {
322
- if (this.files && this.files.length > 0) {
323
- const fileName = this.files[0].name;
324
- fileNameDisplay.textContent = fileName;
325
- fileNameDisplay.style.display = 'block';
326
- docFilename.value = fileName;
327
- } else {
328
- fileNameDisplay.textContent = '';
329
- fileNameDisplay.style.display = 'none';
330
- docFilename.value = '';
331
- }
332
- });
333
  }
334
 
335
  // Text translation function
@@ -386,9 +452,6 @@ window.onload = function() {
386
  if (textOutput) {
387
  textOutput.textContent = data.translated_text;
388
 
389
- // Enable copy button
390
- if (copyTextBtn) copyTextBtn.disabled = false;
391
-
392
  // Apply RTL styling based on target language
393
  applyRtlStyling(targetLang, textOutput);
394
  }
@@ -402,10 +465,7 @@ window.onload = function() {
402
  if (textLoadingIndicator) textLoadingIndicator.style.display = 'none';
403
 
404
  // Show error message
405
- if (errorMessageElement) {
406
- errorMessageElement.style.display = 'block';
407
- errorMessageElement.textContent = `Translation error: ${error.message}`;
408
- }
409
  });
410
  }
411
 
@@ -464,9 +524,34 @@ window.onload = function() {
464
  docResult.classList.remove('hidden');
465
  docResult.style.display = 'flex'; // Ensure the result is visible
466
  }
 
467
  // Update filename and detected language
468
- if (docFilename) docFilename.textContent = data.original_filename || '';
469
  if (docSourceLang) docSourceLang.textContent = (data.detected_source_lang ? getLanguageName(data.detected_source_lang) : getLanguageName(sourceLang));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  })
471
  .catch(error => {
472
  console.error('Error during document translation:', error);
@@ -474,13 +559,77 @@ window.onload = function() {
474
  if (docLoadingIndicator) docLoadingIndicator.style.display = 'none';
475
 
476
  // Show error message
477
- if (errorMessageElement) {
478
- errorMessageElement.style.display = 'block';
479
- errorMessageElement.textContent = `Document translation error: ${error.message}`;
480
- }
481
  });
482
  }
483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  // Helper function to get language name from code
485
  function getLanguageName(code) {
486
  // Hard-coded mapping for common languages
@@ -531,6 +680,19 @@ window.onload = function() {
531
  }
532
  }
533
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  // Initialize by applying RTL styling based on initial language selection
535
  handleLanguageChange();
536
  };
 
34
  const sourceLangDoc = document.getElementById('source-lang-doc');
35
  const targetLangDoc = document.getElementById('target-lang-doc');
36
 
 
 
 
 
37
  // Control buttons
38
+ const swapLanguages = document.getElementById('swap-languages');
39
+ const swapLanguagesDoc = document.getElementById('swap-languages-doc');
40
+ const copyTranslation = document.getElementById('copy-translation');
41
+ const copyDocTranslation = document.getElementById('copy-doc-translation');
42
+ const clearSource = document.getElementById('clear-source');
43
+
44
+ // Get quick phrases elements
45
+ const phraseButtons = document.querySelectorAll('.phrase-btn');
46
 
47
  // RTL language handling - list of languages that use RTL
48
  const rtlLanguages = ['ar', 'he'];
 
51
  if (textTabLink && docTabLink && textSection && docSection) {
52
  textTabLink.addEventListener('click', function(e) {
53
  e.preventDefault();
54
+ docSection.classList.add('hidden');
55
+ textSection.classList.remove('hidden');
56
  textTabLink.parentElement.classList.add('active');
57
  docTabLink.parentElement.classList.remove('active');
58
  });
59
 
60
  docTabLink.addEventListener('click', function(e) {
61
  e.preventDefault();
62
+ textSection.classList.add('hidden');
63
+ docSection.classList.remove('hidden');
64
  docTabLink.parentElement.classList.add('active');
65
  textTabLink.parentElement.classList.remove('active');
66
  });
 
70
  if (textInput && charCountElement) {
71
  textInput.addEventListener('input', function() {
72
  const charCount = textInput.value.length;
73
+ charCountElement.textContent = charCount;
74
 
75
  // Add warning class if approaching or exceeding character limit
76
  if (charCount > 3000) {
 
84
  }
85
 
86
  // Quick phrases implementation
87
+ if (phraseButtons && phraseButtons.length > 0) {
88
+ phraseButtons.forEach(button => {
89
  button.addEventListener('click', function(e) {
90
  e.preventDefault();
91
+ const phrase = this.getAttribute('data-text') || this.getAttribute('data-phrase');
92
 
93
  if (phrase && textInput) {
94
+ // If not on the text tab, switch to it
95
+ if (textSection.classList.contains('hidden')) {
96
+ textTabLink.click();
97
+ }
98
+
99
  // Insert the phrase at cursor position, or append to end
100
  if (typeof textInput.selectionStart === 'number') {
101
  const startPos = textInput.selectionStart;
102
  const endPos = textInput.selectionEnd;
103
  const currentValue = textInput.value;
104
+ const spaceChar = currentValue && currentValue[startPos - 1] !== ' ' && startPos > 0 ? ' ' : '';
105
 
106
  // Insert phrase at cursor position with space if needed
107
  textInput.value = currentValue.substring(0, startPos) +
 
114
  } else {
115
  // Fallback for browsers that don't support selection
116
  const currentValue = textInput.value;
117
+ const spaceChar = currentValue && currentValue.length > 0 && currentValue[currentValue.length - 1] !== ' ' ? ' ' : '';
118
  textInput.value += spaceChar + phrase;
119
  }
120
 
 
124
 
125
  // Focus back on the input
126
  textInput.focus();
127
+
128
+ // Auto translate if needed
129
+ const autoTranslate = this.getAttribute('data-auto-translate') === 'true';
130
+ if (autoTranslate && textTranslationForm) {
131
+ textTranslationForm.dispatchEvent(new Event('submit'));
132
+ }
133
  }
134
  });
135
  });
136
  }
137
 
138
  // Language swap functionality
139
+ if (swapLanguages && sourceLangText && targetLangText) {
140
+ swapLanguages.addEventListener('click', function(e) {
141
  e.preventDefault();
142
 
143
  // Don't swap if source is "auto" (language detection)
 
164
  textInput.dispatchEvent(inputEvent);
165
 
166
  // Trigger translation
167
+ if (textTranslationForm) {
168
+ textTranslationForm.dispatchEvent(new Event('submit'));
169
+ }
170
  }
171
 
172
  // Apply RTL styling as needed
 
175
  });
176
  }
177
 
178
+ // Document language swap functionality
179
+ if (swapLanguagesDoc && sourceLangDoc && targetLangDoc) {
180
+ swapLanguagesDoc.addEventListener('click', function(e) {
181
+ e.preventDefault();
182
+
183
+ // Don't swap if source is "auto" (language detection)
184
+ if (sourceLangDoc.value === 'auto') {
185
+ showNotification('Cannot swap when source language is set to auto-detect.');
186
+ return;
187
+ }
188
+
189
+ // Store the current values
190
+ const sourceValue = sourceLangDoc.value;
191
+ const targetValue = targetLangDoc.value;
192
+
193
+ // Swap the values
194
+ sourceLangDoc.value = targetValue;
195
+ targetLangDoc.value = sourceValue;
196
+ });
197
+ }
198
+
199
  // Apply RTL styling based on language
200
  function applyRtlStyling(langCode, element) {
201
  if (element) {
 
237
  if (targetLangDoc) targetLangDoc.addEventListener('change', handleLanguageChange);
238
 
239
  // Copy translation to clipboard functionality
240
+ if (copyTranslation) {
241
+ copyTranslation.addEventListener('click', function() {
242
  if (textOutput && textOutput.textContent.trim() !== '') {
243
  navigator.clipboard.writeText(textOutput.textContent)
244
  .then(() => {
 
252
  });
253
  }
254
 
255
+ // Copy document translation to clipboard
256
+ if (copyDocTranslation) {
257
+ copyDocTranslation.addEventListener('click', function() {
258
+ if (docOutput && docOutput.textContent.trim() !== '') {
259
+ navigator.clipboard.writeText(docOutput.textContent)
260
+ .then(() => {
261
+ showNotification('Document translation copied to clipboard!');
262
+ })
263
+ .catch(err => {
264
+ console.error('Error copying document: ', err);
265
+ showNotification('Failed to copy document. Please try again.');
266
+ });
267
+ }
268
+ });
269
+ }
270
+
271
  // Clear text functionality
272
+ if (clearSource) {
273
+ clearSource.addEventListener('click', function() {
274
  if (textInput) {
275
  textInput.value = '';
276
  textOutput.textContent = '';
 
285
  });
286
  }
287
 
288
+ // File input handler - Update UI when file is selected
289
+ const fileInput = document.getElementById('doc-input');
290
+ const translateDocumentBtn = document.querySelector('#doc-translation-form .translate-button');
291
+
292
+ if (fileInput && fileNameDisplay && translateDocumentBtn) {
293
+ fileInput.addEventListener('change', function() {
294
+ if (this.files && this.files.length > 0) {
295
+ const fileName = this.files[0].name;
296
+ fileNameDisplay.textContent = `File selected: ${fileName}`;
297
+ fileNameDisplay.style.display = 'block';
298
+
299
+ // Make translate button colored
300
+ translateDocumentBtn.classList.add('active-button');
301
+
302
+ showNotification('Document uploaded successfully!');
303
+ } else {
304
+ fileNameDisplay.textContent = '';
305
+ fileNameDisplay.style.display = 'none';
306
+
307
+ // Make translate button transparent
308
+ translateDocumentBtn.classList.remove('active-button');
309
+ }
310
+ });
311
+ }
312
+
313
  // Text translation form submission
314
  if (textTranslationForm) {
315
  textTranslationForm.addEventListener('submit', function(e) {
 
335
 
336
  const fileInput = document.getElementById('doc-input');
337
  if (!fileInput.files || fileInput.files.length === 0) {
338
+ showError('Please select a document to translate.');
339
  return;
340
  }
341
 
 
348
  }
349
 
350
  // File drag and drop
351
+ const fileUploadArea = document.querySelector('.file-upload-area');
 
352
 
353
+ if (fileUploadArea && fileInput) {
354
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
355
+ fileUploadArea.addEventListener(eventName, preventDefaults, false);
356
  });
357
 
358
  function preventDefaults(e) {
 
361
  }
362
 
363
  ['dragenter', 'dragover'].forEach(eventName => {
364
+ fileUploadArea.addEventListener(eventName, highlight, false);
365
  });
366
 
367
  ['dragleave', 'drop'].forEach(eventName => {
368
+ fileUploadArea.addEventListener(eventName, unhighlight, false);
369
  });
370
 
371
  function highlight() {
372
+ fileUploadArea.classList.add('highlight');
373
  }
374
 
375
  function unhighlight() {
376
+ fileUploadArea.classList.remove('highlight');
377
  }
378
 
379
+ fileUploadArea.addEventListener('drop', handleDrop, false);
380
 
381
  function handleDrop(e) {
382
  const dt = e.dataTransfer;
 
385
  if (files && files.length > 0) {
386
  fileInput.files = files;
387
  const fileName = files[0].name;
388
+ fileNameDisplay.textContent = `File selected: ${fileName}`;
389
  fileNameDisplay.style.display = 'block';
390
+
391
+ // Make translate button colored
392
+ if (translateDocumentBtn) {
393
+ translateDocumentBtn.classList.add('active-button');
394
+ }
395
+
396
+ showNotification('Document uploaded successfully!');
397
  }
398
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  }
400
 
401
  // Text translation function
 
452
  if (textOutput) {
453
  textOutput.textContent = data.translated_text;
454
 
 
 
 
455
  // Apply RTL styling based on target language
456
  applyRtlStyling(targetLang, textOutput);
457
  }
 
465
  if (textLoadingIndicator) textLoadingIndicator.style.display = 'none';
466
 
467
  // Show error message
468
+ showError(`Translation error: ${error.message}`);
 
 
 
469
  });
470
  }
471
 
 
524
  docResult.classList.remove('hidden');
525
  docResult.style.display = 'flex'; // Ensure the result is visible
526
  }
527
+
528
  // Update filename and detected language
529
+ if (docFilename) docFilename.textContent = data.original_filename || file.name;
530
  if (docSourceLang) docSourceLang.textContent = (data.detected_source_lang ? getLanguageName(data.detected_source_lang) : getLanguageName(sourceLang));
531
+
532
+ // Add download button
533
+ const resultArea = document.querySelector('.document-result-area');
534
+ if (resultArea) {
535
+ // Remove existing download button if there was one
536
+ const existingDownloadBtn = document.getElementById('download-translated-doc');
537
+ if (existingDownloadBtn) {
538
+ existingDownloadBtn.remove();
539
+ }
540
+
541
+ // Create download button
542
+ const downloadBtn = document.createElement('button');
543
+ downloadBtn.id = 'download-translated-doc';
544
+ downloadBtn.className = 'download-button';
545
+ downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download Translation';
546
+
547
+ // Add event listener for download
548
+ downloadBtn.addEventListener('click', function() {
549
+ downloadTranslatedDocument(data.translated_text, file.name, file.type);
550
+ });
551
+
552
+ // Append to result area
553
+ resultArea.appendChild(downloadBtn);
554
+ }
555
  })
556
  .catch(error => {
557
  console.error('Error during document translation:', error);
 
559
  if (docLoadingIndicator) docLoadingIndicator.style.display = 'none';
560
 
561
  // Show error message
562
+ showError(`Document translation error: ${error.message}`);
 
 
 
563
  });
564
  }
565
 
566
+ // Function to download translated document
567
+ function downloadTranslatedDocument(content, fileName, fileType) {
568
+ // Determine the file extension
569
+ let extension = '';
570
+ if (fileName.endsWith('.pdf')) {
571
+ extension = '.pdf';
572
+ } else if (fileName.endsWith('.docx')) {
573
+ extension = '.docx';
574
+ } else if (fileName.endsWith('.txt')) {
575
+ extension = '.txt';
576
+ } else {
577
+ extension = '.txt'; // Default to txt
578
+ }
579
+
580
+ // Create file name for translated document
581
+ const baseName = fileName.substring(0, fileName.lastIndexOf('.'));
582
+ const translatedFileName = `${baseName}_translated${extension}`;
583
+
584
+ // For simplicity, we'll handle text downloads here
585
+ // For PDF and DOCX, we would need server-side processing
586
+ if (extension === '.txt') {
587
+ const blob = new Blob([content], { type: 'text/plain' });
588
+ const url = URL.createObjectURL(blob);
589
+
590
+ const a = document.createElement('a');
591
+ a.href = url;
592
+ a.download = translatedFileName;
593
+ document.body.appendChild(a);
594
+ a.click();
595
+ document.body.removeChild(a);
596
+ URL.revokeObjectURL(url);
597
+ } else {
598
+ // For non-text files, we need to request a download from the server
599
+ fetch('/download/translated-document', {
600
+ method: 'POST',
601
+ headers: {
602
+ 'Content-Type': 'application/json',
603
+ },
604
+ body: JSON.stringify({
605
+ content: content,
606
+ filename: translatedFileName,
607
+ original_type: fileType
608
+ }),
609
+ })
610
+ .then(response => {
611
+ if (!response.ok) {
612
+ throw new Error('Failed to generate document for download');
613
+ }
614
+ return response.blob();
615
+ })
616
+ .then(blob => {
617
+ const url = URL.createObjectURL(blob);
618
+ const a = document.createElement('a');
619
+ a.href = url;
620
+ a.download = translatedFileName;
621
+ document.body.appendChild(a);
622
+ a.click();
623
+ document.body.removeChild(a);
624
+ URL.revokeObjectURL(url);
625
+ })
626
+ .catch(error => {
627
+ console.error('Error downloading document:', error);
628
+ showError(`Download error: ${error.message}`);
629
+ });
630
+ }
631
+ }
632
+
633
  // Helper function to get language name from code
634
  function getLanguageName(code) {
635
  // Hard-coded mapping for common languages
 
680
  }
681
  }
682
 
683
+ // Display error message
684
+ function showError(message) {
685
+ if (errorMessageElement) {
686
+ errorMessageElement.textContent = message;
687
+ errorMessageElement.style.display = 'block';
688
+
689
+ // Hide after 5 seconds
690
+ setTimeout(() => {
691
+ errorMessageElement.style.display = 'none';
692
+ }, 5000);
693
+ }
694
+ }
695
+
696
  // Initialize by applying RTL styling based on initial language selection
697
  handleLanguageChange();
698
  };
static/style.css CHANGED
@@ -318,6 +318,24 @@ textarea#text-input:focus {
318
  cursor: not-allowed;
319
  }
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  /* Document upload area */
322
  .file-upload-area {
323
  border: 2px dashed #ddd;
@@ -361,6 +379,10 @@ input.file-input {
361
  font-size: 0.9rem;
362
  color: #4285f4;
363
  font-weight: 500;
 
 
 
 
364
  }
365
 
366
  .document-result-area {
@@ -382,6 +404,30 @@ input.file-input {
382
  color: #777;
383
  }
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  /* Loading indicator */
386
  .loading-indicator {
387
  display: none;
@@ -434,6 +480,20 @@ input.file-input {
434
  .error-message {
435
  background-color: #f44336;
436
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
 
439
  /* Footer */
@@ -520,6 +580,11 @@ footer {
520
  font-weight: normal;
521
  }
522
 
 
 
 
 
 
523
  /* Responsive design */
524
  @media (max-width: 768px) {
525
  .translation-panels {
 
318
  cursor: not-allowed;
319
  }
320
 
321
+ /* Active translate button */
322
+ .translate-button.active-button {
323
+ background-color: #4285f4;
324
+ opacity: 1;
325
+ }
326
+
327
+ /* Translate button for documents - default transparent until file is selected */
328
+ #doc-translation-form .translate-button {
329
+ background-color: rgba(66, 133, 244, 0.5);
330
+ opacity: 0.7;
331
+ transition: all 0.3s ease;
332
+ }
333
+
334
+ #doc-translation-form .translate-button.active-button {
335
+ background-color: #4285f4;
336
+ opacity: 1;
337
+ }
338
+
339
  /* Document upload area */
340
  .file-upload-area {
341
  border: 2px dashed #ddd;
 
379
  font-size: 0.9rem;
380
  color: #4285f4;
381
  font-weight: 500;
382
+ padding: 0.5rem;
383
+ background-color: #e8f0fe;
384
+ border-radius: 4px;
385
+ display: none;
386
  }
387
 
388
  .document-result-area {
 
404
  color: #777;
405
  }
406
 
407
+ /* Download button */
408
+ .download-button {
409
+ background-color: #34A853;
410
+ color: white;
411
+ border: none;
412
+ border-radius: 5px;
413
+ padding: 0.8rem 1.5rem;
414
+ font-size: 1rem;
415
+ cursor: pointer;
416
+ transition: background-color 0.3s;
417
+ display: flex;
418
+ align-items: center;
419
+ gap: 0.5rem;
420
+ margin: 1.5rem auto;
421
+ }
422
+
423
+ .download-button:hover {
424
+ background-color: #2d8e47;
425
+ }
426
+
427
+ .download-button i {
428
+ font-size: 1.1rem;
429
+ }
430
+
431
  /* Loading indicator */
432
  .loading-indicator {
433
  display: none;
 
480
  .error-message {
481
  background-color: #f44336;
482
  color: white;
483
+ padding: 1rem;
484
+ border-radius: 4px;
485
+ margin-bottom: 1rem;
486
+ text-align: center;
487
+ display: none;
488
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
489
+ }
490
+
491
+ /* Show animation for notification */
492
+ .notification.show,
493
+ .error-message.show {
494
+ display: block;
495
+ opacity: 1;
496
+ animation: fadeIn 0.3s;
497
  }
498
 
499
  /* Footer */
 
580
  font-weight: normal;
581
  }
582
 
583
+ /* RTL support for text areas and panels */
584
+ [dir="rtl"] {
585
+ text-align: right;
586
+ }
587
+
588
  /* Responsive design */
589
  @media (max-width: 768px) {
590
  .translation-panels {