complete /class translation

This commit is contained in:
Thibault Duplessis 2020-02-07 16:41:16 -06:00
parent f67b8cc2ce
commit 27b7675b95
10 changed files with 286 additions and 158 deletions

View file

@ -43,7 +43,7 @@ object topnav {
), ),
a(href := routes.Study.allDefault(1))(trans.studyMenu()), a(href := routes.Study.allDefault(1))(trans.studyMenu()),
ctx.noKid option a(href := routes.Coach.allDefault(1))(trans.coaches()), ctx.noKid option a(href := routes.Coach.allDefault(1))(trans.coaches()),
canSeeClasMenu option a(href := routes.Clas.index)("Classes") canSeeClasMenu option a(href := routes.Clas.index)(trans.clas.lichessClasses())
) )
), ),
st.section( st.section(

View file

@ -21,7 +21,9 @@ private object bits {
if (isGranted(_.Teacher)) if (isGranted(_.Teacher))
main(cls := "page-menu")( main(cls := "page-menu")(
st.nav(cls := "page-menu__menu subnav")( st.nav(cls := "page-menu__menu subnav")(
a(cls := active.toOption.map(_.active("classes")), href := routes.Clas.index)("Lichess Classes"), a(cls := active.toOption.map(_.active("classes")), href := routes.Clas.index)(
trans.clas.lichessClasses()
),
active.left.toOption.map { clas => active.left.toOption.map { clas =>
frag( frag(
a(cls := "active", href := routes.Clas.show(clas.clas.id.value))(clas.clas.name), a(cls := "active", href := routes.Clas.show(clas.clas.id.value))(clas.clas.name),
@ -36,7 +38,9 @@ private object bits {
} }
) )
} | { } | {
a(cls := active.toOption.map(_.active("newClass")), href := routes.Clas.form)("New class") a(cls := active.toOption.map(_.active("newClass")), href := routes.Clas.form)(
trans.clas.newClass()
)
} }
), ),
div(cls := "page-menu__content box")(body) div(cls := "page-menu__content box")(body)
@ -44,13 +48,8 @@ private object bits {
else main(cls := "page-small box")(body) else main(cls := "page-small box")(body)
) )
def showArchived(archived: Clas.Recorded) = def showArchived(archived: Clas.Recorded)(implicit ctx: Context) =
div( div(trans.clas.closedByXAtY(userIdLink(archived.by.value.some), momentFromNowOnce(archived.at)))
"Archived by ",
userIdLink(archived.by.value.some),
" ",
momentFromNowOnce(archived.at)
)
val sortNumberTh = th(attr("data-sort-method") := "number") val sortNumberTh = th(attr("data-sort-method") := "number")
val dataSort = attr("data-sort") val dataSort = attr("data-sort")

View file

@ -40,27 +40,27 @@ object clas {
} }
def teacherIndex(classes: List[Clas])(implicit ctx: Context) = def teacherIndex(classes: List[Clas])(implicit ctx: Context) =
bits.layout("Lichess Classes", Right("classes"))( bits.layout(trans.clas.lichessClasses.txt(), Right("classes"))(
cls := "clas-index", cls := "clas-index",
div(cls := "box__top")( div(cls := "box__top")(
h1(trans.clas.lichessClasses()), h1(trans.clas.lichessClasses()),
a( a(
href := routes.Clas.form, href := routes.Clas.form,
cls := "new button button-empty", cls := "new button button-empty",
title := "New Class", title := trans.clas.newClass.txt(),
dataIcon := "O" dataIcon := "O"
) )
), ),
if (classes.isEmpty) if (classes.isEmpty)
frag(hr, p(cls := "box__pad classes__empty")("No classes yet.")) frag(hr, p(cls := "box__pad classes__empty")(trans.clas.noClassesYet()))
else else
renderClasses(classes) renderClasses(classes)
) )
def studentIndex(classes: List[Clas])(implicit ctx: Context) = def studentIndex(classes: List[Clas])(implicit ctx: Context) =
bits.layout("Lichess Classes", Right("classes"))( bits.layout(trans.clas.lichessClasses.txt(), Right("classes"))(
cls := "clas-index", cls := "clas-index",
div(cls := "box__top")(h1("Lichess Classes")), div(cls := "box__top")(h1(trans.clas.lichessClasses())),
renderClasses(classes) renderClasses(classes)
) )
@ -80,16 +80,17 @@ object clas {
} }
) )
def teachers(clas: Clas) = def teachers(clas: Clas)(implicit ctx: Context) =
div(cls := "clas-teachers")( div(cls := "clas-teachers")(
"Teachers: ", trans.clas.teachersX(
fragList(clas.teachers.toList.map(t => userIdLink(t.value.some))) fragList(clas.teachers.toList.map(t => userIdLink(t.value.some)))
)
) )
def create(form: Form[ClasData])(implicit ctx: Context) = def create(form: Form[ClasData])(implicit ctx: Context) =
bits.layout("New class", Right("newClass"))( bits.layout(trans.clas.newClass.txt(), Right("newClass"))(
cls := "box-pad", cls := "box-pad",
h1("New class"), h1(trans.clas.newClass()),
innerForm(form, none) innerForm(form, none)
) )
@ -102,9 +103,8 @@ object clas {
action := routes.Clas.archive(c.id.value, true), action := routes.Clas.archive(c.id.value, true),
cls := "clas-edit__archive" cls := "clas-edit__archive"
)( )(
form3.submit("Archive", icon = none)( form3.submit(trans.clas.closeClass(), icon = none)(
cls := "confirm button-red button-empty", cls := "confirm button-red button-empty"
title := "Disband the class"
) )
) )
) )
@ -114,15 +114,15 @@ object clas {
teacherDashboard.layout(c, students, "wall")( teacherDashboard.layout(c, students, "wall")(
div(cls := "box-pad clas-wall__edit")( div(cls := "box-pad clas-wall__edit")(
p( p(
strong("Send a message to all students."), strong(trans.clas.sendAMessage()),
br, br,
"A link to the class will be automatically added at the end of the message, so you don't need to include it yourself." trans.clas.aLinkToTheClassWillBeAdded()
), ),
postForm(cls := "form3", action := routes.Clas.notifyPost(c.id.value))( postForm(cls := "form3", action := routes.Clas.notifyPost(c.id.value))(
form3.globalError(form), form3.globalError(form),
form3.group( form3.group(
form("text"), form("text"),
frag("Message") frag(trans.message())
)(form3.textarea(_)(rows := 3)), )(form3.textarea(_)(rows := 3)),
form3.actions( form3.actions(
a(href := routes.Clas.wall(c.id.value))(trans.cancel()), a(href := routes.Clas.wall(c.id.value))(trans.cancel()),
@ -135,24 +135,22 @@ object clas {
private def innerForm(form: Form[ClasData], clas: Option[Clas])(implicit ctx: Context) = private def innerForm(form: Form[ClasData], clas: Option[Clas])(implicit ctx: Context) =
postForm(cls := "form3", action := clas.fold(routes.Clas.create())(c => routes.Clas.update(c.id.value)))( postForm(cls := "form3", action := clas.fold(routes.Clas.create())(c => routes.Clas.update(c.id.value)))(
form3.globalError(form), form3.globalError(form),
form3.group(form("name"), frag("Class name"))(form3.input(_)(autofocus)), form3.group(form("name"), trans.clas.className())(form3.input(_)(autofocus)),
form3.group( form3.group(
form("desc"), form("desc"),
frag("Class description"), frag(trans.clas.classDescription()),
help = frag("Visible by both teachers and students of the class").some help = trans.clas.visibleByBothStudentsAndTeachers().some
)(form3.textarea(_)(rows := 5)), )(form3.textarea(_)(rows := 5)),
clas match { clas match {
case None => form3.hidden(form("teachers"), ctx.userId) case None => form3.hidden(form("teachers"), ctx.userId)
case Some(_) => case Some(_) =>
form3.group( form3.group(
form("teachers"), form("teachers"),
frag("Teachers of the class"), trans.clas.teachersOfTheClass(),
help = frag( help = frag(
"Add Lichess usernames to invite them as teachers. One per line.", trans.clas.addLichessUsernames(),
br, br,
"All teachers must be ", trans.clas.theyMustFirstApply()
a(href := routes.Clas.verifyTeacher)("vetted by Lichess"),
" before being invited."
).some ).some
)(form3.textarea(_)(rows := 4)) )(form3.textarea(_)(rows := 4))
}, },

View file

@ -25,10 +25,8 @@ object student {
ctx.flash("password").map { password => ctx.flash("password").map { password =>
flashMessage(cls := "student-show__password")( flashMessage(cls := "student-show__password")(
div( div(
p( p(trans.clas.makeSureToCopy()),
"Make sure to copy or write down the password now. You wont be able to see it again!" pre(trans.clas.passwordX(password))
),
pre(s"""Password: $password""")
) )
) )
}, },
@ -36,36 +34,33 @@ object student {
div(cls := "student-show__archived archived")( div(cls := "student-show__archived archived")(
bits.showArchived(archived), bits.showArchived(archived),
postForm(action := routes.Clas.studentArchive(clas.id.value, s.user.username, false))( postForm(action := routes.Clas.studentArchive(clas.id.value, s.user.username, false))(
form3.submit("Restore", icon = none)( form3.submit(trans.clas.inviteTheStudentBack(), icon = none)(cls := "confirm button-empty")
cls := "confirm button-empty",
title := "Get the student back into the class"
)
) )
) )
}, },
s.student.notes.nonEmpty option div(cls := "student-show__notes")(richText(s.student.notes)), s.student.notes.nonEmpty option div(cls := "student-show__notes")(richText(s.student.notes)),
s.student.managed option div(cls := "student-show__managed")( s.student.managed option div(cls := "student-show__managed")(
p("This student account is managed"), p(trans.clas.thisStudentAccountIsManaged()),
div(cls := "student-show__managed__actions")( div(cls := "student-show__managed__actions")(
postForm(action := routes.Clas.studentSetKid(clas.id.value, s.user.username, !s.user.kid))( postForm(action := routes.Clas.studentSetKid(clas.id.value, s.user.username, !s.user.kid))(
form3.submit(if (s.user.kid) "Disable kid mode" else "Enable kid mode", icon = none)( form3.submit(if (s.user.kid) trans.disableKidMode() else trans.enableKidMode(), icon = none)(
s.student.isArchived option disabled, s.student.isArchived option disabled,
cls := List("confirm button button-empty" -> true, "disabled" -> s.student.isArchived), cls := List("confirm button button-empty" -> true, "disabled" -> s.student.isArchived),
title := "Kid mode prevents the student from communicating with Lichess players" title := trans.kidModeExplanation.txt()
) )
), ),
postForm(action := routes.Clas.studentResetPassword(clas.id.value, s.user.username))( postForm(action := routes.Clas.studentResetPassword(clas.id.value, s.user.username))(
form3.submit("Reset password", icon = none)( form3.submit(trans.clas.resetPassword(), icon = none)(
s.student.isArchived option disabled, s.student.isArchived option disabled,
cls := List("confirm button button-empty" -> true, "disabled" -> s.student.isArchived), cls := List("confirm button button-empty" -> true, "disabled" -> s.student.isArchived),
title := "Generate a new password for the student" title := trans.clas.generateANewPassword.txt()
) )
), ),
a( a(
href := routes.Clas.studentRelease(clas.id.value, s.user.username), href := routes.Clas.studentRelease(clas.id.value, s.user.username),
cls := "button button-empty", cls := "button button-empty",
title := "Upgrade from managed to autonomous" title := trans.clas.upgradeFromManaged.txt()
)("Release") )(trans.clas.release())
) )
), ),
views.html.activity(s.user, activities) views.html.activity(s.user, activities)
@ -82,11 +77,10 @@ object student {
), ),
div(cls := "student-show__top__meta")( div(cls := "student-show__top__meta")(
p( p(
"Invited to ", trans.clas.invitedToXByY(
a(href := routes.Clas.show(clas.id.value))(clas.name), a(href := routes.Clas.show(clas.id.value))(clas.name),
" by ", userIdLink(s.student.created.by.value.some, withOnline = false)
userIdLink(s.student.created.by.value.some, withOnline = false), ),
" ",
momentFromNowOnce(s.student.created.at) momentFromNowOnce(s.student.created.at)
), ),
div( div(
@ -97,11 +91,10 @@ object student {
a( a(
href := routes.Clas.studentEdit(clas.id.value, s.user.username), href := routes.Clas.studentEdit(clas.id.value, s.user.username),
cls := "button button-empty" cls := "button button-empty"
)("Edit"), )(trans.edit()),
a( a(
href := routes.User.show(s.user.username), href := routes.User.show(s.user.username),
cls := "button button-empty", cls := "button button-empty"
title := "View full Lichess profile"
)(trans.profile()) )(trans.profile())
) )
) )
@ -110,8 +103,8 @@ object student {
private def realNameField(form: Form[_], fieldName: String = "realName")(implicit ctx: Context) = private def realNameField(form: Form[_], fieldName: String = "realName")(implicit ctx: Context) =
form3.group( form3.group(
form(fieldName), form(fieldName),
frag("Real name"), trans.clas.realName(),
help = frag("Private. Will never be shown to anyone else. Helps you remember who that student is.").some help = trans.clas.privateWillNeverBeShown().some
)(form3.input(_)) )(form3.input(_))
def form( def form(
@ -121,45 +114,41 @@ object student {
create: Form[_], create: Form[_],
created: Option[lila.clas.Student.WithPassword] = none created: Option[lila.clas.Student.WithPassword] = none
)(implicit ctx: Context) = )(implicit ctx: Context) =
bits.layout("Add student", Left(c withStudents students))( bits.layout(trans.clas.addStudent.txt(), Left(c withStudents students))(
cls := "box-pad student-add", cls := "box-pad student-add",
h1("Add student"), h1(trans.clas.addStudent()),
p( p(
"To ",
a(href := routes.Clas.show(c.id.value))(c.name) a(href := routes.Clas.show(c.id.value))(c.name)
), ),
created map { created map {
case Student.WithPassword(student, password) => case Student.WithPassword(student, password) =>
flashMessage(cls := "student-add__created")( flashMessage(cls := "student-add__created")(
strong( strong(
"Lichess profile ", trans.clas.lichessProfileXCreatedForY(
userIdLink(student.userId.some, withOnline = false), userIdLink(student.userId.some, withOnline = false),
" created for ", student.realName
student.realName, ),
"." p(trans.clas.makeSureToCopy()),
), pre(
p( trans.clas.studentCredentials(student.realName, usernameOrId(student.userId), password.value)
"Make sure to copy or write down the password now. You wont be able to see it again!" )
), )
pre(s"""Student: ${student.realName}
Username: ${usernameOrId(student.userId)}
Password: ${password.value}""")
) )
}, },
standardFlash(), standardFlash(),
div(cls := "student-add__choice")( div(cls := "student-add__choice")(
div(cls := "info")( div(cls := "info")(
h2("Invite a Lichess account"), h2(trans.clas.inviteALichessAccount()),
p("If the student already has a Lichess account, you can invite them to the class."), p(trans.clas.inviteDesc1()),
p("They will receive a message on Lichess with a link to join the class."), p(trans.clas.inviteDesc2()),
p( p(
strong("Important: only invite students you know, and who actively want to join the class."), strong(trans.clas.inviteDesc3()),
br, br,
"Never send unsolicited invites to arbitrary players." trans.clas.inviteDesc4()
) )
), ),
postForm(cls := "form3", action := routes.Clas.studentInvite(c.id.value))( postForm(cls := "form3", action := routes.Clas.studentInvite(c.id.value))(
form3.group(invite("username"), frag("Lichess username"))( form3.group(invite("username"), trans.clas.lichessUsername())(
form3.input(_, klass = "user-autocomplete")(created.isEmpty option autofocus)(dataTag := "span") form3.input(_, klass = "user-autocomplete")(created.isEmpty option autofocus)(dataTag := "span")
), ),
realNameField(invite), realNameField(invite),
@ -169,24 +158,23 @@ Password: ${password.value}""")
div(cls := "student-add__or")("~ or ~"), div(cls := "student-add__or")("~ or ~"),
div(cls := "student-add__choice")( div(cls := "student-add__choice")(
div(cls := "info")( div(cls := "info")(
h2("Create a new Lichess account"), h2(trans.clas.createANewLichessAccount()),
p("If the student doesn't have a Lichess account yet, you can create one for them here."), p(trans.clas.createDesc1()),
p( p(
"No email address is required. A password will be generated, ", trans.clas.createDesc2()
"and you will have to transmit it to the student, so they can log in."
), ),
p( p(
strong("Important: a student must not have multiple accounts."), strong(trans.clas.createDesc3()),
br, br,
"If they already have one, use the invite form instead." trans.clas.createDesc4()
) )
), ),
postForm(cls := "form3", action := routes.Clas.studentCreate(c.id.value))( postForm(cls := "form3", action := routes.Clas.studentCreate(c.id.value))(
form3.group( form3.group(
create("create-username"), create("create-username"),
frag("Lichess username"), trans.clas.lichessUsername(),
help = a(cls := "name-regen", href := s"${routes.Clas.studentForm(c.id.value)}?gen=1")( help = a(cls := "name-regen", href := s"${routes.Clas.studentForm(c.id.value)}?gen=1")(
"Generate a new username" trans.clas.generateANewUsername()
).some ).some
)( )(
form3.input(_)(created.isDefined option autofocus) form3.input(_)(created.isDefined option autofocus)
@ -206,7 +194,7 @@ Password: ${password.value}""")
postForm(cls := "form3", action := routes.Clas.studentUpdate(clas.id.value, s.user.username))( postForm(cls := "form3", action := routes.Clas.studentUpdate(clas.id.value, s.user.username))(
form3.globalError(form), form3.globalError(form),
realNameField(form), realNameField(form),
form3.group(form("notes"), raw("Notes"), help = frag("Only visible to the class teachers").some)( form3.group(form("notes"), trans.notes(), help = trans.clas.onlyVisibleToTeachers().some)(
form3.textarea(_)(autofocus, rows := 15) form3.textarea(_)(autofocus, rows := 15)
), ),
form3.actions( form3.actions(
@ -220,9 +208,8 @@ Password: ${password.value}""")
action := routes.Clas.studentArchive(clas.id.value, s.user.username, true), action := routes.Clas.studentArchive(clas.id.value, s.user.username, true),
cls := "student-show__archive" cls := "student-show__archive"
)( )(
form3.submit("Archive", icon = none)( form3.submit(trans.clas.removeStudent(), icon = none)(
cls := "confirm button-red button-empty", cls := "confirm button-red button-empty"
title := "Remove the student from the class"
) )
) )
) )
@ -236,20 +223,18 @@ Password: ${password.value}""")
cls := "student-show student-edit", cls := "student-show student-edit",
top(clas, s), top(clas, s),
div(cls := "box__pad")( div(cls := "box__pad")(
h2("Release the account so the student can manage it in autonomy."), h2(trans.clas.releaseTheAccount()),
p( p(
"A released account cannot be made managed again. The student will be able to toggle kid mode and reset paswword themselves.", trans.clas.releaseDesc1(),
br, br,
"The student will remain in the class after their account is released." trans.clas.releaseDesc2()
), ),
postForm(cls := "form3", action := routes.Clas.studentReleasePost(clas.id.value, s.user.username))( postForm(cls := "form3", action := routes.Clas.studentReleasePost(clas.id.value, s.user.username))(
form3.globalError(form), form3.globalError(form),
form3.group( form3.group(
form("email"), form("email"),
trans.email(), trans.email(),
help = frag( help = trans.clas.realUniqueEmail().some
"Real, unique email address of the student. We will send a confirmation email to it, with a link to release the account."
).some
)(form3.input(_, typ = "email")(autofocus, required)), )(form3.input(_, typ = "email")(autofocus, required)),
form3.actions( form3.actions(
a(href := routes.Clas.studentShow(clas.id.value, s.user.username))(trans.cancel()), a(href := routes.Clas.studentShow(clas.id.value, s.user.username))(trans.cancel()),

View file

@ -31,7 +31,7 @@ object studentDashboard {
table(cls := "slist slist-pad teachers")( table(cls := "slist slist-pad teachers")(
thead( thead(
tr( tr(
th("Teachers"), th(trans.clas.nbTeachers(teachers.size)),
th, th,
th th
) )
@ -68,10 +68,10 @@ object studentDashboard {
table(cls := "slist slist-pad sortable")( table(cls := "slist slist-pad sortable")(
thead( thead(
tr( tr(
th(attr("data-sort-default") := "1")("Students"), th(attr("data-sort-default") := "1")(trans.clas.nbStudents(students.size)),
sortNumberTh("Rating"), sortNumberTh(trans.rating()),
sortNumberTh("Games"), sortNumberTh(trans.games()),
sortNumberTh("Puzzles"), sortNumberTh(trans.puzzles()),
th th
) )
), ),

View file

@ -27,11 +27,11 @@ object teacherDashboard {
a( a(
cls := active.active("progress"), cls := active.active("progress"),
href := routes.Clas.progress(c.id.value, PerfType.Blitz.key, 7) href := routes.Clas.progress(c.id.value, PerfType.Blitz.key, 7)
)( )(trans.clas.progress()),
"Progress" a(cls := active.active("edit"), href := routes.Clas.edit(c.id.value))(trans.edit()),
), a(cls := active.active("archived"), href := routes.Clas.archived(c.id.value))(
a(cls := active.active("edit"), href := routes.Clas.edit(c.id.value))("Edit"), trans.clas.removedStudents()
a(cls := active.active("archived"), href := routes.Clas.archived(c.id.value))("Archived") )
) )
), ),
standardFlash(), standardFlash(),
@ -39,10 +39,7 @@ object teacherDashboard {
div(cls := "clas-show__archived archived")( div(cls := "clas-show__archived archived")(
bits.showArchived(archived), bits.showArchived(archived),
postForm(action := routes.Clas.archive(c.id.value, false))( postForm(action := routes.Clas.archive(c.id.value, false))(
form3.submit("Restore", icon = none)( form3.submit(trans.clas.reopen(), icon = none)(cls := "confirm button-empty")
cls := "confirm button-empty",
title := "Revive the class"
)
) )
) )
}, },
@ -62,11 +59,11 @@ object teacherDashboard {
href := routes.Clas.studentForm(c.id.value), href := routes.Clas.studentForm(c.id.value),
cls := "button button-clas text", cls := "button button-clas text",
dataIcon := "O" dataIcon := "O"
)("Add student") )(trans.clas.addStudent())
) )
), ),
if (students.isEmpty) if (students.isEmpty)
p(cls := "box__pad students__empty")("No students in the class, yet.") p(cls := "box__pad students__empty")(trans.clas.noStudents())
else else
studentList(c, students) studentList(c, students)
) )
@ -78,7 +75,7 @@ object teacherDashboard {
layout(c, students.filter(_.student.isActive), "archived") { layout(c, students.filter(_.student.isActive), "archived") {
val archived = students.filter(_.student.isArchived) val archived = students.filter(_.student.isArchived)
if (archived.isEmpty) if (archived.isEmpty)
p(cls := "box__pad students__empty")("No archived students.") p(cls := "box__pad students__empty")(trans.clas.noStudents())
else else
studentList(c, archived) studentList(c, archived)
} }
@ -91,7 +88,7 @@ object teacherDashboard {
layout(c, students, "progress")( layout(c, students, "progress")(
div(cls := "progress")( div(cls := "progress")(
div(cls := "progress-perf")( div(cls := "progress-perf")(
label("Variant"), label(trans.variant()),
div(cls := "progress-choices")( div(cls := "progress-choices")(
List( List(
PerfType.Bullet, PerfType.Bullet,
@ -109,7 +106,7 @@ object teacherDashboard {
) )
), ),
div(cls := "progress-days")( div(cls := "progress-days")(
label("Over days"), label(trans.clas.overDays()),
div(cls := "progress-choices")( div(cls := "progress-choices")(
List(1, 2, 3, 7, 10, 14, 21, 30, 60, 90).map { days => List(1, 2, 3, 7, 10, 14, 21, 30, 60, 90).map { days =>
a( a(
@ -125,30 +122,32 @@ object teacherDashboard {
thead( thead(
tr( tr(
th(attr("data-sort-default") := "1")( th(attr("data-sort-default") := "1")(
s"${progress.perfType.name} over last ${progress.days} days" trans.clas.variantXOverLastY(progress.perfType.name, trans.nbDays.txt(progress.days)),
), sortNumberTh(trans.rating()),
sortNumberTh("Rating"), sortNumberTh(trans.clas.progress()),
sortNumberTh("Progress"), sortNumberTh(if (progress.isPuzzle) trans.puzzles() else trans.games()),
sortNumberTh(if (progress.isPuzzle) "Puzzles" else "Games"), if (progress.isPuzzle) sortNumberTh(trans.clas.winrate())
if (progress.isPuzzle) sortNumberTh("Winrate") else sortNumberTh(trans.clas.timePlaying())
else sortNumberTh("Time playing") )
),
tbody(
students.sortBy(_.user.username).map {
case s @ Student.WithUser(_, user) =>
val prog = progress(user)
tr(
studentTd(c, s),
td(dataSort := user.perfs(progress.perfType).intRating, cls := "rating")(
user.perfs(progress.perfType).showRatingProvisional
),
td(dataSort := prog.ratingProgress)(
ratingProgress(prog.ratingProgress) | trans.clas.na.txt()
),
td(prog.nb),
if (progress.isPuzzle) td(dataSort := prog.winRate)(prog.winRate, "%")
else td(dataSort := prog.millis)(showPeriod(prog.period))
)
}
) )
),
tbody(
students.sortBy(_.user.username).map {
case s @ Student.WithUser(_, user) =>
val prog = progress(user)
tr(
studentTd(c, s),
td(dataSort := user.perfs(progress.perfType).intRating, cls := "rating")(
user.perfs(progress.perfType).showRatingProvisional
),
td(dataSort := prog.ratingProgress)(ratingProgress(prog.ratingProgress) | "N/A"),
td(prog.nb),
if (progress.isPuzzle) td(dataSort := prog.winRate)(prog.winRate, "%")
else td(dataSort := prog.millis)(showPeriod(prog.period))
)
}
) )
) )
) )
@ -159,12 +158,12 @@ object teacherDashboard {
table(cls := "slist slist-pad sortable")( table(cls := "slist slist-pad sortable")(
thead( thead(
tr( tr(
th(attr("data-sort-default") := "1")("Student"), th(attr("data-sort-default") := "1")(trans.clas.nbStudents(students.size)),
sortNumberTh("Rating"), sortNumberTh(trans.rating()),
sortNumberTh("Games"), sortNumberTh(trans.games()),
sortNumberTh("Puzzles"), sortNumberTh(trans.puzzles()),
sortNumberTh("Active"), sortNumberTh(trans.clas.lastActiveDate()),
th(iconTag("5")(title := "Managed")) th(iconTag("5")(title := trans.clas.managed.txt()))
) )
), ),
tbody( tbody(
@ -178,7 +177,7 @@ object teacherDashboard {
td(user.count.game.localize), td(user.count.game.localize),
td(user.perfs.puzzle.nb), td(user.perfs.puzzle.nb),
td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce)), td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce)),
td(student.managed option iconTag("5")(title := "Managed")) td(student.managed option iconTag("5")(title := trans.clas.managed.txt()))
) )
} }
) )

View file

@ -18,14 +18,14 @@ object wall {
teacherDashboard.layout(c, students.filter(_.student.isActive), "wall")( teacherDashboard.layout(c, students.filter(_.student.isActive), "wall")(
div(cls := "clas-wall__actions")( div(cls := "clas-wall__actions")(
a(dataIcon := "m", href := routes.Clas.wallEdit(c.id.value), cls := "button button-clas text")( a(dataIcon := "m", href := routes.Clas.wallEdit(c.id.value), cls := "button button-clas text")(
"Edit news" trans.clas.editNews()
), ),
a(dataIcon := "e", href := routes.Clas.notifyStudents(c.id.value), cls := "button button-clas text")( a(dataIcon := "e", href := routes.Clas.notifyStudents(c.id.value), cls := "button button-clas text")(
"Notify all students" trans.clas.notifyAllStudents()
) )
), ),
if (c.wall.isEmpty) if (c.wall.isEmpty)
div(cls := "box__pad clas-wall clas-wall--empty")("Nothing here, yet.") div(cls := "box__pad clas-wall clas-wall--empty")(trans.clas.nothingHere())
else else
div(cls := "box__pad clas-wall")(html) div(cls := "box__pad clas-wall")(html)
) )
@ -38,15 +38,16 @@ object wall {
teacherDashboard.layout(c, students, "wall")( teacherDashboard.layout(c, students, "wall")(
div(cls := "box-pad clas-wall__edit")( div(cls := "box-pad clas-wall__edit")(
p( p(
strong("All class news in a single field."), strong(trans.clas.newsEdit1()),
ul( ul(
li("Add the recent news at the top. Don't delete previous news."), li(trans.clas.newsEdit2()),
li("Separate news with --- it will display a horizontal line."), li(trans.clas.newsEdit3()),
li( li(
a(href := "https://guides.github.com/features/mastering-markdown/", target := "_blank")( trans.clas.markdownAvailable(
"Markdown" a(href := "https://guides.github.com/features/mastering-markdown/", target := "_blank")(
), "Markdown"
" is available for more advanced syntax." )
)
) )
) )
), ),
@ -54,7 +55,7 @@ object wall {
form3.globalError(form), form3.globalError(form),
form3.group( form3.group(
form("wall"), form("wall"),
frag("Class news") trans.clas.news()
)(form3.textarea(_)(rows := 20)), )(form3.textarea(_)(rows := 20)),
form3.actions( form3.actions(
a(href := routes.Clas.wall(c.id.value))(trans.cancel()), a(href := routes.Clas.wall(c.id.value))(trans.cancel()),

View file

@ -756,6 +756,7 @@ val `agreementNice` = new Translated("agreementNice", Site)
val `agreementAccount` = new Translated("agreementAccount", Site) val `agreementAccount` = new Translated("agreementAccount", Site)
val `agreementPolicy` = new Translated("agreementPolicy", Site) val `agreementPolicy` = new Translated("agreementPolicy", Site)
val `searchOrStartNewDiscussion` = new Translated("searchOrStartNewDiscussion", Site) val `searchOrStartNewDiscussion` = new Translated("searchOrStartNewDiscussion", Site)
val `edit` = new Translated("edit", Site)
val `opponentLeftCounter` = new Translated("opponentLeftCounter", Site) val `opponentLeftCounter` = new Translated("opponentLeftCounter", Site)
val `mateInXHalfMoves` = new Translated("mateInXHalfMoves", Site) val `mateInXHalfMoves` = new Translated("mateInXHalfMoves", Site)
val `nextCaptureOrPawnMoveInXHalfMoves` = new Translated("nextCaptureOrPawnMoveInXHalfMoves", Site) val `nextCaptureOrPawnMoveInXHalfMoves` = new Translated("nextCaptureOrPawnMoveInXHalfMoves", Site)
@ -1167,6 +1168,73 @@ val `trackStudentProgress` = new Translated("trackStudentProgress", Clas)
val `messageAllStudents` = new Translated("messageAllStudents", Clas) val `messageAllStudents` = new Translated("messageAllStudents", Clas)
val `freeForAllForever` = new Translated("freeForAllForever", Clas) val `freeForAllForever` = new Translated("freeForAllForever", Clas)
val `applyToBeLichessTeacher` = new Translated("applyToBeLichessTeacher", Clas) val `applyToBeLichessTeacher` = new Translated("applyToBeLichessTeacher", Clas)
val `noClassesYet` = new Translated("noClassesYet", Clas)
val `teachersX` = new Translated("teachersX", Clas)
val `newClass` = new Translated("newClass", Clas)
val `closeClass` = new Translated("closeClass", Clas)
val `closedByXAtY` = new Translated("closedByXAtY", Clas)
val `reopen` = new Translated("reopen", Clas)
val `removeStudent` = new Translated("removeStudent", Clas)
val `removedStudents` = new Translated("removedStudents", Clas)
val `inviteTheStudentBack` = new Translated("inviteTheStudentBack", Clas)
val `sendAMessage` = new Translated("sendAMessage", Clas)
val `aLinkToTheClassWillBeAdded` = new Translated("aLinkToTheClassWillBeAdded", Clas)
val `className` = new Translated("className", Clas)
val `classDescription` = new Translated("classDescription", Clas)
val `visibleByBothStudentsAndTeachers` = new Translated("visibleByBothStudentsAndTeachers", Clas)
val `teachersOfTheClass` = new Translated("teachersOfTheClass", Clas)
val `addLichessUsernames` = new Translated("addLichessUsernames", Clas)
val `theyMustFirstApply` = new Translated("theyMustFirstApply", Clas)
val `resetPassword` = new Translated("resetPassword", Clas)
val `makeSureToCopy` = new Translated("makeSureToCopy", Clas)
val `passwordX` = new Translated("passwordX", Clas)
val `generateANewPassword` = new Translated("generateANewPassword", Clas)
val `invitedToXByY` = new Translated("invitedToXByY", Clas)
val `realName` = new Translated("realName", Clas)
val `privateWillNeverBeShown` = new Translated("privateWillNeverBeShown", Clas)
val `addStudent` = new Translated("addStudent", Clas)
val `lichessProfileXCreatedForY` = new Translated("lichessProfileXCreatedForY", Clas)
val `studentCredentials` = new Translated("studentCredentials", Clas)
val `inviteALichessAccount` = new Translated("inviteALichessAccount", Clas)
val `inviteDesc1` = new Translated("inviteDesc1", Clas)
val `inviteDesc2` = new Translated("inviteDesc2", Clas)
val `inviteDesc3` = new Translated("inviteDesc3", Clas)
val `inviteDesc4` = new Translated("inviteDesc4", Clas)
val `createANewLichessAccount` = new Translated("createANewLichessAccount", Clas)
val `createDesc1` = new Translated("createDesc1", Clas)
val `createDesc2` = new Translated("createDesc2", Clas)
val `createDesc3` = new Translated("createDesc3", Clas)
val `createDesc4` = new Translated("createDesc4", Clas)
val `lichessUsername` = new Translated("lichessUsername", Clas)
val `generateANewUsername` = new Translated("generateANewUsername", Clas)
val `onlyVisibleToTeachers` = new Translated("onlyVisibleToTeachers", Clas)
val `lastActiveDate` = new Translated("lastActiveDate", Clas)
val `managed` = new Translated("managed", Clas)
val `thisStudentAccountIsManaged` = new Translated("thisStudentAccountIsManaged", Clas)
val `upgradeFromManaged` = new Translated("upgradeFromManaged", Clas)
val `release` = new Translated("release", Clas)
val `releaseTheAccount` = new Translated("releaseTheAccount", Clas)
val `releaseDesc1` = new Translated("releaseDesc1", Clas)
val `releaseDesc2` = new Translated("releaseDesc2", Clas)
val `realUniqueEmail` = new Translated("realUniqueEmail", Clas)
val `teachers` = new Translated("teachers", Clas)
val `progress` = new Translated("progress", Clas)
val `noStudents` = new Translated("noStudents", Clas)
val `overDays` = new Translated("overDays", Clas)
val `timePlaying` = new Translated("timePlaying", Clas)
val `variantXOverLastY` = new Translated("variantXOverLastY", Clas)
val `winrate` = new Translated("winrate", Clas)
val `na` = new Translated("na", Clas)
val `news` = new Translated("news", Clas)
val `editNews` = new Translated("editNews", Clas)
val `notifyAllStudents` = new Translated("notifyAllStudents", Clas)
val `nothingHere` = new Translated("nothingHere", Clas)
val `newsEdit1` = new Translated("newsEdit1", Clas)
val `newsEdit2` = new Translated("newsEdit2", Clas)
val `newsEdit3` = new Translated("newsEdit3", Clas)
val `markdownAvailable` = new Translated("markdownAvailable", Clas)
val `nbTeachers` = new Translated("nbTeachers", Clas)
val `nbStudents` = new Translated("nbStudents", Clas)
} }
} }

View file

@ -8,4 +8,81 @@
<string name="messageAllStudents">Message all students about new class material</string> <string name="messageAllStudents">Message all students about new class material</string>
<string name="freeForAllForever">100% free for all, forever, with no ads or trackers</string> <string name="freeForAllForever">100% free for all, forever, with no ads or trackers</string>
<string name="applyToBeLichessTeacher">Apply to be a Lichess Teacher</string> <string name="applyToBeLichessTeacher">Apply to be a Lichess Teacher</string>
<string name="noClassesYet">No classes yet.</string>
<string name="teachersX">Teachers: %s</string>
<string name="newClass">New class</string>
<string name="closeClass">Close class</string>
<string name="closedByXAtY">Closed by %1$s %2$s</string>
<string name="reopen">Reopen</string>
<string name="removeStudent">Remove student</string>
<string name="removedStudents">Removed</string>
<string name="inviteTheStudentBack">Invite the student back</string>
<string name="sendAMessage">Send a message to all students.</string>
<string name="aLinkToTheClassWillBeAdded">A link to the class will be automatically added at the end of the message, so you don't need to include it yourself.</string>
<string name="className">Class name</string>
<string name="classDescription">Class description</string>
<string name="visibleByBothStudentsAndTeachers">Visible by both teachers and students of the class</string>
<string name="teachersOfTheClass">Teachers of the class</string>
<string name="addLichessUsernames">Add Lichess usernames to invite them as teachers. One per line.</string>
<string name="theyMustFirstApply">They must first apply to be Lichess Teachers.</string>
<string name="resetPassword">Reset password</string>
<string name="makeSureToCopy">Make sure to copy or write down the password now. You wont be able to see it again!</string>
<string name="passwordX">Password: %s</string>
<string name="generateANewPassword">Generate a new password for the student</string>
<string name="invitedToXByY">Invited to %1$s by %2$s</string>
<string name="realName">Real name</string>
<string name="privateWillNeverBeShown">Private. Will never be shown outside the class. Helps remember who the student is.</string>
<string name="addStudent">Add student</string>
<string name="lichessProfileXCreatedForY">Lichess profile %1$s created for %2$s.</string>
<string name="studentCredentials">
Student: %1$s
Username: %2$s
Password: %2$s
</string>
<string name="inviteALichessAccount">Invite a Lichess account</string>
<string name="inviteDesc1">If the student already has a Lichess account, you can invite them to the class.</string>
<string name="inviteDesc2">They will receive a message on Lichess with a link to join the class.</string>
<string name="inviteDesc3">Important: only invite students you know, and who actively want to join the class.</string>
<string name="inviteDesc4">Never send unsolicited invites to arbitrary players.</string>
<string name="createANewLichessAccount">Create a new Lichess account</string>
<string name="createDesc1">If the student doesn't have a Lichess account yet, you can create one for them here.</string>
<string name="createDesc2">No email address is required. A password will be generated, and you will have to transmit it to the student, so they can log in.</string>
<string name="createDesc3">Important: a student must not have multiple accounts.</string>
<string name="createDesc4">If they already have one, use the invite form instead.</string>
<string name="lichessUsername">Lichess username</string>
<string name="generateANewUsername">Generate a new username</string>
<string name="onlyVisibleToTeachers">Only visible to the class teachers</string>
<string name="lastActiveDate">Active</string>
<string name="managed">Managed</string>
<string name="thisStudentAccountIsManaged">This student account is managed</string>
<string name="upgradeFromManaged">Upgrade from managed to autonomous</string>
<string name="release">Release</string>
<string name="releaseTheAccount">Release the account so the student can manage it in autonomy.</string>
<string name="releaseDesc1">A released account cannot be made managed again. The student will be able to toggle kid mode and reset paswword themselves.</string>
<string name="releaseDesc2">The student will remain in the class after their account is released.</string>
<string name="realUniqueEmail">Real, unique email address of the student. We will send a confirmation email to it, with a link to release the account.</string>
<string name="teachers">Teachers</string>
<plurals name="nbTeachers">
<item quantity="one">Teacher</item>
<item quantity="other">%s teachers</item>
</plurals>
<plurals name="nbStudents">
<item quantity="one">Student</item>
<item quantity="other">%s students</item>
</plurals>
<string name="progress">Progress</string>
<string name="noStudents">No students in the class, yet.</string>
<string name="overDays">"Over days"</string>
<string name="timePlaying">Time playing</string>
<string name="variantXOverLastY">%1$s over last %2$s</string>
<string name="winrate">Winrate</string>
<string name="na">N/A</string>
<string name="news">Class news</string>
<string name="editNews">Edit news</string>
<string name="notifyAllStudents">Notify all students</string>
<string name="nothingHere">Nothing here, yet.</string>
<string name="newsEdit1">All class news in a single field.</string>
<string name="newsEdit2">Add the recent news at the top. Don't delete previous news.</string>
<string name="newsEdit3">Separate news with --- it will display a horizontal line.</string>
<string name="markdownAvailable">%s is available for more advanced syntax.</string>
</resources> </resources>

View file

@ -901,4 +901,5 @@ computer analysis, game chat and shareable URL.</string>
<string name="agreementAccount">I agree that I will not create multiple accounts.</string> <string name="agreementAccount">I agree that I will not create multiple accounts.</string>
<string name="agreementPolicy">I agree that I will follow all Lichess policies.</string> <string name="agreementPolicy">I agree that I will follow all Lichess policies.</string>
<string name="searchOrStartNewDiscussion">Search or start new discussion</string> <string name="searchOrStartNewDiscussion">Search or start new discussion</string>
<string name="edit">Edit</string>
</resources> </resources>