Rizwan1010 commited on
Commit
4106b79
·
verified ·
1 Parent(s): 837de9e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1159 -19
index.html CHANGED
@@ -1,19 +1,1159 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0"
8
+ />
9
+
10
+ <meta
11
+ name="description"
12
+ content="Free Urdu, Hindi, Roman Urdu and Hinglish to English translator."
13
+ />
14
+
15
+ <title>Urdu & Hindi to English Translator</title>
16
+
17
+ <style>
18
+ :root {
19
+ --primary: #3157f6;
20
+ --primary-dark: #2446cc;
21
+ --secondary: #7b4dff;
22
+ --background: #f4f7fb;
23
+ --card: #ffffff;
24
+ --text: #172033;
25
+ --muted: #667085;
26
+ --border: #dce3ef;
27
+ --success: #14804a;
28
+ --success-bg: #ecfdf3;
29
+ --error: #b42318;
30
+ --error-bg: #fff4f2;
31
+ --warning-bg: #fffaeb;
32
+ --warning-border: #fedf89;
33
+ --warning-text: #7a4d00;
34
+ --shadow: 0 18px 60px rgba(35, 55, 95, 0.12);
35
+ }
36
+
37
+ * {
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ body {
42
+ margin: 0;
43
+ min-height: 100vh;
44
+ background:
45
+ radial-gradient(circle at 10% 0%, #dfe8ff, transparent 34%),
46
+ var(--background);
47
+ color: var(--text);
48
+ font-family:
49
+ Inter,
50
+ -apple-system,
51
+ BlinkMacSystemFont,
52
+ "Segoe UI",
53
+ sans-serif;
54
+ }
55
+
56
+ button,
57
+ textarea,
58
+ select,
59
+ input {
60
+ font: inherit;
61
+ }
62
+
63
+ main {
64
+ width: min(1120px, calc(100% - 28px));
65
+ margin: 28px auto;
66
+ }
67
+
68
+ .header {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 14px;
72
+ margin-bottom: 20px;
73
+ }
74
+
75
+ .logo {
76
+ width: 52px;
77
+ height: 52px;
78
+ display: grid;
79
+ place-items: center;
80
+ flex-shrink: 0;
81
+ border-radius: 16px;
82
+ background: linear-gradient(
83
+ 135deg,
84
+ var(--primary),
85
+ var(--secondary)
86
+ );
87
+ color: #ffffff;
88
+ font-size: 25px;
89
+ font-weight: 800;
90
+ box-shadow: 0 12px 30px rgba(49, 87, 246, 0.25);
91
+ }
92
+
93
+ h1 {
94
+ margin: 0;
95
+ font-size: clamp(1.55rem, 3vw, 2.3rem);
96
+ line-height: 1.15;
97
+ }
98
+
99
+ .subtitle {
100
+ margin-top: 5px;
101
+ color: var(--muted);
102
+ }
103
+
104
+ .card {
105
+ padding: 22px;
106
+ background: var(--card);
107
+ border: 1px solid var(--border);
108
+ border-radius: 24px;
109
+ box-shadow: var(--shadow);
110
+ }
111
+
112
+ .translator-grid {
113
+ display: grid;
114
+ grid-template-columns: 1fr 1fr;
115
+ gap: 18px;
116
+ }
117
+
118
+ .field {
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 9px;
122
+ }
123
+
124
+ .field-header {
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: space-between;
128
+ gap: 12px;
129
+ }
130
+
131
+ label {
132
+ font-weight: 800;
133
+ }
134
+
135
+ .small-label {
136
+ color: var(--muted);
137
+ font-size: 0.85rem;
138
+ font-weight: 600;
139
+ }
140
+
141
+ textarea,
142
+ select {
143
+ width: 100%;
144
+ color: var(--text);
145
+ background: #ffffff;
146
+ border: 1px solid var(--border);
147
+ outline: none;
148
+ transition:
149
+ border-color 0.2s ease,
150
+ box-shadow 0.2s ease;
151
+ }
152
+
153
+ textarea {
154
+ min-height: 320px;
155
+ padding: 16px;
156
+ border-radius: 16px;
157
+ resize: vertical;
158
+ line-height: 1.65;
159
+ }
160
+
161
+ select {
162
+ width: auto;
163
+ min-width: 155px;
164
+ padding: 11px 13px;
165
+ border-radius: 12px;
166
+ }
167
+
168
+ textarea:focus,
169
+ select:focus {
170
+ border-color: var(--primary);
171
+ box-shadow: 0 0 0 4px rgba(49, 87, 246, 0.1);
172
+ }
173
+
174
+ textarea[readonly] {
175
+ background: #fbfcff;
176
+ }
177
+
178
+ .controls {
179
+ display: flex;
180
+ flex-wrap: wrap;
181
+ gap: 9px;
182
+ margin-top: 12px;
183
+ }
184
+
185
+ button,
186
+ .file-button {
187
+ display: inline-flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ gap: 7px;
191
+ min-height: 42px;
192
+ padding: 10px 15px;
193
+ border: 0;
194
+ border-radius: 12px;
195
+ font-weight: 800;
196
+ text-decoration: none;
197
+ cursor: pointer;
198
+ transition:
199
+ transform 0.15s ease,
200
+ background-color 0.15s ease,
201
+ opacity 0.15s ease;
202
+ }
203
+
204
+ button:hover,
205
+ .file-button:hover {
206
+ transform: translateY(-1px);
207
+ }
208
+
209
+ button:disabled {
210
+ opacity: 0.55;
211
+ cursor: not-allowed;
212
+ transform: none;
213
+ }
214
+
215
+ .primary-button {
216
+ color: #ffffff;
217
+ background: var(--primary);
218
+ }
219
+
220
+ .primary-button:hover {
221
+ background: var(--primary-dark);
222
+ }
223
+
224
+ .secondary-button,
225
+ .file-button {
226
+ color: #344054;
227
+ background: #eef2f8;
228
+ }
229
+
230
+ .danger-button {
231
+ color: var(--error);
232
+ background: #fff0ee;
233
+ }
234
+
235
+ input[type="file"] {
236
+ display: none;
237
+ }
238
+
239
+ .metadata {
240
+ display: flex;
241
+ flex-wrap: wrap;
242
+ gap: 8px;
243
+ margin-top: 15px;
244
+ }
245
+
246
+ .pill {
247
+ padding: 7px 11px;
248
+ color: #475467;
249
+ background: #f2f4f7;
250
+ border-radius: 999px;
251
+ font-size: 0.84rem;
252
+ font-weight: 700;
253
+ }
254
+
255
+ .status {
256
+ margin-top: 16px;
257
+ padding: 12px 14px;
258
+ color: var(--muted);
259
+ background: #f8fafc;
260
+ border: 1px solid var(--border);
261
+ border-radius: 12px;
262
+ line-height: 1.5;
263
+ }
264
+
265
+ .status.success {
266
+ color: var(--success);
267
+ background: var(--success-bg);
268
+ border-color: #abefc6;
269
+ }
270
+
271
+ .status.error {
272
+ color: var(--error);
273
+ background: var(--error-bg);
274
+ border-color: #fecdca;
275
+ }
276
+
277
+ .microphone-section {
278
+ margin-top: 19px;
279
+ padding-top: 19px;
280
+ border-top: 1px solid var(--border);
281
+ }
282
+
283
+ .microphone-section h2 {
284
+ margin: 0 0 6px;
285
+ font-size: 1.05rem;
286
+ }
287
+
288
+ .microphone-section p {
289
+ margin: 0;
290
+ color: var(--muted);
291
+ line-height: 1.5;
292
+ }
293
+
294
+ .notice {
295
+ margin-top: 18px;
296
+ padding: 14px;
297
+ color: var(--warning-text);
298
+ background: var(--warning-bg);
299
+ border: 1px solid var(--warning-border);
300
+ border-radius: 13px;
301
+ line-height: 1.55;
302
+ }
303
+
304
+ .footer {
305
+ margin-top: 18px;
306
+ color: var(--muted);
307
+ text-align: center;
308
+ font-size: 0.88rem;
309
+ }
310
+
311
+ @media (max-width: 800px) {
312
+ .translator-grid {
313
+ grid-template-columns: 1fr;
314
+ }
315
+
316
+ textarea {
317
+ min-height: 230px;
318
+ }
319
+
320
+ .card {
321
+ padding: 15px;
322
+ border-radius: 18px;
323
+ }
324
+
325
+ .field-header {
326
+ align-items: flex-start;
327
+ flex-direction: column;
328
+ }
329
+
330
+ select {
331
+ width: 100%;
332
+ }
333
+ }
334
+
335
+ @media (max-width: 480px) {
336
+ main {
337
+ width: min(100% - 18px, 1120px);
338
+ margin: 16px auto;
339
+ }
340
+
341
+ .header {
342
+ align-items: flex-start;
343
+ }
344
+
345
+ .logo {
346
+ width: 46px;
347
+ height: 46px;
348
+ }
349
+
350
+ button,
351
+ .file-button {
352
+ width: 100%;
353
+ }
354
+ }
355
+ </style>
356
+ </head>
357
+
358
+ <body>
359
+ <main>
360
+ <header class="header">
361
+ <div class="logo" aria-hidden="true">文</div>
362
+
363
+ <div>
364
+ <h1>AI English Translator</h1>
365
+
366
+ <div class="subtitle">
367
+ Urdu · Hindi · Roman Urdu · Hinglish → English
368
+ </div>
369
+ </div>
370
+ </header>
371
+
372
+ <section class="card">
373
+ <div class="translator-grid">
374
+ <div class="field">
375
+ <div class="field-header">
376
+ <label for="sourceText">Original text</label>
377
+
378
+ <span class="small-label">
379
+ Enter or dictate your message
380
+ </span>
381
+ </div>
382
+
383
+ <textarea
384
+ id="sourceText"
385
+ dir="auto"
386
+ maxlength="5000"
387
+ placeholder="اردو، हिंदी, Roman Urdu ya Hinglish yahan likhein..."
388
+ ></textarea>
389
+
390
+ <div class="controls">
391
+ <select
392
+ id="sourceLanguage"
393
+ aria-label="Select source language"
394
+ >
395
+ <option value="auto">Auto detect</option>
396
+ <option value="ur">Urdu</option>
397
+ <option value="hi">Hindi</option>
398
+ </select>
399
+
400
+ <label class="file-button" for="textFile">
401
+ Import TXT
402
+ </label>
403
+
404
+ <input
405
+ id="textFile"
406
+ type="file"
407
+ accept=".txt,text/plain"
408
+ />
409
+ </div>
410
+ </div>
411
+
412
+ <div class="field">
413
+ <div class="field-header">
414
+ <label for="translatedText">
415
+ English translation
416
+ </label>
417
+
418
+ <span class="small-label">
419
+ Generated translation
420
+ </span>
421
+ </div>
422
+
423
+ <textarea
424
+ id="translatedText"
425
+ readonly
426
+ placeholder="Your English translation will appear here..."
427
+ ></textarea>
428
+
429
+ <div class="controls">
430
+ <button
431
+ id="translateButton"
432
+ class="primary-button"
433
+ type="button"
434
+ >
435
+ Translate
436
+ </button>
437
+
438
+ <button
439
+ id="copyButton"
440
+ class="secondary-button"
441
+ type="button"
442
+ >
443
+ Copy
444
+ </button>
445
+
446
+ <button
447
+ id="downloadButton"
448
+ class="secondary-button"
449
+ type="button"
450
+ >
451
+ Download
452
+ </button>
453
+
454
+ <button
455
+ id="clearButton"
456
+ class="danger-button"
457
+ type="button"
458
+ >
459
+ Clear
460
+ </button>
461
+ </div>
462
+ </div>
463
+ </div>
464
+
465
+ <div class="metadata">
466
+ <span id="detectedLanguage" class="pill">
467
+ Detected: —
468
+ </span>
469
+
470
+ <span id="characterCount" class="pill">
471
+ 0 characters
472
+ </span>
473
+ </div>
474
+
475
+ <div
476
+ id="statusMessage"
477
+ class="status"
478
+ role="status"
479
+ aria-live="polite"
480
+ >
481
+ Ready.
482
+ </div>
483
+
484
+ <section class="microphone-section">
485
+ <h2>Browser microphone dictation</h2>
486
+
487
+ <p>
488
+ Speak into your microphone and the browser will add the
489
+ transcript to the original text box.
490
+ </p>
491
+
492
+ <div class="controls">
493
+ <select
494
+ id="speechLanguage"
495
+ aria-label="Select microphone language"
496
+ >
497
+ <option value="ur-PK">Urdu — Pakistan</option>
498
+ <option value="hi-IN">Hindi — India</option>
499
+ <option value="en-IN">Hinglish — India</option>
500
+ </select>
501
+
502
+ <button
503
+ id="startMicrophoneButton"
504
+ class="primary-button"
505
+ type="button"
506
+ >
507
+ Start microphone
508
+ </button>
509
+
510
+ <button
511
+ id="stopMicrophoneButton"
512
+ class="danger-button"
513
+ type="button"
514
+ disabled
515
+ >
516
+ Stop microphone
517
+ </button>
518
+ </div>
519
+ </section>
520
+
521
+ <div class="notice">
522
+ <strong>Static Space edition:</strong>
523
+ this version supports browser-based text translation,
524
+ microphone dictation and TXT imports. Audio files, videos,
525
+ PDFs, DOCX files and image OCR require the full Python
526
+ application. Translation is sent to a third-party public
527
+ translation service, so do not enter confidential information.
528
+ </div>
529
+ </section>
530
+
531
+ <footer class="footer">
532
+ Free browser-based Urdu, Hindi, Roman Urdu and Hinglish
533
+ translator.
534
+ </footer>
535
+ </main>
536
+
537
+ <script>
538
+ "use strict";
539
+
540
+ const sourceText = document.getElementById("sourceText");
541
+ const translatedText =
542
+ document.getElementById("translatedText");
543
+
544
+ const sourceLanguage =
545
+ document.getElementById("sourceLanguage");
546
+
547
+ const speechLanguage =
548
+ document.getElementById("speechLanguage");
549
+
550
+ const textFile = document.getElementById("textFile");
551
+
552
+ const translateButton =
553
+ document.getElementById("translateButton");
554
+
555
+ const copyButton =
556
+ document.getElementById("copyButton");
557
+
558
+ const downloadButton =
559
+ document.getElementById("downloadButton");
560
+
561
+ const clearButton =
562
+ document.getElementById("clearButton");
563
+
564
+ const startMicrophoneButton =
565
+ document.getElementById("startMicrophoneButton");
566
+
567
+ const stopMicrophoneButton =
568
+ document.getElementById("stopMicrophoneButton");
569
+
570
+ const detectedLanguage =
571
+ document.getElementById("detectedLanguage");
572
+
573
+ const characterCount =
574
+ document.getElementById("characterCount");
575
+
576
+ const statusMessage =
577
+ document.getElementById("statusMessage");
578
+
579
+ const MAX_FILE_SIZE = 1024 * 1024;
580
+ const MAX_TRANSLATION_LENGTH = 5000;
581
+ const CHUNK_LENGTH = 450;
582
+
583
+ function updateStatus(message, type = "") {
584
+ statusMessage.textContent = message;
585
+ statusMessage.className = "status";
586
+
587
+ if (type === "success") {
588
+ statusMessage.classList.add("success");
589
+ }
590
+
591
+ if (type === "error") {
592
+ statusMessage.classList.add("error");
593
+ }
594
+ }
595
+
596
+ function updateCharacterCount() {
597
+ const count = sourceText.value.length;
598
+
599
+ characterCount.textContent =
600
+ `${count.toLocaleString()} characters`;
601
+ }
602
+
603
+ function detectInputLanguage(text) {
604
+ const trimmedText = text.trim();
605
+
606
+ if (!trimmedText) {
607
+ return {
608
+ code: "ur",
609
+ label: "Unknown"
610
+ };
611
+ }
612
+
613
+ const urduCharacters =
614
+ trimmedText.match(/[\u0600-\u06FF]/g) || [];
615
+
616
+ const hindiCharacters =
617
+ trimmedText.match(/[\u0900-\u097F]/g) || [];
618
+
619
+ if (urduCharacters.length > hindiCharacters.length) {
620
+ return {
621
+ code: "ur",
622
+ label: "Urdu"
623
+ };
624
+ }
625
+
626
+ if (hindiCharacters.length > urduCharacters.length) {
627
+ return {
628
+ code: "hi",
629
+ label: "Hindi"
630
+ };
631
+ }
632
+
633
+ const romanUrduWords = [
634
+ "aap",
635
+ "ap",
636
+ "main",
637
+ "mein",
638
+ "mujhe",
639
+ "mera",
640
+ "meri",
641
+ "mere",
642
+ "hum",
643
+ "ham",
644
+ "hai",
645
+ "hain",
646
+ "ho",
647
+ "tha",
648
+ "thi",
649
+ "nahi",
650
+ "nahin",
651
+ "kya",
652
+ "kyun",
653
+ "kaise",
654
+ "kab",
655
+ "kahan",
656
+ "karna",
657
+ "karo",
658
+ "karen",
659
+ "chahiye",
660
+ "acha",
661
+ "achha",
662
+ "theek",
663
+ "lekin",
664
+ "aur",
665
+ "wala",
666
+ "wali",
667
+ "liye",
668
+ "bohat",
669
+ "bahut"
670
+ ];
671
+
672
+ const normalizedWords = trimmedText
673
+ .toLowerCase()
674
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
675
+ .split(/\s+/)
676
+ .filter(Boolean);
677
+
678
+ const romanMatchCount = normalizedWords.filter(
679
+ (word) => romanUrduWords.includes(word)
680
+ ).length;
681
+
682
+ if (romanMatchCount >= 1) {
683
+ return {
684
+ code: "ur",
685
+ label: "Roman Urdu / Hinglish"
686
+ };
687
+ }
688
+
689
+ return {
690
+ code: "ur",
691
+ label: "Roman or unknown"
692
+ };
693
+ }
694
+
695
+ function splitTextIntoChunks(text, maximumLength) {
696
+ const chunks = [];
697
+ let remainingText = text.trim();
698
+
699
+ while (remainingText.length > maximumLength) {
700
+ let splitPosition =
701
+ remainingText.lastIndexOf(" ", maximumLength);
702
+
703
+ if (splitPosition < maximumLength * 0.5) {
704
+ splitPosition = maximumLength;
705
+ }
706
+
707
+ chunks.push(
708
+ remainingText.slice(0, splitPosition).trim()
709
+ );
710
+
711
+ remainingText =
712
+ remainingText.slice(splitPosition).trim();
713
+ }
714
+
715
+ if (remainingText) {
716
+ chunks.push(remainingText);
717
+ }
718
+
719
+ return chunks;
720
+ }
721
+
722
+ function decodeHtmlEntities(value) {
723
+ const textArea = document.createElement("textarea");
724
+ textArea.innerHTML = value;
725
+
726
+ return textArea.value;
727
+ }
728
+
729
+ async function translateChunk(text, languageCode) {
730
+ const endpoint =
731
+ "https://api.mymemory.translated.net/get";
732
+
733
+ const parameters = new URLSearchParams({
734
+ q: text,
735
+ langpair: `${languageCode}|en`
736
+ });
737
+
738
+ const response = await fetch(
739
+ `${endpoint}?${parameters.toString()}`
740
+ );
741
+
742
+ if (!response.ok) {
743
+ throw new Error(
744
+ `Translation service returned status ${response.status}.`
745
+ );
746
+ }
747
+
748
+ const data = await response.json();
749
+
750
+ const translation =
751
+ data?.responseData?.translatedText;
752
+
753
+ if (!translation) {
754
+ throw new Error(
755
+ data?.responseDetails ||
756
+ "The translation service returned no text."
757
+ );
758
+ }
759
+
760
+ return decodeHtmlEntities(translation);
761
+ }
762
+
763
+ async function translateText() {
764
+ const originalText = sourceText.value.trim();
765
+
766
+ if (!originalText) {
767
+ updateStatus(
768
+ "Please enter or dictate some text first.",
769
+ "error"
770
+ );
771
+
772
+ sourceText.focus();
773
+ return;
774
+ }
775
+
776
+ if (originalText.length > MAX_TRANSLATION_LENGTH) {
777
+ updateStatus(
778
+ `Please limit the text to ${MAX_TRANSLATION_LENGTH.toLocaleString()} characters.`,
779
+ "error"
780
+ );
781
+
782
+ return;
783
+ }
784
+
785
+ const detected = detectInputLanguage(originalText);
786
+
787
+ const selectedLanguage =
788
+ sourceLanguage.value === "auto"
789
+ ? detected.code
790
+ : sourceLanguage.value;
791
+
792
+ detectedLanguage.textContent =
793
+ `Detected: ${detected.label}`;
794
+
795
+ translateButton.disabled = true;
796
+ translatedText.value = "";
797
+
798
+ updateStatus("Translating your text...");
799
+
800
+ try {
801
+ const chunks = splitTextIntoChunks(
802
+ originalText,
803
+ CHUNK_LENGTH
804
+ );
805
+
806
+ const completedTranslations = [];
807
+
808
+ for (let index = 0; index < chunks.length; index += 1) {
809
+ updateStatus(
810
+ `Translating section ${index + 1} of ${chunks.length}...`
811
+ );
812
+
813
+ const translation = await translateChunk(
814
+ chunks[index],
815
+ selectedLanguage
816
+ );
817
+
818
+ completedTranslations.push(translation);
819
+ }
820
+
821
+ translatedText.value =
822
+ completedTranslations
823
+ .join(" ")
824
+ .replace(/\s+/g, " ")
825
+ .trim();
826
+
827
+ updateStatus(
828
+ "Translation completed successfully.",
829
+ "success"
830
+ );
831
+ } catch (error) {
832
+ console.error(error);
833
+
834
+ updateStatus(
835
+ `Translation failed: ${error.message}`,
836
+ "error"
837
+ );
838
+ } finally {
839
+ translateButton.disabled = false;
840
+ }
841
+ }
842
+
843
+ async function copyTranslation() {
844
+ const value = translatedText.value.trim();
845
+
846
+ if (!value) {
847
+ updateStatus(
848
+ "There is no translation to copy.",
849
+ "error"
850
+ );
851
+
852
+ return;
853
+ }
854
+
855
+ try {
856
+ await navigator.clipboard.writeText(value);
857
+
858
+ updateStatus(
859
+ "Translation copied to the clipboard.",
860
+ "success"
861
+ );
862
+ } catch (error) {
863
+ translatedText.select();
864
+
865
+ const copied = document.execCommand("copy");
866
+
867
+ if (copied) {
868
+ updateStatus(
869
+ "Translation copied to the clipboard.",
870
+ "success"
871
+ );
872
+ } else {
873
+ updateStatus(
874
+ "The browser could not copy the translation.",
875
+ "error"
876
+ );
877
+ }
878
+ }
879
+ }
880
+
881
+ function downloadTranslation() {
882
+ const value = translatedText.value.trim();
883
+
884
+ if (!value) {
885
+ updateStatus(
886
+ "There is no translation to download.",
887
+ "error"
888
+ );
889
+
890
+ return;
891
+ }
892
+
893
+ const blob = new Blob(
894
+ [value],
895
+ {
896
+ type: "text/plain;charset=utf-8"
897
+ }
898
+ );
899
+
900
+ const objectUrl = URL.createObjectURL(blob);
901
+ const downloadLink = document.createElement("a");
902
+
903
+ downloadLink.href = objectUrl;
904
+ downloadLink.download = "english_translation.txt";
905
+
906
+ document.body.appendChild(downloadLink);
907
+ downloadLink.click();
908
+ downloadLink.remove();
909
+
910
+ URL.revokeObjectURL(objectUrl);
911
+
912
+ updateStatus(
913
+ "Translation downloaded.",
914
+ "success"
915
+ );
916
+ }
917
+
918
+ function clearApplication() {
919
+ sourceText.value = "";
920
+ translatedText.value = "";
921
+ textFile.value = "";
922
+
923
+ detectedLanguage.textContent = "Detected: —";
924
+
925
+ updateCharacterCount();
926
+ updateStatus("Cleared.");
927
+
928
+ sourceText.focus();
929
+ }
930
+
931
+ async function importTextFile(event) {
932
+ const file = event.target.files?.[0];
933
+
934
+ if (!file) {
935
+ return;
936
+ }
937
+
938
+ if (file.size > MAX_FILE_SIZE) {
939
+ updateStatus(
940
+ "The TXT file is too large. Maximum size is 1 MB.",
941
+ "error"
942
+ );
943
+
944
+ textFile.value = "";
945
+ return;
946
+ }
947
+
948
+ const fileName = file.name.toLowerCase();
949
+
950
+ if (
951
+ file.type !== "text/plain" &&
952
+ !fileName.endsWith(".txt")
953
+ ) {
954
+ updateStatus(
955
+ "Please upload a valid TXT file.",
956
+ "error"
957
+ );
958
+
959
+ textFile.value = "";
960
+ return;
961
+ }
962
+
963
+ try {
964
+ const content = await file.text();
965
+
966
+ if (!content.trim()) {
967
+ throw new Error("The selected TXT file is empty.");
968
+ }
969
+
970
+ sourceText.value =
971
+ content.slice(0, MAX_TRANSLATION_LENGTH);
972
+
973
+ updateCharacterCount();
974
+
975
+ const detected = detectInputLanguage(
976
+ sourceText.value
977
+ );
978
+
979
+ detectedLanguage.textContent =
980
+ `Detected: ${detected.label}`;
981
+
982
+ updateStatus(
983
+ "TXT file loaded successfully.",
984
+ "success"
985
+ );
986
+ } catch (error) {
987
+ updateStatus(
988
+ `Could not read the file: ${error.message}`,
989
+ "error"
990
+ );
991
+ }
992
+ }
993
+
994
+ sourceText.addEventListener(
995
+ "input",
996
+ updateCharacterCount
997
+ );
998
+
999
+ sourceText.addEventListener(
1000
+ "keydown",
1001
+ (event) => {
1002
+ if (
1003
+ (event.ctrlKey || event.metaKey) &&
1004
+ event.key === "Enter"
1005
+ ) {
1006
+ translateText();
1007
+ }
1008
+ }
1009
+ );
1010
+
1011
+ translateButton.addEventListener(
1012
+ "click",
1013
+ translateText
1014
+ );
1015
+
1016
+ copyButton.addEventListener(
1017
+ "click",
1018
+ copyTranslation
1019
+ );
1020
+
1021
+ downloadButton.addEventListener(
1022
+ "click",
1023
+ downloadTranslation
1024
+ );
1025
+
1026
+ clearButton.addEventListener(
1027
+ "click",
1028
+ clearApplication
1029
+ );
1030
+
1031
+ textFile.addEventListener(
1032
+ "change",
1033
+ importTextFile
1034
+ );
1035
+
1036
+ const SpeechRecognition =
1037
+ window.SpeechRecognition ||
1038
+ window.webkitSpeechRecognition;
1039
+
1040
+ let speechRecognition = null;
1041
+
1042
+ if (SpeechRecognition) {
1043
+ speechRecognition = new SpeechRecognition();
1044
+
1045
+ speechRecognition.continuous = true;
1046
+ speechRecognition.interimResults = true;
1047
+
1048
+ let finalTranscript = "";
1049
+
1050
+ speechRecognition.addEventListener(
1051
+ "start",
1052
+ () => {
1053
+ finalTranscript = "";
1054
+
1055
+ startMicrophoneButton.disabled = true;
1056
+ stopMicrophoneButton.disabled = false;
1057
+
1058
+ updateStatus(
1059
+ "Listening through the microphone..."
1060
+ );
1061
+ }
1062
+ );
1063
+
1064
+ speechRecognition.addEventListener(
1065
+ "result",
1066
+ (event) => {
1067
+ let interimTranscript = "";
1068
+
1069
+ for (
1070
+ let index = event.resultIndex;
1071
+ index < event.results.length;
1072
+ index += 1
1073
+ ) {
1074
+ const transcript =
1075
+ event.results[index][0].transcript;
1076
+
1077
+ if (event.results[index].isFinal) {
1078
+ finalTranscript += `${transcript} `;
1079
+ } else {
1080
+ interimTranscript += transcript;
1081
+ }
1082
+ }
1083
+
1084
+ if (finalTranscript.trim()) {
1085
+ const existingText =
1086
+ sourceText.value.trim();
1087
+
1088
+ sourceText.value =
1089
+ `${existingText}${existingText ? " " : ""}${finalTranscript.trim()}`;
1090
+
1091
+ finalTranscript = "";
1092
+
1093
+ updateCharacterCount();
1094
+ }
1095
+
1096
+ if (interimTranscript) {
1097
+ updateStatus(
1098
+ `Listening: ${interimTranscript}`
1099
+ );
1100
+ }
1101
+ }
1102
+ );
1103
+
1104
+ speechRecognition.addEventListener(
1105
+ "error",
1106
+ (event) => {
1107
+ updateStatus(
1108
+ `Microphone error: ${event.error}`,
1109
+ "error"
1110
+ );
1111
+ }
1112
+ );
1113
+
1114
+ speechRecognition.addEventListener(
1115
+ "end",
1116
+ () => {
1117
+ startMicrophoneButton.disabled = false;
1118
+ stopMicrophoneButton.disabled = true;
1119
+
1120
+ updateStatus("Microphone stopped.");
1121
+ }
1122
+ );
1123
+
1124
+ startMicrophoneButton.addEventListener(
1125
+ "click",
1126
+ () => {
1127
+ try {
1128
+ speechRecognition.lang =
1129
+ speechLanguage.value;
1130
+
1131
+ speechRecognition.start();
1132
+ } catch (error) {
1133
+ updateStatus(
1134
+ `Could not start microphone: ${error.message}`,
1135
+ "error"
1136
+ );
1137
+ }
1138
+ }
1139
+ );
1140
+
1141
+ stopMicrophoneButton.addEventListener(
1142
+ "click",
1143
+ () => {
1144
+ speechRecognition.stop();
1145
+ }
1146
+ );
1147
+ } else {
1148
+ startMicrophoneButton.disabled = true;
1149
+ stopMicrophoneButton.disabled = true;
1150
+
1151
+ updateStatus(
1152
+ "Microphone dictation is not supported by this browser. Text translation is still available."
1153
+ );
1154
+ }
1155
+
1156
+ updateCharacterCount();
1157
+ </script>
1158
+ </body>
1159
+ </html>