Merge branch 'master' into trans-swiss

pull/9251/head
Thibault Duplessis 2021-06-28 22:43:03 +02:00 committed by GitHub
commit 09c705d242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
421 changed files with 2970 additions and 1086 deletions

View File

@ -356,7 +356,10 @@ final class Account(
def apiKidPost =
Scoped(_.Preference.Write) { req => me =>
env.user.repo.setKid(me, getBool("v", req)) inject jsonOkResult
getBoolOpt("v", req) match {
case None => BadRequest(jsonError("Missing v parameter")).fuccess
case Some(v) => env.user.repo.setKid(me, v) inject jsonOkResult
}
}
private def currentSessionId(implicit ctx: Context) =

View File

@ -202,7 +202,7 @@ final class Challenge(
Action.async { req =>
import cats.implicits._
val scopes = List(OAuthScope.Challenge.Write)
(get("token1", req) map AccessToken.Id, get("token2", req) map AccessToken.Id).mapN {
(get("token1", req) map AccessToken.Id.apply, get("token2", req) map AccessToken.Id.apply).mapN {
env.oAuth.server.authBoth(scopes)
} ?? {
_ flatMap {

View File

@ -20,6 +20,16 @@ final class Editor(env: Env) extends LilaController(env) {
})
}
private lazy val endgamePositionsJson = lila.common.String.html.safeJsonValue {
JsArray(
chess.EndgamePosition.positions map { p =>
Json.obj(
"name" -> p.name,
"fen" -> p.fen
)
})
}
def index = load("")
def load(urlFen: String) =
@ -35,7 +45,8 @@ final class Editor(env: Env) extends LilaController(env) {
html.board.editor(
sit = situation,
fen = Forsyth >> situation,
positionsJson
positionsJson,
endgamePositionsJson
)
)
}

View File

@ -48,9 +48,9 @@ final class OAuth(env: Env) extends LilaController(env) {
prompt.authorize(me) match {
case Validated.Valid(authorized) =>
env.oAuth.authorizationApi.create(authorized) map { code =>
Redirect(authorized.redirectUrl(code))
SeeOther(authorized.redirectUrl(code))
}
case Validated.Invalid(error) => Redirect(prompt.redirectUri.error(error, prompt.state)).fuccess
case Validated.Invalid(error) => SeeOther(prompt.redirectUri.error(error, prompt.state)).fuccess
}
}
}

View File

@ -2,6 +2,7 @@ package lila.app
package templating
import play.api.data._
import play.api.i18n.Lang
import lila.api.Context
import lila.app.ui.ScalatagsTemplate._
@ -26,6 +27,14 @@ trait FormHelper { self: I18nHelper =>
val postForm = form(method := "post")
val submitButton = button(tpe := "submit")
def markdownAvailable(implicit lang: Lang) =
trans.markdownAvailable(
a(
href := "https://guides.github.com/features/mastering-markdown/",
targetBlank
)("Markdown")
)
object form3 {
private val idPrefix = "form3"

View File

@ -150,6 +150,10 @@ object pref {
setting(
sayGgWpAfterLosingOrDrawing(),
radios(form("behavior.courtesy"), booleanChoices)
),
setting(
scrollOnTheBoardToReplayMoves(),
radios(form("behavior.scrollMoves"), booleanChoices)
)
),
categFieldset(PrefCateg.Privacy, categ)(

View File

@ -1,17 +0,0 @@
package views.html.base
import com.github.blemale.scaffeine.LoadingCache
import scala.concurrent.duration._
import lila.app.ui.ScalatagsTemplate._
object markdown {
private val renderer = new lila.common.Markdown
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterWrite(10 minutes)
.maximumSize(256)
.build(renderer.apply)
def apply(text: String): Frag = raw(cache get text)
}

View File

@ -59,6 +59,7 @@ object bits {
trans.flipBoard,
trans.loadPosition,
trans.popularOpenings,
trans.endgamePositions,
trans.castling,
trans.whiteCastlingKingside,
trans.blackCastlingKingside,

View File

@ -13,7 +13,8 @@ object editor {
def apply(
sit: chess.Situation,
fen: FEN,
positionsJson: String
positionsJson: String,
endgamePositionsJson: String,
)(implicit ctx: Context) =
views.html.base.layout(
title = trans.boardEditor.txt(),
@ -21,7 +22,7 @@ object editor {
jsModule("editor"),
embedJsUnsafeLoadThen(
s"""const data=${safeJsonValue(bits.jsData(sit, fen))};data.positions=$positionsJson;
LichessEditor(document.getElementById('board-editor'), data);"""
data.endgamePositions=$endgamePositionsJson;LichessEditor(document.getElementById('board-editor'), data);"""
)
),
moreCss = cssTag("editor"),

View File

@ -27,7 +27,7 @@ object theirs {
case Status.Created | Status.Offline =>
frag(
h1(
if (c.isOpen) c.name | "Open Challenge"
if (c.isOpen) c.name | "Open challenge"
else
user.fold[Frag]("Anonymous")(u =>
frag(

View File

@ -34,16 +34,7 @@ object wall {
ul(
li(trans.clas.newsEdit2()),
li(trans.clas.newsEdit3()),
li(
trans.clas.markdownAvailable(
a(
href := "https://guides.github.com/features/mastering-markdown/",
targetBlank
)(
"Markdown"
)
)
)
li(markdownAvailable)
)
),
postForm(cls := "form3", action := routes.Clas.wallUpdate(c.id.value))(

View File

@ -75,8 +75,10 @@ object coordinate {
),
div(cls := "coord-trainer__board main-board")(
svgTag(cls := "coords-svg", viewBoxAttr := "0 0 100 100")(
textTag(cls := "coord current-coord"),
textTag(cls := "coord next-coord")
textTag(cls := "coord coord--resolved"),
textTag(cls := "coord coord--current"),
textTag(cls := "coord coord--next"),
textTag(cls := "coord coord--new")
),
chessgroundBoard
),

View File

@ -63,7 +63,7 @@ object event {
)
),
e.description.map { d =>
div(cls := "desc")(views.html.base.markdown(d))
div(cls := "desc")(markdown(d))
},
if (e.isFinished) p(cls := "desc")("The event is finished.")
else if (e.isNow) a(href := e.url, cls := "button button-fat")(trans.eventInProgress())
@ -76,6 +76,18 @@ object event {
)
}
private object markdown {
import scala.concurrent.duration._
private val renderer = new lila.common.Markdown(table = true, list = true)
private val cache: com.github.blemale.scaffeine.LoadingCache[String, String] =
lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterAccess(10 minutes)
.maximumSize(64)
.build(renderer.apply)
def apply(text: String): Frag = raw(cache get text)
}
def manager(events: List[Event])(implicit ctx: Context) = {
val title = "Event manager"
layout(title = title) {

View File

@ -139,7 +139,8 @@ object inquiry {
)
},
isGranted(_.Shadowban) option
a(href := routes.Mod.communicationPublic(in.user.id))("View", br, "Comms")
a(href := routes.Mod.communicationPublic(in.user.id))("View", br, "Comms"),
in.report.isAppeal option a(href := routes.Appeal.show(in.user.id))("View", br, "Appeal")
),
div(cls := "actions")(
isGranted(_.ModMessage) option div(cls := "dropper warn buttons")(

View File

@ -71,6 +71,7 @@ object racer {
s.raceComplete,
s.spectating,
s.joinTheRace,
s.startTheRace,
s.yourRankX,
s.waitForRematch,
s.nextRace,

View File

@ -55,7 +55,7 @@ object form {
),
postForm(cls := "terminate", action := routes.Simul.abort(simul.id))(
submitButton(dataIcon := "", cls := "text button button-red confirm")(
"Cancel the simul"
trans.cancelSimul()
)
)
)
@ -94,11 +94,11 @@ object form {
form3.split(
form3.group(
form("clockTime"),
raw("Clock initial time"),
trans.clockInitialTime(),
help = trans.simulClockHint().some,
half = true
)(form3.select(_, clockTimeChoices)),
form3.group(form("clockIncrement"), raw("Clock increment"), half = true)(
form3.group(form("clockIncrement"), trans.clockIncrement(), half = true)(
form3.select(_, clockIncrementChoices)
)
),
@ -111,37 +111,38 @@ object form {
)(
form3.select(_, clockExtraChoices)
),
form3.group(form("color"), raw("Host color for each game"), half = true)(
form3.group(form("color"), trans.simulHostcolor(), half = true)(
form3.select(_, colorChoices)
)
),
form3.split(
teams.nonEmpty option
form3.group(form("team"), raw("Only members of team"), half = true)(
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
form3.group(form("team"), trans.onlyMembersOfTeam(), half = true)(
form3.select(_, List(("", trans.noRestriction.txt())) ::: teams.map(_.pair))
),
form3.group(
form("position"),
trans.startPosition(),
klass = "position",
half = true,
help = views.html.tournament.form.positionInputHelp.some
help =
trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
)(form3.input(_))
),
form3.group(
form("estimatedStartAt"),
frag("Estimated start time"),
trans.estimatedStart(),
half = true
)(form3.flatpickr(_)),
form3.group(
form("text"),
raw("Simul description"),
help = frag("Anything you want to tell the participants?").some
trans.simulDescription(),
help = trans.simulDescriptionHelp().some
)(form3.textarea(_)(rows := 10)),
ctx.me.exists(_.canBeFeatured) option form3.checkbox(
form("featured"),
frag("Feature on lichess.org/simul"),
help = frag("Show your simul to everyone on lichess.org/simul. Disable for private simuls.").some
trans.simulFeatured("lichess.org/simul"),
help = trans.simulFeaturedHelp("lichess.org/simul").some
)
)
}

View File

@ -205,7 +205,10 @@ object contact {
Leaf(
"casual",
noRatingPoints(),
p(ratedGame())
frag(
p(ratedGame()),
botRatingAbuse()
)
),
Leaf(
"error-page",

View File

@ -14,14 +14,14 @@ object form {
def create(form: Form[_], teamId: TeamID)(implicit ctx: Context) =
views.html.base.layout(
title = "New Swiss tournament",
title = trans.swiss.newSwiss.txt(),
moreCss = cssTag("swiss.form"),
moreJs = jsModule("tourForm")
) {
val fields = new SwissFields(form, none)
main(cls := "page-small")(
div(cls := "swiss__form tour__form box box-pad")(
h1("New Swiss tournament"),
h1(trans.swiss.newSwiss()),
postForm(cls := "form3", action := routes.Swiss.create(teamId))(
form3.split(fields.name, fields.nbRounds),
form3.split(fields.rated, fields.variant),
@ -80,33 +80,33 @@ object form {
),
postForm(cls := "terminate", action := routes.Swiss.terminate(swiss.id.value))(
submitButton(dataIcon := "", cls := "text button button-red confirm")(
"Cancel the tournament"
trans.cancelTournament()
)
)
)
)
}
private def condition(form: Form[_], fields: SwissFields, swiss: Option[Swiss])(implicit ctx: Context) =
private def condition(form: Form[_], fields: SwissFields, swiss: Option[Swiss])(implicit ctx: Context) =
frag(
form3.split(
form3.group(form("conditions.nbRatedGame.nb"), frag("Minimum rated games"), half = true)(
form3.group(form("conditions.nbRatedGame.nb"), trans.minimumRatedGames(), half = true)(
form3.select(_, SwissCondition.DataForm.nbRatedGameChoices)
),
(ctx.me.exists(_.hasTitle) || isGranted(_.ManageTournament)) ?? {
form3.checkbox(
form("conditions.titled"),
frag("Only titled players"),
help = frag("Require an official title to join the tournament").some,
trans.onlyTitled(),
help = trans.onlyTitledHelp().some,
half = true
)
}
),
form3.split(
form3.group(form("conditions.minRating.rating"), frag("Minimum rating"), half = true)(
form3.group(form("conditions.minRating.rating"), trans.minimumRating(), half = true)(
form3.select(_, SwissCondition.DataForm.minRatingChoices)
),
form3.group(form("conditions.maxRating.rating"), frag("Maximum weekly rating"), half = true)(
form3.group(form("conditions.maxRating.rating"), trans.maximumWeeklyRating(), half = true)(
form3.select(_, SwissCondition.DataForm.maxRatingChoices)
)
)
@ -133,8 +133,8 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
def nbRounds =
form3.group(
form("nbRounds"),
"Number of rounds",
help = raw("An odd number of rounds allows optimal color balance.").some,
trans.swiss.nbRounds(),
help = trans.swiss.nbRoundsHelp().some,
half = true
)(
form3.input(_, typ = "number")
@ -145,7 +145,7 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
form3.checkbox(
form("rated"),
trans.rated(),
help = raw("Games are rated<br>and impact players ratings").some
help = trans.ratedFormHelp().some
),
st.input(tpe := "hidden", st.name := form("rated").name, value := "false") // hack allow disabling rated
)
@ -167,16 +167,14 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
)
)
def roundInterval =
form3.group(form("roundInterval"), frag("Interval between rounds"), half = true)(
form3.group(form("roundInterval"), trans.swiss.roundInterval(), half = true)(
form3.select(_, SwissForm.roundIntervalChoices)
)
def description =
form3.group(
form("description"),
frag("Tournament description"),
help = frag(
"Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)"
).some,
trans.tournDescription(),
help = trans.tournDescriptionHelp().some,
half = true
)(form3.textarea(_)(rows := 4))
def position =
@ -185,25 +183,25 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
trans.startPosition(),
klass = "position",
half = true,
help = views.html.tournament.form.positionInputHelp.some
help = trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
)(form3.input(_))
def startsAt =
form3.group(
form("startsAt"),
frag("Tournament start date"),
help = frag("In your own local timezone").some,
trans.swiss.tournStartDate(),
help = trans.inYourLocalTimezone().some,
half = true
)(form3.flatpickr(_))
def chatFor =
form3.group(form("chatFor"), frag("Tournament chat"), half = true) { f =>
form3.group(form("chatFor"), trans.tournChat(), half = true) { f =>
form3.select(
f,
Seq(
Swiss.ChatFor.NONE -> "No chat",
Swiss.ChatFor.LEADERS -> "Only team leaders",
Swiss.ChatFor.MEMBERS -> "Only team members",
Swiss.ChatFor.ALL -> "All Lichess players"
Swiss.ChatFor.NONE -> trans.noChat.txt(),
Swiss.ChatFor.LEADERS -> trans.onlyTeamLeaders.txt(),
Swiss.ChatFor.MEMBERS -> trans.onlyTeamMembers.txt(),
Swiss.ChatFor.ALL -> trans.study.everyone.txt()
)
)
}
@ -219,10 +217,8 @@ final private class SwissFields(form: Form[_], swiss: Option[Swiss])(implicit ct
def forbiddenPairings =
form3.group(
form("forbiddenPairings"),
frag("Forbidden pairings"),
help = frag(
"Usernames of players that must not play together (Siblings, for instance). Two usernames per line, separated by a space."
).some,
trans.swiss.forbiddenPairings(),
help = trans.swiss.forbiddenPairingsHelp().some,
half = true
)(form3.textarea(_)(rows := 4))
}

View File

@ -103,8 +103,18 @@ object form {
private def textFields(form: Form[_])(implicit ctx: Context) = frag(
form3.group(form("location"), trans.location())(form3.input(_)),
form3.group(form("description"), trans.description())(form3.textarea(_)(rows := 10)),
form3.group(form("descPrivate"), trans.descPrivate(), help = trans.descPrivateHelp().some)(
form3.group(form("description"), trans.description(), help = markdownAvailable.some)(
form3.textarea(_)(rows := 10)
),
form3.group(
form("descPrivate"),
trans.descPrivate(),
help = frag(
trans.descPrivateHelp(),
br,
markdownAvailable
).some
)(
form3.textarea(_)(rows := 10)
)
)

View File

@ -8,7 +8,7 @@ import lila.app.mashup.TeamInfo
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.paginator.Paginator
import lila.common.String.html.{ markdownLinksOrRichText, richText, safeJsonValue }
import lila.common.String.html.{ richText, safeJsonValue }
import lila.team.Team
object show {
@ -176,7 +176,7 @@ object show {
div(cls := "team-show__content__col2")(
standardFlash(),
st.section(cls := "team-show__desc")(
markdownLinksOrRichText {
markdown {
t.descPrivate.ifTrue(info.mine) | t.description
},
t.location.map { loc =>
@ -228,6 +228,18 @@ object show {
)
}
private object markdown {
import scala.concurrent.duration._
private val renderer = new lila.common.Markdown(list = true)
private val cache: com.github.blemale.scaffeine.LoadingCache[String, String] =
lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterAccess(10 minutes)
.maximumSize(512)
.build(renderer.apply)
def apply(text: String): Frag = raw(cache get text)
}
// handle special teams here
private def joinButton(t: Team)(implicit ctx: Context) =
t.id match {

View File

@ -22,7 +22,7 @@ object form {
main(cls := "page-small")(
div(cls := "tour__form box box-pad")(
h1(
if (fields.isTeamBattle) "New Team Battle"
if (fields.isTeamBattle) trans.arena.newTeamBattle()
else trans.createANewTournament()
),
postForm(cls := "form3", action := routes.Tournament.create)(
@ -86,7 +86,7 @@ object form {
),
postForm(cls := "terminate", action := routes.Tournament.terminate(tour.id))(
submitButton(dataIcon := "", cls := "text button button-red confirm")(
"Cancel the tournament"
trans.cancelTournament()
)
)
)
@ -116,13 +116,13 @@ object form {
case None => baseField
case Some(team) => baseField.copy(value = team.some)
}
form3.group(field, frag("Only members of team"), half = true)(
form3.select(_, List(("", "No Restriction")) ::: teams.map(_.pair))
form3.group(field, trans.onlyMembersOfTeam(), half = true)(
form3.select(_, List(("", trans.noRestriction.txt())) ::: teams.map(_.pair))
)
}
),
form3.split(
form3.group(form("conditions.nbRatedGame.nb"), frag("Minimum rated games"), half = true)(
form3.group(form("conditions.nbRatedGame.nb"), trans.minimumRatedGames(), half = true)(
form3.select(_, Condition.DataForm.nbRatedGameChoices)
),
autoField(auto, form("conditions.nbRatedGame.perf")) { field =>
@ -132,7 +132,7 @@ object form {
}
),
form3.split(
form3.group(form("conditions.minRating.rating"), frag("Minimum rating"), half = true)(
form3.group(form("conditions.minRating.rating"), trans.minimumRating(), half = true)(
form3.select(_, Condition.DataForm.minRatingChoices)
),
autoField(auto, form("conditions.minRating.perf")) { field =>
@ -140,7 +140,7 @@ object form {
}
),
form3.split(
form3.group(form("conditions.maxRating.rating"), frag("Maximum weekly rating"), half = true)(
form3.group(form("conditions.maxRating.rating"), trans.maximumWeeklyRating(), half = true)(
form3.select(_, Condition.DataForm.maxRatingChoices)
),
autoField(auto, form("conditions.maxRating.perf")) { field =>
@ -151,15 +151,15 @@ object form {
(ctx.me.exists(_.hasTitle) || isGranted(_.ManageTournament)) ?? {
form3.checkbox(
form("conditions.titled"),
frag("Only titled players"),
help = frag("Require an official title to join the tournament").some,
trans.onlyTitled(),
help = trans.onlyTitledHelp().some,
half = true
)
},
form3.checkbox(
form("berserkable"),
frag("Allow Berserk"),
help = frag("Let players halve their clock time to gain an extra point").some,
trans.arena.allowBerserk(),
help = trans.arena.allowBerserkHelp().some,
half = true
),
form3.hidden(form("berserkable"), "false".some) // hack to allow disabling berserk
@ -168,14 +168,14 @@ object form {
form3.checkbox(
form("hasChat"),
trans.chatRoom(),
help = frag("Let players discuss in a chat room").some,
help = trans.arena.allowChatHelp().some,
half = true
),
form3.hidden(form("hasChat"), "false".some), // hack to allow disabling chat
form3.checkbox(
form("streakable"),
frag("Arena streaks"),
help = frag("After 2 wins, consecutive wins grant 4 points instead of 2.").some,
trans.arena.arenaStreaks(),
help = trans.arena.arenaStreaksHelp().some,
half = true
),
form3.hidden(form("streakable"), "false".some) // hack to allow disabling streaks
@ -186,18 +186,6 @@ object form {
form3.input(field)(
tour.exists(t => !t.isCreated && t.position.isEmpty).option(disabled := true)
)
val positionInputHelp = frag(
"Paste a valid FEN to start every game from a given position.",
br,
"It only works for standard games, not with variants.",
br,
"You can use the ",
a(href := routes.Editor.index, target := "_blank")("board editor"),
" to generate a FEN position, then paste it here.",
br,
"Leave empty to start games from the normal initial position."
)
}
final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit ctx: Context) {
@ -228,7 +216,7 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
form3.checkbox(
form("rated"),
trans.rated(),
help = raw("Games are rated<br>and impact players ratings").some
help = trans.ratedFormHelp().some
),
st.input(tpe := "hidden", st.name := form("rated").name, value := "false") // hack allow disabling rated
)
@ -246,7 +234,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
trans.startPosition(),
klass = "position",
half = true,
help = tournament.form.positionInputHelp.some
help =
trans.positionInputHelp(a(href := routes.Editor.index, targetBlank)(trans.boardEditor.txt())).some
)(
views.html.tournament.form.startingPosition(_, tour)
)
@ -270,10 +259,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
def description(half: Boolean) =
form3.group(
form("description"),
frag("Tournament description"),
help = frag(
"Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)"
).some,
trans.tournDescription(),
help = trans.tournDescriptionHelp().some,
half = half
)(form3.textarea(_)(rows := 4))
def password =
@ -286,10 +273,8 @@ final private class TourFields(form: Form[_], tour: Option[Tournament])(implicit
def startDate =
form3.group(
form("startDate"),
frag("Custom start date"),
help = frag(
"""In your own local timezone. This overrides the "Time before tournament starts" setting"""
).some
trans.arena.customStartDate(),
help = trans.arena.customStartDateHelp().some
)(form3.flatpickr(_))
def advancedSettings =
frag(

View File

@ -26,7 +26,7 @@ class Report:
def short_lang(lang):
if lang in ["ne-NP", "la-LA", "nn-NO", "zh-CN", "ur-PK", "zh-TW", "tlh-AA", "ml-IN", "pt-BR", "tt-RU"]:
if lang in ["ne-NP", "la-LA", "nn-NO", "zh-CN", "ur-PK", "zh-TW", "tlh-AA", "ml-IN", "pt-BR", "tt-RU", "de-CH"]:
return lang.replace("-", "").lower()
elif lang == "kab-DZ":
return "kaby"

View File

@ -415,7 +415,7 @@ fishnet {
}
offline_mode = true # any client can provide moves and analysis
actor.name = fishnet
analysis.nodes = 2000000 # nnue
analysis.nodes = 2100000 # nnue
move.plies = 300
client_min_version = "2.1.3"
}

View File

@ -16,7 +16,7 @@ object BlogTransform {
private type Text = String
private type Html = String
private val renderer = new lila.common.Markdown
private val renderer = new lila.common.Markdown(table = true)
private val cache: LoadingCache[Text, Html] = lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterWrite(15 minutes)

View File

@ -6,7 +6,7 @@ import scala.concurrent.duration._
final class ClasMarkup {
private val renderer = new lila.common.Markdown(autoLink = true)
private val renderer = new lila.common.Markdown(autoLink = true, list = true)
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterAccess(20 minutes)

View File

@ -4,6 +4,8 @@ import lila.user.User
import org.joda.time.DateTime
import lila.common.SecureRandom
case class Student(
_id: Student.Id, // userId:clasId
userId: User.ID,
@ -50,10 +52,9 @@ object Student {
private[clas] object password {
private val random = new java.security.SecureRandom()
private val chars = ('2' to '9') ++ (('a' to 'z').toSet - 'l') mkString
private val nbChars = chars.length
private def secureChar = chars(random nextInt nbChars)
private def secureChar = chars(SecureRandom nextInt nbChars)
def generate =
User.ClearPassword {

View File

@ -98,7 +98,10 @@ final class CoachApi(
_.refreshAfterWrite(1 hour)
.buildAsyncFuture { _ =>
userRepo.coll.secondaryPreferred
.distinctEasy[String, Set]("profile.country", $doc("roles" -> lila.security.Permission.Coach.dbKey, "enabled" -> true))
.distinctEasy[String, Set](
"profile.country",
$doc("roles" -> lila.security.Permission.Coach.dbKey, "enabled" -> true)
)
}
}
def allCountries: Fu[Set[String]] = countriesCache.get {}

View File

@ -32,53 +32,55 @@ final class CoachPager(
def selector = listableSelector ++ lang.?? { l => $doc("languages" -> l.code) }
val adapter = country match {
case Some(country) => new AdapterLike[Coach.WithUser] {
def nbResults: Fu[Int] = coll.secondaryPreferred.countSel(selector)
case Some(country) =>
new AdapterLike[Coach.WithUser] {
def nbResults: Fu[Int] = coll.secondaryPreferred.countSel(selector)
def slice(offset: Int, length: Int): Fu[List[Coach.WithUser]] =
coll
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
import framework._
Match(selector) -> List(
Sort(
order match {
case Alphabetical => Ascending("_id")
case NbReview => Descending("nbReview")
case LichessRating => Descending("user.rating")
case Login => Descending("user.seenAt")
}
),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> userRepo.coll.name,
"localField" -> "_id",
"foreignField" -> "_id",
"as" -> "_user"
def slice(offset: Int, length: Int): Fu[List[Coach.WithUser]] =
coll
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
import framework._
Match(selector) -> List(
Sort(
order match {
case Alphabetical => Ascending("_id")
case NbReview => Descending("nbReview")
case LichessRating => Descending("user.rating")
case Login => Descending("user.seenAt")
}
),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> userRepo.coll.name,
"localField" -> "_id",
"foreignField" -> "_id",
"as" -> "_user"
)
)
)
),
UnwindField("_user"),
Match($doc("_user.profile.country" -> country.code)),
Skip(offset),
Limit(length)
)
}
.map { docs =>
for {
doc <- docs
coach <- doc.asOpt[Coach]
user <- doc.getAsOpt[User]("_user")
} yield Coach.WithUser(coach, user)
}
}
),
UnwindField("_user"),
Match($doc("_user.profile.country" -> country.code)),
Skip(offset),
Limit(length)
)
}
.map { docs =>
for {
doc <- docs
coach <- doc.asOpt[Coach]
user <- doc.getAsOpt[User]("_user")
} yield Coach.WithUser(coach, user)
}
}
case None => new Adapter[Coach](
collection = coll,
selector = selector,
projection = none,
sort = order.predicate
) mapFutureList withUsers
case None =>
new Adapter[Coach](
collection = coll,
selector = selector,
projection = none,
sort = order.predicate
) mapFutureList withUsers
}
Paginator(

View File

@ -1,6 +1,5 @@
package lila.common
import ornicar.scalalib.Random
import play.api.mvc._
import scala.concurrent.ExecutionContext
@ -12,7 +11,7 @@ final class LilaCookie(domain: NetDomain, baker: SessionCookieBaker) {
def makeSessionId(implicit req: RequestHeader) = session(LilaCookie.sessionId, generateSessionId())
def generateSessionId() = Random secureString 22
def generateSessionId() = SecureRandom nextString 22
def session(name: String, value: String)(implicit req: RequestHeader): Cookie =
withSession { s =>

View File

@ -8,22 +8,41 @@ import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.data.MutableDataSet
import scala.jdk.CollectionConverters._
final class Markdown(autoLink: Boolean = false) {
final class Markdown(
autoLink: Boolean = true,
table: Boolean = false,
strikeThrough: Boolean = false,
header: Boolean = false,
blockQuote: Boolean = false,
list: Boolean = false
) {
private type Text = String
private type Html = String
private val extensions: java.util.List[Parser.ParserExtension] = List(
TablesExtension.create().some,
StrikethroughExtension.create().some,
table option TablesExtension.create(),
strikeThrough option StrikethroughExtension.create(),
autoLink option AutolinkExtension.create()
).flatten.asJava
private val options = new MutableDataSet()
options.set(Parser.EXTENSIONS, extensions)
options.set(TablesExtension.CLASS_NAME, "slist")
options.set(HtmlRenderer.ESCAPE_HTML, Boolean.box(true))
options.set(HtmlRenderer.ESCAPE_HTML, Boolean box true)
options.set(HtmlRenderer.SOFT_BREAK, "<br>")
// always disabled
options.set(Parser.HTML_BLOCK_PARSER, Boolean box false)
options.set(Parser.INDENTED_CODE_BLOCK_PARSER, Boolean box false)
options.set(Parser.FENCED_CODE_BLOCK_PARSER, Boolean box false)
// configurable
if (table) options.set(TablesExtension.CLASS_NAME, "slist")
if (!header) options.set(Parser.HEADING_PARSER, Boolean box false)
if (!blockQuote) options.set(Parser.BLOCK_QUOTE_PARSER, Boolean box false)
if (!list) options.set(Parser.LIST_BLOCK_PARSER, Boolean box false)
private val parser = Parser.builder(options).build()
private val renderer = HtmlRenderer.builder(options).build()

View File

@ -1,12 +1,10 @@
package lila.common
import ornicar.scalalib.Random
case class Nonce(value: String) extends AnyVal with StringValue {
def scriptSrc = s"'nonce-$value'"
}
object Nonce {
def random: Nonce = Nonce(Random.secureString(24))
def random: Nonce = Nonce(SecureRandom.nextString(24))
}

View File

@ -0,0 +1,49 @@
package lila.common
import scala.collection.mutable.StringBuilder
abstract class Random {
protected def current: java.util.Random
def nextBoolean(): Boolean = current.nextBoolean()
def nextDouble(): Double = current.nextDouble()
def nextFloat(): Float = current.nextFloat()
def nextInt(): Int = current.nextInt()
def nextInt(n: Int): Int = current.nextInt(n)
def nextLong(): Long = current.nextLong()
def nextGaussian(): Double = current.nextGaussian()
def nextBytes(len: Int): Array[Byte] = {
val bytes = new Array[Byte](len)
current.nextBytes(bytes)
bytes
}
private def nextAlphanumeric(): Char = {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
chars charAt nextInt(chars.length) // Constant time
}
def nextString(len: Int): String = {
val sb = new StringBuilder(len)
for (_ <- 0 until len) sb += nextAlphanumeric()
sb.result()
}
def shuffle[T, C](xs: IterableOnce[T])(implicit bf: scala.collection.BuildFrom[xs.type, T, C]): C =
new scala.util.Random(current).shuffle(xs)
def oneOf[A](vec: Vector[A]): Option[A] =
vec.nonEmpty ?? {
vec lift nextInt(vec.size)
}
}
object ThreadLocalRandom extends Random {
override def current = java.util.concurrent.ThreadLocalRandom.current
}
object SecureRandom extends Random {
override val current = new java.security.SecureRandom()
}

View File

@ -1,34 +0,0 @@
package lila.common
import scala.collection.mutable.StringBuilder
object ThreadLocalRandom {
import java.util.concurrent.ThreadLocalRandom.current
def nextBoolean(): Boolean = current.nextBoolean()
def nextBytes(bytes: Array[Byte]): Unit = current.nextBytes(bytes)
def nextDouble(): Double = current.nextDouble()
def nextFloat(): Float = current.nextFloat()
def nextInt(): Int = current.nextInt()
def nextInt(n: Int): Int = current.nextInt(n)
def nextLong(): Long = current.nextLong()
def nextGaussian(): Double = current.nextGaussian()
def nextChar(): Char = {
val i = nextInt(62)
if (i < 26) i + 65
else if (i < 52) i + 71
else i - 4
}.toChar
def shuffle[T, C](xs: IterableOnce[T])(implicit bf: scala.collection.BuildFrom[xs.type, T, C]): C =
new scala.util.Random(current).shuffle(xs)
def nextString(len: Int): String = {
val sb = new StringBuilder(len)
for (_ <- 0 until len) sb += nextChar()
sb.result()
}
def oneOf[A](vec: Vector[A]): Option[A] =
vec.nonEmpty ?? {
vec lift nextInt(vec.size)
}
}

View File

@ -15,7 +15,7 @@ case class AssetVersion(value: String) extends AnyVal with StringValue
object AssetVersion {
var current = random
def change() = { current = random }
private def random = AssetVersion(ornicar.scalalib.Random secureString 6)
private def random = AssetVersion(SecureRandom nextString 6)
}
case class IsMobile(value: Boolean) extends AnyVal with BooleanValue

View File

@ -95,5 +95,5 @@ final class EvalCacheApi(
}
private def destSize(fen: FEN): Int =
chess.Game(chess.variant.Standard.some, fen.some).situation.destinations.size
chess.Game(chess.variant.Standard.some, fen.some).situation.moves.view.map(_._2.size).sum
}

View File

@ -1,8 +1,7 @@
package lila.fishnet
import ornicar.scalalib.Random
import com.gilt.gfc.semver.SemVer
import lila.common.IpAddress
import lila.common.{ IpAddress, SecureRandom }
import scala.util.{ Failure, Success, Try }
import org.joda.time.DateTime
@ -98,5 +97,5 @@ object Client {
}
}
def makeKey = Key(Random.secureString(8))
def makeKey = Key(SecureRandom.nextString(8))
}

View File

@ -4,9 +4,8 @@ import scala.concurrent.duration._
import chess.{ Black, Clock, White }
import lila.common.Future
import lila.common.{ Future, ThreadLocalRandom }
import lila.game.{ Game, GameRepo, UciMemo }
import ornicar.scalalib.Random.approximately
final class FishnetPlayer(
redis: FishnetRedis,
@ -43,7 +42,7 @@ final class FishnetPlayer(
sleep = (delay * accel) atMost 500
if sleep > 25
millis = sleep * 10
randomized = approximately(0.5f)(millis)
randomized = millis + millis * (ThreadLocalRandom.nextDouble() - 0.5)
divided = randomized / (if (g.turns > 9) 1 else 2)
} yield divided.millis

View File

@ -1,9 +1,8 @@
package lila.game
import chess.Color
import java.security.SecureRandom
import ornicar.scalalib.Random
import lila.common.{ SecureRandom, ThreadLocalRandom }
import lila.db.dsl._
final class IdGenerator(gameRepo: GameRepo)(implicit ec: scala.concurrent.ExecutionContext) {
@ -32,16 +31,15 @@ final class IdGenerator(gameRepo: GameRepo)(implicit ec: scala.concurrent.Execut
object IdGenerator {
private[this] val secureRandom = new SecureRandom()
private[this] val whiteSuffixChars = ('0' to '4') ++ ('A' to 'Z') mkString
private[this] val blackSuffixChars = ('5' to '9') ++ ('a' to 'z') mkString
def uncheckedGame: Game.ID = lila.common.ThreadLocalRandom nextString Game.gameIdSize
def uncheckedGame: Game.ID = ThreadLocalRandom nextString Game.gameIdSize
def player(color: Color): Player.ID = {
// Trick to avoid collisions between player ids in the same game.
val suffixChars = color.fold(whiteSuffixChars, blackSuffixChars)
val suffix = suffixChars(secureRandom nextInt suffixChars.length)
Random.secureString(Game.playerIdSize - 1) + suffix
val suffix = suffixChars(SecureRandom nextInt suffixChars.length)
SecureRandom.nextString(Game.playerIdSize - 1) + suffix
}
}

View File

@ -319,6 +319,7 @@ val `membersOnly` = new I18nKey("membersOnly")
val `boardEditor` = new I18nKey("boardEditor")
val `setTheBoard` = new I18nKey("setTheBoard")
val `popularOpenings` = new I18nKey("popularOpenings")
val `endgamePositions` = new I18nKey("endgamePositions")
val `startPosition` = new I18nKey("startPosition")
val `clearBoard` = new I18nKey("clearBoard")
val `savePosition` = new I18nKey("savePosition")
@ -712,6 +713,31 @@ val `welcome` = new I18nKey("welcome")
val `lichessPatronInfo` = new I18nKey("lichessPatronInfo")
val `coachManager` = new I18nKey("coachManager")
val `streamerManager` = new I18nKey("streamerManager")
val `cancelTournament` = new I18nKey("cancelTournament")
val `tournDescription` = new I18nKey("tournDescription")
val `tournDescriptionHelp` = new I18nKey("tournDescriptionHelp")
val `ratedFormHelp` = new I18nKey("ratedFormHelp")
val `onlyMembersOfTeam` = new I18nKey("onlyMembersOfTeam")
val `noRestriction` = new I18nKey("noRestriction")
val `minimumRatedGames` = new I18nKey("minimumRatedGames")
val `minimumRating` = new I18nKey("minimumRating")
val `maximumWeeklyRating` = new I18nKey("maximumWeeklyRating")
val `onlyTitled` = new I18nKey("onlyTitled")
val `onlyTitledHelp` = new I18nKey("onlyTitledHelp")
val `positionInputHelp` = new I18nKey("positionInputHelp")
val `cancelSimul` = new I18nKey("cancelSimul")
val `simulHostcolor` = new I18nKey("simulHostcolor")
val `estimatedStart` = new I18nKey("estimatedStart")
val `simulFeatured` = new I18nKey("simulFeatured")
val `simulFeaturedHelp` = new I18nKey("simulFeaturedHelp")
val `simulDescription` = new I18nKey("simulDescription")
val `simulDescriptionHelp` = new I18nKey("simulDescriptionHelp")
val `markdownAvailable` = new I18nKey("markdownAvailable")
val `inYourLocalTimezone` = new I18nKey("inYourLocalTimezone")
val `tournChat` = new I18nKey("tournChat")
val `noChat` = new I18nKey("noChat")
val `onlyTeamLeaders` = new I18nKey("onlyTeamLeaders")
val `onlyTeamMembers` = new I18nKey("onlyTeamMembers")
val `opponentLeftCounter` = new I18nKey("opponentLeftCounter")
val `mateInXHalfMoves` = new I18nKey("mateInXHalfMoves")
val `nextCaptureOrPawnMoveInXHalfMoves` = new I18nKey("nextCaptureOrPawnMoveInXHalfMoves")
@ -776,6 +802,14 @@ val `thisIsPrivate` = new I18nKey("arena:thisIsPrivate")
val `shareUrl` = new I18nKey("arena:shareUrl")
val `drawStreak` = new I18nKey("arena:drawStreak")
val `history` = new I18nKey("arena:history")
val `newTeamBattle` = new I18nKey("arena:newTeamBattle")
val `customStartDate` = new I18nKey("arena:customStartDate")
val `customStartDateHelp` = new I18nKey("arena:customStartDateHelp")
val `allowBerserk` = new I18nKey("arena:allowBerserk")
val `allowBerserkHelp` = new I18nKey("arena:allowBerserkHelp")
val `allowChatHelp` = new I18nKey("arena:allowChatHelp")
val `arenaStreaks` = new I18nKey("arena:arenaStreaks")
val `arenaStreaksHelp` = new I18nKey("arena:arenaStreaksHelp")
val `drawingWithinNbMoves` = new I18nKey("arena:drawingWithinNbMoves")
val `viewAllXTeams` = new I18nKey("arena:viewAllXTeams")
}
@ -1238,7 +1272,6 @@ val `nothingHere` = new I18nKey("class:nothingHere")
val `newsEdit1` = new I18nKey("class:newsEdit1")
val `newsEdit2` = new I18nKey("class:newsEdit2")
val `newsEdit3` = new I18nKey("class:newsEdit3")
val `markdownAvailable` = new I18nKey("class:markdownAvailable")
val `nbPendingInvitations` = new I18nKey("class:nbPendingInvitations")
val `nbTeachers` = new I18nKey("class:nbTeachers")
val `nbStudents` = new I18nKey("class:nbStudents")
@ -1297,6 +1330,7 @@ val `fideMate` = new I18nKey("contact:fideMate")
val `knightMate` = new I18nKey("contact:knightMate")
val `noRatingPoints` = new I18nKey("contact:noRatingPoints")
val `ratedGame` = new I18nKey("contact:ratedGame")
val `botRatingAbuse` = new I18nKey("contact:botRatingAbuse")
val `errorPage` = new I18nKey("contact:errorPage")
val `reportErrorPage` = new I18nKey("contact:reportErrorPage")
val `banAppeal` = new I18nKey("contact:banAppeal")
@ -1567,6 +1601,7 @@ val `inputMovesWithTheKeyboard` = new I18nKey("preferences:inputMovesWithTheKeyb
val `snapArrowsToValidMoves` = new I18nKey("preferences:snapArrowsToValidMoves")
val `sayGgWpAfterLosingOrDrawing` = new I18nKey("preferences:sayGgWpAfterLosingOrDrawing")
val `yourPreferencesHaveBeenSaved` = new I18nKey("preferences:yourPreferencesHaveBeenSaved")
val `scrollOnTheBoardToReplayMoves` = new I18nKey("preferences:scrollOnTheBoardToReplayMoves")
}
object team {
@ -1845,6 +1880,13 @@ val `swissTournaments` = new I18nKey("swiss:swissTournaments")
val `roundsAreStartedManually` = new I18nKey("swiss:roundsAreStartedManually")
val `startingIn` = new I18nKey("swiss:startingIn")
val `nextRound` = new I18nKey("swiss:nextRound")
val `tournStartDate` = new I18nKey("swiss:tournStartDate")
val `nbRounds` = new I18nKey("swiss:nbRounds")
val `nbRoundsHelp` = new I18nKey("swiss:nbRoundsHelp")
val `roundInterval` = new I18nKey("swiss:roundInterval")
val `forbiddenPairings` = new I18nKey("swiss:forbiddenPairings")
val `forbiddenPairingsHelp` = new I18nKey("swiss:forbiddenPairingsHelp")
val `newSwiss` = new I18nKey("swiss:newSwiss")
val `viewAllXRounds` = new I18nKey("swiss:viewAllXRounds")
val `nbRounds` = new I18nKey("swiss:nbRounds")
val `oneRoundEveryXDays` = new I18nKey("swiss:oneRoundEveryXDays")
@ -2096,6 +2138,7 @@ val `waitingForMorePlayers` = new I18nKey("storm:waitingForMorePlayers")
val `raceComplete` = new I18nKey("storm:raceComplete")
val `spectating` = new I18nKey("storm:spectating")
val `joinTheRace` = new I18nKey("storm:joinTheRace")
val `startTheRace` = new I18nKey("storm:startTheRace")
val `yourRankX` = new I18nKey("storm:yourRankX")
val `waitForRematch` = new I18nKey("storm:waitForRematch")
val `nextRace` = new I18nKey("storm:nextRace")

View File

@ -113,7 +113,7 @@ final class IrcApi(
text = s":$icon: ${slackdown.linkifyUsers(text)}",
channel = s"tavern-monitor-${tpe.toString.toLowerCase}"
)
val md = s":$icon: ${markdown.linkifyUsers(text)}"
val md = s"${markdown.userLink(mod.name)} :$icon: ${markdown.linkifyUsers(text)}"
slack(msg) >>
slack(msg.copy(channel = SlackClient.rooms.tavernMonitorAll)) >>
zulip.mod(s"monitor-${tpe.toString.toLowerCase}")(md) >>

View File

@ -278,7 +278,7 @@ final class ModlogApi(repo: ModlogRepo, userRepo: UserRepo, ircApi: IrcApi)(impl
import lila.mod.{ Modlog => M }
val icon = m.action match {
case M.alt | M.engine | M.booster | M.troll | M.closeAccount => "thorhammer"
case M.unalt | M.unengine | M.unbooster | M.untroll | M.reopenAccount => "large_blue_circle"
case M.unalt | M.unengine | M.unbooster | M.untroll | M.reopenAccount => "blue_circle"
case M.deletePost | M.deleteTeam | M.terminateTournament => "x"
case M.chatTimeout => "hourglass_flowing_sand"
case M.closeTopic | M.disableTeam => "lock"

View File

@ -3,6 +3,7 @@ package lila.oauth
import org.joda.time.DateTime
import reactivemongo.api.bson._
import lila.common.SecureRandom
import lila.user.User
case class AccessToken(
@ -22,14 +23,11 @@ case class AccessToken(
object AccessToken {
val idSize = 16
case class Id(value: String) extends AnyVal {
def isPersonal = value.lengthIs == idSize
case class Id(value: String) extends AnyVal
object Id {
def random() = Id(s"lio_${SecureRandom.nextString(32)}")
}
def makeId = Id(ornicar.scalalib.Random secureString idSize)
case class ForAuth(userId: User.ID, scopes: List[OAuthScope])
case class WithApp(token: AccessToken, app: OAuthApp)

View File

@ -15,7 +15,7 @@ final class AccessTokenApi(colls: OauthColls)(implicit ec: scala.concurrent.Exec
def create(granted: AccessTokenRequest.Granted): Fu[AccessToken] = {
val token = AccessToken(
id = AccessToken.Id(Protocol.Secret.random("lio_").value),
id = AccessToken.Id.random(),
publicId = BSONObjectID.generate(),
clientId = PersonalToken.clientId, // TODO
userId = granted.userId,

View File

@ -17,7 +17,7 @@ object AccessTokenRequest {
for {
grantType <- grantType.toValid(Error.GrantTypeRequired).andThen(GrantType.from)
code <- code.map(AuthorizationCode.apply).toValid(Error.CodeRequired)
codeVerifier <- codeVerifier.map(CodeVerifier.apply).toValid(Error.CodeVerifierRequired)
codeVerifier <- codeVerifier.toValid(Error.CodeVerifierRequired).andThen(CodeVerifier.from)
redirectUri <- redirectUri.map(UncheckedRedirectUri.apply).toValid(Error.RedirectUriRequired)
clientId <- clientId.map(ClientId.apply).toValid(Error.ClientIdRequired)
} yield Prepared(grantType, code, codeVerifier, redirectUri, clientId)

View File

@ -13,7 +13,7 @@ final class AuthorizationApi(val coll: Coll)(implicit ec: scala.concurrent.Execu
val code = Protocol.AuthorizationCode.random()
coll.insert.one(
PendingAuthorizationBSONHandler write PendingAuthorization(
code.secret.hashed,
code.hashed,
request.clientId,
request.user,
request.redirectUri,
@ -27,7 +27,7 @@ final class AuthorizationApi(val coll: Coll)(implicit ec: scala.concurrent.Execu
def consume(
request: AccessTokenRequest.Prepared
): Fu[Validated[Protocol.Error, AccessTokenRequest.Granted]] =
coll.findAndModify($doc(F.hashedCode -> request.code.secret.hashed), coll.removeModifier) map {
coll.findAndModify($doc(F.hashedCode -> request.code.hashed), coll.removeModifier) map {
_.result[PendingAuthorization]
.toValid(Protocol.Error.AuthorizationCodeInvalid)
.ensure(Protocol.Error.AuthorizationCodeExpired)(_.expires.isAfter(DateTime.now()))

View File

@ -5,7 +5,6 @@ import cats.data.Validated
import com.roundeights.hasher.Algo
import io.lemonlabs.uri.AbsoluteUrl
import org.joda.time.DateTime
import ornicar.scalalib.Random
import play.api.libs.json._
import lila.user.User

View File

@ -2,6 +2,7 @@ package lila.oauth
import org.joda.time.DateTime
import lila.common.SecureRandom
import lila.user.User
import io.lemonlabs.uri.AbsoluteUrl
@ -21,8 +22,8 @@ object OAuthApp {
case class Id(value: String) extends AnyVal
case class Secret(value: String) extends AnyVal
def makeId = Id(ornicar.scalalib.Random secureString 16)
def makeSecret = Secret(ornicar.scalalib.Random secureString 32)
def makeId = Id(SecureRandom nextString 16)
def makeSecret = Secret(SecureRandom nextString 32)
object BSONFields {
val clientId = "client_id"

View File

@ -29,7 +29,7 @@ object OAuthForm {
) {
def make(user: lila.user.User) =
AccessToken(
id = AccessToken.makeId,
id = AccessToken.Id.random(),
publicId = BSONObjectID.generate(),
clientId = PersonalToken.clientId,
userId = user.id,

View File

@ -6,26 +6,16 @@ import cats.data.Validated
import play.api.libs.json.Json
import com.roundeights.hasher.Algo
import io.lemonlabs.uri.AbsoluteUrl
import ornicar.scalalib.Random
import lila.common.SecureRandom
object Protocol {
case class Secret(value: String) {
def hashed: String = Algo.sha256(value).hex
override def toString = "Secret(***)"
override def equals(other: Any) = other match {
case other: Secret => hashed == other.hashed
case _ => false
}
override def hashCode = hashed.hashCode()
case class AuthorizationCode(secret: String) extends AnyVal {
def hashed = Algo.sha256(secret).hex
override def toString = "AuthorizationCode(***)"
}
object Secret {
def random(prefix: String) = Secret(s"$prefix${Random.secureString(32)}")
}
case class AuthorizationCode(secret: Secret) extends AnyVal
object AuthorizationCode {
def apply(value: String): AuthorizationCode = AuthorizationCode(Secret(value))
def random() = AuthorizationCode(Secret.random("liu_"))
def random() = AuthorizationCode(s"liu_${SecureRandom.nextString(32)}")
}
case class ClientId(value: String) extends AnyVal
@ -50,6 +40,13 @@ object Protocol {
Base64.getUrlEncoder().withoutPadding().encodeToString(Algo.sha256(value).bytes)
)
}
object CodeVerifier {
def from(value: String): Validated[Error, CodeVerifier] =
Validated
.valid(value)
.ensure(Error.CodeVerifierTooShort)(_.size >= 43)
.map(CodeVerifier.apply)
}
case class ResponseType()
object ResponseType {
@ -83,7 +80,7 @@ object Protocol {
def code(code: AuthorizationCode, state: Option[State]): String = value
.withQueryString(
"code" -> Some(code.secret.value),
"code" -> Some(code.secret),
"state" -> state.map(_.value)
)
.toString
@ -128,13 +125,14 @@ object Protocol {
case object RedirectUriRequired extends InvalidRequest("redirect_uri required")
case object RedirectUriInvalid extends InvalidRequest("redirect_uri invalid")
case object RedirectSchemeNotAllowed
extends InvalidRequest("contact us to get exotic redirect_uri schemes whitelisted")
extends InvalidRequest("open a github issue to get exotic redirect_uri schemes whitelisted")
case object ResponseTypeRequired extends InvalidRequest("response_type required")
case object CodeChallengeRequired extends InvalidRequest("code_challenge required")
case object CodeChallengeMethodRequired extends InvalidRequest("code_challenge_method required")
case object GrantTypeRequired extends InvalidRequest("grant_type required")
case object CodeRequired extends InvalidRequest("code required")
case object CodeVerifierRequired extends InvalidRequest("code_verifier required")
case object CodeVerifierTooShort extends InvalidRequest("code_verifier too short")
case class InvalidScope(val key: String) extends Error("invalid_scope") {
def description = s"invalid scope: ${URLEncoder.encode(key, "UTF-8")}"

View File

@ -39,7 +39,7 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
.make(
owner = player,
puzzles = puzzles.grouped(2).flatMap(_.headOption).toList,
countdownSeconds = 10
countdownSeconds = 5
)
store.put(race.id, race)
lila.mon.racer.race(lobby = race.isLobby).increment()
@ -69,18 +69,23 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
def join(id: RacerRace.Id, player: RacerPlayer.Id): Option[RacerRace] =
get(id).flatMap(_ join player) map { r =>
val race = start(r) | r
val race = (r.isLobby ?? doStart(r)) | r
saveAndPublish(race)
race
}
private def start(race: RacerRace): Option[RacerRace] = race.startCountdown.map { starting =>
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
finish(race.id)
}
starting
private[racer] def manualStart(race: RacerRace): Unit = !race.isLobby ?? {
doStart(race) foreach saveAndPublish
}
private def doStart(race: RacerRace): Option[RacerRace] =
race.startCountdown.map { starting =>
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
finish(race.id)
}
starting
}
private def finish(id: RacerRace.Id): Unit =
get(id) foreach { race =>
lila.mon.racer.players(lobby = race.isLobby).record(race.players.size)

View File

@ -27,7 +27,8 @@ final class RacerJson(stormJson: StormJson, sign: StormSign, lightUserSync: Ligh
.add("lobby", race.isLobby),
"player" -> player,
"puzzles" -> race.puzzles
) ++ state(race)
)
.add("owner", race.owner == player.id) ++ state(race)
// socket updates
def state(race: RacerRace) = Json

View File

@ -1,5 +1,6 @@
package lila.racer
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.RemoteSocket.{ Protocol => P, _ }
import play.api.libs.json.{ JsObject, Json }
@ -28,6 +29,12 @@ final private class RacerSocket(
api.join(raceId, playerId).unit
case Protocol.In.PlayerScore(raceId, playerId, score) =>
api.registerPlayerScore(raceId, playerId, score)
case Protocol.In.RaceStart(raceId, playerId) =>
api
.get(raceId)
.filter(_.startsAt.isEmpty)
.filter(_.owner == playerId)
.foreach(api.manualStart)
}
remoteSocketApi.subscribe("racer-in", Protocol.In.reader)(
@ -45,6 +52,7 @@ object RacerSocket {
case class PlayerJoin(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
case class PlayerScore(race: RacerRace.Id, player: RacerPlayer.Id, score: Int) extends P.In
case class RaceStart(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
val reader: P.In.Reader = raw => raceReader(raw) orElse RP.In.reader(raw)
@ -58,6 +66,10 @@ object RacerSocket {
raw.get(3) { case Array(raceId, playerId, scoreStr) =>
scoreStr.toIntOption map { PlayerScore(RacerRace.Id(raceId), RacerPlayer.Id(playerId), _) }
}
case "racer/start" =>
raw.get(2) { case Array(raceId, playerId) =>
RaceStart(RacerRace.Id(raceId), RacerPlayer.Id(playerId)).some
}
case _ => none
}
}

View File

@ -6,7 +6,14 @@ import scala.concurrent.duration._
final class RelayMarkup {
private val renderer = new lila.common.Markdown(autoLink = true)
private val renderer =
new lila.common.Markdown(
autoLink = true,
list = true,
table = true,
strikeThrough = true,
header = true
)
private val cache: LoadingCache[String, String] = lila.memo.CacheApi.scaffeineNoScheduler
.expireAfterAccess(20 minutes)

View File

@ -1,7 +1,6 @@
package lila.security
import org.joda.time.DateTime
import ornicar.scalalib.Random
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.{ Constraint, Valid => FormValid, Invalid, ValidationError }
@ -12,7 +11,7 @@ import reactivemongo.api.ReadPreference
import scala.annotation.nowarn
import scala.concurrent.duration._
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest, IpAddress }
import lila.common.{ ApiVersion, EmailAddress, HTTPRequest, IpAddress, SecureRandom }
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.dsl._
import lila.oauth.{ AccessToken, OAuthScope, OAuthServer }
@ -94,7 +93,7 @@ final class SecurityApi(
userRepo mustConfirmEmail userId flatMap {
case true => fufail(SecurityApi MustConfirmEmail userId)
case false =>
val sessionId = Random secureString 22
val sessionId = SecureRandom nextString 22
if (tor isExitNode HTTPRequest.ipAddress(req)) logger.info(s"Tor login $userId")
store.save(sessionId, userId, req, apiVersion, up = true, fp = none) inject sessionId
}
@ -102,7 +101,7 @@ final class SecurityApi(
def saveSignup(userId: User.ID, apiVersion: Option[ApiVersion], fp: Option[FingerPrint])(implicit
req: RequestHeader
): Funit = {
val sessionId = lila.common.ThreadLocalRandom nextString 22
val sessionId = SecureRandom nextString 22
store.save(s"SIG-$sessionId", userId, req, apiVersion, up = false, fp = fp)
}
@ -211,7 +210,7 @@ final class SecurityApi(
sessionId.startsWith(prefix) ?? store.getIfPresent(sessionId)
def saveAuthentication(userId: User.ID)(implicit req: RequestHeader): Fu[SessionId] = {
val sessionId = s"$prefix${Random secureString 22}"
val sessionId = s"$prefix${SecureRandom nextString 22}"
store.put(sessionId, userId)
logger.info(s"Appeal login by $userId")
fuccess(sessionId)

View File

@ -1,11 +1,11 @@
package lila.user
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.{ IvParameterSpec, SecretKeySpec }
import com.roundeights.hasher.Implicits._
import lila.common.SecureRandom
import lila.common.config.Secret
/** Encryption for bcrypt hashes.
@ -54,14 +54,12 @@ final private class PasswordHasher(
import org.mindrot.BCrypt
import User.ClearPassword
private val prng = new SecureRandom()
private val aes = new Aes(secret)
private val aes = new Aes(secret)
private def bHash(salt: Array[Byte], p: ClearPassword) =
hashTimer(BCrypt.hashpwRaw(p.value.sha512, 'a', logRounds, salt))
def hash(p: ClearPassword): HashedPassword = {
val salt = new Array[Byte](16)
prng.nextBytes(salt)
val salt = SecureRandom.nextBytes(16)
HashedPassword(salt ++ aes.encrypt(Aes.iv(salt), bHash(salt, p)))
}

View File

@ -1,12 +1,13 @@
package lila.user
import org.apache.commons.codec.binary.Base32
import reactivemongo.api.bson._
import java.security.SecureRandom
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.nio.ByteBuffer
import org.apache.commons.codec.binary.Base32
import reactivemongo.api.bson._
import lila.common.SecureRandom
import User.TotpToken
case class TotpSecret(secret: Array[Byte]) extends AnyVal {
@ -45,15 +46,9 @@ object TotpSecret {
"0" * (6 - s.length) + s
}
private[this] val secureRandom = new SecureRandom()
def apply(base32: String) = new TotpSecret(new Base32().decode(base32))
def random: TotpSecret = {
val secret = new Array[Byte](20)
secureRandom.nextBytes(secret)
TotpSecret(secret)
}
def random = TotpSecret(SecureRandom.nextBytes(20))
private[user] val totpSecretBSONHandler = lila.db.dsl.quickHandler[TotpSecret](
{ case v: BSONBinary => TotpSecret(v.byteArray) },

View File

@ -22,7 +22,7 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
val normalize = User normalize _
def topNbGame(nb: Int): Fu[List[User]] =
coll.find(enabledSelect).sort($sort desc "count.game").cursor[User]().list(nb)
coll.find(enabledNoBotSelect).sort($sort desc "count.game").cursor[User]().list(nb)
def byId(id: ID): Fu[Option[User]] = User.noGhost(id) ?? coll.byId[User](id)
@ -277,6 +277,7 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
$doc(F.marks -> UserMark.Boost.key),
$doc(F.marks -> UserMark.Troll.key)
)
val enabledNoBotSelect = enabledSelect ++ $doc(F.title $ne Title.BOT)
def stablePerfSelect(perf: String) =
$doc(s"perfs.$perf.gl.d" -> $lt(lila.rating.Glicko.provisionalDeviation))
val patronSelect = $doc(s"${F.plan}.active" -> true)
@ -391,8 +392,6 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
def updateTroll(user: User) = setTroll(user.id, user.marks.troll)
def isEngine(id: ID): Fu[Boolean] = coll.exists($id(id) ++ engineSelect(true))
def filterEngine(ids: Seq[ID]): Fu[Set[ID]] =
coll.distinct[ID, Set]("_id", Some($inIds(ids) ++ engineSelect(true)))

View File

@ -8,7 +8,7 @@ object Dependencies {
val scalalib = "com.github.ornicar" %% "scalalib" % "7.0.2"
val hasher = "com.roundeights" %% "hasher" % "1.2.1"
val jodaTime = "joda-time" % "joda-time" % "2.10.10"
val chess = "org.lichess" %% "scalachess" % "10.2.2"
val chess = "org.lichess" %% "scalachess" % "10.2.4"
val compression = "org.lichess" %% "compression" % "1.6"
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.3.1-THIB"
val prismic = "io.prismic" %% "scala-kit" % "1.2.19-THIB213"
@ -22,7 +22,7 @@ object Dependencies {
val autoconfig = "io.methvin.play" %% "autoconfig-macros" % "0.3.2" % "provided"
val scalatest = "org.scalatest" %% "scalatest" % "3.1.0" % Test
val uaparser = "org.uaparser" %% "uap-scala" % "0.13.0"
val specs2 = "org.specs2" %% "specs2-core" % "4.12.1" % Test
val specs2 = "org.specs2" %% "specs2-core" % "4.12.2" % Test
val apacheText = "org.apache.commons" % "commons-text" % "1.9"
val bloomFilter = "com.github.alexandrnikitin" %% "bloom-filter" % "0.13.1"

View File

@ -91,7 +91,7 @@ lichess.advantageChart = function (data, trans, el) {
events: {
click: function (event) {
if (event.point) {
event.point.select();
event.point.select(true);
lichess.pubsub.emit('analysis.chart.click', event.point.x);
}
},

View File

@ -27,12 +27,12 @@
<item quantity="other">Spillede %1$s træk</item>
</plurals>
<plurals name="inNbCorrespondenceGames">
<item quantity="one">i %1$s korrespondance-spil</item>
<item quantity="other">i %1$s korrespondance-spil</item>
<item quantity="one">i %1$s korrespondanceparti</item>
<item quantity="other">i %1$s korrespondancepartier</item>
</plurals>
<plurals name="completedNbGames">
<item quantity="one">Afsluttede %s korrespondancespil</item>
<item quantity="other">Afsluttede %s korrespondancespil</item>
<item quantity="one">Afsluttede %s korrespondanceparti</item>
<item quantity="other">Afsluttede %s korrespondancepartier</item>
</plurals>
<plurals name="followedNbPlayers">
<item quantity="one">Begyndte at følge %s spiller</item>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity">Verlauf</string>
<string name="hostedALiveStream">Hät en livestream gmacht</string>
<string name="hostedALiveStream">Hät en liveschtriim gmacht</string>
<plurals name="supportedNbMonths">
<item quantity="one">Unterstützt lichess.org sit %1$s Monät als %2$s</item>
<item quantity="other">Unterstützt lichess.org seit %1$s Monaten als %2$s</item>

View File

@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity">Գործունեություն</string>
<plurals name="competedInNbTournaments">
<item quantity="one">Ավարտված է %s «Արենա» մրցաշարը</item>
<item quantity="other">Ավարտված են %s «Արենա» մրցաշարերը</item>
</plurals>
</resources>

View File

@ -43,4 +43,10 @@
<item quantity="one">Вижте отбора</item>
<item quantity="other">Вижте всички %s отбора</item>
</plurals>
<string name="newTeamBattle">Нова отборна битка</string>
<string name="customStartDate">Начална дата</string>
<string name="allowBerserk">Позволи Берсерк</string>
<string name="allowChatHelp">Позволи на играчите да обсъждат в чата</string>
<string name="arenaStreaks">Последователности в арената</string>
<string name="arenaStreaksHelp">След 2 победи, всяка последователна победа носи 4 точки вместо 2.</string>
</resources>

View File

@ -41,4 +41,6 @@ Juga ràpid i torna al vestíbul per jugar més partits i guanyar més punts.</s
<item quantity="one">Veure l\'equip</item>
<item quantity="other">Veure els %s equips</item>
</plurals>
<string name="newTeamBattle">Nova batalla d\'equips</string>
<string name="allowBerserk">Es permet Berserk</string>
</resources>

View File

@ -24,7 +24,7 @@ Bersærk giver kun ekstra point, hvis du laver mindst 7 træk i partiet.</string
<string name="howIsTheWinnerDecided">Hvordan findes vinderen?</string>
<string name="howIsTheWinnerDecidedAnswer">Spilleren (eller spillerne) med flest point, når turneringens fastsatte tidsgrænse nås, erklæres for vinder(e).</string>
<string name="howDoesPairingWork">Hvordan foretages pardannelse?</string>
<string name="howDoesPairingWorkAnswer">Ved starten på turneringen parres spillere på baggrund af deres rating. Vend tilbage til turneringslobbyen, når du afslutter et parti: så vil du igen blive parret med en spiller tæt på din score. Det sikrer minimal ventetid, men du møder måske ikke alle andre deltagere i turneringen.
<string name="howDoesPairingWorkAnswer">Ved starten på turneringen parres spillere på baggrund af deres rating. Vend tilbage til turneringslobbyen, når du afslutter et parti: så vil du igen blive parret med en spiller tæt på din ranking. Det sikrer minimal ventetid, men du møder måske ikke alle andre deltagere i turneringen.
Spil hurtigt og vend tilbage til lobbyen for at spille flere partier og vinde flere point.</string>
<string name="howDoesItEnd">Hvordan afsluttes det?</string>
<string name="howDoesItEndAnswer">Turneringen har et nedtællingsur. Når det rammer nul fastfryses turneringsplaceringer, og en vinder kåres. Igangværende partier skal spilles færdig, men de tæller ikke med i turneringen.</string>
@ -42,4 +42,12 @@ Spil hurtigt og vend tilbage til lobbyen for at spille flere partier og vinde fl
<item quantity="one">Se holdet</item>
<item quantity="other">Se alle %s hold</item>
</plurals>
<string name="newTeamBattle">Ny holdkamp</string>
<string name="customStartDate">Tilpasset startdato</string>
<string name="customStartDateHelp">I din egen lokale tidszone. Dette tilsidesætter \"Tid før turnering starter\" indstillingen</string>
<string name="allowBerserk">Tillad Berserk</string>
<string name="allowBerserkHelp">Lad spillerne halvere deres tid på uret for at få et ekstra point</string>
<string name="allowChatHelp">Lad spillere diskutere i et chatrum</string>
<string name="arenaStreaks">Arena-stime</string>
<string name="arenaStreaksHelp">Efter 2 sejre giver fortløbende sejre 4 point i stedet for 2.</string>
</resources>

View File

@ -29,8 +29,7 @@ Wenn zwei oder meh Schpiller/-innä di gliich Punktzahl bsitzäd, entscheidät d
Sobald du es Schpiil beändät häsch, chasch du zur Turnierübersicht zrugggoh. Dir wird dänn än Schpiller/ ä Schpillerin mit ähnlichäm Rang zuäteilt. So wird ä churzi Warteziit erreicht, allerdings cha passiärä, dass du nöd geg alli anderä Schpiller/-innä im Turnier spillsch.
Prässiär und gohn zur Turnierübersicht zrugg, zum meh Schpiil z\'schpilä und ä höcheri Punktzahl z\'erziilä.</string>
<string name="howDoesItEnd">Wänn ändät äsTurnier?</string>
<string name="howDoesItEndAnswer">WährendmäTurnier wird d\'Ziit abäzellt. Wenn si null erreicht, wird d\'Ranglischte iigfrorä und dä Gwünner bekanntgeh.
Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</string>
<string name="howDoesItEndAnswer">WährendmäTurnier wird d\'Ziit abäzellt. Wenn si null erreicht, wird d\'Ranglischte iigfrorä und dä Gwünner bekanntgeh. Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</string>
<string name="otherRules">Anderi wichtigi Reglä</string>
<string name="thereIsACountdown">Es git en Countdown für din erschtä Zug. Züchsch du nöd innerhalb vo därä Zit, wird dä Siig dim Gegner zuegsprochä. Usserdem bechömäd Zürcher immer ein Punkt Abzug.</string>
<plurals name="drawingWithinNbMoves">
@ -45,4 +44,12 @@ Laufendi Partiä münd beändä wärdä, zelläd aber nümä für das Turnier.</
<item quantity="one">Das Team ansehen</item>
<item quantity="other">Alli %s Teams aluägä</item>
</plurals>
<string name="newTeamBattle">Neuer Teamkampf</string>
<string name="customStartDate">Benutzerdefiniertes Startdatum</string>
<string name="customStartDateHelp">In deiner eigenen, lokalen Zeitzone. Dies wird die Einstellung \"Zeit bevor das Turnier beginnt\" überschreiben</string>
<string name="allowBerserk">Berserk erlauben</string>
<string name="allowBerserkHelp">Lasse Spieler ihre Bedenkzeit halbieren, um einen zusätzlichen Punkt zu erzielen</string>
<string name="allowChatHelp">Lasse Spieler in einem Chatraum diskutieren</string>
<string name="arenaStreaks">Arena-Siegesserien</string>
<string name="arenaStreaksHelp">Nach 2 Siegen gewähren aufeinander folgende Siege 4 Punkte anstelle von 2.</string>
</resources>

View File

@ -29,8 +29,7 @@ Wenn zwei oder mehr Spieler die gleiche Punktzahl besitzen, entscheidet die Turn
Sobald du ein Spiel beendet hast, kannst du zur Turnierübersicht zurückkehren. Dir wird dann ein Spieler mit ähnlichem Rang zugeteilt. Dadurch wird eine kurze Wartezeit erreicht, allerdings kann es sein, dass du nicht gegen alle anderen Spieler in diesem Turnier spielen wirst.
Spiele schnell und kehre zur Turnierübersicht zurück, um mehr Spiele zu spielen und eine höhere Punktzahl zu erzielen.</string>
<string name="howDoesItEnd">Wann endet das Turnier?</string>
<string name="howDoesItEndAnswer">Während des Turniers wird die Zeit heruntergezählt. Wenn sie null erreicht, wird die Rangliste eingefroren und der Gewinner bekanntgegeben.
Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnier.</string>
<string name="howDoesItEndAnswer">Während des Turniers wird die Zeit heruntergezählt. Wenn sie null erreicht, wird die Rangliste eingefroren und der Gewinner bekanntgegeben. Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnier.</string>
<string name="otherRules">Andere wichtige Regeln</string>
<string name="thereIsACountdown">Es gibt einen Countdown für deinen ersten Zug. Ziehst du nicht innerhalb dieser Zeit, wird der Sieg deinem Gegner zugesprochen.</string>
<plurals name="drawingWithinNbMoves">
@ -45,4 +44,12 @@ Laufende Partien müssen beendet werden, zählen aber nicht mehr für das Turnie
<item quantity="one">Das Team ansehen</item>
<item quantity="other">Alle %s Teams ansehen</item>
</plurals>
<string name="newTeamBattle">Neuer Teamkampf</string>
<string name="customStartDate">Benutzerdefiniertes Startdatum</string>
<string name="customStartDateHelp">In deiner eigenen, lokalen Zeitzone. Dies wird die Einstellung \"Zeit bevor das Turnier beginnt\" überschreiben</string>
<string name="allowBerserk">Berserk erlauben</string>
<string name="allowBerserkHelp">Lasse Spieler ihre Bedenkzeit halbieren, um einen zusätzlichen Punkt zu erzielen</string>
<string name="allowChatHelp">Lasse Spieler in einem Chatraum diskutieren</string>
<string name="arenaStreaks">Arena-Siegesserien</string>
<string name="arenaStreaksHelp">Nach 2 Siegen gewähren aufeinander folgende Siege 4 Punkte anstelle von 2.</string>
</resources>

View File

@ -44,4 +44,12 @@ To Berserk δεν ισχύει για παρτίδες με μηδενικό α
<item quantity="one">Δείτε την ομάδα</item>
<item quantity="other">Δείτε όλες τις %s ομάδες</item>
</plurals>
<string name="newTeamBattle">Νέα ομαδική μάχη</string>
<string name="customStartDate">Προσαρμοσμένη ημερομηνία έναρξης</string>
<string name="customStartDateHelp">Στην τοπική ζώνη ώρας σας. Αυτό υπερισχύει της ρύθμισης \"Χρόνος προτού ξεκινήσει το πρωτάθλημα\"</string>
<string name="allowBerserk">Να επιτρέπεται το Berserk</string>
<string name="allowBerserkHelp">Να επιτρέπεται στους παίκτες να μειώσουν τον χρόνο στο ρολόι τους κατά το ήμισυ για να κερδίσουν έναν επιπλέον πόντο</string>
<string name="allowChatHelp">Να επιτρέπεται στους παίκτες να συζητούν σε δωμάτιο συνομιλίας</string>
<string name="arenaStreaks">Arena streaks</string>
<string name="arenaStreaksHelp">Μετά από 2 νίκες, επιπλέον διαδοχικές νίκες σας 4 πόντους αντί για 2.</string>
</resources>

View File

@ -45,4 +45,12 @@ Play fast and return to the lobby to play more games and win more points.</strin
<item quantity="one">View the team</item>
<item quantity="other">View all %s teams</item>
</plurals>
<string name="newTeamBattle">New Team Battle</string>
<string name="customStartDate">Custom start date</string>
<string name="customStartDateHelp">In your own local timezone. This overrides the \"Time before tournament starts\" setting</string>
<string name="allowBerserk">Allow Berserk</string>
<string name="allowBerserkHelp">Let players halve their clock time to gain an extra point</string>
<string name="allowChatHelp">Let players discuss in a chat room</string>
<string name="arenaStreaks">Arena streaks</string>
<string name="arenaStreaksHelp">After 2 wins, consecutive wins grant 4 points instead of 2.</string>
</resources>

View File

@ -44,4 +44,12 @@ Juega rápido y vuelve al recibidor para jugar más partidas y ganar más puntos
<item quantity="one">Ver equipo</item>
<item quantity="other">Ver los %s equipos</item>
</plurals>
<string name="newTeamBattle">Nueva batalla de equipo</string>
<string name="customStartDate">Fecha de inicio personalizada</string>
<string name="customStartDateHelp">En tu propia zona horaria local. Esto reemplaza la configuración de «Tiempo antes de que comience el torneo».</string>
<string name="allowBerserk">Permitir Berserk</string>
<string name="allowBerserkHelp">Permite a los jugadores reducir el tiempo de la partida a la mitad para ganar un punto extra</string>
<string name="allowChatHelp">Permite a los jugadores comunicarse en una sala de chat</string>
<string name="arenaStreaks">Rachas de torneos</string>
<string name="arenaStreaksHelp">Después de ganar 2 partidas seguidas, cada victoria consecutiva concede 4 puntos en lugar de 2.</string>
</resources>

View File

@ -43,4 +43,12 @@ Jokatu azkar, itzuli egongelara partida gehiago jokatu eta puntu gehiago irabazt
<item quantity="one">Taldea ikusi</item>
<item quantity="other">%s taldeak ikusi</item>
</plurals>
<string name="newTeamBattle">Taldeen Arteko Txapelketa berria</string>
<string name="customStartDate">Pertsonalizatutako hasiera data</string>
<string name="customStartDateHelp">Zure ordu-zonan. Honek \"Txapeketa hasi aurreko denbora\" ezarpena gainidazten du</string>
<string name="allowBerserk">Berserk onartu</string>
<string name="allowBerserkHelp">Utzi jokalariei beren denbora erdira jaisten puntu gehigarri bat lortzeko</string>
<string name="allowChatHelp">Utzi jokalariei txatean hitz egiten</string>
<string name="arenaStreaks">Arena boladak</string>
<string name="arenaStreaksHelp">Bi garaipenen ostean, jarraian datozen garaipenek 4 puntu emango dituzte 2 eman beharrean.</string>
</resources>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="arenaTournaments">مسابقات</string>
<string name="isItRated">آیا رسمی است؟</string>
<string name="willBeNotified">هنگام شروع تورنومنت به شما اطلاع داده خواهد شد، پس در زمان انتظار بازی کردن در تب های دیگر بلامانع است.</string>
<string name="isRated">این تورنومنت رسمی است و بر روی ریتینگ شما تاثیر میگذارد.</string>
@ -25,10 +26,17 @@
<string name="howDoesPairingWorkAnswer">در ابتدای تورنومنت، بازیکنان با توجه به امتیازهایشان با یکدیگر روبه رو میشوند.
به محض اینکه شما بازی را تمام کردید و به لابی تورنومنت برگردید: شما به بازیکنی که رتبه نزدیک تری به شما دارد متصل میشوید. این مکانیزم در حداقل زمان ممکن انجام میشود، هرچند امکان دارد با همه بازیکنان تورنومنت روبه رو نشوید.
سریع بازی کنید و به لابی برگردید تا بازی های بیشتری بازی کنید و امتیاز بیشتری بگیرید.</string>
<string name="howDoesItEnd">چگونه پایان میابد؟</string>
<string name="howDoesItEndAnswer">تورنومنت یک ساعت شمارش معکوس دارد. وقتی به صفر برسد، رتبه بندی تورنومنت غیرقابل تغییر میشود، و برنده اعلام میشود.
بازی های در حال اجرا باید تمام شوند، هر چند نتیجه آن بازی ها تاثیری در تورنومنت ندارد.</string>
<string name="howDoesItEnd">چگونه به پایان می رسد؟</string>
<string name="howDoesItEndAnswer">تورنومنت یک ساعت شمارش معکوس دارد. وقتی به صفر برسد، رتبه بندی تورنومنت غیرقابل تغییر میشود، و برنده اعلام میشود. بازی های در حال اجرا باید تمام شوند، هر چند نتیجه آن بازی ها تاثیری در تورنومنت ندارد.</string>
<string name="otherRules">قوانین مهم دیگر</string>
<string name="thisIsPrivate">این یک تورنومنت خصوصی است</string>
<string name="shareUrl">این لینک را برای پیوستن دیگران به اشتراک بگذارید.%s</string>
<string name="history">تاریخچه مسابقات</string>
<string name="newTeamBattle">مبارزه تیمی جدید</string>
<string name="customStartDate">تنظیم تاریخ شروع</string>
<string name="customStartDateHelp">در منطقه زمانی محلی خودتان. تنظیمات \"زمان قبل شروع شدن مسابقه\" را نادیده می گیرد</string>
<string name="allowBerserk">مجاز کردن برسرک</string>
<string name="allowBerserkHelp">به بازیکنان اجازه دهید تا زمان خود را نصف کنند تا یک امتیاز اضافی بگیرند</string>
<string name="allowChatHelp">اجازه دادن بحث به بازیکنان در چت روم</string>
<string name="arenaStreaksHelp">بعد از دو برد، بردهای پی در پی بجای 2 امتیاز 4 امتیاز می دهند.</string>
</resources>

View File

@ -43,4 +43,12 @@ Pelaa nopeasti ja palaa aulaan, niin voit pelata lisää pelejä ja ansaita lis
<item quantity="one">Näytä joukkue</item>
<item quantity="other">Näytä kaikki %s joukkuetta</item>
</plurals>
<string name="newTeamBattle">Uusi joukkuetaisto</string>
<string name="customStartDate">Mukautettu alkamisaika</string>
<string name="customStartDateHelp">Omalla aikavyöhykkeelläsi. Tämä korvaa asetuksen \"Aika ennen turnauksen alkua\"</string>
<string name="allowBerserk">Salli berserkki</string>
<string name="allowBerserkHelp">Anna pelaajien puolittaa aika kellossaan ja tavoitella lisäpistettä</string>
<string name="allowChatHelp">Anna pelaajien kirjoittaa keskusteluhuoneeseen</string>
<string name="arenaStreaks">Areenaputket</string>
<string name="arenaStreaksHelp">Kahden voiton jälkeen jokaisesta seuraavasta voitosta saa 4 pistettä (2 pisteen sijaan).</string>
</resources>

View File

@ -42,4 +42,12 @@ Jouez vite et retournez à la page d\'accueil du tournoi pour jouer plus de part
<item quantity="one">Voir l\'équipe</item>
<item quantity="other">Voir toutes les équipes %s</item>
</plurals>
<string name="newTeamBattle">Nouveau combat en équipe</string>
<string name="customStartDate">Date de début modifiée</string>
<string name="customStartDateHelp">Selon votre fuseau horaire. Ce paramètre désactive le paramètre « Temps restant avant le début du tournoi ».</string>
<string name="allowBerserk">Activer l\'option Berserk</string>
<string name="allowBerserkHelp">Permettre aux joueurs de réduire de moitié leur temps pour gagner un point supplémentaire</string>
<string name="allowChatHelp">Permettre aux joueurs de clavarder dans le salon de discussion</string>
<string name="arenaStreaks">Série de victoires dans l\'arène</string>
<string name="arenaStreaksHelp">Après 2 victoires, des victoires consécutives donnent 4 points au lieu de 2.</string>
</resources>

View File

@ -51,4 +51,12 @@ Imir go tapa agus filleadh chuig an forhalla chun níos mó cluichí a imirt agu
<item quantity="many">Féach ar na %s foireann go léir</item>
<item quantity="other">Féach ar na %s foireann go léir</item>
</plurals>
<string name="newTeamBattle">Cath Foirne Nua</string>
<string name="customStartDate">Dáta tosaigh saincheaptha</string>
<string name="customStartDateHelp">I do chrios ama áitiúil féin. Sáraíonn sé seo an socrú \"Am sula dtosaíonn an comórtas\"</string>
<string name="allowBerserk">Lig Berserk</string>
<string name="allowBerserkHelp">Lig dimreoirí a gcuid ama clog a laghdú chun pointe breise a fháil</string>
<string name="allowChatHelp">Lig dimreoirí plé a dhéanamh i seomra comhrá</string>
<string name="arenaStreaks">Sruthanna airéine</string>
<string name="arenaStreaksHelp">Tar éis 2 bhua, deonaíonn 4 bhua as a chéile 4 phointe in ionad 2.</string>
</resources>

View File

@ -43,4 +43,12 @@ Xoga rápido e volta ao lobby para xogar máis partidas e gañar máis puntos.</
<item quantity="one">Ver o equipo</item>
<item quantity="other">Ver todos os %s equipos</item>
</plurals>
<string name="newTeamBattle">Nova batalla por equipos</string>
<string name="customStartDate">Fecha de comezo persoalizada</string>
<string name="customStartDateHelp">Na túa zona de tempo local. Isto sobreescribe a preferencia \"Tempo antes de que o torneo comece\"</string>
<string name="allowBerserk">Berserk permitido</string>
<string name="allowBerserkHelp">Deixa ás xogadoras que reduzan á metade o seu tempo para gañar un ponto extra</string>
<string name="allowChatHelp">Deixa ás xogadoras falar nunha sala de chat</string>
<string name="arenaStreaks">Racha de victorias no Torneo</string>
<string name="arenaStreaksHelp">Despois de 2 victorias, as seguintes victorias consecutivas dan 4 pontos en lugar de 2.</string>
</resources>

View File

@ -43,4 +43,12 @@ Játssz gyorsan, térj vissza a lobbiba további játszmákért, hogy több pont
<item quantity="one">Csapat megtekintése</item>
<item quantity="other">Mind a %s csapat megtekintése</item>
</plurals>
<string name="newTeamBattle">Új csapatverseny</string>
<string name="customStartDate">Kezdés időpontja</string>
<string name="customStartDateHelp">Saját időzóna szerint. Ez felülbírálja a \"Hátralévő idő a verseny kezdetéig\" beállítást</string>
<string name="allowBerserk">Berserk engedélyezése</string>
<string name="allowBerserkHelp">A játékosok saját idejük feléért cserébe többletpontot kapnak</string>
<string name="allowChatHelp">Chatszoba engedélyezése a játékosoknak</string>
<string name="arenaStreaks">Aréna sorozatok</string>
<string name="arenaStreaksHelp">A második nyert játszmát követően minden nyert játszma 4 pontot ér 2 helyett.</string>
</resources>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="arenaTournaments">«Արենա» մրցաշարեր</string>
<string name="isItRated">Գնահատված է?</string>
<string name="willBeNotified">Դուք կտեղեկացվեք մրցաշարի սկզբի մասին, այնպես որ հանգիստ սպասեք:</string>
<string name="isRated">Այս մրցաշարում վարկանիշի հաշվարկ կա, այնպես որ այն կազդի Ձեր վարկանիշի վրա:</string>
@ -26,4 +27,6 @@
<string name="howDoesPairingWorkAnswer">Մրցաշարի սկզբում զույգերը կազմվում են ըստ անհատական վարկանիշների:
Խաղի ավարտից հետո սեղմեք &lt;&lt;դեպի մրցաշար&gt;&gt; հետ վերադարձի կոճակը, որից հետո Դուք կստանաք նոր մրցակից՝ ըստ Ձեր զբաղեցրած դիրքի: Վիճակահանությունը կտևի ոչ այդքան երկար, սակայն բոլորի հետ խաղալ չեք կարող, միայն Ձեր մոտակայքում գտնվող խաղացողներից կկազմվի զույգը:
Խաղացեք արագ և վերադարձեք մրցաշարի հիմնական էջ, որպեսզի խաղաք շատ խաղեր և հավաքեք շատ միավորներ:</string>
<string name="drawStreak">Ոչ-ոքիների շարք. եթե խաղացողը «Արենայում» ունի իրար հաջորդող մի քանի ոչ-ոքի, ապա միավոր է տրվում միայն առաջին ոչ-ոքիի համար, կամ այն ոչ-ոքիի համար, որը կտևի %s քայլից ավելի։ Ոչ-ոքիների շարքը կարելի է ընդհատել միայն հաղթանակով, բայց ոչ պարտությամբ կամ ևս մեկ ոչ-ոքիով։</string>
<string name="history">«Արենայի» պատմություն</string>
</resources>

View File

@ -28,4 +28,12 @@
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other]&#10;&#10;%s is the number of teams that can be viewed">
<item quantity="other">Lihat semua %s tim</item>
</plurals>
<string name="newTeamBattle">Pertempuran Tim Baru</string>
<string name="customStartDate">Tanggal mulai khusus</string>
<string name="customStartDateHelp">Dalam zona waktu lokal Anda sendiri. Ini akan menimpa pengaturan \"Waktu sebelum turnamen dimulai\"</string>
<string name="allowBerserk">Izinkan Berserk</string>
<string name="allowBerserkHelp">Biarkan pemain memotong waktu jam mereka untuk mendapatkan sebuah poin ekstra</string>
<string name="allowChatHelp">Biarkan pemain berdiskusi dalam sebuah ruang obrol</string>
<string name="arenaStreaks">Streak arena</string>
<string name="arenaStreaksHelp">Setelah 2 kemenangan, secar berurutan akan mendapatkan 4 poin alih-alih 2.</string>
</resources>

View File

@ -35,4 +35,12 @@
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other]&#10;&#10;%s is the number of teams that can be viewed">
<item quantity="other">%s チームを表示</item>
</plurals>
<string name="newTeamBattle">新しいチームバトル</string>
<string name="customStartDate">開始日を指定</string>
<string name="customStartDateHelp">自分のタイムゾーンでの日時。これは「トーナメント開始時刻」の設定より優先されます。</string>
<string name="allowBerserk">バーサークあり</string>
<string name="allowBerserkHelp">持時間半減で 1 ポイントボーナスを認める</string>
<string name="allowChatHelp">チャットルームでの会話を認める</string>
<string name="arenaStreaks">連勝ボーナスあり</string>
<string name="arenaStreaksHelp">2 連勝すると以降の勝ちは 2 ではなく 4 ポイントになる。</string>
</resources>

View File

@ -43,4 +43,12 @@
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other]&#10;&#10;%s is the number of teams that can be viewed">
<item quantity="other">모두 %s개 팀 보기</item>
</plurals>
<string name="newTeamBattle">새 팀 배틀</string>
<string name="customStartDate">시작시간 설정</string>
<string name="customStartDateHelp">당신의 현지 시간대입니다. \"토너먼트 시작까지 시간\" 설정을 덮어씁니다.</string>
<string name="allowBerserk">버서크 모드 허용</string>
<string name="allowBerserkHelp">플레이어는 자신의 시간을 절반으로 줄이며 대신 추가 승점을 얻습니다</string>
<string name="allowChatHelp">플레이어가 채팅방에서 토론할 수 있습니다</string>
<string name="arenaStreaks">아레나 연승</string>
<string name="arenaStreaksHelp">2승 이후 추가로 연승을 하면 2점이 아니라 4점을 얻습니다.</string>
</resources>

View File

@ -47,4 +47,12 @@ Kai tik baigiate partiją, grįžkite į turnyro laukiamąjį: tuomet būsite su
<item quantity="many">Peržvelgti visas %s komandų</item>
<item quantity="other">Peržvelgti visas %s komandų</item>
</plurals>
<string name="newTeamBattle">Nauja komandinė kova</string>
<string name="customStartDate">Kita pradžios data</string>
<string name="customStartDateHelp">Jūsų laiko zonoje. Turi pirmenybę prieš \"Laikas iki turnyro pradžios\" nustatymą</string>
<string name="allowBerserk">Leisti \"įsiutį\"</string>
<string name="allowBerserkHelp">Leisti žaidėjams gauti tašką perpus sumažinant laiką ant savo laikrodžio</string>
<string name="allowChatHelp">Leisti žaidėjams kalbėtis pokalbių kambaryje</string>
<string name="arenaStreaks">Arenos serijos</string>
<string name="arenaStreaksHelp">Po dviejų pergalių kiti laimėjimai suteikia keturis taškus vietoje dviejų.</string>
</resources>

View File

@ -48,4 +48,12 @@ Spēlē ātri un atgriezies vestibilā lai spēlētu vairāk spēļu un iegūtu
<item quantity="one">Skatīt %s komandu</item>
<item quantity="other">Skatīt visas %s komandas</item>
</plurals>
<string name="newTeamBattle">Jauna Komandas Kauja</string>
<string name="customStartDate">Pielāgots sākuma datums</string>
<string name="customStartDateHelp">Jūsu laika zonā. Šis aizstās iestatījumu \"Laiks līdz turnīra sākumam\"</string>
<string name="allowBerserk">Atļaut \"dullās\" spēles</string>
<string name="allowBerserkHelp">Ļaut spēlētājiem apmainīt pusi laika pret iespēju gūt papildpunktu</string>
<string name="allowChatHelp">Ļaut spēlētājiem izmantot sarunu istabu</string>
<string name="arenaStreaks">Arēnas uzvaru sērija</string>
<string name="arenaStreaksHelp">Pēc 2 uzvarām, secīgās uzvaras piešķirs 4 punktus nevis 2.</string>
</resources>

View File

@ -1,2 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="newTeamBattle">အသင်းအလိုက် တိုက်ပွဲ အသစ်</string>
<string name="customStartDate">ပြိုင်ပွဲ စတင်ရန် စိတ်ကြိုက် သတ်မှတ်သည့် နေ့ရက်</string>
<string name="customStartDateHelp">သင့် ဒေသန္တရ အချိန်ဇုံ အရ။ ဤသတ်မှတ်ချက်က \"ပြိုင်ပွဲ မစမီ အချိန်ကာလ\" ဆက်တင်ကို လွှမ်းမိုးသည်</string>
<string name="allowBerserk">အသဲအသန် Berserk အချိန်တိုင်း ပုံစံ ခွင့်ပြုသည်</string>
<string name="allowBerserkHelp">ခွင့်ပြုချိန် တစ်ဝက် စွန့်လွှတ်ပါက အမှတ် တစ်မှတ် အပို ပေးမည်</string>
<string name="allowChatHelp">ကစားသမားတို့ ချက်ထ်ရူးမ် အတွင်း စကားပြောခွင့် ပေးသည်</string>
<string name="arenaStreaks">အရီနာပုံစံ ပြိုင်ပွဲ တွတ် သတ်မှတ်ချက်</string>
<string name="arenaStreaksHelp">နှစ်ပွဲနိုင်ပြီးနောက် ဆက်တိုက် နိုင်သည့် ပွဲတိုင်း ၂-မှတ် အစား ၄-မှတ် ရမည်။</string>
</resources>

View File

@ -37,4 +37,12 @@ Berserk gir ett ekstrapoeng bare om du spiller minst 7 trekk i partiet.</string>
<item quantity="one">Vis laget</item>
<item quantity="other">Vis alle %s lagene</item>
</plurals>
<string name="newTeamBattle">Ny lagkamp</string>
<string name="customStartDate">Egendefinert startdato</string>
<string name="customStartDateHelp">I tidssonen din. Dette overstyrer innstillingen «Tid før turnering starter»</string>
<string name="allowBerserk">Tillat berserk</string>
<string name="allowBerserkHelp">La spillerne halvere tiden sin på klokken for et ekstra poeng</string>
<string name="allowChatHelp">La spillerne diskutere i samtalerom</string>
<string name="arenaStreaks">Arenarekker</string>
<string name="arenaStreaksHelp">Etter 2 seiere gir påfølgende seiere 4 poeng i stedet for 2.</string>
</resources>

View File

@ -43,4 +43,12 @@ Speel snel en ga terug naar de toernooilobby om meer partijen te spelen en meer
<item quantity="one">Bekijk het team</item>
<item quantity="other">Bekijk alle %s teams</item>
</plurals>
<string name="newTeamBattle">Nieuwe wedstrijd</string>
<string name="customStartDate">Aangepaste startdatum</string>
<string name="customStartDateHelp">In je eigen lokale tijdzone. Dit overschrijft de instelling \"Tijd voordat het toernooi begint\"</string>
<string name="allowBerserk">Berserk toestaan</string>
<string name="allowBerserkHelp">Spelers kunnen met de helft van de tijd spelen om een extra punt te krijgen</string>
<string name="allowChatHelp">Spelers kunnen chatten in de chat room</string>
<string name="arenaStreaks">Arena streaks</string>
<string name="arenaStreaksHelp">Na 2 overwinningen geven opeenvolgende overwinningen 4 punten in plaats van 2.</string>
</resources>

View File

@ -37,4 +37,12 @@ Berserk gjev eitt ekstrapoeng berre om du spelar minst 7 trekk i partiet.</strin
<item quantity="one">Vis laget</item>
<item quantity="other">Vis alle %s lag</item>
</plurals>
<string name="newTeamBattle">Ny lagkamp</string>
<string name="customStartDate">Eigendefinert startdato</string>
<string name="customStartDateHelp">I din tidssone. Dette overstyrar innstillinga «Tid før turnering startar»</string>
<string name="allowBerserk">Tillat berserk</string>
<string name="allowBerserkHelp">Lat spelarane halvere tida si på klokka for eit ekstra poeng</string>
<string name="allowChatHelp">Lat spelarane diskutera i samtalerom</string>
<string name="arenaStreaks">Arena-sigersrekkjer</string>
<string name="arenaStreaksHelp">Etter to sigrar gjev seinara sigrar fire poeng i staden for to.</string>
</resources>

View File

@ -47,4 +47,12 @@ Graj szybko i wracaj do poczekalni, by rozegrać więcej partii i zdobyć więce
<item quantity="many">Zobacz wszystkie %s zespołów</item>
<item quantity="other">Zobacz wszystkie %s zespołów</item>
</plurals>
<string name="newTeamBattle">Nowa bitwa klubów</string>
<string name="customStartDate">Niestandardowa data rozpoczęcia</string>
<string name="customStartDateHelp">W Twojej lokalnej strefie czasowej. Nadpisuje ustawienie \"Czas przed rozpoczęciem turnieju\"</string>
<string name="allowBerserk">Zezwalaj na Berserk</string>
<string name="allowBerserkHelp">Pozwól graczom na zmniejszenie o połowę czasu pozwalając na zdobycie dodatkowego punktu</string>
<string name="allowChatHelp">Pozwól graczom dyskutować na czacie</string>
<string name="arenaStreaks">Serie aren</string>
<string name="arenaStreaksHelp">Po dwóch zwycięstwach, kolejne zwycięstwa dają 4 punkty zamiast 2.</string>
</resources>

View File

@ -42,4 +42,12 @@ Assim que terminar a sua partida, você voltará a aguardar o emparceiramento co
<item quantity="one">Ver a equipe</item>
<item quantity="other">Ver todas as %s equipes</item>
</plurals>
<string name="newTeamBattle">Nova batalha de equipes</string>
<string name="customStartDate">Mudar a data de início</string>
<string name="customStartDateHelp">No seu próprio fuso horário. Isso irá cancelar a configuração \"Contagem regressiva para início do torneio\"</string>
<string name="allowBerserk">Permitir Berserk</string>
<string name="allowBerserkHelp">Os jogadores poderão reduzir seu tempo pela metade para ganhar um ponto extra</string>
<string name="allowChatHelp">Permite que os jogadores discutam em uma sala de chat</string>
<string name="arenaStreaks">Sequências de vitória em arena</string>
<string name="arenaStreaksHelp">Após ganhar 2 partidas, vitórias consecutivas valem 4 pontos, ao invés de 2.</string>
</resources>

View File

@ -44,4 +44,12 @@ Joga rápido e volta ao vestíbulo para jogares mais jogos e ganhares mais ponto
<item quantity="one">Ver a equipa</item>
<item quantity="other">Ver todas as %s equipas</item>
</plurals>
<string name="newTeamBattle">Batalha de Equipas</string>
<string name="customStartDate">Data inicial personalizada</string>
<string name="customStartDateHelp">No seu próprio fuso horário local. Substitui a configuração \"Tempo antes do torneio começar\"</string>
<string name="allowBerserk">Permitir Berserk</string>
<string name="allowBerserkHelp">Permitir aos jogadores ganhar um ponto extra ao reduzir a duração do relógio a metade</string>
<string name="allowChatHelp">Permitir que os jogadores discutam numa sala de bate-papo</string>
<string name="arenaStreaks">Séries de Arena</string>
<string name="arenaStreaksHelp">Depois de 2 vitórias, vitórias consecutivas concedem 4 pontos em vez de 2.</string>
</resources>

View File

@ -45,4 +45,7 @@ Joacă rapid și întoarce-te la lobby pentru a juca mai multe meciuri și pentr
<item quantity="few">Vezi toate cele %s echipe</item>
<item quantity="other">Vezi toate cele %s de echipe</item>
</plurals>
<string name="customStartDate">Data de început personalizată</string>
<string name="allowBerserkHelp">Lăsați jucătorii să își înjumătățească timpul pentru a obține un punct în plus</string>
<string name="allowChatHelp">Permiteți jucătorilor să discute într-o cameră de chat</string>
</resources>

View File

@ -41,4 +41,12 @@
<item quantity="many">Посмотреть все %s клубов</item>
<item quantity="other">Посмотреть все %s клубов</item>
</plurals>
<string name="newTeamBattle">Новый межклубный турнир</string>
<string name="customStartDate">Особая дата начала</string>
<string name="customStartDateHelp">В вашем часовом поясе. Это переопределяет настройку «Время до начала турнира»</string>
<string name="allowBerserk">Разрешить Берсерк</string>
<string name="allowBerserkHelp">Разрешает игрокам получать дополнительные очки, если они уменьшат своё время наполовину</string>
<string name="allowChatHelp">Разрешить игрокам обсуждение в чате</string>
<string name="arenaStreaks">Серии Арены</string>
<string name="arenaStreaksHelp">После двух побед подряд каждая следующая победа даёт не 2 очка, а 4.</string>
</resources>

View File

@ -41,4 +41,12 @@ Igrajte hitro in se vrnite v čakalno vrsto v igralni dvorani, ter si tako zagot
<string name="shareUrl">Delite ta URL, da se igralci lahko pridružijo: %s</string>
<string name="drawStreak">Niz remijev: Ko igralec zeporedoma remizira v areni, bo le leprvi remi rezultiral v točko ali remiji z več kot %s potezami. Niz remijev se lahko prekine le z zmago, ne pa s porazom ali remijem.</string>
<string name="history">Zgodovina arene</string>
<string name="newTeamBattle">Nova skupinska bitka</string>
<string name="customStartDate">Začetni datum po meri</string>
<string name="customStartDateHelp">V svojem lokalnem časovnem pasu. To preglasi nastavitev \"Čas pred začetkom turnirja\"</string>
<string name="allowBerserk">Dovoli divjanje</string>
<string name="allowBerserkHelp">Naj igralci razpolovijo svoj čas ure, da pridobijo dodatno točko</string>
<string name="allowChatHelp">Naj igralci razpravljajo v klepetalnici</string>
<string name="arenaStreaks">Arena proge</string>
<string name="arenaStreaksHelp">Po 2 zaporednih zmagah nadaljne zmage namesto 2 dodelijo 4 točke.</string>
</resources>

View File

@ -42,4 +42,12 @@ Daha fazla oyun oynayıp daha çok puan kazanmak için hızlıca oynayın ve lob
<item quantity="one">Takımı görüntüleyin</item>
<item quantity="other">Tüm %s takımlarını görüntüleyin</item>
</plurals>
<string name="newTeamBattle">Yeni Takım Çarpışması</string>
<string name="customStartDate">Özel başlangıç tarihi</string>
<string name="customStartDateHelp">Kendi saat diliminizi kullanın. \"Turnuvaya kalan süre\" ayarı geçersiz kalacaktır</string>
<string name="allowBerserk">Divane moduna izin ver</string>
<string name="allowBerserkHelp">Ekstra puan kazanmak isteyen oyuncuların, sürelerinin yarısından feragat etmesine izin ver</string>
<string name="allowChatHelp">Oyuncuların sohbet odasında konuşmasına izin ver</string>
<string name="arenaStreaks">Arena serisi</string>
<string name="arenaStreaksHelp">2 kez üst üste kazandıktan sonra yapılan maçlar 2 yerine 4 puan verir.</string>
</resources>

View File

@ -46,4 +46,12 @@
<item quantity="many">Переглянути %s команд</item>
<item quantity="other">Переглянути %s команди</item>
</plurals>
<string name="newTeamBattle">Нова Командна битва</string>
<string name="customStartDate">Власна дата початку</string>
<string name="customStartDateHelp">У вашому локальному часовому поясі. Це замінює налаштування \"Час до початку турніру\"</string>
<string name="allowBerserk">Дозволити Берсерк</string>
<string name="allowBerserkHelp">Дозволити гравцям зменшити наполовину свій час, щоб отримати додатковий бал</string>
<string name="allowChatHelp">Дозволити гравцям обговорення в чаті</string>
<string name="arenaStreaks">Серії Арени</string>
<string name="arenaStreaksHelp">Після двох перемог, кожна наступна перемога принесе 4 бали замість 2.</string>
</resources>

View File

@ -36,8 +36,7 @@
</plurals>
<string name="thisIsPrivate">這是一個非公開的錦標賽</string>
<string name="shareUrl">分享這個網址讓其他人加入這場錦標賽:%s</string>
<string name="drawStreak">連續平手機制:當一位玩家連續在錦標賽中或的平局,只有第一場平局會獲得積分,或是在下超過%s步時才會獲得積分。
連續平手機制只會在獲勝或輸的時候被解除。</string>
<string name="drawStreak">連續平手機制:當一位玩家連續在錦標賽中或的平局,只有第一場平局會獲得積分,或是在下超過%s步時才會獲得積分。連續平手機制只會在獲勝或輸的時候被解除。</string>
<string name="history">先前的重要錦標賽</string>
<plurals name="viewAllXTeams" comment="viewAllXTeams [one] [other]&#10;&#10;%s is the number of teams that can be viewed">
<item quantity="other">查看所有 %s 團隊</item>

View File

@ -3,10 +3,14 @@
<string name="broadcasts">Трансляція</string>
<string name="liveBroadcasts">Онлайн трансляції турнірів</string>
<string name="newBroadcast">Нова трансляція наживо</string>
<string name="addRound">Додати раунд</string>
<string name="ongoing">Поточні</string>
<string name="upcoming">Майбутні</string>
<string name="completed">Завершені</string>
<string name="roundNumber">Номер раунда</string>
<string name="roundName">Назва раунду</string>
<string name="roundNumber">Номер раунду</string>
<string name="tournamentName">Назва турніру</string>
<string name="tournamentDescription">Короткий опис турніру</string>
<string name="fullDescription">Повний опис події</string>
<string name="fullDescriptionHelp">Необов\'язковий довгий опис трансляції. Наявна розмітка %1$s. Довжина має бути менша ніж %2$s символів.</string>
<string name="sourceUrlOrGameIds">URL джерела, або ідентифікатор гри</string>
@ -15,4 +19,9 @@
<string name="startDate">Дата початку у Вашому часовому поясі</string>
<string name="startDateHelp">Необов\'язково, якщо Ви знаєте, коли починається подія</string>
<string name="credits">Вдячність джерелу</string>
<string name="resetRound">Скинути цей раунд</string>
<string name="deleteRound">Видалити цей раунд</string>
<string name="broadcastUrl">Посилання на трансляцію</string>
<string name="currentRoundUrl">Посилання на поточний раунд</string>
<string name="currentGameUrl">Посилання на поточну гру</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="challenges">Usäfoderigä</string>
<string name="challengeToPlay">Zu nere partie useforde</string>
<string name="challengeToPlay">Zunärä Partii usäfordä</string>
<string name="challengeDeclined">Usäfoderig abglähnt</string>
<string name="challengeAccepted">Usäfoderig angnoo!</string>
<string name="challengeCanceled">Usäfoderig abbrochää.</string>
@ -13,11 +13,11 @@
<string name="xOnlyAcceptsChallengesFromFriends">%s nimmt Usäfoderigä nur vo Brudis a.</string>
<string name="declineGeneric">I nim momentan kei Usäfoderigä me a.</string>
<string name="declineLater">I nim momentan kei Usäfoderigä me a, bitte frog spöter nonemol.</string>
<string name="declineTooFast">Ziet zum überlege isch z\'churz für mi, bitte forderä mich nomel mit meh Bedenkzeit ussä.</string>
<string name="declineTooSlow">Ziet zum überlege isch z\'lang für mi, bitte forderä mich nomel mit weniger Bedenkzeit ussä.</string>
<string name="declineTooFast">Ziit zum Überlegä isch z\'churz für mich, bitte fordärä mich nomol mit meh Bedänkziit usä.</string>
<string name="declineTooSlow">Ziit zum Überlegä isch z\'lang für mich, bitte fordärä mich nomol mit wäniger Bedänkziit usä.</string>
<string name="declineTimeControl">I nim momentan kei Usäfoderigä me a.</string>
<string name="declineRated">Bitte forderä mi stattdessä zu enerä gewertetä Partie usä.</string>
<string name="declineCasual">Bitte fordere mich stattdessä zu einer ungewertetä Partie usä.</string>
<string name="declineRated">Bitte fordrää mich schtattdessä zunärä gwärtätä Partii usä.</string>
<string name="declineCasual">Bitte fordärä mich schtattdessä zunärä ungwärtätä Partii usä.</string>
<string name="declineStandard">I nimm momentan kei Usäforderigä für anderi Schpiilvariantä a.</string>
<string name="declineVariant">I bi im moment nöd parrat, diä Variantä zu spielä.</string>
<string name="declineNoBot">Ich nimm kei Usäfoderigä von Bots a.</string>

View File

@ -1,2 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="challenges">Defioj</string>
<string name="challengeToPlay">Defii al nova ludo</string>
<string name="challengeDeclined">Defio malakceptita</string>
<string name="challengeAccepted">Defio akceptita!</string>
<string name="challengeCanceled">Defio nuligita.</string>
<string name="youCannotChallengeX">Vi ne povas defii %s.</string>
<string name="xDoesNotAcceptChallenges">%s ne akceptas defiojn.</string>
<string name="xOnlyAcceptsChallengesFromFriends">%s nur akceptas defiojn de amikoj.</string>
<string name="declineGeneric">Mi ne akceptas defiojn en tiu ĉi momento.</string>
<string name="declineNoBot">Mi ne akceptas defiojn de robotoj.</string>
<string name="declineOnlyBot">Mi nur akceptas defiojn de robotoj.</string>
</resources>

View File

@ -8,5 +8,18 @@
<string name="registerToSendChallenges">אנא הירשמו על מנת לשלוח הזמנות למשחקים.</string>
<string name="youCannotChallengeX">לא ניתן להזמין את %s למשחק.</string>
<string name="xDoesNotAcceptChallenges">%s לא מקבל הזמנות למשחקים.</string>
<string name="yourXRatingIsTooFarFromY">הדירוג שלך %1$s רחוק מדי מ%2$s.</string>
<string name="cannotChallengeDueToProvisionalXRating">אין אפשרות לאתגר בגלל דירוג זמני של %s.</string>
<string name="xOnlyAcceptsChallengesFromFriends">%s מאשר רק אתגרים מחברים.</string>
<string name="declineGeneric">לא מקבל תלמידים בזמן זה.</string>
<string name="declineLater">זה לא הזמן המתאים עבורי, אנא שאל שוב מאוחר יותר.</string>
<string name="declineTooFast">הזמנים האלו יותר מדי מהירים בשבילי, בבקשה תאתגר שוב עם משחק יותר איטי.</string>
<string name="declineTooSlow">הזמנים האלו יותר מדי איטיים בשבילי, בבקשה תאתגר שוב עם משחק יותר מהיר.</string>
<string name="declineTimeControl">אני לא מאשר אתגרים עם בקרת זמנים.</string>
<string name="declineRated">בבקשה תשלח לי אתגר מדורג במקום.</string>
<string name="declineCasual">בבקשה תשלח לי אתגר לא מדורג במקום.</string>
<string name="declineStandard">אני לא מקבל אתגרים של גרסאות אחרות בזמן זה.</string>
<string name="declineVariant">אני לא מוכן לשחק את הגרסה האחרת הזאת עכשיו.</string>
<string name="declineNoBot">אני לא מאשר הזמנות מבוטים.</string>
<string name="declineOnlyBot">מאשר הזמנות למשחק רק מבוטים.</string>
</resources>

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