get rid of twirl

pull/5007/head^2
Thibault Duplessis 2019-04-22 16:42:25 +07:00
parent 3cae3ceb33
commit f81b9db930
106 changed files with 783 additions and 915 deletions

View File

@ -2,7 +2,6 @@ package lila.app
package actor
import akka.actor._
import play.twirl.api.Html
import lila.game.Pov
import views.{ html => V }

View File

@ -49,7 +49,7 @@ object Blog extends LilaController {
blogApi context req flatMap { implicit prismic =>
blogApi.recent(prismic.api, none, 1, lila.common.MaxPerPage(50)) map {
_ ?? { docs =>
Ok(views.xml.blog.atom(docs)) as XML
Ok(views.html.blog.atom(docs)) as XML
}
}
}

View File

@ -3,7 +3,6 @@ package controllers
import chess.format.Forsyth
import chess.Situation
import play.api.libs.json._
import play.twirl.api.Html
import lila.app._
import lila.game.GameRepo

View File

@ -6,7 +6,6 @@ import play.api.http._
import play.api.libs.json.{ Json, JsObject, JsArray, JsString, Writes }
import play.api.mvc._
import play.api.mvc.BodyParsers.parse
import play.twirl.api.Html
import scalatags.Text.Frag
import lila.api.{ PageData, Context, HeaderContext, BodyContext }
@ -32,8 +31,6 @@ private[controllers] trait LilaController
def fuccess = scala.concurrent.Future successful result
}
protected implicit def LilaHtmlToResult(content: Html): Result = Ok(content)
protected implicit def LilaFragToResult(frag: Frag): Result = Ok(frag)
protected implicit def makeApiVersion(v: Int) = ApiVersion(v)

View File

@ -4,7 +4,6 @@ import play.api.data.Form
import play.api.libs.iteratee._
import play.api.libs.json._
import play.api.mvc._
import play.twirl.api.Html
import scala.concurrent.duration._
import lila.api.{ Context, BodyContext }
@ -275,36 +274,36 @@ object User extends LilaController {
Env.pref.api.getPref(user).logTimeIfGt(s"$username pref.getPref", 2 seconds) flatMap {
case history ~ charges ~ reports ~ pref =>
Env.user.lightUserApi.preloadMany(reports.userIds).logTimeIfGt(s"$username lightUserApi.preloadMany", 2 seconds) inject
Html(html.user.mod.parts(user, history, charges, reports, pref).render).some
html.user.mod.parts(user, history, charges, reports, pref).some
}
val actions = UserRepo.isErased(user) map { erased =>
Html(html.user.mod.actions(user, emails, erased).render).some
html.user.mod.actions(user, emails, erased).some
}
val spyFu = Env.security.userSpy(user).logTimeIfGt(s"$username security.userSpy", 2 seconds)
val others = spyFu flatMap { spy =>
val familyUserIds = user.id :: spy.otherUserIds.toList
Env.user.noteApi.forMod(familyUserIds).logTimeIfGt(s"$username noteApi.forMod", 2 seconds) zip
Env.playban.api.bans(familyUserIds).logTimeIfGt(s"$username playban.bans", 2 seconds) map {
case notes ~ bans => Html(html.user.mod.otherUsers(user, spy, notes, bans).render).some
case notes ~ bans => html.user.mod.otherUsers(user, spy, notes, bans).some
}
}
val identification = spyFu map { spy =>
Html(html.user.mod.identification(user, spy).render).some
html.user.mod.identification(user, spy).some
}
val irwin = Env.irwin.api.reports.withPovs(user) map {
_ ?? { reps =>
Html(html.irwin.report(reps).render).some
html.irwin.report(reps).some
}
}
val assess = Env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id) flatMap {
_ ?? { as =>
Env.user.lightUserApi.preloadMany(as.games.flatMap(_.userIds)) inject Html(html.user.mod.assessments(as).render).some
Env.user.lightUserApi.preloadMany(as.games.flatMap(_.userIds)) inject html.user.mod.assessments(as).some
}
}
import play.api.libs.EventSource
implicit val extractor = EventSource.EventDataExtractor[Html](_.toString)
implicit val extractor = EventSource.EventDataExtractor[scalatags.Text.Frag](_.render)
Ok.chunked {
(Enumerator(Html(html.user.mod.menu(user).render)) interleave
(Enumerator(html.user.mod.menu(user)) interleave
futureToEnumerator(parts.logTimeIfGt(s"$username parts", 2 seconds)) interleave
futureToEnumerator(actions.logTimeIfGt(s"$username actions", 2 seconds)) interleave
futureToEnumerator(others.logTimeIfGt(s"$username others", 2 seconds)) interleave

View File

@ -24,8 +24,7 @@ object Environment
with SecurityHelper
with TeamHelper
with TournamentHelper
with ChessgroundHelper
with ui.ScalatagsTwirl {
with ChessgroundHelper {
type FormWithCaptcha = (play.api.data.Form[_], lila.common.Captcha)

View File

@ -2,33 +2,29 @@ package lila.app
package templating
import play.api.data._
import play.twirl.api.Html
import lila.api.Context
import lila.app.ui.ScalatagsTemplate._
import lila.i18n.I18nDb
trait FormHelper { self: I18nHelper =>
def errMsg(form: Field)(implicit ctx: Context): Html = errMsg(form.errors)
def errMsg(form: Field)(implicit ctx: Context): Frag = errMsg(form.errors)
def errMsg(form: Form[_])(implicit ctx: Context): Html = errMsg(form.errors)
def errMsg(form: Form[_])(implicit ctx: Context): Frag = errMsg(form.errors)
def errMsg(error: FormError)(implicit ctx: Context): Html = Html {
s"""<p class="error">${transKey(error.message, I18nDb.Site, error.args).render}</p>"""
}
def errMsg(error: FormError)(implicit ctx: Context): Frag =
p(cls := "error")(transKey(error.message, I18nDb.Site, error.args))
def errMsg(errors: Seq[FormError])(implicit ctx: Context): Html = Html {
def errMsg(errors: Seq[FormError])(implicit ctx: Context): Frag =
errors map errMsg mkString
}
def globalError(form: Form[_])(implicit ctx: Context): Option[Html] =
def globalError(form: Form[_])(implicit ctx: Context): Option[Frag] =
form.globalError map errMsg
val booleanChoices = Seq("true" -> "✓ Yes", "false" -> "✗ No")
object form3 extends ui.ScalatagsPlay {
import ui.ScalatagsTemplate._
object form3 {
private val idPrefix = "form3"
@ -54,10 +50,6 @@ trait FormHelper { self: I18nHelper =>
case ("constraint.max", Seq(m: Int)) => max := m
}
/* All public methods must return HTML
* because twirl just calls toString on scalatags frags
* and that escapes the content :( */
val split = div(cls := "form-split")
def group(
@ -66,18 +58,17 @@ trait FormHelper { self: I18nHelper =>
klass: String = "",
half: Boolean = false,
help: Option[Frag] = None
)(content: Field => Frag)(implicit ctx: Context): Html =
div(cls := List(
"form-group" -> true,
"is-invalid" -> field.hasErrors,
"form-half" -> half,
klass -> klass.nonEmpty
))(
groupLabel(field)(labelContent),
content(field),
errors(field),
help map { helper(_) }
)
)(content: Field => Frag)(implicit ctx: Context): Frag = div(cls := List(
"form-group" -> true,
"is-invalid" -> field.hasErrors,
"form-half" -> half,
klass -> klass.nonEmpty
))(
groupLabel(field)(labelContent),
content(field),
errors(field),
help map { helper(_) }
)
def input(field: Field, typ: String = "", klass: String = ""): BaseTagType =
st.input(
@ -88,66 +79,60 @@ trait FormHelper { self: I18nHelper =>
cls := List("form-control" -> true, klass -> klass.nonEmpty)
)(validationModifiers(field))
def inputHtml(field: Field, typ: String = "", klass: String = "")(modifiers: Modifier*): Html =
input(field, typ, klass)(modifiers)
def checkbox(
field: Field,
labelContent: Frag,
half: Boolean = false,
help: Option[Frag] = None,
disabled: Boolean = false
): Html =
div(cls := List(
"form-check form-group" -> true,
"form-half" -> half
))(
div(
span(cls := "form-check-input")(
st.input(
st.id := id(field),
name := field.name,
value := "true",
tpe := "checkbox",
cls := "form-control cmn-toggle",
field.value.has("true") option checked,
disabled option st.disabled
),
label(`for` := id(field))
): Frag = div(cls := List(
"form-check form-group" -> true,
"form-half" -> half
))(
div(
span(cls := "form-check-input")(
st.input(
st.id := id(field),
name := field.name,
value := "true",
tpe := "checkbox",
cls := "form-control cmn-toggle",
field.value.has("true") option checked,
disabled option st.disabled
),
groupLabel(field)(labelContent)
label(`for` := id(field))
),
help map { helper(_) }
)
groupLabel(field)(labelContent)
),
help map { helper(_) }
)
def select(
field: Field,
options: Iterable[(Any, String)],
default: Option[String] = None
): Html =
st.select(
st.id := id(field),
name := field.name,
cls := "form-control"
)(validationModifiers(field))(
default map { option(value := "")(_) },
options.toSeq map {
case (value, name) => option(
st.value := value.toString,
field.value.has(value.toString) option selected
)(name)
}
)
): Frag = st.select(
st.id := id(field),
name := field.name,
cls := "form-control"
)(validationModifiers(field))(
default map { option(value := "")(_) },
options.toSeq map {
case (value, name) => option(
st.value := value.toString,
field.value.has(value.toString) option selected
)(name)
}
)
def textarea(
field: Field,
klass: String = ""
)(modifiers: Modifier*): Html =
st.textarea(
st.id := id(field),
name := field.name,
cls := List("form-control" -> true, klass -> klass.nonEmpty)
)(validationModifiers(field))(modifiers)(~field.value)
)(modifiers: Modifier*): Frag = st.textarea(
st.id := id(field),
name := field.name,
cls := List("form-control" -> true, klass -> klass.nonEmpty)
)(validationModifiers(field))(modifiers)(~field.value)
val actions = div(cls := "form-actions")
val action = div(cls := "form-actions single")
@ -158,7 +143,7 @@ trait FormHelper { self: I18nHelper =>
nameValue: Option[(String, String)] = None,
klass: String = "",
confirm: Option[String] = None
): Html = button(
): Frag = button(
tpe := "submit",
dataIcon := icon,
name := nameValue.map(_._1),
@ -172,13 +157,12 @@ trait FormHelper { self: I18nHelper =>
title := confirm
)(content)
def hidden(field: Field, value: Option[String] = None): Html =
st.input(
st.id := id(field),
name := field.name,
st.value := value.orElse(field.value),
tpe := "hidden"
)
def hidden(field: Field, value: Option[String] = None): Frag = st.input(
st.id := id(field),
name := field.name,
st.value := value.orElse(field.value),
tpe := "hidden"
)
def password(field: Field, content: Frag)(implicit ctx: Context): Frag =
group(field, content)(input(_, typ = "password")(required))
@ -186,20 +170,20 @@ trait FormHelper { self: I18nHelper =>
def passwordModified(field: Field, content: Frag)(modifiers: Modifier*)(implicit ctx: Context): Frag =
group(field, content)(input(_, typ = "password")(required)(modifiers))
def globalError(form: Form[_])(implicit ctx: Context): Option[Html] =
def globalError(form: Form[_])(implicit ctx: Context): Option[Frag] =
form.globalError map { err =>
div(cls := "form-group is-invalid")(error(err))
}
def flatpickr(field: Field, withTime: Boolean = true): Html =
def flatpickr(field: Field, withTime: Boolean = true): Frag =
input(field, klass = "flatpickr")(
dataEnableTime := withTime,
datatime24h := withTime
)
object file {
def image(name: String): Html = st.input(tpe := "file", st.name := name, accept := "image/*")
def pgn(name: String): Html = st.input(tpe := "file", st.name := name, accept := ".pgn")
def image(name: String): Frag = st.input(tpe := "file", st.name := name, accept := "image/*")
def pgn(name: String): Frag = st.input(tpe := "file", st.name := name, accept := ".pgn")
}
}
}

View File

@ -9,7 +9,7 @@ import lila.app.ui.ScalatagsTemplate._
import lila.common.String.frag.escapeHtml
import lila.game.{ Game, Player, Namer, Pov }
import lila.i18n.{ I18nKeys, enLang }
import lila.user.{ User, UserContext }
import lila.user.{ User, UserContext, Title }
trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHelper with ChessgroundHelper =>
@ -78,7 +78,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
}
def shortClockName(clock: Option[Clock.Config])(implicit ctx: UserContext): Frag =
clock.fold[Frag](I18nKeys.unlimited.frag())(shortClockName)
clock.fold[Frag](I18nKeys.unlimited())(shortClockName)
def shortClockName(clock: Clock.Config): Frag = raw(clock.show)
@ -92,8 +92,17 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
case Mode.Rated => I18nKeys.rated.literalTxtTo(enLang)
}
def playerUsername(player: Player, withRating: Boolean = true, withTitle: Boolean = true) =
Namer.player(player, withRating, withTitle)(lightUser)
def playerUsername(player: Player, withRating: Boolean = true, withTitle: Boolean = true): Frag = raw {
player.aiLevel.fold(
player.userId.flatMap(lightUser).fold(lila.user.User.anonymous) { user =>
val title = withTitle ?? user.title ?? { t =>
s"""<span class="title"${(Title(t) == Title.BOT) ?? " data-bot"} title="${Title titleName Title(t)}">$t</span>&nbsp;"""
}
if (withRating) s"$title${user.name}&nbsp;(${lila.game.Namer ratingString player})"
else s"$title${user.name}"
}
) { level => s"A.I. level $level" }
}
def playerText(player: Player, withRating: Boolean = false) =
Namer.playerText(player, withRating)(lightUser)

View File

@ -8,7 +8,7 @@ import lila.app.ui.ScalatagsTemplate._
import lila.common.String.frag.escapeHtml
import lila.team.Env.{ current => teamEnv }
trait TeamHelper extends ui.ScalatagsTwirl {
trait TeamHelper {
private def api = teamEnv.api

View File

@ -234,7 +234,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
}
def userGameFilterTitle(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext): Frag =
if (filter == GameFilter.Search) frag(br, trans.advancedSearch.frag())
if (filter == GameFilter.Search) frag(br, trans.advancedSearch())
else splitNumber(userGameFilterTitleNoTag(u, nbs, filter))
def userGameFilterTitleNoTag(u: User, nbs: UserInfo.NbGames, filter: GameFilter)(implicit ctx: UserContext): String = (filter match {

View File

@ -3,8 +3,6 @@ package ui
import ornicar.scalalib.Zero
import play.twirl.api.Html
import scalatags.Text.all.{ genericAttr, attr, StringFrag }
import scalatags.text.Builder
import scalatags.Text.{ Aggregate, Cap }
import scalatags.Text.all._
@ -95,38 +93,12 @@ trait ScalatagsTemplate extends Styles
val trans = lila.i18n.I18nKeys
def main = scalatags.Text.tags2.main
}
object ScalatagsTemplate extends ScalatagsTemplate
// what to import in all twirl templates
trait ScalatagsTwirl extends ScalatagsPlay
// what to import in twirl templates containing scalatags forms
// Allows `*.rows := 5`
trait ScalatagsTwirlForm extends ScalatagsPlay with Cap with Aggregate {
object * extends Cap with Attrs with ScalatagsAttrs
}
object ScalatagsTwirlForm extends ScalatagsTwirlForm
// interop with play
trait ScalatagsPlay {
/* Feed frags back to twirl by converting them to rendered Html */
implicit def fragToPlayHtml(frag: Frag): Html = Html(frag.render)
/* Use play Html inside tags without double-encoding */
implicit def playHtmlToFrag(html: Html): Frag = RawFrag(html.body)
/* Convert play URLs to scalatags attributes with toString */
implicit val playCallAttr = genericAttr[play.api.mvc.Call]
@inline implicit def fragToHtml(frag: Frag) = new FragToHtml(frag)
}
final class FragToHtml(private val self: Frag) extends AnyVal {
def toHtml: Html = Html(self.render)
}
object ScalatagsTemplate extends ScalatagsTemplate
// generic extensions
trait ScalatagsExtensions {

View File

@ -14,15 +14,15 @@ object close {
active = "close"
) {
div(cls := "account box box-pad")(
h1(dataIcon := "j", cls := "text")(trans.closeAccount.frag()),
h1(dataIcon := "j", cls := "text")(trans.closeAccount()),
st.form(cls := "form3", action := routes.Account.closeConfirm, method := "POST")(
div(cls := "form-group")(trans.closeAccountExplanation.frag()),
div(cls := "form-group")(trans.closeAccountExplanation()),
div(cls := "form-group")("You will not be allowed to open a new account with the same name, even if the case if different."),
form3.passwordModified(form("passwd"), trans.password.frag())(autocomplete := "off"),
form3.passwordModified(form("passwd"), trans.password())(autocomplete := "off"),
form3.actions(frag(
a(href := routes.User.show(u.username))(trans.changedMindDoNotCloseAccount.frag()),
a(href := routes.User.show(u.username))(trans.changedMindDoNotCloseAccount()),
form3.submit(
trans.closeAccount.frag(),
trans.closeAccount(),
icon = "j".some,
confirm = "Closing is definitive. There is no going back. Are you sure?".some,
klass = "button-red"

View File

@ -15,14 +15,14 @@ object email {
) {
div(cls := "account box box-pad")(
h1(
trans.changeEmail.frag(),
trans.changeEmail(),
ctx.req.queryString.contains("ok") option
frag(" ", i(cls := "is-green", dataIcon := "E"))
),
st.form(cls := "form3", action := routes.Account.emailApply, method := "POST")(
form3.password(form("passwd"), trans.password.frag()),
form3.group(form("email"), trans.email.frag())(form3.input(_, typ = "email")(required)),
form3.action(form3.submit(trans.apply.frag()))
form3.password(form("passwd"), trans.password()),
form3.group(form("email"), trans.email())(form3.input(_, typ = "email")(required)),
form3.action(form3.submit(trans.apply()))
)
)
}

View File

@ -26,13 +26,13 @@ object emailConfirmHelp {
form3.split(
form3.group(
form("username"),
trans.username.frag(),
trans.username(),
help = raw("What username did you create?").some
) { f =>
form3.input(f)(pattern := lila.user.User.newUsernameRegex.regex)
},
div(cls := "form-group")(
form3.submit(trans.apply.frag())
form3.submit(trans.apply())
)
)
),

View File

@ -14,8 +14,8 @@ object kid {
active = "kid"
) {
div(cls := "account box box-pad")(
h1(trans.kidMode.frag()),
p(trans.kidModeExplanation.frag()),
h1(trans.kidMode()),
p(trans.kidModeExplanation()),
br,
br,
br,
@ -31,7 +31,7 @@ object kid {
),
br,
br,
p(trans.inKidModeTheLichessLogoGetsIconX.frag(raw(s"""<span title="${trans.kidMode()}" class="kiddo">😊</span>""")))
p(trans.inKidModeTheLichessLogoGetsIconX(raw(s"""<span title="${trans.kidMode()}" class="kiddo">😊</span>""")))
)
}
}

View File

@ -27,28 +27,28 @@ object layout {
)
},
a(activeCls("kid"), href := routes.Account.kid())(
trans.kidMode.frag()
trans.kidMode()
),
div(cls := "sep"),
a(activeCls("editProfile"), href := routes.Account.profile())(
trans.editProfile.frag()
trans.editProfile()
),
isGranted(_.Coach) option a(activeCls("coach"), href := routes.Coach.edit)("Coach profile"),
div(cls := "sep"),
a(activeCls("password"), href := routes.Account.passwd())(
trans.changePassword.frag()
trans.changePassword()
),
a(activeCls("email"), href := routes.Account.email())(
trans.changeEmail.frag()
trans.changeEmail()
),
a(activeCls("username"), href := routes.Account.username())(
trans.changeUsername.frag()
trans.changeUsername()
),
a(activeCls("twofactor"), href := routes.Account.twoFactor())(
"Two-factor authentication"
),
a(activeCls("security"), href := routes.Account.security())(
trans.security.frag()
trans.security()
),
div(cls := "sep"),
a(href := routes.Plan.index)("Patron"),
@ -59,7 +59,7 @@ object layout {
ctx.noBot option a(activeCls("oauth.app"), href := routes.OAuthApp.index)("OAuth Apps"),
div(cls := "sep"),
a(activeCls("close"), href := routes.Account.close())(
trans.closeAccount.frag()
trans.closeAccount()
)
),
div(cls := "page-menu__content")(body)

View File

@ -15,15 +15,15 @@ object passwd {
) {
div(cls := "account box box-pad")(
h1(
trans.changePassword.frag(),
trans.changePassword(),
ctx.req.queryString.contains("ok") option
frag(" ", i(cls := "is-green", dataIcon := "E"))
),
st.form(cls := "form3", action := routes.Account.passwdApply, method := "POST")(
form3.password(form("oldPasswd"), trans.currentPassword.frag()),
form3.password(form("newPasswd1"), trans.newPassword.frag()),
form3.password(form("newPasswd2"), trans.newPasswordAgain.frag()),
form3.action(form3.submit(trans.apply.frag()))
form3.password(form("oldPasswd"), trans.currentPassword()),
form3.password(form("newPasswd1"), trans.newPassword()),
form3.password(form("newPasswd2"), trans.newPasswordAgain()),
form3.action(form3.submit(trans.apply()))
)
)
}

View File

@ -44,117 +44,117 @@ object pref {
st.form(cls := "autosubmit", action := routes.Pref.formApply, method := "POST")(
categFieldset(PrefCateg.GameDisplay, categ)(
setting(
trans.pieceAnimation.frag(),
trans.pieceAnimation(),
radios(form("display.animation"), translatedAnimationChoices)
),
setting(
trans.materialDifference.frag(),
trans.materialDifference(),
radios(form("display.captured"), booleanChoices)
),
setting(
trans.boardHighlights.frag(),
trans.boardHighlights(),
radios(form("display.highlight"), booleanChoices)
),
setting(
trans.pieceDestinations.frag(),
trans.pieceDestinations(),
radios(form("display.destination"), booleanChoices)
),
setting(
trans.boardCoordinates.frag(),
trans.boardCoordinates(),
radios(form("display.coords"), translatedBoardCoordinateChoices)
),
setting(
trans.moveListWhilePlaying.frag(),
trans.moveListWhilePlaying(),
radios(form("display.replay"), translatedMoveListWhilePlayingChoices)
),
setting(
trans.pgnPieceNotation.frag(),
trans.pgnPieceNotation(),
radios(form("display.pieceNotation"), translatedPieceNotationChoices)
),
setting(
trans.zenMode.frag(),
trans.zenMode(),
radios(form("display.zen"), booleanChoices)
),
setting(
trans.blindfoldChess.frag(),
trans.blindfoldChess(),
radios(form("display.blindfold"), translatedBlindfoldChoices)
)
),
categFieldset(PrefCateg.ChessClock, categ)(
setting(
trans.tenthsOfSeconds.frag(),
trans.tenthsOfSeconds(),
radios(form("clockTenths"), translatedClockTenthsChoices)
),
setting(
trans.horizontalGreenProgressBars.frag(),
trans.horizontalGreenProgressBars(),
radios(form("clockBar"), booleanChoices)
),
setting(
trans.soundWhenTimeGetsCritical.frag(),
trans.soundWhenTimeGetsCritical(),
radios(form("clockSound"), booleanChoices)
)
),
categFieldset(PrefCateg.GameBehavior, categ)(
setting(
trans.howDoYouMovePieces.frag(),
trans.howDoYouMovePieces(),
radios(form("behavior.moveEvent"), translatedMoveEventChoices)
),
setting(
trans.premovesPlayingDuringOpponentTurn.frag(),
trans.premovesPlayingDuringOpponentTurn(),
radios(form("behavior.premove"), booleanChoices)
),
setting(
trans.takebacksWithOpponentApproval.frag(),
trans.takebacksWithOpponentApproval(),
radios(form("behavior.takeback"), translatedTakebackChoices)
),
setting(
trans.promoteToQueenAutomatically.frag(),
trans.promoteToQueenAutomatically(),
radios(form("behavior.autoQueen"), translatedAutoQueenChoices)
),
setting(
trans.claimDrawOnThreefoldRepetitionAutomatically.frag(),
trans.claimDrawOnThreefoldRepetitionAutomatically(),
radios(form("behavior.autoThreefold"), translatedAutoThreefoldChoices)
),
setting(
trans.moveConfirmation.frag(),
trans.moveConfirmation(),
radios(form("behavior.submitMove"), submitMoveChoices)
),
setting(
trans.confirmResignationAndDrawOffers.frag(),
trans.confirmResignationAndDrawOffers(),
radios(form("behavior.confirmResign"), confirmResignChoices)
),
setting(
trans.inputMovesWithTheKeyboard.frag(),
trans.inputMovesWithTheKeyboard(),
radios(form("behavior.keyboardMove"), booleanChoices)
),
setting(
trans.castleByMovingTheKingTwoSquaresOrOntoTheRook.frag(),
trans.castleByMovingTheKingTwoSquaresOrOntoTheRook(),
radios(form("behavior.rookCastle"), translatedRookCastleChoices)
)
),
categFieldset(PrefCateg.Privacy, categ)(
setting(
trans.letOtherPlayersFollowYou.frag(),
trans.letOtherPlayersFollowYou(),
radios(form("follow"), booleanChoices)
),
setting(
trans.letOtherPlayersChallengeYou.frag(),
trans.letOtherPlayersChallengeYou(),
radios(form("challenge"), translatedChallengeChoices)
),
setting(
trans.letOtherPlayersMessageYou.frag(),
trans.letOtherPlayersMessageYou(),
radios(form("message"), translatedMessageChoices)
),
setting(
trans.letOtherPlayersInviteYouToStudy.frag(),
trans.letOtherPlayersInviteYouToStudy(),
radios(form("studyInvite"), translatedStudyInviteChoices)
),
setting(
trans.shareYourInsightsData.frag(),
trans.shareYourInsightsData(),
radios(form("insightShare"), translatedInsightSquareChoices)
)
),
p(cls := "saved text none", dataIcon := "E")(trans.yourPreferencesHaveBeenSaved.frag())
p(cls := "saved text none", dataIcon := "E")(trans.yourPreferencesHaveBeenSaved())
)
)
}

View File

@ -24,29 +24,29 @@ object profile {
st.form(cls := "form3", action := routes.Account.profileApply, method := "POST")(
div(cls := "form-group")(trans.allInformationIsPublicAndOptional()),
form3.split(
form3.group(form("country"), trans.country.frag(), half = true) { f =>
form3.group(form("country"), trans.country(), half = true) { f =>
form3.select(f, lila.user.Countries.allPairs, default = "".some)
},
form3.group(form("location"), trans.location.frag(), half = true)(form3.input(_))
form3.group(form("location"), trans.location(), half = true)(form3.input(_))
),
NotForKids {
form3.group(form("bio"), trans.biography.frag(), help = trans.biographyDescription.frag().some) { f =>
form3.group(form("bio"), trans.biography(), help = trans.biographyDescription().some) { f =>
form3.textarea(f)(rows := 5)
}
},
form3.split(
form3.group(form("firstName"), trans.firstName.frag(), half = true)(form3.input(_)),
form3.group(form("lastName"), trans.lastName.frag(), half = true)(form3.input(_))
form3.group(form("firstName"), trans.firstName(), half = true)(form3.input(_)),
form3.group(form("lastName"), trans.lastName(), half = true)(form3.input(_))
),
form3.split(
List("fide", "uscf", "ecf").map { rn =>
form3.group(form(s"${rn}Rating"), trans.xRating.frag(rn.toUpperCase), help = trans.ifNoneLeaveEmpty.frag().some, klass = "form-third")(form3.input(_, typ = "number"))
form3.group(form(s"${rn}Rating"), trans.xRating(rn.toUpperCase), help = trans.ifNoneLeaveEmpty().some, klass = "form-third")(form3.input(_, typ = "number"))
}
),
form3.group(form("links"), raw("Social media links "), help = Some(linksHelp)) { f =>
form3.textarea(f)(rows := 5)
},
form3.action(form3.submit(trans.apply.frag()))
form3.action(form3.submit(trans.apply()))
)
)
}

View File

@ -12,14 +12,14 @@ object security {
def apply(u: lila.user.User, sessions: List[lila.security.LocatedSession], curSessionId: String)(implicit ctx: Context) =
account.layout(title = s"${u.username} - ${trans.security.txt()}", active = "security") {
div(cls := "account security box")(
h1(trans.security.frag()),
h1(trans.security()),
div(cls := "box__pad")(
p(trans.thisIsAListOfDevicesThatHaveLoggedIntoYourAccount.frag()),
p(trans.thisIsAListOfDevicesThatHaveLoggedIntoYourAccount()),
sessions.length > 1 option div(
trans.alternativelyYouCanX.frag {
trans.alternativelyYouCanX {
form(cls := "revoke-all", action := routes.Account.signout("all"), method := "POST")(
button(tpe := "submit", cls := "button button-empty button-red confirm")(
trans.revokeAllSessions.frag()
trans.revokeAllSessions()
)
)
}

View File

@ -30,7 +30,7 @@ object twoFactor {
qrCode,
div(cls := "form-group explanation")("Enter your password and the authentication code generated by the app to complete the setup. You will need an authentication code every time you log in."),
form3.hidden(form("secret")),
form3.password(form("passwd"), trans.password.frag()),
form3.password(form("passwd"), trans.password()),
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required)),
form3.globalError(form),
div(cls := "form-group")("Note: If you lose access to your two-factor authentication codes, you can do a password reset via email."),
@ -54,7 +54,7 @@ object twoFactor {
"You need your password and an authentication code from your authenticator app to disable two-factor authentication. ",
"If you lost access to your authentication codes, you can also do a password reset via email."
),
form3.password(form("passwd"), trans.password.frag()),
form3.password(form("passwd"), trans.password()),
form3.group(form("token"), raw("Authentication code"))(form3.input(_)(pattern := "[0-9]{6}", autocomplete := "off", required)),
form3.action(form3.submit(raw("Disable two-factor authentication"), icon = None))
)

View File

@ -14,11 +14,11 @@ object username {
active = "username"
) {
div(cls := "account box box-pad")(
h1(cls := "text", dataIcon := "*")(trans.changeUsername.frag()),
h1(cls := "text", dataIcon := "*")(trans.changeUsername()),
st.form(cls := "form3", action := routes.Account.usernameApply, method := "POST")(
form3.globalError(form),
form3.group(form("username"), trans.username.frag(), help = trans.changeUsernameDescription.frag().some)(form3.input(_)(required)),
form3.action(form3.submit(trans.apply.frag()))
form3.group(form("username"), trans.username(), help = trans.changeUsernameDescription().some)(form3.input(_)(required)),
form3.action(form3.submit(trans.apply()))
)
)
}

View File

@ -19,7 +19,7 @@ object help {
private def k(str: String) = raw(s"""<kbd>$str</kbd>""")
def apply(isStudy: Boolean)(implicit ctx: Context) = frag(
h2(trans.keyboardShortcuts.frag()),
h2(trans.keyboardShortcuts()),
table(
tbody(
header("Navigate the move tree"),
@ -49,8 +49,8 @@ object help {
tr(
td(cls := "mouse", colspan := 2)(
ul(
li(trans.youCanAlsoScrollOverTheBoardToMoveInTheGame.frag()),
li(trans.analysisShapesHowTo.frag())
li(trans.youCanAlsoScrollOverTheBoardToMoveInTheGame()),
li(trans.analysisShapesHowTo())
)
)
)

View File

@ -1,7 +1,5 @@
package views.html.analyse
import play.twirl.api.Html
import bits.dataPanel
import lila.api.Context
import lila.app.templating.Environment._

View File

@ -13,15 +13,15 @@ import controllers.routes
object bits {
def formFields(username: Field, password: Field, emailOption: Option[Field], register: Boolean)(implicit ctx: Context) = frag(
form3.group(username, if (register) trans.username.frag() else trans.usernameOrEmail.frag()) { f =>
form3.group(username, if (register) trans.username() else trans.usernameOrEmail()) { f =>
frag(
form3.input(f)(autofocus, required),
p(cls := "error exists none")(trans.usernameAlreadyUsed.frag())
p(cls := "error exists none")(trans.usernameAlreadyUsed())
)
},
form3.password(password, trans.password.frag()),
form3.password(password, trans.password()),
emailOption.map { email =>
form3.group(email, trans.email.frag())(form3.input(_, typ = "email")(required))
form3.group(email, trans.email())(form3.input(_, typ = "email")(required))
}
)
@ -36,16 +36,16 @@ object bits {
ok.map { r =>
span(cls := (if (r) "is-green" else "is-red"), dataIcon := (if (r) "E" else "L"))
},
trans.passwordReset.frag()
trans.passwordReset()
),
st.form(
cls := "form3",
action := routes.Auth.passwordResetApply,
method := "post"
)(
form3.group(form("email"), trans.email.frag())(form3.input(_, typ = "email")(autofocus)),
form3.group(form("email"), trans.email())(form3.input(_, typ = "email")(autofocus)),
views.html.base.captcha(form, captcha),
form3.action(form3.submit(trans.emailMeALink.frag()))
form3.action(form3.submit(trans.emailMeALink()))
)
)
}
@ -55,9 +55,9 @@ object bits {
title = trans.passwordReset.txt()
) {
main(cls := "page-small box box-pad")(
h1(cls := "is-green text", dataIcon := "E")(trans.checkYourEmail.frag()),
p(trans.weHaveSentYouAnEmailTo.frag(email)),
p(trans.ifYouDoNotSeeTheEmailCheckOtherPlaces.frag())
h1(cls := "is-green text", dataIcon := "E")(trans.checkYourEmail()),
p(trans.weHaveSentYouAnEmailTo(email)),
p(trans.ifYouDoNotSeeTheEmailCheckOtherPlaces())
)
}
@ -74,14 +74,14 @@ object bits {
})(
userLink(u, withOnline = false),
" - ",
trans.changePassword.frag()
trans.changePassword()
),
st.form(cls := "form3", action := routes.Auth.passwordResetConfirmApply(token), method := "POST")(
form3.hidden(form("token")),
form3.passwordModified(form("newPasswd1"), trans.newPassword.frag())(autofocus),
form3.password(form("newPasswd2"), trans.newPasswordAgain.frag()),
form3.passwordModified(form("newPasswd1"), trans.newPassword())(autofocus),
form3.password(form("newPasswd2"), trans.newPasswordAgain()),
form3.globalError(form),
form3.action(form3.submit(trans.changePassword.frag()))
form3.action(form3.submit(trans.changePassword()))
)
)
}

View File

@ -21,7 +21,7 @@ object login {
moreCss = cssTag("auth")
) {
main(cls := "auth auth-login box box-pad")(
h1(trans.signIn.frag()),
h1(trans.signIn()),
st.form(
cls := "form3",
action := s"${routes.Auth.authenticate}${referrer.?? { ref => s"?referrer=${java.net.URLEncoder.encode(ref, "US-ASCII")}" }}",
@ -30,17 +30,17 @@ object login {
div(cls := "one-factor")(
form3.globalError(form),
auth.bits.formFields(form("username"), form("password"), none, register = false),
form3.submit(trans.signIn.frag(), icon = none)
form3.submit(trans.signIn(), icon = none)
),
div(cls := "two-factor none")(
form3.group(form("token"), raw("Authentication code"), help = Some(twoFactorHelp))(form3.input(_)(autocomplete := "off", pattern := "[0-9]{6}")),
p(cls := "error none")("Invalid code."),
form3.submit(trans.signIn.frag(), icon = none)
form3.submit(trans.signIn(), icon = none)
)
),
div(cls := "alternative")(
a(href := routes.Auth.signup())(trans.signUp.frag()),
a(href := routes.Auth.passwordReset())(trans.passwordReset.frag())
a(href := routes.Auth.signup())(trans.signUp()),
a(href := routes.Auth.passwordReset())(trans.passwordReset())
)
)
}

View File

@ -25,7 +25,7 @@ object signup {
csp = defaultCsp.withRecaptcha.some
) {
main(cls := "auth auth-signup box box-pad")(
h1(trans.signUp.frag()),
h1(trans.signUp()),
st.form(
id := "signup_form",
cls := "form3",
@ -35,16 +35,16 @@ object signup {
auth.bits.formFields(form("username"), form("password"), form("email").some, register = true),
input(id := "signup-fp-input", name := "fp", tpe := "hidden"),
div(cls := "form-group text", dataIcon := "")(
trans.computersAreNotAllowedToPlay.frag(), br,
small(trans.byRegisteringYouAgreeToBeBoundByOur.frag(a(href := routes.Page.tos)(trans.termsOfService.frag())))
trans.computersAreNotAllowedToPlay(), br,
small(trans.byRegisteringYouAgreeToBeBoundByOur(a(href := routes.Page.tos)(trans.termsOfService())))
),
if (recaptcha.enabled)
button(
cls := "g-recaptcha submit button text big",
attr("data-sitekey") := recaptcha.key,
attr("data-callback") := "signupSubmit"
)(trans.signUp.frag())
else form3.submit(trans.signUp.frag(), icon = none, klass = "big")
)(trans.signUp())
else form3.submit(trans.signUp(), icon = none, klass = "big")
)
)
}

View File

@ -39,19 +39,19 @@ object captcha {
)(div(cls := "cg-board"))
),
div(cls := "captcha-explanation")(
label(cls := "form-label")(trans.colorPlaysCheckmateInOne.frag(
(if (captcha.white) trans.white else trans.black).frag()
label(cls := "form-label")(trans.colorPlaysCheckmateInOne(
(if (captcha.white) trans.white else trans.black)()
)),
br, br,
trans.thisIsAChessCaptcha.frag(),
trans.thisIsAChessCaptcha(),
br,
trans.clickOnTheBoardToMakeYourMove.frag(),
trans.clickOnTheBoardToMakeYourMove(),
br, br,
trans.help.frag(),
trans.help(),
" ",
a(title := trans.viewTheSolution.txt(), target := "_blank", href := url)(url),
div(cls := "result success text", dataIcon := "E")(trans.checkmate.frag()),
div(cls := "result failure text", dataIcon := "k")(trans.notACheckmate.frag()),
div(cls := "result success text", dataIcon := "E")(trans.checkmate()),
div(cls := "result failure text", dataIcon := "k")(trans.notACheckmate()),
form3.hidden(form("move"))
)
)

View File

@ -190,7 +190,7 @@ object layout {
div(cls := "friend_box_title")(
strong(cls := "online")("?"),
" ",
trans.onlineFriends.frag()
trans.onlineFriends()
),
div(cls := "content_wrap")(
div(cls := "content list"),
@ -198,15 +198,15 @@ object layout {
"nobody" -> true,
"none" -> ctx.onlineFriends.users.nonEmpty
))(
span(trans.noFriendsOnline.frag()),
span(trans.noFriendsOnline()),
a(cls := "find button", href := routes.User.opponents)(
span(cls := "is3 text", dataIcon := "h")(trans.findFriends.frag())
span(cls := "is3 text", dataIcon := "h")(trans.findFriends())
)
)
)
)
},
a(id := "reconnecting", cls := "link text", dataIcon := "B")(trans.reconnecting.frag()),
a(id := "reconnecting", cls := "link text", dataIcon := "B")(trans.reconnecting()),
chessground option jsTag("vendor/chessground.min.js"),
ctx.requiresFingerprint option fingerprintTag,
if (isProd)

View File

@ -16,57 +16,57 @@ object topnav {
cls := (if (ctx.blind) "blind" else "hover")
)(
st.section(
linkTitle("/", trans.play.frag()),
linkTitle("/", trans.play()),
div(role := "group")(
if (ctx.noBot) a(href := "/?any#hook")(trans.createAGame.frag())
else a(href := "/?any#friend")(trans.playWithAFriend.frag()),
if (ctx.noBot) a(href := "/?any#hook")(trans.createAGame())
else a(href := "/?any#friend")(trans.playWithAFriend()),
ctx.noBot option frag(
a(href := routes.Tournament.home())(trans.tournament.frag()),
a(href := routes.Simul.home)(trans.simultaneousExhibitions.frag())
a(href := routes.Tournament.home())(trans.tournament()),
a(href := routes.Simul.home)(trans.simultaneousExhibitions())
)
)
),
st.section(
linkTitle(routes.Puzzle.home.toString, trans.learnMenu.frag()),
linkTitle(routes.Puzzle.home.toString, trans.learnMenu()),
div(role := "group")(
ctx.noBot option frag(
a(href := routes.Learn.index)(trans.chessBasics.frag()),
a(href := routes.Puzzle.home)(trans.training.frag()),
a(href := routes.Learn.index)(trans.chessBasics()),
a(href := routes.Puzzle.home)(trans.training()),
a(href := routes.Practice.index)("Practice"),
a(href := routes.Coordinate.home)(trans.coordinates.coordinates.frag())
a(href := routes.Coordinate.home)(trans.coordinates.coordinates())
),
a(href := routes.Study.allDefault(1))("Study"),
a(href := routes.Coach.allDefault(1))(trans.coaches.frag())
a(href := routes.Coach.allDefault(1))(trans.coaches())
)
),
st.section(
linkTitle(routes.Tv.index.toString, trans.watch.frag()),
linkTitle(routes.Tv.index.toString, trans.watch()),
div(role := "group")(
a(href := routes.Tv.index)("Lichess TV"),
a(href := routes.Tv.games)(trans.currentGames.frag()),
a(href := routes.Tv.games)(trans.currentGames()),
a(href := routes.Streamer.index())("Streamers"),
a(href := routes.Relay.index())("Broadcasts (beta)"),
ctx.noBot option a(href := routes.Video.index)(trans.videoLibrary.frag())
ctx.noBot option a(href := routes.Video.index)(trans.videoLibrary())
)
),
st.section(
linkTitle(routes.User.list.toString, trans.community.frag()),
linkTitle(routes.User.list.toString, trans.community()),
div(role := "group")(
a(href := routes.User.list)(trans.players.frag()),
a(href := routes.User.list)(trans.players()),
NotForKids(frag(
a(href := routes.Team.home())(trans.teams.frag()),
a(href := routes.ForumCateg.index)(trans.forum.frag())
a(href := routes.Team.home())(trans.teams()),
a(href := routes.ForumCateg.index)(trans.forum())
))
)
),
st.section(
linkTitle(routes.UserAnalysis.index.toString, trans.tools.frag()),
linkTitle(routes.UserAnalysis.index.toString, trans.tools()),
div(role := "group")(
a(href := routes.UserAnalysis.index)(trans.analysis.frag()),
a(href := s"${routes.UserAnalysis.index}#explorer")(trans.openingExplorer.frag()),
a(href := routes.Editor.index)(trans.boardEditor.frag()),
a(href := routes.Importer.importGame)(trans.importGame.frag()),
a(href := routes.Search.index())(trans.advancedSearch.frag())
a(href := routes.UserAnalysis.index)(trans.analysis()),
a(href := s"${routes.UserAnalysis.index}#explorer")(trans.openingExplorer()),
a(href := routes.Editor.index)(trans.boardEditor()),
a(href := routes.Importer.importGame)(trans.importGame()),
a(href := routes.Search.index())(trans.advancedSearch())
)
)
)

View File

@ -0,0 +1,47 @@
package views.html.blog
import play.api.mvc.RequestHeader
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.paginator.Paginator
import controllers.routes
object atom {
def apply(pager: Paginator[io.prismic.Document])(implicit req: RequestHeader) = frag(
raw("""<?xml version="1.0" encoding="UTF-8"?>"""),
raw("""<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">"""),
tag("id")(routes.Blog.index().absoluteURL(true)),
link(rel := "alternate", tpe := "text/html", href := routes.Blog.index().absoluteURL(true)),
link(rel := "self", tpe := "application/atom+xml", href := routes.Blog.atom().absoluteURL(true)),
tag("title")("lichess.org blog"),
tag("updated")(pager.currentPageResults.headOption.flatMap(atomDate("blog.date"))),
pager.currentPageResults.map { doc =>
tag("entry")(
tag("id")(routes.Blog.show(doc.id, doc.slug).absoluteURL(true)),
tag("published")(atomDate("blog.date")(doc)),
tag("updated")(atomDate("blog.date")(doc)),
link(rel := "alternate", tpe := "text/html", href := routes.Blog.show(doc.id, doc.slug).absoluteURL(true)),
tag("title")(doc.getText("blog.title")),
tag("category")(
tag("term")(doc.getText("blog.category")),
tag("label")(slugify(~doc.getText("blog.category")))
),
tag("content")(tpe := "html")(
doc.getText("blog.shortlede"),
"<br>", // yes, scalatags encodes it.
doc.getImage("blog.image", "main").map { img =>
s"""<img src="${img.url}"/>"""
},
"<br>",
lila.blog.ProtocolFix.add(doc.getStructuredText("blog.body") ?? lila.blog.BlogApi.extract)
),
tag("tag")("media:thumbnail")(attr("url") := doc.getImage(s"blog.image", "main").map(_.url)),
tag("author")(tag("name")(doc.getText("blog.author")))
)
}
)
}

View File

@ -1,37 +0,0 @@
@(pager: Paginator[io.prismic.Document])(implicit req: RequestHeader)
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<id>@routes.Blog.index().absoluteURL(true)</id>
<link rel="alternate" type="text/html" href="@routes.Blog.index().absoluteURL(true)"/>
<link rel="self" type="application/atom+xml" href="@routes.Blog.atom().absoluteURL(true)"/>
<title>lichess.org blog</title>
<updated>@pager.currentPageResults.headOption.flatMap(atomDate("blog.date"))</updated>
@pager.currentPageResults.map { doc =>
<entry>
<id>@routes.Blog.show(doc.id, doc.slug).absoluteURL(true)</id>
<published>@atomDate("blog.date")(doc)</published>
<updated>@atomDate("blog.date")(doc)</updated>
<link rel="alternate" type="text/html" href="@routes.Blog.show(doc.id, doc.slug).absoluteURL(true)"/>
<title>@doc.getText("blog.title")</title>
<category>
<term>@doc.getText("blog.category")</term>
<label>@slugify(~doc.getText("blog.category"))</label>
</category>
<content type="html">
@doc.getText("blog.shortlede")
&lt;br /&gt;
@doc.getImage("blog.image", "main").map { img =>
&lt;img src="@img.url"/&gt;
}
&lt;br /&gt;
@lila.blog.ProtocolFix.add(doc.getStructuredText("blog.body") ?? lila.blog.BlogApi.extract)
</content>
<media:thumbnail url="@doc.getImage(s"blog.image", "main").map(_.url)" />
<author>
<name>@doc.getText("blog.author")</name>
</author>
</entry>
}
</feed>

View File

@ -31,8 +31,8 @@ data: ${safeJsonValue(json)}
br,
span(cls := "clock")(
c.daysPerTurn map { days =>
if (days == 1) trans.oneDay.frag()
else trans.nbDays.pluralSameFrag(days)
if (days == 1) trans.oneDay()
else trans.nbDays.pluralSame(days)
} getOrElse shortClockName(c.clock.map(_.config))
)
)

View File

@ -13,7 +13,7 @@ object mine {
val cancelForm =
form(method := "post", action := routes.Challenge.cancel(c.id), cls := "cancel xhr")(
button(tpe := "submit", cls := "button button-red text", dataIcon := "L")(trans.cancel.frag())
button(tpe := "submit", cls := "button button-red text", dataIcon := "L")(trans.cancel())
)
views.html.base.layout(
@ -25,13 +25,13 @@ object mine {
main(cls := "page-small challenge-page box box-pad")(
c.status match {
case Status.Created | Status.Offline => div(id := "ping-challenge")(
h1(trans.challengeToPlay.frag()),
h1(trans.challengeToPlay()),
bits.details(c),
c.destUserId.map { destId =>
div(cls := "waiting")(
userIdLink(destId.some, cssClass = "target".some),
spinner,
p(trans.waitingForOpponent.frag())
p(trans.waitingForOpponent())
)
} getOrElse div(cls := "invite")(
div(
@ -46,7 +46,7 @@ object mine {
),
button(title := "Copy URL", cls := "copy button", dataRel := "challenge-id", dataIcon := "\"")
),
p(trans.theFirstPersonToComeOnThisUrlWillPlayWithYou.frag())
p(trans.theFirstPersonToComeOnThisUrlWillPlayWithYou())
),
ctx.isAuth option div(
p("Or invite a lichess user:"),
@ -67,19 +67,19 @@ object mine {
case Status.Declined => div(cls := "follow-up")(
h1("Challenge declined"),
bits.details(c),
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent.frag())
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent())
)
case Status.Accepted => div(cls := "follow-up")(
h1("Challenge accepted!"),
bits.details(c),
a(id := "challenge-redirect", href := routes.Round.watcher(c.id, "white"), cls := "button-fat")(
trans.joinTheGame.frag()
trans.joinTheGame()
)
)
case Status.Canceled => div(cls := "follow-up")(
h1("Challenge canceled."),
bits.details(c),
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent.frag())
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent())
)
}
)

View File

@ -34,9 +34,9 @@ object theirs {
},
if (!c.mode.rated || ctx.isAuth) frag(
(c.mode.rated && c.unlimited) option
badTag(trans.bewareTheGameIsRatedButHasNoClock.frag()),
badTag(trans.bewareTheGameIsRatedButHasNoClock()),
form(cls := "accept", action := routes.Challenge.accept(c.id), method := "post")(
button(tpe := "submit", cls := "text button button-fat", dataIcon := "G")(trans.joinTheGame.frag())
button(tpe := "submit", cls := "text button button-fat", dataIcon := "G")(trans.joinTheGame())
)
)
else frag(
@ -45,7 +45,7 @@ object theirs {
p("This game is rated"),
p(
"You must ",
a(cls := "button", href := s"${routes.Auth.login}?referrer=${routes.Round.watcher(c.id, "white")}")(trans.signIn.frag()),
a(cls := "button", href := s"${routes.Auth.login}?referrer=${routes.Round.watcher(c.id, "white")}")(trans.signIn()),
" to join it."
)
)
@ -54,19 +54,19 @@ object theirs {
case Status.Declined => div(cls := "follow-up")(
h1("Challenge declined"),
bits.details(c),
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent.frag())
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent())
)
case Status.Accepted => div(cls := "follow-up")(
h1("Challenge accepted!"),
bits.details(c),
a(id := "challenge-redirect", href := routes.Round.watcher(c.id, "white"), cls := "button button-fat")(
trans.joinTheGame.frag()
trans.joinTheGame()
)
)
case Status.Canceled => div(cls := "follow-up")(
h1("Challenge canceled."),
bits.details(c),
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent.frag())
a(cls := "button button-fat", href := routes.Lobby.home())(trans.newOpponent())
)
}
)

View File

@ -47,7 +47,7 @@ object review {
)(
mine.map(_.text)
),
button(tpe := "submit", cls := "button")(trans.apply.frag())
button(tpe := "submit", cls := "button")(trans.apply())
)
)

View File

@ -35,14 +35,14 @@ object coordinate {
)(
div(cls := "coord-trainer__side")(
div(cls := "box")(
h1(trans.coordinates.coordinates.frag()),
h1(trans.coordinates.coordinates()),
if (ctx.isAuth) scoreOption.map { score =>
div(cls := "scores")(scoreCharts(score))
}
else div(cls := "register")(
p(trans.toTrackYourProgress.frag()),
p(trans.toTrackYourProgress()),
p(cls := "signup")(
a(cls := "button", href := routes.Auth.signup)(trans.signUp.frag())
a(cls := "button", href := routes.Auth.signup)(trans.signUp())
)
)
),
@ -71,15 +71,15 @@ object coordinate {
),
div(cls := "coord-trainer__table")(
div(cls := "explanation")(
p(trans.coordinates.knowingTheChessBoard.frag()),
p(trans.coordinates.knowingTheChessBoard()),
ul(
li(trans.coordinates.mostChessCourses.frag()),
li(trans.coordinates.talkToYourChessFriends.frag()),
li(trans.coordinates.youCanAnalyseAGameMoreEffectively.frag())
li(trans.coordinates.mostChessCourses()),
li(trans.coordinates.talkToYourChessFriends()),
li(trans.coordinates.youCanAnalyseAGameMoreEffectively())
),
p(trans.coordinates.aSquareNameAppears.frag())
p(trans.coordinates.aSquareNameAppears())
),
button(cls := "start button button-fat")(trans.coordinates.startTraining.frag())
button(cls := "start button button-fat")(trans.coordinates.startTraining())
),
div(cls := "coord-trainer__score")(0),
div(cls := "coord-trainer__progress")(div(cls := "progress_bar"))

View File

@ -46,7 +46,7 @@ object event {
},
if (e.isFinished) p(cls := "desc")("The event is finished.")
else {
if (e.isNow) a(href := e.url, cls := "button button-fat")(trans.eventInProgress.frag())
if (e.isNow) a(href := e.url, cls := "button button-fat")(trans.eventInProgress())
else ul(cls := "countdown", dataSeconds := ~e.secondsToStart)(
List("Days", "Hours", "Minutes", "Seconds") map { t =>
li(span(cls := t.toLowerCase), t)
@ -113,7 +113,7 @@ object event {
form3.checkbox(form("enabled"), raw("Enabled"), help = raw("Display the event").some, half = true),
form3.group(form("homepageHours"), raw("Hours on homepage (0 to 24)"), half = true, help = raw("Ask on slack first!").some)(form3.input(_, typ = "number"))
),
form3.action(form3.submit(trans.apply.frag()))
form3.action(form3.submit(trans.apply()))
)
private def layout(title: String, css: String = "mod.misc")(body: Frag)(implicit ctx: Context) =

View File

@ -41,7 +41,7 @@ object categ {
val newTopicButton = canWrite option
a(href := routes.ForumTopic.form(categ.slug), cls := "button button-empty button-green text", dataIcon := "m")(
trans.createANewTopic.frag()
trans.createANewTopic()
)
def showTopic(sticky: Boolean)(topic: lila.forum.TopicView) = tr(cls := List("sticky" -> sticky))(
td(cls := "subject")(
@ -89,9 +89,9 @@ object categ {
thead(
tr(
th,
th(cls := "right")(trans.views.frag()),
th(cls := "right")(trans.replies.frag()),
th(trans.lastPost.frag())
th(cls := "right")(trans.views()),
th(cls := "right")(trans.replies()),
th(trans.lastPost())
)
),
tbody(
@ -109,9 +109,9 @@ object categ {
thead(
tr(
th,
th(cls := "right")(trans.topics.frag()),
th(cls := "right")(trans.posts.frag()),
th(trans.lastPost.frag())
th(cls := "right")(trans.topics()),
th(cls := "right")(trans.posts()),
th(trans.lastPost())
)
),
tbody(
@ -130,7 +130,7 @@ object categ {
momentFromNow(post.createdAt)
),
br,
trans.by.frag(authorName(post))
trans.by(authorName(post))
)
}
)

View File

@ -72,9 +72,9 @@ object post {
)(post.text),
div(cls := "edit-buttons")(
a(cls := "edit-post-cancel", href := routes.ForumPost.redirect(post.id), style := "margin-left:20px")(
trans.cancel.frag()
trans.cancel()
),
button(`type` := "submit", cls := "button")(trans.apply.frag())
button(`type` := "submit", cls := "button")(trans.apply())
)
)
)

View File

@ -41,14 +41,14 @@ object topic {
),
st.form(cls := "form3", action := routes.ForumTopic.create(categ.slug), method := "POST")(
form3.group(form("name"), trans.subject.frag())(form3.input(_)(autofocus)),
form3.group(form("post")("text"), trans.message.frag())(form3.textarea(_, klass = "post-text-area")(rows := 10)),
form3.group(form("name"), trans.subject())(form3.input(_)(autofocus)),
form3.group(form("post")("text"), trans.message())(form3.textarea(_, klass = "post-text-area")(rows := 10)),
views.html.base.captcha(form("post"), captcha),
form3.actions(
a(href := routes.ForumCateg.show(categ.slug))(trans.cancel.frag()),
a(href := routes.ForumCateg.show(categ.slug))(trans.cancel()),
isGranted(_.PublicMod) option
form3.submit(frag("Create as mod"), nameValue = (form("post")("modIcon").name, "true").some, icon = "".some),
form3.submit(trans.createTheTopic.frag())
form3.submit(trans.createTheTopic())
)
)
)
@ -104,8 +104,8 @@ object topic {
else if (topic.isOld)
p("This topic has been archived and can no longer be replied to.")
else if (formWithCaptcha.isDefined)
h2(id := "reply")(trans.replyToThisTopic.frag())
else if (topic.closed) p(trans.thisTopicIsNowClosed.frag())
h2(id := "reply")(trans.replyToThisTopic())
else if (topic.closed) p(trans.thisTopicIsNowClosed())
else categ.team.filterNot(myTeam).map { teamId =>
p(
a(href := routes.Team.show(teamId)),
@ -143,15 +143,15 @@ object topic {
method := "POST",
novalidate
)(
form3.group(form("text"), trans.message.frag()) { f =>
form3.group(form("text"), trans.message()) { f =>
form3.textarea(f, klass = "post-text-area")(rows := 10, bits.dataTopic := topic.id)
},
views.html.base.captcha(form, captcha),
form3.actions(
a(href := routes.ForumCateg.show(categ.slug))(trans.cancel.frag()),
a(href := routes.ForumCateg.show(categ.slug))(trans.cancel()),
isGranted(_.PublicMod) option
form3.submit(frag("Reply as mod"), nameValue = (form("modIcon").name, "true").some, icon = "".some),
form3.submit(trans.reply.frag())
form3.submit(trans.reply())
)
)
}

View File

@ -86,7 +86,7 @@ object bits {
ctxOption flatMap { implicit ctx =>
pov.game.daysPerTurn map { days =>
span(cls := "vstext__clock")(
if (days == 1) trans.oneDay.frag() else trans.nbDays.pluralSame(days)
if (days == 1) trans.oneDay() else trans.nbDays.pluralSame(days)
)
}
}

View File

@ -26,12 +26,12 @@ object importGame {
h1(trans.importGame()),
p(cls := "explanation")(trans.importGameExplanation()),
st.form(cls := "form3 import", action := routes.Importer.sendGame(), method := "post")(
form3.group(form("pgn"), trans.pasteThePgnStringHere.frag())(form3.textarea(_)()),
form3.group(form("pgn"), trans.pasteThePgnStringHere())(form3.textarea(_)()),
form3.group(form("pgnFile"), raw("Or upload a PGN file"), klass = "upload") { f =>
form3.file.pgn(f.name)
},
form3.checkbox(form("analyse"), trans.requestAComputerAnalysis.frag(), help = Some(analyseHelp), disabled = ctx.isAnon),
form3.action(form3.submit(trans.importGame.frag(), "/".some))
form3.checkbox(form("analyse"), trans.requestAComputerAnalysis(), help = Some(analyseHelp), disabled = ctx.isAnon),
form3.action(form3.submit(trans.importGame(), "/".some))
)
)
}

View File

@ -90,7 +90,7 @@ object side {
game.winner.map { winner =>
frag(
separator,
winner.color.fold(trans.whiteIsVictorious, trans.blackIsVictorious).frag()
winner.color.fold(trans.whiteIsVictorious, trans.blackIsVictorious)()
)
}
)

View File

@ -64,7 +64,7 @@ object irwin {
)
}
def report(report: lila.irwin.IrwinReport.WithPovs)(implicit ctx: Context) =
def report(report: lila.irwin.IrwinReport.WithPovs)(implicit ctx: Context): Frag =
div(id := "mz_irwin")(
header(
a(cls := "title", href := routes.Irwin.dashboard)(

View File

@ -21,8 +21,8 @@ object bits {
)(implicit ctx: Context) = frag(
div(cls := "lobby__leaderboard lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "C")(trans.leaderboard.frag()),
a(cls := "more", href := routes.User.list)(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "C")(trans.leaderboard()),
a(cls := "more", href := routes.User.list)(trans.more(), " »")
),
div(cls := "lobby__box__content")(
table(tbody(
@ -40,8 +40,8 @@ object bits {
),
div(cls := "lobby__winners lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "g")(trans.tournamentWinners.frag()),
a(cls := "more", href := routes.Tournament.leaderboard)(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "g")(trans.tournamentWinners()),
a(cls := "more", href := routes.Tournament.leaderboard)(trans.more(), " »")
),
div(cls := "lobby__box__content")(
table(tbody(
@ -56,8 +56,8 @@ object bits {
),
div(cls := "lobby__tournaments lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "g")(trans.openTournaments.frag()),
a(cls := "more", href := routes.Tournament.home())(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "g")(trans.openTournaments()),
a(cls := "more", href := routes.Tournament.home())(trans.more(), " »")
),
div(id := "enterable_tournaments", cls := "enterable_list lobby__box__content")(
views.html.tournament.bits.enterable(tours)
@ -65,8 +65,8 @@ object bits {
),
div(cls := "lobby__simuls lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "f")(trans.simultaneousExhibitions.frag()),
a(cls := "more", href := routes.Simul.home())(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "f")(trans.simultaneousExhibitions()),
a(cls := "more", href := routes.Simul.home())(trans.more(), " »")
),
div(id := "enterable_simuls", cls := "enterable_list lobby__box__content")(
views.html.simul.bits.allCreated(simuls)
@ -77,8 +77,8 @@ object bits {
def lastPosts(posts: List[lila.blog.MiniPost])(implicit ctx: Context): Option[Frag] = posts.nonEmpty option
div(cls := "lobby__blog lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "6")(trans.latestUpdates.frag()),
a(cls := "more", href := routes.Blog.index())(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "6")(trans.latestUpdates()),
a(cls := "more", href := routes.Blog.index())(trans.more(), " »")
),
div(cls := "lobby__box__content")(
posts map { post =>

View File

@ -59,20 +59,20 @@ object home {
a(href := routes.Setup.hookForm, cls := List(
"button button-metal config_hook" -> true,
"disabled" -> (playban.isDefined || currentGame.isDefined || ctx.isBot)
), trans.createAGame.frag()),
), trans.createAGame()),
a(href := routes.Setup.friendForm(none), cls := List(
"button button-metal config_friend" -> true,
"disabled" -> currentGame.isDefined
), trans.playWithAFriend.frag()),
), trans.playWithAFriend()),
a(href := routes.Setup.aiForm, cls := List(
"button button-metal config_ai" -> true,
"disabled" -> currentGame.isDefined
), trans.playWithTheMachine.frag())
), trans.playWithTheMachine())
),
div(cls := "lobby__counters")(
a(id := "nb_connected_players", href := ctx.noBlind.option(routes.User.list.toString))(trans.nbPlayers.frag(nbPlayersPlaceholder)),
a(id := "nb_connected_players", href := ctx.noBlind.option(routes.User.list.toString))(trans.nbPlayers(nbPlayersPlaceholder)),
a(id := "nb_games_in_play", href := ctx.noBlind.option(routes.Tv.games.toString))(
trans.nbGamesInPlay.pluralFrag(nbRounds, strong(nbRounds))
trans.nbGamesInPlay.plural(nbRounds, strong(nbRounds))
)
)
),
@ -92,12 +92,12 @@ object home {
div(cls := "timeline", dataHref := routes.Timeline.home)(
views.html.timeline entries userTimeline,
// userTimeline.size >= 8 option
a(cls := "more", href := routes.Timeline.home)(trans.more.frag(), " »")
a(cls := "more", href := routes.Timeline.home)(trans.more(), " »")
)
} getOrElse div(cls := "about-side")(
trans.xIsAFreeYLibreOpenSourceChessServer.frag("Lichess", a(cls := "blue", href := routes.Plan.features)(trans.really.txt())),
trans.xIsAFreeYLibreOpenSourceChessServer("Lichess", a(cls := "blue", href := routes.Plan.features)(trans.really.txt())),
" ",
a(cls := "blue", href := "/about")(trans.aboutX.frag("lichess.org"), "...")
a(cls := "blue", href := "/about")(trans.aboutX("lichess.org"), "...")
)
),
featured map { g =>
@ -110,7 +110,7 @@ object home {
div(cls := "lobby__puzzle", title := trans.clickToSolve.txt())(
raw(p.html),
div(cls := "vstext")(
trans.puzzleOfTheDay.frag(),
trans.puzzleOfTheDay(),
br,
p.color.fold(trans.whitePlays, trans.blackPlays)()
)
@ -119,8 +119,8 @@ object home {
ctx.noBot option bits.underboards(tours, simuls, leaderboard, tournamentWinners),
ctx.noKid option div(cls := "lobby__forum lobby__box")(
div(cls := "lobby__box__top")(
span(cls := "title text", dataIcon := "d")(trans.latestForumPosts.frag()),
a(cls := "more", href := routes.ForumCateg.index)(trans.more.frag(), " »")
span(cls := "title text", dataIcon := "d")(trans.latestForumPosts()),
a(cls := "more", href := routes.ForumCateg.index)(trans.more(), " »")
),
div(cls := "lobby__box__content")(
views.html.forum.post recent forumRecent
@ -132,32 +132,32 @@ object home {
iconTag(patronIconChar),
span(cls := "lobby__support__text")(
strong("Lichess Patron"),
span(trans.directlySupportLichess.frag())
span(trans.directlySupportLichess())
)
),
a(href := routes.Page.swag)(
iconTag(""),
span(cls := "lobby__support__text")(
strong("Swag Store"),
span(trans.playChessInStyle.frag())
span(trans.playChessInStyle())
)
)
),
div(cls := "lobby__about")(
a(href := "/about")(trans.aboutX.frag("lichess.org")),
a(href := "/about")(trans.aboutX("lichess.org")),
a(href := "/faq")("FAQ"),
a(href := "/contact")(trans.contact.frag()),
a(href := "/contact")(trans.contact()),
ctx.noKid option frag(
a(href := "/mobile")(trans.mobileApp.frag()),
a(href := "/developers")(trans.webmasters.frag()),
a(href := "/help/contribute")(trans.contribute.frag()),
a(href := "/patron")(trans.donate.frag())
a(href := "/mobile")(trans.mobileApp()),
a(href := "/developers")(trans.webmasters()),
a(href := "/help/contribute")(trans.contribute()),
a(href := "/patron")(trans.donate())
),
a(href := "/thanks")(trans.thankYou.frag()),
a(href := routes.Page.tos)(trans.termsOfService.frag()),
a(href := routes.Page.privacy)(trans.privacy.frag()),
a(href := "https://database.lichess.org/")(trans.database.frag()),
a(href := "https://github.com/ornicar/lila")(trans.sourceCode.frag())
a(href := "/thanks")(trans.thankYou()),
a(href := routes.Page.tos)(trans.termsOfService()),
a(href := routes.Page.privacy)(trans.privacy()),
a(href := "https://database.lichess.org/")(trans.database()),
a(href := "https://github.com/ornicar/lila")(trans.sourceCode())
)
)
}

View File

@ -26,7 +26,7 @@ object form {
moreJs = jsTag("message.js")
) {
main(cls := "message-new box box-pad")(
h1(trans.composeMessage.frag()),
h1(trans.composeMessage()),
reqUser.ifFalse(canMessage).map { u =>
frag(
br, br, hr, br,
@ -42,7 +42,7 @@ object form {
action := s"${routes.Message.create()}${reqUser.??(u => "?username=" + u.username)}",
method := "post"
)(
form3.group(form("username"), trans.recipient.frag()) { f =>
form3.group(form("username"), trans.recipient()) { f =>
reqUser map { user =>
frag(
userLink(user),
@ -63,7 +63,7 @@ object form {
form3.group(form("preset"), frag("Preset")) { form3.select(_, Nil) },
embedJsUnsafe(s"""lichess_mod_presets=${safeJsonValue(lila.message.ModPreset.asJson)}""")
),
form3.group(form("subject"), trans.subject.frag()) { f =>
form3.group(form("subject"), trans.subject()) { f =>
input(
cls := "form-control",
required,
@ -79,8 +79,8 @@ object form {
form3.textarea(f)(required)
},
form3.actions(
a(cls := "cancel", href := routes.Message.inbox())(trans.cancel.frag()),
button(cls := "button text", dataIcon := "E", tpe := "submit")(trans.send.frag())
a(cls := "cancel", href := routes.Message.inbox())(trans.cancel()),
button(cls := "button text", dataIcon := "E", tpe := "submit")(trans.send())
)
)
}

View File

@ -17,7 +17,7 @@ object inbox {
) {
main(cls := "message-list box")(
div(cls := "box__top")(
h1(trans.inbox.frag()),
h1(trans.inbox()),
threads.nbResults > 0 option div(cls := "box__top__actions")(
select(cls := "select")(
option(value := "")("Select"),
@ -32,7 +32,7 @@ object inbox {
option(value := "read")("Mark as read"),
option(value := "delete")("Delete")
),
a(href := routes.Message.form, cls := "button button-green text", dataIcon := "m")(trans.composeMessage.frag())
a(href := routes.Message.form, cls := "button button-green text", dataIcon := "m")(trans.composeMessage())
)
),
table(cls := "slist slist-pad")(
@ -51,7 +51,7 @@ object inbox {
)
}
)
else tbody(tr(td(trans.noNewMessages.frag())))
else tbody(tr(td(trans.noNewMessages())))
)
)
}

View File

@ -73,8 +73,8 @@ object thread {
errMsg(form("text"))
),
div(cls := "actions")(
a(cls := "cancel", href := routes.Message.inbox(1))(trans.cancel.frag()),
button(cls := "button text", dataIcon := "E", tpe := "submit")(trans.send.frag())
a(cls := "cancel", href := routes.Message.inbox(1))(trans.cancel()),
button(cls := "button text", dataIcon := "E", tpe := "submit")(trans.send())
)
)
}

View File

@ -14,7 +14,7 @@ object mobile {
) {
main(
div(cls := "mobile page-small box box-pad")(
h1(trans.playChessEverywhere.frag()),
h1(trans.playChessEverywhere()),
div(cls := "sides")(
div(cls := "left-side")(
div(cls := "stores")(
@ -24,26 +24,26 @@ object mobile {
div(cls := "apk")(
raw(~apkDoc.getHtml("doc.content", resolver))
),
h2(trans.asFreeAsLichess.frag()),
h2(trans.asFreeAsLichess()),
ul(cls := "block")(
li(trans.builtForTheLoveOfChessNotMoney.frag()),
li(trans.everybodyGetsAllFeaturesForFree.frag()),
li(trans.zeroAdvertisement.frag()),
li(trans.builtForTheLoveOfChessNotMoney()),
li(trans.everybodyGetsAllFeaturesForFree()),
li(trans.zeroAdvertisement()),
li("Entirely ", a(href := "https://github.com/veloce/lichobile")("Open Source"))
),
h2(trans.fullFeatured.frag()),
h2(trans.fullFeatured()),
ul(cls := "block")(
li(trans.phoneAndTablet.frag()),
li(trans.bulletBlitzClassical.frag()),
li(trans.correspondenceChess.frag()),
li(trans.onlineAndOfflinePlay.frag()),
li(trans.tournaments.frag()),
li(trans.puzzles.frag()),
li(trans.gameAnalysis.frag()),
li(trans.boardEditor.frag()),
li(trans.phoneAndTablet()),
li(trans.bulletBlitzClassical()),
li(trans.correspondenceChess()),
li(trans.onlineAndOfflinePlay()),
li(trans.tournaments()),
li(trans.puzzles()),
li(trans.gameAnalysis()),
li(trans.boardEditor()),
li("Lichess TV"),
li(trans.followAndChallengeFriends.frag()),
li(trans.availableInNbLanguages.pluralSameFrag(80))
li(trans.followAndChallengeFriends()),
li(trans.availableInNbLanguages.pluralSame(80))
)
),
div(cls := "right-side")(

View File

@ -61,7 +61,7 @@ object form {
),
form3.actions(
a(href := routes.OAuthApp.index)("Cancel"),
form3.submit(trans.apply.frag())
form3.submit(trans.apply())
)
)
}

View File

@ -44,7 +44,7 @@ object create {
),
form3.actions(
a(href := routes.OAuthToken.index)("Cancel"),
form3.submit(trans.apply.frag())
form3.submit(trans.apply())
)
)
)

View File

@ -32,8 +32,8 @@ object embed {
div(id := "daily-puzzle", cls := "embedded", title := trans.clickToSolve.txt())(
raw(daily.html),
div(cls := "vstext", style := "text-align: center; justify-content: center")(
trans.puzzleOfTheDay.frag(), br,
daily.color.fold(trans.whitePlays, trans.blackPlays).frag()
trans.puzzleOfTheDay(), br,
daily.color.fold(trans.whitePlays, trans.blackPlays)()
)
),
jQueryTag,

View File

@ -18,9 +18,9 @@ object bits {
div(cls := "box__top")(
h1(userLink(u, withOnline = false)),
div(cls := "actions")(
trans.nbFollowers.pluralSameFrag(pag.nbResults),
trans.nbFollowers.pluralSame(pag.nbResults),
" ", amp, " ",
a(href := routes.Relation.following(u.username))(trans.nbFollowing.pluralSameFrag(nbFollowing))
a(href := routes.Relation.following(u.username))(trans.nbFollowing.pluralSame(nbFollowing))
)
),
pagTable(pag, routes.Relation.followers(u.username))
@ -31,9 +31,9 @@ object bits {
div(cls := "box__top")(
h1(userLink(u, withOnline = false)),
div(cls := "actions")(
trans.nbFollowing.pluralSameFrag(pag.nbResults),
trans.nbFollowing.pluralSame(pag.nbResults),
" ", amp, " ",
a(href := routes.Relation.followers(u.username))(trans.nbFollowers.pluralSameFrag(nbFollowers))
a(href := routes.Relation.followers(u.username))(trans.nbFollowers.pluralSame(nbFollowers))
)
),
pagTable(pag, routes.Relation.following(u.username))
@ -44,7 +44,7 @@ object bits {
div(cls := "box__top")(
h1(userLink(u, withOnline = false)),
div(cls := "actions")(
trans.blocks.pluralSameFrag(pag.nbResults)
trans.blocks.pluralSame(pag.nbResults)
)
),
pagTable(pag, routes.Relation.blocks())
@ -67,7 +67,7 @@ object bits {
tr(cls := "paginated")(
td(userLink(r.user)),
td(showBestPerf(r.user)),
td(trans.nbGames.pluralSameFrag(r.user.count.game)),
td(trans.nbGames.pluralSame(r.user.count.game)),
td(actions(r.user.id, relation = r.relation, followable = r.followable, blocked = false))
)
}

View File

@ -18,19 +18,19 @@ object mini {
cls := "btn-rack__btn relation-button text",
dataIcon := "h",
href := s"${routes.Relation.follow(userId)}?mini=1"
)(trans.follow.frag())
)(trans.follow())
case Some(true) => a(
cls := "btn-rack__btn relation-button text",
title := trans.unfollow.txt(),
href := s"${routes.Relation.unfollow(userId)}?mini=1",
dataIcon := "h"
)(trans.following.frag())
)(trans.following())
case Some(false) => a(
cls := "btn-rack__btn relation-button text",
title := trans.unblock.txt(),
href := s"${routes.Relation.unblock(userId)}?mini=1",
dataIcon := "k"
)(trans.blocked.frag())
)(trans.blocked())
case _ => emptyFrag
}
}

View File

@ -49,8 +49,8 @@ object form {
form3.group(form("throttle"), raw("Throttle in seconds"), help = raw("Optional, to manually throttle requests. Min 2s, max 60s.").some, half = true)(form3.input(_, typ = "number"))
),
form3.actions(
a(href := routes.Relay.index(1))(trans.cancel.frag()),
form3.submit(trans.apply.frag())
a(href := routes.Relay.index(1))(trans.cancel()),
form3.submit(trans.apply())
)
)
}

View File

@ -18,28 +18,28 @@ object form {
moreJs = captchaTag
) {
main(cls := "page-small box box-pad report")(
h1(trans.reportAUser.frag()),
h1(trans.reportAUser()),
st.form(
cls := "form3",
action := s"${routes.Report.create()}${reqUser.??(u => "?username=" + u.username)}",
method := "post"
)(
form3.globalError(form),
form3.group(form("username"), trans.user.frag(), klass = "field_to") { f =>
form3.group(form("username"), trans.user(), klass = "field_to") { f =>
reqUser.map { user =>
frag(userLink(user), form3.hidden(f, user.id.some))
}.getOrElse {
div(form3.input(f, klass = "user-autocomplete")(dataTag := "span"))
}
},
form3.group(form("reason"), trans.reason.frag()) { f =>
form3.group(form("reason"), trans.reason()) { f =>
form3.select(f, translatedReasonChoices, trans.whatIsIheMatter.txt().some)
},
form3.group(form("text"), trans.description.frag(), help = trans.reportDescriptionHelp.frag().some)(form3.textarea(_)(rows := 8)),
form3.group(form("text"), trans.description(), help = trans.reportDescriptionHelp().some)(form3.textarea(_)(rows := 8)),
views.html.base.captcha(form, captcha),
form3.actions(
a(href := routes.Lobby.home())(trans.cancel.frag()),
form3.submit(trans.send.frag())
a(href := routes.Lobby.home())(trans.cancel()),
form3.submit(trans.send())
)
)
)

View File

@ -83,10 +83,10 @@ object bits {
span(cls := "loss")(s.losses, " L"), " / ",
s.ongoing, " ongoing"
)
} getOrElse trans.currentGames.frag(),
} getOrElse trans.currentGames(),
"round-toggle-autoswitch" |> { id =>
span(cls := "move-on switcher", st.title := trans.automaticallyProceedToNextGameAfterMoving.txt())(
label(`for` := id)(trans.autoSwitch.frag()),
label(`for` := id)(trans.autoSwitch()),
span(cls := "switch")(
input(st.id := id, cls := "cmn-toggle", tpe := "checkbox"),
label(`for` := id)
@ -103,7 +103,7 @@ object bits {
span(cls := "meta")(
playerText(pov.opponent, withRating = false),
span(cls := "indicator")(
if (pov.isMyTurn) pov.remainingSeconds.fold(trans.yourTurn())(secondsFromNow(_, true))
if (pov.isMyTurn) pov.remainingSeconds.fold[Frag](trans.yourTurn())(secondsFromNow(_, true))
else nbsp
)
)

View File

@ -38,7 +38,7 @@ private object bits {
def renderVariant(form: Form[_], variants: List[SelectChoice])(implicit ctx: Context) =
div(cls := "variant label_select")(
renderLabel(form("variant"), trans.variant.frag()),
renderLabel(form("variant"), trans.variant()),
renderSelect(form("variant"), variants.filter {
case (id, _, _) => ctx.noBlind || lila.game.Game.blindModeVariants.exists(_.id.toString == id)
})

View File

@ -19,7 +19,7 @@ object forms {
def hook(form: Form[_])(implicit ctx: Context) = layout(
form,
"hook",
trans.createAGame.frag(),
trans.createAGame(),
routes.Setup.hook("uid-placeholder")
) {
frag(
@ -31,7 +31,7 @@ object forms {
),
ctx.noBlind option div(cls := "optional_config")(
div(cls := "rating-range-config slider")(
trans.ratingRange.frag(),
trans.ratingRange(),
": ",
span(cls := "range")("? - ?"),
div(cls := "rating-range")(
@ -47,26 +47,26 @@ object forms {
}
def ai(form: Form[_], ratings: Map[Int, Int], validFen: Option[lila.setup.ValidFen])(implicit ctx: Context) =
layout(form, "ai", trans.playWithTheMachine.frag(), routes.Setup.ai) {
layout(form, "ai", trans.playWithTheMachine(), routes.Setup.ai) {
frag(
renderVariant(form, translatedAiVariantChoices),
fenInput(form("fen"), true, validFen),
renderTimeMode(form, lila.setup.AiConfig),
if (ctx.blind) frag(
renderLabel(form("level"), trans.level.frag()),
renderLabel(form("level"), trans.level()),
renderSelect(form("level"), lila.setup.AiConfig.levelChoices),
blindSideChoice(form)
)
else frag(
br,
trans.level.frag(),
trans.level(),
div(cls := "level buttons")(
div(id := "config_level")(
renderRadios(form("level"), lila.setup.AiConfig.levelChoices)
),
div(cls := "ai_info")(
ratings.toList.map {
case (level, rating) => div(cls := s"${prefix}level_$level")(trans.aiNameLevelAiLevel.frag("A.I.", level))
case (level, rating) => div(cls := s"${prefix}level_$level")(trans.aiNameLevelAiLevel("A.I.", level))
}
)
)
@ -101,7 +101,7 @@ object forms {
private def blindSideChoice(form: Form[_])(implicit ctx: Context) =
ctx.blind option frag(
renderLabel(form("color"), trans.side.frag()),
renderLabel(form("color"), trans.side()),
renderSelect(form("color").copy(value = "random".some), translatedSideChoices)
)
@ -146,7 +146,7 @@ object forms {
div(cls := "ratings")(
lila.rating.PerfType.nonPuzzle.map { perfType =>
div(cls := perfType.key)(
trans.perfRatingX.frag(
trans.perfRatingX(
raw(s"""<strong data-icon="${perfType.iconChar}">${me.perfs(perfType.key).map(_.intRating).getOrElse("?")}</strong> ${perfType.name}""")
)
)

View File

@ -21,9 +21,9 @@ object bits {
title = trans.noSimulFound.txt()
) {
main(cls := "page-small box box-pad")(
h1(trans.noSimulFound.frag()),
p(trans.noSimulExplanation.frag()),
p(a(href := routes.Simul.home())(trans.returnToSimulHomepage.frag()))
h1(trans.noSimulFound()),
p(trans.noSimulExplanation()),
p(a(href := routes.Simul.home())(trans.returnToSimulHomepage()))
)
}

View File

@ -20,28 +20,28 @@ object form {
moreCss = cssTag("simul.form")
) {
main(cls := "box box-pad page-small simul-form")(
h1(trans.hostANewSimul.frag()),
h1(trans.hostANewSimul()),
st.form(cls := "form3", action := routes.Simul.create(), method := "POST")(
br, br,
p(trans.whenCreateSimul.frag()),
p(trans.whenCreateSimul()),
br, br,
globalError(form),
form3.group(form("variant"), trans.simulVariantsHint.frag()) { f =>
form3.group(form("variant"), trans.simulVariantsHint()) { f =>
div(cls := "variants")(
views.html.setup.filter.renderCheckboxes(form, "variants", form.value.map(_.variants.map(_.toString)).getOrElse(Nil), translatedVariantChoicesWithVariants)
)
},
form3.split(
form3.group(form("clockTime"), raw("Clock initial time"), help = trans.simulClockHint.frag().some, half = true)(form3.select(_, clockTimeChoices)),
form3.group(form("clockTime"), raw("Clock initial time"), help = trans.simulClockHint().some, half = true)(form3.select(_, clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(form3.select(_, clockIncrementChoices))
),
form3.group(form("clockExtra"), trans.simulHostExtraTime.frag(), help = trans.simulAddExtraTime.frag().some)(
form3.group(form("clockExtra"), trans.simulHostExtraTime(), help = trans.simulAddExtraTime().some)(
form3.select(_, clockExtraChoices)
),
form3.group(form("color"), raw("Host color for each game"))(form3.select(_, colorChoices)),
form3.actions(
a(href := routes.Simul.home())(trans.cancel.frag()),
form3.submit(trans.hostANewSimul.frag(), icon = "g".some)
a(href := routes.Simul.home())(trans.cancel()),
form3.submit(trans.hostANewSimul(), icon = "g".some)
)
)
)

View File

@ -33,10 +33,10 @@ object home {
st.aside(cls := "page-menu__menu simul-list__help")(
p(trans.aboutSimul()),
img(src := staticUrl("images/fischer-simul.jpg"), alt := "Simul IRL with Bobby Fischer")(
em("[1964] ", trans.aboutSimulImage.frag()),
p(trans.aboutSimulRealLife.frag()),
p(trans.aboutSimulRules.frag()),
p(trans.aboutSimulSettings.frag())
em("[1964] ", trans.aboutSimulImage()),
p(trans.aboutSimulRealLife()),
p(trans.aboutSimulRules()),
p(trans.aboutSimulSettings())
)
),
div(cls := "page-menu__content simul-list__content")(

View File

@ -16,13 +16,13 @@ object homeInner {
finisheds: List[lila.simul.Simul]
)(implicit ctx: Context) =
div(cls := "box")(
h1(trans.simultaneousExhibitions.frag()),
h1(trans.simultaneousExhibitions()),
table(cls := "slist slist-pad")(
thead(
tr(
th(trans.createdSimuls.frag()),
th(trans.host.frag()),
th(trans.players.frag())
th(trans.createdSimuls()),
th(trans.host()),
th(trans.players())
)
),
tbody(
@ -35,16 +35,16 @@ object homeInner {
},
ctx.isAuth option tr(cls := "create")(
td(colspan := "4")(
a(href := routes.Simul.form(), cls := "action button text")(trans.hostANewSimul.frag())
a(href := routes.Simul.form(), cls := "action button text")(trans.hostANewSimul())
)
)
),
starteds.nonEmpty option (frag(
thead(
tr(
th(trans.eventInProgress.frag()),
th(trans.host.frag()),
th(trans.players.frag())
th(trans.eventInProgress()),
th(trans.host()),
th(trans.players())
)
),
starteds.map { sim =>
@ -57,9 +57,9 @@ object homeInner {
)),
thead(
tr(
th(trans.finished.frag()),
th(trans.host.frag()),
th(trans.players.frag())
th(trans.finished()),
th(trans.host()),
th(trans.players())
)
),
tbody(

View File

@ -44,21 +44,21 @@ chat: ${chatOption.fold("null")(c => safeJsonValue(views.html.chat.json(c.chat,
div(cls := "setup")(
sim.variants.map(_.name).mkString(", "),
" • ",
trans.casual.frag()
trans.casual()
)
)
),
trans.simulHostExtraTime.frag(),
trans.simulHostExtraTime(),
": ",
pluralize("minute", sim.clock.hostExtraMinutes),
br,
trans.hostColorX.frag(sim.color match {
case Some("white") => trans.white.frag()
case Some("black") => trans.black.frag()
case _ => trans.randomColor.frag()
trans.hostColorX(sim.color match {
case Some("white") => trans.white()
case Some("black") => trans.black()
case _ => trans.randomColor()
})
),
trans.by.frag(usernameOrId(sim.hostId)),
trans.by(usernameOrId(sim.hostId)),
" ",
momentFromNow(sim.createdAt)
),

View File

@ -89,7 +89,7 @@ object help {
frag(
h1("Embed a chess game in your site"),
raw(s"""<iframe src="/embed/MPJcy1JW?bg=auto&theme=auto" $args></iframe>"""),
p(raw("""On a game analysis page, click the <em>"FEN &amp; PGN"</em> tab at the bottom, then """), "\"", em(trans.embedInYourWebsite.frag(), "\".")),
p(raw("""On a game analysis page, click the <em>"FEN &amp; PGN"</em> tab at the bottom, then """), "\"", em(trans.embedInYourWebsite(), "\".")),
parameters,
p("The text is automatically translated to your visitor's language.")
)
@ -124,18 +124,18 @@ object help {
def activeCls(c: String) = cls := active.activeO(c)
main(cls := "page-menu")(
st.nav(cls := "page-menu__menu subnav")(
a(activeCls("about"), href := routes.Page.about)(trans.aboutX.frag("lichess.org")),
a(activeCls("about"), href := routes.Page.about)(trans.aboutX("lichess.org")),
a(activeCls("faq"), href := routes.Main.faq)("FAQ"),
a(activeCls("contact"), href := routes.Main.contact)(trans.contact.frag()),
a(activeCls("tos"), href := routes.Page.tos)(trans.termsOfService.frag()),
a(activeCls("privacy"), href := routes.Page.privacy)(trans.privacy.frag()),
a(activeCls("contact"), href := routes.Main.contact)(trans.contact()),
a(activeCls("tos"), href := routes.Page.tos)(trans.termsOfService()),
a(activeCls("privacy"), href := routes.Page.privacy)(trans.privacy()),
a(activeCls("master"), href := routes.Page.master)("Title verification"),
sep,
a(activeCls("help"), href := routes.Page.help)(trans.contribute.frag()),
a(activeCls("thanks"), href := routes.Page.thanks)(trans.thankYou.frag()),
a(activeCls("help"), href := routes.Page.help)(trans.contribute()),
a(activeCls("thanks"), href := routes.Page.thanks)(trans.thankYou()),
sep,
a(activeCls("webmasters"), href := routes.Main.webmasters)(trans.webmasters.frag()),
a(activeCls("database"), href := "https://database.lichess.org")(trans.database.frag(), external),
a(activeCls("webmasters"), href := routes.Main.webmasters)(trans.webmasters()),
a(activeCls("database"), href := "https://database.lichess.org")(trans.database(), external),
a(activeCls("api"), href := routes.Api.index)("API", external),
a(activeCls("source"), href := "https://github.com/ornicar/lila")("Source code", external),
sep,

View File

@ -25,7 +25,7 @@ object ratingDistribution {
main(cls := "page-menu")(
user.bits.communityMenu("ratings"),
div(cls := "rating-stats page-menu__content box box-pad")(
h1(trans.weeklyPerfTypeRatingDistribution.frag(views.html.base.bits.mselect(
h1(trans.weeklyPerfTypeRatingDistribution(views.html.base.bits.mselect(
"variant-stats",
span(perfType.name),
PerfType.leaderboardable map { pt =>

View File

@ -124,7 +124,7 @@ object edit {
else
form3.checkbox(form("approval.ignored"), raw("Ignore further approval requests"), help = modsOnly, half = true)
),
form3.action(form3.submit(trans.apply.frag()))
form3.action(form3.submit(trans.apply()))
),
form3.split(
form3.group(form("twitch"), raw("Your Twitch username or URL"), help = raw("Optional. Leave empty if none").some, half = true)(form3.input(_)),
@ -138,7 +138,7 @@ object edit {
form3.group(form("description"), raw("Long description"))(form3.textarea(_)(rows := 10)),
form3.actions(
a(href := routes.Streamer.show(s.user.username))("Cancel"),
form3.submit(trans.apply.frag())
form3.submit(trans.apply())
)
)
)

View File

@ -50,7 +50,7 @@ object header {
strong(s.status)
)
} getOrElse frag(
p(cls := "at")(trans.lastSeenActive.frag(momentFromNow(s.streamer.seenAt))),
p(cls := "at")(trans.lastSeenActive(momentFromNow(s.streamer.seenAt))),
s.streamer.liveAt.map { liveAt =>
p(cls := "at")("Last stream ", momentFromNow(liveAt))
}
@ -62,8 +62,8 @@ object header {
"follow button text" -> true,
"active" -> f
), tpe := "submit")(
span(cls := "active-no")(trans.follow.frag()),
span(cls := "active-yes")(trans.following.frag())
span(cls := "active-no")(trans.follow()),
span(cls := "active-yes")(trans.following())
)
}
)

View File

@ -56,7 +56,7 @@ object index {
strong(s.status)
)
} getOrElse frag(
p(cls := "at")(trans.lastSeenActive.frag(momentFromNow(s.streamer.seenAt))),
p(cls := "at")(trans.lastSeenActive(momentFromNow(s.streamer.seenAt))),
s.streamer.liveAt.map { liveAt =>
p(cls := "at")("Last stream ", momentFromNow(liveAt))
}

View File

@ -17,14 +17,14 @@ object bits {
),
ctx.me.??(_.canTeam) option
a(cls := tab.active("mine"), href := routes.Team.mine())(
trans.myTeams.frag()
trans.myTeams()
),
a(cls := tab.active("all"), href := routes.Team.all())(
trans.allTeams.frag()
trans.allTeams()
),
ctx.me.??(_.canTeam) option
a(cls := tab.active("form"), href := routes.Team.form())(
trans.newTeam.frag()
trans.newTeam()
)
)
}

View File

@ -22,16 +22,16 @@ object form {
h1(trans.newTeam()),
st.form(cls := "form3", action := routes.Team.create(), method := "POST")(
form3.globalError(form),
form3.group(form("name"), trans.name.frag())(form3.input(_)),
form3.group(form("open"), trans.joiningPolicy.frag()) { f =>
form3.group(form("name"), trans.name())(form3.input(_)),
form3.group(form("open"), trans.joiningPolicy()) { f =>
form3.select(form("open"), Seq(0 -> trans.aConfirmationIsRequiredToJoin.txt(), 1 -> trans.anyoneCanJoin.txt()))
},
form3.group(form("location"), trans.location.frag())(form3.input(_)),
form3.group(form("description"), trans.description.frag())(form3.textarea(_)(rows := 10)),
form3.group(form("location"), trans.location())(form3.input(_)),
form3.group(form("description"), trans.description())(form3.textarea(_)(rows := 10)),
views.html.base.captcha(form, captcha),
form3.actions(
a(href := routes.Team.home(1))(trans.cancel()),
form3.submit(trans.newTeam.frag())
form3.submit(trans.newTeam())
)
)
)
@ -50,14 +50,14 @@ object form {
a(cls := "button button-empty", href := routes.Team.kick(t.id))("Kick someone out of the team"),
a(cls := "button button-empty", href := routes.Team.changeOwner(t.id))("Appoint another team owner")
),
form3.group(form("open"), trans.joiningPolicy.frag()) { f =>
form3.group(form("open"), trans.joiningPolicy()) { f =>
form3.select(f, Seq(0 -> trans.aConfirmationIsRequiredToJoin.txt(), 1 -> trans.anyoneCanJoin.txt()))
},
form3.group(form("location"), trans.location.frag())(form3.input(_)),
form3.group(form("description"), trans.description.frag())(form3.textarea(_)(rows := 10)),
form3.group(form("location"), trans.location())(form3.input(_)),
form3.group(form("description"), trans.description())(form3.textarea(_)(rows := 10)),
form3.actions(
a(href := routes.Team.show(t.id), style := "margin-left:20px")(trans.cancel()),
form3.submit(trans.apply.frag())
form3.submit(trans.apply())
)
),
hr,

View File

@ -31,7 +31,7 @@ object request {
views.html.base.captcha(form, captcha),
form3.actions(
a(href := routes.Team.show(t.slug))(trans.cancel()),
form3.submit(trans.joinTeam.frag())
form3.submit(trans.joinTeam())
)
)
)

View File

@ -26,7 +26,7 @@ object show {
h1(cls := "text", dataIcon := "f")(t.name, " ", em("TEAM")),
div(
if (t.disabled) span(cls := "staff")("CLOSED")
else trans.nbMembers.pluralFrag(t.nbMembers, raw("<strong>" + t.nbMembers.localize + "</strong>"))
else trans.nbMembers.plural(t.nbMembers, strong(t.nbMembers.localize))
)
),
(info.mine || t.enabled) option div(cls := "team-show__content")(

View File

@ -23,7 +23,7 @@ object timeline {
moreCss = cssTag("slist")
)(
main(cls := "timeline page-small box")(
h1(trans.timeline.frag()),
h1(trans.timeline()),
table(cls := "slist slist-pad")(
tbody(
filterEntries(entries) map { e =>
@ -40,7 +40,7 @@ object timeline {
private def entry(e: lila.timeline.Entry)(implicit ctx: Context) = frag(
e.decode.map[Frag] {
case Follow(u1, u2) => playHtmlToFrag(trans.xStartedFollowingY(userIdLink(u1.some, withOnline = false), userIdLink(u2.some, withOnline = false)))
case Follow(u1, u2) => trans.xStartedFollowingY(userIdLink(u1.some, withOnline = false), userIdLink(u2.some, withOnline = false))
case TeamJoin(userId, teamId) => trans.xJoinedTeamY(userIdLink(userId.some, withOnline = false), teamLink(teamId, withIcon = false))
case TeamCreate(userId, teamId) => trans.xCreatedTeamY(userIdLink(userId.some, withOnline = false), teamLink(teamId, withIcon = false))
case ForumPost(userId, topicId, topicName, postId) => trans.xPostedInForumY(userIdLink(userId.some, withOnline = false), raw("""<a href="%s" title="%s">%s</a>""".format(routes.ForumPost.redirect(postId), escapeHtml(topicName), shorten(topicName, 30))))

View File

@ -28,12 +28,12 @@ object bits {
title = trans.tournamentNotFound.txt()
) {
main(cls := "page-small box box-pad")(
h1(trans.tournamentNotFound.frag()),
p(trans.tournamentDoesNotExist.frag()),
p(trans.tournamentMayHaveBeenCanceled.frag()),
h1(trans.tournamentNotFound()),
p(trans.tournamentDoesNotExist()),
p(trans.tournamentMayHaveBeenCanceled()),
br,
br,
a(href := routes.Tournament.home())(trans.returnToTournamentsHomepage.frag())
a(href := routes.Tournament.home())(trans.returnToTournamentsHomepage())
)
}

View File

@ -77,12 +77,12 @@ object crud {
form3.group(form("clockTime"), raw("Clock time"), half = true)(form3.select(_, DataForm.clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(form3.select(_, DataForm.clockIncrementChoices))
),
form3.group(form("position"), trans.startPosition.frag())(tournament.form.startingPosition(_)),
form3.group(form("position"), trans.startPosition())(tournament.form.startingPosition(_)),
hr,
h2("Conditions of entry"),
tournament.form.condition(form, auto = false, Nil),
form3.action(form3.submit(trans.apply.frag()))
form3.action(form3.submit(trans.apply()))
)
def index(tours: Paginator[Tournament])(implicit ctx: Context) = layout(

View File

@ -25,7 +25,7 @@ object form {
h1(trans.createANewTournament()),
st.form(cls := "form3", action := routes.Tournament.create, method := "POST")(
DataForm.canPickName(me) ?? {
form3.group(form("name"), trans.name.frag()) { f =>
form3.group(form("name"), trans.name()) { f =>
div(
form3.input(f), " Arena", br,
small(cls := "form-help")(
@ -37,18 +37,18 @@ object form {
}
},
form3.split(
form3.checkbox(form("rated"), trans.rated.frag(), help = raw("Games are rated<br>and impact players ratings").some),
form3.checkbox(form("rated"), trans.rated(), help = raw("Games are rated<br>and impact players ratings").some),
st.input(tpe := "hidden", name := form("rated").name, value := "false"), // hack allow disabling rated
form3.group(form("variant"), trans.variant.frag(), half = true)(form3.select(_, translatedVariantChoicesWithVariants.map(x => x._1 -> x._2)))
form3.group(form("variant"), trans.variant(), half = true)(form3.select(_, translatedVariantChoicesWithVariants.map(x => x._1 -> x._2)))
),
form3.group(form("position"), trans.startPosition.frag(), klass = "position")(startingPosition(_)),
form3.group(form("position"), trans.startPosition(), klass = "position")(startingPosition(_)),
form3.split(
form3.group(form("clockTime"), raw("Clock initial time"), half = true)(form3.select(_, DataForm.clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(form3.select(_, DataForm.clockIncrementChoices))
),
form3.split(
form3.group(form("minutes"), trans.duration.frag(), half = true)(form3.select(_, DataForm.minuteChoices)),
form3.group(form("waitMinutes"), trans.timeBeforeTournamentStarts.frag(), half = true)(form3.select(_, DataForm.waitMinuteChoices))
form3.group(form("minutes"), trans.duration(), half = true)(form3.select(_, DataForm.minuteChoices)),
form3.group(form("waitMinutes"), trans.timeBeforeTournamentStarts(), half = true)(form3.select(_, DataForm.waitMinuteChoices))
),
form3.globalError(form),
fieldset(cls := "conditions")(
@ -62,7 +62,7 @@ object form {
a(cls := "show")(trans.showAdvancedSettings())
),
div(cls := "form")(
form3.group(form("password"), trans.password.frag(), help = raw("Make the tournament private, and restrict access with a password").some)(form3.input(_)),
form3.group(form("password"), trans.password(), help = raw("Make the tournament private, and restrict access with a password").some)(form3.input(_)),
condition(form, auto = true, teams = teams),
input(tpe := "hidden", name := form("berserkable").name, value := "false"), // hack allow disabling berserk
form3.group(form("startDate"), raw("Custom start date"), help = raw("""This overrides the "Time before tournament starts" setting""").some)(form3.flatpickr(_))
@ -70,7 +70,7 @@ object form {
),
form3.actions(
a(href := routes.Tournament.home())(trans.cancel()),
form3.submit(trans.createANewTournament.frag(), icon = "g".some)
form3.submit(trans.createANewTournament(), icon = "g".some)
)
)
),

View File

@ -38,7 +38,7 @@ var d=lichess.StrongSocket.defaults;d.params.flag="tournament";d.events.reload=a
main(cls := "tour-home")(
st.aside(cls := "tour-home__side")(
h2(
a(href := routes.Tournament.leaderboard)(trans.leaderboard.frag())
a(href := routes.Tournament.leaderboard)(trans.leaderboard())
),
ul(cls := "leaderboard")(
winners.top.map { w =>
@ -50,9 +50,9 @@ var d=lichess.StrongSocket.defaults;d.params.flag="tournament";d.events.reload=a
),
p(cls := "tour__links")(
a(href := "/tournament/calendar")(trans.tournamentCalendar()), br,
a(href := routes.Tournament.help("arena".some))(trans.tournamentFAQ.frag())
a(href := routes.Tournament.help("arena".some))(trans.tournamentFAQ())
),
h2(trans.lichessTournaments.frag()),
h2(trans.lichessTournaments()),
div(cls := "scheduled")(
scheduled.map { tour =>
tour.schedule.filter(s => s.freq != lila.tournament.Schedule.Freq.Hourly) map { s =>

View File

@ -34,7 +34,7 @@ object side {
separator,
tour.durationString
),
tour.mode.fold(trans.casualTournament, trans.ratedTournament).frag(),
tour.mode.fold(trans.casualTournament, trans.ratedTournament)(),
separator,
systemName(tour.system).capitalize,
isGranted(_.TerminateTournament) option
@ -59,7 +59,7 @@ object side {
"accepted" -> (ctx.isAuth && verdicts.accepted),
"refused" -> (ctx.isAuth && !verdicts.accepted)
))(div(
(verdicts.list.size < 2) option p(trans.conditionOfEntry.frag()),
(verdicts.list.size < 2) option p(trans.conditionOfEntry()),
verdicts.list map { v =>
p(cls := List(
"condition text" -> true,
@ -69,7 +69,7 @@ object side {
}
)),
tour.noBerserk option div(cls := "text", dataIcon := "`")("No Berserk allowed"),
!tour.isScheduled option frag(trans.by.frag(usernameOrId(tour.createdBy)), br),
!tour.isScheduled option frag(trans.by(usernameOrId(tour.createdBy)), br),
(!tour.isStarted || (tour.isScheduled && !tour.position.initial)) option absClientDateTime(tour.startsAt),
!tour.position.initial option p(
a(target := "_blank", href := tour.position.url)(

View File

@ -45,7 +45,7 @@ onload=function(){LichessRound.boot({data:${safeJsonValue(data)},i18n:$transJs})
div(cls := "round__underboard")(
views.html.round.bits.crosstable(cross, pov.game),
div(cls := "tv-history")(
h2(trans.previouslyOnLichessTV.frag()),
h2(trans.previouslyOnLichessTV()),
div(cls := "now-playing")(
history.map { p =>
a(href := gameLink(p))(views.html.game.bits.mini(p))

View File

@ -12,9 +12,9 @@ object bits {
def communityMenu(active: String)(implicit ctx: Context) =
st.nav(cls := "page-menu__menu subnav")(
a(cls := active.active("leaderboard"), href := routes.User.list)(trans.leaderboard.frag()),
a(cls := active.active("ratings"), href := routes.Stat.ratingDistribution("blitz"))(trans.ratingStats.frag()),
a(cls := active.active("tournament"), href := routes.Tournament.leaderboard)(trans.tournamentWinners.frag()),
a(cls := active.active("leaderboard"), href := routes.User.list)(trans.leaderboard()),
a(cls := active.active("ratings"), href := routes.Stat.ratingDistribution("blitz"))(trans.ratingStats()),
a(cls := active.active("tournament"), href := routes.Tournament.leaderboard)(trans.tournamentWinners()),
a(cls := active.active("shield"), href := routes.Tournament.shields)("Shields")
)

View File

@ -31,7 +31,7 @@ object list {
bits.communityMenu("leaderboard"),
div(cls := "community page-menu__content box box-pad")(
st.section(cls := "community__online")(
h2(trans.onlinePlayers.frag()),
h2(trans.onlinePlayers()),
ol(cls := "user_top")(online map { u =>
li(
userLink(u),
@ -40,7 +40,7 @@ object list {
})
),
div(cls := "community__leaders")(
h2(trans.leaderboard.frag()),
h2(trans.leaderboard()),
div(cls := "leaderboards")(
userTopPerf(leaderboards.bullet, PerfType.Bullet),
userTopPerf(leaderboards.blitz, PerfType.Blitz),
@ -68,7 +68,7 @@ object list {
private def tournamentWinners(winners: List[lila.tournament.Winner])(implicit ctx: Context) =
st.section(cls := "user_top")(
h2(cls := "text", dataIcon := "g")(
a(href := routes.Tournament.leaderboard)(trans.tournament.frag())
a(href := routes.Tournament.leaderboard)(trans.tournament())
),
ol(winners take 10 map { w =>
li(

View File

@ -3,8 +3,8 @@ package views.html.user
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.user.User
import lila.evaluation.Display
import lila.user.User
import controllers.routes
@ -26,7 +26,7 @@ object mod {
)
)
def actions(u: User, emails: User.Emails, erased: User.Erased)(implicit ctx: Context) =
def actions(u: User, emails: User.Emails, erased: User.Erased)(implicit ctx: Context): Frag =
div(id := "mz_actions")(
isGranted(_.UserEvaluate) option div(cls := "btn-rack")(
st.form(method := "POST", action := routes.Mod.spontaneousInquiry(u.username), title := "Start an inquiry")(
@ -223,175 +223,178 @@ object mod {
)
)
def assessments(pag: lila.evaluation.PlayerAggregateAssessment.WithGames)(implicit ctx: Context) = div(id := "mz_assessments")(
pag.pag.sfAvgBlurs.map { blursYes =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with blurs is ", strong(blursYes),
pag.pag.sfAvgNoBlurs ?? { blursNo =>
frag(" against ", strong(blursNo), " in games without blurs.")
}
)
},
pag.pag.sfAvgLowVar.map { lowVar =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with consistent move times is ", strong(lowVar),
pag.pag.sfAvgHighVar ?? { highVar =>
frag(" against ", strong(highVar), " in games with random move times.")
}
)
},
pag.pag.sfAvgHold.map { holdYes =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with bot signature ", strong(holdYes),
pag.pag.sfAvgNoHold.map { holdNo =>
frag(" against ", strong(holdNo), " in games without bot signature.")
}
)
},
table(cls := "slist")(
thead(
tr(
th("Opponent"),
th("Game"),
th("Centi-Pawn", br, "(Avg ± SD)"),
th("Move Times", br, "(Avg ± SD)"),
th(span(title := "The frequency of which the user leaves the game page.")("Blurs")),
th(span(title := "Bot detection using grid click analysis.")("Bot")),
th(span(title := "Aggregate match")(raw("&Sigma;")))
)
),
tbody(
pag.pag.playerAssessments.sortBy(-_.assessment.id).take(15).map { result =>
tr(
td(
a(href := routes.Round.watcher(result.gameId, result.color.name))(
pag.pov(result) match {
case None => result.gameId
case Some(p) => playerLink(p.opponent, withRating = true, withDiff = true, withOnline = false, link = false)
}
)
),
td(
pag.pov(result).map { p =>
a(href := routes.Round.watcher(p.gameId, p.color.name))(
p.game.isTournament option iconTag("g"),
p.game.perfType.map { pt => iconTag(pt.iconChar) },
shortClockName(p.game.clock.map(_.config))
)
}
),
td(
span(cls := s"sig sig_${Display.stockfishSig(result)}", dataIcon := "J"),
s" ${result.sfAvg} ± ${result.sfSd}"
),
td(
span(cls := s"sig sig_${Display.moveTimeSig(result)}", dataIcon := "J"),
s" ${result.mtAvg / 10} ± ${result.mtSd / 10}",
(~result.mtStreak) ?? frag(br, "STREAK")
),
td(
span(cls := s"sig sig_${Display.blurSig(result)}", dataIcon := "J"),
s" ${result.blurs}%",
(~result.blurStreak >= 8) ?? frag(br, s"STREAK ${result.blurStreak}/12")
),
td(
span(cls := s"sig sig_${Display.holdSig(result)}", dataIcon := "J"),
if (result.hold) "Yes" else "No"
),
td(
div(cls := "aggregate")(
span(cls := s"sig sig_${result.assessment.id}")(result.assessment.emoticon)
)
)
)
}
)
)
)
def otherUsers(u: User, spy: lila.security.UserSpy, notes: List[lila.user.Note], bans: Map[String, Int])(implicit ctx: Context) = div(id := "mz_others")(
table(cls := "slist")(
thead(
tr(
th(spy.otherUsers.size, " similar user(s)"),
th("Same"),
th(attr("data-sort-method") := "number")("Games"),
th("Status"),
th(attr("data-sort-method") := "number")("Created"),
th(attr("data-sort-method") := "number")("Active")
)
),
tbody(
spy.withMeSorted(u).map {
case lila.security.UserSpy.OtherUser(o, byIp, byFp) => {
tr((o == u) option (cls := "same"))(
td(attr("data-sort") := o.id)(userLink(o, withBestRating = true, params = "?mod")),
td(
if (o == u) "-"
else List(byIp option "IP", byFp option "Print").flatten.mkString(", ")
),
td(attr("data-sort") := o.count.game)(o.count.game.localize),
td(cls := "i") {
val ns = notes.filter(_.to == o.id)
frag(
ns.nonEmpty option {
a(href := s"${routes.User.show(o.username)}?notes")(i(title := s"Notes from ${ns.map(_.from).map(usernameOrId).mkString(", ")}", dataIcon := "m", cls := "is-green"), ns.size)
},
userMarks(o, bans.get(o.id))
)
},
td(attr("data-sort") := o.createdAt.getMillis)(momentFromNowOnce(o.createdAt)),
td(attr("data-sort") := o.seenAt.map(_.getMillis.toString))(o.seenAt.map(momentFromNowOnce))
)
def assessments(pag: lila.evaluation.PlayerAggregateAssessment.WithGames)(implicit ctx: Context): Frag =
div(id := "mz_assessments")(
pag.pag.sfAvgBlurs.map { blursYes =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with blurs is ", strong(blursYes),
pag.pag.sfAvgNoBlurs ?? { blursNo =>
frag(" against ", strong(blursNo), " in games without blurs.")
}
}
)
)
)
def identification(u: User, spy: lila.security.UserSpy)(implicit ctx: Context) = div(id := "mz_identification")(
div(cls := "spy_ips")(
strong(spy.ips.size, " IP addresses"),
ul(
spy.ipsByLocations.map {
case (location, ips) => {
li(
p(location.toString),
ul(
ips.map { ip =>
li(cls := "ip")(
a(cls := List("address" -> true, "blocked" -> ip.blocked), href := s"${routes.Mod.search}?q=${ip.ip.value}")(
tag("ip")(ip.ip.value.value), " ", momentFromNowOnce(ip.ip.date)
)
)
},
pag.pag.sfAvgLowVar.map { lowVar =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with consistent move times is ", strong(lowVar),
pag.pag.sfAvgHighVar ?? { highVar =>
frag(" against ", strong(highVar), " in games with random move times.")
}
)
},
pag.pag.sfAvgHold.map { holdYes =>
p(cls := "text", dataIcon := "j")(
"ACPL in games with bot signature ", strong(holdYes),
pag.pag.sfAvgNoHold.map { holdNo =>
frag(" against ", strong(holdNo), " in games without bot signature.")
}
)
},
table(cls := "slist")(
thead(
tr(
th("Opponent"),
th("Game"),
th("Centi-Pawn", br, "(Avg ± SD)"),
th("Move Times", br, "(Avg ± SD)"),
th(span(title := "The frequency of which the user leaves the game page.")("Blurs")),
th(span(title := "Bot detection using grid click analysis.")("Bot")),
th(span(title := "Aggregate match")(raw("&Sigma;")))
)
),
tbody(
pag.pag.playerAssessments.sortBy(-_.assessment.id).take(15).map { result =>
tr(
td(
a(href := routes.Round.watcher(result.gameId, result.color.name))(
pag.pov(result) match {
case None => result.gameId
case Some(p) => playerLink(p.opponent, withRating = true, withDiff = true, withOnline = false, link = false)
}
)
),
td(
pag.pov(result).map { p =>
a(href := routes.Round.watcher(p.gameId, p.color.name))(
p.game.isTournament option iconTag("g"),
p.game.perfType.map { pt => iconTag(pt.iconChar) },
shortClockName(p.game.clock.map(_.config))
)
}
),
td(
span(cls := s"sig sig_${Display.stockfishSig(result)}", dataIcon := "J"),
s" ${result.sfAvg} ± ${result.sfSd}"
),
td(
span(cls := s"sig sig_${Display.moveTimeSig(result)}", dataIcon := "J"),
s" ${result.mtAvg / 10} ± ${result.mtSd / 10}",
(~result.mtStreak) ?? frag(br, "STREAK")
),
td(
span(cls := s"sig sig_${Display.blurSig(result)}", dataIcon := "J"),
s" ${result.blurs}%",
(~result.blurStreak >= 8) ?? frag(br, s"STREAK ${result.blurStreak}/12")
),
td(
span(cls := s"sig sig_${Display.holdSig(result)}", dataIcon := "J"),
if (result.hold) "Yes" else "No"
),
td(
div(cls := "aggregate")(
span(cls := s"sig sig_${result.assessment.id}")(result.assessment.emoticon)
)
)
)
}
}
)
),
div(cls := "spy_uas")(
strong(spy.uas.size, " User agent(s)"),
ul(
spy.uas.sorted.map { ua =>
li(ua.value, " ", momentFromNowOnce(ua.date))
}
)
),
div(cls := "spy_fps")(
strong(pluralize("Fingerprint", spy.prints.size)),
ul(
spy.prints.sorted.map { fp =>
li(
a(href := s"${routes.Mod.search}?q=${java.net.URLEncoder.encode(fp.value.value, "US-ASCII")}")(
fp.value.value, " ", momentFromNowOnce(fp.date)
)
)
}
)
)
)
def otherUsers(u: User, spy: lila.security.UserSpy, notes: List[lila.user.Note], bans: Map[String, Int])(implicit ctx: Context): Frag =
div(id := "mz_others")(
table(cls := "slist")(
thead(
tr(
th(spy.otherUsers.size, " similar user(s)"),
th("Same"),
th(attr("data-sort-method") := "number")("Games"),
th("Status"),
th(attr("data-sort-method") := "number")("Created"),
th(attr("data-sort-method") := "number")("Active")
)
),
tbody(
spy.withMeSorted(u).map {
case lila.security.UserSpy.OtherUser(o, byIp, byFp) => {
tr((o == u) option (cls := "same"))(
td(attr("data-sort") := o.id)(userLink(o, withBestRating = true, params = "?mod")),
td(
if (o == u) "-"
else List(byIp option "IP", byFp option "Print").flatten.mkString(", ")
),
td(attr("data-sort") := o.count.game)(o.count.game.localize),
td(cls := "i") {
val ns = notes.filter(_.to == o.id)
frag(
ns.nonEmpty option {
a(href := s"${routes.User.show(o.username)}?notes")(i(title := s"Notes from ${ns.map(_.from).map(usernameOrId).mkString(", ")}", dataIcon := "m", cls := "is-green"), ns.size)
},
userMarks(o, bans.get(o.id))
)
},
td(attr("data-sort") := o.createdAt.getMillis)(momentFromNowOnce(o.createdAt)),
td(attr("data-sort") := o.seenAt.map(_.getMillis.toString))(o.seenAt.map(momentFromNowOnce))
)
}
}
)
)
)
def identification(u: User, spy: lila.security.UserSpy)(implicit ctx: Context): Frag =
div(id := "mz_identification")(
div(cls := "spy_ips")(
strong(spy.ips.size, " IP addresses"),
ul(
spy.ipsByLocations.map {
case (location, ips) => {
li(
p(location.toString),
ul(
ips.map { ip =>
li(cls := "ip")(
a(cls := List("address" -> true, "blocked" -> ip.blocked), href := s"${routes.Mod.search}?q=${ip.ip.value}")(
tag("ip")(ip.ip.value.value), " ", momentFromNowOnce(ip.ip.date)
)
)
}
)
)
}
}
)
),
div(cls := "spy_uas")(
strong(spy.uas.size, " User agent(s)"),
ul(
spy.uas.sorted.map { ua =>
li(ua.value, " ", momentFromNowOnce(ua.date))
}
)
),
div(cls := "spy_fps")(
strong(pluralize("Fingerprint", spy.prints.size)),
ul(
spy.prints.sorted.map { fp =>
li(
a(href := s"${routes.Mod.search}?q=${java.net.URLEncoder.encode(fp.value.value, "US-ASCII")}")(
fp.value.value, " ", momentFromNowOnce(fp.date)
)
)
}
)
)
)
)
def userMarks(o: User, playbans: Option[Int])(implicit ctx: Context) = div(cls := "user_marks")(
playbans.map { nb => iconTag("p", nb.toString)(title := "Playban") },

View File

@ -24,7 +24,7 @@ object opponents {
td(
r.nbGames.filter(_ > 0).map { nbGames =>
a(href := s"${routes.User.games(u.username, "search")}?players.b=${r.user.username}", title := "Games count over your last 1000 games")(
trans.nbGames.pluralSameFrag(nbGames)
trans.nbGames.pluralSame(nbGames)
)
}
),

View File

@ -95,7 +95,7 @@ object header {
(ctx.noKid && !ctx.is(u)) option div(cls := "note-zone")(
form(action := s"${routes.User.writeNote(u.username)}?note", method := "post")(
textarea(name := "text", placeholder := "Write a note about this user only you and your friends can read"),
button(tpe := "submit", cls := "button")(trans.send.frag()),
button(tpe := "submit", cls := "button")(trans.send()),
if (isGranted(_.ModNote)) label(style := "margin-left: 1em;")(
input(tpe := "checkbox", name := "mod", checked, value := "true", style := "vertical-align: middle;"),
"For moderators only"
@ -137,11 +137,11 @@ object header {
!ctx.is(u) option frag(
u.engine option div(cls := "warning engine_warning")(
span(dataIcon := "j", cls := "is4"),
trans.thisPlayerUsesChessComputerAssistance.frag()
trans.thisPlayerUsesChessComputerAssistance()
),
(u.booster && (u.count.game > 0 || isGranted(_.Hunter))) option div(cls := "warning engine_warning")(
span(dataIcon := "j", cls := "is4"),
trans.thisPlayerArtificiallyIncreasesTheirRating.frag(),
trans.thisPlayerArtificiallyIncreasesTheirRating(),
(u.count.game == 0) option """
Only visible to mods. A booster mark without any games is a way to
prevent a player from ever playing (except against boosters/cheaters).
@ -170,26 +170,26 @@ It's useful against spambots. These marks are not visible to the public."""
c.name
)
},
p(cls := "thin")(trans.memberSince.frag(), " ", showDate(u.createdAt)),
p(cls := "thin")(trans.memberSince(), " ", showDate(u.createdAt)),
u.seenAt.map { seen =>
p(cls := "thin")(trans.lastSeenActive.frag(momentFromNow(seen)))
p(cls := "thin")(trans.lastSeenActive(momentFromNow(seen)))
},
info.completionRatePercent.map { c =>
p(cls := "thin")(trans.gameCompletionRate.frag(s"$c%"))
p(cls := "thin")(trans.gameCompletionRate(s"$c%"))
},
(ctx is u) option frag(
a(href := routes.Account.profile, title := trans.editProfile.txt())(
trans.profileCompletion.frag(s"${profile.completionPercent}%")
trans.profileCompletion(s"${profile.completionPercent}%")
),
br,
a(href := routes.User.opponents)(trans.favoriteOpponents.frag())
a(href := routes.User.opponents)(trans.favoriteOpponents())
),
info.playTime.map { playTime =>
frag(
br, br,
p(trans.tpTimeSpentPlaying.frag(showPeriod(playTime.totalPeriod))),
p(trans.tpTimeSpentPlaying(showPeriod(playTime.totalPeriod))),
playTime.nonEmptyTvPeriod.map { tvPeriod =>
p(trans.tpTimeSpentOnTV.frag(showPeriod(tvPeriod)))
p(trans.tpTimeSpentOnTV(showPeriod(tvPeriod)))
}
)
},
@ -223,7 +223,7 @@ It's useful against spambots. These marks are not visible to the public."""
"active" -> (angle == Angle.Activity)
),
href := routes.User.show(u.username)
)(trans.activity.activity.frag()),
)(trans.activity.activity()),
a(
dataTab := "games",
cls := List(
@ -232,7 +232,7 @@ It's useful against spambots. These marks are not visible to the public."""
),
href := routes.User.gamesAll(u.username)
)(
trans.nbGames.pluralFrag(info.user.count.game, info.user.count.game.localize),
trans.nbGames.plural(info.user.count.game, info.user.count.game.localize),
info.nbs.playing > 0 option
span(cls := "unread", title := trans.nbPlaying.pluralTxt(info.nbs.playing, info.nbs.playing.localize))(
info.nbs.playing

View File

@ -95,7 +95,7 @@ object page {
views.html.base.layout(title = u.username, robots = false) {
main(cls := "box box-pad")(
h1(u.username),
p(trans.thisAccountIsClosed.frag())
p(trans.thisAccountIsClosed())
)
}

View File

@ -3,7 +3,6 @@ import com.typesafe.sbt.SbtScalariform.autoImport.scalariformFormat
import com.typesafe.sbt.web.SbtWeb.autoImport._
import play.Play.autoImport._
import play.sbt.PlayImport._
import play.twirl.sbt.Import._
import PlayKeys._
import BuildSettings._
@ -36,17 +35,6 @@ libraryDependencies ++= Seq(
kamon.core, kamon.influxdb, scalatags,
java8compat, semver, scrimage, scalaConfigs, scaffeine
)
TwirlKeys.templateImports ++= Seq(
"lila.game.{ Game, Player, Pov }",
"lila.tournament.Tournament",
"lila.user.{ User, UserContext }",
"lila.security.Permission",
"lila.app.templating.Environment._",
"lila.api.Context",
"lila.i18n.{ I18nKeys => trans }",
"lila.common.paginator.Paginator",
"lila.common.String.html._"
)
resourceDirectory in Assets := (sourceDirectory in Compile).value / "assets"
unmanagedResourceDirectories in Assets ++= (if (scala.sys.env.get("SERVE_ASSETS").exists(_ == "1")) Seq(baseDirectory.value / "public") else Nil)
@ -140,7 +128,7 @@ lazy val perfStat = module("perfStat", Seq(common, db, user, game, rating)).sett
)
lazy val history = module("history", Seq(common, db, memo, game, user)).settings(
libraryDependencies ++= provided(play.api, reactivemongo.driver)
libraryDependencies ++= provided(play.api, scalatags, reactivemongo.driver)
)
lazy val db = module("db", Seq(common)).settings(
@ -238,7 +226,7 @@ lazy val insight = module(
Seq(common, game, user, analyse, relation, pref, socket, round, security)
).settings(
libraryDependencies ++= provided(
play.api, reactivemongo.driver, reactivemongo.iteratees
play.api, reactivemongo.driver, reactivemongo.iteratees, scalatags
)
)

View File

@ -2,8 +2,7 @@ package lila.common
import java.text.Normalizer
import play.api.libs.json._
import play.twirl.api.Html
import scalatags.Text.RawFrag
import scalatags.Text.all._
import lila.base.RawHtml
import lila.common.base.StringUtils.{ safeJsonString, escapeHtml => escapeHtmlRaw }
@ -50,24 +49,24 @@ final object String {
val atUsernameRegex = RawHtml.atUsernameRegex
object html {
def richText(rawText: String, nl2br: Boolean = true) = Html {
def richText(rawText: String, nl2br: Boolean = true): Frag = raw {
val withLinks = RawHtml.addLinks(rawText)
if (nl2br) RawHtml.nl2br(withLinks) else withLinks
}
def nl2brUnsafe(text: String) = Html {
RawHtml.nl2br(text)
def nl2brUnsafe(text: String): Frag = raw {
RawHtml nl2br text
}
def nl2br(text: String): Html = nl2brUnsafe(escapeHtmlRaw(text))
def nl2br(text: String): Frag = nl2brUnsafe(escapeHtmlRaw(text))
def escapeHtml(s: String) = Html {
def escapeHtml(s: String): Frag = raw {
escapeHtmlRaw(s)
}
def escapeString(s: String) = escapeHtmlRaw(s)
def escapeString(s: String): Frag = escapeHtmlRaw(s)
def markdownLinks(text: String) = Html {
def markdownLinks(text: String): Frag = raw {
RawHtml.markdownLinks(text)
}
@ -88,7 +87,7 @@ final object String {
}
}
def safeJsonHtml(jsValue: JsValue) = Html(safeJsonValue(jsValue))
def safeJsonHtml(jsValue: JsValue): Frag = raw(safeJsonValue(jsValue))
}
object frag {

View File

@ -1,7 +1,7 @@
package lila.base
import org.specs2.mutable.Specification
import play.twirl.api.Html
// import scalatags.Text.all._
import RawHtml._

View File

@ -1,8 +1,8 @@
package lila.common
import org.specs2.mutable.Specification
import scalatags.Text.all._
import play.twirl.api.Html
import org.specs2.mutable.Specification
class StringTest extends Specification {
@ -16,15 +16,15 @@ class StringTest extends Specification {
"richText" should {
"handle nl" in {
val url = "http://imgur.com/gallery/pMtTE"
String.html.richText(s"link to $url here\n") must_== Html {
String.html.richText(s"link to $url here\n") must_== raw {
s"""link to <a rel="nofollow" href="$url" target="_blank">$url</a> here<br />"""
}
String.html.richText(s"link\n", false) must_== Html("link\n")
String.html.richText(s"link\n", false) must_== raw("link\n")
}
"escape chars" in {
String.html.richText(s"&") must_== Html("&amp;")
String.html.richText(s"&") must_== raw("&amp;")
}
}

View File

@ -1,26 +1,10 @@
package lila.game
import lila.common.LightUser
import lila.user.{ User, Title }
import play.twirl.api.Html
import lila.user.User
object Namer {
def players(game: Game, withRatings: Boolean = true)(implicit lightUser: LightUser.GetterSync): (Html, Html) =
player(game.firstPlayer, withRatings) -> player(game.secondPlayer, withRatings)
def player(p: Player, withRating: Boolean = true, withTitle: Boolean = true)(implicit lightUser: LightUser.GetterSync) = Html {
p.aiLevel.fold(
p.userId.flatMap(lightUser).fold(lila.user.User.anonymous) { user =>
val title = withTitle ?? user.title ?? { t =>
s"""<span class="title"${(Title(t) == Title.BOT) ?? " data-bot"} title="${Title titleName Title(t)}">$t</span>&nbsp;"""
}
if (withRating) s"$title${user.name}&nbsp;(${ratingString(p)})"
else s"$title${user.name}"
}
) { level => s"A.I. level $level" }
}
def playerText(player: Player, withRating: Boolean = false)(implicit lightUser: LightUser.GetterSync): String =
player.aiLevel.fold(
player.userId.flatMap(lightUser).fold(player.name | "Anon.") { u =>
@ -31,7 +15,7 @@ object Namer {
def gameVsText(game: Game, withRatings: Boolean = false)(implicit lightUser: LightUser.GetterSync): String =
s"${playerText(game.whitePlayer, withRatings)} - ${playerText(game.blackPlayer, withRatings)}"
private def ratingString(p: Player) = p.rating match {
def ratingString(p: Player) = p.rating match {
case Some(rating) => s"$rating${if (p.provisional) "?" else ""}"
case _ => "?"
}

View File

@ -1,6 +1,5 @@
package lila.i18n
import play.twirl.api.Html
import scalatags.Text.RawFrag
import lila.common.Lang
@ -9,26 +8,18 @@ sealed trait I18nKey {
val key: String
def literalHtmlTo(lang: Lang, args: Seq[Any] = Seq.empty): Html
def pluralHtmlTo(lang: Lang, count: Count, args: Seq[Any] = Nil): Html
def literalFragTo(lang: Lang, args: Seq[Any] = Seq.empty): RawFrag
def pluralFragTo(lang: Lang, count: Count, args: Seq[Any] = Nil): RawFrag
def literalTo(lang: Lang, args: Seq[Any] = Seq.empty): RawFrag
def pluralTo(lang: Lang, count: Count, args: Seq[Any] = Nil): RawFrag
def literalTxtTo(lang: Lang, args: Seq[Any] = Seq.empty): String
def pluralTxtTo(lang: Lang, count: Count, args: Seq[Any] = Nil): String
/* Implicit context convenience functions */
// html
def apply(args: Any*)(implicit lang: Lang): Html = literalHtmlTo(lang, args)
def plural(count: Count, args: Any*)(implicit lang: Lang): Html = pluralHtmlTo(lang, count, args)
def pluralSame(count: Int)(implicit lang: Lang): Html = plural(count, count)
// frag
def frag(args: Any*)(implicit lang: Lang): RawFrag = literalFragTo(lang, args)
def pluralFrag(count: Count, args: Any*)(implicit lang: Lang): RawFrag = pluralFragTo(lang, count, args)
def pluralSameFrag(count: Int)(implicit lang: Lang): RawFrag = pluralFrag(count, count)
def apply(args: Any*)(implicit lang: Lang): RawFrag = literalTo(lang, args)
def plural(count: Count, args: Any*)(implicit lang: Lang): RawFrag = pluralTo(lang, count, args)
def pluralSame(count: Int)(implicit lang: Lang): RawFrag = plural(count, count)
// txt
def txt(args: Any*)(implicit lang: Lang): String = literalTxtTo(lang, args)
@ -38,16 +29,10 @@ sealed trait I18nKey {
final class Translated(val key: String, val db: I18nDb.Ref) extends I18nKey {
def literalHtmlTo(lang: Lang, args: Seq[Any] = Nil): Html =
Translator.html.literal(key, db, args, lang)
def pluralHtmlTo(lang: Lang, count: Count, args: Seq[Any] = Nil): Html =
Translator.html.plural(key, db, count, args, lang)
def literalFragTo(lang: Lang, args: Seq[Any] = Nil): RawFrag =
def literalTo(lang: Lang, args: Seq[Any] = Nil): RawFrag =
Translator.frag.literal(key, db, args, lang)
def pluralFragTo(lang: Lang, count: Count, args: Seq[Any] = Nil): RawFrag =
def pluralTo(lang: Lang, count: Count, args: Seq[Any] = Nil): RawFrag =
Translator.frag.plural(key, db, count, args, lang)
def literalTxtTo(lang: Lang, args: Seq[Any] = Nil): String =
@ -59,11 +44,8 @@ final class Translated(val key: String, val db: I18nDb.Ref) extends I18nKey {
final class Untranslated(val key: String) extends I18nKey {
def literalHtmlTo(lang: Lang, args: Seq[Any]) = Html(key)
def pluralHtmlTo(lang: Lang, count: Count, args: Seq[Any]) = Html(key)
def literalFragTo(lang: Lang, args: Seq[Any]) = RawFrag(key)
def pluralFragTo(lang: Lang, count: Count, args: Seq[Any]) = RawFrag(key)
def literalTo(lang: Lang, args: Seq[Any]) = RawFrag(key)
def pluralTo(lang: Lang, count: Count, args: Seq[Any]) = RawFrag(key)
def literalTxtTo(lang: Lang, args: Seq[Any]) = key
def pluralTxtTo(lang: Lang, count: Count, args: Seq[Any]) = key

View File

@ -1,10 +1,8 @@
package lila.i18n
import play.twirl.api.Html
import scalatags.Text.RawFrag
import scalatags.Text.all._
import lila.common.String.html.escapeHtml
import lila.common.String.frag.{ escapeHtml => escapeFrag }
import lila.common.String.frag.escapeHtml
private sealed trait Translation
@ -14,14 +12,10 @@ private final class Simple(val message: String) extends Translation {
if (args.isEmpty) message
else message.format(args: _*)
def formatFrag(args: Seq[RawFrag]): RawFrag =
def format(args: Seq[RawFrag]): RawFrag =
if (args.isEmpty) RawFrag(message)
else RawFrag(message.format(args.map(_.v): _*))
def formatHtml(args: Seq[Html]): Html =
if (args.isEmpty) Html(message)
else Html(message.format(args.map(_.body): _*))
override def toString = s"Simple($message)"
}
@ -31,14 +25,10 @@ private final class Escaped(val message: String, escaped: String) extends Transl
if (args.isEmpty) message
else message.format(args: _*)
def formatFrag(args: Seq[RawFrag]): RawFrag =
def format(args: Seq[RawFrag]): RawFrag =
if (args.isEmpty) RawFrag(escaped)
else RawFrag(escaped.format(args.map(_.v): _*))
def formatHtml(args: Seq[Html]): Html =
if (args.isEmpty) Html(escaped)
else Html(escaped.format(args.map(_.body): _*))
override def toString = s"Escaped($message)"
}
@ -55,19 +45,12 @@ private final class Plurals(val messages: Map[I18nQuantity, String]) extends Tra
else message.format(args: _*)
}
def formatFrag(quantity: I18nQuantity, args: Seq[RawFrag]): Option[RawFrag] =
def format(quantity: I18nQuantity, args: Seq[RawFrag]): Option[RawFrag] =
messageFor(quantity).map { message =>
val escaped = escapeFrag(message)
val escaped = escapeHtml(message)
if (args.isEmpty) escaped
else RawFrag(escaped.v.format(args.map(_.v): _*))
}
def formatHtml(quantity: I18nQuantity, args: Seq[Html]): Option[Html] =
messageFor(quantity).map { message =>
val escaped = escapeHtml(message)
if (args.isEmpty) escaped
else Html(escaped.body.format(args.map(_.body): _*))
}
override def toString = s"Plurals($messages)"
}

View File

@ -1,7 +1,6 @@
package lila.i18n
import play.twirl.api.Html
import scalatags.Text.RawFrag
import scalatags.Text.all._
import lila.common.Lang
import lila.common.String.html.escapeHtml
@ -9,41 +8,6 @@ import lila.common.String.frag.{ escapeHtml => escapeFrag }
object Translator {
object html {
def literal(key: MessageKey, db: I18nDb.Ref, args: Seq[Any], lang: Lang): Html =
translate(key, db, lang, I18nQuantity.Other /* grmbl */ , args)
def plural(key: MessageKey, db: I18nDb.Ref, count: Count, args: Seq[Any], lang: Lang): Html =
translate(key, db, lang, I18nQuantity(lang, count), args)
private def translate(key: MessageKey, db: I18nDb.Ref, lang: Lang, quantity: I18nQuantity, args: Seq[Any]): Html =
findTranslation(key, db, lang) flatMap { translation =>
val htmlArgs = escapeArgs(args)
try {
translation match {
case literal: Simple => Some(literal.formatHtml(htmlArgs))
case literal: Escaped => Some(literal.formatHtml(htmlArgs))
case plurals: Plurals => plurals.formatHtml(quantity, htmlArgs)
}
} catch {
case e: Exception =>
logger.warn(s"Failed to format html $db/$lang/$key -> $translation (${args.toList})", e)
Some(Html(key))
}
} getOrElse {
logger.info(s"No translation found for $quantity $key in $lang")
Html(key)
}
private def escapeArgs(args: Seq[Any]): Seq[Html] = args.map {
case s: String => escapeHtml(s)
case h: Html => h
case r: RawFrag => Html(r.v)
case a => Html(a.toString)
}
}
object frag {
def literal(key: MessageKey, db: I18nDb.Ref, args: Seq[Any], lang: Lang): RawFrag =
translate(key, db, lang, I18nQuantity.Other /* grmbl */ , args)
@ -56,9 +20,9 @@ object Translator {
val htmlArgs = escapeArgs(args)
try {
translation match {
case literal: Simple => Some(literal.formatFrag(htmlArgs))
case literal: Escaped => Some(literal.formatFrag(htmlArgs))
case plurals: Plurals => plurals.formatFrag(quantity, htmlArgs)
case literal: Simple => Some(literal.format(htmlArgs))
case literal: Escaped => Some(literal.format(htmlArgs))
case plurals: Plurals => plurals.format(quantity, htmlArgs)
}
} catch {
case e: Exception =>
@ -72,7 +36,6 @@ object Translator {
private def escapeArgs(args: Seq[Any]): Seq[RawFrag] = args.map {
case s: String => escapeFrag(s)
case h: Html => RawFrag(h.body)
case r: RawFrag => r
case a => RawFrag(a.toString)
}

View File

@ -1,6 +1,6 @@
package lila.insight
import play.twirl.api.Html
import scalatags.Text.all._
import reactivemongo.bson._
import play.api.libs.json._
@ -14,7 +14,7 @@ sealed abstract class Dimension[A: BSONValueHandler](
val name: String,
val dbKey: String,
val position: Position,
val description: Html
val description: Frag
) {
def bson = implicitly[BSONValueHandler[A]]
@ -32,77 +32,77 @@ object Dimension {
case object Period extends Dimension[Period](
"period", "Date", F.date, Game,
Html("The date at which the game was played")
raw("The date at which the game was played")
)
case object Date extends Dimension[lila.insight.DateRange](
"date", "Date", F.date, Game,
Html("The date at which the game was played")
raw("The date at which the game was played")
)
case object Perf extends Dimension[PerfType](
"variant", "Variant", F.perf, Game,
Html("The rating category of the game, like Bullet, Blitz, or Chess960.")
raw("The rating category of the game, like Bullet, Blitz, or Chess960.")
)
case object Phase extends Dimension[Phase](
"phase", "Game phase", F.moves("p"), Move,
Html("The portion of the game: Opening, Middlegame, or Endgame.")
raw("The portion of the game: Opening, Middlegame, or Endgame.")
)
case object Result extends Dimension[Result](
"result", "Game result", F.result, Game,
Html("Whether you won, lost, or drew the game.")
raw("Whether you won, lost, or drew the game.")
)
case object Termination extends Dimension[Termination](
"termination", "Game termination", F.termination, Game,
Html("The way that the game ended, like Checkmate or Resignation.")
raw("The way that the game ended, like Checkmate or Resignation.")
)
case object Color extends Dimension[Color](
"color", "Color", F.color, Game,
Html("The side you are playing: White or Black.")
raw("The side you are playing: White or Black.")
)
case object Opening extends Dimension[chess.opening.Ecopening](
"opening", "Opening", F.eco, Game,
Html("ECO identification of the initial moves, like \"A58 Benko Gambit\".")
raw("ECO identification of the initial moves, like \"A58 Benko Gambit\".")
)
case object OpponentStrength extends Dimension[RelativeStrength](
"opponentStrength", "Opponent strength", F.opponentStrength, Game,
Html("Rating of your opponent compared to yours. Much weaker:-200, Weaker:-100, Stronger:+100, Much stronger:+200.")
raw("Rating of your opponent compared to yours. Much weaker:-200, Weaker:-100, Stronger:+100, Much stronger:+200.")
)
case object PieceRole extends Dimension[Role](
"piece", "Piece moved", F.moves("r"), Move,
Html("The type of piece you move.")
raw("The type of piece you move.")
)
case object MovetimeRange extends Dimension[MovetimeRange](
"movetime", "Move time", F.moves("t"), Move,
Html("The amount of time you spend thinking on each move, in seconds.")
raw("The amount of time you spend thinking on each move, in seconds.")
)
case object MyCastling extends Dimension[Castling](
"myCastling", "My castling side", F.myCastling, Game,
Html("The side you castled on during the game: kingside, queenside, or none.")
raw("The side you castled on during the game: kingside, queenside, or none.")
)
case object OpCastling extends Dimension[Castling](
"opCastling", "Opponent castling side", F.opponentCastling, Game,
Html("The side your opponent castled on during the game: kingside, queenside, or none.")
raw("The side your opponent castled on during the game: kingside, queenside, or none.")
)
case object QueenTrade extends Dimension[QueenTrade](
"queenTrade", "Queen trade", F.queenTrade, Game,
Html("Whether queens were traded before the endgame or not.")
raw("Whether queens were traded before the endgame or not.")
)
case object MaterialRange extends Dimension[MaterialRange](
"material", "Material imbalance", F.moves("i"), Move,
Html("Value of your pieces compared to your opponent's. Pawn=1, Bishop/Knight=3, Rook=5, Queen=9.")
raw("Value of your pieces compared to your opponent's. Pawn=1, Bishop/Knight=3, Rook=5, Queen=9.")
)
def requiresStableRating(d: Dimension[_]) = d match {

View File

@ -15,7 +15,7 @@ final class JsonView {
"key" -> D.Opening.key,
"name" -> D.Opening.name,
"position" -> D.Opening.position,
"description" -> D.Opening.description.body,
"description" -> D.Opening.description.render,
"values" -> Dimension.valuesOf(D.Opening).filter { o =>
ecos contains o.eco
}.map(Dimension.valueToJson(D.Opening))
@ -93,7 +93,7 @@ final class JsonView {
"key" -> d.key,
"name" -> d.name,
"position" -> d.position,
"description" -> d.description.body,
"description" -> d.description.render,
"values" -> Dimension.valuesOf(d).map(Dimension.valueToJson(d))
)
}
@ -102,7 +102,7 @@ final class JsonView {
Json.obj(
"key" -> m.key,
"name" -> m.name,
"description" -> m.description.body,
"description" -> m.description.render,
"position" -> m.position
)
}

View File

@ -1,6 +1,7 @@
package lila.insight
import play.twirl.api.Html
import scalatags.Text.all._
import reactivemongo.bson._
sealed abstract class Metric(
@ -10,7 +11,7 @@ sealed abstract class Metric(
val position: Position,
val per: Position,
val dataType: Metric.DataType,
val description: Html
val description: Frag
)
object Metric {
@ -30,7 +31,7 @@ object Metric {
import Entry.{ BSONFields => F }
case object MeanCpl extends Metric("acpl", "Average centipawn loss", F moves "c", Move, Move, Average,
Html("""Precision of your moves. Lower is better."""))
raw("""Precision of your moves. Lower is better."""))
case object Movetime extends Metric("movetime", "Move time", F moves "t", Move, Move, Seconds,
Dimension.MovetimeRange.description)
@ -42,22 +43,22 @@ object Metric {
Dimension.Termination.description)
case object RatingDiff extends Metric("ratingDiff", "Rating gain", F.ratingDiff, Game, Game, Average,
Html("The amount of rating points you win or lose when the game ends."))
raw("The amount of rating points you win or lose when the game ends."))
case object OpponentRating extends Metric("opponentRating", "Opponent rating", F.opponentRating, Game, Game, Average,
Html("The average rating of your opponent for the relevant variant."))
raw("The average rating of your opponent for the relevant variant."))
case object NbMoves extends Metric("nbMoves", "Moves per game", F moves "r", Move, Game, Average,
Html("Number of moves you play in the game. Doesn't count the opponent moves."))
raw("Number of moves you play in the game. Doesn't count the opponent moves."))
case object PieceRole extends Metric("piece", "Piece moved", F moves "r", Move, Move, Percent,
Dimension.PieceRole.description)
case object Opportunism extends Metric("opportunism", "Opportunism", F moves "o", Move, Move, Percent,
Html("How often you take advantage of your opponent blunders. 100% means you punish them all, 0% means you counter-blunder them all."))
raw("How often you take advantage of your opponent blunders. 100% means you punish them all, 0% means you counter-blunder them all."))
case object Luck extends Metric("luck", "Luck", F moves "l", Move, Move, Percent,
Html("How often your opponent fails to punish your blunders. 100% means they miss all your blunders, 0% means they spot them all."))
raw("How often your opponent fails to punish your blunders. 100% means they miss all your blunders, 0% means they spot them all."))
case object Material extends Metric("material", "Material imbalance", F moves "i", Move, Move, Average,
Dimension.MaterialRange.description)

View File

@ -1,6 +1,6 @@
package lila.security
import play.twirl.api.Html
import scalatags.Text.all.raw
import lila.common.{ Lang, EmailAddress }
import lila.common.String.html.nl2brUnsafe
@ -38,7 +38,7 @@ $body
${Mailgun.txt.serviceNote}
""",
htmlBody = Html(s"""
htmlBody = raw(s"""
<div itemscope itemtype="http://schema.org/EmailMessage">
<p itemprop="description">${nl2brUnsafe(body)}</p>
${Mailgun.html.serviceNote}
@ -67,7 +67,7 @@ $body
${Mailgun.txt.serviceNote}
""",
htmlBody = Html(s"""
htmlBody = raw(s"""
<div itemscope itemtype="http://schema.org/EmailMessage">
<p itemprop="description">${nl2brUnsafe(body)}</p>
${Mailgun.html.serviceNote}
@ -107,7 +107,7 @@ $body
${Mailgun.txt.serviceNote}
""",
htmlBody = Html(s"""
htmlBody = raw(s"""
<div itemscope itemtype="http://schema.org/EmailMessage">
<p itemprop="description">${nl2brUnsafe(body)}</p>
${Mailgun.html.serviceNote}

Some files were not shown because too many files have changed in this diff Show More