l-min-components 1.7.1507 → 1.7.1508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -384,6 +384,210 @@ const AppMainLayout = ({ children }) => {
|
|
|
384
384
|
"Build the future with Learngual!",
|
|
385
385
|
"Choose our custom plans for more calls and better flexibility.",
|
|
386
386
|
"We know it's inconvenient. For better user accessibility, login with a desktop device.",
|
|
387
|
+
// Additional text from codebase analysis
|
|
388
|
+
"Student",
|
|
389
|
+
"Instructor account",
|
|
390
|
+
"Enterprise account",
|
|
391
|
+
"Student account",
|
|
392
|
+
"Personal Account",
|
|
393
|
+
"Dashboard",
|
|
394
|
+
"Courses",
|
|
395
|
+
"My courses",
|
|
396
|
+
"Manage courses",
|
|
397
|
+
"Manage report",
|
|
398
|
+
"Manage teams",
|
|
399
|
+
"Manage student",
|
|
400
|
+
"Messages",
|
|
401
|
+
"Announcements",
|
|
402
|
+
"File manager",
|
|
403
|
+
"Library",
|
|
404
|
+
"Add on",
|
|
405
|
+
"Contracts",
|
|
406
|
+
"Demo",
|
|
407
|
+
"Explore",
|
|
408
|
+
"Connect",
|
|
409
|
+
"Log out",
|
|
410
|
+
"profile",
|
|
411
|
+
"account photo",
|
|
412
|
+
"Active",
|
|
413
|
+
"Expired",
|
|
414
|
+
"Pending",
|
|
415
|
+
"Loading",
|
|
416
|
+
"Error",
|
|
417
|
+
"Success",
|
|
418
|
+
"Warning",
|
|
419
|
+
"Info",
|
|
420
|
+
"Confirm",
|
|
421
|
+
"Yes",
|
|
422
|
+
"No",
|
|
423
|
+
"OK",
|
|
424
|
+
"Cancel",
|
|
425
|
+
"Continue",
|
|
426
|
+
"Back",
|
|
427
|
+
"Next",
|
|
428
|
+
"Submit",
|
|
429
|
+
"Close",
|
|
430
|
+
"Open",
|
|
431
|
+
"View",
|
|
432
|
+
"Edit",
|
|
433
|
+
"Delete",
|
|
434
|
+
"Save",
|
|
435
|
+
"Create",
|
|
436
|
+
"Update",
|
|
437
|
+
"Name",
|
|
438
|
+
"Email",
|
|
439
|
+
"Password",
|
|
440
|
+
"Username",
|
|
441
|
+
"Login",
|
|
442
|
+
"Logout",
|
|
443
|
+
"Register",
|
|
444
|
+
"Signup",
|
|
445
|
+
"Signin",
|
|
446
|
+
"User",
|
|
447
|
+
"Admin",
|
|
448
|
+
"Account",
|
|
449
|
+
"Profile",
|
|
450
|
+
"Preferences",
|
|
451
|
+
"Configuration",
|
|
452
|
+
"Options",
|
|
453
|
+
"Language",
|
|
454
|
+
"Translation",
|
|
455
|
+
"Text",
|
|
456
|
+
"File",
|
|
457
|
+
"Document",
|
|
458
|
+
"Folder",
|
|
459
|
+
"Upload",
|
|
460
|
+
"Download",
|
|
461
|
+
"Share",
|
|
462
|
+
"Copy",
|
|
463
|
+
"Move",
|
|
464
|
+
"Rename",
|
|
465
|
+
"Filter",
|
|
466
|
+
"Sort",
|
|
467
|
+
"Search",
|
|
468
|
+
"Results",
|
|
469
|
+
"Total",
|
|
470
|
+
"Count",
|
|
471
|
+
"Date",
|
|
472
|
+
"Time",
|
|
473
|
+
"Status",
|
|
474
|
+
"Type",
|
|
475
|
+
"Category",
|
|
476
|
+
"Priority",
|
|
477
|
+
"Level",
|
|
478
|
+
"Progress",
|
|
479
|
+
"Score",
|
|
480
|
+
"Grade",
|
|
481
|
+
"Free",
|
|
482
|
+
"Premium",
|
|
483
|
+
"Basic",
|
|
484
|
+
"Advanced",
|
|
485
|
+
"Pro",
|
|
486
|
+
"Enterprise",
|
|
487
|
+
"Personal",
|
|
488
|
+
"Public",
|
|
489
|
+
"Private",
|
|
490
|
+
"Online",
|
|
491
|
+
"Offline",
|
|
492
|
+
"Available",
|
|
493
|
+
"Unavailable",
|
|
494
|
+
"Enabled",
|
|
495
|
+
"Disabled",
|
|
496
|
+
"Required",
|
|
497
|
+
"Optional",
|
|
498
|
+
"Popular",
|
|
499
|
+
"Featured",
|
|
500
|
+
"Latest",
|
|
501
|
+
"Recent",
|
|
502
|
+
"New",
|
|
503
|
+
"Updated",
|
|
504
|
+
"Completed",
|
|
505
|
+
"In Progress",
|
|
506
|
+
"Approved",
|
|
507
|
+
"Rejected",
|
|
508
|
+
"Cancelled",
|
|
509
|
+
"Scheduled",
|
|
510
|
+
"Valid",
|
|
511
|
+
"Invalid",
|
|
512
|
+
"Correct",
|
|
513
|
+
"Incorrect",
|
|
514
|
+
"Start",
|
|
515
|
+
"Stop",
|
|
516
|
+
"Play",
|
|
517
|
+
"Pause",
|
|
518
|
+
"Resume",
|
|
519
|
+
"Record",
|
|
520
|
+
"Volume",
|
|
521
|
+
"Speed",
|
|
522
|
+
"Quality",
|
|
523
|
+
"Size",
|
|
524
|
+
"Format",
|
|
525
|
+
"Original",
|
|
526
|
+
"Source",
|
|
527
|
+
"Target",
|
|
528
|
+
"Input",
|
|
529
|
+
"Output",
|
|
530
|
+
"Audio",
|
|
531
|
+
"Video",
|
|
532
|
+
"Image",
|
|
533
|
+
"Button",
|
|
534
|
+
"Menu",
|
|
535
|
+
"Page",
|
|
536
|
+
"Section",
|
|
537
|
+
"Header",
|
|
538
|
+
"Footer",
|
|
539
|
+
"Content",
|
|
540
|
+
"Navigation",
|
|
541
|
+
"List",
|
|
542
|
+
"Table",
|
|
543
|
+
"Row",
|
|
544
|
+
"Column",
|
|
545
|
+
"Field",
|
|
546
|
+
"Value",
|
|
547
|
+
"Option",
|
|
548
|
+
"Selection",
|
|
549
|
+
"Modal",
|
|
550
|
+
"Dialog",
|
|
551
|
+
"Alert",
|
|
552
|
+
"Notification",
|
|
553
|
+
"Message",
|
|
554
|
+
"Card",
|
|
555
|
+
"Item",
|
|
556
|
+
"Entry",
|
|
557
|
+
"Record",
|
|
558
|
+
"Data",
|
|
559
|
+
"Information",
|
|
560
|
+
"Details",
|
|
561
|
+
"Summary",
|
|
562
|
+
"Overview",
|
|
563
|
+
"Preview",
|
|
564
|
+
"Icon",
|
|
565
|
+
"Avatar",
|
|
566
|
+
"Theme",
|
|
567
|
+
"Style",
|
|
568
|
+
"Color",
|
|
569
|
+
"Font",
|
|
570
|
+
"Mobile",
|
|
571
|
+
"Desktop",
|
|
572
|
+
"Tablet",
|
|
573
|
+
"Device",
|
|
574
|
+
"Browser",
|
|
575
|
+
"Application",
|
|
576
|
+
"System",
|
|
577
|
+
"Network",
|
|
578
|
+
"Internet",
|
|
579
|
+
"Connection",
|
|
580
|
+
"Server",
|
|
581
|
+
"Database",
|
|
582
|
+
"Storage",
|
|
583
|
+
"Session",
|
|
584
|
+
"Token",
|
|
585
|
+
"Key",
|
|
586
|
+
"Permission",
|
|
587
|
+
"Role",
|
|
588
|
+
"Member",
|
|
589
|
+
"Customer",
|
|
590
|
+
"Visitor",
|
|
387
591
|
];
|
|
388
592
|
|
|
389
593
|
const {
|
|
@@ -7,11 +7,11 @@ export const languagesData = [
|
|
|
7
7
|
flag: usFlag,
|
|
8
8
|
slug: "en",
|
|
9
9
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
{
|
|
11
|
+
name: "Korean",
|
|
12
|
+
flag: koreanFlag,
|
|
13
|
+
slug: "ko",
|
|
14
|
+
},
|
|
15
15
|
];
|
|
16
16
|
|
|
17
17
|
export const selectedLanguageData = {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import React, { useCallback, useContext, useEffect, useState } from "react";
|
|
2
2
|
import useAxios from "axios-hooks";
|
|
3
3
|
|
|
4
|
+
// Global cache for master translations
|
|
5
|
+
if (!window.translationCache) {
|
|
6
|
+
window.translationCache = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const CACHE_DURATION = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
10
|
+
const S3_BASE_URL =
|
|
11
|
+
"https://learngual-bucket.sfo3.digitaloceanspaces.com/media/media/";
|
|
12
|
+
|
|
4
13
|
const useTranslation = (initialSentences = []) => {
|
|
5
14
|
const value = localStorage?.getItem("defaultLang");
|
|
6
15
|
|
|
@@ -14,7 +23,7 @@ const useTranslation = (initialSentences = []) => {
|
|
|
14
23
|
}, [value]);
|
|
15
24
|
|
|
16
25
|
const [defaultLang, setDefaultLang] = useState(value ?? "en");
|
|
17
|
-
const [translations, setTranslations] = useState({}); // returned translation from
|
|
26
|
+
const [translations, setTranslations] = useState({}); // returned translation from S3
|
|
18
27
|
const [isTranslationsLoading, setIsTranslationsLoading] = useState(true);
|
|
19
28
|
|
|
20
29
|
const findText = useCallback(
|
|
@@ -24,7 +33,7 @@ const useTranslation = (initialSentences = []) => {
|
|
|
24
33
|
[translations]
|
|
25
34
|
);
|
|
26
35
|
|
|
27
|
-
// api request for sending data to backend
|
|
36
|
+
// api request for sending data to backend (fire-and-forget)
|
|
28
37
|
const [{ ...translateData }, translate] = useAxios(
|
|
29
38
|
{
|
|
30
39
|
method: "POST",
|
|
@@ -34,14 +43,17 @@ const useTranslation = (initialSentences = []) => {
|
|
|
34
43
|
}
|
|
35
44
|
);
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Send translation request to backend (fire-and-forget)
|
|
48
|
+
*/
|
|
49
|
+
const sendTranslationRequestToBackend = async (language, words) => {
|
|
38
50
|
try {
|
|
39
|
-
await
|
|
51
|
+
// Fire-and-forget - don't await or use response
|
|
52
|
+
translate({
|
|
40
53
|
url: `/iam/v1/utils/translate/`,
|
|
41
54
|
data: {
|
|
42
55
|
language,
|
|
43
56
|
sentences: words,
|
|
44
|
-
// kwargs,
|
|
45
57
|
},
|
|
46
58
|
params: {
|
|
47
59
|
_account: "",
|
|
@@ -51,38 +63,135 @@ const useTranslation = (initialSentences = []) => {
|
|
|
51
63
|
},
|
|
52
64
|
});
|
|
53
65
|
} catch (err) {
|
|
54
|
-
console.
|
|
66
|
+
console.warn("Translation request failed:", err);
|
|
55
67
|
}
|
|
56
68
|
};
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Fetch master translations from S3 with caching
|
|
72
|
+
*/
|
|
73
|
+
const getMasterTranslationsWithCache = async () => {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const cached = window.translationCache.master;
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
// Check if cache is valid and not expired
|
|
78
|
+
if (cached && cached.data && cached.expires > now) {
|
|
79
|
+
return cached.data;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Fetch from S3 - master JSON contains all languages
|
|
84
|
+
const response = await fetch(
|
|
85
|
+
`${S3_BASE_URL}qFpINMa05DrgUgzO0PEboReejximpq2r3VL2AmFZAwIq6fTN3A.json`
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`Failed to fetch translations: ${response.status}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const masterTranslations = await response.json();
|
|
93
|
+
|
|
94
|
+
console.log(masterTranslations, "MASTER");
|
|
95
|
+
|
|
96
|
+
// Cache in memory (single cache for all languages since JSON contains all)
|
|
97
|
+
window.translationCache.master = {
|
|
98
|
+
data: masterTranslations,
|
|
99
|
+
timestamp: now,
|
|
100
|
+
expires: now + CACHE_DURATION,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return masterTranslations;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("Failed to fetch master translations:", error);
|
|
106
|
+
|
|
107
|
+
// Fallback to cached data if available (even if expired)
|
|
108
|
+
if (cached && cached.data) {
|
|
109
|
+
return cached.data;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extract relevant translations for specific texts
|
|
118
|
+
*/
|
|
119
|
+
const extractRelevantTranslations = (masterTranslations, words, language) => {
|
|
120
|
+
const relevantTranslations = {};
|
|
121
|
+
const languageCode = language.toUpperCase(); // ko -> KO, fr -> FR
|
|
122
|
+
|
|
123
|
+
words.forEach((word) => {
|
|
124
|
+
if (masterTranslations[word] && masterTranslations[word][languageCode]) {
|
|
125
|
+
const translation = masterTranslations[word][languageCode];
|
|
126
|
+
// Only use translation if it's not empty
|
|
127
|
+
relevantTranslations[word] = translation.trim() || word;
|
|
128
|
+
} else {
|
|
129
|
+
// Return original text if translation not found
|
|
130
|
+
relevantTranslations[word] = word;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return relevantTranslations;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleTranslate = async (language, words) => {
|
|
138
|
+
if (!words || words.length === 0) {
|
|
139
|
+
setIsTranslationsLoading(false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Step 1: Fire-and-forget translation request to backend
|
|
145
|
+
sendTranslationRequestToBackend(language, words);
|
|
146
|
+
|
|
147
|
+
// Step 2: Get master translations (cached or fresh from S3)
|
|
148
|
+
const masterTranslations = await getMasterTranslationsWithCache();
|
|
149
|
+
|
|
150
|
+
// Step 3: Extract only the translations we need
|
|
151
|
+
const relevantTranslations = extractRelevantTranslations(
|
|
152
|
+
masterTranslations,
|
|
153
|
+
words,
|
|
154
|
+
language
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Step 4: Update state
|
|
158
|
+
setTranslations((prevTranslations) => ({
|
|
159
|
+
...prevTranslations,
|
|
160
|
+
...relevantTranslations,
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
setIsTranslationsLoading(false);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error("Failed to load translations:", error);
|
|
166
|
+
|
|
167
|
+
// Fallback: return original texts
|
|
168
|
+
const fallbackTranslations = {};
|
|
169
|
+
words.forEach((word) => {
|
|
170
|
+
fallbackTranslations[word] = word;
|
|
67
171
|
});
|
|
172
|
+
|
|
68
173
|
setTranslations((prevTranslations) => ({
|
|
69
174
|
...prevTranslations,
|
|
70
|
-
...
|
|
175
|
+
...fallbackTranslations,
|
|
71
176
|
}));
|
|
177
|
+
|
|
178
|
+
setIsTranslationsLoading(false);
|
|
72
179
|
}
|
|
73
|
-
}
|
|
180
|
+
};
|
|
74
181
|
|
|
75
182
|
useEffect(() => {
|
|
76
|
-
if (
|
|
183
|
+
if (initialSentences && initialSentences.length > 0) {
|
|
184
|
+
handleTranslate(defaultLang, initialSentences);
|
|
185
|
+
} else {
|
|
77
186
|
setIsTranslationsLoading(false);
|
|
78
187
|
}
|
|
79
|
-
}, [
|
|
80
|
-
console.log("translateData", translateData);
|
|
188
|
+
}, [defaultLang]);
|
|
81
189
|
|
|
190
|
+
// Remove the old useEffect that processed translateData.data since we don't use API response anymore
|
|
82
191
|
return {
|
|
83
192
|
defaultLang,
|
|
84
193
|
setDefaultLang,
|
|
85
|
-
translateData,
|
|
194
|
+
translateData, // Keep for backward compatibility (though not used anymore)
|
|
86
195
|
handleTranslate,
|
|
87
196
|
translations,
|
|
88
197
|
findText,
|
package/src/utils/translation.js
CHANGED
|
@@ -1,6 +1,142 @@
|
|
|
1
1
|
import { db } from "./db";
|
|
2
2
|
import axios from "./axiosConfig";
|
|
3
3
|
|
|
4
|
+
// Global cache for master translations
|
|
5
|
+
if (!window.translationCache) {
|
|
6
|
+
window.translationCache = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const CACHE_DURATION = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
10
|
+
const S3_BASE_URL =
|
|
11
|
+
"https://learngual-bucket.sfo3.digitaloceanspaces.com/media/media/";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Send translation request to backend (fire-and-forget)
|
|
15
|
+
*/
|
|
16
|
+
async function sendTranslationRequest(texts, kwargs, language) {
|
|
17
|
+
try {
|
|
18
|
+
axios({
|
|
19
|
+
url: "/iam/v1/utils/translate/",
|
|
20
|
+
method: "POST",
|
|
21
|
+
data: {
|
|
22
|
+
language,
|
|
23
|
+
sentences: texts,
|
|
24
|
+
kwargs,
|
|
25
|
+
},
|
|
26
|
+
params: {
|
|
27
|
+
_account: "",
|
|
28
|
+
},
|
|
29
|
+
authRequired: false,
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Fire-and-forget, ignore errors
|
|
33
|
+
console.warn("Translation request failed:", error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetch master translations from S3 with caching
|
|
39
|
+
* Since the JSON contains all languages, we cache it once regardless of language
|
|
40
|
+
*/
|
|
41
|
+
async function getMasterTranslationsWithCache() {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const cached = window.translationCache.master;
|
|
44
|
+
|
|
45
|
+
// Check if cache is valid and not expired
|
|
46
|
+
if (cached && cached.data && cached.expires > now) {
|
|
47
|
+
return cached.data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Fetch from S3 - master JSON contains all languages
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
`${S3_BASE_URL}qFpINMa05DrgUgzO0PEboReejximpq2r3VL2AmFZAwIq6fTN3A.json`
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error(`Failed to fetch translations: ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const masterTranslations = await response.json();
|
|
61
|
+
|
|
62
|
+
// Cache in memory (single cache for all languages since JSON contains all)
|
|
63
|
+
window.translationCache.master = {
|
|
64
|
+
data: masterTranslations,
|
|
65
|
+
timestamp: now,
|
|
66
|
+
expires: now + CACHE_DURATION,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
console.log(masterTranslations, "master");
|
|
70
|
+
|
|
71
|
+
return masterTranslations;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("Failed to fetch master translations:", error);
|
|
74
|
+
|
|
75
|
+
// Fallback to cached data if available (even if expired)
|
|
76
|
+
if (cached && cached.data) {
|
|
77
|
+
return cached.data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extract relevant translations for specific texts
|
|
86
|
+
* Supports multiple languages: ko -> KO, fr -> FR, etc.
|
|
87
|
+
*/
|
|
88
|
+
function extractRelevantTranslations(masterTranslations, texts, language) {
|
|
89
|
+
const relevantTranslations = {};
|
|
90
|
+
const languageCode = language.toUpperCase(); // ko -> KO, fr -> FR
|
|
91
|
+
|
|
92
|
+
texts.forEach((text) => {
|
|
93
|
+
if (masterTranslations[text] && masterTranslations[text][languageCode]) {
|
|
94
|
+
const translation = masterTranslations[text][languageCode];
|
|
95
|
+
// Only use translation if it's not empty
|
|
96
|
+
relevantTranslations[text] = translation.trim() || text;
|
|
97
|
+
} else {
|
|
98
|
+
// Return original text if translation not found
|
|
99
|
+
relevantTranslations[text] = text;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return relevantTranslations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Cache screen-specific translations in IndexedDB
|
|
108
|
+
*/
|
|
109
|
+
async function cacheScreenTranslations(translations, screenId, language) {
|
|
110
|
+
try {
|
|
111
|
+
const translationEntries = Object.keys(translations).map((key) => ({
|
|
112
|
+
key,
|
|
113
|
+
value: translations[key],
|
|
114
|
+
screenId,
|
|
115
|
+
updatedAt: new Date().toDateString(),
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
await db[language].bulkPut(translationEntries);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error("Failed to cache translations:", error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get cached translations from IndexedDB as fallback
|
|
126
|
+
*/
|
|
127
|
+
async function getCachedTranslations(screenId, language) {
|
|
128
|
+
try {
|
|
129
|
+
const cached = await db[language].where({ screenId }).toArray();
|
|
130
|
+
return cached.reduce((acc, item) => {
|
|
131
|
+
acc[item.key] = item.value;
|
|
132
|
+
return acc;
|
|
133
|
+
}, {});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("Failed to get cached translations:", error);
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
4
140
|
async function loadTranslations(language = "ko") {
|
|
5
141
|
await new Promise((resolve) => {
|
|
6
142
|
//check if loading else resolve
|
|
@@ -25,38 +161,42 @@ async function loadTranslations(language = "ko") {
|
|
|
25
161
|
*/
|
|
26
162
|
const kwargList = await db.kwarg.where({ screenId }).toArray();
|
|
27
163
|
const kwargs = kwargList.reduce((x, t) => ({ ...x, [t.key]: t.value }), {});
|
|
28
|
-
/**
|
|
29
|
-
* @type {{ data: {has_translated: boolean; result: string[]} }}
|
|
30
|
-
*/
|
|
31
|
-
const res = await axios({
|
|
32
|
-
url: "/iam/v1/utils/translate/",
|
|
33
|
-
method: "POST",
|
|
34
|
-
data: {
|
|
35
|
-
language,
|
|
36
|
-
sentences: texts,
|
|
37
|
-
kwargs,
|
|
38
|
-
},
|
|
39
|
-
params: {
|
|
40
|
-
_account: "",
|
|
41
|
-
},
|
|
42
|
-
authRequired: false,
|
|
43
|
-
});
|
|
44
|
-
// build an object using texts as key and results from request as value
|
|
45
|
-
if (!res.data) return {}; // somehow data was not found
|
|
46
|
-
const translations = texts.reduce(
|
|
47
|
-
(x, y, z) => ({ ...x, [y]: res.data.result[z] }),
|
|
48
|
-
{}
|
|
49
|
-
);
|
|
50
|
-
db[language].bulkPut(
|
|
51
|
-
Object.keys(translations).map((x, i) => ({
|
|
52
|
-
key: texts[i],
|
|
53
|
-
value: translations[x],
|
|
54
|
-
screenId,
|
|
55
|
-
updatedAt: new Date().toDateString(),
|
|
56
|
-
}))
|
|
57
|
-
);
|
|
58
164
|
|
|
59
|
-
|
|
165
|
+
// Step 1: Fire-and-forget translation request to backend
|
|
166
|
+
sendTranslationRequest(texts, kwargs, language);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Step 2: Get master translations (cached or fresh from S3)
|
|
170
|
+
const masterTranslations = await getMasterTranslationsWithCache();
|
|
171
|
+
|
|
172
|
+
// Step 3: Extract only the translations we need
|
|
173
|
+
const relevantTranslations = extractRelevantTranslations(
|
|
174
|
+
masterTranslations,
|
|
175
|
+
texts,
|
|
176
|
+
language
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Step 4: Cache the extracted translations in IndexedDB
|
|
180
|
+
await cacheScreenTranslations(relevantTranslations, screenId, language);
|
|
181
|
+
|
|
182
|
+
return relevantTranslations;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Failed to load translations:", error);
|
|
185
|
+
|
|
186
|
+
// Step 5: Fallback to cached translations from IndexedDB
|
|
187
|
+
const cachedTranslations = await getCachedTranslations(screenId, language);
|
|
188
|
+
|
|
189
|
+
// If we have cached translations, return them
|
|
190
|
+
if (Object.keys(cachedTranslations).length > 0) {
|
|
191
|
+
return cachedTranslations;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Last resort: return original texts
|
|
195
|
+
return texts.reduce((acc, text) => {
|
|
196
|
+
acc[text] = text;
|
|
197
|
+
return acc;
|
|
198
|
+
}, {});
|
|
199
|
+
}
|
|
60
200
|
}
|
|
61
201
|
|
|
62
202
|
export function extractBracedText(text) {
|