@learnpack/learnpack 5.0.306 → 5.0.307

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.
@@ -472,6 +472,9 @@
472
472
  .mx-2 {
473
473
  margin-inline: calc(var(--spacing) * 2);
474
474
  }
475
+ .mx-4 {
476
+ margin-inline: calc(var(--spacing) * 4);
477
+ }
475
478
  .mx-auto {
476
479
  margin-inline: auto;
477
480
  }
@@ -10,8 +10,8 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-CTQfJYti.js"></script>
14
- <link rel="stylesheet" crossorigin href="/creator/assets/index-DTjdV1LF.css">
13
+ <script type="module" crossorigin src="/creator/assets/index-B37w_ZhT.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/creator/assets/index-D4SYZg0r.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.306",
4
+ "version": "5.0.307",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -31,6 +31,7 @@ import NotificationListener from "./components/NotificationListener"
31
31
  import { slugify } from "./utils/creatorUtils"
32
32
  import TurnstileModal from "./components/TurnstileModal"
33
33
  import { TMessage } from "./components/Message"
34
+ import LanguageDetectionModal from "./components/LanguageDetectionModal"
34
35
 
35
36
  function App() {
36
37
  const navigate = useNavigate()
@@ -72,6 +73,8 @@ function App() {
72
73
 
73
74
  const [notificationId, setNotificationId] = useState<string>("")
74
75
  const [showTurnstileModal, setShowTurnstileModal] = useState(false)
76
+ const [showLanguageDetectionModal, setShowLanguageDetectionModal] = useState(false)
77
+ const [detectedLanguage, setDetectedLanguage] = useState<string>("")
75
78
 
76
79
  useEffect(() => {
77
80
  if (formState.isCompleted) {
@@ -130,27 +133,45 @@ function App() {
130
133
  if (duration && !isNaN(parseInt(duration))) {
131
134
  if (["15", "30", "60"].includes(duration)) {
132
135
  const durationInt = parseInt(duration)
133
- console.log("duration", durationInt)
134
136
  setFormState({
135
137
  duration: durationInt,
136
138
  })
137
139
  } else {
138
- console.log("Invalid duration received in params", duration)
140
+ console.error("Invalid duration received in params", duration)
139
141
  }
140
142
  }
141
-
143
+
142
144
  if (plan) {
143
145
  setPlanToRedirect(plan)
144
146
  } else {
145
147
  console.debug("No plan received in params")
146
148
  }
149
+
150
+ // Language detection logic
151
+ let finalLanguage = language
147
152
 
148
- if (language && language.length === 2) {
153
+ // Change if query param is provided
154
+ if (finalLanguage && finalLanguage.length === 2) {
155
+ console.log("LANGUAGE", finalLanguage)
149
156
  setFormState({
150
- language: language,
157
+ language: finalLanguage,
151
158
  })
159
+ if (i18n.language !== finalLanguage) {
160
+ i18n.changeLanguage(finalLanguage)
161
+ }
152
162
  }
153
163
 
164
+ // If no language is provided, detect the language of the description
165
+ if (!finalLanguage && description) {
166
+ const detectedLang = detectLanguage(description)
167
+ if (detectedLang && detectedLang !== i18n.language) {
168
+ finalLanguage = detectedLang
169
+ setDetectedLanguage(detectedLang)
170
+ setShowLanguageDetectionModal(true)
171
+ }
172
+ }
173
+
174
+
154
175
  if (purpose && purpose.length > 0 && possiblePurposes.includes(purpose)) {
155
176
  setFormState({
156
177
  purpose: purpose,
@@ -233,8 +254,6 @@ function App() {
233
254
  formState.purpose || "learnpack-lesson-writer",
234
255
  tokenType === "rigo" ? false : true
235
256
  )
236
- console.log("RES", res)
237
-
238
257
  setNotificationId(res.notificationId)
239
258
  } catch (error) {
240
259
  console.error(error, "ERROR CREATING TUTORIAL")
@@ -254,6 +273,18 @@ function App() {
254
273
  }
255
274
  }
256
275
 
276
+ const handleLanguageSwitch = () => {
277
+ setFormState({
278
+ language: detectedLanguage,
279
+ })
280
+ i18n.changeLanguage(detectedLanguage)
281
+ setShowLanguageDetectionModal(false)
282
+ }
283
+
284
+ const handleLanguageStay = () => {
285
+ setShowLanguageDetectionModal(false)
286
+ }
287
+
257
288
  const buildSteps = () => {
258
289
  const steps = [
259
290
  {
@@ -263,9 +294,11 @@ function App() {
263
294
  required: true,
264
295
  validator: (value: string) => {
265
296
  const lang = detectLanguage(value)
297
+ console.log("Description validator language detection:", { lang, currentLang: i18n.language, value })
266
298
 
267
- if (lang) {
268
- i18n.changeLanguage(lang)
299
+ if (lang && lang !== "en" && lang !== i18n.language) {
300
+ setDetectedLanguage(lang)
301
+ setShowLanguageDetectionModal(true)
269
302
  }
270
303
 
271
304
  return value.length > 0
@@ -451,6 +484,13 @@ function App() {
451
484
  return (
452
485
  <>
453
486
  <ParamsChecker />
487
+ {showLanguageDetectionModal && (
488
+ <LanguageDetectionModal
489
+ detectedLanguage={detectedLanguage}
490
+ onSwitch={handleLanguageSwitch}
491
+ onStay={handleLanguageStay}
492
+ />
493
+ )}
454
494
  {showTurnstileModal && (
455
495
  <TurnstileModal
456
496
  onClose={() => {
@@ -477,7 +517,6 @@ function App() {
477
517
  {notificationId && (
478
518
  <NotificationListener
479
519
  onNotification={(res) => {
480
- console.log("Async response", res)
481
520
  const lessons = res.parsed.listOfSteps.map((lesson: any) => {
482
521
  return parseLesson(lesson, [])
483
522
  })
@@ -0,0 +1,46 @@
1
+ import { useTranslation } from "react-i18next"
2
+
3
+ interface LanguageDetectionModalProps {
4
+ detectedLanguage: string
5
+ onSwitch: () => void
6
+ onStay: () => void
7
+ }
8
+
9
+ export default function LanguageDetectionModal({
10
+ detectedLanguage,
11
+ onSwitch,
12
+ onStay,
13
+ }: LanguageDetectionModalProps) {
14
+ const { t, i18n } = useTranslation()
15
+
16
+ return (
17
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000">
18
+ <div className="bg-white p-6 rounded-xl shadow-md max-w-md w-full mx-4">
19
+ <h2 className="text-xl font-semibold text-center mb-4">
20
+ {t("languageDetection.title")}
21
+ </h2>
22
+ <p className="text-gray-600 text-center mb-6">
23
+ {t("languageDetection.message", {
24
+ language: t(`languageDetection.languages.${detectedLanguage.toLowerCase()}`)
25
+ })}
26
+ </p>
27
+ <div className="flex flex-col sm:flex-row gap-3">
28
+ <button
29
+ onClick={onSwitch}
30
+ className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md font-semibold hover:bg-blue-700 transition-colors"
31
+ >
32
+ {t("languageDetection.switchTo", {
33
+ language: t(`languageDetection.languages.${detectedLanguage.toLowerCase()}`)
34
+ })}
35
+ </button>
36
+ <button
37
+ onClick={onStay}
38
+ className="flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md font-semibold hover:bg-gray-300 transition-colors"
39
+ >
40
+ {t(`languageDetection.stayIn${i18n.language.toLowerCase()}`)}
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ )
46
+ }
@@ -1,16 +1,24 @@
1
1
  import { useEffect } from "react"
2
2
  import useStore from "../utils/store"
3
+ import { checkParams } from "../utils/lib"
4
+ import { useTranslation } from "react-i18next"
3
5
 
4
6
  export const ParamsChecker = () => {
7
+ const { i18n } = useTranslation()
5
8
  const setMode = useStore((state) => state.setMode)
6
9
 
7
10
  useEffect(() => {
8
- const params = new URLSearchParams(window.location.search)
9
- const mode = params.get("mode")
11
+ const params = checkParams(["mode", "language"])
12
+ const mode = params.mode
10
13
  if (mode && ["teacher", "student"].includes(mode.toLowerCase())) {
11
- console.log("Setting mode to ", mode.toLowerCase())
12
14
  setMode(mode.toLowerCase() as "teacher" | "student")
13
15
  }
16
+ if (
17
+ params.language &&
18
+ ["en", "es"].includes(params.language.toLowerCase())
19
+ ) {
20
+ i18n.changeLanguage(params.language.toLowerCase())
21
+ }
14
22
  }, [])
15
23
 
16
24
  return <></>
@@ -94,6 +94,17 @@
94
94
  "okMessage": "If you're satisfied, type 'OK' in the chat.",
95
95
  "instructionsMessage": "If not, what would you like me to change? You can say things like:\n - Add more exercises\n - Make it more difficult\n - Remove step 1.1 and replace it with a new step that explains the concept of X "
96
96
  },
97
+ "languageDetection": {
98
+ "title": "Language detected",
99
+ "message": "We have detected {{language}} as the main language for this package, would you like to switch to {{language}}?",
100
+ "switchTo": "Yes, switch to {{language}}",
101
+ "stayInen": "Stay in English",
102
+ "stayInes": "Stay in Spanish",
103
+ "languages": {
104
+ "en": "English",
105
+ "es": "Spanish"
106
+ }
107
+ },
97
108
  "login": {
98
109
  "creatingAccount": "Creating account…",
99
110
  "loggedInSuccessfully": "Logged in successfully",
@@ -94,6 +94,17 @@
94
94
  "okMessage": "Si estás satisfecho, escribe 'OK' en el chat.",
95
95
  "instructionsMessage": "Si no, ¿qué te gustaría que cambiara? Puedes decir cosas como:\n - Añadir más ejercicios\n - Hacerlo más difícil\n - Eliminar el paso 1.1 y reemplazarlo con un nuevo paso que explique el concepto de X "
96
96
  },
97
+ "languageDetection": {
98
+ "title": "Idioma detectado",
99
+ "message": "Hemos detectado {{language}} como el idioma principal para este paquete, ¿te gustaría cambiar a {{language}}?",
100
+ "switchTo": "Sí, cambiar a {{language}}",
101
+ "stayInen": "Mantenerse en Inglés",
102
+ "stayInes": "Mantenerse en Español",
103
+ "languages": {
104
+ "en": "Inglés",
105
+ "es": "Español"
106
+ }
107
+ },
97
108
  "login": {
98
109
  "creatingAccount": "Creando cuenta…",
99
110
  "loggedInSuccessfully": "Inicio de sesión exitoso",