diff --git a/Makefile b/Makefile
index 84df75ec1..56d2ae3c3 100644
--- a/Makefile
+++ b/Makefile
@@ -35,17 +35,22 @@ static/dune:
# Generates up-to-date translation template for lang % from the sources
LANGS = $(patsubst translations/%.po,%,$(wildcard translations/*.po))
translations/$(LANGS:=.pot):
- @for f in $(LANGS); do echo >> translations/$$f.po; done
- @rm -f translations/*.pot
+ @for f in $(LANGS); do \
+ echo >> translations/$$f.po; \
+ rm -f translations/$$f.pot; \
+ cp translations/$$f.po.header translations/$$f.pot; \
+ done
@${DUNE} clean ${DUNE_ARGS}
-rm -f ${INDEX_ODOC_PATH}
@DUMP_POT=1 ${DUNE} build ${DUNE_ARGS} -j 1
@for f in $(LANGS); do \
mv translations/$$f.pot translations/$$f.pot.bak; \
- msguniq translations/$$f.pot.bak > translations/$$f.pot; \
- rm translations/$$f.pot.bak; \
+ msguniq -t utf-8 translations/$$f.pot.bak > translations/$$f.pot \
+ && rm translations/$$f.pot.bak; \
done
+.PHONY: translations/$(LANGS:=.pot)
+
# Updates existing translations (.po) for the latest source template
update-%-translation: translations/%.pot
@msgmerge -U translations/$*.po translations/$*.pot
diff --git a/src/app/dune b/src/app/dune
index e39e59219..22ff6da05 100644
--- a/src/app/dune
+++ b/src/app/dune
@@ -39,7 +39,8 @@
learnocaml_toplevel
js_of_ocaml-ppx
ocplib_i18n)
- (modules Learnocaml_teacher_tab
+ (modules Learnocaml_teacher_tab_doc
+ Learnocaml_teacher_tab
Learnocaml_index_main)
(preprocess (pps ppx_ocplib_i18n js_of_ocaml-ppx))
)
diff --git a/src/app/learnocaml_common.ml b/src/app/learnocaml_common.ml
index 8cc865c54..fe95e6ec9 100644
--- a/src/app/learnocaml_common.ml
+++ b/src/app/learnocaml_common.ml
@@ -143,16 +143,25 @@ let confirm ~title ?(ok_label=[%i"OK"]) ?(cancel_label=[%i"Cancel"]) contents f
close_button cancel_label;
]
-let ask_string ~title ?(ok_label=[%i"OK"]) contents =
+let ask_string ~title ?(ok_label=[%i"OK"]) ?(may_cancel=true) contents =
let input_field =
H.input ~a:[
H.a_input_type `Text;
] ()
in
let result_t, up = Lwt.wait () in
- ext_alert ~title (contents @ [input_field]) ~buttons:[
- box_button ok_label (fun () -> Lwt.wakeup up @@ Manip.value input_field)
- ];
+ let validate _ =
+ Lwt.wakeup up @@ Manip.value input_field
+ in
+ Manip.Ev.onreturn input_field validate;
+ let buttons =
+ box_button ok_label validate
+ :: (if may_cancel
+ then [close_button [%i"Cancel"]]
+ else [])
+ in
+ ext_alert ~title (contents @ [input_field]) ~buttons;
+ Manip.focus input_field;
result_t
let default_exn_printer = function
@@ -1157,7 +1166,7 @@ let get_token ?(has_server = true) () =
Lwt.return
with
Not_found ->
- ask_string ~title:"Token"
+ ask_string ~title:"Token" ~may_cancel:false
[H.txt [%i"Enter your token"]]
>>= fun input_tok ->
let token = Token.parse (input_tok) in
diff --git a/src/app/learnocaml_common.mli b/src/app/learnocaml_common.mli
index f94ce7f6b..eff755d06 100644
--- a/src/app/learnocaml_common.mli
+++ b/src/app/learnocaml_common.mli
@@ -45,6 +45,7 @@ val confirm :
val ask_string :
title: string ->
?ok_label: string ->
+ ?may_cancel: bool ->
[< Html_types.div_content > `Input] Tyxml_js.Html.elt list ->
string Lwt.t
diff --git a/src/app/learnocaml_teacher_tab.ml b/src/app/learnocaml_teacher_tab.ml
index f0bf5fbd2..0993e6470 100644
--- a/src/app/learnocaml_teacher_tab.ml
+++ b/src/app/learnocaml_teacher_tab.ml
@@ -68,22 +68,46 @@ let tag_addremove list_id placeholder add_fun remove_fun =
] [ H.txt "\xe2\x9e\x96" (* U+2796 heavy minus sign *) ];
]
+let help_button name (title,md_text) =
+ let dialog () =
+ let text_div =
+ let d =
+ H.div []
+ ~a:[H.a_class ["doc-popup-body"]]
+ in
+ (* Manip.SetCss.maxHeight d "85vh";
+ * Manip.SetCss.overflowY d "auto"; *)
+ let doc_html_string =
+ Omd.(md_text |> of_string |> to_html)
+ in
+ Manip.setInnerHtml d doc_html_string;
+ d
+ in
+ Learnocaml_common.ext_alert ~title [text_div]
+ in
+ H.button ~a:[
+ H.a_id ("button_help_"^name);
+ H.a_onclick (fun _ -> dialog (); true);
+ H.a_style "margin-left: 1em;";
+ ] [H.txt "?"]
+
let rec teacher_tab token _select _params () =
let action_new_token () =
- retrieve (Learnocaml_api.Create_teacher_token token)
+ Learnocaml_common.ask_string
+ ~title:"NEW TEACHER TOKEN"
+ [H.txt @@ "Enter a nickname for the new token:"]
+ >>= fun nickname ->
+ let nick = match String.trim nickname with
+ | "" -> None
+ | s -> Some s
+ in
+ retrieve (Learnocaml_api.Create_teacher_token (token, nick))
>|= fun new_token ->
alert ~title:[%i"TEACHER TOKEN"]
(Printf.sprintf [%if"New teacher token created:\n%s\n\n\
write it down."]
(Token.to_string new_token))
in
- let action_csv_export () =
- retrieve (Learnocaml_api.Students_csv (token, [], []))
- >|= fun csv ->
- Learnocaml_common.fake_download
- ~name:"learnocaml.csv"
- ~contents:(Js.string csv)
- in
let indent_style lvl =
H.a_style (Printf.sprintf "text-align: left; padding-left: %dem;" lvl)
in
@@ -183,6 +207,23 @@ let rec teacher_tab token _select _params () =
let assignment_change = ref (fun _ -> assert false) in
let assignment_remove = ref (fun _ -> assert false) in
+ let action_csv_export () =
+ let exercises =
+ Hashtbl.to_seq_keys selected_exercises |>
+ List.of_seq
+ in
+ let students =
+ Hashtbl.to_seq_keys selected_students |>
+ Seq.filter_map (function `Token tk -> Some tk | `Any -> None) |>
+ List.of_seq
+ in
+ retrieve (Learnocaml_api.Students_csv (token, exercises, students))
+ >|= fun csv ->
+ Learnocaml_common.fake_download
+ ~name:"learnocaml.csv"
+ ~contents:(Js.string csv)
+ in
+
(* Exercises table *)
let rec mk_table group_level acc status group =
match group with
@@ -211,7 +252,7 @@ let rec teacher_tab token _select _params () =
in
let open_partition_ () =
Lwt.async (fun () ->
- ask_string ~title:"Choose a function name"
+ ask_string ~title:"Partitioning of student solutions"
[H.txt @@ "Choose a function name to partition codes from "^ id ^": "]
>|= fun funname ->
let _win =
@@ -258,12 +299,13 @@ let rec teacher_tab token _select _params () =
H.td [stars_div meta.Exercise.Meta.stars];
H.td [
let cls, text =
- if Token.Map.is_empty ES.(st.assignments.token_map) then
- match ES.(st.assignments.default) with
- | ES.Open -> "exo_open", [%i"Open"]
- | ES.Closed -> "exo_closed", [%i"Closed"]
- | ES.Assigned _ -> "exo_assigned", [%i"Assigned"]
- else "exo_assigned", [%i"Assigned"]
+ match Token.Map.is_empty ES.(st.assignments.token_map),
+ ES.(st.assignments.default) with
+ | true, ES.Open -> "exo_open", [%i"Open"]
+ | true, ES.Closed -> "exo_closed", [%i"Closed"]
+ | _, (ES.Assigned _ | ES.Closed) ->
+ "exo_assigned", [%i"Assigned"]
+ | false, ES.Open -> "exo_assigned", [%i"Open/Assg"]
in
H.span ~a:[H.a_class [cls]] [H.txt text]
];
@@ -328,11 +370,14 @@ let rec teacher_tab token _select _params () =
let exercise_skills_list_id = "exercise_skills_list" in
let exercises_div =
let legend =
- H.legend ~a:[
- H.a_onclick (fun _ ->
- !toggle_selected_exercises (all_exercises !exercises_index);
- true);
- ] [H.txt [%i"Exercises"]; H.txt " \xe2\x98\x90" (* U+2610 *)]
+ H.legend [
+ H.span
+ [ H.txt [%i"Exercises"]; H.txt " \xe2\x98\x90" (* U+2610 *) ]
+ ~a:[H.a_onclick (fun _ ->
+ !toggle_selected_exercises (all_exercises !exercises_index);
+ true)];
+ help_button "exercises" Learnocaml_teacher_tab_doc.exercises_pane_md
+ ]
in
H.div ~a:[H.a_id "exercises_pane"; H.a_class ["learnocaml_pane"]] [
H.div ~a:[H.a_id "exercises_filter_box"] [
@@ -530,23 +575,26 @@ let rec teacher_tab token _select _params () =
in
let students_div =
let legend =
- H.legend ~a:[
- H.a_onclick (fun _ ->
- let all =
- Token.Map.fold (fun k _ acc -> (`Token k)::acc)
- !students_map [`Any]
- in
- let all =
- List.filter (fun t ->
- not (Manip.hasClass (find_component (student_line_id t))
- "student_hidden"))
- all
- in
- !toggle_selected_students all;
- true
- );
- ] [H.txt [%i"Students"];
- H.txt " \xe2\x98\x90" (* U+2610 ballot box *)]
+ H.legend [
+ H.span
+ [ H.txt [%i"Students"];
+ H.txt " \xe2\x98\x90" (* U+2610 ballot box *) ]
+ ~a:[H.a_onclick (fun _ ->
+ let all =
+ Token.Map.fold (fun k _ acc -> (`Token k)::acc)
+ !students_map [`Any]
+ in
+ let all =
+ List.filter (fun t ->
+ not (Manip.hasClass (find_component (student_line_id t))
+ "student_hidden"))
+ all
+ in
+ !toggle_selected_students all;
+ true
+ )];
+ help_button "students" Learnocaml_teacher_tab_doc.students_pane_md
+ ]
in
H.div ~a:[H.a_id "students_pane"; H.a_class ["learnocaml_pane"]] [
H.div ~a:[H.a_id "students_filter_box"] [
@@ -812,12 +860,10 @@ let rec teacher_tab token _select _params () =
ES.(default_assignment st.assignments = Open))
ids
then ES.(fun assg ->
- (* fixme: invisible change if the exercise is assigned! *)
match default_assignment assg with
| Open -> set_default_assignment assg Closed
| _ -> assg)
else ES.(fun assg ->
- (* fixme: invisible change if the exercise is assigned! *)
match default_assignment assg with
| Closed -> set_default_assignment assg Open
| _ -> assg)
@@ -841,9 +887,16 @@ let rec teacher_tab token _select _params () =
Manip.appendChild exercises_div exercise_control_div;
let assignments_div = H.div [] in
let control_div =
+ let legend =
+ H.legend [
+ H.txt [%i"Assignments"];
+ help_button "assignments"
+ Learnocaml_teacher_tab_doc.assignments_pane_md
+ ]
+ in
H.div ~a:[H.a_id "control_pane"] [
H.fieldset
- ~legend:(H.legend [H.txt [%i"Assignments"]])
+ ~legend
[assignments_div];
]
in
@@ -932,7 +985,7 @@ let rec teacher_tab token _select _params () =
H.li ~a: [ H.a_onclick (fun _ -> Lwt.async action_new_token; true) ]
[ H.txt [%i"Create new teacher token"] ];
H.li ~a: [ H.a_onclick (fun _ -> Lwt.async action_csv_export; true) ]
- [ H.txt [%i"Download student data as CSV"] ];
+ [ H.txt [%i"Download the data for selected students/exercises as CSV"] ];
]
];
]
@@ -1102,10 +1155,16 @@ let rec teacher_tab token _select _params () =
if SMap.is_empty !status_changes &&
Token.Map.is_empty !students_changes then
(Manip.replaceChildren status_text_div [];
- Manip.removeClass status_text_div "warning")
+ Manip.removeClass status_text_div "warning";
+ Option.iter
+ (fun b -> Manip.removeClass b "warning")
+ (Manip.by_id "button_apply"))
else
(Manip.replaceChildren status_text_div [H.txt [%i"Unsaved changes"]];
- Manip.addClass status_text_div "warning")
+ Manip.addClass status_text_div "warning";
+ Option.iter
+ (fun b -> Manip.addClass b "warning")
+ (Manip.by_id "button_apply"))
end;
toggle_selected_exercises := begin
fun ?force ?(update = force=None) ids ->
diff --git a/src/app/learnocaml_teacher_tab_doc.ml b/src/app/learnocaml_teacher_tab_doc.ml
new file mode 100644
index 000000000..a5b3d4296
--- /dev/null
+++ b/src/app/learnocaml_teacher_tab_doc.ml
@@ -0,0 +1,118 @@
+(* This file is part of Learn-OCaml.
+ *
+ * Copyright (C) 2023 OCaml Software Foundation.
+ *
+ * Learn-OCaml is distributed under the terms of the MIT license. See the
+ * included LICENSE file for details. *)
+
+type markdown_doc = string * string (** title, markdown text *)
+
+let exercises_pane_md =
+ [%i"Handling exercises"], [%i{markdown|
+> Don't forget to click "Apply" on the bottom-right after any changes
+
+- **Filtering exercises**
+ Use the đ box on top.
+ - type for raw text search on the titles
+ - click again to list the tags and filter based on those
+ - multiple search terms are allowed
+
+- **Selecting which exercises can be seen by the students**
+ - select the exercises with a click (the box on top (de)selects all; you can
+ also click folder titles)
+ - click the [Open/Close] button to toggle
+ - for complex selections, use the filter box then select all
+
+- **Defining exercise skill tags**
+ Those can also be pre-defined in the exercises metadata.
+ 1. Select the exercises to tag
+ 2. Select or type a new tag in one of the boxes on the lower-right of the pane
+ (left for requirements, right for skills trained by the exercise)
+ 3. Click the corresponding [+] button to add the tag ([-] to remove)
+
+- **Checking an exercise contents**
+ - double-click an exercise
+ - it will be opened in a new tab
+
+- **Classifying students answers**
+ As documented
+[here](https://ocaml-sf.org/learn-ocaml/howto-classify-students-answers.html):
+ - Assume you want to analyze students' definitions of function `foo` in
+ exercise `exo`
+ - Hold Ctrl (on macOS: Hold â) and left click on `exo` in the list of
+ exercises
+ - Enter `foo` in the dialog box
+ - The dissimilarity analysis will be opened in a new tab
+|markdown}]
+
+let students_pane_md =
+ [%i"Handling students"], [%i{markdown|
+> Don't forget to click "Apply" on the bottom-right after any changes
+
+- **Filtering students**
+ Use the đ box on top.
+ - type for raw text search on the nicknames
+ - click again to list the tags and filter based on those
+ - multiple search terms are allowed
+
+- **Tagging students**
+ Tags can be used for groups, level, etc.
+ 1. select the students to tag. To select a group, use the filter box and the
+ box on top to select all. Use the `Sort by` box on the top right to easily
+ select new students or untagged studends
+ 2. select or type the tag on the box to the lower-right
+ 3. Click the corresponding [+] button to add the tag ([-] to remove)
+
+- **Following a student**
+ - at a glance:
+ a bar on the right of each student shows a short insight of their progress,
+ - vertical stripes correspond to the exercises, in order
+ - grey is shown for exercises not attempted
+ - the color otherwise varies from green (good) to red (bad) depending on the
+ grade
+ - in detail:
+ - double-click on the student line to open their track record in a new tab
+ - there you can see their detailed results per exercise, and check their
+ answers
+ - see "Classifying students answers" in the exercise pane guide for a way
+ to compare solutions between students
+
+- **Downloading the grades**
+ - use the [Actions] menu at the bottom to export a CSV file containing a line
+ per student, and two columns (grade and date) per exercise
+ - if any exercises or students are selected, the CSV will only contain those
+ - otherwise everything is exported
+|markdown}]
+
+let assignments_pane_md =
+ [%i"Handling assignments"], [%i{markdown|
+Assignments provide a way to open select exercises to select students for a
+given period of time. Once due, the students will be restricted to read-only
+access.
+
+- **Creating an assignment**
+ 1. Select exercises, as above
+ 2. Select students, as above. You can use the "any future students" line to
+ apply to students who haven't yet registered
+ 3. Click the "New assignment" button at the bottom
+ 4. Edit the two dates (start and end of the assignment, resp.)
+ 5. Don't forget to [Apply] on the bottom right
+ 6. Click the assignment again to unselect it and quit assignment mode
+
+- **Editing an assignment**
+ - select it in the bottom pane
+ - the corresponding exercises and students are automatically selected. Update
+ the selection as needed
+ - click the assignment again when finished (to quit editing mode), and don't
+ forget to [Apply]
+
+- **Following assignments**
+ - an additional small "progression" bar is shown for each assignment in the
+ students pane
+ - past assignment dates are outlined in red
+ - when selecting students, exercises assigned to any of them will be greyed
+ out to prevent assigning multiple times the same exercise to the same
+ student
+ - conversely, when selecting exercises, students who already have them
+ assigned are greyed out
+|markdown}]
diff --git a/src/app/learnocaml_teacher_tab_doc.mli b/src/app/learnocaml_teacher_tab_doc.mli
new file mode 100644
index 000000000..d24e16e20
--- /dev/null
+++ b/src/app/learnocaml_teacher_tab_doc.mli
@@ -0,0 +1,15 @@
+(* This file is part of Learn-OCaml.
+ *
+ * Copyright (C) 2023 OCaml Software Foundation.
+ *
+ * Learn-OCaml is distributed under the terms of the MIT license. See the
+ * included LICENSE file for details. *)
+
+(** This module contains documentation strings (in markdown) for the different
+ panes in the teacher tab *)
+
+type markdown_doc = string * string (** title, markdown text *)
+
+val exercises_pane_md: markdown_doc
+val students_pane_md: markdown_doc
+val assignments_pane_md: markdown_doc
diff --git a/src/server/learnocaml_server.ml b/src/server/learnocaml_server.ml
index 5e9917f0e..0bccadfac 100644
--- a/src/server/learnocaml_server.ml
+++ b/src/server/learnocaml_server.ml
@@ -279,12 +279,15 @@ module Request_handler = struct
(function
| Failure body -> (`Bad_request, body)
| exn -> (`Internal_server_error, Printexc.to_string exn))
- | Api.Create_teacher_token token ->
+ | Api.Create_teacher_token (token, nick) ->
verify_teacher_token token
>?= fun () ->
Token.create_teacher ()
- >>= respond_json cache
-
+ >>= fun tok ->
+ (match nick with | None -> Lwt.return_unit
+ | Some nickname ->
+ Save.set tok Save.{empty with nickname})
+ >>= fun () -> respond_json cache tok
| Api.Fetch_save token ->
lwt_catch_fail
(fun () ->
diff --git a/src/state/learnocaml_api.ml b/src/state/learnocaml_api.ml
index 30a084402..16496a9d0 100644
--- a/src/state/learnocaml_api.ml
+++ b/src/state/learnocaml_api.ml
@@ -101,7 +101,7 @@ type _ request =
| Create_token:
string * student token option * string option -> student token request
| Create_teacher_token:
- teacher token -> teacher token request
+ teacher token * string option -> teacher token request
| Fetch_save:
'a token -> Save.t request
| Archive_zip:
@@ -299,9 +299,10 @@ module Conversions (Json: JSON_CODEC) = struct
| Create_token (secret_candiate, token, nick) ->
get ?token (["sync"; "new"; secret_candiate] @
(match nick with None -> [] | Some n -> [n]))
- | Create_teacher_token token ->
+ | Create_teacher_token (token, nick) ->
assert (Token.is_teacher token);
- get ~token ["teacher"; "new"]
+ get ~token (["teacher"; "new"] @
+ (match nick with None -> [] | Some n -> [n]))
| Fetch_save token ->
get ~token ["save.json"]
@@ -415,8 +416,9 @@ module Server (Json: JSON_CODEC) (Rh: REQUEST_HANDLER) = struct
| `GET, ["sync"; "new"; secret_candidate; nick], token ->
Create_token (secret_candidate, token, Some nick) |> k
| `GET, ["teacher"; "new"], Some token when Token.is_teacher token ->
- Create_teacher_token token |> k
-
+ Create_teacher_token (token, None) |> k
+ | `GET, ["teacher"; "new"; nick], Some token when Token.is_teacher token ->
+ Create_teacher_token (token, Some nick) |> k
| `GET, ["save.json"], Some token ->
Fetch_save token |> k
| `GET, ["archive.zip"], Some token ->
diff --git a/src/state/learnocaml_api.mli b/src/state/learnocaml_api.mli
index 4b04ab6f0..86f0ca385 100644
--- a/src/state/learnocaml_api.mli
+++ b/src/state/learnocaml_api.mli
@@ -87,7 +87,7 @@ type _ request =
| Create_token:
string * student token option * string option -> student token request
| Create_teacher_token:
- teacher token -> teacher token request
+ teacher token * string option -> teacher token request
| Fetch_save:
'a token -> Save.t request
| Archive_zip:
diff --git a/src/utils/dune b/src/utils/dune
index 51b8db206..cb9f0f4dc 100644
--- a/src/utils/dune
+++ b/src/utils/dune
@@ -21,7 +21,7 @@
(wrapped false)
(flags :standard -warn-error -4-42-44-45-48)
(kind ppx_rewriter)
- (libraries ocaml-migrate-parsetree)
+ (libraries ocaml-migrate-parsetree re)
(modules Ppx_ocplib_i18n)
)
diff --git a/src/utils/ppx_ocplib_i18n.ml b/src/utils/ppx_ocplib_i18n.ml
index fd5bfab09..28a60c4c5 100644
--- a/src/utils/ppx_ocplib_i18n.ml
+++ b/src/utils/ppx_ocplib_i18n.ml
@@ -103,7 +103,11 @@ let dump_pot () =
Format.pp_print_char fmt ' ';
Location.print_loc fmt l)
locs;
- Format.fprintf fmt "\nmsgid %S\n" s;
+ Format.fprintf fmt "\nmsgid \"%s\"\n"
+ Re.(s |>
+ replace_string (compile (char '\\')) ~by:"\\\\" |>
+ replace_string (compile (char '"')) ~by:"\\\"" |>
+ replace_string (compile (char '\n')) ~by:"\\n");
Format.fprintf fmt "msgstr \"\"\n\n";
) misses;
close_out oc
diff --git a/static/css/learnocaml_teacher_tab.css b/static/css/learnocaml_teacher_tab.css
index 9c06f086a..a2dffac50 100644
--- a/static/css/learnocaml_teacher_tab.css
+++ b/static/css/learnocaml_teacher_tab.css
@@ -54,10 +54,19 @@
cursor: cell;
}
+#learnocaml-main-teacher > #teacher_menubar {
+ margin-top: auto;
+ max-height: 4ex;
+}
+
#learnocaml-main-teacher > #teacher_menubar > * {
float: right;
}
+#learnocaml-main-teacher > #teacher_menubar button {
+ margin: 5px;
+}
+
#learnocaml-main-teacher > #teacher_menubar > #status-text-div {
float: left;
margin-left: 16px;
@@ -68,6 +77,13 @@
color: red;
}
+#learnocaml-main-teacher > #teacher_menubar button.warning {
+ color: black;
+ background-color: #d62;
+ border-color: #910;
+ border-radius: 4px;
+}
+
#learnocaml-main-teacher table {
width: 100%;
border-spacing: 0;
@@ -663,3 +679,27 @@ td.skills-prereq {
line-height: 24px;
margin: 0 5px;
}
+
+.doc-popup-body {
+ max-height: 80vh;
+ overflow-y: auto;
+}
+.doc-popup-body blockquote p {
+ background-color: #555;
+ padding: 4px 4px 4px 4px;
+}
+.doc-popup-body code {
+ display: inline !important;
+ background-color: #222;
+ font-size: 80%;
+ padding: 0;
+ border: 1px solid #666;
+ border-radius: 3px;
+}
+.doc-popup-body > ul > li {
+ margin: 2ex 0;
+ line-height: 120%;
+}
+.doc-popup-body li li {
+ line-height: 110%;
+}
diff --git a/translations/fr.po b/translations/fr.po
index 782bedd19..d52ec457e 100644
--- a/translations/fr.po
+++ b/translations/fr.po
@@ -6,12 +6,12 @@
msgid ""
msgstr ""
"Project-Id-Version: learn-ocaml ~dev\n"
-"PO-Revision-Date: 2023-04-18 19:58+0200\n"
+"PO-Revision-Date: 2023-05-09 14:32+0200\n"
"Last-Translator: Erik Martin-Dorel \n"
"Language-Team: OCSF\n"
"Language: french\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: File "src/grader/learnocaml_report.ml", line 240, characters 50-66 595,
@@ -85,247 +85,251 @@ msgstr "OK"
msgid "ERROR"
msgstr "ERREUR"
-#: File "src/app/learnocaml_common.ml", line 140, characters 58-66 446, 12-20
-#: 942, 19-27 "src/app/learnocaml_index_main.ml", 583, 17-25
+#: File "src/app/learnocaml_common.ml", line 140, characters 58-66 160, 30-38
+#: 455, 12-20 952, 19-27 "src/app/learnocaml_index_main.ml", 583, 17-25
msgid "Cancel"
msgstr "Annuler"
-#: File "src/app/learnocaml_common.ml", line 438, characters 26-41 937, 32-47
+#: File "src/app/learnocaml_common.ml", line 447, characters 26-41 947, 32-47
#: "src/app/learnocaml_index_main.ml", 578,
msgid "REQUEST ERROR"
msgstr "ERREUR DE REQUĂTE"
-#: File "src/app/learnocaml_common.ml", line 439, characters 22-59 938, 30-67
+#: File "src/app/learnocaml_common.ml", line 448, characters 22-59 948, 30-67
#: "src/app/learnocaml_index_main.ml", 579, 28-65
msgid "Could not retrieve data from server"
msgstr "Ăchec lors du tĂ©lĂ©chargement des donnĂ©es du serveur"
-#: File "src/app/learnocaml_common.ml", line 442, characters 12-19 486, 13-20
-#: 941, 19-26 "src/app/learnocaml_index_main.ml", 582, 17-24
+#: File "src/app/learnocaml_common.ml", line 451, characters 12-19 495, 13-20
+#: 951, 19-26 "src/app/learnocaml_index_main.ml", 582, 17-24
msgid "Retry"
msgstr "RĂ©essayer"
-#: File "src/app/learnocaml_common.ml", line 445, characters 25-33 487, 13-21
+#: File "src/app/learnocaml_common.ml", line 454, characters 25-33 496, 13-21
msgid "Ignore"
msgstr "Ignorer"
-#: File "src/app/learnocaml_common.ml", line 482, characters 26-39
+#: File "src/app/learnocaml_common.ml", line 491, characters 26-39
msgid "SYNC FAILED"
msgstr "ECHEC DE LA SYNCHRONISATION"
-#: File "src/app/learnocaml_common.ml", line 483, characters 22-66
+#: File "src/app/learnocaml_common.ml", line 492, characters 22-66
msgid "Could not synchronise save with the server"
msgstr "Les donnĂ©es n'ont pas pu ĂȘtre synchronisĂ©es avec le serveur"
-#: File "src/app/learnocaml_common.ml", line 542, characters 39-50
+#: File "src/app/learnocaml_common.ml", line 552, characters 39-50
msgid "%dd %02dh"
msgstr "%dj %02dh"
-#: File "src/app/learnocaml_common.ml", line 543, characters 40-51
+#: File "src/app/learnocaml_common.ml", line 553, characters 40-51
msgid "%02d:%02d"
msgstr "%02d:%02d"
-#: File "src/app/learnocaml_common.ml", line 544, characters 23-36
+#: File "src/app/learnocaml_common.ml", line 554, characters 23-36
msgid "0:%02d:%02d"
msgstr "0:%02d:%02d"
-#: File "src/app/learnocaml_common.ml", line 575, characters 34-55 1182, 38-59
+#: File "src/app/learnocaml_common.ml", line 585, characters 34-55 1209, 38-59
msgid "difficulty: %d / 40"
msgstr "difficulté: %d / 40"
-#: File "src/app/learnocaml_common.ml", line 610, characters 30-75
+#: File "src/app/learnocaml_common.ml", line 620, characters 30-75
msgid "No description available for this exercise."
msgstr "Aucune description pour cet exercice."
-#: File "src/app/learnocaml_common.ml", line 633, characters 32-41
+#: File "src/app/learnocaml_common.ml", line 643, characters 32-41
#: "src/app/learnocaml_index_main.ml", 132, 54-63
msgid "project"
msgstr "projet"
-#: File "src/app/learnocaml_common.ml", line 634, characters 32-41
+#: File "src/app/learnocaml_common.ml", line 644, characters 32-41
#: "src/app/learnocaml_index_main.ml", 133, 54-63
msgid "problem"
msgstr "problĂšme"
-#: File "src/app/learnocaml_common.ml", line 635, characters 33-43
+#: File "src/app/learnocaml_common.ml", line 645, characters 33-43
#: "src/app/learnocaml_index_main.ml", 134, 55-65
msgid "exercise"
msgstr "exercice"
-#: File "src/app/learnocaml_common.ml", line 787, characters 26-33
+#: File "src/app/learnocaml_common.ml", line 797, characters 26-33
msgid "Clear"
msgstr "Effacer"
-#: File "src/app/learnocaml_common.ml", line 792, characters 25-32 917, 24-31
+#: File "src/app/learnocaml_common.ml", line 802, characters 25-32 927, 24-31
msgid "Reset"
msgstr "RĂ©initialiser"
-#: File "src/app/learnocaml_common.ml", line 797, characters 22-35
+#: File "src/app/learnocaml_common.ml", line 807, characters 22-35
msgid "Eval phrase"
msgstr "Ăvaluer la phrase"
-#: File "src/app/learnocaml_common.ml", line 812, characters 24-51
+#: File "src/app/learnocaml_common.ml", line 822, characters 24-51
msgid "Preparing the environment"
msgstr "Préparation de l'environnement"
-#: File "src/app/learnocaml_common.ml", line 813, characters 39-47 818, 37-45
+#: File "src/app/learnocaml_common.ml", line 823, characters 39-47 828, 37-45
msgid "Editor"
msgstr "Ăditeur"
-#: File "src/app/learnocaml_common.ml", line 814, characters 41-51
-#: "src/app/learnocaml_index_main.ml", 691, 30-40
+#: File "src/app/learnocaml_common.ml", line 824, characters 41-51
+#: "src/app/learnocaml_index_main.ml", 692, 30-40
msgid "Toplevel"
msgstr "Toplevel"
-#: File "src/app/learnocaml_common.ml", line 815, characters 39-47 827,
+#: File "src/app/learnocaml_common.ml", line 825, characters 39-47 837,
#: "src/app/learnocaml_exercise_main.ml", 58, 30-38 62, 67,
#: "src/app/learnocaml_student_view.ml", 383, 28-36 396, 400, 405,
msgid "Report"
msgstr "Rapport"
-#: File "src/app/learnocaml_common.ml", line 816, characters 37-47
+#: File "src/app/learnocaml_common.ml", line 826, characters 37-47
msgid "Exercise"
msgstr "Exercice"
-#: File "src/app/learnocaml_common.ml", line 817, characters 37-46
+#: File "src/app/learnocaml_common.ml", line 827, characters 37-46
msgid "Details"
msgstr "DĂ©tails"
-#: File "src/app/learnocaml_common.ml", line 819, characters 27-70
+#: File "src/app/learnocaml_common.ml", line 829, characters 27-70
msgid "Click the Grade button to get your report"
msgstr "Cliquez sur le bouton Noter pour obtenir votre rapport"
-#: File "src/app/learnocaml_common.ml", line 824, characters 22-44
+#: File "src/app/learnocaml_common.ml", line 834, characters 22-44
msgid "Loading student data"
msgstr "Chargement des informations sur les Ă©tudiants"
-#: File "src/app/learnocaml_common.ml", line 825, characters 38-45
+#: File "src/app/learnocaml_common.ml", line 835, characters 38-45
msgid "Stats"
msgstr "Statistiques"
-#: File "src/app/learnocaml_common.ml", line 826, characters 37-48
-#: "src/app/learnocaml_index_main.ml", 688, 29-40
-#: "src/app/learnocaml_teacher_tab.ml", 329, 18-29
+#: File "src/app/learnocaml_common.ml", line 836, characters 37-48
+#: "src/app/learnocaml_index_main.ml", 689, 29-40
+#: "src/app/learnocaml_teacher_tab.ml", 375, 21-32
#: "src/app/learnocaml_exercise_main.ml", 204, 23-34
msgid "Exercises"
msgstr "Exercices"
-#: File "src/app/learnocaml_common.ml", line 828, characters 37-46
+#: File "src/app/learnocaml_common.ml", line 838, characters 37-46
msgid "Subject"
msgstr "ĂnoncĂ©"
-#: File "src/app/learnocaml_common.ml", line 829, characters 39-47
+#: File "src/app/learnocaml_common.ml", line 839, characters 39-47
msgid "Answer"
msgstr "RĂ©ponse"
-#: File "src/app/learnocaml_common.ml", line 918, characters 22-42 968, 34-54
+#: File "src/app/learnocaml_common.ml", line 928, characters 22-42 978, 34-54
msgid "START FROM SCRATCH"
msgstr "TOUT RECOMMENCER"
-#: File "src/app/learnocaml_common.ml", line 919, characters 16-65 969, 28-77
+#: File "src/app/learnocaml_common.ml", line 929, characters 16-65 979, 28-77
msgid "This will discard all your edits. Are you sure?"
msgstr "Toutes vos modifications seront perdues. Ătes-vous sĂ»r·e ?"
-#: File "src/app/learnocaml_common.ml", line 933, characters 28-45 1152,
+#: File "src/app/learnocaml_common.ml", line 943, characters 28-45 1179,
#: "src/app/learnocaml_index_main.ml", 574,
msgid "TOKEN NOT FOUND"
msgstr "TOKEN NON TROUVĂ"
-#: File "src/app/learnocaml_common.ml", line 934, characters 17-60 1153,
+#: File "src/app/learnocaml_common.ml", line 944, characters 17-60 1180,
#: "src/app/learnocaml_index_main.ml", 575,
msgid "The entered token couldn't be recognised."
msgstr "Le token entré n'a pas été reconnu."
-#: File "src/app/learnocaml_common.ml", line 952, characters 34-61 957, 25-52
+#: File "src/app/learnocaml_common.ml", line 962, characters 34-61 967, 25-52
msgid "Reload latest graded code"
msgstr "Reprendre le dernier code noté"
-#: File "src/app/learnocaml_common.ml", line 953, characters 28-99
+#: File "src/app/learnocaml_common.ml", line 963, characters 28-99
msgid "This will replace your code with your last graded code. Are you sure?"
-msgstr "Cela va remplacer votre code par la derniĂšre version Ă©valuĂ©e. Ătes-vous sĂ»r·e ?"
+msgstr ""
+"Cela va remplacer votre code par la derniĂšre version Ă©valuĂ©e. Ătes-vous "
+"sûr·e ?"
-#: File "src/app/learnocaml_common.ml", line 960, characters 34-61 965, 25-52
+#: File "src/app/learnocaml_common.ml", line 970, characters 34-61 975, 25-52
msgid "Reload latest saved draft"
msgstr "Reprendre le dernier code synchronisé"
-#: File "src/app/learnocaml_common.ml", line 961, characters 28-99
+#: File "src/app/learnocaml_common.ml", line 971, characters 28-99
msgid "This will replace your code with your last saved draft. Are you sure?"
-msgstr "Cela va remplacer votre code par la derniĂšre version synchronisĂ©e. Ătes-vous sĂ»r·e ?"
+msgstr ""
+"Cela va remplacer votre code par la derniĂšre version synchronisĂ©e. Ătes-vous "
+"sûr·e ?"
-#: File "src/app/learnocaml_common.ml", line 972, characters 25-52
+#: File "src/app/learnocaml_common.ml", line 982, characters 25-52
msgid "Reset to initial template"
msgstr "RĂ©initialiser le code"
-#: File "src/app/learnocaml_common.ml", line 975, characters 9-17
+#: File "src/app/learnocaml_common.ml", line 985, characters 9-17
msgid "Reload"
msgstr "Recharger"
-#: File "src/app/learnocaml_common.ml", line 982, characters 27-37
+#: File "src/app/learnocaml_common.ml", line 1011, characters 27-37
msgid "Download"
msgstr "Télécharger"
-#: File "src/app/learnocaml_common.ml", line 990, characters 22-33
+#: File "src/app/learnocaml_common.ml", line 1019, characters 22-33
msgid "Eval code"
msgstr "Exécuter le code"
-#: File "src/app/learnocaml_common.ml", line 1000, characters 23-29
+#: File "src/app/learnocaml_common.ml", line 1029, characters 23-29
msgid "Sync"
msgstr "Sync"
-#: File "src/app/learnocaml_common.ml", line 1069, characters 34-49 1104,
+#: File "src/app/learnocaml_common.ml", line 1098, characters 34-49 1133,
msgid "OCaml prelude"
msgstr "Prélude OCaml"
-#: File "src/app/learnocaml_common.ml", line 1076, characters 59-65 1111,
+#: File "src/app/learnocaml_common.ml", line 1105, characters 59-65 1140,
msgid "Hide"
msgstr "Cacher"
-#: File "src/app/learnocaml_common.ml", line 1081, characters 59-65 1118,
+#: File "src/app/learnocaml_common.ml", line 1110, characters 59-65 1147,
msgid "Show"
msgstr "Montrer"
-#: File "src/app/learnocaml_common.ml", line 1143, characters 18-36
+#: File "src/app/learnocaml_common.ml", line 1170, characters 18-36
#: "src/app/learnocaml_index_main.ml", 629, 31-49
msgid "Enter your token"
msgstr "Entrez votre token"
-#: File "src/app/learnocaml_common.ml", line 1188, characters 22-35
+#: File "src/app/learnocaml_common.ml", line 1215, characters 22-35
msgid "Difficulty:"
msgstr "Difficulté :"
-#: File "src/app/learnocaml_common.ml", line 1202, characters 39-49
+#: File "src/app/learnocaml_common.ml", line 1229, characters 39-49
msgid "Kind: %s"
msgstr "Type : %s"
-#: File "src/app/learnocaml_common.ml", line 1343, characters 46-59
+#: File "src/app/learnocaml_common.ml", line 1370, characters 46-59
msgid "Identifier:"
msgstr "Identifiant de l'exercice :"
-#: File "src/app/learnocaml_common.ml", line 1347, characters 48-57
+#: File "src/app/learnocaml_common.ml", line 1374, characters 48-57
msgid "Author:"
msgstr "Auteur :"
-#: File "src/app/learnocaml_common.ml", line 1348, characters 47-57
+#: File "src/app/learnocaml_common.ml", line 1375, characters 47-57
msgid "Authors:"
msgstr "Auteurs :"
-#: File "src/app/learnocaml_common.ml", line 1353, characters 31-48
+#: File "src/app/learnocaml_common.ml", line 1380, characters 31-48
msgid "Skills trained:"
msgstr "Compétences pratiquées :"
-#: File "src/app/learnocaml_common.ml", line 1357, characters 31-49
+#: File "src/app/learnocaml_common.ml", line 1384, characters 31-49
msgid "Skills required:"
msgstr "Compétences requises :"
-#: File "src/app/learnocaml_common.ml", line 1362, characters 36-57
+#: File "src/app/learnocaml_common.ml", line 1389, characters 36-57
msgid "Previous exercises:"
msgstr "Exercices précédents :"
-#: File "src/app/learnocaml_common.ml", line 1365, characters 35-52
+#: File "src/app/learnocaml_common.ml", line 1392, characters 35-52
msgid "Next exercises:"
msgstr "Exercices suivants :"
-#: File "src/app/learnocaml_common.ml", line 1370, characters 26-36
+#: File "src/app/learnocaml_common.ml", line 1397, characters 26-36
msgid "Metadata"
msgstr "Métadonnées"
@@ -523,41 +527,41 @@ msgstr "Vous avez déjà un token ? Par ici !"
msgid "Connect"
msgstr "Se connecter"
-#: File "src/app/learnocaml_index_main.ml", line 631, characters 35-50
-msgid "Send feedback to Learn-OCaml developers"
-msgstr "Envoyer un commentaire aux développeurs Learn-OCaml"
-
#: File "src/app/learnocaml_index_main.ml", line 638, characters 9-19 640,
-#: "src/app/learnocaml_teacher_tab.ml", 558, 22-32
+#: "src/app/learnocaml_teacher_tab.ml", 612, 22-32
msgid "Nickname"
msgstr "Pseudonyme"
-#: File "src/app/learnocaml_index_main.ml", line 675, characters 38-59
+#: File "src/app/learnocaml_index_main.ml", line 647, characters 7-48
+msgid "Send feedback to Learn-OCaml developers"
+msgstr "Envoyer un commentaire aux développeurs Learn-OCaml"
+
+#: File "src/app/learnocaml_index_main.ml", line 676, characters 38-59
msgid "Choose an activity."
msgstr "Sélectionnez une activité."
-#: File "src/app/learnocaml_index_main.ml", line 684, characters 31-42
+#: File "src/app/learnocaml_index_main.ml", line 685, characters 31-42
msgid "Tutorials"
msgstr "Tutoriels"
-#: File "src/app/learnocaml_index_main.ml", line 686, characters 29-38
+#: File "src/app/learnocaml_index_main.ml", line 687, characters 29-38
msgid "Lessons"
msgstr "Cours"
-#: File "src/app/learnocaml_index_main.ml", line 693, characters 32-44
+#: File "src/app/learnocaml_index_main.ml", line 694, characters 32-44
#: "src/app/learnocaml_playground_main.ml", 77, 23-35
msgid "Playground"
msgstr "Bac-Ă -sable"
-#: File "src/app/learnocaml_index_main.ml", line 696, characters 28-35
+#: File "src/app/learnocaml_index_main.ml", line 697, characters 28-35
msgid "Teach"
msgstr "Enseignement"
-#: File "src/app/learnocaml_index_main.ml", line 794, characters 15-69
+#: File "src/app/learnocaml_index_main.ml", line 795, characters 15-69
msgid "Be sure to write down your token before logging out:"
msgstr "Assurez-vous d'avoir noté votre token :"
-#: File "src/app/learnocaml_index_main.ml", lines 796-798, characters 15-26
+#: File "src/app/learnocaml_index_main.ml", lines 797-799, characters 15-26
msgid ""
"WARNING: the data could not be synchronised with the server. Logging out "
"will lose your local changes, be sure you exported a backup."
@@ -566,40 +570,40 @@ msgstr ""
"En vous déconnectant, vous perdrez tous les changements locaux, à moins "
"d'avoir exporté votre espace de travail au préalable."
-#: File "src/app/learnocaml_index_main.ml", line 800, characters 22-30 45-53
-#: 822, 9-17
+#: File "src/app/learnocaml_index_main.ml", line 801, characters 22-30 45-53
+#: 823, 9-17
msgid "Logout"
msgstr "DĂ©connexion"
-#: File "src/app/learnocaml_index_main.ml", line 813, characters 9-21
+#: File "src/app/learnocaml_index_main.ml", line 814, characters 9-21
msgid "Show token"
msgstr "Afficher le token"
-#: File "src/app/learnocaml_index_main.ml", line 816, characters 9-25
+#: File "src/app/learnocaml_index_main.ml", line 817, characters 9-25
msgid "Sync workspace"
msgstr "Synchroniser"
-#: File "src/app/learnocaml_index_main.ml", line 819, characters 9-25
+#: File "src/app/learnocaml_index_main.ml", line 820, characters 9-25
msgid "Export to file"
msgstr "Exporter vers un fichier"
-#: File "src/app/learnocaml_index_main.ml", line 820, characters 9-17
+#: File "src/app/learnocaml_index_main.ml", line 821, characters 9-17
msgid "Import"
msgstr "Importer"
-#: File "src/app/learnocaml_index_main.ml", line 821, characters 9-36
+#: File "src/app/learnocaml_index_main.ml", line 822, characters 9-36
msgid "Download all source files"
msgstr "Télécharger tous les fichiers sources"
-#: File "src/app/learnocaml_index_main.ml", line 827, characters 38-44
+#: File "src/app/learnocaml_index_main.ml", line 828, characters 38-44
msgid "Menu"
msgstr "Menu"
-#: File "src/app/learnocaml_teacher_tab.ml", line 75, characters 20-35
+#: File "src/app/learnocaml_teacher_tab.ml", line 106, characters 20-35
msgid "TEACHER TOKEN"
msgstr "TOKEN PROF."
-#: File "src/app/learnocaml_teacher_tab.ml", lines 76-77, characters 26-42
+#: File "src/app/learnocaml_teacher_tab.ml", lines 107-108, characters 26-42
msgid ""
"New teacher token created:\n"
"%s\n"
@@ -611,112 +615,365 @@ msgstr ""
"\n"
"Notez-le !"
-#: File "src/app/learnocaml_teacher_tab.ml", line 257, characters 48-54
+#: File "src/app/learnocaml_teacher_tab.ml", line 304, characters 52-58
msgid "Open"
msgstr "Ouvert"
-#: File "src/app/learnocaml_teacher_tab.ml", line 258, characters 52-60
+#: File "src/app/learnocaml_teacher_tab.ml", line 305, characters 56-64
msgid "Closed"
msgstr "Fermé"
-#: File "src/app/learnocaml_teacher_tab.ml", line 259, characters 58-68 260,
-#: 42-52
+#: File "src/app/learnocaml_teacher_tab.ml", line 307, characters 41-51
msgid "Assigned"
msgstr "Devoir"
-#: File "src/app/learnocaml_teacher_tab.ml", line 320, characters 49-61 340,
+#: File "src/app/learnocaml_teacher_tab.ml", line 308, characters 57-68
+msgid "Open/Assg"
+msgstr "Ouvert/Devoir"
+
+#: File "src/app/learnocaml_teacher_tab.ml", line 368, characters 49-61 391,
#: 48-60
msgid "Loading..."
msgstr "Chargement..."
-#: File "src/app/learnocaml_teacher_tab.ml", line 394, characters 17-38
+#: File "src/app/learnocaml_teacher_tab.ml", line 445, characters 17-38
msgid "any future students"
msgstr "tout nouvel Ă©tudiant"
-#: File "src/app/learnocaml_teacher_tab.ml", line 542, characters 18-28
+#: File "src/app/learnocaml_teacher_tab.ml", line 580, characters 21-31
msgid "Students"
msgstr "Ătudiants"
-#: File "src/app/learnocaml_teacher_tab.ml", line 552, characters 20-29
+#: File "src/app/learnocaml_teacher_tab.ml", line 606, characters 20-29
msgid "Sort by"
msgstr "Tri par"
-#: File "src/app/learnocaml_teacher_tab.ml", line 560, characters 22-29
+#: File "src/app/learnocaml_teacher_tab.ml", line 614, characters 22-29
msgid "Token"
msgstr "Token"
-#: File "src/app/learnocaml_teacher_tab.ml", line 562, characters 22-37
+#: File "src/app/learnocaml_teacher_tab.ml", line 616, characters 22-37
msgid "Creation date"
msgstr "Date d'entrée"
-#: File "src/app/learnocaml_teacher_tab.ml", line 564, characters 22-28
+#: File "src/app/learnocaml_teacher_tab.ml", line 618, characters 22-28
msgid "Tags"
msgstr "Tags"
-#: File "src/app/learnocaml_teacher_tab.ml", line 569, characters 46-52
+#: File "src/app/learnocaml_teacher_tab.ml", line 623, characters 46-52
msgid "tags"
msgstr "tags"
-#: File "src/app/learnocaml_teacher_tab.ml", line 645, characters 16-28
+#: File "src/app/learnocaml_teacher_tab.ml", line 699, characters 16-28
msgid "1 exercise"
msgstr "1 exercice"
-#: File "src/app/learnocaml_teacher_tab.ml", line 646, characters 32-46
+#: File "src/app/learnocaml_teacher_tab.ml", line 700, characters 32-46
msgid "%d exercises"
msgstr "%d exercices"
-#: File "src/app/learnocaml_teacher_tab.ml", line 649, characters 23-34
+#: File "src/app/learnocaml_teacher_tab.ml", line 703, characters 23-34
msgid "1 student"
msgstr "1 Ă©tudiant"
-#: File "src/app/learnocaml_teacher_tab.ml", line 650, characters 39-52
+#: File "src/app/learnocaml_teacher_tab.ml", line 704, characters 39-52
msgid "%d students"
msgstr "%d Ă©tudiants"
-#: File "src/app/learnocaml_teacher_tab.ml", line 651, characters 38-52
+#: File "src/app/learnocaml_teacher_tab.ml", line 705, characters 38-52
msgid "%d+ students"
msgstr "%d+ Ă©tudiants"
-#: File "src/app/learnocaml_teacher_tab.ml", line 718, characters 45-61
+#: File "src/app/learnocaml_teacher_tab.ml", line 772, characters 45-61
msgid "New assignment"
msgstr "Nouveau devoir"
-#: File "src/app/learnocaml_teacher_tab.ml", line 821, characters 16-28
+#: File "src/app/learnocaml_teacher_tab.ml", line 873, characters 16-28
msgid "Open/Close"
msgstr "Ouvrir/Fermer"
-#: File "src/app/learnocaml_teacher_tab.ml", line 827, characters 47-64
+#: File "src/app/learnocaml_teacher_tab.ml", line 879, characters 47-64
msgid "required skills"
msgstr "comp. requises"
-#: File "src/app/learnocaml_teacher_tab.ml", line 831, characters 47-63
+#: File "src/app/learnocaml_teacher_tab.ml", line 883, characters 47-63
msgid "trained skills"
msgstr "comp. travaillées"
-#: File "src/app/learnocaml_teacher_tab.ml", line 840, characters 36-49
+#: File "src/app/learnocaml_teacher_tab.ml", line 892, characters 17-30
msgid "Assignments"
msgstr "Devoirs"
-#: File "src/app/learnocaml_teacher_tab.ml", line 923, characters 18-25
+#: File "src/app/learnocaml_teacher_tab.ml", line 982, characters 18-25
msgid "Apply"
msgstr "Appliquer"
-#: File "src/app/learnocaml_teacher_tab.ml", line 924, characters 54-63
+#: File "src/app/learnocaml_teacher_tab.ml", line 983, characters 54-63
msgid "Actions"
msgstr "Actions"
-#: File "src/app/learnocaml_teacher_tab.ml", line 927, characters 23-49
+#: File "src/app/learnocaml_teacher_tab.ml", line 986, characters 23-49
msgid "Create new teacher token"
msgstr "Créer un nouveau token enseignant"
-#: File "src/app/learnocaml_teacher_tab.ml", line 929, characters 23-53
-msgid "Download student data as CSV"
-msgstr "Exporter les données étudiants en CSV"
+#: File "src/app/learnocaml_teacher_tab.ml", line 988, characters 23-81
+msgid "Download the data for selected students/exercises as CSV"
+msgstr "Télécharger un CSV des exercices/étudiants sélectionnés"
-#: File "src/app/learnocaml_teacher_tab.ml", line 1101, characters 55-72
+#: File "src/app/learnocaml_teacher_tab.ml", line 1163, characters 55-72
msgid "Unsaved changes"
msgstr "Modifications non sauvegardées"
+#: File "src/app/learnocaml_teacher_tab_doc.ml", line 11, characters 5-25
+msgid "Handling exercises"
+msgstr "Gestion des exercices"
+
+#: File "src/app/learnocaml_teacher_tab_doc.ml", lines 11-46, characters 31-10
+msgid ""
+"\n"
+"> Don't forget to click \"Apply\" on the bottom-right after any changes\n"
+"\n"
+"- **Filtering exercises**
\n"
+" Use the đ box on top.\n"
+" - type for raw text search on the titles\n"
+" - click again to list the tags and filter based on those\n"
+" - multiple search terms are allowed\n"
+"\n"
+"- **Selecting which exercises can be seen by the students**\n"
+" - select the exercises with a click (the box on top (de)selects all; you "
+"can\n"
+" also click folder titles)\n"
+" - click the [Open/Close] button to toggle\n"
+" - for complex selections, use the filter box then select all\n"
+"\n"
+"- **Defining exercise skill tags**
\n"
+" Those can also be pre-defined in the exercises metadata.\n"
+" 1. Select the exercises to tag\n"
+" 2. Select or type a new tag in one of the boxes on the lower-right of the "
+"pane\n"
+" (left for requirements, right for skills trained by the exercise)\n"
+" 3. Click the corresponding [+] button to add the tag ([-] to remove)\n"
+"\n"
+"- **Checking an exercise contents**\n"
+" - double-click an exercise\n"
+" - it will be opened in a new tab\n"
+"\n"
+"- **Classifying students answers**
\n"
+" As documented\n"
+"[here](https://ocaml-sf.org/learn-ocaml/howto-classify-students-answers."
+"html):\n"
+" - Assume you want to analyze students' definitions of function `foo` in\n"
+" exercise `exo`\n"
+" - Hold Ctrl (on macOS: Hold â) and left click on `exo` in the list of\n"
+" exercises\n"
+" - Enter `foo` in the dialog box\n"
+" - The dissimilarity analysis will be opened in a new tab\n"
+msgstr ""
+"\n"
+"> Don't forget to click \"Apply\" on the bottom-right after any changes\n"
+"\n"
+"- **Filtering exercises**
\n"
+" Use the đ box on top.\n"
+" - type for raw text search on the titles\n"
+" - click again to list the tags and filter based on those\n"
+" - multiple search terms are allowed\n"
+"\n"
+"- **Selecting which exercises can be seen by the students**\n"
+" - select the exercises with a click (the box on top (de)selects all; you can\n"
+" also click folder titles)\n"
+" - click the [Open/Close] button to toggle\n"
+" - for complex selections, use the filter box then select all\n"
+"\n"
+"- **Defining exercise skill tags**
\n"
+" Those can also be pre-defined in the exercises metadata.\n"
+" 1. Select the exercises to tag\n"
+" 2. Select or type a new tag in one of the boxes on the lower-right of the pane\n"
+" (left for requirements, right for skills trained by the exercise)\n"
+" 3. Click the corresponding [+] button to add the tag ([-] to remove)\n"
+"\n"
+"- **Checking an exercise contents**\n"
+" - double-click an exercise\n"
+" - it will be opened in a new tab\n"
+"\n"
+"- **Classifying students answers**
\n"
+" As documented\n"
+"[here](https://ocaml-sf.org/learn-ocaml/howto-classify-students-answers.html):\n"
+" - Assume you want to analyze students' definitions of function `foo` in\n"
+" exercise `exo`\n"
+" - Hold Ctrl (on macOS: Hold â) and left click on `exo` in the list of\n"
+" exercises\n"
+" - Enter `foo` in the dialog box\n"
+" - The dissimilarity analysis will be opened in a new tab\n"
+
+#: File "src/app/learnocaml_teacher_tab_doc.ml", line 49, characters 5-24
+msgid "Handling students"
+msgstr "Gestion des Ă©tudiants"
+
+#: File "src/app/learnocaml_teacher_tab_doc.ml", lines 49-85, characters 30-10
+msgid ""
+"\n"
+"> Don't forget to click \"Apply\" on the bottom-right after any changes\n"
+"\n"
+"- **Filtering students**
\n"
+" Use the đ box on top.\n"
+" - type for raw text search on the nicknames\n"
+" - click again to list the tags and filter based on those\n"
+" - multiple search terms are allowed\n"
+"\n"
+"- **Tagging students**
\n"
+" Tags can be used for groups, level, etc.\n"
+" 1. select the students to tag. To select a group, use the filter box and "
+"the\n"
+" box on top to select all. Use the `Sort by` box on the top right to "
+"easily\n"
+" select new students or untagged studends\n"
+" 2. select or type the tag on the box to the lower-right\n"
+" 3. Click the corresponding [+] button to add the tag ([-] to remove)\n"
+"\n"
+"- **Following a student**\n"
+" - at a glance:\n"
+" a bar on the right of each student shows a short insight of their "
+"progress,\n"
+" - vertical stripes correspond to the exercises, in order\n"
+" - grey is shown for exercises not attempted\n"
+" - the color otherwise varies from green (good) to red (bad) depending on "
+"the\n"
+" grade\n"
+" - in detail:\n"
+" - double-click on the student line to open their track record in a new "
+"tab\n"
+" - there you can see their detailed results per exercise, and check "
+"their\n"
+" answers\n"
+" - see \"Classifying students answers\" in the exercise pane guide for a "
+"way\n"
+" to compare solutions between students\n"
+"\n"
+"- **Downloading the grades**\n"
+" - use the [Actions] menu at the bottom to export a CSV file containing a "
+"line\n"
+" per student, and two columns (grade and date) per exercise\n"
+" - if any exercises or students are selected, the CSV will only contain "
+"those\n"
+" - otherwise everything is exported\n"
+msgstr ""
+"\n"
+"> Don't forget to click \"Apply\" on the bottom-right after any changes\n"
+"\n"
+"- **Filtering students**
\n"
+" Use the đ box on top.\n"
+" - type for raw text search on the nicknames\n"
+" - click again to list the tags and filter based on those\n"
+" - multiple search terms are allowed\n"
+"\n"
+"- **Tagging students**
\n"
+" Tags can be used for groups, level, etc.\n"
+" 1. select the students to tag. To select a group, use the filter box and the\n"
+" box on top to select all. Use the `Sort by` box on the top right to easily\n"
+" select new students or untagged studends\n"
+" 2. select or type the tag on the box to the lower-right\n"
+" 3. Click the corresponding [+] button to add the tag ([-] to remove)\n"
+"\n"
+"- **Following a student**\n"
+" - at a glance:\n"
+" a bar on the right of each student shows a short insight of their progress,\n"
+" - vertical stripes correspond to the exercises, in order\n"
+" - grey is shown for exercises not attempted\n"
+" - the color otherwise varies from green (good) to red (bad) depending on the\n"
+" grade\n"
+" - in detail:\n"
+" - double-click on the student line to open their track record in a new tab\n"
+" - there you can see their detailed results per exercise, and check their\n"
+" answers\n"
+" - see \"Classifying students answers\" in the exercise pane guide for a way\n"
+" to compare solutions between students\n"
+"\n"
+"- **Downloading the grades**\n"
+" - use the [Actions] menu at the bottom to export a CSV file containing a line\n"
+" per student, and two columns (grade and date) per exercise\n"
+" - if any exercises or students are selected, the CSV will only contain those\n"
+" - otherwise everything is exported\n"
+
+#: File "src/app/learnocaml_teacher_tab_doc.ml", line 88, characters 5-27
+msgid "Handling assignments"
+msgstr "Gestion des devoirs"
+
+#: File "src/app/learnocaml_teacher_tab_doc.ml", lines 88-118, characters 33-10
+msgid ""
+"\n"
+"Assignments provide a way to open select exercises to select students for a\n"
+"given period of time. Once due, the students will be restricted to read-"
+"only\n"
+"access.\n"
+"\n"
+"- **Creating an assignment**\n"
+" 1. Select exercises, as above\n"
+" 2. Select students, as above. You can use the \"any future students\" line "
+"to\n"
+" apply to students who haven't yet registered\n"
+" 3. Click the \"New assignment\" button at the bottom\n"
+" 4. Edit the two dates (start and end of the assignment, resp.)\n"
+" 5. Don't forget to [Apply] on the bottom right\n"
+" 6. Click the assignment again to unselect it and quit assignment mode\n"
+"\n"
+"- **Editing an assignment**\n"
+" - select it in the bottom pane\n"
+" - the corresponding exercises and students are automatically selected. "
+"Update\n"
+" the selection as needed\n"
+" - click the assignment again when finished (to quit editing mode), and "
+"don't\n"
+" forget to [Apply]\n"
+"\n"
+"- **Following assignments**\n"
+" - an additional small \"progression\" bar is shown for each assignment in "
+"the\n"
+" students pane\n"
+" - past assignment dates are outlined in red\n"
+" - when selecting students, exercises assigned to any of them will be "
+"greyed\n"
+" out to prevent assigning multiple times the same exercise to the same\n"
+" student\n"
+" - conversely, when selecting exercises, students who already have them\n"
+" assigned are greyed out\n"
+msgstr ""
+"\n"
+"Assignments provide a way to open select exercises to select students for a\n"
+"given period of time. Once due, the students will be restricted to read-"
+"only\n"
+"access.\n"
+"\n"
+"- **Creating an assignment**\n"
+" 1. Select exercises, as above\n"
+" 2. Select students, as above. You can use the \"any future students\" line "
+"to\n"
+" apply to students who haven't yet registered\n"
+" 3. Click the \"New assignment\" button at the bottom\n"
+" 4. Edit the two dates (start and end of the assignment, resp.)\n"
+" 5. Don't forget to [Apply] on the bottom right\n"
+" 6. Click the assignment again to unselect it and quit assignment mode\n"
+"\n"
+"- **Editing an assignment**\n"
+" - select it in the bottom pane\n"
+" - the corresponding exercises and students are automatically selected. "
+"Update\n"
+" the selection as needed\n"
+" - click the assignment again when finished (to quit editing mode), and "
+"don't\n"
+" forget to [Apply]\n"
+"\n"
+"- **Following assignments**\n"
+" - an additional small \"progression\" bar is shown for each assignment in "
+"the\n"
+" students pane\n"
+" - past assignment dates are outlined in red\n"
+" - when selecting students, exercises assigned to any of them will be "
+"greyed\n"
+" out to prevent assigning multiple times the same exercise to the same\n"
+" student\n"
+" - conversely, when selecting exercises, students who already have them\n"
+" assigned are greyed out\n"
+
#: File "src/grader/grading.ml", line 16, characters 28-67
msgid ""
"Exercise definition error %s:\n"
@@ -966,6 +1223,9 @@ msgstr ""
msgid "Unexpected error:\n"
msgstr "Erreur inattendue:\n"
+#~ msgid "Download student data as CSV"
+#~ msgstr "Exporter les données étudiants en CSV"
+
#~ msgid "Fetch from server"
#~ msgstr "Télécharger du serveur"
@@ -1050,11 +1310,11 @@ msgstr "Erreur inattendue:\n"
#~ "icon_upload_white.svg\" class=\"icon\" alt=\"Restaurer\">."
#~ msgid ""
-#~ "Save online using the button above."
+#~ "Save online using the button above."
#~ msgstr ""
-#~ "Sauvez sur le serveur avec le bouton ."
+#~ "Sauvez sur le serveur avec le bouton ."
#~ msgid "Before log out, be sure to save your token"
#~ msgstr "Avant de vous déconnecter, assurez-vous de sauvegarder votre token"
@@ -1088,4 +1348,3 @@ msgstr "Erreur inattendue:\n"
#~ msgid "This session has been closed. You can close this tab."
#~ msgstr "La session a été fermée. Vous pouvez fermer cet onglet."
-