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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "l-min-components",
3
- "version": "1.7.1507",
3
+ "version": "1.7.1508",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/assets",
@@ -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
- // name: "Korean",
12
- // flag: koreanFlag,
13
- // slug: "ko",
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 backend
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
- const handleTranslate = async (language, words) => {
46
+ /**
47
+ * Send translation request to backend (fire-and-forget)
48
+ */
49
+ const sendTranslationRequestToBackend = async (language, words) => {
38
50
  try {
39
- await translate({
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.log(err);
66
+ console.warn("Translation request failed:", err);
55
67
  }
56
68
  };
57
69
 
58
- useEffect(() => {
59
- handleTranslate(defaultLang, initialSentences);
60
- }, [defaultLang]);
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
- useEffect(() => {
63
- if (translateData?.data) {
64
- const newTranslations = {};
65
- initialSentences.map((word, index) => {
66
- newTranslations[word] = translateData?.data?.result[index];
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
- ...newTranslations,
175
+ ...fallbackTranslations,
71
176
  }));
177
+
178
+ setIsTranslationsLoading(false);
72
179
  }
73
- }, [translateData?.data]);
180
+ };
74
181
 
75
182
  useEffect(() => {
76
- if (Object.keys(translations)?.length > 0) {
183
+ if (initialSentences && initialSentences.length > 0) {
184
+ handleTranslate(defaultLang, initialSentences);
185
+ } else {
77
186
  setIsTranslationsLoading(false);
78
187
  }
79
- }, [translations]);
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,
@@ -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
- return translations;
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) {