Claude commited on
Commit
e63672e
·
unverified ·
1 Parent(s): 68432fb

Add EN/FR language toggle, English by default

Browse files

Interface is now in English by default with a FR button in the
top-right corner to switch to French. All UI labels, placeholders,
error messages, and analysis scores are translated. The toggle
switches instantly without page reload.

https://claude.ai/code/session_015z3yZxNNfXF63JuQDuPbEG

Files changed (3) hide show
  1. static/app.js +116 -17
  2. static/index.html +29 -26
  3. static/style.css +19 -0
static/app.js CHANGED
@@ -6,6 +6,105 @@
6
  (function () {
7
  "use strict";
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  /* ===== State (in-memory only, lost on tab close) ===== */
10
  var state = {
11
  mode: "TUTOR",
@@ -275,7 +374,7 @@
275
  .then(function (data) {
276
  setTyping(false);
277
  if (!data.reply) {
278
- addMessage("assistant", "Reponse vide du serveur. Verifiez la configuration API.");
279
  btnSend.disabled = false;
280
  return;
281
  }
@@ -291,7 +390,7 @@
291
  .catch(function (err) {
292
  setTyping(false);
293
  console.error("sendMessage error:", err);
294
- addMessage("assistant", "Erreur: " + (err.message || "Connexion impossible. Veuillez reessayer."));
295
  btnSend.disabled = false;
296
  });
297
  }
@@ -318,20 +417,17 @@
318
  .catch(function () {
319
  setTyping(false);
320
  btnEnd.disabled = false;
321
- alert("Erreur lors de l'analyse. Veuillez reessayer.");
322
  });
323
  }
324
 
325
  /* ===== Analysis rendering ===== */
326
  function renderAnalysis(data) {
327
- var scores = [
328
- { key: "reasoningScore", label: "Raisonnement" },
329
- { key: "clarityScore", label: "Clarte" },
330
- { key: "skepticismScore", label: "Scepticisme" },
331
- { key: "processScore", label: "Processus" },
332
- { key: "reflectionScore", label: "Reflexion" },
333
- { key: "integrityScore", label: "Integrite" }
334
- ];
335
 
336
  scoresGrid.innerHTML = "";
337
  scores.forEach(function (s) {
@@ -345,7 +441,7 @@
345
  scoresGrid.appendChild(card);
346
  });
347
 
348
- summaryEl.textContent = data.summary || "Aucun bilan disponible.";
349
 
350
  strengthsEl.innerHTML = "";
351
  (data.keyStrengths || []).forEach(function (s) {
@@ -452,7 +548,7 @@
452
  state.phaseTurns = 0;
453
  state.history = [];
454
  state.timestamps = [];
455
- modeBadge.textContent = state.mode === "TUTOR" ? "Tuteur" : "Critique";
456
  topicBadge.textContent = topic;
457
 
458
  // Show doc count badge
@@ -479,7 +575,7 @@
479
  method: "POST",
480
  headers: { "Content-Type": "application/json" },
481
  body: JSON.stringify({
482
- message: "Bonjour, je souhaite explorer le sujet : " + state.topic,
483
  mode: state.mode,
484
  topic: state.topic,
485
  phase: state.phase,
@@ -498,7 +594,7 @@
498
  .then(function (data) {
499
  setTyping(false);
500
  if (!data.reply) {
501
- addMessage("assistant", "Reponse vide du serveur. Verifiez la configuration API.");
502
  btnSend.disabled = false;
503
  return;
504
  }
@@ -514,7 +610,7 @@
514
  .catch(function (err) {
515
  setTyping(false);
516
  console.error("startSession error:", err);
517
- addMessage("assistant", "Erreur: " + (err.message || "Connexion impossible. Veuillez reessayer."));
518
  btnSend.disabled = false;
519
  });
520
  }
@@ -535,7 +631,7 @@
535
  // End session -> analysis
536
  btnEnd.addEventListener("click", function () {
537
  if (state.history.length === 0) {
538
- alert("Aucun echange a analyser.");
539
  return;
540
  }
541
  requestAnalysis();
@@ -553,4 +649,7 @@
553
  // Load existing docs on startup
554
  loadDocumentList();
555
 
 
 
 
556
  })();
 
6
  (function () {
7
  "use strict";
8
 
9
+ /* ===== i18n ===== */
10
+ var LANG = {
11
+ en: {
12
+ subtitle: "Socratic Learning Companion",
13
+ topicLabel: "Exploration topic",
14
+ topicPlaceholder: "E.g.: Artificial intelligence in professional training",
15
+ docsLabel: "Reference documents (optional)",
16
+ uploadText: "Drag your files here or click to select",
17
+ uploadHint: "PDF, PPTX, TXT or ZIP \u2014 multiple files allowed",
18
+ modeLabel: "Mode",
19
+ modeTutor: "Tutor",
20
+ modeTutorDesc: "Supportive guidance, open-ended questions",
21
+ modeCritic: "Critic",
22
+ modeCriticDesc: "Devil's advocate, tests logical weaknesses",
23
+ btnStart: "Start session",
24
+ btnEnd: "End session",
25
+ btnRestart: "Restart",
26
+ chatPlaceholder: "Type your thoughts here...",
27
+ btnSend: "Send",
28
+ reportTitle: "Session Report",
29
+ summaryTitle: "Summary",
30
+ strengthsTitle: "Key strengths",
31
+ weaknessesTitle: "Areas for improvement",
32
+ rhythmTitle: "Pace",
33
+ rhythmText: "Responses under 8 seconds:",
34
+ btnExport: "Export JSON",
35
+ btnNewSession: "New session",
36
+ modeBadgeTutor: "Tutor",
37
+ modeBadgeCritic: "Critic",
38
+ errorEmpty: "No response from server. Check API configuration.",
39
+ errorConnection: "Connection error. Please try again.",
40
+ errorNoExchange: "No exchanges to analyze.",
41
+ errorAnalysis: "Analysis error. Please try again.",
42
+ startMessage: "Hello, I would like to explore the topic: ",
43
+ scoreLabels: ["Reasoning", "Clarity", "Skepticism", "Process", "Reflection", "Integrity"],
44
+ noSummary: "No summary available.",
45
+ langBtn: "FR"
46
+ },
47
+ fr: {
48
+ subtitle: "Compagnon socratique d'apprentissage",
49
+ topicLabel: "Sujet d'exploration",
50
+ topicPlaceholder: "Ex : L'intelligence artificielle en formation professionnelle",
51
+ docsLabel: "Documents de reference (optionnel)",
52
+ uploadText: "Glisse tes fichiers ici ou clique pour selectionner",
53
+ uploadHint: "PDF, PPTX, TXT ou ZIP \u2014 plusieurs fichiers possibles",
54
+ modeLabel: "Mode",
55
+ modeTutor: "Tuteur",
56
+ modeTutorDesc: "Accompagnement bienveillant, questions ouvertes",
57
+ modeCritic: "Critique",
58
+ modeCriticDesc: "Avocat du diable, teste les failles logiques",
59
+ btnStart: "Commencer la session",
60
+ btnEnd: "Terminer la session",
61
+ btnRestart: "Recommencer",
62
+ chatPlaceholder: "Tape ta reflexion ici...",
63
+ btnSend: "Envoyer",
64
+ reportTitle: "Rapport de session",
65
+ summaryTitle: "Bilan",
66
+ strengthsTitle: "Points forts",
67
+ weaknessesTitle: "Axes d'amelioration",
68
+ rhythmTitle: "Rythme",
69
+ rhythmText: "Reponses en moins de 8 secondes :",
70
+ btnExport: "Exporter JSON",
71
+ btnNewSession: "Nouvelle session",
72
+ modeBadgeTutor: "Tuteur",
73
+ modeBadgeCritic: "Critique",
74
+ errorEmpty: "Reponse vide du serveur. Verifiez la configuration API.",
75
+ errorConnection: "Erreur de connexion. Veuillez reessayer.",
76
+ errorNoExchange: "Aucun echange a analyser.",
77
+ errorAnalysis: "Erreur lors de l'analyse. Veuillez reessayer.",
78
+ startMessage: "Bonjour, je souhaite explorer le sujet : ",
79
+ scoreLabels: ["Raisonnement", "Clarte", "Scepticisme", "Processus", "Reflexion", "Integrite"],
80
+ noSummary: "Aucun bilan disponible.",
81
+ langBtn: "EN"
82
+ }
83
+ };
84
+
85
+ var currentLang = "en";
86
+
87
+ function t(key) {
88
+ return LANG[currentLang][key] || key;
89
+ }
90
+
91
+ function applyLanguage() {
92
+ document.querySelectorAll("[data-i18n]").forEach(function (el) {
93
+ el.textContent = t(el.dataset.i18n);
94
+ });
95
+ document.querySelectorAll("[data-i18n-placeholder]").forEach(function (el) {
96
+ el.placeholder = t(el.dataset.i18nPlaceholder);
97
+ });
98
+ document.getElementById("btn-lang").textContent = t("langBtn");
99
+ document.documentElement.lang = currentLang === "fr" ? "fr" : "en";
100
+ document.title = currentLang === "en" ? "AIM - Learning Companion" : "AIM - Compagnon d'apprentissage";
101
+ }
102
+
103
+ document.getElementById("btn-lang").addEventListener("click", function () {
104
+ currentLang = currentLang === "en" ? "fr" : "en";
105
+ applyLanguage();
106
+ });
107
+
108
  /* ===== State (in-memory only, lost on tab close) ===== */
109
  var state = {
110
  mode: "TUTOR",
 
374
  .then(function (data) {
375
  setTyping(false);
376
  if (!data.reply) {
377
+ addMessage("assistant", t("errorEmpty"));
378
  btnSend.disabled = false;
379
  return;
380
  }
 
390
  .catch(function (err) {
391
  setTyping(false);
392
  console.error("sendMessage error:", err);
393
+ addMessage("assistant", err.message || t("errorConnection"));
394
  btnSend.disabled = false;
395
  });
396
  }
 
417
  .catch(function () {
418
  setTyping(false);
419
  btnEnd.disabled = false;
420
+ alert(t("errorAnalysis"));
421
  });
422
  }
423
 
424
  /* ===== Analysis rendering ===== */
425
  function renderAnalysis(data) {
426
+ var scoreKeys = ["reasoningScore", "clarityScore", "skepticismScore", "processScore", "reflectionScore", "integrityScore"];
427
+ var labels = t("scoreLabels");
428
+ var scores = scoreKeys.map(function (key, i) {
429
+ return { key: key, label: labels[i] };
430
+ });
 
 
 
431
 
432
  scoresGrid.innerHTML = "";
433
  scores.forEach(function (s) {
 
441
  scoresGrid.appendChild(card);
442
  });
443
 
444
+ summaryEl.textContent = data.summary || t("noSummary");
445
 
446
  strengthsEl.innerHTML = "";
447
  (data.keyStrengths || []).forEach(function (s) {
 
548
  state.phaseTurns = 0;
549
  state.history = [];
550
  state.timestamps = [];
551
+ modeBadge.textContent = state.mode === "TUTOR" ? t("modeBadgeTutor") : t("modeBadgeCritic");
552
  topicBadge.textContent = topic;
553
 
554
  // Show doc count badge
 
575
  method: "POST",
576
  headers: { "Content-Type": "application/json" },
577
  body: JSON.stringify({
578
+ message: t("startMessage") + state.topic,
579
  mode: state.mode,
580
  topic: state.topic,
581
  phase: state.phase,
 
594
  .then(function (data) {
595
  setTyping(false);
596
  if (!data.reply) {
597
+ addMessage("assistant", t("errorEmpty"));
598
  btnSend.disabled = false;
599
  return;
600
  }
 
610
  .catch(function (err) {
611
  setTyping(false);
612
  console.error("startSession error:", err);
613
+ addMessage("assistant", err.message || t("errorConnection"));
614
  btnSend.disabled = false;
615
  });
616
  }
 
631
  // End session -> analysis
632
  btnEnd.addEventListener("click", function () {
633
  if (state.history.length === 0) {
634
+ alert(t("errorNoExchange"));
635
  return;
636
  }
637
  requestAnalysis();
 
649
  // Load existing docs on startup
650
  loadDocumentList();
651
 
652
+ // Apply default language
653
+ applyLanguage();
654
+
655
  })();
static/index.html CHANGED
@@ -1,29 +1,32 @@
1
  <!DOCTYPE html>
2
- <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AIM - Compagnon d'apprentissage</title>
7
  <link rel="stylesheet" href="/static/style.css">
8
  </head>
9
  <body>
10
 
 
 
 
11
  <!-- ===== Setup Screen ===== -->
12
  <div id="setup-screen" class="screen active">
13
  <h1>AIM</h1>
14
- <p class="subtitle">Compagnon socratique d'apprentissage</p>
15
 
16
  <div class="form-group">
17
- <label for="topic-input">Sujet d'exploration</label>
18
- <input type="text" id="topic-input" placeholder="Ex : L'intelligence artificielle en formation professionnelle">
19
  </div>
20
 
21
  <div class="form-group">
22
- <label>Documents de reference (optionnel)</label>
23
  <div class="upload-zone" id="upload-zone">
24
  <div class="upload-icon">+</div>
25
- <div class="upload-text">Glisse tes fichiers ici ou clique pour selectionner</div>
26
- <div class="upload-hint">PDF, PPTX, TXT ou ZIP — plusieurs fichiers possibles</div>
27
  <input type="file" id="file-input" multiple accept=".pdf,.pptx,.ppt,.txt,.zip" hidden>
28
  </div>
29
  <div class="upload-list" id="upload-list"></div>
@@ -31,20 +34,20 @@
31
  </div>
32
 
33
  <div class="form-group">
34
- <label>Mode</label>
35
  <div class="mode-selector">
36
  <div class="mode-btn selected" data-mode="TUTOR">
37
- <div class="mode-title">Tuteur</div>
38
- <div class="mode-desc">Accompagnement bienveillant, questions ouvertes</div>
39
  </div>
40
  <div class="mode-btn" data-mode="CRITIC">
41
- <div class="mode-title">Critique</div>
42
- <div class="mode-desc">Avocat du diable, teste les failles logiques</div>
43
  </div>
44
  </div>
45
  </div>
46
 
47
- <button id="btn-start" class="btn-primary" disabled>Commencer la session</button>
48
  </div>
49
 
50
  <!-- ===== Chat Screen ===== -->
@@ -56,8 +59,8 @@
56
  <span id="docs-badge" class="badge badge-docs" style="display:none"></span>
57
  </div>
58
  <div class="chat-header-right">
59
- <button id="btn-end-session" class="btn-end">Terminer la session</button>
60
- <button id="btn-reset" class="btn-reset">Recommencer</button>
61
  </div>
62
  </div>
63
 
@@ -71,40 +74,40 @@
71
  </div>
72
 
73
  <div class="input-bar">
74
- <input type="text" id="chat-input" placeholder="Tape ta reflexion ici...">
75
- <button id="btn-send" class="btn-send">Envoyer</button>
76
  </div>
77
  </div>
78
 
79
  <!-- ===== Analysis Screen ===== -->
80
  <div id="analysis-screen" class="screen">
81
- <h2>Rapport de session</h2>
82
 
83
  <div class="scores-grid" id="scores-grid"></div>
84
 
85
  <div class="analysis-section">
86
- <h3>Bilan</h3>
87
  <p id="analysis-summary"></p>
88
  </div>
89
 
90
  <div class="analysis-section">
91
- <h3>Points forts</h3>
92
  <ul id="analysis-strengths"></ul>
93
  </div>
94
 
95
  <div class="analysis-section">
96
- <h3>Axes d'amelioration</h3>
97
  <ul id="analysis-weaknesses"></ul>
98
  </div>
99
 
100
  <div class="analysis-section">
101
- <h3>Rythme</h3>
102
- <p>Reponses en moins de 8 secondes : <span id="rhythm-count" class="rhythm-badge">0</span></p>
103
  </div>
104
 
105
  <div class="analysis-actions">
106
- <button id="btn-export" class="btn-secondary">Exporter JSON</button>
107
- <button id="btn-new-session" class="btn-primary">Nouvelle session</button>
108
  </div>
109
  </div>
110
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AIM - Learning Companion</title>
7
  <link rel="stylesheet" href="/static/style.css">
8
  </head>
9
  <body>
10
 
11
+ <!-- Language toggle (visible on all screens) -->
12
+ <button id="btn-lang" class="btn-lang" title="Switch language">FR</button>
13
+
14
  <!-- ===== Setup Screen ===== -->
15
  <div id="setup-screen" class="screen active">
16
  <h1>AIM</h1>
17
+ <p class="subtitle" data-i18n="subtitle">Socratic Learning Companion</p>
18
 
19
  <div class="form-group">
20
+ <label for="topic-input" data-i18n="topicLabel">Exploration topic</label>
21
+ <input type="text" id="topic-input" data-i18n-placeholder="topicPlaceholder" placeholder="E.g.: Artificial intelligence in professional training">
22
  </div>
23
 
24
  <div class="form-group">
25
+ <label data-i18n="docsLabel">Reference documents (optional)</label>
26
  <div class="upload-zone" id="upload-zone">
27
  <div class="upload-icon">+</div>
28
+ <div class="upload-text" data-i18n="uploadText">Drag your files here or click to select</div>
29
+ <div class="upload-hint" data-i18n="uploadHint">PDF, PPTX, TXT or ZIP — multiple files allowed</div>
30
  <input type="file" id="file-input" multiple accept=".pdf,.pptx,.ppt,.txt,.zip" hidden>
31
  </div>
32
  <div class="upload-list" id="upload-list"></div>
 
34
  </div>
35
 
36
  <div class="form-group">
37
+ <label data-i18n="modeLabel">Mode</label>
38
  <div class="mode-selector">
39
  <div class="mode-btn selected" data-mode="TUTOR">
40
+ <div class="mode-title" data-i18n="modeTutor">Tutor</div>
41
+ <div class="mode-desc" data-i18n="modeTutorDesc">Supportive guidance, open-ended questions</div>
42
  </div>
43
  <div class="mode-btn" data-mode="CRITIC">
44
+ <div class="mode-title" data-i18n="modeCritic">Critic</div>
45
+ <div class="mode-desc" data-i18n="modeCriticDesc">Devil's advocate, tests logical weaknesses</div>
46
  </div>
47
  </div>
48
  </div>
49
 
50
+ <button id="btn-start" class="btn-primary" disabled data-i18n="btnStart">Start session</button>
51
  </div>
52
 
53
  <!-- ===== Chat Screen ===== -->
 
59
  <span id="docs-badge" class="badge badge-docs" style="display:none"></span>
60
  </div>
61
  <div class="chat-header-right">
62
+ <button id="btn-end-session" class="btn-end" data-i18n="btnEnd">End session</button>
63
+ <button id="btn-reset" class="btn-reset" data-i18n="btnRestart">Restart</button>
64
  </div>
65
  </div>
66
 
 
74
  </div>
75
 
76
  <div class="input-bar">
77
+ <input type="text" id="chat-input" data-i18n-placeholder="chatPlaceholder" placeholder="Type your thoughts here...">
78
+ <button id="btn-send" class="btn-send" data-i18n="btnSend">Send</button>
79
  </div>
80
  </div>
81
 
82
  <!-- ===== Analysis Screen ===== -->
83
  <div id="analysis-screen" class="screen">
84
+ <h2 data-i18n="reportTitle">Session Report</h2>
85
 
86
  <div class="scores-grid" id="scores-grid"></div>
87
 
88
  <div class="analysis-section">
89
+ <h3 data-i18n="summaryTitle">Summary</h3>
90
  <p id="analysis-summary"></p>
91
  </div>
92
 
93
  <div class="analysis-section">
94
+ <h3 data-i18n="strengthsTitle">Key strengths</h3>
95
  <ul id="analysis-strengths"></ul>
96
  </div>
97
 
98
  <div class="analysis-section">
99
+ <h3 data-i18n="weaknessesTitle">Areas for improvement</h3>
100
  <ul id="analysis-weaknesses"></ul>
101
  </div>
102
 
103
  <div class="analysis-section">
104
+ <h3 data-i18n="rhythmTitle">Pace</h3>
105
+ <p><span data-i18n="rhythmText">Responses under 8 seconds:</span> <span id="rhythm-count" class="rhythm-badge">0</span></p>
106
  </div>
107
 
108
  <div class="analysis-actions">
109
+ <button id="btn-export" class="btn-secondary" data-i18n="btnExport">Export JSON</button>
110
+ <button id="btn-new-session" class="btn-primary" data-i18n="btnNewSession">New session</button>
111
  </div>
112
  </div>
113
 
static/style.css CHANGED
@@ -15,6 +15,25 @@
15
  --radius-sm: 8px;
16
  }
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
19
 
20
  body {
 
15
  --radius-sm: 8px;
16
  }
17
 
18
+ .btn-lang {
19
+ position: fixed;
20
+ top: 12px;
21
+ right: 16px;
22
+ z-index: 100;
23
+ background: var(--bg-tertiary);
24
+ color: var(--text-primary);
25
+ border: 1px solid var(--border);
26
+ border-radius: var(--radius-sm);
27
+ padding: 6px 14px;
28
+ font-size: 0.85rem;
29
+ font-weight: 600;
30
+ cursor: pointer;
31
+ transition: background 0.2s;
32
+ }
33
+ .btn-lang:hover {
34
+ background: var(--accent);
35
+ }
36
+
37
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
38
 
39
  body {